aws-eventbridge-cron 0.1.2.1 → 0.2.0.0
raw patch · 8 files changed
+111/−106 lines, 8 filesdep ~containersdep ~megaparsecdep ~textPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: containers, megaparsec, text, time, tz, tzdata
API changes (from Hackage documentation)
- AWS.EventBridge.Cron: nextRunTimes :: CronExprT -> UTCTime -> Int -> Either String [UTCTime]
+ AWS.EventBridge.Cron: nextRunTimes :: UTCTime -> Int -> CronExprT -> Either String [UTCTime]
- AWS.EventBridge.Schedule: nextRunTimesLocal :: Schedule -> LocalTime -> Int -> Either String [LocalTime]
+ AWS.EventBridge.Schedule: nextRunTimesLocal :: LocalTime -> Int -> Schedule -> Either String [LocalTime]
- AWS.EventBridge.Schedule: nextRunTimesLocalFromUTC :: Schedule -> UTCTime -> Int -> Either String [LocalTime]
+ AWS.EventBridge.Schedule: nextRunTimesLocalFromUTC :: UTCTime -> Int -> Schedule -> Either String [LocalTime]
- AWS.EventBridge.Schedule: nextRunTimesLocalFromZoned :: Schedule -> ZonedTime -> Int -> Either String [LocalTime]
+ AWS.EventBridge.Schedule: nextRunTimesLocalFromZoned :: ZonedTime -> Int -> Schedule -> Either String [LocalTime]
- AWS.EventBridge.Schedule: nextRunTimesUTC :: Schedule -> UTCTime -> Int -> Either String [UTCTime]
+ AWS.EventBridge.Schedule: nextRunTimesUTC :: UTCTime -> Int -> Schedule -> Either String [UTCTime]
- AWS.EventBridge.Schedule: nextRunTimesUTCFromLocal :: Schedule -> LocalTime -> Int -> Either String [UTCTime]
+ AWS.EventBridge.Schedule: nextRunTimesUTCFromLocal :: LocalTime -> Int -> Schedule -> Either String [UTCTime]
- AWS.EventBridge.Schedule: nextRunTimesUTCFromZoned :: Schedule -> ZonedTime -> Int -> Either String [UTCTime]
+ AWS.EventBridge.Schedule: nextRunTimesUTCFromZoned :: ZonedTime -> Int -> Schedule -> Either String [UTCTime]
- AWS.EventBridge.Schedule: nextRunTimesZoned :: Schedule -> ZonedTime -> Int -> Either String [ZonedTime]
+ AWS.EventBridge.Schedule: nextRunTimesZoned :: ZonedTime -> Int -> Schedule -> Either String [ZonedTime]
- AWS.EventBridge.Schedule: nextRunTimesZonedFromLocal :: Schedule -> LocalTime -> Int -> Either String [ZonedTime]
+ AWS.EventBridge.Schedule: nextRunTimesZonedFromLocal :: LocalTime -> Int -> Schedule -> Either String [ZonedTime]
- AWS.EventBridge.Schedule: nextRunTimesZonedFromUTC :: Schedule -> UTCTime -> Int -> Either String [ZonedTime]
+ AWS.EventBridge.Schedule: nextRunTimesZonedFromUTC :: UTCTime -> Int -> Schedule -> Either String [ZonedTime]
Files
- CHANGELOG.md +6/−0
- README.md +12/−15
- aws-eventbridge-cron.cabal +17/−17
- bench/Main.hs +2/−2
- src/AWS/EventBridge/Cron.hs +11/−11
- src/AWS/EventBridge/Schedule.hs +40/−38
- test/AWS/EventBridge/CronSpec.hs +13/−13
- test/AWS/EventBridge/ScheduleSpec.hs +10/−10
CHANGELOG.md view
@@ -5,6 +5,12 @@ The format is based on Keep a Changelog and this project adheres to Semantic Versioning. +## [0.2.0.0] - 2025-11-22+### Changed+- Reordered `nextRunTimes` and all schedule helper signatures to accept `(base -> limit -> schedule/expr)` so callers can compose with `>>=`/`fmap` without lambdas. This is a breaking change for all call sites, including the exported zone-aware helpers and benchmark functions.+### Fixed+- README, Haddocks, tests, and benchmarks now show the new argument order to prevent confusion when following examples.+ ## [0.1.2.1] - 2025-11-22 ### Added - `Show` instance for `Schedule` to facilitate REPL debugging (displays the expression and timezone label).
README.md view
@@ -34,7 +34,7 @@ ```cabal build-depends:- aws-eventbridge-cron >= 0.1 && < 0.2+ aws-eventbridge-cron >= 0.2 && < 0.3 ``` ## Quick Start@@ -48,9 +48,8 @@ base = UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 0 0)) example :: Either String [UTCTime]-example = do- expr <- parseCronText "cron(0/15 9 ? NOV SUN 2025)"- nextRunTimes expr base 4+example =+ parseCronText "cron(0/15 9 ? NOV SUN 2025)" >>= nextRunTimes base 4 -- Right [2025-11-16 09:00:00 UTC, 2025-11-16 09:15:00 UTC, ...] ``` @@ -58,14 +57,12 @@ ```haskell rateExample :: Either String [UTCTime]-rateExample = do- expr <- parseCronText "rate(10 minutes)"- nextRunTimes expr base 3+rateExample =+ parseCronText "rate(10 minutes)" >>= nextRunTimes base 3 atExample :: Either String [UTCTime]-atExample = do- expr <- parseCronText "at(2025-11-16T09:30:00)"- nextRunTimes expr base 5+atExample =+ parseCronText "at(2025-11-16T09:30:00)" >>= nextRunTimes base 5 -- Introspect the parsed expression without re-parsing downstream. kindExample :: Either String ScheduleKind@@ -95,15 +92,15 @@ zonedSchedule = scheduleFromText America__New_York "cron(0 9 * NOV ? 2025)" utcRuns :: Either String [UTCTime]-utcRuns = nextRunTimesUTC <$> zonedSchedule <*> pure baseUTC <*> pure 2+utcRuns = zonedSchedule >>= nextRunTimesUTC baseUTC 2 -- Right [2025-11-01 13:00:00 UTC,2025-11-02 14:00:00 UTC] localRuns :: Either String [LocalTime]-localRuns = nextRunTimesLocalFromUTC <$> zonedSchedule <*> pure baseUTC <*> pure 2+localRuns = zonedSchedule >>= nextRunTimesLocalFromUTC baseUTC 2 -- Right [2025-11-01 09:00:00,2025-11-02 09:00:00] zonedRuns :: Either String [ZonedTime]-zonedRuns = nextRunTimesZonedFromUTC <$> zonedSchedule <*> pure baseUTC <*> pure 2+zonedRuns = zonedSchedule >>= nextRunTimesZonedFromUTC baseUTC 2 -- Right [2025-11-01 09:00:00 EDT,2025-11-02 09:00:00 EST] ``` @@ -174,7 +171,7 @@ example :: Either String [LocalTime] example = do sched <- mkScheduleFromConfig "Asia/Kolkata" "cron(0 9 * * ? *)"- nextRunTimesLocal sched (read "2025-11-15 09:00:00" :: LocalTime) 2+ nextRunTimesLocal (read "2025-11-15 09:00:00" :: LocalTime) 2 sched ``` </details>@@ -196,7 +193,7 @@ fromApi :: Either String [UTCTime] fromApi = do sched <- resolveSchedule (Just "America/Los_Angeles") "cron(0 9 * * ? *)"- nextRunTimesUTC sched (read "2025-11-01 17:00:00 UTC" :: UTCTime) 1+ nextRunTimesUTC (read "2025-11-01 17:00:00 UTC" :: UTCTime) 1 sched ``` </details>
aws-eventbridge-cron.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0 name: aws-eventbridge-cron-version: 0.1.2.1+version: 0.2.0.0 synopsis: AWS EventBridge cron, rate, and one-time parser with scheduler description: Parse AWS EventBridge cron, rate, and one-time expressions and compute@@ -20,7 +20,7 @@ license: BSD-3-Clause license-file: LICENSE build-type: Simple-tested-with: GHC == 9.6.* || == 9.8.* || == 9.10.* || == 9.12.*+tested-with: GHC == 9.6.7 || == 9.8.4 || == 9.10.3 || == 9.12.2 extra-doc-files: README.md@@ -50,12 +50,12 @@ -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints build-depends: base >=4.14 && <5- , containers- , megaparsec- , text- , time- , tz- , tzdata+ , containers >=0.6.7 && <0.9+ , megaparsec >=9.0 && <10+ , text >=2.0.2 && <2.2+ , time >=1.12.2 && <1.17+ , tz >=0.1.3 && <0.1.4+ , tzdata >=0.2.20250115 && <0.3 test-suite aws-eventbridge-cron-test type: exitcode-stdio-1.0@@ -90,16 +90,16 @@ build-depends: aws-eventbridge-cron , base >=4.14 && <5- , containers- , megaparsec+ , containers >=0.6.7 && <0.9+ , megaparsec >=9.0 && <10 , QuickCheck >=2.14 && <2.16 , tasty >=1.4 && <1.6 , tasty-hunit >=0.10 && <0.11 , tasty-quickcheck >=0.10 && <0.11- , text- , time- , tz- , tzdata+ , text >=2.0.2 && <2.2+ , time >=1.12.2 && <1.17+ , tz >=0.1.3 && <0.1.4+ , tzdata >=0.2.20250115 && <0.3 benchmark aws-eventbridge-cron-bench type: exitcode-stdio-1.0@@ -112,6 +112,6 @@ aws-eventbridge-cron , base >=4.14 && <5 , criterion >=1.5 && <1.7- , text- , time- , tz+ , text >=2.0.2 && <2.2+ , time >=1.12.2 && <1.17+ , tz >=0.1.3 && <0.1.4
bench/Main.hs view
@@ -29,11 +29,11 @@ runCron :: CronExprT -> Int -> [UTCTime] runCron expr limit =- forceRight "nextRunTimes" $ nextRunTimes expr cronBase limit+ forceRight "nextRunTimes" $ nextRunTimes cronBase limit expr runSchedule :: Schedule -> Int -> [LocalTime] runSchedule sched limit =- forceRight "nextRunTimesLocal" $ nextRunTimesLocal sched scheduleBase limit+ forceRight "nextRunTimesLocal" $ nextRunTimesLocal scheduleBase limit sched cronBase :: UTCTime cronBase = UTCTime (fromGregorian 2025 1 1) (secondsToDiffTime (5 * 3600))
src/AWS/EventBridge/Cron.hs view
@@ -115,29 +115,29 @@ -- The list always includes occurrences at or after the base time, limited to the -- requested count. Errors bubble up if the expression cannot produce valid timestamps -- (for example conflicting day-of-month/day-of-week fields).-nextRunTimes :: CronExprT -> UTCTime -> Int -> Either String [UTCTime]-nextRunTimes expr base limit =+nextRunTimes :: UTCTime -> Int -> CronExprT -> Either String [UTCTime]+nextRunTimes base limit expr = case expr of- RateExpr r -> futureRateTimes r base limit- OneTimeExpr o -> futureOneTime o base limit- CronExpr m h dom mon dow yr -> futureCronTimes m h dom mon dow yr base limit+ RateExpr r -> futureRateTimes base limit r+ OneTimeExpr o -> futureOneTime base limit o+ CronExpr m h dom mon dow yr -> futureCronTimes limit m h dom mon dow yr base -futureOneTime :: OneTimeExprT -> UTCTime -> Int -> Either String [UTCTime]-futureOneTime expr base limit+futureOneTime :: UTCTime -> Int -> OneTimeExprT -> Either String [UTCTime]+futureOneTime base limit expr | t >= base && limit > 0 = Right [t] | otherwise = Right [] where t = evaluateOneTimeT expr -futureRateTimes :: RateExprT -> UTCTime -> Int -> Either String [UTCTime]-futureRateTimes expr base limit = case evaluateRateT expr of+futureRateTimes :: UTCTime -> Int -> RateExprT -> Either String [UTCTime]+futureRateTimes base limit expr = case evaluateRateT expr of Left err -> Left err Right delta -> Right $ take limit $ iterate (addUTCTime delta) base -futureCronTimes :: MinutesExprT -> HoursExprT -> DayOfMonthExprT -> MonthsExprT -> DayOfWeekExprT -> YearsExprT -> UTCTime -> Int -> Either String [UTCTime]-futureCronTimes minExpr hourExpr domExpr monExpr dowExpr yrExpr base limit+futureCronTimes :: Int -> MinutesExprT -> HoursExprT -> DayOfMonthExprT -> MonthsExprT -> DayOfWeekExprT -> YearsExprT -> UTCTime -> Either String [UTCTime]+futureCronTimes limit minExpr hourExpr domExpr monExpr dowExpr yrExpr base | domIsQuestion == dowIsQuestion = Left "day-of-month and day-of-week fields must use '?' in exactly one position" | otherwise = do minutes <- evaluateMinuteT minExpr
src/AWS/EventBridge/Schedule.hs view
@@ -127,7 +127,7 @@ -- let base = read "2025-11-16 03:30:00 UTC" :: UTCTime -- in case scheduleFromText Asia__Kolkata "cron(0 9 ? NOV SUN 2025)" of -- Left err -> Left err--- Right sched -> nextRunTimesUTC sched base 1+-- Right sched -> nextRunTimesUTC base 1 sched -- :} -- Right [2025-11-16 03:30:00 UTC] scheduleFromText :: TZLabel -> Text -> Either String Schedule@@ -139,7 +139,7 @@ -- API payloads or configuration files that keep the canonical string form. -- -- >>> let base = read "2025-11-16 03:30:00 UTC" :: UTCTime--- >>> scheduleFromTextIANA "Asia/Kolkata" "cron(0 9 ? NOV SUN 2025)" >>= \sched -> nextRunTimesUTC sched base 1+-- >>> scheduleFromTextIANA "Asia/Kolkata" "cron(0 9 ? NOV SUN 2025)" >>= \sched -> nextRunTimesUTC base 1 sched -- Right [2025-11-16 03:30:00 UTC] scheduleFromTextIANA :: Text -> Text -> Either String Schedule scheduleFromTextIANA tzName input =@@ -151,7 +151,7 @@ -- | Alias for 'scheduleFromTextIANA'. ----- >>> parseCronTextWithIANA "America/New_York" "cron(0 9 * * ? *)" >>= \sched -> nextRunTimesLocal sched (read "2025-11-01 08:30:00" :: LocalTime) 1+-- >>> parseCronTextWithIANA "America/New_York" "cron(0 9 * * ? *)" >>= \sched -> nextRunTimesLocal (read "2025-11-01 08:30:00" :: LocalTime) 1 sched -- Right [2025-11-01 09:00:00] parseCronTextWithIANA :: Text -> Text -> Either String Schedule parseCronTextWithIANA = scheduleFromTextIANA@@ -164,10 +164,10 @@ -- -- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)" -- >>> let base = read "2025-11-01 08:00:00" :: LocalTime--- >>> nextRunTimesLocal sched base 1+-- >>> nextRunTimesLocal base 1 sched -- Right [2025-11-01 09:00:00]-nextRunTimesLocal :: Schedule -> LocalTime -> Int -> Either String [LocalTime]-nextRunTimesLocal = scheduleLocalOccurrences+nextRunTimesLocal :: LocalTime -> Int -> Schedule -> Either String [LocalTime]+nextRunTimesLocal base limit schedule = scheduleLocalOccurrences schedule base limit -- | Conversion helper. --@@ -177,10 +177,11 @@ -- -- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)" -- >>> let base = read "2025-11-01 12:00:00 UTC" :: UTCTime--- >>> nextRunTimesLocalFromUTC sched base 1+-- >>> nextRunTimesLocalFromUTC base 1 sched -- Right [2025-11-01 09:00:00]-nextRunTimesLocalFromUTC :: Schedule -> UTCTime -> Int -> Either String [LocalTime]-nextRunTimesLocalFromUTC schedule base = scheduleLocalOccurrences schedule (utcToLocalTimeTZ (scheduleZone schedule) base)+nextRunTimesLocalFromUTC :: UTCTime -> Int -> Schedule -> Either String [LocalTime]+nextRunTimesLocalFromUTC base limit schedule =+ scheduleLocalOccurrences schedule (utcToLocalTimeTZ (scheduleZone schedule) base) limit -- | Conversion helper. --@@ -191,10 +192,11 @@ -- -- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)" -- >>> let base = read "2025-11-01 09:00:00-04:00" :: ZonedTime--- >>> nextRunTimesLocalFromZoned sched base 2+-- >>> nextRunTimesLocalFromZoned base 2 sched -- Right [2025-11-01 09:00:00,2025-11-02 09:00:00]-nextRunTimesLocalFromZoned :: Schedule -> ZonedTime -> Int -> Either String [LocalTime]-nextRunTimesLocalFromZoned schedule base = scheduleLocalOccurrences schedule (utcToLocalTimeTZ (scheduleZone schedule) (zonedTimeToUTC base))+nextRunTimesLocalFromZoned :: ZonedTime -> Int -> Schedule -> Either String [LocalTime]+nextRunTimesLocalFromZoned base limit schedule =+ scheduleLocalOccurrences schedule (utcToLocalTimeTZ (scheduleZone schedule) (zonedTimeToUTC base)) limit -- | UTC primary helper. --@@ -203,11 +205,11 @@ -- -- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)" -- >>> let base = read "2025-11-01 12:30:00 UTC" :: UTCTime--- >>> nextRunTimesUTC sched base 1+-- >>> nextRunTimesUTC base 1 sched -- Right [2025-11-01 13:00:00 UTC]-nextRunTimesUTC :: Schedule -> UTCTime -> Int -> Either String [UTCTime]-nextRunTimesUTC schedule base limit =- fmap (map (localToUTC schedule)) (nextRunTimesLocalFromUTC schedule base limit)+nextRunTimesUTC :: UTCTime -> Int -> Schedule -> Either String [UTCTime]+nextRunTimesUTC base limit schedule =+ fmap (map (localToUTC schedule)) (nextRunTimesLocalFromUTC base limit schedule) -- | Conversion helper. --@@ -216,11 +218,11 @@ -- -- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)" -- >>> let base = read "2025-11-01 08:00:00" :: LocalTime--- >>> nextRunTimesUTCFromLocal sched base 1+-- >>> nextRunTimesUTCFromLocal base 1 sched -- Right [2025-11-01 13:00:00 UTC]-nextRunTimesUTCFromLocal :: Schedule -> LocalTime -> Int -> Either String [UTCTime]-nextRunTimesUTCFromLocal schedule base limit =- fmap (map (localToUTC schedule)) (nextRunTimesLocal schedule base limit)+nextRunTimesUTCFromLocal :: LocalTime -> Int -> Schedule -> Either String [UTCTime]+nextRunTimesUTCFromLocal base limit schedule =+ fmap (map (localToUTC schedule)) (nextRunTimesLocal base limit schedule) -- | Conversion helper. --@@ -230,11 +232,11 @@ -- -- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)" -- >>> let base = read "2025-11-01 09:00:00-04:00" :: ZonedTime--- >>> nextRunTimesUTCFromZoned sched base 1+-- >>> nextRunTimesUTCFromZoned base 1 sched -- Right [2025-11-01 13:00:00 UTC]-nextRunTimesUTCFromZoned :: Schedule -> ZonedTime -> Int -> Either String [UTCTime]-nextRunTimesUTCFromZoned schedule base limit =- fmap (map (localToUTC schedule)) (nextRunTimesLocalFromZoned schedule base limit)+nextRunTimesUTCFromZoned :: ZonedTime -> Int -> Schedule -> Either String [UTCTime]+nextRunTimesUTCFromZoned base limit schedule =+ fmap (map (localToUTC schedule)) (nextRunTimesLocalFromZoned base limit schedule) -- | Zoned-time primary helper. --@@ -245,11 +247,11 @@ -- -- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)" -- >>> let base = read "2025-11-01 09:00:00-04:00" :: ZonedTime--- >>> nextRunTimesZoned sched base 2+-- >>> nextRunTimesZoned base 2 sched -- Right [2025-11-01 09:00:00-04:00,2025-11-02 09:00:00-05:00]-nextRunTimesZoned :: Schedule -> ZonedTime -> Int -> Either String [ZonedTime]-nextRunTimesZoned schedule base limit =- fmap (map (localToZoned schedule)) (nextRunTimesLocalFromZoned schedule base limit)+nextRunTimesZoned :: ZonedTime -> Int -> Schedule -> Either String [ZonedTime]+nextRunTimesZoned base limit schedule =+ fmap (map (localToZoned schedule)) (nextRunTimesLocalFromZoned base limit schedule) -- | Conversion helper. --@@ -257,11 +259,11 @@ -- -- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)" -- >>> let base = read "2025-11-01 12:30:00 UTC" :: UTCTime--- >>> nextRunTimesZonedFromUTC sched base 2+-- >>> nextRunTimesZonedFromUTC base 2 sched -- Right [2025-11-01 09:00:00-04:00,2025-11-02 09:00:00-05:00]-nextRunTimesZonedFromUTC :: Schedule -> UTCTime -> Int -> Either String [ZonedTime]-nextRunTimesZonedFromUTC schedule base limit =- fmap (map (localToZoned schedule)) (nextRunTimesLocalFromUTC schedule base limit)+nextRunTimesZonedFromUTC :: UTCTime -> Int -> Schedule -> Either String [ZonedTime]+nextRunTimesZonedFromUTC base limit schedule =+ fmap (map (localToZoned schedule)) (nextRunTimesLocalFromUTC base limit schedule) -- | Conversion helper. --@@ -269,21 +271,21 @@ -- -- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)" -- >>> let base = read "2025-11-01 09:00:00" :: LocalTime--- >>> nextRunTimesZonedFromLocal sched base 1+-- >>> nextRunTimesZonedFromLocal base 1 sched -- Right [2025-11-01 09:00:00-04:00]-nextRunTimesZonedFromLocal :: Schedule -> LocalTime -> Int -> Either String [ZonedTime]-nextRunTimesZonedFromLocal schedule base limit =- fmap (map (localToZoned schedule)) (nextRunTimesLocal schedule base limit)+nextRunTimesZonedFromLocal :: LocalTime -> Int -> Schedule -> Either String [ZonedTime]+nextRunTimesZonedFromLocal base limit schedule =+ fmap (map (localToZoned schedule)) (nextRunTimesLocal base limit schedule) scheduleLocalOccurrences :: Schedule -> LocalTime -> Int -> Either String [LocalTime] scheduleLocalOccurrences sched@Schedule{..} base limit = case scheduleKind scheduleExpr of RateSchedule -> do let baseUtc = localToUTC sched base- timesUtc <- nextRunTimes scheduleExpr baseUtc limit+ timesUtc <- nextRunTimes baseUtc limit scheduleExpr pure (map (utcToLocalTimeTZ scheduleZone) timesUtc) _ ->- fmap (map utcToLocalNaive) (nextRunTimes scheduleExpr (localToNaiveUTC base) limit)+ fmap (map utcToLocalNaive) (nextRunTimes (localToNaiveUTC base) limit scheduleExpr) localToNaiveUTC :: LocalTime -> UTCTime localToNaiveUTC (LocalTime day tod) = UTCTime day (timeOfDayToTime tod)
test/AWS/EventBridge/CronSpec.hs view
@@ -29,7 +29,7 @@ , testCase "next run times rate" $ do let base = UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 0 0)) rateExpr <- expectParseWith "rate" parseCronText "rate(5 minutes)"- nextRunTimes rateExpr base 3+ nextRunTimes base 3 rateExpr @?= Right [ base , addMinutes base 5@@ -38,12 +38,12 @@ , testCase "next run times one-time" $ do let base = UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 0 0)) oneTimeExpr <- expectParseWith "one-time" parseCronText "at(2025-11-16T09:30:00)"- nextRunTimes oneTimeExpr base 1+ nextRunTimes base 1 oneTimeExpr @?= Right [UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 30 0))] , testCase "next run times cron" $ do let base = UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 0 0)) cronExpr <- expectParseWith "cron" parseCronText "cron(0/15 9 ? NOV SUN 2025)"- nextRunTimes cronExpr base 4+ nextRunTimes base 4 cronExpr @?= Right [ UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 0 0)) , UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 15 0))@@ -53,14 +53,14 @@ , testCase "cron day-of-month evaluated when day-of-week '?'" $ do let base = UTCTime (fromGregorian 2025 11 1) (timeOfDayToTime (TimeOfDay 8 0 0)) cronExpr <- expectParseWith "cron" parseCronText "cron(0 9 5 NOV ? 2025)"- nextRunTimes cronExpr base 2+ nextRunTimes base 2 cronExpr @?= Right [ UTCTime (fromGregorian 2025 11 5) (timeOfDayToTime (TimeOfDay 9 0 0)) ] , testCase "cron skips intraday times earlier than base" $ do let base = UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 7 30)) cronExpr <- expectParseWith "cron" parseCronText "cron(0/15 9 ? NOV SUN 2025)"- nextRunTimes cronExpr base 3+ nextRunTimes base 3 cronExpr @?= Right [ UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 15 0)) , UTCTime (fromGregorian 2025 11 16) (timeOfDayToTime (TimeOfDay 9 30 0))@@ -69,19 +69,19 @@ , testCase "cron spans multiple allowed years" $ do let base = UTCTime (fromGregorian 2026 12 31) (timeOfDayToTime (TimeOfDay 23 0 0)) cronExpr <- expectParseWith "cron" parseCronText "cron(0 0 1 JAN ? 2027-2028)"- nextRunTimes cronExpr base 2+ nextRunTimes base 2 cronExpr @?= Right [ UTCTime (fromGregorian 2027 1 1) (timeOfDayToTime (TimeOfDay 0 0 0)) , UTCTime (fromGregorian 2028 1 1) (timeOfDayToTime (TimeOfDay 0 0 0)) ] , testCase "invalid day fields combination" $ do cronExpr <- expectParseWith "cron" parseCronText "cron(0 0 1 * 2 2025)"- nextRunTimes cronExpr (UTCTime (fromGregorian 2025 1 1) 0) 1+ nextRunTimes (UTCTime (fromGregorian 2025 1 1) 0) 1 cronExpr @?= Left "day-of-month and day-of-week fields must use '?' in exactly one position" , testCase "invalid rate" $ do rateExpr <- expectParseWith "rate" parseCronText "rate(0 minutes)" let base = UTCTime (fromGregorian 2025 1 1) 0- nextRunTimes rateExpr base 1+ nextRunTimes base 1 rateExpr @?= Left "invalid rate minutes: 0 (expected 1..31536000)" , testCase "invalid one-time" $ assertLeft (parseCronText "at(2025-13-01T00:00:00)")@@ -152,7 +152,7 @@ in case parseCronText ("rate(" <> T.pack (show minutes) <> " minutes)") of Left err -> QC.counterexample ("rate parse failed: " <> err) False Right rateExpr ->- case nextRunTimes rateExpr base 5 of+ case nextRunTimes base 5 rateExpr of Left err -> QC.counterexample ("schedule failed: " <> err) False Right ts -> QC.counterexample (show ts) (sorted ts) where@@ -165,7 +165,7 @@ in case parseCronText "cron(0/30 9 ? NOV SUN 2025)" of Left err -> QC.counterexample ("cron parse failed: " <> err) False Right cronExpr ->- case nextRunTimes cronExpr base limit of+ case nextRunTimes base limit cronExpr of Left err -> QC.counterexample ("cron evaluation failed: " <> err) False Right ts -> QC.counterexample (show ts) (length ts <= limit) @@ -176,7 +176,7 @@ in case parseCronText "cron(0/15 9 ? NOV SUN 2025)" of Left err -> QC.counterexample ("cron parse failed: " <> err) False Right cronExpr ->- case nextRunTimes cronExpr base limit of+ case nextRunTimes base limit cronExpr of Left err -> QC.counterexample ("cron evaluation failed: " <> err) False Right ts -> QC.counterexample (show ts) (nonDecreasing ts && all (>= base) ts) where@@ -191,7 +191,7 @@ case evaluateDayOfWeekT year month dowExpr of Left err -> QC.counterexample ("day-of-week evaluation failed: " <> err) False Right allowedDays ->- case nextRunTimes cronExpr base limit of+ case nextRunTimes base limit cronExpr of Left err -> QC.counterexample ("cron evaluation failed: " <> err) False Right ts -> let days = map (thirdOf . toGregorian . utctDay) ts@@ -209,7 +209,7 @@ case evaluateDayOfMonthT year month domExpr of Left err -> QC.counterexample ("day-of-month evaluation failed: " <> err) False Right allowedDays ->- case nextRunTimes cronExpr base limit of+ case nextRunTimes base limit cronExpr of Left err -> QC.counterexample ("cron evaluation failed: " <> err) False Right ts -> let days = map (thirdOf . toGregorian . utctDay) ts
test/AWS/EventBridge/ScheduleSpec.hs view
@@ -22,7 +22,7 @@ sched <- either assertFailure pure (scheduleFromText Asia__Kolkata "cron(0 9 * * ? *)") let base = UTCTime (fromGregorian 2025 11 16) 0 expected = UTCTime (fromGregorian 2025 11 16) (secondsToDiffTime (3 * 3600 + 30 * 60))- nextRunTimesUTC sched base 1 @?= Right [expected]+ nextRunTimesUTC base 1 sched @?= Right [expected] , testCase "cron zoned outputs reflect DST" $ do sched <- either assertFailure pure (scheduleFromText America__New_York "cron(0 9 * * ? *)")@@ -32,14 +32,14 @@ , "2025-11-02 09:00:00 EST" ] - fmap (map show) (nextRunTimesZoned sched base 2) @?= Right expected+ fmap (map show) (nextRunTimesZoned base 2 sched) @?= Right expected , testCase "rate expressions remain absolute" $ do expr <- either assertFailure pure (parseCronText "rate(1 hour)") let sched = scheduleFromExpr Asia__Tokyo expr base = UTCTime (fromGregorian 2025 11 1) 0 expected = take 2 (iterate (addUTCTime 3600) base)- nextRunTimesUTC sched base 2 @?= Right expected+ nextRunTimesUTC base 2 sched @?= Right expected , testCase "local helper trims times before base" $ do sched <- either assertFailure pure (scheduleFromText Asia__Kolkata "cron(0 9 * * ? *)")@@ -48,19 +48,19 @@ [ read "2025-11-16 09:00:00" :: LocalTime , read "2025-11-17 09:00:00" ]- nextRunTimesLocal sched baseLocal 2 @?= Right expected+ nextRunTimesLocal baseLocal 2 sched @?= Right expected , testCase "local-from-UTC matches local helper" $ do sched <- either assertFailure pure (scheduleFromText Asia__Kolkata "cron(0 9 * * ? *)") let baseUtc = UTCTime (fromGregorian 2025 11 16) (secondsToDiffTime (3 * 3600 + 30 * 60))- nextRunTimesLocalFromUTC sched baseUtc 1- @?= nextRunTimesLocal sched (read "2025-11-16 09:00:00" :: LocalTime) 1+ nextRunTimesLocalFromUTC baseUtc 1 sched+ @?= nextRunTimesLocal (read "2025-11-16 09:00:00" :: LocalTime) 1 sched , testCase "UTC-from-local converts to absolute" $ do sched <- either assertFailure pure (scheduleFromText Asia__Kolkata "cron(0 9 * * ? *)") let baseLocal = read "2025-11-16 09:00:00" :: LocalTime expected = UTCTime (fromGregorian 2025 11 16) (secondsToDiffTime (3 * 3600 + 30 * 60))- nextRunTimesUTCFromLocal sched baseLocal 1 @?= Right [expected]+ nextRunTimesUTCFromLocal baseLocal 1 sched @?= Right [expected] , testCase "zoned-from-UTC emits offsets" $ do sched <- either assertFailure pure (scheduleFromText America__New_York "cron(0 9 * * ? *)")@@ -69,7 +69,7 @@ [ "2025-11-01 09:00:00 EDT" , "2025-11-02 09:00:00 EST" ]- fmap (map show) (nextRunTimesZonedFromUTC sched baseUtc 2) @?= Right expected+ fmap (map show) (nextRunTimesZonedFromUTC baseUtc 2 sched) @?= Right expected , testCase "scheduleFromTextIANA resolves canonical names" $ do sched <- either assertFailure pure (scheduleFromTextIANA "America/New_York" "cron(0 9 * * ? *)")@@ -85,6 +85,6 @@ schedA <- either assertFailure pure (parseCronTextWithZone Asia__Kolkata expr) schedB <- either assertFailure pure (scheduleFromText Asia__Kolkata expr) scheduleZoneLabel schedA @?= scheduleZoneLabel schedB- nextRunTimesUTC schedA (UTCTime (fromGregorian 2025 1 1) 0) 1- @?= nextRunTimesUTC schedB (UTCTime (fromGregorian 2025 1 1) 0) 1+ nextRunTimesUTC (UTCTime (fromGregorian 2025 1 1) 0) 1 schedA+ @?= nextRunTimesUTC (UTCTime (fromGregorian 2025 1 1) 0) 1 schedB ]