aws-eventbridge-cron 0.1.0.1 → 0.1.1.0
raw patch · 6 files changed
+429/−1 lines, 6 filesdep +tzdep +tzdataPVP ok
version bump matches the API change (PVP)
Dependencies added: tz, tzdata
API changes (from Hackage documentation)
+ AWS.EventBridge.Schedule: Schedule :: CronExprT -> TZ -> TZLabel -> Schedule
+ AWS.EventBridge.Schedule: [scheduleExpr] :: Schedule -> CronExprT
+ AWS.EventBridge.Schedule: [scheduleZoneLabel] :: Schedule -> TZLabel
+ AWS.EventBridge.Schedule: [scheduleZone] :: Schedule -> TZ
+ AWS.EventBridge.Schedule: data Schedule
+ AWS.EventBridge.Schedule: nextRunTimesLocal :: Schedule -> LocalTime -> Int -> Either String [LocalTime]
+ AWS.EventBridge.Schedule: nextRunTimesLocalFromUTC :: Schedule -> UTCTime -> Int -> Either String [LocalTime]
+ AWS.EventBridge.Schedule: nextRunTimesLocalFromZoned :: Schedule -> ZonedTime -> Int -> Either String [LocalTime]
+ AWS.EventBridge.Schedule: nextRunTimesUTC :: Schedule -> UTCTime -> Int -> Either String [UTCTime]
+ AWS.EventBridge.Schedule: nextRunTimesUTCFromLocal :: Schedule -> LocalTime -> Int -> Either String [UTCTime]
+ AWS.EventBridge.Schedule: nextRunTimesUTCFromZoned :: Schedule -> ZonedTime -> Int -> Either String [UTCTime]
+ AWS.EventBridge.Schedule: nextRunTimesZoned :: Schedule -> ZonedTime -> Int -> Either String [ZonedTime]
+ AWS.EventBridge.Schedule: nextRunTimesZonedFromLocal :: Schedule -> LocalTime -> Int -> Either String [ZonedTime]
+ AWS.EventBridge.Schedule: nextRunTimesZonedFromUTC :: Schedule -> UTCTime -> Int -> Either String [ZonedTime]
+ AWS.EventBridge.Schedule: parseCronTextWithZone :: TZLabel -> Text -> Either String Schedule
+ AWS.EventBridge.Schedule: scheduleFromExpr :: TZLabel -> CronExprT -> Schedule
+ AWS.EventBridge.Schedule: scheduleFromText :: TZLabel -> Text -> Either String Schedule
Files
- CHANGELOG.md +12/−0
- README.md +62/−0
- aws-eventbridge-cron.cabal +8/−1
- src/AWS/EventBridge/Schedule.hs +264/−0
- test/AWS/EventBridge/ScheduleSpec.hs +81/−0
- test/Main.hs +2/−0
CHANGELOG.md view
@@ -5,6 +5,18 @@ The format is based on Keep a Changelog and this project adheres to Semantic Versioning. +## [0.1.1.0] - 2025-11-20+### Added+- `AWS.EventBridge.Schedule` module that pairs schedules with an IANA timezone+ (via the `tz`/`tzdata` packages) and exposes the full matrix of+ `nextRunTimes` variants for UTC, local, and zoned outputs.+- Smart constructors such as `scheduleFromText` plus helper variants+ (`nextRunTimesUTCFromLocal`, `nextRunTimesZonedFromUTC`, etc.) documented with+ Haddock examples.+- Property-style regression tests in `ScheduleSpec` covering cron/one-time rate+ conversions, DST transitions, and timezone-dependent outputs.+- README examples describing timezone-aware workflows and feature list updates.+ ## [0.1.0.1] - 2025-11-17 ### Added - `ScheduleKind`, `scheduleKind`, and `isRecurring` helpers for classifying parsed schedules without re-parsing downstream.
README.md view
@@ -16,6 +16,9 @@ - Schedule introspection helpers: `scheduleKind` returns a `ScheduleKind`, and `isRecurring` distinguishes recurring (`cron`/`rate`) expressions from `at(...)` one-time schedules.+- Timezone-aware helpers: `AWS.EventBridge.Schedule` pairs expressions with an+ IANA timezone (via the `tz`/`tzdata` packages) and exposes `nextRunTimes`+ variants for UTC, local, or fully-zoned outputs. - Extensive property-based test suite that mirrors the behaviour documented by AWS. @@ -71,6 +74,65 @@ isRecurringExample = isRecurring <$> parseCronText "at(2025-11-16T09:30:00)" -- Right False ```++### Timezone-Aware Schedules++EventBridge rules can set a schedule timezone. Use `AWS.EventBridge.Schedule` to+bind an expression to a `TZLabel` so you can request run times in UTC, as local+wall-clock values, or as `ZonedTime`s tagged with the appropriate offset.++```haskell+import AWS.EventBridge.Schedule+import Data.Time (UTCTime(..), LocalTime, ZonedTime, fromGregorian)+import Data.Time.Zones.All (TZLabel(..))++baseUTC :: UTCTime+baseUTC = UTCTime (fromGregorian 2025 11 1) 0++zonedSchedule :: Either String Schedule+zonedSchedule = scheduleFromText America__New_York "cron(0 9 * NOV ? 2025)"++utcRuns :: Either String [UTCTime]+utcRuns = nextRunTimesUTC <$> zonedSchedule <*> pure baseUTC <*> pure 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+-- Right [2025-11-01 09:00:00,2025-11-02 09:00:00]++zonedRuns :: Either String [ZonedTime]+zonedRuns = nextRunTimesZonedFromUTC <$> zonedSchedule <*> pure baseUTC <*> pure 2+-- Right [2025-11-01 09:00:00 EDT,2025-11-02 09:00:00 EST]+```++Every combination of base input (`UTCTime`, `LocalTime`, `ZonedTime`) and output+form is available, so you can normalize timestamps at the edges of your system+and avoid comparing values that silently belong to different timezones.++### API Overview++1. Parse using `parseCronText` (UTC) or `scheduleFromText` (timezone-aware).+2. Wrap with `scheduleFromExpr`/`scheduleFromText` if you need timezone metadata.+3. Choose an evaluation helper based on the base input you have and the output+ you need. Prefer the primary trio (`nextRunTimesUTC`, `nextRunTimesLocal`,+ `nextRunTimesZoned`) and reach for the conversion helpers when you want to+ avoid manual conversions.++| Base input | Output | Function |+|-------------|-------------|------------------------------------|+| `UTCTime` | `UTCTime` | `nextRunTimesUTC` |+| `LocalTime` | `UTCTime` | `nextRunTimesUTCFromLocal` |+| `ZonedTime` | `UTCTime` | `nextRunTimesUTCFromZoned` |+| `UTCTime` | `LocalTime` | `nextRunTimesLocalFromUTC` |+| `LocalTime` | `LocalTime` | `nextRunTimesLocal` |+| `ZonedTime` | `LocalTime` | `nextRunTimesLocalFromZoned` |+| `UTCTime` | `ZonedTime` | `nextRunTimesZonedFromUTC` |+| `LocalTime` | `ZonedTime` | `nextRunTimesZonedFromLocal` |+| `ZonedTime` | `ZonedTime` | `nextRunTimesZoned` |++Use the conversion helpers when you already have a base timestamp in a specific+representation and want the library to handle the translation for you (for+example, UI-provided `LocalTime` that needs to be compared against UTC events). ### Error Reporting
aws-eventbridge-cron.cabal view
@@ -1,7 +1,7 @@ cabal-version: 3.0 name: aws-eventbridge-cron-version: 0.1.0.1+version: 0.1.1.0 synopsis: AWS EventBridge cron, rate, and one-time parser with scheduler description: Parse AWS EventBridge cron, rate, and one-time expressions and compute@@ -33,6 +33,7 @@ library exposed-modules: AWS.EventBridge.Cron+ , AWS.EventBridge.Schedule other-modules: AWS.EventBridge.DayOfMonth , AWS.EventBridge.DayOfWeek@@ -53,6 +54,8 @@ , megaparsec , text , time+ , tz+ , tzdata test-suite aws-eventbridge-cron-test type: exitcode-stdio-1.0@@ -69,6 +72,8 @@ , AWS.EventBridge.YearsSpec , AWS.EventBridge.RateSpec , AWS.EventBridge.CronSpec+ , AWS.EventBridge.Schedule+ , AWS.EventBridge.ScheduleSpec , AWS.EventBridge.OneTimeSpec , TestSupport , AWS.EventBridge.Cron@@ -93,3 +98,5 @@ , tasty-quickcheck >=0.10 && <0.11 , text , time+ , tz+ , tzdata
+ src/AWS/EventBridge/Schedule.hs view
@@ -0,0 +1,264 @@+{-# LANGUAGE RecordWildCards #-}++-- | Zone-aware helpers built on top of 'AWS.EventBridge.Cron'.+--+-- ## Choosing an entry point+--+-- 1. Parse the expression: use 'parseCronText' (UTC-only) or 'scheduleFromText'+-- when the rule already specifies an IANA timezone.+-- 2. Wrap the expression with 'scheduleFromExpr' or 'scheduleFromText' to carry+-- timezone metadata.+-- 3. Evaluate upcoming runs via the primary trio:+--+-- * 'nextRunTimesUTC' – keep everything in UTC and compare against other+-- absolute timestamps.+-- * 'nextRunTimesLocal' – receive wall-clock values in the schedule's zone.+-- * 'nextRunTimesZoned' – like 'nextRunTimesLocal' but tagged with the+-- `TimeZone` used at each occurrence (captures DST changes).+--+-- Conversion helpers (the `*FromUTC`, `*FromLocal`, and `*FromZoned` variants)+-- simply preprocess the base value before delegating to one of the three+-- primary functions. They are handy when your caller already has a specific+-- representation and you want to avoid manual conversions.+--+-- ### Base/Input vs Output quick reference+--+-- | Base type | Desired output | Function |+-- |------------|----------------|----------------------------------|+-- | 'UTCTime' | 'UTCTime' | 'nextRunTimesUTC' |+-- | 'LocalTime'| 'UTCTime' | 'nextRunTimesUTCFromLocal' |+-- | 'ZonedTime'| 'UTCTime' | 'nextRunTimesUTCFromZoned' |+-- | 'UTCTime' | 'LocalTime' | 'nextRunTimesLocalFromUTC' |+-- | 'LocalTime'| 'LocalTime' | 'nextRunTimesLocal' |+-- | 'ZonedTime'| 'LocalTime' | 'nextRunTimesLocalFromZoned' |+-- | 'UTCTime' | 'ZonedTime' | 'nextRunTimesZonedFromUTC' |+-- | 'LocalTime'| 'ZonedTime' | 'nextRunTimesZonedFromLocal' |+-- | 'ZonedTime'| 'ZonedTime' | 'nextRunTimesZoned' |+module AWS.EventBridge.Schedule+ ( -- * Schedule construction+ Schedule(..)+ , scheduleFromExpr+ , scheduleFromText+ , parseCronTextWithZone+ -- * Primary evaluation helpers+ , nextRunTimesUTC+ , nextRunTimesLocal+ , nextRunTimesZoned+ -- * Conversion helpers+ , nextRunTimesUTCFromLocal+ , nextRunTimesUTCFromZoned+ , nextRunTimesLocalFromUTC+ , nextRunTimesLocalFromZoned+ , nextRunTimesZonedFromUTC+ , nextRunTimesZonedFromLocal+ ) where++import AWS.EventBridge.Cron+ ( CronExprT+ , ScheduleKind(..)+ , nextRunTimes+ , parseCronText+ , scheduleKind+ )+import Data.Text (Text)+import Data.Time+ ( LocalTime(..)+ , ZonedTime(..)+ , UTCTime(..)+ , timeOfDayToTime+ , timeToTimeOfDay+ , zonedTimeToUTC+ )+import Data.Time.Zones+ ( TZ+ , localTimeToUTCTZ+ , timeZoneForUTCTime+ , utcToLocalTimeTZ+ )+import Data.Time.Zones.All (TZLabel(..), tzByLabel)++-- | Scheduling expression paired with its IANA timezone.+--+-- Use 'scheduleFromText' when you need to parse the expression and bind the+-- timezone in one step, or 'scheduleFromExpr' if you already have a+-- 'CronExprT'.+data Schedule = Schedule+ { scheduleExpr :: CronExprT+ , scheduleZone :: TZ+ , scheduleZoneLabel :: TZLabel+ }++-- | Construct a 'Schedule' from an existing 'CronExprT' and timezone label.+--+-- @tz@ ships with bindings for the entire IANA database through 'TZLabel'. The+-- resulting schedule can be fed directly into the zone-aware 'nextRunTimes*'+-- helpers below.+scheduleFromExpr :: TZLabel -> CronExprT -> Schedule+scheduleFromExpr label expr = Schedule+ { scheduleExpr = expr+ , scheduleZone = tzByLabel label+ , scheduleZoneLabel = label+ }++-- | Parse an EventBridge expression and attach a timezone in the same step.+--+-- >>> :{+-- 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 [2025-11-16 03:30:00 UTC]+scheduleFromText :: TZLabel -> Text -> Either String Schedule+scheduleFromText label input =+ fmap (scheduleFromExpr label) (parseCronText input)++-- | Backwards-compatible alias for 'scheduleFromText'.+parseCronTextWithZone :: TZLabel -> Text -> Either String Schedule+parseCronTextWithZone = scheduleFromText++-- | Local-time primary helper.+--+-- Evaluate run times in the schedule's local timezone, starting from a local+-- base time that is already expressed in the schedule's zone. Choose this when+-- you want to present results exactly as the rule owner configured them.+--+-- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)"+-- >>> let base = read "2025-11-01 08:00:00" :: LocalTime+-- >>> nextRunTimesLocal sched base 1+-- Right [2025-11-01 09:00:00]+nextRunTimesLocal :: Schedule -> LocalTime -> Int -> Either String [LocalTime]+nextRunTimesLocal = scheduleLocalOccurrences++-- | Conversion helper.+--+-- Evaluate run times in the schedule's local timezone using a UTC base. Ideal+-- when upstream systems give you absolute timestamps (e.g. database clocks) but+-- UI clients expect the local wall clock.+--+-- >>> 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+-- Right [2025-11-01 09:00:00]+nextRunTimesLocalFromUTC :: Schedule -> UTCTime -> Int -> Either String [LocalTime]+nextRunTimesLocalFromUTC schedule base = scheduleLocalOccurrences schedule (utcToLocalTimeTZ (scheduleZone schedule) base)++-- | Conversion helper.+--+-- Evaluate run times in the schedule's local timezone using an arbitrary+-- 'ZonedTime' base (the offset on the input is ignored; only the instant+-- matters). Handy when you get user input such as "2025-11-01 09:00 EDT" and+-- want to keep working in local values.+--+-- >>> 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+-- 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))++-- | UTC primary helper.+--+-- Evaluate run times in UTC while accepting a UTC base. This is the simplest+-- option when the rest of your system already speaks UTC.+--+-- >>> 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+-- 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)++-- | Conversion helper.+--+-- Evaluate run times in UTC with a local base. Use this when your caller has+-- local wall time but downstream systems expect UTC instants.+--+-- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)"+-- >>> let base = read "2025-11-01 08:00:00" :: LocalTime+-- >>> nextRunTimesUTCFromLocal sched base 1+-- 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)++-- | Conversion helper.+--+-- Evaluate run times in UTC with a 'ZonedTime' base. Helpful when upstream APIs+-- hand you zoned timestamps and you want to compare the schedule against other+-- UTC data.+--+-- >>> 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+-- 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)++-- | Zoned-time primary helper.+--+-- Evaluate run times as 'ZonedTime' values using a zoned base. This preserves+-- the original offset used for the base and propagates DST changes into the+-- outputs, which is useful for logs or API responses that must mention the+-- effective offset explicitly.+--+-- >>> 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+-- 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)++-- | Conversion helper.+--+-- Evaluate run times as 'ZonedTime' values from a UTC base.+--+-- >>> 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+-- 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)++-- | Conversion helper.+--+-- Evaluate run times as 'ZonedTime' values from a local base.+--+-- >>> let Right sched = scheduleFromText America__New_York "cron(0 9 * * ? *)"+-- >>> let base = read "2025-11-01 09:00:00" :: LocalTime+-- >>> nextRunTimesZonedFromLocal sched base 1+-- 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)++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+ pure (map (utcToLocalTimeTZ scheduleZone) timesUtc)+ _ ->+ fmap (map utcToLocalNaive) (nextRunTimes scheduleExpr (localToNaiveUTC base) limit)++localToNaiveUTC :: LocalTime -> UTCTime+localToNaiveUTC (LocalTime day tod) = UTCTime day (timeOfDayToTime tod)++utcToLocalNaive :: UTCTime -> LocalTime+utcToLocalNaive (UTCTime day diff) = LocalTime day (timeToTimeOfDay diff)++localToUTC :: Schedule -> LocalTime -> UTCTime+localToUTC Schedule{..} = localTimeToUTCTZ scheduleZone++localToZoned :: Schedule -> LocalTime -> ZonedTime+localToZoned schedule local =+ let utcVal = localToUTC schedule local+ zone = scheduleZone schedule+ localWall = utcToLocalTimeTZ zone utcVal+ tzInfo = timeZoneForUTCTime zone utcVal+ in ZonedTime localWall tzInfo
+ test/AWS/EventBridge/ScheduleSpec.hs view
@@ -0,0 +1,81 @@+{-# LANGUAGE OverloadedStrings #-}++module AWS.EventBridge.ScheduleSpec (tests) where++import AWS.EventBridge.Schedule+import AWS.EventBridge.Cron (parseCronText)+import Data.Time+ ( LocalTime+ , UTCTime(..)+ , ZonedTime+ , addUTCTime+ , fromGregorian+ , secondsToDiffTime+ )+import Data.Time.Zones.All (TZLabel(..))+import Test.Tasty (TestTree, testGroup)+import Test.Tasty.HUnit++tests :: TestTree+tests = testGroup "schedule"+ [ testCase "cron nextRunTimesUTC respects timezone" $ do+ 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]++ , testCase "cron zoned outputs reflect DST" $ do+ sched <- either assertFailure pure (scheduleFromText America__New_York "cron(0 9 * * ? *)")+ let base = read "2025-11-01 09:00:00-04:00" :: ZonedTime+ expected =+ [ "2025-11-01 09:00:00 EDT"+ , "2025-11-02 09:00:00 EST"+ ]++ fmap (map show) (nextRunTimesZoned sched base 2) @?= 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++ , testCase "local helper trims times before base" $ do+ sched <- either assertFailure pure (scheduleFromText Asia__Kolkata "cron(0 9 * * ? *)")+ let baseLocal = read "2025-11-16 08:30:00" :: LocalTime+ expected =+ [ read "2025-11-16 09:00:00" :: LocalTime+ , read "2025-11-17 09:00:00"+ ]+ nextRunTimesLocal sched baseLocal 2 @?= 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++ , 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]++ , testCase "zoned-from-UTC emits offsets" $ do+ sched <- either assertFailure pure (scheduleFromText America__New_York "cron(0 9 * * ? *)")+ let baseUtc = UTCTime (fromGregorian 2025 11 1) (secondsToDiffTime (12 * 3600))+ expected =+ [ "2025-11-01 09:00:00 EDT"+ , "2025-11-02 09:00:00 EST"+ ]+ fmap (map show) (nextRunTimesZonedFromUTC sched baseUtc 2) @?= Right expected++ , testCase "parseCronTextWithZone aliases scheduleFromText" $ do+ let expr = "cron(0 0 1 1 ? 2025)"+ 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+ ]
test/Main.hs view
@@ -9,6 +9,7 @@ import qualified AWS.EventBridge.RateSpec as RateSpec import qualified AWS.EventBridge.OneTimeSpec as OneTimeSpec import qualified AWS.EventBridge.CronSpec as CronSpec+import qualified AWS.EventBridge.ScheduleSpec as ScheduleSpec import Test.Tasty (defaultMain, testGroup) main :: IO ()@@ -24,4 +25,5 @@ , RateSpec.tests , OneTimeSpec.tests , CronSpec.tests+ , ScheduleSpec.tests ]