# aws-eventbridge-cron Parse AWS EventBridge cron, rate, and one-time expressions and compute their future run times. Status: early preview. Expect small API tweaks before a `1.0.0` release. ## Features - Single entry point: `AWS.EventBridge.Cron` exports the `CronExprT` type, `parseCronText` parser, and `nextRunTimes` scheduler. - Full support for EventBridge-specific syntax such as `?` wildcards, `L`, `LW`, weekday ranges, and nth-weekday modifiers (`2#1`). - `rate(...)` and `at(...)` expressions share the same API, so callers do not need to branch on expression variants. - 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. - Convenience constructors accept canonical IANA names (`"America/New_York"`) in addition to the generated `TZLabel` constructors. - Extensive property-based test suite that mirrors the behaviour documented by AWS. ## Installation ``` cabal install aws-eventbridge-cron ``` Or add the package to your component: ```cabal build-depends: aws-eventbridge-cron >= 0.1 && < 0.2 ``` ## Quick Start ```haskell import AWS.EventBridge.Cron import Data.Time (UTCTime(..), fromGregorian) import Data.Time.LocalTime (TimeOfDay(..), timeOfDayToTime) base :: UTCTime 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 -- Right [2025-11-16 09:00:00 UTC, 2025-11-16 09:15:00 UTC, ...] ``` The parser also accepts `rate(...)` and `at(...)` expressions: ```haskell rateExample :: Either String [UTCTime] rateExample = do expr <- parseCronText "rate(10 minutes)" nextRunTimes expr base 3 atExample :: Either String [UTCTime] atExample = do expr <- parseCronText "at(2025-11-16T09:30:00)" nextRunTimes expr base 5 -- Introspect the parsed expression without re-parsing downstream. kindExample :: Either String ScheduleKind kindExample = scheduleKind <$> parseCronText "rate(5 minutes)" -- Right RateSchedule isRecurringExample :: Either String Bool 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 |