packages feed

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 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   ]