We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Doesn't catch invalid `rate(1 hours)`
*cfn-lint version: (`cfn-lint --version`)*

0.44.7

*Description of issue.*

cfn-lint doesn't recognize that this ScheduledExpression is invalid (should be `rate(1 hour)`)
```yaml
  ExampleRule:
    Type: AWS::Events::Rule
    Properties:
      Description: desc
      Name: name
      ScheduleExpression: rate(1 hours)
      State: ENABLED
```

But when building the cloudformation, I get the following error:

```
Parameter ScheduleExpression is not valid. (Service: AmazonCloudWatchEvents; Status Code: 400; Error Code: ValidationException; Request ID: ...; Proxy: null)
```

I saw #816, but since this is a `rate` issue, not a `cron` issue, I thought I should open a new ticket

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/cfnlint/rules/resources/events/RuleScheduleExpression.py`
Content:
```
1 """
2 Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 SPDX-License-Identifier: MIT-0
4 """
5 from cfnlint.rules import CloudFormationLintRule, RuleMatch
6 
7 
8 class RuleScheduleExpression(CloudFormationLintRule):
9     """Validate AWS Events Schedule expression format"""
10 
11     id = "E3027"
12     shortdesc = "Validate AWS Event ScheduleExpression format"
13     description = "Validate the formation of the AWS::Event ScheduleExpression"
14     source_url = "https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html"
15     tags = ["resources", "events"]
16 
17     def initialize(self, cfn):
18         """Initialize the rule"""
19         self.resource_property_types = ["AWS::Events::Rule"]
20 
21     def check_rate(self, value, path):
22         """Check Rate configuration"""
23         matches = []
24         # Extract the expression from rate(XXX)
25         rate_expression = value[value.find("(") + 1 : value.find(")")]
26 
27         if not rate_expression:
28             matches.append(
29                 RuleMatch(path, "Rate value of ScheduleExpression cannot be empty")
30             )
31         else:
32             # Rate format: rate(Value Unit)
33             items = rate_expression.split(" ")
34 
35             if len(items) != 2:
36                 message = "Rate expression must contain 2 elements (Value Unit), rate contains {} elements"
37                 matches.append(RuleMatch(path, message.format(len(items))))
38             else:
39                 # Check the Value
40                 if not items[0].isdigit():
41                     message = "Rate Value ({}) should be of type Integer."
42                     extra_args = {
43                         "actual_type": type(items[0]).__name__,
44                         "expected_type": int.__name__,
45                     }
46                     matches.append(
47                         RuleMatch(path, message.format(items[0]), **extra_args)
48                     )
49 
50         return matches
51 
52     def check_cron(self, value, path):
53         """Check Cron configuration"""
54         matches = []
55         # Extract the expression from cron(XXX)
56         cron_expression = value[value.find("(") + 1 : value.find(")")]
57 
58         if not cron_expression:
59             matches.append(
60                 RuleMatch(path, "Cron value of ScheduleExpression cannot be empty")
61             )
62         else:
63             # Rate format: cron(Minutes Hours Day-of-month Month Day-of-week Year)
64             items = cron_expression.split(" ")
65 
66             if len(items) != 6:
67                 message = "Cron expression must contain 6 elements (Minutes Hours Day-of-month Month Day-of-week Year), cron contains {} elements"
68                 matches.append(RuleMatch(path, message.format(len(items))))
69                 return matches
70 
71             _, _, day_of_month, _, day_of_week, _ = cron_expression.split(" ")
72             if day_of_month != "?" and day_of_week != "?":
73                 matches.append(
74                     RuleMatch(
75                         path,
76                         "Don't specify the Day-of-month and Day-of-week fields in the same cron expression",
77                     )
78                 )
79 
80         return matches
81 
82     def check_value(self, value, path):
83         """Count ScheduledExpression value"""
84         matches = []
85 
86         # Value is either "cron()" or "rate()"
87         if value.startswith("rate(") and value.endswith(")"):
88             matches.extend(self.check_rate(value, path))
89         elif value.startswith("cron(") and value.endswith(")"):
90             matches.extend(self.check_cron(value, path))
91         else:
92             message = "Invalid ScheduledExpression specified ({}). Value has to be either cron() or rate()"
93             matches.append(RuleMatch(path, message.format(value)))
94 
95         return matches
96 
97     def match_resource_properties(self, properties, _, path, cfn):
98         """Check CloudFormation Properties"""
99         matches = []
100 
101         matches.extend(
102             cfn.check_value(
103                 obj=properties,
104                 key="ScheduleExpression",
105                 path=path[:],
106                 check_value=self.check_value,
107             )
108         )
109 
110         return matches
111
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/cfnlint/rules/resources/events/RuleScheduleExpression.py b/src/cfnlint/rules/resources/events/RuleScheduleExpression.py
--- a/src/cfnlint/rules/resources/events/RuleScheduleExpression.py
+++ b/src/cfnlint/rules/resources/events/RuleScheduleExpression.py
@@ -25,29 +25,43 @@
         rate_expression = value[value.find("(") + 1 : value.find(")")]
 
         if not rate_expression:
-            matches.append(
-                RuleMatch(path, "Rate value of ScheduleExpression cannot be empty")
-            )
-        else:
-            # Rate format: rate(Value Unit)
-            items = rate_expression.split(" ")
-
-            if len(items) != 2:
-                message = "Rate expression must contain 2 elements (Value Unit), rate contains {} elements"
-                matches.append(RuleMatch(path, message.format(len(items))))
-            else:
-                # Check the Value
-                if not items[0].isdigit():
-                    message = "Rate Value ({}) should be of type Integer."
-                    extra_args = {
-                        "actual_type": type(items[0]).__name__,
-                        "expected_type": int.__name__,
-                    }
-                    matches.append(
-                        RuleMatch(path, message.format(items[0]), **extra_args)
-                    )
+            return [RuleMatch(path, "Rate value of ScheduleExpression cannot be empty")]
+
+        # Rate format: rate(Value Unit)
+        items = rate_expression.split(" ")
+
+        if len(items) != 2:
+            message = "Rate expression must contain 2 elements (Value Unit), rate contains {} elements"
+            matches.append(RuleMatch(path, message.format(len(items))))
+            return [RuleMatch(path, message.format(len(items)))]
+
+        # Check the Value
+        if not items[0].isdigit():
+            message = "Rate Value ({}) should be of type Integer."
+            extra_args = {
+                "actual_type": type(items[0]).__name__,
+                "expected_type": int.__name__,
+            }
+            return [RuleMatch(path, message.format(items[0]), **extra_args)]
+
+        if float(items[0]) <= 0:
+            return [
+                RuleMatch(path, f"Rate Value {items[0]!r} should be greater than 0.")
+            ]
+
+        if float(items[0]) <= 1:
+            valid_periods = ["minute", "hour", "day"]
+        elif float(items[0]) > 1:
+            valid_periods = ["minutes", "hours", "days"]
+        # Check the Unit
+        if items[1] not in valid_periods:
+            return [
+                RuleMatch(
+                    path, f"Rate Unit {items[1]!r} should be one of {valid_periods!r}."
+                )
+            ]
 
-        return matches
+        return []
 
     def check_cron(self, value, path):
         """Check Cron configuration"""

{"golden_diff": "diff --git a/src/cfnlint/rules/resources/events/RuleScheduleExpression.py b/src/cfnlint/rules/resources/events/RuleScheduleExpression.py\n--- a/src/cfnlint/rules/resources/events/RuleScheduleExpression.py\n+++ b/src/cfnlint/rules/resources/events/RuleScheduleExpression.py\n@@ -25,29 +25,43 @@\n         rate_expression = value[value.find(\"(\") + 1 : value.find(\")\")]\n \n         if not rate_expression:\n-            matches.append(\n-                RuleMatch(path, \"Rate value of ScheduleExpression cannot be empty\")\n-            )\n-        else:\n-            # Rate format: rate(Value Unit)\n-            items = rate_expression.split(\" \")\n-\n-            if len(items) != 2:\n-                message = \"Rate expression must contain 2 elements (Value Unit), rate contains {} elements\"\n-                matches.append(RuleMatch(path, message.format(len(items))))\n-            else:\n-                # Check the Value\n-                if not items[0].isdigit():\n-                    message = \"Rate Value ({}) should be of type Integer.\"\n-                    extra_args = {\n-                        \"actual_type\": type(items[0]).__name__,\n-                        \"expected_type\": int.__name__,\n-                    }\n-                    matches.append(\n-                        RuleMatch(path, message.format(items[0]), **extra_args)\n-                    )\n+            return [RuleMatch(path, \"Rate value of ScheduleExpression cannot be empty\")]\n+\n+        # Rate format: rate(Value Unit)\n+        items = rate_expression.split(\" \")\n+\n+        if len(items) != 2:\n+            message = \"Rate expression must contain 2 elements (Value Unit), rate contains {} elements\"\n+            matches.append(RuleMatch(path, message.format(len(items))))\n+            return [RuleMatch(path, message.format(len(items)))]\n+\n+        # Check the Value\n+        if not items[0].isdigit():\n+            message = \"Rate Value ({}) should be of type Integer.\"\n+            extra_args = {\n+                \"actual_type\": type(items[0]).__name__,\n+                \"expected_type\": int.__name__,\n+            }\n+            return [RuleMatch(path, message.format(items[0]), **extra_args)]\n+\n+        if float(items[0]) <= 0:\n+            return [\n+                RuleMatch(path, f\"Rate Value {items[0]!r} should be greater than 0.\")\n+            ]\n+\n+        if float(items[0]) <= 1:\n+            valid_periods = [\"minute\", \"hour\", \"day\"]\n+        elif float(items[0]) > 1:\n+            valid_periods = [\"minutes\", \"hours\", \"days\"]\n+        # Check the Unit\n+        if items[1] not in valid_periods:\n+            return [\n+                RuleMatch(\n+                    path, f\"Rate Unit {items[1]!r} should be one of {valid_periods!r}.\"\n+                )\n+            ]\n \n-        return matches\n+        return []\n \n     def check_cron(self, value, path):\n         \"\"\"Check Cron configuration\"\"\"\n", "issue": "Doesn't catch invalid `rate(1 hours)`\n*cfn-lint version: (`cfn-lint --version`)*\r\n\r\n0.44.7\r\n\r\n*Description of issue.*\r\n\r\ncfn-lint doesn't recognize that this ScheduledExpression is invalid (should be `rate(1 hour)`)\r\n```yaml\r\n  ExampleRule:\r\n    Type: AWS::Events::Rule\r\n    Properties:\r\n      Description: desc\r\n      Name: name\r\n      ScheduleExpression: rate(1 hours)\r\n      State: ENABLED\r\n```\r\n\r\nBut when building the cloudformation, I get the following error:\r\n\r\n```\r\nParameter ScheduleExpression is not valid. (Service: AmazonCloudWatchEvents; Status Code: 400; Error Code: ValidationException; Request ID: ...; Proxy: null)\r\n```\r\n\r\nI saw #816, but since this is a `rate` issue, not a `cron` issue, I thought I should open a new ticket\n", "before_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nfrom cfnlint.rules import CloudFormationLintRule, RuleMatch\n\n\nclass RuleScheduleExpression(CloudFormationLintRule):\n    \"\"\"Validate AWS Events Schedule expression format\"\"\"\n\n    id = \"E3027\"\n    shortdesc = \"Validate AWS Event ScheduleExpression format\"\n    description = \"Validate the formation of the AWS::Event ScheduleExpression\"\n    source_url = \"https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html\"\n    tags = [\"resources\", \"events\"]\n\n    def initialize(self, cfn):\n        \"\"\"Initialize the rule\"\"\"\n        self.resource_property_types = [\"AWS::Events::Rule\"]\n\n    def check_rate(self, value, path):\n        \"\"\"Check Rate configuration\"\"\"\n        matches = []\n        # Extract the expression from rate(XXX)\n        rate_expression = value[value.find(\"(\") + 1 : value.find(\")\")]\n\n        if not rate_expression:\n            matches.append(\n                RuleMatch(path, \"Rate value of ScheduleExpression cannot be empty\")\n            )\n        else:\n            # Rate format: rate(Value Unit)\n            items = rate_expression.split(\" \")\n\n            if len(items) != 2:\n                message = \"Rate expression must contain 2 elements (Value Unit), rate contains {} elements\"\n                matches.append(RuleMatch(path, message.format(len(items))))\n            else:\n                # Check the Value\n                if not items[0].isdigit():\n                    message = \"Rate Value ({}) should be of type Integer.\"\n                    extra_args = {\n                        \"actual_type\": type(items[0]).__name__,\n                        \"expected_type\": int.__name__,\n                    }\n                    matches.append(\n                        RuleMatch(path, message.format(items[0]), **extra_args)\n                    )\n\n        return matches\n\n    def check_cron(self, value, path):\n        \"\"\"Check Cron configuration\"\"\"\n        matches = []\n        # Extract the expression from cron(XXX)\n        cron_expression = value[value.find(\"(\") + 1 : value.find(\")\")]\n\n        if not cron_expression:\n            matches.append(\n                RuleMatch(path, \"Cron value of ScheduleExpression cannot be empty\")\n            )\n        else:\n            # Rate format: cron(Minutes Hours Day-of-month Month Day-of-week Year)\n            items = cron_expression.split(\" \")\n\n            if len(items) != 6:\n                message = \"Cron expression must contain 6 elements (Minutes Hours Day-of-month Month Day-of-week Year), cron contains {} elements\"\n                matches.append(RuleMatch(path, message.format(len(items))))\n                return matches\n\n            _, _, day_of_month, _, day_of_week, _ = cron_expression.split(\" \")\n            if day_of_month != \"?\" and day_of_week != \"?\":\n                matches.append(\n                    RuleMatch(\n                        path,\n                        \"Don't specify the Day-of-month and Day-of-week fields in the same cron expression\",\n                    )\n                )\n\n        return matches\n\n    def check_value(self, value, path):\n        \"\"\"Count ScheduledExpression value\"\"\"\n        matches = []\n\n        # Value is either \"cron()\" or \"rate()\"\n        if value.startswith(\"rate(\") and value.endswith(\")\"):\n            matches.extend(self.check_rate(value, path))\n        elif value.startswith(\"cron(\") and value.endswith(\")\"):\n            matches.extend(self.check_cron(value, path))\n        else:\n            message = \"Invalid ScheduledExpression specified ({}). Value has to be either cron() or rate()\"\n            matches.append(RuleMatch(path, message.format(value)))\n\n        return matches\n\n    def match_resource_properties(self, properties, _, path, cfn):\n        \"\"\"Check CloudFormation Properties\"\"\"\n        matches = []\n\n        matches.extend(\n            cfn.check_value(\n                obj=properties,\n                key=\"ScheduleExpression\",\n                path=path[:],\n                check_value=self.check_value,\n            )\n        )\n\n        return matches\n", "path": "src/cfnlint/rules/resources/events/RuleScheduleExpression.py"}], "after_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nfrom cfnlint.rules import CloudFormationLintRule, RuleMatch\n\n\nclass RuleScheduleExpression(CloudFormationLintRule):\n    \"\"\"Validate AWS Events Schedule expression format\"\"\"\n\n    id = \"E3027\"\n    shortdesc = \"Validate AWS Event ScheduleExpression format\"\n    description = \"Validate the formation of the AWS::Event ScheduleExpression\"\n    source_url = \"https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html\"\n    tags = [\"resources\", \"events\"]\n\n    def initialize(self, cfn):\n        \"\"\"Initialize the rule\"\"\"\n        self.resource_property_types = [\"AWS::Events::Rule\"]\n\n    def check_rate(self, value, path):\n        \"\"\"Check Rate configuration\"\"\"\n        matches = []\n        # Extract the expression from rate(XXX)\n        rate_expression = value[value.find(\"(\") + 1 : value.find(\")\")]\n\n        if not rate_expression:\n            return [RuleMatch(path, \"Rate value of ScheduleExpression cannot be empty\")]\n\n        # Rate format: rate(Value Unit)\n        items = rate_expression.split(\" \")\n\n        if len(items) != 2:\n            message = \"Rate expression must contain 2 elements (Value Unit), rate contains {} elements\"\n            matches.append(RuleMatch(path, message.format(len(items))))\n            return [RuleMatch(path, message.format(len(items)))]\n\n        # Check the Value\n        if not items[0].isdigit():\n            message = \"Rate Value ({}) should be of type Integer.\"\n            extra_args = {\n                \"actual_type\": type(items[0]).__name__,\n                \"expected_type\": int.__name__,\n            }\n            return [RuleMatch(path, message.format(items[0]), **extra_args)]\n\n        if float(items[0]) <= 0:\n            return [\n                RuleMatch(path, f\"Rate Value {items[0]!r} should be greater than 0.\")\n            ]\n\n        if float(items[0]) <= 1:\n            valid_periods = [\"minute\", \"hour\", \"day\"]\n        elif float(items[0]) > 1:\n            valid_periods = [\"minutes\", \"hours\", \"days\"]\n        # Check the Unit\n        if items[1] not in valid_periods:\n            return [\n                RuleMatch(\n                    path, f\"Rate Unit {items[1]!r} should be one of {valid_periods!r}.\"\n                )\n            ]\n\n        return []\n\n    def check_cron(self, value, path):\n        \"\"\"Check Cron configuration\"\"\"\n        matches = []\n        # Extract the expression from cron(XXX)\n        cron_expression = value[value.find(\"(\") + 1 : value.find(\")\")]\n\n        if not cron_expression:\n            matches.append(\n                RuleMatch(path, \"Cron value of ScheduleExpression cannot be empty\")\n            )\n        else:\n            # Rate format: cron(Minutes Hours Day-of-month Month Day-of-week Year)\n            items = cron_expression.split(\" \")\n\n            if len(items) != 6:\n                message = \"Cron expression must contain 6 elements (Minutes Hours Day-of-month Month Day-of-week Year), cron contains {} elements\"\n                matches.append(RuleMatch(path, message.format(len(items))))\n                return matches\n\n            _, _, day_of_month, _, day_of_week, _ = cron_expression.split(\" \")\n            if day_of_month != \"?\" and day_of_week != \"?\":\n                matches.append(\n                    RuleMatch(\n                        path,\n                        \"Don't specify the Day-of-month and Day-of-week fields in the same cron expression\",\n                    )\n                )\n\n        return matches\n\n    def check_value(self, value, path):\n        \"\"\"Count ScheduledExpression value\"\"\"\n        matches = []\n\n        # Value is either \"cron()\" or \"rate()\"\n        if value.startswith(\"rate(\") and value.endswith(\")\"):\n            matches.extend(self.check_rate(value, path))\n        elif value.startswith(\"cron(\") and value.endswith(\")\"):\n            matches.extend(self.check_cron(value, path))\n        else:\n            message = \"Invalid ScheduledExpression specified ({}). Value has to be either cron() or rate()\"\n            matches.append(RuleMatch(path, message.format(value)))\n\n        return matches\n\n    def match_resource_properties(self, properties, _, path, cfn):\n        \"\"\"Check CloudFormation Properties\"\"\"\n        matches = []\n\n        matches.extend(\n            cfn.check_value(\n                obj=properties,\n                key=\"ScheduleExpression\",\n                path=path[:],\n                check_value=self.check_value,\n            )\n        )\n\n        return matches\n", "path": "src/cfnlint/rules/resources/events/RuleScheduleExpression.py"}]}