From 9431791c3e617609fe91d545a7d3f19024059c85 Mon Sep 17 00:00:00 2001 From: Durable Workflow Date: Wed, 21 Oct 2026 05:14:00 +0000 Subject: [PATCH] Run PHP schedule specifications, policies and namespaces natively Native schedules were a bounded subset: one UTC interval of a day or two seconds, the skip policy, no jitter, at most one run, the default namespace. They now follow published PHP 2.5.14: - cron expressions exactly as dragonmantank/cron-expression 3.6.0 evaluates them, including PHP's DateTime rules around daylight-saving changes, time zone names as new DateTimeZone() reads them, and computeNextFireAt over cron expressions and offset intervals; - creation with PHP's Laravel validation rules, messages and normalization, any schedule id, jitter, run quota, memo, note and creation paused; - triggerDetailed and the tick: all six overlap policies, buffering and its drain, closing the other run, stale and exhausted occurrences, failures, the recent-actions ring and the next fire time after each fire; - PHP's description, history snapshot and history page shapes, pause and resume notes, and namespace-scoped schedules. scripts/conformance/cron-oracle regenerates the committed corpora of PHP's own answers from the frozen image; native matches all of them. The new schedule-policies fixture compares 60 responses with a reviewed copy of published PHP's. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/server-parity.yml | 8 +- docs/rust-server-port.md | 60 + rust/Cargo.lock | 105 +- rust/Cargo.toml | 1 + rust/src/runtime/execution.rs | 39 +- rust/src/runtime/http.rs | 4 + rust/src/runtime/laravel_rules.rs | 360 ++++ rust/src/runtime/memos.rs | 2 +- rust/src/runtime/mod.rs | 4 + rust/src/runtime/mysql.rs | 4 +- rust/src/runtime/schedule_cron.rs | 914 +++++++++ rust/src/runtime/schedule_execution.rs | 741 ++++++-- rust/src/runtime/schedule_http.rs | 144 +- rust/src/runtime/schedule_spec.rs | 232 +++ rust/src/runtime/schedule_storage.rs | 251 ++- rust/src/runtime/schedule_zones.rs | 302 +++ rust/src/runtime/schedules.rs | 426 +++-- rust/tests/execution/namespaces.rs | 2 +- rust/tests/execution/schedules.rs | 42 + .../fixtures/cron/php-2.5.14-next-fire.jsonl | 600 ++++++ .../fixtures/cron/php-2.5.14-next-run.jsonl | 1200 ++++++++++++ .../fixtures/cron/php-2.5.14-zone-names.jsonl | 1068 +++++++++++ scripts/conformance/cron-oracle/README.md | 33 + scripts/conformance/cron-oracle/next-fire.php | 48 + scripts/conformance/cron-oracle/next-run.php | 63 + .../conformance/cron-oracle/zone-names.php | 33 + .../conformance/server-parity/contract.mjs | 3 + scripts/conformance/server-parity/probe.php | 6 + .../schedule-policies-contract.mjs | 85 + .../server-parity/schedule-policies-probe.php | 73 + .../ServerParity/schedule-policies.json | 56 + .../schedule-policies.json | 1677 +++++++++++++++++ ...rverParitySchedulePoliciesContractTest.mjs | 48 + 33 files changed, 8197 insertions(+), 437 deletions(-) create mode 100644 rust/src/runtime/laravel_rules.rs create mode 100644 rust/src/runtime/schedule_cron.rs create mode 100644 rust/src/runtime/schedule_spec.rs create mode 100644 rust/src/runtime/schedule_zones.rs create mode 100644 rust/tests/fixtures/cron/php-2.5.14-next-fire.jsonl create mode 100644 rust/tests/fixtures/cron/php-2.5.14-next-run.jsonl create mode 100644 rust/tests/fixtures/cron/php-2.5.14-zone-names.jsonl create mode 100644 scripts/conformance/cron-oracle/README.md create mode 100644 scripts/conformance/cron-oracle/next-fire.php create mode 100644 scripts/conformance/cron-oracle/next-run.php create mode 100644 scripts/conformance/cron-oracle/zone-names.php create mode 100644 scripts/conformance/server-parity/schedule-policies-contract.mjs create mode 100644 scripts/conformance/server-parity/schedule-policies-probe.php create mode 100644 tests/Fixtures/ServerParity/schedule-policies.json create mode 100644 tests/Fixtures/ServerParityExpected/schedule-policies.json create mode 100644 tests/Unit/ServerParitySchedulePoliciesContractTest.mjs diff --git a/.github/workflows/server-parity.yml b/.github/workflows/server-parity.yml index 38b7bef31..e730816c1 100644 --- a/.github/workflows/server-parity.yml +++ b/.github/workflows/server-parity.yml @@ -19,6 +19,7 @@ on: - tests/Unit/ServerParityScheduleContractTest.mjs - tests/Support/ServerParityScheduleObservation.mjs - tests/Fixtures/ServerParityPending/** + - tests/Fixtures/ServerParityExpected/** - tests/Fixtures/ServerParityProfiles/** - tests/Unit/ServerParityVisibilityContractTest.mjs - tests/Unit/ServerParityAdmissionContractTest.mjs @@ -26,7 +27,7 @@ on: - tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs - tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs - tests/Unit/ServerParitySearchAttributeContractTest.mjs - - tests/Unit/ServerParityStorageTestContractTest.mjs + - tests/Unit/ServerParityStorageTestContractTest.mjs tests/Unit/ServerParitySchedulePoliciesContractTest.mjs - tests/Unit/ServerParityWorkerListingContractTest.mjs - tests/Unit/ServerParityWorkerReplacementContractTest.mjs - tests/Unit/ServerParityWaitingHistoryContractTest.mjs @@ -61,6 +62,7 @@ on: - tests/Unit/ServerParityScheduleContractTest.mjs - tests/Support/ServerParityScheduleObservation.mjs - tests/Fixtures/ServerParityPending/** + - tests/Fixtures/ServerParityExpected/** - tests/Fixtures/ServerParityProfiles/** - tests/Unit/ServerParityVisibilityContractTest.mjs - tests/Unit/ServerParityAdmissionContractTest.mjs @@ -68,7 +70,7 @@ on: - tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs - tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs - tests/Unit/ServerParitySearchAttributeContractTest.mjs - - tests/Unit/ServerParityStorageTestContractTest.mjs + - tests/Unit/ServerParityStorageTestContractTest.mjs tests/Unit/ServerParitySchedulePoliciesContractTest.mjs - tests/Unit/ServerParityWorkerListingContractTest.mjs - tests/Unit/ServerParityWorkerReplacementContractTest.mjs - tests/Unit/ServerParityWaitingHistoryContractTest.mjs @@ -1460,7 +1462,7 @@ jobs: -e COMPOSER_HOME=/tmp/composer -v "$PWD:/source" -w /source "$image" \ install --working-dir=scripts/conformance/server-parity --no-interaction --no-progress docker run --rm --user=1000:1000 --entrypoint node -v "$PWD:/source:ro" -w /source \ - "$image" --test tests/Unit/ServerParityRunnerTest.mjs tests/Unit/ServerParityCooperativeContractTest.mjs tests/Unit/ServerParityChildCancellationContractTest.mjs tests/Unit/ServerParityScheduleContractTest.mjs tests/Unit/ServerParityVisibilityContractTest.mjs tests/Unit/ServerParityAdmissionContractTest.mjs tests/Unit/ServerParityRoleTokenContractTest.mjs tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs tests/Unit/ServerParitySearchAttributeContractTest.mjs tests/Unit/ServerParityWorkerListingContractTest.mjs tests/Unit/ServerParityWorkerReplacementContractTest.mjs tests/Unit/ServerParityPatchDeploymentContractTest.mjs tests/Unit/ServerParityWaitingHistoryContractTest.mjs tests/Unit/ServerParityTaskRetryContractTest.mjs tests/Unit/ServerParityPublishedPatchContractTest.mjs tests/Unit/ServerParityPublishedPatchTransportTest.mjs tests/Unit/ServerParityRecordFileTest.mjs tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs tests/Unit/ServerParityStorageTestContractTest.mjs > parity-evidence/comparator-tests.txt + "$image" --test tests/Unit/ServerParityRunnerTest.mjs tests/Unit/ServerParityCooperativeContractTest.mjs tests/Unit/ServerParityChildCancellationContractTest.mjs tests/Unit/ServerParityScheduleContractTest.mjs tests/Unit/ServerParityVisibilityContractTest.mjs tests/Unit/ServerParityAdmissionContractTest.mjs tests/Unit/ServerParityRoleTokenContractTest.mjs tests/Unit/ServerParityWorkerDeregistrationContractTest.mjs tests/Unit/ServerParitySearchAttributeContractTest.mjs tests/Unit/ServerParityWorkerListingContractTest.mjs tests/Unit/ServerParityWorkerReplacementContractTest.mjs tests/Unit/ServerParityPatchDeploymentContractTest.mjs tests/Unit/ServerParityWaitingHistoryContractTest.mjs tests/Unit/ServerParityTaskRetryContractTest.mjs tests/Unit/ServerParityPublishedPatchContractTest.mjs tests/Unit/ServerParityPublishedPatchTransportTest.mjs tests/Unit/ServerParityRecordFileTest.mjs tests/Unit/ServerParityLateWorkflowCompletionContractTest.mjs tests/Unit/ServerParityStorageTestContractTest.mjs tests/Unit/ServerParitySchedulePoliciesContractTest.mjs > parity-evidence/comparator-tests.txt docker run --rm --user=1000:1000 --cpus=1 --memory=256m --memory-swap=256m \ --network=none --entrypoint node -v "$PWD:/source:ro" -w /source \ "$image" --test tests/Unit/ServerParityCodecDeadlineLoadTest.mjs > parity-evidence/codec-load-tests.txt diff --git a/docs/rust-server-port.md b/docs/rust-server-port.md index 168a88a56..281b527fc 100644 --- a/docs/rust-server-port.md +++ b/docs/rust-server-port.md @@ -229,6 +229,66 @@ support is not evidence of multi-node safety. Newest checkpoints first. Each entry is exact to the source, tree and runs it names, including unsuccessful observations. +October 21, PHP schedule specifications and policies: + +- **Cron.** `schedule_cron.rs` ports `dragonmantank/cron-expression` 3.6.0 as + published PHP 2.5.14 runs it: validation, aliases, names, ranges, steps, + lists, `L`, `W`, `#` and `?`, the separate day-of-month and day-of-week + searches, the 1,000-step limit and each field's quirks. PHP's `DateTime` + rules around daylight-saving changes are kept: a skipped wall-clock time + takes the offset from before the change, a repeated one keeps the current + daylight flag, and hour and minute steps are applied in UTC. Zone rules come + from `jiff`'s bundled IANA data (2026e). Ireland and Morocco use negative + daylight saving in the main IANA data PHP bundles, so PHP's flag is + inverted there. +- **Zones.** `schedule_zones.rs` reads names as `new DateTimeZone()` does: + numeric offsets (`+5`, `-0330`, `GMT+1`), PHP's 144 abbreviations first + (`CET` is fixed +01:00) and then IANA identifiers in any case, with matched + parentheses. Offsets of 26 hours or more, which PHP accepts up to 100, are + refused. +- **Specifications.** `schedule_spec.rs` is `computeNextFireAt`: the earliest + occurrence across cron expressions in the zone and intervals counted from + the epoch plus an offset, with PHP's `DateInterval` grammar and its + 365-day year and 30-day month. +- **Corpora.** `scripts/conformance/cron-oracle` regenerates, from the frozen + image, 1,200 cron cases, every PHP zone spelling (1,068 lines) and 600 whole + specifications. Native matches all of them, and 6,000 and 2,000 more seeded + cases checked locally. Six zones whose rules changed after PHP's 2026.1 + database are left out (Casablanca, El Aaiun, Edmonton, Inuvik, Vancouver, + Winnipeg); native uses the newer rules. +- **Schedules.** Creation follows PHP's Laravel rules and messages (a small + `laravel_rules.rs` engine: trimming, empty strings as null, `*` expansion, + attribute names and order). It accepts any schedule id, cron and interval + specifications in any zone, all six overlap policies, jitter, any run + quota, memo, note and creation paused. Descriptions, history snapshots and + history pages have PHP's shapes and timestamps. Pause accepts an + already paused schedule and records its note; resume records its note + after the resumed audit. Triggers follow `triggerDetailed`: the stale and + status checks, quota exhaustion, buffering with a full `buffer_one`, + closing the latest run for `cancel_other` and `terminate_other`, the + ten-entry recent actions, failure accounting and the next fire time after + each fire. The tick drains buffered actions of finished runs before due + schedules. Schedules are namespace-scoped and start runs in their own + namespace. +- **Deliberate differences.** PHP's unhandled 500 for resuming an active + schedule is a 409 `schedule_not_paused`. A tick that finds another native + node already fired the occurrence skips it silently instead of recording + `stale_occurrence`. Plain JSON array input and search attributes are + refused, as native start refuses them; Avro input that does not decode is + refused at creation. +- **Evidence.** The new `schedule-policies` fixture sends 60 requests to a fresh + namespace and compares every response, without generated identities and + times, with a reviewed copy of published PHP's + (`tests/Fixtures/ServerParityExpected`). Published PHP, embedded and native + passed it locally, and native from main failed it. Published PHP on SQLite + can fail a trigger with `database is locked` against its own maintenance + roles, and the transaction rolls back, so the probe retries exactly that. + The two existing schedule fixtures still pass. A local harness running PHP + on PostgreSQL matched native on all 72 compared responses except the two + refusals above. +- **Next.** Update, listing with visibility queries and page tokens, and + backfill; then narrowing the takeover schedule refusal (#568). + October 20, external payload storage diagnostic: - **Source.** Native serves `POST /api/storage/test` for operators, in the diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 5e9d9240f..f2f154701 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -142,6 +142,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.2" @@ -371,6 +377,37 @@ dependencies = [ "syn 3.0.6", ] +[[package]] +name = "defmt" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 3.0.6", +] + +[[package]] +name = "defmt-parser" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0" +dependencies = [ + "thiserror", +] + [[package]] name = "digest" version = "0.10.7" @@ -421,6 +458,7 @@ dependencies = [ "futures-util", "getrandom 0.3.4", "hmac", + "jiff", "libc", "phpserz", "regex-lite", @@ -942,6 +980,50 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jiff" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b005715dcbeb0089a3c0dab99f2ff1cc3b2525323552703d648585d342a383" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc9817253cf7c7ee4684451bd327e88d6f3658014e54a29198625590650695c" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8377070c6bae868759445e5a77f66d84f0b72f3a054bfb00e6d038b8282da7" + [[package]] name = "js-sys" version = "0.3.106" @@ -1166,6 +1248,21 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.6" @@ -1295,7 +1392,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.2", ] [[package]] @@ -1324,7 +1421,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" dependencies = [ - "bitflags", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys", @@ -1634,7 +1731,7 @@ dependencies = [ name = "sqlx-mysql" version = "0.9.0" dependencies = [ - "bitflags", + "bitflags 2.13.2", "byteorder", "bytes", "chrono", @@ -1663,7 +1760,7 @@ checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64", - "bitflags", + "bitflags 2.13.2", "byteorder", "chrono", "crc", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index fc1aa4842..69291acba 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -28,6 +28,7 @@ tokio-util = { version = "=0.7.19", features = ["io", "io-util"] } tokio = { version = "1", features = ["macros", "rt-multi-thread", "net", "signal", "sync", "time", "fs", "io-util"] } ulid = "1" chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] } +jiff = { version = "0.2.38", default-features = false, features = ["std", "tzdb-bundle-always"] } [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/rust/src/runtime/execution.rs b/rust/src/runtime/execution.rs index 8081ef4fb..c0938cc0c 100644 --- a/rust/src/runtime/execution.rs +++ b/rust/src/runtime/execution.rs @@ -352,20 +352,29 @@ impl Runtime { Storage::MySql(store) => store.start(body, memo, external).await, } } - pub(crate) async fn create_schedule(&self, body: Value, context: Value) -> Result { - let definition = super::schedules::definition(&body, super::store::now())?; - let arguments = super::envelope(&definition.action, "input")?; - super::queries::decode( - self.signal_codec.clone(), - arguments, - true, - "invalid_schedule_arguments", - ) - .await?; + pub(crate) async fn create_schedule( + &self, + body: Value, + schedule_id: String, + created: Value, + paused: Value, + ) -> Result { + let mut definition = super::schedules::definition(&body)?; + definition.schedule_id = Some(schedule_id); + if definition.action.get("input").is_some_and(|input| !input.is_null()) { + let arguments = super::envelope(&definition.action, "input")?; + super::queries::decode( + self.signal_codec.clone(), + arguments, + true, + "invalid_schedule_arguments", + ) + .await?; + } match &*self.storage { - Storage::Sqlite(store) => store.create_schedule(definition, context).await, - Storage::Postgres(store) => store.create_schedule(definition, context).await, - Storage::MySql(store) => store.create_schedule(definition, context).await, + Storage::Sqlite(store) => store.create_schedule(definition, created, paused).await, + Storage::Postgres(store) => store.create_schedule(definition, created, paused).await, + Storage::MySql(store) => store.create_schedule(definition, created, paused).await, } } delegate!(describe_schedule(schedule: &str) -> Result); @@ -379,8 +388,8 @@ impl Runtime { ) -> Result ); delegate!(schedule_history(schedule: &str, after: i64, limit: i64) -> Result); - delegate!(edit_schedule(schedule: &str, operation: &str, context: Value) -> Result); - delegate!(trigger_schedule(schedule: &str, context: Value) -> Result); + delegate!(edit_schedule(schedule: &str, operation: &str, note: Option, context: Value) -> Result); + delegate!(trigger_schedule(schedule: &str, overlap: Option, context: Value) -> Result); pub(crate) async fn signal( &self, workflow_id: &str, diff --git a/rust/src/runtime/http.rs b/rust/src/runtime/http.rs index 5be3b8bf8..763f8f895 100644 --- a/rust/src/runtime/http.rs +++ b/rust/src/runtime/http.rs @@ -424,6 +424,10 @@ fn named_namespace_operation(method: &str, path: &str) -> bool { if path.starts_with("/api/external-payloads/v1/") { return method == "GET"; } + // Schedules are namespace-scoped rows, as in PHP. + if path == "/api/schedules" || path.starts_with("/api/schedules/") { + return true; + } // Keep unimplemented families from reaching default-namespace selectors. // Further families require their own shared namespace fixtures. if path == "/api/workflows" { diff --git a/rust/src/runtime/laravel_rules.rs b/rust/src/runtime/laravel_rules.rs new file mode 100644 index 000000000..f4f510aaa --- /dev/null +++ b/rust/src/runtime/laravel_rules.rs @@ -0,0 +1,360 @@ +//! The subset of Laravel request validation published PHP Server's control +//! plane uses, with Laravel's own messages, attribute names and order. +//! +//! Laravel first trims every string in the body and turns empty strings into +//! null (its TrimStrings and ConvertEmptyStringsToNull middleware). It then +//! expands `*` rules over the present items, validates the plain rules in +//! their listed order and the expanded ones after them, and for each +//! attribute reports every failing rule, stopping after a failed presence +//! rule. A rule other than a presence rule ignores an absent attribute, and +//! `nullable` lets null pass. +use super::RuntimeError; +use axum::http::StatusCode; +use serde_json::{Map, Value, json}; + +#[derive(Clone, Copy, Debug)] +pub(crate) enum Rule { + Required, + Nullable, + /// Required when the named attribute is present and not empty. + RequiredWith(&'static str), + String, + Array, + Integer, + Boolean, + /// A size floor: a number, string length or item count, by type rule. + Min(i64), + Max(i64), + In(&'static [&'static str]), +} + +pub(crate) type Rules = [(&'static str, &'static [Rule])]; + +/// Laravel's TrimStrings and ConvertEmptyStringsToNull, applied throughout. +pub(crate) fn normalize(value: &Value) -> Value { + match value { + Value::String(text) => { + let trimmed = text.trim_matches(|character: char| { + character.is_whitespace() || matches!(character, '\u{feff}' | '\u{200b}' | '\u{200e}') + }); + if trimmed.is_empty() { + Value::Null + } else { + json!(trimmed) + } + } + Value::Array(items) => Value::Array(items.iter().map(normalize).collect()), + Value::Object(fields) => Value::Object( + fields + .iter() + .map(|(key, value)| (key.clone(), normalize(value))) + .collect(), + ), + other => other.clone(), + } +} + +/// The value at a dotted path, as `data_get`/`Arr::has` see it. +fn lookup<'a>(data: &'a Value, path: &str) -> Option<&'a Value> { + path.split('.').try_fold(data, |value, key| match value { + Value::Object(fields) => fields.get(key), + Value::Array(items) => key.parse::().ok().and_then(|index| items.get(index)), + _ => None, + }) +} + +/// The concrete attributes a `*` rule covers, in data order. +fn expand(data: &Value, pattern: &str) -> Vec { + let mut paths = vec![String::new()]; + for segment in pattern.split('.') { + let mut next = Vec::new(); + for prefix in paths { + let joined = |key: &str| { + if prefix.is_empty() { + key.to_owned() + } else { + format!("{prefix}.{key}") + } + }; + if segment == "*" { + let parent = if prefix.is_empty() { + Some(data) + } else { + lookup(data, &prefix) + }; + match parent { + Some(Value::Array(items)) => { + next.extend((0..items.len()).map(|index| joined(&index.to_string()))) + } + Some(Value::Object(fields)) => next.extend(fields.keys().map(|key| joined(key))), + _ => {} + } + } else { + next.push(joined(segment)); + } + } + paths = next; + } + paths +} + +/// Laravel's displayable attribute: expanded `*` attributes keep their raw +/// path; others read with spaces for underscores. +fn display(attribute: &str, expanded: bool) -> String { + if expanded { + attribute.to_owned() + } else { + attribute.replace('_', " ") + } +} + +fn filled(value: Option<&Value>) -> bool { + match value { + None | Some(Value::Null) => false, + Some(Value::String(text)) => !text.trim().is_empty(), + Some(Value::Array(items)) => !items.is_empty(), + Some(Value::Object(fields)) => !fields.is_empty(), + Some(_) => true, + } +} + +/// `FILTER_VALIDATE_INT` on a decoded JSON value. +fn integer(value: &Value) -> Option { + match value { + Value::Number(number) => number.as_i64().or_else(|| { + number + .as_f64() + .filter(|float| float.fract() == 0.0 && float.abs() < 9.2e18) + .map(|float| float as i64) + }), + Value::String(text) => { + let text = text.trim(); + let digits = text.strip_prefix(['+', '-']).unwrap_or(text); + if digits.is_empty() + || !digits.bytes().all(|byte| byte.is_ascii_digit()) + || (digits.len() > 1 && digits.starts_with('0')) + { + return None; + } + text.parse().ok() + } + Value::Bool(true) => Some(1), + _ => None, + } +} + +/// PHP's `(string)` of a scalar, as `in` compares it. +fn php_string(value: &Value) -> Option { + match value { + Value::String(text) => Some(text.clone()), + Value::Number(number) => Some(number.to_string()), + Value::Bool(true) => Some("1".into()), + Value::Bool(false) | Value::Null => Some(String::new()), + _ => None, + } +} + +fn message(rule: Rule, attribute: &str, rules: &[Rule], value: Option<&Value>) -> String { + let kind = if rules.iter().any(|rule| matches!(rule, Rule::Integer)) { + "numeric" + } else if rules.iter().any(|rule| matches!(rule, Rule::Array)) || value.is_some_and(Value::is_array) { + "array" + } else { + "string" + }; + match rule { + Rule::Required => format!("The {attribute} field is required."), + Rule::RequiredWith(other) => { + format!("The {attribute} field is required when {} is present.", display(other, false)) + } + Rule::String => format!("The {attribute} field must be a string."), + Rule::Array => format!("The {attribute} field must be an array."), + Rule::Integer => format!("The {attribute} field must be an integer."), + Rule::Boolean => format!("The {attribute} field must be true or false."), + Rule::In(_) => format!("The selected {attribute} is invalid."), + Rule::Min(limit) => match kind { + "numeric" => format!("The {attribute} field must be at least {limit}."), + "array" => format!("The {attribute} field must have at least {limit} items."), + _ => format!("The {attribute} field must be at least {limit} characters."), + }, + Rule::Max(limit) => match kind { + "numeric" => format!("The {attribute} field must not be greater than {limit}."), + "array" => format!("The {attribute} field must not have more than {limit} items."), + _ => format!("The {attribute} field must not be greater than {limit} characters."), + }, + Rule::Nullable => String::new(), + } +} + +fn size(value: &Value, rules: &[Rule]) -> Option { + if rules.iter().any(|rule| matches!(rule, Rule::Integer)) { + return integer(value).map(|number| number as f64); + } + match value { + Value::Array(items) => Some(items.len() as f64), + Value::Object(fields) => Some(fields.len() as f64), + Value::String(text) => Some(text.chars().count() as f64), + Value::Number(number) => number.as_f64(), + _ => None, + } +} + +fn passes(rule: Rule, data: &Value, value: Option<&Value>, rules: &[Rule]) -> bool { + match rule { + Rule::Required => filled(value), + Rule::RequiredWith(other) => !filled(lookup(data, other)) || filled(value), + Rule::Nullable => true, + Rule::String => value.is_some_and(Value::is_string), + Rule::Array => value.is_some_and(|value| value.is_array() || value.is_object()), + Rule::Integer => value.and_then(integer).is_some(), + Rule::Boolean => matches!( + value, + Some(Value::Bool(_)) + ) || value.is_some_and(|value| { + value.as_i64().is_some_and(|number| number == 0 || number == 1) + || value.as_str().is_some_and(|text| text == "0" || text == "1") + }), + Rule::In(allowed) => value + .and_then(php_string) + .is_some_and(|text| allowed.contains(&text.as_str())), + Rule::Min(limit) => value + .and_then(|value| size(value, rules)) + .is_some_and(|size| size >= limit as f64), + Rule::Max(limit) => value + .and_then(|value| size(value, rules)) + .is_some_and(|size| size <= limit as f64), + } +} + +/// Validates `body` (already normalized) and returns Laravel's 422 failure, +/// or nothing when every rule passes. +pub(crate) fn validate(body: &Value, rules: &Rules) -> Result<(), RuntimeError> { + let mut checks: Vec<(String, bool, &[Rule])> = Vec::new(); + let mut expanded = Vec::new(); + for (pattern, list) in rules { + if pattern.contains('*') { + expanded.extend(expand(body, pattern).into_iter().map(|path| (path, true, *list))); + } else { + checks.push(((*pattern).to_owned(), false, *list)); + } + } + checks.extend(expanded); + let mut errors: Vec<(String, Vec)> = Vec::new(); + for (attribute, raw, list) in checks { + let value = lookup(body, &attribute); + let nullable = list.iter().any(|rule| matches!(rule, Rule::Nullable)); + let name = display(&attribute, raw); + let mut messages = Vec::new(); + for rule in list { + let implicit = matches!(rule, Rule::Required | Rule::RequiredWith(_)); + if matches!(rule, Rule::Nullable) { + continue; + } + // presentOrRuleIsImplicit, then isNotNullIfMarkedAsNullable. + let blank = value.is_some_and(|value| value.as_str().is_some_and(|text| text.trim().is_empty())); + if !implicit && (value.is_none() || blank) { + continue; + } + if !implicit && nullable && value.is_some_and(Value::is_null) { + continue; + } + if !passes(*rule, body, value, list) { + messages.push(message(*rule, &name, list, value)); + if implicit { + break; + } + } + } + if !messages.is_empty() { + errors.push((attribute, messages)); + } + } + match failure(errors) { + Some(error) => Err(error), + None => Ok(()), + } +} + +/// Laravel's 422 ValidationException body: the first message, a count of the +/// rest, and every attribute's messages under `errors` and `validation_errors`. +pub(crate) fn failure(errors: Vec<(String, Vec)>) -> Option { + let total: usize = errors.iter().map(|(_, messages)| messages.len()).sum(); + let mut message = errors.first()?.1.first()?.clone(); + match total - 1 { + 0 => {} + 1 => message.push_str(" (and 1 more error)"), + more => message.push_str(&format!(" (and {more} more errors)")), + } + let errors: Map = errors + .into_iter() + .map(|(field, messages)| (field, json!(messages))) + .collect(); + Some(RuntimeError::Protocol { + status: StatusCode::UNPROCESSABLE_ENTITY, + response: json!({"message":message,"reason":"validation_failed", + "errors":errors.clone(),"validation_errors":errors}), + }) +} + +/// Laravel's `validated()`: the normalized body restricted to the ruled +/// top-level keys, keeping whatever those keys hold. +pub(crate) fn validated(body: &Value, rules: &Rules) -> Value { + let mut result = Map::new(); + for (pattern, _) in rules { + let top = pattern.split('.').next().unwrap_or(pattern); + if let Some(value) = body.get(top) { + result.insert(top.to_owned(), value.clone()); + } + } + Value::Object(result) +} + +#[cfg(test)] +mod tests { + use super::*; + + const STORE: &Rules = &[ + ("spec", &[Rule::Required, Rule::Array]), + ("spec.cron_expressions.*", &[Rule::String]), + ("spec.intervals.*.every", &[Rule::RequiredWith("spec.intervals"), Rule::String, Rule::Max(64)]), + ("spec.timezone", &[Rule::Nullable, Rule::String, Rule::Max(64)]), + ("action.workflow_type", &[Rule::Required, Rule::String]), + ("jitter_seconds", &[Rule::Nullable, Rule::Integer, Rule::Min(0), Rule::Max(3600)]), + ("paused", &[Rule::Nullable, Rule::Boolean]), + ]; + + fn errors(body: Value) -> Value { + match validate(&normalize(&body), STORE) { + Err(RuntimeError::Protocol { response, .. }) => response, + other => panic!("{other:?}"), + } + } + + #[test] + fn laravel_names_messages_and_order() { + let response = errors(json!({"spec":{"cron_expressions":[5,"x"],"intervals":[{"offset":"x"}],"timezone":7}, + "action":{"workflow_type":" "},"jitter_seconds":4000,"paused":"maybe"})); + assert_eq!(response["message"], "The spec.timezone field must be a string. (and 5 more errors)"); + assert_eq!( + response["errors"], + json!({"spec.timezone":["The spec.timezone field must be a string."], + "action.workflow_type":["The action.workflow type field is required."], + "jitter_seconds":["The jitter seconds field must not be greater than 3600."], + "paused":["The paused field must be true or false."], + "spec.cron_expressions.0":["The spec.cron_expressions.0 field must be a string."], + "spec.intervals.0.every":["The spec.intervals.0.every field is required when spec.intervals is present."]}) + ); + assert_eq!(response["errors"], response["validation_errors"]); + } + + #[test] + fn absent_attributes_skip_rules_other_than_presence() { + assert!(validate(&normalize(&json!({"spec":{"timezone":"UTC"},"action":{"workflow_type":"echo"}})), STORE).is_ok()); + let response = errors(json!({})); + assert_eq!( + response["errors"], + json!({"spec":["The spec field is required."], + "action.workflow_type":["The action.workflow type field is required."]}) + ); + } +} diff --git a/rust/src/runtime/memos.rs b/rust/src/runtime/memos.rs index 0ddfaeb83..5259ee3ca 100644 --- a/rust/src/runtime/memos.rs +++ b/rust/src/runtime/memos.rs @@ -142,7 +142,7 @@ fn encode(values: BTreeMap) -> Result { Ok(prepared) } -fn initial(value: Value) -> Result { +pub(super) fn initial(value: Value) -> Result { let entries = match value { Value::Null => return Ok(BTreeMap::new()), Value::Array(values) if values.is_empty() => return Ok(BTreeMap::new()), diff --git a/rust/src/runtime/mod.rs b/rust/src/runtime/mod.rs index 7eda7c920..b8203a1cf 100644 --- a/rust/src/runtime/mod.rs +++ b/rust/src/runtime/mod.rs @@ -44,6 +44,10 @@ mod query_authority; mod run_observation; mod run_observation_store; mod run_observation_waits; +pub(crate) mod laravel_rules; +pub(crate) mod schedule_cron; +pub(crate) mod schedule_spec; +pub(crate) mod schedule_zones; mod schedule_execution; mod schedule_http; mod schedule_storage; diff --git a/rust/src/runtime/mysql.rs b/rust/src/runtime/mysql.rs index a4a76dbf5..2371c724c 100644 --- a/rust/src/runtime/mysql.rs +++ b/rust/src/runtime/mysql.rs @@ -677,11 +677,11 @@ mod tests { "spec":{"intervals":[{"every":"P1D"}],"timezone":"UTC"}, "action":{"workflow_type":"echo","task_queue":"test","input":{"codec":"avro","blob":input}, "execution_timeout_seconds":3600,"run_timeout_seconds":600}, - "overlap_policy":"skip","jitter_seconds":0}),super::super::store::now()).unwrap(); + "overlap_policy":"skip","jitter_seconds":0})).unwrap(); // This isolated synthetic test surfaces typed-driver failures which // the public HTTP boundary correctly redacts as storage_unavailable. store - .create_schedule(definition, json!({"test":"schedule admission"})) + .create_schedule(definition, json!({"test":"schedule admission"}), json!({})) .await .unwrap(); let history = store diff --git a/rust/src/runtime/schedule_cron.rs b/rust/src/runtime/schedule_cron.rs new file mode 100644 index 000000000..947e6b768 --- /dev/null +++ b/rust/src/runtime/schedule_cron.rs @@ -0,0 +1,914 @@ +//! Cron occurrences exactly as published PHP Server computes them. +//! +//! PHP Server 2.5.14 uses `dragonmantank/cron-expression` 3.6.0 through +//! `WorkflowSchedule::nextCronOccurrence` (`getNextRunDate($after, 0, false, +//! $timezone)`). This is a direct port of that library's search, including its +//! field quirks, its 1,000-step limit and PHP's wall-clock rules around +//! daylight-saving changes. The differential corpus in the tests holds PHP's +//! own answers. Zone rules come from the bundled IANA database, so a rule that +//! changed after PHP's bundled release can give a different instant. +use jiff::{ + Timestamp, + civil::{Date, DateTime}, + tz::{AmbiguousOffset, TimeZone}, +}; +use super::schedule_zones::PhpZone; +use std::cmp::Ordering; + +/// The PHP exception a cron evaluation ends with, by class name. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum CronError { + /// An invalid expression or field value (`InvalidArgumentException`). + InvalidArgument, + /// A step range outside its field (`OutOfRangeException`). + OutOfRange, + /// No occurrence within the search limit, or no nearest weekday + /// (`RuntimeException`). + Runtime, + /// A day-of-month and day-of-week search that found neither (`TypeError`). + Type, + /// A negative step, which PHP 8.3's `range()` refuses (`ValueError`). + Value, + /// A zone name PHP does not know (`DateInvalidTimeZoneException`). + UnknownTimezone, +} + +impl CronError { + pub(crate) fn php_class(self) -> &'static str { + match self { + Self::InvalidArgument => "InvalidArgumentException", + Self::OutOfRange => "OutOfRangeException", + Self::Runtime => "RuntimeException", + Self::Type => "TypeError", + Self::Value => "ValueError", + Self::UnknownTimezone => "DateInvalidTimeZoneException", + } + } +} + +type Result = std::result::Result; + +const ALIASES: [(&str, &str); 7] = [ + ("@yearly", "0 0 1 1 *"), + ("@annually", "0 0 1 1 *"), + ("@monthly", "0 0 1 * *"), + ("@weekly", "0 0 * * 0"), + ("@daily", "0 0 * * *"), + ("@midnight", "0 0 * * *"), + ("@hourly", "0 * * * *"), +]; +const MINUTE: usize = 0; +const HOUR: usize = 1; +const DAY: usize = 2; +const MONTH: usize = 3; +const WEEKDAY: usize = 4; +/// The library's evaluation order (it skips the unused year position). +const ORDER: [usize; 5] = [MONTH, DAY, WEEKDAY, HOUR, MINUTE]; +const MAX_ITERATIONS: usize = 1000; + +/// A validated cron expression. +#[derive(Clone, Debug)] +pub(crate) struct Cron { + parts: Vec, +} + +impl Cron { + /// `new CronExpression($expression)`, refusing what PHP refuses. + pub(crate) fn parse(expression: &str) -> Result { + let lower = expression.to_lowercase(); + let expression = ALIASES + .iter() + .find(|(alias, _)| *alias == lower) + .map_or(expression, |(_, value)| value); + let parts: Vec = expression + .split(is_pcre_space) + .filter(|part| !part.is_empty()) + .map(str::to_owned) + .collect(); + let question = |index: usize| parts.get(index).is_some_and(|part| part == "?"); + if parts.len() < 5 + || question(0) + || question(1) + || question(3) + || (question(2) && question(4)) + { + return Err(CronError::InvalidArgument); + } + for (position, part) in parts.iter().enumerate() { + let field = Field::at(position).ok_or(CronError::InvalidArgument)?; + if !field.validate(part) { + return Err(CronError::InvalidArgument); + } + } + Ok(Self { parts }) + } + + /// `CronExpression::isValidExpression`. + pub(crate) fn is_valid(expression: &str) -> bool { + Self::parse(expression).is_ok() + } + + /// The first occurrence strictly after `after`'s minute, in `zone`. + pub(crate) fn next_after(&self, after: Timestamp, zone: &str) -> Result { + let zone = PhpZone::parse(zone).ok_or(CronError::UnknownTimezone)?; + let found = run_date(&self.parts, after, &zone)?; + Timestamp::from_second(found.seconds).map_err(|_| CronError::Runtime) + } +} + +fn is_pcre_space(character: char) -> bool { + matches!(character, ' ' | '\t' | '\n' | '\r' | '\x0b' | '\x0c') +} + +/// A PHP `DateTime` with a named zone, to whole seconds: cron search never +/// carries a fraction past its first minute truncation. +#[derive(Clone)] +struct Moment<'z> { + seconds: i64, + zone: &'z PhpZone, +} + +impl<'z> Moment<'z> { + fn timestamp(&self) -> Timestamp { + Timestamp::from_second(self.seconds).expect("cron search stays within jiff's range") + } + fn local(&self) -> DateTime { + self.zone.zone.to_datetime(self.timestamp()) + } + fn offset(&self) -> i64 { + i64::from(self.zone.zone.to_offset(self.timestamp()).seconds()) + } + fn dst(&self) -> bool { + self.zone.dst(self.timestamp()) + } + fn minute(&self) -> i64 { + i64::from(self.local().minute()) + } + fn hour(&self) -> i64 { + i64::from(self.local().hour()) + } + fn day(&self) -> i64 { + i64::from(self.local().day()) + } + fn month(&self) -> i64 { + i64::from(self.local().month()) + } + fn year(&self) -> i16 { + self.local().year() + } + fn days_in_month(&self) -> i64 { + i64::from(self.local().date().days_in_month()) + } + /// PHP's `N`: Monday 1 to Sunday 7. + fn iso_weekday(&self) -> i64 { + i64::from(self.local().weekday().to_monday_one_offset()) + } + /// PHP's `w`: Sunday 0 to Saturday 6. + fn weekday(&self) -> i64 { + i64::from(self.local().weekday().to_sunday_zero_offset()) + } + /// The library's `timezoneSafeModify`: elapsed seconds, applied in UTC. + fn shift(&mut self, seconds: i64) -> Result<()> { + self.seconds = self + .seconds + .checked_add(seconds) + .filter(|value| Timestamp::from_second(*value).is_ok()) + .ok_or(CronError::Runtime)?; + Ok(()) + } + /// Moves to a wall-clock time in this zone as PHP does: a time skipped by + /// a forward change keeps the offset from before it, and a repeated time + /// keeps this moment's daylight-saving flag. + fn set_local(&mut self, local: DateTime) -> Result<()> { + let wall = local + .to_zoned(TimeZone::UTC) + .map_err(|_| CronError::Runtime)? + .timestamp() + .as_second(); + let current = self.dst(); + self.seconds = match self.zone.zone.to_ambiguous_timestamp(local).offset() { + AmbiguousOffset::Unambiguous { offset } => wall - i64::from(offset.seconds()), + AmbiguousOffset::Gap { before, .. } => wall - i64::from(before.seconds()), + AmbiguousOffset::Fold { before, after } => { + let earlier = wall - i64::from(before.seconds()); + let later = wall - i64::from(after.seconds()); + let dst = |seconds: i64| { + Timestamp::from_second(seconds) + .map(|at| self.zone.dst(at)) + .unwrap_or(false) + }; + if dst(earlier) == current || dst(later) != current { + earlier + } else { + later + } + } + }; + Timestamp::from_second(self.seconds).map_err(|_| CronError::Runtime)?; + Ok(()) + } + /// `setTime($hour, $minute)`. + fn set_time(&mut self, hour: i8, minute: i8) -> Result<()> { + let date = self.local().date(); + self.set_local(date.at(hour, minute, 0, 0)) + } + /// `add(new DateInterval('P1D'))`: the same wall-clock time a day later. + fn add_day(&mut self) -> Result<()> { + let local = self.local(); + let next = local.date().tomorrow().map_err(|_| CronError::Runtime)?; + self.set_local(next.to_datetime(local.time())) + } + /// `modify('first day of next month')`. + fn first_day_of_next_month(&mut self) -> Result<()> { + let local = self.local(); + let first = local.date().first_of_month(); + let next = first + .checked_add(jiff::Span::new().months(1)) + .map_err(|_| CronError::Runtime)?; + self.set_local(next.to_datetime(local.time())) + } +} + +/// A local wall-clock rendering used for the library's `Y-m-d H:i:s` sort. +fn wall(moment: &Moment<'_>) -> String { + moment.local().strftime("%Y-%m-%d %H:%M:%S").to_string() +} + +/// `getRunDate($currentTime, 0, false, false, $timeZone)`. +fn run_date<'z>(parts: &[String], after: Timestamp, zone: &'z PhpZone) -> Result> { + let part = |position: usize| { + parts + .get(position) + .map(String::as_str) + .filter(|part| *part != "*") + }; + if let (Some(day), Some(weekday)) = (part(DAY), part(WEEKDAY)) { + // Either field may match: search each half separately. + let dom = format!("{} {} {} {} *", parts[0], parts[1], parts[2], parts[3]); + let dow = format!("{} {} * {} {}", parts[0], parts[1], parts[3], parts[4]); + let mut combined = Vec::new(); + for (expression, field) in [(dom, day), (dow, weekday)] { + let half = Cron::parse(&expression)?; + // getMultipleRunDates stops at a RuntimeException only. + match run_date(&half.parts, after, zone) { + Ok(found) if field != "?" => combined.push(found), + Ok(_) | Err(CronError::Runtime) => {} + Err(error) => return Err(error), + } + } + // A stable sort by local wall-clock text, as usort compares them. + combined.sort_by_key(wall); + return combined.into_iter().next().ok_or(CronError::Type); + } + // Truncate to the current minute in the target zone. + let current = Moment { + seconds: after.as_second().div_euclid(60) * 60, + zone, + }; + let mut next = current.clone(); + 'search: for _ in 0..MAX_ITERATIONS { + for position in ORDER { + let Some(value) = part(position) else { + continue; + }; + let field = Field::at(position).expect("validated position"); + let satisfied = if value.contains(',') { + let mut any = false; + for item in value.split(',') { + if field.satisfied_by(&next, item.trim())? { + any = true; + break; + } + } + any + } else { + field.satisfied_by(&next, value)? + }; + if !satisfied { + field.increment(&mut next, Some(value))?; + continue 'search; + } + } + if next.seconds == current.seconds { + Field::Minute.increment(&mut next, part(MINUTE))?; + continue; + } + return Ok(next); + } + Err(CronError::Runtime) +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Field { + Minute, + Hour, + DayOfMonth, + Month, + DayOfWeek, +} + +const MONTHS: [&str; 12] = [ + "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC", +]; +const WEEKDAYS: [&str; 7] = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]; + +impl Field { + fn at(position: usize) -> Option { + Some(match position { + MINUTE => Self::Minute, + HOUR => Self::Hour, + DAY => Self::DayOfMonth, + MONTH => Self::Month, + WEEKDAY => Self::DayOfWeek, + _ => return None, + }) + } + + fn range(self) -> (i64, i64) { + match self { + Self::Minute => (0, 59), + Self::Hour => (0, 23), + Self::DayOfMonth => (1, 31), + Self::Month => (1, 12), + Self::DayOfWeek => (0, 7), + } + } + + /// `convertLiterals`: a whole-value name lookup, case-insensitive. + fn convert_literals(self, value: &str) -> String { + let names: &[&str] = match self { + Self::Month => &MONTHS, + Self::DayOfWeek => &WEEKDAYS, + _ => &[], + }; + let upper = value.to_ascii_uppercase(); + names + .iter() + .position(|name| *name == upper) + .map_or_else(|| value.to_owned(), |index| (index + 1).to_string()) + } + + /// `validate`, including each subclass's additions. + fn validate(self, value: &str) -> bool { + match self { + Self::DayOfMonth => { + let basic = self.basic_validate(value); + if value.contains(',') && (value.contains('W') || value.contains('L')) { + return false; + } + if basic { + return true; + } + if value == "?" || value == "L" { + return true; + } + match value.strip_suffix('W') { + Some(prefix) => self.validate(prefix), + None => false, + } + } + Self::DayOfWeek => { + if self.basic_validate(value) { + return true; + } + if value == "?" { + return true; + } + if value.contains('#') { + let mut chunks = value.split('#'); + let weekday = self.convert_literals(chunks.next().unwrap_or("")); + let nth = chunks.next().unwrap_or(""); + if self.basic_validate(&weekday) + && php_is_numeric(nth) + && (1..=5).contains(&php_int(nth)) + { + return true; + } + } + match value.strip_suffix('L') { + Some(prefix) => self.validate(prefix), + None => false, + } + } + _ => self.basic_validate(value), + } + } + + /// `AbstractField::validate`; recursion uses the subclass rules. + fn basic_validate(self, value: &str) -> bool { + let value = self.convert_literals(value); + if value == "*" { + return true; + } + if value.contains(',') { + return value.split(',').all(|item| self.validate(item)); + } + if value.contains('/') { + let mut chunks = value.split('/'); + let range = chunks.next().unwrap_or(""); + let step = chunks.next().unwrap_or(""); + if php_is_numeric(range) { + return false; + } + return self.validate(range) && php_filter_int(step).is_some_and(|step| step != 0); + } + if value.contains('-') { + if value.matches('-').count() > 1 { + return false; + } + let (start, end) = value.split_once('-').expect("one dash"); + let start = self.convert_literals(start); + let end = self.convert_literals(end); + if start == "*" || end == "*" { + return false; + } + return self.validate(&start) && self.validate(&end); + } + if !php_is_numeric(&value) || value.contains('.') { + return false; + } + let (low, high) = self.range(); + (low..=high).contains(&php_int(&value)) + } + + /// `isSatisfiedBy($date, $value, false)`. + fn satisfied_by(self, date: &Moment<'_>, value: &str) -> Result { + if value == "?" && self != Self::Hour { + return Ok(true); + } + match self { + Self::Minute => self.satisfied(date.minute(), value), + Self::Month => self.satisfied(date.month(), &self.convert_literals(value)), + Self::Hour => { + let hour = date.hour(); + if self.satisfied(hour, value)? { + return Ok(true); + } + // On the edge of an offset change, the skipped or repeated + // hour also counts. + if !transition_within_hour(date) { + return Ok(false); + } + let mut before = date.clone(); + before.shift(-3600)?; + let mut after = date.clone(); + after.shift(3600)?; + let change = after.offset() - before.offset(); + if change >= 3600 { + return self.satisfied(hour - 1, value); + } + if change <= -3600 { + return self.satisfied(hour + 1, value); + } + Ok(false) + } + Self::DayOfMonth => { + if value == "L" { + return Ok(date.day() == date.days_in_month()); + } + if let Some(position) = value.find('W').filter(|position| *position > 0) { + let target = php_int(&value[..position]); + return match nearest_weekday(date.year(), date.month(), target) { + Some(day) => Ok(date.day() == day), + None => Err(CronError::Runtime), + }; + } + self.satisfied(date.day(), value) + } + Self::DayOfWeek => self.weekday_satisfied(date, &self.convert_literals(value)), + } + } + + fn weekday_satisfied(self, date: &Moment<'_>, value: &str) -> Result { + if let Some(position) = value.find('L').filter(|position| *position > 0) { + let weekday = php_int(&self.convert_literals(&value[..position])).rem_euclid(7); + let remaining = date.days_in_month() - date.day(); + return Ok(weekday == date.weekday() && remaining < 7); + } + if value.find('#').is_some_and(|position| position > 0) { + let mut chunks = value.split('#'); + let weekday = chunks.next().unwrap_or(""); + let nth = chunks.next().unwrap_or(""); + if !php_is_numeric(nth) { + return Err(CronError::InvalidArgument); + } + let nth = php_int(nth); + let weekday = if weekday == "0" { "7" } else { weekday }; + let weekday = php_int(&self.convert_literals(weekday)); + if !(0..=7).contains(&weekday) || !(1..=5).contains(&nth) { + return Err(CronError::InvalidArgument); + } + if date.iso_weekday() != weekday { + return Ok(false); + } + // The library counts from the first of the month. + let first = Date::new(date.year(), date.month() as i8, 1).map_err(|_| CronError::Runtime)?; + let last = date.days_in_month(); + let mut count = 0; + let mut day = 1; + while day < last + 1 { + let current = first + .checked_add(jiff::Span::new().days(day - 1)) + .map_err(|_| CronError::Runtime)?; + if i64::from(current.weekday().to_monday_one_offset()) == weekday { + count += 1; + if count >= nth { + break; + } + } + day += 1; + } + return Ok(date.day() == day); + } + let mut value = value.to_owned(); + if value.contains('-') { + let mut parts: Vec = value.split('-').map(str::to_owned).collect(); + if parts[0] == "7" { + parts[0] = "0".into(); + } else if parts.get(1).is_some_and(|part| part == "0") { + parts[1] = "7".into(); + } + value = parts.join("-"); + } + // Any character 7 selects ISO numbering (Sunday 7). + let iso = value.chars().any(|character| character == '7'); + let field = if iso { date.iso_weekday() } else { date.weekday() }; + self.satisfied(field, &value) + } + + /// `AbstractField::isSatisfied`. + fn satisfied(self, current: i64, value: &str) -> Result { + if value.contains('/') { + return self.in_increments(current, value); + } + if value.contains('-') { + let (start, end) = value.split_once('-').expect("one dash"); + let start = php_int(&self.convert_literals(start.trim())); + let end = php_int(&self.convert_literals(end.trim())); + return Ok(current >= start && current <= end); + } + Ok(value == "*" || current == php_int(value)) + } + + /// `isInIncrementsOfRanges`. + fn in_increments(self, current: i64, value: &str) -> Result { + let (range, step) = match value.split_once('/') { + Some((range, step)) => (range.trim(), step.trim()), + None => (value.trim(), "0"), + }; + if step == "0" { + return Ok(false); + } + let step = php_int(step); + let (low, high) = self.range(); + let full; + let range = if range == "*" { + full = format!("{low}-{high}"); + full.as_str() + } else { + range + }; + let (start, end) = match range.split_once('-') { + Some((start, end)) => (php_int(start), php_int(end)), + None => (php_int(range), php_int(range)), + }; + if start < low || start > high || start > end { + return Err(CronError::OutOfRange); + } + if end < low || end > high || end < start { + return Err(CronError::OutOfRange); + } + if step > high { + let count = high - low + 1; + return Ok(current == low + step.rem_euclid(count)); + } + if step > end - start { + return Ok(current == start); + } + if step < 1 { + // PHP 8.3's range() refuses a negative step for a rising range. + return Err(CronError::Value); + } + Ok(current >= start && current <= end && (current - start) % step == 0) + } + + /// `getRangeForExpression($expression, $max)` for one list item. + fn expand(self, expression: &str, max: i64) -> Result> { + let expression = self.convert_literals(expression); + if !expression.contains('-') && !expression.contains('/') { + return Ok(vec![php_int(&expression)]); + } + let (low, high) = self.range(); + let (offset, to, step) = if !expression.contains('/') { + let (offset, to) = expression.split_once('-').expect("a range"); + ( + php_int(&self.convert_literals(offset)), + php_int(&self.convert_literals(to)), + 1, + ) + } else { + let (range, step) = expression.split_once('/').expect("a step"); + let step = php_int(step.trim()); + let range = range.trim(); + let (offset, to) = match range.split_once('-') { + Some((offset, to)) => (offset, Some(to)), + None => (range, None), + }; + let offset = if offset == "*" { low } else { php_int(offset) }; + (offset, to.map_or(max, php_int), step) + }; + if step >= high { + let count = high - low + 1; + return Ok(vec![low + step.rem_euclid(count)]); + } + if step < 1 { + // PHP loops forever here; native refuses instead. + return Err(CronError::Value); + } + let mut values = Vec::new(); + let mut value = offset; + while value <= to { + values.push(value); + value += step; + } + values.sort_unstable(); + Ok(values) + } + + /// `increment($date, false, $parts)`. + fn increment(self, date: &mut Moment<'_>, parts: Option<&str>) -> Result<()> { + match self { + Self::Minute => { + let Some(parts) = parts else { + return date.shift(60); + }; + let mut items: Vec<&str> = parts.split(',').collect(); + items.sort_by(|left, right| php_compare(left, right)); + let mut minutes = Vec::new(); + for item in items { + minutes.extend(self.expand(item, 59)?); + } + // An empty expansion reads PHP's missing key as zero. + let target = minutes + .get(position(&minutes, date.minute())) + .copied() + .unwrap_or(0); + let mut minute = date.minute(); + if minute >= target { + date.shift((60 - minute) * 60)?; + minute = date.minute(); + } + date.shift((target - minute) * 60) + } + Self::Hour => { + let original = date.seconds; + let mut hours = Vec::new(); + for item in parts.unwrap_or("*").split(',') { + hours.extend(self.expand(item, 23)?); + } + let target = hours + .get(position(&hours, date.hour())) + .copied() + .unwrap_or(0); + let mut hour = date.hour(); + let dst = date.dst(); + let day = date.day(); + let offset = date.offset(); + if hour >= target { + date.shift((24 - hour) * 3600)?; + if date.day() != day + 1 && date.hour() != 0 { + let change = offset - date.offset(); + date.shift(change)?; + } + hour = date.hour(); + } + date.shift((target - hour) * 3600)?; + if !dst && date.dst() { + date.shift(-3600)?; + } + // setTimeHour: the top of the reached hour, never backwards. + let reached = date.hour() as i8; + date.set_time(reached, 0)?; + if date.seconds <= original { + date.shift(3600)?; + } + Ok(()) + } + Self::DayOfMonth | Self::DayOfWeek => { + date.add_day()?; + date.set_time(0, 0) + } + Self::Month => { + date.first_day_of_next_month()?; + date.set_time(0, 0) + } + } + } +} + +/// The library's next-target position in an expanded list, which assumes the +/// list is ascending even when it is not. +fn position(values: &[i64], current: i64) -> usize { + if values.len() > 1 { + for index in 0..values.len() - 1 { + if current >= values[index] && current < values[index + 1] { + return index + 1; + } + } + } + 0 +} + +/// Whether the zone changed offset (or rule) within the past hour. +fn transition_within_hour(date: &Moment<'_>) -> bool { + date.zone + .zone + .preceding(date.timestamp().checked_add(jiff::SignedDuration::from_secs(1)).unwrap_or(date.timestamp())) + .next() + .is_some_and(|transition| transition.timestamp().as_second() > date.seconds - 3600) +} + +/// `DayOfMonthField::getNearestWeekday`, with PHP's date overflow. +fn nearest_weekday(year: i16, month: i64, target: i64) -> Option { + let first = Date::new(year, month as i8, 1).ok()?; + let overflow = |day: i64| first.checked_add(jiff::Span::new().days(day - 1)).ok(); + let date = overflow(target)?; + let iso = |date: Date| i64::from(date.weekday().to_monday_one_offset()); + if iso(date) < 6 { + return Some(i64::from(date.day())); + } + let last = i64::from(date.days_in_month()); + for adjust in [-1, 1, -2, 2] { + let adjusted = target + adjust; + if adjusted > 0 && adjusted <= last { + let candidate = overflow(adjusted)?; + if iso(candidate) < 6 && i64::from(candidate.month()) == month { + return Some(i64::from(candidate.day())); + } + } + } + None +} + +/// PHP 8's `is_numeric` for the short ASCII strings cron fields contain. +fn php_is_numeric(value: &str) -> bool { + let trimmed = value.trim_start_matches(is_pcre_space).trim_end_matches(is_pcre_space); + let digits = trimmed.strip_prefix(['+', '-']).unwrap_or(trimmed); + let (mantissa, exponent) = match digits.find(['e', 'E']) { + Some(index) => (&digits[..index], Some(&digits[index + 1..])), + None => (digits, None), + }; + let (whole, fraction) = mantissa.split_once('.').unwrap_or((mantissa, "")); + let mantissa_ok = (!whole.is_empty() || !fraction.is_empty()) + && whole.bytes().all(|byte| byte.is_ascii_digit()) + && fraction.bytes().all(|byte| byte.is_ascii_digit()); + let exponent_ok = exponent.is_none_or(|exponent| { + let exponent = exponent.strip_prefix(['+', '-']).unwrap_or(exponent); + !exponent.is_empty() && exponent.bytes().all(|byte| byte.is_ascii_digit()) + }); + mantissa_ok && exponent_ok +} + +/// PHP 8's `(int)` cast of a string: its leading number, or zero. +pub(crate) fn php_int(value: &str) -> i64 { + let trimmed = value.trim_start_matches(is_pcre_space); + let end = trimmed + .char_indices() + .take_while(|(index, character)| { + character.is_ascii_digit() || (*index == 0 && matches!(character, '+' | '-')) + }) + .last() + .map_or(0, |(index, character)| index + character.len_utf8()); + let integer = &trimmed[..end]; + // A numeric string with a fraction or exponent casts through a float. + let rest = &trimmed[end..]; + if (rest.starts_with('.') || rest.starts_with(['e', 'E'])) && php_is_numeric(trimmed) { + return trimmed.parse::().map_or(0, |number| number as i64); + } + integer.parse().unwrap_or(0) +} + +/// `filter_var($value, FILTER_VALIDATE_INT)`. +fn php_filter_int(value: &str) -> Option { + let value = value.trim_matches(is_pcre_space); + let digits = value.strip_prefix(['+', '-']).unwrap_or(value); + if digits.is_empty() + || !digits.bytes().all(|byte| byte.is_ascii_digit()) + || (digits.len() > 1 && digits.starts_with('0')) + { + return None; + } + value.parse().ok() +} + +/// PHP 8's standard comparison of two strings, as `sort()` uses it. +fn php_compare(left: &str, right: &str) -> Ordering { + if php_is_numeric(left) && php_is_numeric(right) { + let left: f64 = left.trim().parse().unwrap_or(0.0); + let right: f64 = right.trim().parse().unwrap_or(0.0); + return left.partial_cmp(&right).unwrap_or(Ordering::Equal); + } + left.as_bytes().cmp(right.as_bytes()) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::Value; + + /// Published PHP 2.5.14's answers (scripts/conformance/cron-oracle): each case + /// is an expression, an instant, a zone and PHP's next occurrence or + /// exception class. + const CORPUS: &str = include_str!("../../tests/fixtures/cron/php-2.5.14-next-run.jsonl"); + + #[test] + fn next_occurrences_match_published_php() { + let mut mismatches = Vec::new(); + let mut cases = 0; + for line in CORPUS.lines().filter(|line| !line.trim().is_empty()) { + cases += 1; + let case: Value = serde_json::from_str(line).unwrap(); + let after: Timestamp = case["after"].as_str().unwrap().parse().unwrap(); + let tz = case["tz"].as_str().unwrap(); + let actual = Cron::parse(case["cron"].as_str().unwrap()) + .and_then(|cron| cron.next_after(after, tz)) + .map(|next| next.strftime("%Y-%m-%dT%H:%M:%S.%6fZ").to_string()) + .map_err(CronError::php_class); + let expected = match case["next"].as_str() { + Some(next) => Ok(next.to_owned()), + None => Err(case["error"].as_str().unwrap()), + }; + if actual != expected { + mismatches.push(format!("{line} -> {actual:?}")); + } + } + assert!(cases > 0); + assert!( + mismatches.is_empty(), + "{} of {cases} differ:\n{}", + mismatches.len(), + mismatches.join("\n") + ); + } + + #[test] + fn php_scalar_helpers_follow_php_8() { + assert!(php_is_numeric("007") && php_is_numeric("+5") && php_is_numeric("1e1")); + assert!(!php_is_numeric("5-10") && !php_is_numeric("*") && !php_is_numeric("")); + assert_eq!(php_int("1e1"), 10); + assert_eq!(php_int("12abc"), 12); + assert_eq!(php_int("MON"), 0); + assert_eq!(php_filter_int("+2"), Some(2)); + assert_eq!(php_filter_int("02"), None); + assert_eq!(php_compare("10", "5"), Ordering::Greater); + assert_eq!(php_compare("30", "5-10"), Ordering::Less); + } +} + + +#[cfg(test)] +mod zone_tests { + use super::*; + + /// Published PHP 2.5.14's answers for every zone spelling it was given: + /// each identifier it lists (with links), case variants, every + /// abbreviation, numeric offsets and refused strings. Each line is the + /// name and PHP's result for the three fixed cases below. + const ZONES: &str = include_str!("../../tests/fixtures/cron/php-2.5.14-zone-names.jsonl"); + const CASES: [(&str, &str); 3] = [ + ("0 12 * * *", "2026-07-01T10:17:00Z"), + ("30 2 * * *", "2026-03-08T05:00:00Z"), + ("15 1 * * *", "2026-10-25T00:20:00Z"), + ]; + + #[test] + fn zone_names_resolve_as_published_php_resolves_them() { + let mut mismatches = Vec::new(); + let lines: Vec<&str> = ZONES.lines().filter(|line| !line.trim().is_empty()).collect(); + for line in &lines { + let row: Vec = serde_json::from_str(line).unwrap(); + let actual: Vec = CASES + .iter() + .map(|(cron, after)| { + Cron::parse(cron) + .and_then(|cron| cron.next_after(after.parse().unwrap(), &row[0])) + .map_or_else( + |error| error.php_class().to_owned(), + |next| next.strftime("%Y-%m-%dT%H:%MZ").to_string(), + ) + }) + .collect(); + if actual[..] != row[1..] { + mismatches.push(format!("{line} -> {actual:?}")); + } + } + assert!(lines.len() > 1000); + assert!( + mismatches.is_empty(), + "{} of {} differ:\n{}", + mismatches.len(), + lines.len(), + mismatches.join("\n") + ); + } +} diff --git a/rust/src/runtime/schedule_execution.rs b/rust/src/runtime/schedule_execution.rs index 6b87bb86a..26c42dcfb 100644 --- a/rust/src/runtime/schedule_execution.rs +++ b/rust/src/runtime/schedule_execution.rs @@ -1,7 +1,9 @@ -//! Schedule controls and atomic admission through the existing start transition. +//! Schedule controls and firing as published PHP's `ScheduleManager` does +//! them, with each admission committed atomically with its original run. +use super::schedule_storage::{iso8601, occurrence as occurrence_text}; use super::transaction::Transaction; use super::{ - Result, + Result, RuntimeError, backend::Backend, refuse, schedules, store::{Store, id, now}, @@ -11,6 +13,36 @@ use chrono::{DateTime, Utc}; use serde_json::{Value, json}; use sqlx::{ColumnIndex, Decode, Encode, Executor, IntoArguments, Type}; +/// The result of one trigger evaluation (`ScheduleTriggerResult`). +pub(crate) enum Trigger { + Triggered { workflow: String, run: String }, + Buffered, + BufferFull, + Skipped(String), +} + +/// What the locked evaluation decided before any start. +enum Decision { + Start { policy: String }, + /// Close the latest run first (`cancel_other`/`terminate_other`). + Close { policy: String, workflow: String }, + Done(Trigger), +} + +const BUFFER_FULL: &str = "Previous workflow is still running and buffer is at capacity."; + +fn close_reason(policy: &str) -> String { + let reason = format!("Schedule overlap: {policy} policy applied."); + crate::codec::ValueCodec::new() + .and_then(|codec| { + codec.encode(&crate::codec::Value::Map(std::collections::BTreeMap::from([( + "reason".into(), + crate::codec::Value::String(reason), + )]))) + }) + .unwrap_or_default() +} + impl Store where for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, @@ -18,208 +50,615 @@ where for<'q> &'q str: Encode<'q, DB> + Type, for<'q> Option<&'q str>: Encode<'q, DB>, for<'q> Option: Encode<'q, DB>, + for<'q> Option: Encode<'q, DB>, for<'q> String: Encode<'q, DB> + Type, for<'q> i64: Encode<'q, DB> + Type, for<'r> i64: Decode<'r, DB>, usize: ColumnIndex, { + /// Pause, resume or delete a live schedule. pub(crate) async fn edit_schedule( &self, schedule: &str, operation: &str, + note: Option, context: Value, ) -> Result { let mut tx = self.begin().await?; - let row = Self::schedule_row(&mut tx, schedule, false).await?; + let row = self.schedule_row(&mut tx, schedule, false).await?; let key = DB::string(&row, "id")?; - let status = DB::string(&row, "status")?; let changed = now(); - let (event, outcome, payload) = match operation { - "pause" if status == "active" => { - Self::query("UPDATE workflow_schedules SET status='paused',paused_at=$1,updated_at=$2 WHERE id=$3") - .bind(DB::bind_time(changed)).bind(DB::bind_time(changed)).bind(&key).execute(&mut *tx).await?; - ("SchedulePaused", "paused", json!({"paused_at":changed})) + let outcome = match operation { + "pause" => { + Self::pause_in(&mut tx, &key, note.as_deref(), &context).await?; + "paused" } - "resume" if status == "paused" => { - let next = schedules::next(&DB::document_row(&row, "spec")?, changed)?; - Self::query("UPDATE workflow_schedules SET status='active',paused_at=NULL,next_fire_at=$1,updated_at=$2 WHERE id=$3") - .bind(DB::bind_time(next)).bind(DB::bind_time(changed)).bind(&key).execute(&mut *tx).await?; - ("ScheduleResumed", "resumed", json!({"next_fire_at":next})) + "resume" => { + if DB::string(&row, "status")? != "paused" { + // PHP raises an unhandled LogicException (500) here. + return Err(RuntimeError::Protocol { + status: StatusCode::CONFLICT, + response: json!({"message":format!("Schedule [{schedule}] is not paused."), + "reason":"schedule_not_paused","schedule_id":schedule}), + }); + } + let next = schedules::next_fire( + &DB::optional_document_row(&row, "spec")?.unwrap_or(Value::Null), + DB::number(&row, "jitter_seconds")?, + changed, + )?; + Self::set_next(&mut tx, &key, next).await?; + Self::query("UPDATE workflow_schedules SET status='active',paused_at=NULL,updated_at=$1 WHERE id=$2") + .bind(DB::bind_time(changed)).bind(&key).execute(&mut *tx).await?; + Self::append_schedule(&mut tx, &key, "ScheduleResumed", + json!({"next_fire_at":next.map(iso8601)}), Some(&context), None, None).await?; + if let Some(note) = ¬e { + Self::query("UPDATE workflow_schedules SET note=$1 WHERE id=$2") + .bind(note.as_str()).bind(&key).execute(&mut *tx).await?; + } + "resumed" } "delete" => { - Self::query("UPDATE workflow_schedules SET status='deleted',deleted_at=$1,next_fire_at=NULL,updated_at=$2 WHERE id=$3") + Self::query("UPDATE workflow_schedules SET status='deleted',deleted_at=$1,next_fire_at=NULL,buffered_actions=NULL,updated_at=$2 WHERE id=$3") .bind(DB::bind_time(changed)).bind(DB::bind_time(changed)).bind(&key).execute(&mut *tx).await?; - ( - "ScheduleDeleted", - "deleted", - json!({"reason":"deleted","deleted_at":changed}), - ) + Self::append_schedule(&mut tx, &key, "ScheduleDeleted", + json!({"reason":"deleted","deleted_at":iso8601(changed)}), Some(&context), None, None).await?; + "deleted" } - _ => return Err(refuse(StatusCode::CONFLICT, "schedule_status_conflict")), + _ => return Err(refuse(StatusCode::NOT_FOUND, "schedule_operation_not_found")), }; - Self::append_schedule(&mut tx, &key, event, payload, Some(&context), None, None).await?; tx.commit().await?; Ok(json!({"schedule_id":schedule,"outcome":outcome})) } - pub(crate) async fn trigger_schedule(&self, schedule: &str, context: Value) -> Result { - let mut tx = self.begin().await?; - let row = Self::schedule_row(&mut tx, schedule, false).await?; - if DB::string(&row, "status")? != "active" { - let skipped = - Self::skip_schedule(&mut tx, &row, "status_not_triggerable", Some(&context)) + async fn set_next(tx: &mut Transaction<'_, DB>, key: &str, next: Option>) -> Result<()> { + match next { + Some(next) => Self::query("UPDATE workflow_schedules SET next_fire_at=$1 WHERE id=$2") + .bind(DB::bind_time(next)) + .bind(key) + .execute(&mut **tx) + .await?, + None => Self::query("UPDATE workflow_schedules SET next_fire_at=NULL WHERE id=$1") + .bind(key) + .execute(&mut **tx) + .await?, + }; + Ok(()) + } + + async fn recompute_next(tx: &mut Transaction<'_, DB>, row: &DB::Row) -> Result<()> { + let next = schedules::next_fire( + &DB::optional_document_row(row, "spec")?.unwrap_or(Value::Null), + DB::number(row, "jitter_seconds")?, + now(), + )?; + Self::set_next(tx, &DB::string(row, "id")?, next).await + } + + /// `ScheduleController::trigger`: one manual evaluation, optionally with + /// another overlap policy. + pub(crate) async fn trigger_schedule( + &self, + schedule: &str, + overlap: Option, + context: Value, + ) -> Result { + let key = { + let mut tx = self.begin().await?; + let row = self.schedule_row(&mut tx, schedule, false).await?; + DB::string(&row, "id")? + }; + let result = match self + .trigger_detailed(&key, overlap.as_deref(), Some(&context), None, "scheduled") + .await + { + Ok(result) => result, + Err(error @ RuntimeError::Database(_)) => return Err(error), + // PHP answers any other failure with this 500 body. + Err(error) => { + return Err(RuntimeError::Protocol { + status: StatusCode::INTERNAL_SERVER_ERROR, + response: json!({"schedule_id":schedule,"outcome":"trigger_failed","reason":error.to_string()}), + }); + } + }; + let response = match result { + Trigger::Triggered { workflow, run } => json!({"schedule_id":schedule,"outcome":"triggered", + "workflow_id":workflow,"run_id":run}), + Trigger::Buffered => { + let row = Self::query("SELECT buffered_actions FROM workflow_schedules WHERE id=$1") + .bind(&key) + .fetch_one(&self.pool) .await?; - tx.commit().await?; - return Ok(skipped); - } - let started = Self::fire_schedule(&mut tx, &row, None, Some(&context)).await?; - tx.commit().await?; + let depth = DB::optional_document_row(&row, "buffered_actions")? + .and_then(|items| items.as_array().map(Vec::len)) + .unwrap_or(0); + json!({"schedule_id":schedule,"outcome":"buffered","buffer_depth":depth}) + } + Trigger::BufferFull => json!({"schedule_id":schedule,"outcome":"buffer_full","reason":BUFFER_FULL}), + Trigger::Skipped(reason) => json!({"schedule_id":schedule,"outcome":"skipped","reason":reason}), + }; self.wake.notify_waiters(); - Ok(started) + Ok(response) } - pub(crate) async fn fire_due_schedules(&self) -> Result<()> { - // A bounded read probe avoids the global transition lock on idle ticks. - let due = Self::query("SELECT schedule_id FROM workflow_schedules WHERE namespace='default' AND status='active' AND next_fire_at IS NOT NULL AND next_fire_at<=$1 ORDER BY next_fire_at,id LIMIT 100") - .bind(DB::bind_time(now())).fetch_all(&self.pool).await?; - for candidate in due { - let name = DB::string(&candidate, "schedule_id")?; + /// `ScheduleManager::triggerDetailed` on the schedule row `key`. + async fn trigger_detailed( + &self, + key: &str, + overlap: Option<&str>, + context: Option<&Value>, + occurrence: Option>, + outcome: &str, + ) -> Result { + let mut closed = false; + loop { let mut tx = self.begin().await?; - let row = Self::schedule_row(&mut tx, &name, true).await?; - if DB::string(&row, "status")? != "active" { - continue; + let row = Self::query("SELECT * FROM workflow_schedules WHERE id=$1") + .bind(key) + .fetch_one(&mut *tx) + .await?; + let store = self.in_namespace(DB::optional_string(&row, "namespace")?.as_deref().unwrap_or("default")); + match store + .decide(&mut tx, &row, overlap, context, occurrence, closed) + .await? + { + Decision::Done(result) => { + tx.commit().await?; + return Ok(result); + } + Decision::Close { policy, workflow } => { + // PHP closes the other run best-effort before starting. + drop(tx); + let command = if policy == "cancel_other" { + super::cancellation::TerminalCommand::Cancel + } else { + super::cancellation::TerminalCommand::Terminate + }; + let reason = format!("Schedule overlap: {policy} policy applied."); + let _ = store + .close_workflow(command, &workflow, None, Some(reason), Some(close_reason(&policy))) + .await; + closed = true; + } + Decision::Start { policy } => { + let result = store + .start_scheduled(&mut tx, &row, occurrence, outcome, &policy, context) + .await; + match result { + Ok(result) => { + tx.commit().await?; + return Ok(result); + } + Err(RuntimeError::Refused { reason, .. }) => { + // The start was refused: PHP's + // WorkflowExecutionUnavailableException path. + drop(tx); + let mut tx = self.begin().await?; + let row = Self::query("SELECT * FROM workflow_schedules WHERE id=$1") + .bind(key) + .fetch_one(&mut *tx) + .await?; + Self::record_skip(&mut tx, &row, reason, context).await?; + Self::recompute_next(&mut tx, &row).await?; + tx.commit().await?; + return Ok(Trigger::Skipped(reason.to_owned())); + } + Err(error) => return Err(error), + } + } } - let Some(occurrence) = - DB::optional_instant(&row, "next_fire_at")?.filter(|deadline| *deadline <= now()) - else { - continue; - }; - // Rereading under the existing cross-node transition lock fences - // competing evaluators. Failure rolls back the original occurrence. - Self::fire_schedule(&mut tx, &row, Some(occurrence), None).await?; - tx.commit().await?; - self.wake.notify_waiters(); } - Ok(()) } - async fn skip_schedule( + /// The checks of `triggerDetailed` before a start, on the locked row. + async fn decide( + &self, tx: &mut Transaction<'_, DB>, row: &DB::Row, - reason: &str, + overlap: Option<&str>, context: Option<&Value>, - ) -> Result { - let changed = now(); - let skipped = DB::number(row, "skipped_trigger_count")? - .checked_add(1) - .ok_or_else(|| refuse(StatusCode::CONFLICT, "schedule_skip_count_exhausted"))?; - let key = DB::string(row, "id")?; - Self::query("UPDATE workflow_schedules SET skipped_trigger_count=$1,last_skip_reason=$2,last_skipped_at=$3,updated_at=$4 WHERE id=$5") - .bind(skipped).bind(reason).bind(DB::bind_time(changed)).bind(DB::bind_time(changed)).bind(&key).execute(&mut **tx).await?; - Self::append_schedule( - tx, - &key, - "ScheduleTriggerSkipped", - json!({"reason":reason,"skipped_trigger_count":skipped,"last_skipped_at":changed}), - context, - None, - None, - ) - .await?; - Ok( - json!({"schedule_id":DB::string(row,"schedule_id")?,"outcome":"skipped","reason":reason}), - ) + occurrence: Option>, + closed: bool, + ) -> Result { + let skip = |reason: &str| Decision::Done(Trigger::Skipped(reason.to_owned())); + if let Some(occurrence) = occurrence { + let next = DB::optional_instant(row, "next_fire_at")?; + if next.is_none_or(|next| occurrence_key(next) != occurrence_key(occurrence)) { + // PHP records a stale_occurrence skip here. Native nodes all + // evaluate due schedules, so the occurrence another node just + // handled is left without a spurious skip record. + return Ok(skip("stale_occurrence")); + } + } + if DB::string(row, "status")? != "active" { + Self::record_skip(tx, row, "status_not_triggerable", context).await?; + return Ok(skip("status_not_triggerable")); + } + if DB::optional_number(row, "remaining_actions")?.is_some_and(|remaining| remaining <= 0) { + Self::record_skip(tx, row, "remaining_actions_exhausted", context).await?; + return Ok(skip("remaining_actions_exhausted")); + } + if let Some(occurrence) = occurrence + && self.has_fired(tx, row, occurrence).await? + { + self.skip_fired(tx, row, occurrence, context).await?; + return Ok(skip("already_fired_occurrence")); + } + let stored = DB::string(row, "overlap_policy")?; + let policy = overlap + .or(Some(stored.as_str())) + .filter(|policy| schedules::OVERLAP_POLICIES.contains(policy)) + .unwrap_or("skip") + .to_owned(); + let running = self.running_workflow(tx, row).await?; + if policy.starts_with("buffer_") && running.is_some() { + let buffered = DB::optional_document_row(row, "buffered_actions")? + .and_then(|items| items.as_array().cloned()) + .unwrap_or_default(); + if policy == "buffer_one" && !buffered.is_empty() { + Self::record_skip(tx, row, "buffer_full", context).await?; + Self::recompute_next(tx, row).await?; + return Ok(Decision::Done(Trigger::BufferFull)); + } + let mut action = json!({"buffered_at":iso8601(now())}); + if let Some(occurrence) = occurrence { + action["occurrence_time"] = json!(occurrence_text(occurrence)); + } + let mut buffered = buffered; + buffered.push(action); + Self::query("UPDATE workflow_schedules SET buffered_actions=$1,updated_at=$2 WHERE id=$3") + .bind(DB::document(&json!(buffered))).bind(DB::bind_time(now())).bind(DB::string(row, "id")?) + .execute(&mut **tx).await?; + Self::recompute_next(tx, row).await?; + return Ok(Decision::Done(Trigger::Buffered)); + } + let allowed = policy == "allow_all" + || running.is_none() + || matches!(policy.as_str(), "cancel_other" | "terminate_other"); + if !allowed { + let reason = format!("overlap_policy_{policy}"); + Self::record_skip(tx, row, &reason, context).await?; + Self::recompute_next(tx, row).await?; + return Ok(Decision::Done(Trigger::Skipped(reason))); + } + if !closed + && matches!(policy.as_str(), "cancel_other" | "terminate_other") + && let Some(workflow) = DB::optional_string(row, "latest_workflow_instance_id")? + { + return Ok(Decision::Close { policy, workflow }); + } + Ok(Decision::Start { policy }) } - async fn fire_schedule( + /// `ScheduleManager::startRun` through the server's remote starter. + async fn start_scheduled( + &self, tx: &mut Transaction<'_, DB>, row: &DB::Row, occurrence: Option>, + outcome: &str, + policy: &str, context: Option<&Value>, - ) -> Result { + ) -> Result { let key = DB::string(row, "id")?; let schedule = DB::string(row, "schedule_id")?; - let spec = DB::document_row(row, "spec")?; - if let Some(latest) = DB::optional_string(row, "latest_workflow_instance_id")? { - let active = Self::query("SELECT r.status FROM workflow_instances i JOIN workflow_runs r ON r.id=i.current_run_id WHERE i.id=$1 AND i.namespace='default'") - .bind(latest).fetch_optional(&mut **tx).await?; - if active - .as_ref() - .map(|run| DB::string(run, "status")) - .transpose()? - .is_some_and(|status| matches!(status.as_str(), "pending" | "running" | "waiting")) - { - let skipped = Self::skip_schedule(tx, row, "overlap_policy_skip", context).await?; - let next = schedules::next(&spec, now())?; - Self::query("UPDATE workflow_schedules SET next_fire_at=$1 WHERE id=$2") - .bind(DB::bind_time(next)) - .bind(&key) - .execute(&mut **tx) - .await?; - return Ok(skipped); + let stored_policy = DB::string(row, "overlap_policy")?; + let spec = DB::optional_document_row(row, "spec")?.unwrap_or(Value::Null); + let action = schedules::normalize_action_timeouts( + &DB::optional_document_row(row, "action")?.unwrap_or(Value::Null), + ); + let mut start = json!({"workflow_id":id(),"workflow_type":action["workflow_type"], + "task_queue":action["task_queue"].as_str().filter(|queue| !queue.trim().is_empty()).map(str::trim).unwrap_or("default"), + "input":action.get("input").filter(|input| !input.is_null()).cloned().unwrap_or_else(empty_arguments)}); + for field in ["execution_timeout_seconds", "run_timeout_seconds"] { + if let Some(seconds) = action.get(field).filter(|value| !value.is_null()) { + start[field] = seconds.clone(); } } - let trigger_number = DB::number(row, "fires_count")? + let memo = match DB::optional_document_row(row, "memo")?.filter(|memo| !memo.is_null()) { + Some(memo) => super::memos::initial(memo)?, + None => Default::default(), + }; + let started = Self::start_in(tx, &start, &self.namespace, Some(&memo), None).await?; + let workflow = started["workflow_id"].as_str().unwrap_or_default().to_owned(); + let run = started["run_id"].as_str().unwrap_or_default().to_owned(); + let fires = DB::number(row, "fires_count")?; + let trigger_number = fires .checked_add(1) .ok_or_else(|| refuse(StatusCode::CONFLICT, "schedule_fire_count_exhausted"))?; - let mut action = DB::document_row(row, "action")?; - action["workflow_id"] = json!(id()); - let started = Self::start_in(tx, &action, "default", None, None).await?; - let workflow = started["workflow_id"].as_str().unwrap(); - let run = started["run_id"].as_str().unwrap(); - let mut marker = json!({"schedule_id":schedule,"schedule_ulid":key,"timezone":"UTC","overlap_policy":"skip","trigger_number":trigger_number}); - let mut audit = json!({"workflow_instance_id":workflow,"workflow_run_id":run,"outcome":"scheduled","effective_overlap_policy":"skip","trigger_number":trigger_number}); + // recordScheduleTriggered: the run's own schedule marker. + let mut marker = json!({"schedule_id":schedule,"schedule_ulid":key,"overlap_policy":stored_policy, + "trigger_number":trigger_number}); + if let Some(cron) = spec["cron_expressions"] + .as_array() + .and_then(|items| items.first()) + { + marker["cron_expression"] = json!(match cron { + Value::String(text) => text.clone(), + other => other.to_string(), + }); + } + if let Some(zone) = spec.get("timezone").filter(|zone| !zone.is_null()) { + marker["timezone"] = zone.clone(); + } + let mut audit = json!({"workflow_instance_id":workflow,"workflow_run_id":run,"outcome":outcome, + "effective_overlap_policy":policy,"trigger_number":trigger_number}); if let Some(occurrence) = occurrence { - let value = occurrence.format("%Y-%m-%dT%H:%M:%S%.6f+00:00").to_string(); - marker["occurrence_time"] = json!(value); + marker["occurrence_time"] = json!(occurrence_text(occurrence)); audit["occurrence_time"] = marker["occurrence_time"].clone(); } - Self::append(tx, run, "ScheduleTriggered", marker, None, None).await?; - Self::append_schedule( - tx, - &key, - "ScheduleTriggered", - audit, - context, - Some(workflow), - Some(run), - ) - .await?; + Self::append(tx, &run, "ScheduleTriggered", marker, None, None).await?; + Self::append_schedule(tx, &key, "ScheduleTriggered", audit, context, Some(&workflow), Some(&run)) + .await?; + // recordFire: the ten most recent actions and the next occurrence. let fired = now(); - let mut recent = - json!({"workflow_id":workflow,"run_id":run,"outcome":"scheduled","fired_at":fired}); + let mut recent_action = json!({"workflow_id":workflow,"run_id":run,"outcome":outcome,"fired_at":iso8601(fired)}); if let Some(occurrence) = occurrence { - recent["occurrence_time"] = - json!(occurrence.format("%Y-%m-%dT%H:%M:%S%.6f+00:00").to_string()); - recent["fire_lag_ms"] = json!((fired - occurrence).num_milliseconds()); + recent_action["occurrence_time"] = json!(occurrence_text(occurrence)); + let lag = (fired - occurrence).num_microseconds().unwrap_or(0) as f64 / 1000.0; + recent_action["fire_lag_ms"] = json!((lag.round() as i64).max(0)); } - Self::query("UPDATE workflow_schedules SET fires_count=$1,last_fired_at=$2,latest_workflow_instance_id=$3,recent_actions=$4,updated_at=$5 WHERE id=$6") - .bind(trigger_number).bind(DB::bind_time(fired)).bind(workflow).bind(DB::document(&json!([recent]))).bind(DB::bind_time(fired)).bind(&key).execute(&mut **tx).await?; - if DB::optional_number(row, "max_runs")? == Some(1) { - Self::query("UPDATE workflow_schedules SET status='deleted',remaining_actions=0,next_fire_at=NULL,deleted_at=$1 WHERE id=$2") + let recent = recent_actions::(row, recent_action)?; + let next = schedules::next_fire(&spec, DB::number(row, "jitter_seconds")?, fired)?; + let remaining = DB::optional_number(row, "remaining_actions")?.map(|remaining| (remaining - 1).max(0)); + Self::query("UPDATE workflow_schedules SET fires_count=$1,last_fired_at=$2,latest_workflow_instance_id=$3,recent_actions=$4,remaining_actions=$5,updated_at=$6 WHERE id=$7") + .bind(trigger_number).bind(DB::bind_time(fired)).bind(workflow.as_str()).bind(DB::document(&recent)) + .bind(remaining).bind(DB::bind_time(fired)).bind(&key).execute(&mut **tx).await?; + Self::set_next(tx, &key, next).await?; + if remaining.is_some_and(|remaining| remaining <= 0) { + Self::query("UPDATE workflow_schedules SET status='deleted',deleted_at=$1,next_fire_at=NULL,buffered_actions=NULL WHERE id=$2") .bind(DB::bind_time(fired)).bind(&key).execute(&mut **tx).await?; Self::append_schedule( tx, &key, "ScheduleDeleted", - json!({"reason":"max_runs_exhausted","deleted_at":fired}), - None, + json!({"reason":"max_runs_exhausted","deleted_at":iso8601(fired)}), + context, None, None, ) .await?; - } else { - let next = schedules::next(&spec, fired)?; - Self::query("UPDATE workflow_schedules SET next_fire_at=$1 WHERE id=$2") - .bind(DB::bind_time(next)) - .bind(&key) - .execute(&mut **tx) - .await?; } - Ok( - json!({"schedule_id":schedule,"outcome":"triggered","workflow_id":workflow,"run_id":run}), + Ok(Trigger::Triggered { workflow, run }) + } + + /// `recordSkip`. + async fn record_skip( + tx: &mut Transaction<'_, DB>, + row: &DB::Row, + reason: &str, + context: Option<&Value>, + ) -> Result<()> { + let changed = now(); + let key = DB::string(row, "id")?; + let current = Self::query("SELECT skipped_trigger_count FROM workflow_schedules WHERE id=$1") + .bind(&key) + .fetch_one(&mut **tx) + .await?; + let skipped = DB::number(¤t, "skipped_trigger_count")? + .checked_add(1) + .ok_or_else(|| refuse(StatusCode::CONFLICT, "schedule_skip_count_exhausted"))?; + Self::query("UPDATE workflow_schedules SET skipped_trigger_count=$1,last_skip_reason=$2,last_skipped_at=$3,updated_at=$4 WHERE id=$5") + .bind(skipped).bind(reason).bind(DB::bind_time(changed)).bind(DB::bind_time(changed)).bind(&key).execute(&mut **tx).await?; + Self::append_schedule( + tx, + &key, + "ScheduleTriggerSkipped", + json!({"reason":reason,"skipped_trigger_count":skipped,"last_skipped_at":iso8601(changed)}), + context, + None, + None, ) + .await + } + + /// `recordFailure`, committed on its own after a failed evaluation. + async fn record_failure(&self, key: &str, reason: &str) -> Result<()> { + let mut tx = self.begin().await?; + let row = Self::query("SELECT * FROM workflow_schedules WHERE id=$1") + .bind(key) + .fetch_one(&mut *tx) + .await?; + let failed = now(); + let recent = recent_actions::( + &row, + json!({"outcome":"failed","reason":reason,"fired_at":iso8601(failed)}), + )?; + let failures = DB::number(&row, "failures_count")?.saturating_add(1); + Self::query("UPDATE workflow_schedules SET recent_actions=$1,failures_count=$2,last_fired_at=$3,updated_at=$4 WHERE id=$5") + .bind(DB::document(&recent)).bind(failures).bind(DB::bind_time(failed)).bind(DB::bind_time(failed)).bind(key) + .execute(&mut *tx).await?; + // A spec PHP cannot evaluate keeps its stored occurrence. + if let Ok(next) = schedules::next_fire( + &DB::optional_document_row(&row, "spec")?.unwrap_or(Value::Null), + DB::number(&row, "jitter_seconds")?, + failed, + ) { + Self::set_next(&mut tx, key, next).await?; + } + tx.commit().await?; + Ok(()) } + + /// `hasFiredOccurrence`, including history written without the key. + async fn has_fired( + &self, + tx: &mut Transaction<'_, DB>, + row: &DB::Row, + occurrence: DateTime, + ) -> Result { + let key = DB::string(row, "id")?; + let wanted = occurrence_key(occurrence); + let found = Self::query("SELECT id FROM workflow_schedule_history_events WHERE workflow_schedule_id=$1 AND event_type='ScheduleTriggered' AND occurrence_at_utc=$2 LIMIT 1") + .bind(&key).bind(wanted.as_str()).fetch_optional(&mut **tx).await?; + if found.is_some() { + return Ok(true); + } + let legacy = Self::query("SELECT payload FROM workflow_schedule_history_events WHERE workflow_schedule_id=$1 AND event_type='ScheduleTriggered' AND occurrence_at_utc IS NULL") + .bind(&key).fetch_all(&mut **tx).await?; + for event in legacy { + let payload = DB::optional_document_row(&event, "payload")?.unwrap_or(Value::Null); + if payload["occurrence_time"] + .as_str() + .and_then(|text| DateTime::parse_from_rfc3339(text).ok()) + .is_some_and(|time| occurrence_key(time.with_timezone(&Utc)) == wanted) + { + return Ok(true); + } + } + Ok(false) + } + + /// `skipFiredOccurrence`. + async fn skip_fired( + &self, + tx: &mut Transaction<'_, DB>, + row: &DB::Row, + occurrence: DateTime, + context: Option<&Value>, + ) -> Result<()> { + Self::record_skip(tx, row, "already_fired_occurrence", context).await?; + if DB::optional_instant(row, "next_fire_at")? + .is_some_and(|next| occurrence_key(next) == occurrence_key(occurrence)) + && now().timestamp() >= occurrence.timestamp() + { + Self::recompute_next(tx, row).await?; + } + Ok(()) + } + + /// `runningRunExists`: the latest generated workflow's current run, if + /// it is still open. + async fn running_workflow(&self, tx: &mut Transaction<'_, DB>, row: &DB::Row) -> Result> { + let Some(latest) = DB::optional_string(row, "latest_workflow_instance_id")? else { + return Ok(None); + }; + let run = Self::query("SELECT r.status FROM workflow_instances i JOIN workflow_runs r ON r.id=i.current_run_id WHERE i.id=$1") + .bind(latest.as_str()).fetch_optional(&mut **tx).await?; + let open = run + .as_ref() + .map(|run| DB::string(run, "status")) + .transpose()? + .is_some_and(|status| matches!(status.as_str(), "pending" | "running" | "waiting")); + Ok(open.then_some(latest)) + } + + /// `ScheduleManager::tick`: drain buffered actions whose run finished, + /// then evaluate due schedules, in every namespace. + pub(crate) async fn fire_due_schedules(&self) -> Result<()> { + // A bounded read probe avoids the global transition lock on idle ticks. + let buffered = Self::query("SELECT id FROM workflow_schedules WHERE status='active' AND buffered_actions IS NOT NULL ORDER BY updated_at,id LIMIT 100") + .fetch_all(&self.pool).await?; + for candidate in buffered { + let key = DB::string(&candidate, "id")?; + if let Err(error) = self.drain_one(&key).await { + if matches!(error, RuntimeError::Database(_)) { + return Err(error); + } + self.record_failure(&key, &error.to_string()).await?; + } + } + let due = Self::query("SELECT id,next_fire_at FROM workflow_schedules WHERE status='active' AND next_fire_at IS NOT NULL AND next_fire_at<=$1 ORDER BY next_fire_at,id LIMIT 100") + .bind(DB::bind_time(now())).fetch_all(&self.pool).await?; + for candidate in due { + let key = DB::string(&candidate, "id")?; + let occurrence = DB::optional_instant(&candidate, "next_fire_at")?; + match self + .trigger_detailed(&key, None, None, occurrence, "scheduled") + .await + { + Ok(_) => self.wake.notify_waiters(), + Err(error @ RuntimeError::Database(_)) => return Err(error), + Err(error) => self.record_failure(&key, &error.to_string()).await?, + } + } + Ok(()) + } + + /// One buffered action of the tick's first phase. + async fn drain_one(&self, key: &str) -> Result<()> { + let mut tx = self.begin().await?; + let row = Self::query("SELECT * FROM workflow_schedules WHERE id=$1") + .bind(key) + .fetch_one(&mut *tx) + .await?; + let store = self.in_namespace(DB::optional_string(&row, "namespace")?.as_deref().unwrap_or("default")); + let mut buffered = DB::optional_document_row(&row, "buffered_actions")? + .and_then(|items| items.as_array().cloned()) + .unwrap_or_default(); + if buffered.is_empty() || store.running_workflow(&mut tx, &row).await?.is_some() { + return Ok(()); + } + let action = buffered.remove(0); + let rest = (!buffered.is_empty()).then(|| DB::document(&json!(buffered))); + Self::query("UPDATE workflow_schedules SET buffered_actions=$1,updated_at=$2 WHERE id=$3") + .bind(rest).bind(DB::bind_time(now())).bind(key).execute(&mut *tx).await?; + let occurrence = action["occurrence_time"] + .as_str() + .and_then(|text| DateTime::parse_from_rfc3339(text).ok()) + .map(|time| time.with_timezone(&Utc)); + if let Some(occurrence) = occurrence + && store.has_fired(&mut tx, &row, occurrence).await? + { + store.skip_fired(&mut tx, &row, occurrence, None).await?; + tx.commit().await?; + return Ok(()); + } + if DB::string(&row, "status")? != "active" { + drop(tx); + let mut tx = self.begin().await?; + Self::record_skip(&mut tx, &row, "schedule_not_triggerable", None).await?; + tx.commit().await?; + return Ok(()); + } + let policy = DB::string(&row, "overlap_policy")?; + match store + .start_scheduled(&mut tx, &row, occurrence, "drained", &policy, None) + .await + { + Ok(_) => { + tx.commit().await?; + self.wake.notify_waiters(); + Ok(()) + } + Err(RuntimeError::Refused { reason, .. }) => { + // The drained action is kept: PHP rolls the drain back too. + drop(tx); + let mut tx = self.begin().await?; + Self::record_skip(&mut tx, &row, reason, None).await?; + tx.commit().await?; + Ok(()) + } + Err(error) => Err(error), + } + } +} + +/// `utcOccurrenceKey`. +fn occurrence_key(time: DateTime) -> String { + time.format("%Y-%m-%d %H:%M:%S%.6f").to_string() +} + +/// The empty argument list, as PHP serializes `[]`. +fn empty_arguments() -> Value { + let blob = crate::codec::ValueCodec::new() + .and_then(|codec| codec.encode(&crate::codec::Value::Array(vec![]))) + .unwrap_or_default(); + json!({"codec":"avro","blob":blob}) +} + +/// The recent-actions ring with one more action, keeping the last ten. +fn recent_actions(row: &DB::Row, action: Value) -> Result +where + usize: ColumnIndex, +{ + let mut actions = DB::optional_document_row(row, "recent_actions")? + .and_then(|items| items.as_array().cloned()) + .unwrap_or_default(); + actions.push(action); + let skip = actions.len().saturating_sub(10); + Ok(json!(actions.split_off(skip))) } #[cfg(test)] @@ -239,15 +678,21 @@ mod tests { .unwrap() .encode(&Payload::Array(vec![Payload::Long(9007199254740993)])) .unwrap(); - let at = now() - chrono::Duration::seconds(4); let definition = schedules::definition(&json!({"schedule_id":"atomic-occurrence", "spec":{"intervals":[{"every":"PT2S"}],"timezone":"UTC"}, "action":{"workflow_type":"echo","task_queue":"test","input":{"codec":"avro","blob":input}, "execution_timeout_seconds":3600,"run_timeout_seconds":600}, - "overlap_policy":"skip","jitter_seconds":0,"max_runs":1}),at).unwrap(); - let occurrence = definition.next_fire; + "overlap_policy":"skip","jitter_seconds":0,"max_runs":1})) + .unwrap(); store - .create_schedule(definition, json!({"test":"original creation"})) + .create_schedule(definition, json!({"test":"original creation"}), json!({})) + .await + .unwrap(); + // Make the stored occurrence due now. + let occurrence = now() - chrono::Duration::seconds(4); + sqlx::query("UPDATE workflow_schedules SET next_fire_at=?") + .bind(occurrence.format("%Y-%m-%d %H:%M:%S%.6f").to_string()) + .execute(&store.pool) .await .unwrap(); sqlx::query("CREATE TRIGGER reject_schedule_audit BEFORE INSERT ON workflow_schedule_history_events WHEN NEW.event_type='ScheduleTriggered' BEGIN SELECT RAISE(ABORT,'injected schedule audit failure'); END") @@ -273,12 +718,11 @@ mod tests { } let before = store.describe_schedule("atomic-occurrence").await.unwrap(); assert_eq!(before["fires_count"], 0); - assert_eq!(before["failures_count"], 0); assert_eq!(before["remaining_actions"], 1); assert_eq!(before["status"], "active"); assert_eq!( chrono::DateTime::parse_from_rfc3339(before["next_fire_at"].as_str().unwrap()).unwrap(), - occurrence + chrono::DateTime::parse_from_rfc3339(&iso8601(occurrence)).unwrap() ); assert_eq!( store @@ -302,13 +746,8 @@ mod tests { .unwrap(); assert_eq!(history["events"].as_array().unwrap().len(), 3); assert_eq!( - chrono::DateTime::parse_from_rfc3339( - history["events"][1]["payload"]["occurrence_time"] - .as_str() - .unwrap() - ) - .unwrap(), - occurrence + history["events"][1]["payload"]["occurrence_time"], + occurrence_text(occurrence) ); assert_eq!( history["events"][2]["payload"]["reason"], diff --git a/rust/src/runtime/schedule_http.rs b/rust/src/runtime/schedule_http.rs index ecdb7c341..3eed4f815 100644 --- a/rust/src/runtime/schedule_http.rs +++ b/rust/src/runtime/schedule_http.rs @@ -1,12 +1,13 @@ //! Schedule HTTP controls and audit provenance derived after the shared guard. -use super::{Result, Runtime, refuse, store::id, text}; +use super::{Result, Runtime, refuse, store::id}; +use super::laravel_rules::{Rule, Rules, normalize, validate}; use axum::{ - Json, + Extension, Json, body::{Body, to_bytes}, - extract::{ConnectInfo, Path, Query, Request, State}, + extract::{ConnectInfo, Path, Query, Request}, http::{StatusCode, request::Parts}, }; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; use std::net::SocketAddr; @@ -44,7 +45,21 @@ fn normalized(value: &Value) -> Value { } } -fn context(request: &Parts, payload: &Value, schedule: &str, operation: &str) -> Value { +fn context(request: &Parts, payload: &Value, namespace: &str, schedule: &str, operation: &str) -> Value { + context_for(request, payload, namespace, schedule, operation, operation) +} + +/// The command context of `command` for a request routed as `route`. +fn context_for( + request: &Parts, + payload: &Value, + namespace: &str, + schedule: &str, + route: &str, + command: &str, +) -> Value { + // PHP fingerprints the request as Laravel normalized it. + let payload = &normalize(payload); let header = |name: &str| { request .headers @@ -83,7 +98,7 @@ fn context(request: &Parts, payload: &Value, schedule: &str, operation: &str) -> .get::>() .map(|peer| peer.0.ip().to_string()) .unwrap_or_else(|| "unknown".to_owned()); - let mut metadata = json!({"method":method,"path":path,"route_name":format!("api.schedules.{operation}"), + let mut metadata = json!({"method":method,"path":path,"route_name":format!("api.schedules.{route}"), "ip":ip,"user_agent":header("user-agent"),"fingerprint":format!("sha256:{fingerprint}")}); if !recorded_headers.is_array() { metadata["headers"] = recorded_headers; @@ -101,8 +116,8 @@ fn context(request: &Parts, payload: &Value, schedule: &str, operation: &str) -> "auth":{"status":"authorized","method":"token","role":"admin","roles":["admin"], "principal":{"subject":"legacy-token","roles":["admin"],"claims":{"legacy_credential":true},"legacy_full_access":true}}, "principal":{"type":"auth:token","id":"legacy-token","label":"Admin"}, - "request":metadata,"server":{"operation_id":id(),"namespace":"default","workflow_id":schedule, - "command":format!("schedule.{operation}"),"metadata":[]}}}) + "request":metadata,"server":{"operation_id":id(),"namespace":namespace,"workflow_id":schedule, + "command":format!("schedule.{command}"),"metadata":[]}}}) } async fn body(request: Body) -> Result { @@ -117,47 +132,86 @@ async fn body(request: Body) -> Result { Ok(if value.is_null() { json!([]) } else { value }) } +/// `POST /api/schedules`. pub(super) async fn create( - State(runtime): State, + Extension(runtime): Extension, request: Request, ) -> Result<(StatusCode, Json)> { let (parts, bytes) = request.into_parts(); let payload = body(bytes).await?; - let audit = context(&parts, &payload, text(&payload, "schedule_id")?, "create"); + let namespace = runtime.namespace().to_owned(); + // The audit names the caller's schedule id, or PHP's generated one. + let target = payload["schedule_id"] + .as_str() + .map(str::trim) + .filter(|id| !id.is_empty()) + .map_or_else(|| ulid::Ulid::new().to_string(), str::to_owned); + let created = context(&parts, &payload, &namespace, &target, "create"); + let paused = context_for(&parts, &payload, &namespace, &target, "create", "pause"); Ok(( StatusCode::CREATED, - Json(runtime.create_schedule(payload, audit).await?), + Json(runtime.create_schedule(payload, target, created, paused).await?), )) } +/// `GET /api/schedules/{id}`. pub(super) async fn describe( - State(runtime): State, + Extension(runtime): Extension, Path(schedule): Path, ) -> Result> { runtime.describe_schedule(&schedule).await.map(Json) } -#[derive(Deserialize)] -pub(super) struct Page { - limit: Option, - after_sequence: Option, +/// `ScheduleController::parseHistoryLimit`: 100 by default, at most 500. +fn history_limit(raw: Option<&str>) -> i64 { + let value = raw.map_or(0, |text| super::schedule_cron::php_int(text)); + if value <= 0 { 100 } else { value.min(500) } } +/// `GET /api/schedules/{id}/history`. pub(super) async fn history( - State(runtime): State, + Extension(runtime): Extension, Path(schedule): Path, - Query(page): Query, + Query(query): Query>, ) -> Result> { + let after = match query.get("after_sequence").map(String::as_str) { + None | Some("") => 0, + Some(text) => match text + .strip_prefix('-') + .unwrap_or(text) + .bytes() + .all(|byte| byte.is_ascii_digit()) + .then(|| text.parse::().ok()) + .flatten() + .filter(|value| *value >= 0) + { + Some(value) => value, + None => { + return Err(super::RuntimeError::Protocol { + status: StatusCode::UNPROCESSABLE_ENTITY, + response: json!({"message":"after_sequence must be a non-negative integer.", + "reason":"invalid_after_sequence"}), + }); + } + }, + }; + let limit = history_limit(query.get("limit").map(String::as_str)); runtime - .schedule_history( - &schedule, - page.after_sequence.unwrap_or(0), - page.limit.unwrap_or(100), - ) + .schedule_history(&schedule, after, limit) .await .map(Json) } +const NOTE: &Rules = &[("note", &[Rule::Nullable, Rule::String, Rule::Max(1000)])]; +const OVERLAP: &Rules = &[( + "overlap_policy", + &[ + Rule::Nullable, + Rule::String, + Rule::In(super::schedules::OVERLAP_POLICIES), + ], +)]; + async fn control( runtime: Runtime, schedule: String, @@ -166,48 +220,54 @@ async fn control( ) -> Result> { let (parts, bytes) = request.into_parts(); let payload = body(bytes).await?; - let empty = payload.as_object().is_some_and(|value| value.is_empty()) - || payload.as_array().is_some_and(|value| value.is_empty()); - if !empty { - return Err(refuse( - StatusCode::UNPROCESSABLE_ENTITY, - "unsupported_schedule_control_field", - )); - } - let audit = context(&parts, &payload, &schedule, operation); - if operation == "trigger" { - runtime.trigger_schedule(&schedule, audit).await.map(Json) - } else { - runtime - .edit_schedule(&schedule, operation, audit) + let audit = context(&parts, &payload, runtime.namespace(), &schedule, operation); + // PHP finds the schedule before it validates the body. + runtime.describe_schedule(&schedule).await?; + let normalized = normalize(&payload); + match operation { + "trigger" => { + validate(&normalized, OVERLAP)?; + let overlap = normalized["overlap_policy"].as_str().map(str::to_owned); + runtime.trigger_schedule(&schedule, overlap, audit).await.map(Json) + } + "delete" => runtime + .edit_schedule(&schedule, operation, None, audit) .await - .map(Json) + .map(Json), + _ => { + validate(&normalized, NOTE)?; + let note = normalized["note"].as_str().map(str::to_owned); + runtime + .edit_schedule(&schedule, operation, note, audit) + .await + .map(Json) + } } } pub(super) async fn pause( - State(runtime): State, + Extension(runtime): Extension, Path(schedule): Path, request: Request, ) -> Result> { control(runtime, schedule, "pause", request).await } pub(super) async fn resume( - State(runtime): State, + Extension(runtime): Extension, Path(schedule): Path, request: Request, ) -> Result> { control(runtime, schedule, "resume", request).await } pub(super) async fn trigger( - State(runtime): State, + Extension(runtime): Extension, Path(schedule): Path, request: Request, ) -> Result> { control(runtime, schedule, "trigger", request).await } pub(super) async fn remove( - State(runtime): State, + Extension(runtime): Extension, Path(schedule): Path, request: Request, ) -> Result> { diff --git a/rust/src/runtime/schedule_spec.rs b/rust/src/runtime/schedule_spec.rs new file mode 100644 index 000000000..b43efbdfa --- /dev/null +++ b/rust/src/runtime/schedule_spec.rs @@ -0,0 +1,232 @@ +//! A schedule's next fire time exactly as published PHP Server computes it +//! (`WorkflowSchedule::computeNextFireAt`): the earliest occurrence after the +//! given instant across its cron expressions, in its time zone, and its +//! intervals, which PHP counts in UTC from the Unix epoch plus an offset. +use super::schedule_cron::{Cron, CronError}; +use jiff::Timestamp; +use serde_json::Value; + +/// The members PHP's `foreach` visits: a list's items or a map's values. +fn members(value: &Value) -> Vec<&Value> { + match value { + Value::Array(items) => items.iter().collect(), + Value::Object(fields) => fields.values().collect(), + _ => Vec::new(), + } +} + +/// The next fire time without jitter, `None` when no rule yields one, or the +/// exception PHP throws evaluating a cron expression. +pub(crate) fn next_fire(spec: &Value, after: Timestamp) -> Result, CronError> { + let zone = spec + .get("timezone") + .and_then(Value::as_str) + .unwrap_or("UTC"); + let mut earliest: Option = None; + let mut consider = |next: Timestamp| { + if earliest.is_none_or(|current| next < current) { + earliest = Some(next); + } + }; + for expression in members(spec.get("cron_expressions").unwrap_or(&Value::Null)) { + let Some(expression) = expression.as_str().filter(|text| !text.is_empty()) else { + continue; + }; + consider(Cron::parse(expression)?.next_after(after, zone)?); + } + for interval in members(spec.get("intervals").unwrap_or(&Value::Null)) { + if let Some(next) = interval_occurrence(interval, after) { + consider(next); + } + } + Ok(earliest) +} + +/// `WorkflowSchedule::nextIntervalOccurrence`. +fn interval_occurrence(interval: &Value, after: Timestamp) -> Option { + let every = duration_seconds(interval.get("every")?.as_str()?)?; + if every <= 0 { + return None; + } + // An invalid offset counts as none. + let offset = interval + .get("offset") + .and_then(Value::as_str) + .and_then(duration_seconds) + .unwrap_or(0); + let elapsed = i128::from(after.as_second()) - offset; + let next = offset + (elapsed.div_euclid(every) + 1) * every; + Timestamp::from_second(i64::try_from(next).ok()?).ok() +} + +/// `dateIntervalToSeconds(new DateInterval($spec))`, or `None` where PHP's +/// `DateInterval` refuses the string (or it is empty). +pub(crate) fn duration_seconds(text: &str) -> Option { + let parts = duration(text)?; + Some( + parts.years * 365 * 86400 + + parts.months * 30 * 86400 + + parts.days * 86400 + + parts.hours * 3600 + + parts.minutes * 60 + + parts.seconds, + ) +} + +#[derive(Default, Debug, PartialEq)] +struct Duration { + years: i128, + months: i128, + days: i128, + hours: i128, + minutes: i128, + seconds: i128, +} + +/// PHP's ISO 8601 duration grammar: `P[nY][nM][nW][nD][T[nH][nM][nS]]` in that +/// order with at least one part, or `PYYYY-MM-DDTHH:MM:SS`; surrounding +/// blanks allowed; whole non-negative numbers that fit a signed 64-bit integer. +fn duration(text: &str) -> Option { + let text = text.trim_matches([' ', '\t']); + let body = text.strip_prefix('P')?; + if let Some(parts) = combined(body) { + return Some(parts); + } + let (date, time) = match body.split_once('T') { + Some((date, time)) => (date, Some(time)), + None => (body, None), + }; + let mut parts = Duration::default(); + let mut any = false; + let mut read = |section: &str, units: &[u8]| -> Option> { + let mut found = Vec::new(); + let mut rest = section; + let mut position = 0; + while !rest.is_empty() { + let digits = rest.bytes().take_while(u8::is_ascii_digit).count(); + if digits == 0 { + return None; + } + let value: i64 = rest[..digits].parse().ok()?; + let unit = *rest.as_bytes().get(digits)?; + let index = units[position..].iter().position(|candidate| *candidate == unit)?; + position += index + 1; + found.push((unit, i128::from(value))); + rest = &rest[digits + 1..]; + any = true; + } + Some(found) + }; + for (unit, value) in read(date, b"YMWD")? { + match unit { + b'Y' => parts.years = value, + b'M' => parts.months = value, + b'W' => parts.days += value * 7, + _ => parts.days += value, + } + } + if let Some(time) = time { + let found = read(time, b"HMS")?; + if found.is_empty() { + return None; + } + for (unit, value) in found { + match unit { + b'H' => parts.hours = value, + b'M' => parts.minutes = value, + _ => parts.seconds = value, + } + } + } + any.then_some(parts) +} + +/// The `YYYY-MM-DDTHH:MM:SS` alternative form. +fn combined(body: &str) -> Option { + let bytes = body.as_bytes(); + let shape = b"0000-00-00T00:00:00"; + if bytes.len() != shape.len() + || !bytes + .iter() + .zip(shape) + .all(|(byte, expected)| if *expected == b'0' { byte.is_ascii_digit() } else { byte == expected }) + { + return None; + } + let number = |range: std::ops::Range| body[range].parse::().ok(); + Some(Duration { + years: number(0..4)?, + months: number(5..7)?, + days: number(8..10)?, + hours: number(11..13)?, + minutes: number(14..16)?, + seconds: number(17..19)?, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Published PHP 2.5.14's `computeNextFireAt` (scripts/conformance/cron-oracle) for seeded specs mixing + /// cron expressions, intervals with offsets, zones and malformed rules. + const CORPUS: &str = include_str!("../../tests/fixtures/cron/php-2.5.14-next-fire.jsonl"); + + #[test] + fn next_fire_times_match_published_php() { + let mut mismatches = Vec::new(); + let lines: Vec<&str> = CORPUS.lines().filter(|line| !line.trim().is_empty()).collect(); + for line in &lines { + let case: Value = serde_json::from_str(line).unwrap(); + let after: Timestamp = case["after"].as_str().unwrap().parse().unwrap(); + let actual = next_fire(&case["spec"], after) + .map(|next| next.map(|next| next.strftime("%Y-%m-%dT%H:%M:%S.%6fZ").to_string())) + .map_err(CronError::php_class); + let expected = match case.get("error") { + Some(error) => Err(error.as_str().unwrap()), + None => Ok(case["next"].as_str().map(str::to_owned)), + }; + if actual != expected { + mismatches.push(format!("{line} -> {actual:?}")); + } + } + assert!(lines.len() > 100); + assert!( + mismatches.is_empty(), + "{} of {} differ:\n{}", + mismatches.len(), + lines.len(), + mismatches.join("\n") + ); + } + + #[test] + fn durations_follow_php_date_interval() { + for (text, seconds) in [ + ("P1D", Some(86400)), + ("PT2S", Some(2)), + ("P1W2D", Some(9 * 86400)), + ("P1Y2M3DT4H5M6S", Some(365 * 86400 + 60 * 86400 + 3 * 86400 + 4 * 3600 + 5 * 60 + 6)), + ("P0001-00-02T00:00:00", Some(367 * 86400)), + (" P1D", Some(86400)), + ("P1D ", Some(86400)), + ("P01D", Some(86400)), + ("P0D", Some(0)), + ("P1DT0S", Some(86400)), + ("p1d", None), + ("P", None), + ("PT", None), + ("P1DT", None), + ("PT1H1D", None), + ("P1D1Y", None), + ("P2D1W", None), + ("PT1.5S", None), + ("P-1D", None), + ("P1H", None), + ("P1M1M", None), + ("P10000000000000000000D", None), + ] { + assert_eq!(duration_seconds(text), seconds, "{text}"); + } + } +} diff --git a/rust/src/runtime/schedule_storage.rs b/rust/src/runtime/schedule_storage.rs index 75d14c667..db0561ba5 100644 --- a/rust/src/runtime/schedule_storage.rs +++ b/rust/src/runtime/schedule_storage.rs @@ -1,16 +1,35 @@ -//! PHP-layout schedule rows and durable audit pages. +//! PHP-layout schedule rows, descriptions and durable audit pages. use super::transaction::Transaction; use super::{ - Result, + Result, RuntimeError, backend::Backend, refuse, - schedules::Definition, + schedules::{self, Definition}, store::{Store, id, now}, }; use axum::http::StatusCode; +use chrono::{DateTime, Utc}; use serde_json::{Value, json}; use sqlx::{ColumnIndex, Decode, Encode, Executor, IntoArguments, Type}; +/// Carbon's `toIso8601String()` for a UTC instant. +pub(crate) fn iso8601(time: DateTime) -> String { + time.format("%Y-%m-%dT%H:%M:%S+00:00").to_string() +} + +/// `format('Y-m-d\TH:i:s.uP')`, the occurrence form PHP records. +pub(crate) fn occurrence(time: DateTime) -> String { + time.format("%Y-%m-%dT%H:%M:%S%.6f+00:00").to_string() +} + +pub(super) fn not_found(schedule_id: &str, namespace: &str) -> RuntimeError { + RuntimeError::Protocol { + status: StatusCode::NOT_FOUND, + response: json!({"message":format!("Schedule [{schedule_id}] not found in namespace [{namespace}]."), + "reason":"schedule_not_found"}), + } +} + impl Store where for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>, @@ -18,74 +37,124 @@ where for<'q> &'q str: Encode<'q, DB> + Type, for<'q> Option<&'q str>: Encode<'q, DB>, for<'q> Option: Encode<'q, DB>, + for<'q> Option: Encode<'q, DB>, for<'q> String: Encode<'q, DB> + Type, for<'q> i64: Encode<'q, DB> + Type, for<'r> i64: Decode<'r, DB>, usize: ColumnIndex, { + /// `ScheduleController::store`: the duplicate check, spec check, row, + /// creation audit and optional pause commit together. pub(super) async fn create_schedule( &self, definition: Definition, - context: Value, + created: Value, + paused: Value, ) -> Result { + let schedule_id = definition + .schedule_id + .clone() + .unwrap_or_else(|| ulid::Ulid::new().to_string()); let mut tx = self.begin().await?; - let exists = Self::query( - "SELECT id FROM workflow_schedules WHERE namespace='default' AND schedule_id=$1", + let existing = Self::query( + "SELECT status FROM workflow_schedules WHERE namespace=$1 AND schedule_id=$2", ) - .bind(&definition.schedule_id) + .bind(self.namespace.as_ref()) + .bind(&schedule_id) .fetch_optional(&mut *tx) .await?; - if exists.is_some() { - return Err(refuse(StatusCode::CONFLICT, "schedule_id_already_exists")); + if existing.is_some() { + // PHP refuses a live duplicate here; it fails a deleted one on + // the unique key. Both stay a conflict. + return Err(RuntimeError::Protocol { + status: StatusCode::CONFLICT, + response: json!({"message":format!("Schedule [{schedule_id}] already exists in namespace [{}].", self.namespace), + "reason":"schedule_already_exists","schedule_id":schedule_id}), + }); } - let schedule = id(); - let created = now(); - Self::query("INSERT INTO workflow_schedules(id,schedule_id,namespace,spec,action,status,overlap_policy,jitter_seconds,fires_count,failures_count,skipped_trigger_count,next_fire_at,created_at,updated_at) VALUES ($1,$2,'default',$3,$4,'active','skip',0,0,0,0,$5,$6,$7)") - .bind(&schedule).bind(&definition.schedule_id).bind(DB::document(&definition.spec)).bind(DB::document(&definition.action)) - .bind(DB::bind_time(definition.next_fire)).bind(DB::bind_time(created)).bind(DB::bind_time(created)).execute(&mut *tx).await?; - if definition.max_runs.is_some() { - Self::query("UPDATE workflow_schedules SET max_runs=1,remaining_actions=1 WHERE id=$1") - .bind(&schedule) + if let Some(problem) = schedules::spec_problem(&definition.spec) { + return Err(schedules::invalid_spec(problem, &schedule_id)); + } + let created_at = now(); + let next = schedules::next_fire(&definition.spec, definition.jitter_seconds, created_at)?; + let key = id(); + Self::query("INSERT INTO workflow_schedules(id,schedule_id,namespace,spec,action,status,overlap_policy,memo,jitter_seconds,max_runs,remaining_actions,fires_count,failures_count,skipped_trigger_count,note,created_at,updated_at) VALUES ($1,$2,$3,$4,$5,'active',$6,$7,$8,$9,$10,0,0,0,$11,$12,$13)") + .bind(&key).bind(&schedule_id).bind(self.namespace.as_ref()) + .bind(DB::document(&definition.spec)).bind(DB::document(&definition.action)) + .bind(&definition.overlap_policy).bind(definition.memo.as_ref().map(DB::document)) + .bind(definition.jitter_seconds).bind(definition.max_runs).bind(definition.max_runs) + .bind(definition.note.as_deref()) + .bind(DB::bind_time(created_at)).bind(DB::bind_time(created_at)).execute(&mut *tx).await?; + if let Some(next) = next { + Self::query("UPDATE workflow_schedules SET next_fire_at=$1 WHERE id=$2") + .bind(DB::bind_time(next)) + .bind(&key) .execute(&mut *tx) .await?; } Self::append_schedule( &mut tx, - &schedule, + &key, "ScheduleCreated", json!({"spec":definition.spec,"action":definition.action, - "overlap_policy":"skip","next_fire_at":definition.next_fire}), - Some(&context), + "overlap_policy":definition.overlap_policy,"next_fire_at":next.map(iso8601)}), + Some(&created), None, None, ) .await?; + if definition.paused { + Self::pause_in(&mut tx, &key, None, &paused).await?; + } tx.commit().await?; - Ok(json!({"schedule_id":definition.schedule_id,"outcome":"created"})) + Ok(json!({"schedule_id":schedule_id,"outcome":"created"})) + } + + /// `ScheduleManager::pause`: an active or paused schedule becomes paused + /// now, keeping its note unless one is given. + pub(super) async fn pause_in( + tx: &mut Transaction<'_, DB>, + key: &str, + note: Option<&str>, + context: &Value, + ) -> Result<()> { + let changed = now(); + Self::query("UPDATE workflow_schedules SET status='paused',paused_at=$1,note=COALESCE($2,note),updated_at=$3 WHERE id=$4") + .bind(DB::bind_time(changed)).bind(note).bind(DB::bind_time(changed)).bind(key).execute(&mut **tx).await?; + let mut payload = json!({"paused_at":iso8601(changed)}); + if let Some(note) = note { + payload["reason"] = json!(note); + } + Self::append_schedule(tx, key, "SchedulePaused", payload, Some(context), None, None).await } + /// The live (or, when asked, deleted) schedule in this namespace. pub(super) async fn schedule_row( + &self, tx: &mut Transaction<'_, DB>, schedule_id: &str, include_deleted: bool, ) -> Result { - let row = Self::query( - "SELECT * FROM workflow_schedules WHERE namespace='default' AND schedule_id=$1", - ) - .bind(schedule_id) - .fetch_optional(&mut **tx) - .await? - .ok_or_else(|| refuse(StatusCode::NOT_FOUND, "schedule_not_found"))?; + let row = Self::query("SELECT * FROM workflow_schedules WHERE namespace=$1 AND schedule_id=$2") + .bind(self.namespace.as_ref()) + .bind(schedule_id) + .fetch_optional(&mut **tx) + .await? + .ok_or_else(|| not_found(schedule_id, &self.namespace))?; if !include_deleted && DB::string(&row, "status")? == "deleted" { - return Err(refuse(StatusCode::NOT_FOUND, "schedule_not_found")); + return Err(not_found(schedule_id, &self.namespace)); } Ok(row) } + /// `WorkflowScheduleHistoryEvent::scheduleSnapshot`. pub(super) fn schedule_snapshot(row: &DB::Row) -> Result { - let mut snapshot = json!({"id":DB::string(row,"id")?,"schedule_id":DB::string(row,"schedule_id")?,"namespace":"default", + let mut snapshot = json!({"id":DB::string(row,"id")?,"schedule_id":DB::string(row,"schedule_id")?, "status":DB::string(row,"status")?,"overlap_policy":DB::string(row,"overlap_policy")?, "fires_count":DB::number(row,"fires_count")?,"skipped_trigger_count":DB::number(row,"skipped_trigger_count")?}); + if let Some(namespace) = DB::optional_string(row, "namespace")? { + snapshot["namespace"] = json!(namespace); + } for field in [ "next_fire_at", "last_fired_at", @@ -94,10 +163,10 @@ where "last_skipped_at", ] { if let Some(value) = DB::optional_instant(row, field)? { - snapshot[field] = json!(value); + snapshot[field] = json!(iso8601(value)); } } - for field in ["last_skip_reason", "latest_workflow_instance_id"] { + for field in ["last_skip_reason", "latest_workflow_instance_id", "note"] { if let Some(value) = DB::optional_string(row, field)? { snapshot[field] = json!(value); } @@ -118,10 +187,11 @@ where .bind(schedule) .fetch_one(&mut **tx) .await?; - payload["schedule"] = Self::schedule_snapshot(&row)?; if let Some(context) = context { payload["command_context"] = context.clone(); } + // PHP adds the snapshot after the event's own fields. + payload["schedule"] = Self::schedule_snapshot(&row)?; // The transition lock already serializes appenders. Read the physical // integer column: MariaDB promotes COALESCE(MAX(unsigned),0) to DECIMAL. let position = Self::query("SELECT sequence FROM workflow_schedule_history_events WHERE workflow_schedule_id=$1 ORDER BY sequence DESC LIMIT 1") @@ -135,9 +205,9 @@ where .checked_add(1) .ok_or_else(|| refuse(StatusCode::CONFLICT, "schedule_history_sequence_exhausted"))?; let recorded = now(); - let occurrence = payload - .get("occurrence_time") - .and_then(Value::as_str) + let occurrence = (kind == "ScheduleTriggered") + .then(|| payload.get("occurrence_time").and_then(Value::as_str)) + .flatten() .map(chrono::DateTime::parse_from_rfc3339) .transpose() .map_err(|_| refuse(StatusCode::CONFLICT, "invalid_stored_schedule_occurrence"))? @@ -147,35 +217,62 @@ where .format("%Y-%m-%d %H:%M:%S%.6f") .to_string() }); - Self::query("INSERT INTO workflow_schedule_history_events(id,workflow_schedule_id,schedule_id,namespace,sequence,event_type,payload,workflow_instance_id,workflow_run_id,recorded_at,created_at,updated_at,occurrence_at_utc) VALUES ($1,$2,$3,'default',$4,$5,$6,$7,$8,$9,$10,$11,$12)") - .bind(id()).bind(schedule).bind(DB::string(&row,"schedule_id")?).bind(sequence).bind(kind).bind(DB::document(&payload)) + Self::query("INSERT INTO workflow_schedule_history_events(id,workflow_schedule_id,schedule_id,namespace,sequence,event_type,payload,workflow_instance_id,workflow_run_id,recorded_at,created_at,updated_at,occurrence_at_utc) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)") + .bind(id()).bind(schedule).bind(DB::string(&row,"schedule_id")?).bind(DB::optional_string(&row,"namespace")?.as_deref()) + .bind(sequence).bind(kind).bind(DB::document(&payload)) .bind(workflow).bind(run).bind(DB::bind_time(recorded)).bind(DB::bind_time(recorded)).bind(DB::bind_time(recorded)).bind(occurrence.as_deref()).execute(&mut **tx).await?; Ok(()) } + /// `ScheduleController::formatDetail`. + pub(crate) fn schedule_detail(row: &DB::Row) -> Result { + let status = DB::string(row, "status")?; + let paused = status == "paused"; + let note = DB::optional_string(row, "note")?; + let time = |field: &str| -> Result { + Ok(json!(DB::optional_instant(row, field)?.map(iso8601))) + }; + let next = time("next_fire_at")?; + let last = time("last_fired_at")?; + let fires = DB::number(row, "fires_count")?; + let failures = DB::number(row, "failures_count")?; + let action = DB::optional_document_row(row, "action")? + .map(|action| schedules::normalize_action_timeouts(&action)); + Ok(json!({ + "schedule_id":DB::string(row,"schedule_id")?, + "spec":DB::optional_document_row(row,"spec")?, + "action":action, + "overlap_policy":DB::string(row,"overlap_policy")?, + "state":{"status":status,"paused":paused,"note":note}, + "info":{"next_fire":next,"last_fire":last,"fires_count":fires,"failures_count":failures, + "recent_actions":DB::optional_document_row(row,"recent_actions")?.unwrap_or_else(|| json!([])), + "buffered_actions":DB::optional_document_row(row,"buffered_actions")?.unwrap_or_else(|| json!([])), + "skipped_trigger_count":DB::number(row,"skipped_trigger_count")?, + "last_skip_reason":DB::optional_string(row,"last_skip_reason")?, + "last_skipped_at":time("last_skipped_at")?}, + "memo":DB::optional_document_row(row,"memo")?, + "search_attributes":DB::optional_document_row(row,"search_attributes")?, + "created_at":time("created_at")?, + "updated_at":time("updated_at")?, + "namespace":DB::optional_string(row,"namespace")?, + "status":status,"paused":paused,"note":note, + "fires_count":fires,"failures_count":failures, + "next_fire_at":next,"last_fired_at":last,"paused_at":time("paused_at")?, + "jitter_seconds":DB::number(row,"jitter_seconds")?, + "max_runs":DB::optional_number(row,"max_runs")?, + "remaining_actions":DB::optional_number(row,"remaining_actions")?, + "latest_workflow_instance_id":DB::optional_string(row,"latest_workflow_instance_id")?, + })) + } + pub(crate) async fn describe_schedule(&self, schedule: &str) -> Result { - let row = Self::query("SELECT * FROM workflow_schedules WHERE namespace='default' AND schedule_id=$1 AND status!='deleted'") - .bind(schedule).fetch_optional(&self.pool).await?.ok_or_else(|| refuse(StatusCode::NOT_FOUND,"schedule_not_found"))?; - let status = DB::string(&row, "status")?; - let fires = DB::number(&row, "fires_count")?; - let failures = DB::number(&row, "failures_count")?; - let skipped = DB::number(&row, "skipped_trigger_count")?; - let next = DB::optional_instant(&row, "next_fire_at")?; - let last = DB::optional_instant(&row, "last_fired_at")?; - let recent = - DB::optional_document_row(&row, "recent_actions")?.unwrap_or_else(|| json!([])); - let mut result = json!({"schedule_id":schedule,"namespace":"default","spec":DB::document_row(&row,"spec")?,"action":DB::document_row(&row,"action")?, - "status":status,"paused":status=="paused","note":null,"overlap_policy":"skip","memo":null,"search_attributes":null, - "state":{"status":status,"paused":status=="paused","note":null}, - "info":{"next_fire":next,"last_fire":last,"fires_count":fires,"failures_count":failures,"recent_actions":recent,"buffered_actions":[], - "skipped_trigger_count":skipped,"last_skip_reason":DB::optional_string(&row,"last_skip_reason")?,"last_skipped_at":DB::optional_instant(&row,"last_skipped_at")?}, - "fires_count":fires,"failures_count":failures,"next_fire_at":next,"last_fired_at":last,"jitter_seconds":0, - "max_runs":DB::optional_number(&row,"max_runs")?,"remaining_actions":DB::optional_number(&row,"remaining_actions")?, - "latest_workflow_instance_id":DB::optional_string(&row,"latest_workflow_instance_id")?}); - for field in ["created_at", "updated_at", "paused_at"] { - result[field] = json!(DB::optional_instant(&row, field)?); - } - Ok(result) + let row = Self::query("SELECT * FROM workflow_schedules WHERE namespace=$1 AND schedule_id=$2 AND status!='deleted'") + .bind(self.namespace.as_ref()) + .bind(schedule) + .fetch_optional(&self.pool) + .await? + .ok_or_else(|| not_found(schedule, &self.namespace))?; + Self::schedule_detail(&row) } pub(crate) async fn schedule_history( @@ -184,31 +281,25 @@ where after: i64, limit: i64, ) -> Result { - if after < 0 || !(1..=100).contains(&limit) { - return Err(refuse( - StatusCode::UNPROCESSABLE_ENTITY, - "invalid_schedule_history_page", - )); - } - let exists = Self::query( - "SELECT id FROM workflow_schedules WHERE namespace='default' AND schedule_id=$1", - ) - .bind(schedule) - .fetch_optional(&self.pool) - .await?; - if exists.is_none() { - return Err(refuse(StatusCode::NOT_FOUND, "schedule_not_found")); - } - let rows = Self::query("SELECT * FROM workflow_schedule_history_events WHERE namespace='default' AND schedule_id=$1 AND sequence>$2 ORDER BY sequence LIMIT $3") - .bind(schedule).bind(after).bind(limit+1).fetch_all(&self.pool).await?; + let exists = Self::query("SELECT schedule_id,namespace FROM workflow_schedules WHERE namespace=$1 AND schedule_id=$2") + .bind(self.namespace.as_ref()) + .bind(schedule) + .fetch_optional(&self.pool) + .await?; + let Some(schedule_row) = exists else { + return Err(not_found(schedule, &self.namespace)); + }; + let rows = Self::query("SELECT * FROM workflow_schedule_history_events WHERE namespace=$1 AND schedule_id=$2 AND sequence>$3 ORDER BY sequence LIMIT $4") + .bind(self.namespace.as_ref()).bind(schedule).bind(after).bind(limit+1).fetch_all(&self.pool).await?; let has_more = rows.len() > limit as usize; let mut events = Vec::new(); for row in rows.iter().take(limit as usize) { events.push(json!({"id":DB::string(row,"id")?,"sequence":DB::number(row,"sequence")?,"event_type":DB::string(row,"event_type")?, "payload":DB::document_row(row,"payload")?,"workflow_instance_id":DB::optional_string(row,"workflow_instance_id")?, - "workflow_run_id":DB::optional_string(row,"workflow_run_id")?,"recorded_at":DB::instant(row,"recorded_at")?})); + "workflow_run_id":DB::optional_string(row,"workflow_run_id")?,"recorded_at":DB::optional_instant(row,"recorded_at")?.map(iso8601)})); } - let cursor = has_more.then(|| events.last().unwrap()["sequence"].clone()); - Ok(json!({"events":events,"next_cursor":cursor})) + let cursor = (has_more && !events.is_empty()).then(|| events.last().unwrap()["sequence"].clone()); + Ok(json!({"schedule_id":DB::string(&schedule_row,"schedule_id")?,"namespace":DB::optional_string(&schedule_row,"namespace")?, + "events":events,"next_cursor":cursor,"has_more":has_more})) } } diff --git a/rust/src/runtime/schedule_zones.rs b/rust/src/runtime/schedule_zones.rs new file mode 100644 index 000000000..4945ebc4a --- /dev/null +++ b/rust/src/runtime/schedule_zones.rs @@ -0,0 +1,302 @@ +//! Time zones exactly as PHP's `new DateTimeZone($name)` reads them. +//! +//! This follows timelib's `timelib_parse_zone`: optional leading blanks or +//! `(`, a `GMT+`/`GMT-` prefix, a numeric offset after `+` or `-`, otherwise +//! one word that is first an abbreviation (PHP's table, below) and then an +//! IANA identifier, matched without regard to case, then at most as many `)` +//! as were opened and nothing else. Offsets of 26 hours or more, which PHP +//! accepts up to 100 hours, are refused. +use jiff::{ + Timestamp, + tz::{Offset, TimeZone}, +}; + +/// A zone and, for an abbreviation, its fixed daylight-saving flag. +#[derive(Clone, Debug)] +pub(crate) struct PhpZone { + pub(super) zone: TimeZone, + fixed_dst: Option, +} + +/// Zones whose standard time, in the IANA main data PHP bundles, is their +/// summer time: winter is "daylight saving" with a negative offset. The +/// bundled database here uses the positive-only convention, so PHP's flag is +/// the opposite one for current dates (Ireland since 1971, Morocco and +/// Western Sahara since 2018). +const NEGATIVE_DST_ZONES: [&str; 4] = [ + "europe/dublin", + "eire", + "africa/casablanca", + "africa/el_aaiun", +]; + +impl PhpZone { + /// PHP's `format('I')`: whether this instant is daylight-saving time. + pub(super) fn dst(&self, at: Timestamp) -> bool { + if let Some(dst) = self.fixed_dst { + return dst; + } + let dst = self.zone.to_offset_info(at).dst().is_dst(); + let negative = self.zone.iana_name().is_some_and(|name| { + NEGATIVE_DST_ZONES.contains(&name.to_ascii_lowercase().as_str()) + }); + dst != negative + } + + /// `new DateTimeZone($name)`, or `None` where PHP throws. + pub(crate) fn parse(name: &str) -> Option { + let mut rest = name.trim_start_matches([' ', '\t', '(']); + // Only as many closing parentheses as were opened may follow. + let opened = name[..name.len() - rest.len()].matches('(').count(); + if rest.len() > 3 + && rest.starts_with("GMT") + && matches!(rest.as_bytes()[3], b'+' | b'-') + { + rest = &rest[3..]; + } + let (zone, rest) = match rest.as_bytes().first() { + Some(sign @ (b'+' | b'-')) => { + let (seconds, rest) = offset(&rest[1..])?; + let seconds = if *sign == b'-' { -seconds } else { seconds }; + // timezone_initialize's range; jiff's own is narrower. + if seconds.abs() >= 100 * 3600 { + return None; + } + let offset = Offset::from_seconds(i32::try_from(seconds).ok()?).ok()?; + ( + Self { + zone: TimeZone::fixed(offset), + fixed_dst: Some(false), + }, + rest, + ) + } + _ => { + let end = rest + .find(|character: char| { + !(character.is_ascii_alphanumeric() || "/_-+".contains(character)) + }) + .unwrap_or(rest.len()); + let word = &rest[..end]; + let abbreviation = (word.len() < 6) + .then(|| abbreviation(word)) + .flatten(); + let zone = match abbreviation { + Some((seconds, dst)) if word != "UTC" => Self { + zone: TimeZone::fixed(Offset::from_seconds(seconds).ok()?), + fixed_dst: Some(dst), + }, + _ => match TimeZone::get(word) { + Ok(zone) => Self { + zone, + fixed_dst: None, + }, + Err(_) => { + let (seconds, dst) = abbreviation?; + Self { + zone: TimeZone::fixed(Offset::from_seconds(seconds).ok()?), + fixed_dst: Some(dst), + } + } + }, + }; + (zone, &rest[end..]) + } + }; + let closing = rest.len() - rest.trim_start_matches(')').len(); + (closing == rest.len() && closing <= opened).then_some(zone) + } +} + +/// `timelib_parse_tz_cor`: the digits and colons after a sign. +fn offset(text: &str) -> Option<(i64, &str)> { + let end = text + .find(|character: char| !(character.is_ascii_digit() || character == ':')) + .unwrap_or(text.len()); + let (digits, rest) = text.split_at(end); + let bytes = digits.as_bytes(); + // strtol: the leading digits of a slice, as C reads them. + let number = |from: usize| -> i64 { + digits[from.min(digits.len())..] + .bytes() + .take_while(u8::is_ascii_digit) + .fold(0i64, |value, byte| value.saturating_mul(10).saturating_add(i64::from(byte - b'0'))) + }; + let seconds = match digits.len() { + 1 | 2 => number(0) * 3600, + 3 | 4 if bytes[1] == b':' => number(0) * 3600 + number(2) * 60, + 3 | 4 if bytes[2] == b':' => number(0) * 3600 + number(3) * 60, + 3 | 4 => { + let value = number(0); + value / 100 * 3600 + value % 100 * 60 + } + 5 if bytes[2] == b':' => number(0) * 3600 + number(3) * 60, + 6 => { + let value = number(0); + value / 10000 * 3600 + (value / 100) % 100 * 60 + value % 100 + } + 8 if bytes[2] == b':' && bytes[5] == b':' => { + number(0) * 3600 + number(3) * 60 + number(6) + } + _ => return None, + }; + Some((seconds, rest)) +} + +/// The abbreviation's offset and daylight flag, as PHP resolves it: its +/// first entry in timelib's table (`DateTimeZone::listAbbreviations()`). +fn abbreviation(word: &str) -> Option<(i32, bool)> { + let lower = word.to_ascii_lowercase(); + ABBREVIATIONS + .iter() + .find(|(name, _, _)| *name == lower) + .map(|(_, seconds, dst)| (*seconds, *dst)) +} + +/// PHP 8.3's timelib abbreviations (time zone database 2026.1). +const ABBREVIATIONS: [(&str, i32, bool); 144] = [ + ("acdt", 37800, true), + ("acst", 34200, false), + ("addt", -7200, true), + ("adt", -10800, true), + ("aedt", 39600, true), + ("aest", 36000, false), + ("ahdt", -32400, true), + ("ahst", -36000, false), + ("akdt", -28800, true), + ("akst", -32400, false), + ("amt", -13840, false), + ("apt", -10800, true), + ("ast", -14400, false), + ("awdt", 32400, true), + ("awst", 28800, false), + ("awt", -10800, true), + ("bdst", 7200, true), + ("bdt", -36000, true), + ("bmt", -14309, false), + ("bst", 3600, true), + ("cast", 34200, false), + ("cat", 7200, false), + ("cddt", -14400, true), + ("cdt", -18000, true), + ("cemt", 10800, true), + ("cest", 7200, true), + ("cet", 3600, false), + ("cmt", -15408, false), + ("cpt", -18000, true), + ("cst", -21600, false), + ("cwt", -18000, true), + ("chst", 36000, false), + ("dmt", -1521, false), + ("eat", 10800, false), + ("eddt", -10800, true), + ("edt", -14400, true), + ("eest", 10800, true), + ("eet", 7200, false), + ("emt", -26248, false), + ("ept", -14400, true), + ("est", -18000, false), + ("ewt", -14400, true), + ("ffmt", -14660, false), + ("fmt", -4056, false), + ("gdt", 39600, true), + ("gmt", 0, false), + ("gst", 36000, false), + ("hdt", -34200, true), + ("hkst", 32400, true), + ("hkt", 28800, false), + ("hmt", -19776, false), + ("hpt", -34200, true), + ("hst", -36000, false), + ("hwt", -34200, true), + ("iddt", 14400, true), + ("idt", 10800, true), + ("imt", 25025, false), + ("ist", 7200, false), + ("jdt", 36000, true), + ("jmt", 8440, false), + ("jst", 32400, false), + ("kdt", 36000, true), + ("kmt", 5736, false), + ("kst", 30600, false), + ("lst", 9394, true), + ("mddt", -18000, true), + ("mdst", 16279, true), + ("mdt", -21600, true), + ("mest", 7200, true), + ("met", 3600, false), + ("mmt", 9017, false), + ("mpt", -21600, true), + ("msd", 14400, true), + ("msk", 10800, false), + ("mst", -25200, false), + ("mwt", -21600, true), + ("nddt", -5400, true), + ("ndt", -9052, true), + ("npt", -9000, true), + ("nst", -12600, false), + ("nwt", -9000, true), + ("nzdt", 46800, true), + ("nzmt", 41400, false), + ("nzst", 43200, false), + ("pddt", -21600, true), + ("pdt", -25200, true), + ("pkst", 21600, true), + ("pkt", 18000, false), + ("plmt", 25590, false), + ("pmt", -13236, false), + ("ppmt", -17340, false), + ("ppt", -25200, true), + ("pst", -28800, false), + ("pwt", -25200, true), + ("qmt", -18840, false), + ("rmt", 5794, false), + ("sast", 7200, false), + ("sdmt", -16800, false), + ("sjmt", -20173, false), + ("smt", -13884, false), + ("sst", -39600, false), + ("tbmt", 10751, false), + ("tmt", 12344, false), + ("uct", 0, false), + ("utc", 0, false), + ("wast", 7200, true), + ("wat", 3600, false), + ("wemt", 7200, true), + ("west", 3600, true), + ("wet", 0, false), + ("wib", 25200, false), + ("wita", 28800, false), + ("wit", 32400, false), + ("wmt", 5040, false), + ("yddt", -25200, true), + ("ydt", -28800, true), + ("ypt", -28800, true), + ("yst", -32400, false), + ("ywt", -28800, true), + ("a", 3600, false), + ("b", 7200, false), + ("c", 10800, false), + ("d", 14400, false), + ("e", 18000, false), + ("f", 21600, false), + ("g", 25200, false), + ("h", 28800, false), + ("i", 32400, false), + ("k", 36000, false), + ("l", 39600, false), + ("m", 43200, false), + ("n", -3600, false), + ("o", -7200, false), + ("p", -10800, false), + ("q", -14400, false), + ("r", -18000, false), + ("s", -21600, false), + ("t", -25200, false), + ("u", -28800, false), + ("v", -32400, false), + ("w", -36000, false), + ("x", -39600, false), + ("y", -43200, false), + ("z", 0, false), +]; diff --git a/rust/src/runtime/schedules.rs b/rust/src/runtime/schedules.rs index e6899d68b..2f44a37fc 100644 --- a/rust/src/runtime/schedules.rs +++ b/rust/src/runtime/schedules.rs @@ -1,168 +1,318 @@ -//! Bounded UTC interval schedule definitions; broader policies remain explicit. -use super::{Result, envelope, refuse, store::reject_fields, text}; +//! Schedule definitions as published PHP Server accepts and stores them +//! (`ScheduleController::store`/`update` and `ScheduleManager`). +use super::laravel_rules::{Rule, Rules, normalize, validate, validated}; +use super::{Result, RuntimeError, refuse, schedule_cron::Cron, schedule_spec}; use axum::http::StatusCode; use chrono::{DateTime, Utc}; -use serde_json::Value; - -pub(super) struct Definition { - pub(super) schedule_id: String, - pub(super) spec: Value, - pub(super) action: Value, - pub(super) max_runs: Option, - pub(super) next_fire: DateTime, +use serde_json::{Value, json}; + +pub(crate) const OVERLAP_POLICIES: &[&str] = &[ + "skip", + "buffer_one", + "buffer_all", + "cancel_other", + "terminate_other", + "allow_all", +]; + +/// `ScheduleController::storeRules`, in order. +const STORE_RULES: &Rules = &[ + ("schedule_id", &[Rule::Nullable, Rule::String, Rule::Max(128)]), + ("spec", &[Rule::Required, Rule::Array]), + ("spec.cron_expressions", &[Rule::Nullable, Rule::Array]), + ("spec.cron_expressions.*", &[Rule::String]), + ("spec.intervals", &[Rule::Nullable, Rule::Array]), + ( + "spec.intervals.*.every", + &[Rule::RequiredWith("spec.intervals"), Rule::String, Rule::Max(64)], + ), + ("spec.intervals.*.offset", &[Rule::Nullable, Rule::String, Rule::Max(64)]), + ("spec.timezone", &[Rule::Nullable, Rule::String, Rule::Max(64)]), + ("action", &[Rule::Required, Rule::Array]), + ("action.workflow_type", &[Rule::Required, Rule::String]), + ("action.task_queue", &[Rule::Nullable, Rule::String]), + ("action.input", &[Rule::Nullable, Rule::Array]), + ("action.execution_timeout_seconds", &[Rule::Nullable, Rule::Integer, Rule::Min(1)]), + ("action.run_timeout_seconds", &[Rule::Nullable, Rule::Integer, Rule::Min(1)]), + ("action.workflow_execution_timeout", &[Rule::Nullable, Rule::Integer, Rule::Min(1)]), + ("action.workflow_run_timeout", &[Rule::Nullable, Rule::Integer, Rule::Min(1)]), + ("overlap_policy", &[Rule::Nullable, Rule::String, Rule::In(OVERLAP_POLICIES)]), + ("jitter_seconds", &[Rule::Nullable, Rule::Integer, Rule::Min(0), Rule::Max(3600)]), + ("max_runs", &[Rule::Nullable, Rule::Integer, Rule::Min(1)]), + ("memo", &[Rule::Nullable, Rule::Array]), + ("search_attributes", &[Rule::Nullable, Rule::Array]), + ("paused", &[Rule::Nullable, Rule::Boolean]), + ("note", &[Rule::Nullable, Rule::String, Rule::Max(1000)]), +]; + +/// A validated schedule creation. +pub(crate) struct Definition { + pub(crate) schedule_id: Option, + pub(crate) spec: Value, + pub(crate) action: Value, + pub(crate) overlap_policy: String, + pub(crate) memo: Option, + pub(crate) jitter_seconds: i64, + pub(crate) max_runs: Option, + pub(crate) note: Option, + pub(crate) paused: bool, } -pub(super) fn period(spec: &Value) -> Result { - reject_fields(spec, &["intervals", "timezone"])?; - if spec["timezone"] != "UTC" - || spec["intervals"] - .as_array() - .is_none_or(|items| items.len() != 1) - { - return Err(refuse( - StatusCode::UNPROCESSABLE_ENTITY, - "unsupported_schedule_spec", - )); +/// An integer as Laravel's `integer` rule accepted it. +fn integer(value: &Value) -> Option { + value + .as_i64() + .or_else(|| value.as_f64().map(|float| float as i64)) + .or_else(|| value.as_str().and_then(|text| text.trim().parse().ok())) +} + +fn empty(value: &Value) -> bool { + match value { + Value::Null => true, + Value::Array(items) => items.is_empty(), + Value::Object(fields) => fields.is_empty(), + _ => false, } - let interval = &spec["intervals"][0]; - reject_fields(interval, &["every"])?; - match interval["every"].as_str() { - Some("P1D") => Ok(86400), - Some("PT2S") => Ok(2), - _ => Err(refuse( - StatusCode::UNPROCESSABLE_ENTITY, - "unsupported_schedule_interval", - )), +} + +/// `WorkflowSchedule::normalizeActionTimeouts`. +pub(crate) fn normalize_action_timeouts(action: &Value) -> Value { + let mut action = action.clone(); + let Some(fields) = action.as_object_mut() else { + return action; + }; + for (canonical, alias) in [ + ("execution_timeout_seconds", "workflow_execution_timeout"), + ("run_timeout_seconds", "workflow_run_timeout"), + ] { + if fields.get(canonical).is_none_or(Value::is_null) + && let Some(value) = fields.get(alias).filter(|value| !value.is_null()) + { + let value = json!(integer(value).unwrap_or(0)); + fields.insert(canonical.to_owned(), value); + } + fields.remove(alias); } + action } -pub(super) fn next(spec: &Value, after: DateTime) -> Result> { - let seconds = period(spec)?; - // The installed UTC interval calculator anchors at midnight. These two - // periods divide a UTC day, so the epoch boundary has the same phase. - let timestamp = after - .timestamp() - .div_euclid(seconds) - .checked_add(1) - .and_then(|tick| tick.checked_mul(seconds)) - .and_then(|seconds| DateTime::from_timestamp(seconds, 0)) - .ok_or_else(|| { - refuse( - StatusCode::UNPROCESSABLE_ENTITY, - "schedule_timestamp_overflow", - ) - })?; - Ok(timestamp) +/// The byte length of PHP's default `json_encode`, which escapes `/` and +/// every non-ASCII character. +pub(crate) fn php_json_len(value: &Value) -> usize { + match value { + Value::String(text) => { + 2 + text + .chars() + .map(|character| match character { + '"' | '\\' | '/' | '\u{8}' | '\u{c}' | '\n' | '\r' | '\t' => 2, + character if (character as u32) < 0x20 => 6, + character if character.is_ascii() => 1, + character => 6 * character.len_utf16(), + }) + .sum::() + } + Value::Array(items) if items.is_empty() => 2, + Value::Array(items) => 1 + items.iter().map(|item| php_json_len(item) + 1).sum::(), + Value::Object(fields) if fields.is_empty() => 2, + Value::Object(fields) => { + 1 + fields + .iter() + .map(|(key, value)| php_json_len(&json!(key)) + 1 + php_json_len(value) + 1) + .sum::() + } + other => other.to_string().len(), + } } -pub(super) fn definition(body: &Value, at: DateTime) -> Result { - reject_fields( - body, - &[ - "schedule_id", - "spec", - "action", - "overlap_policy", - "jitter_seconds", - "max_runs", - ], - )?; - let schedule_id = text(body, "schedule_id")?; - if schedule_id.len() > 128 - || !schedule_id - .bytes() - .all(|byte| byte.is_ascii_alphanumeric() || b"._:-".contains(&byte)) - { - return Err(refuse( - StatusCode::UNPROCESSABLE_ENTITY, - "invalid_schedule_id", - )); +/// `ScheduleController::validateMemoSize`. +fn memo_size(memo: &Value) -> Result<()> { + if !(memo.is_object() || memo.is_array()) { + return Ok(()); + } + let limit = std::env::var("DW_MAX_MEMO_BYTES") + .or_else(|_| std::env::var("WORKFLOW_MAX_MEMO_BYTES")) + .ok() + .and_then(|value| value.trim().parse::().ok()) + .unwrap_or(256 * 1024); + if php_json_len(memo) as i64 > limit { + return Err(RuntimeError::Protocol { + status: StatusCode::UNPROCESSABLE_ENTITY, + response: json!({"message":format!("The memo exceeds the maximum allowed size of {limit} bytes."), + "reason":"memo_too_large","limit":limit}), + }); } - if body["overlap_policy"] != "skip" || body["jitter_seconds"].as_i64() != Some(0) { + Ok(()) +} + +/// `ScheduleManager::assertValidSpec`, with PHP's messages. +pub(crate) fn spec_problem(spec: &Value) -> Option { + let cron = spec.get("cron_expressions").unwrap_or(&Value::Null); + let intervals = spec.get("intervals").unwrap_or(&Value::Null); + let list = |value: &Value| value.is_null() || value.is_array() || value.is_object(); + if !list(cron) || !list(intervals) { + return Some("Schedule spec cron_expressions/intervals must be arrays.".into()); + } + if empty(cron) && empty(intervals) { + return Some( + "Schedule spec must include at least one cron_expression or interval.".into(), + ); + } + let expressions: Vec<&Value> = match cron { + Value::Array(items) => items.iter().collect(), + Value::Object(fields) => fields.values().collect(), + _ => Vec::new(), + }; + for expression in expressions { + if !expression.as_str().is_some_and(Cron::is_valid) { + let text = match expression { + Value::String(text) => text.clone(), + Value::Number(number) => number.to_string(), + Value::Bool(true) => "1".into(), + _ => String::new(), + }; + return Some(format!("Invalid cron expression: [{text}].")); + } + } + None +} + +/// `ScheduleController::invalidScheduleSpecResponse`. +pub(crate) fn invalid_spec(message: String, schedule_id: &str) -> RuntimeError { + let cron = message.starts_with("Invalid cron expression"); + let field = if cron { "spec.cron_expressions" } else { "spec" }; + RuntimeError::Protocol { + status: StatusCode::UNPROCESSABLE_ENTITY, + response: json!({"message":message, + "reason": if cron {"invalid_cron_expression"} else {"invalid_schedule_spec"}, + "schedule_id":schedule_id,"errors":{field:[message]}}), + } +} + +/// Validates a creation body as PHP's `store` does, before any lookup. +pub(crate) fn definition(body: &Value) -> Result { + let body = normalize(body); + validate(&body, STORE_RULES)?; + let validated = validated(&body, STORE_RULES); + memo_size(&validated["memo"])?; + if !empty(&validated["search_attributes"]) { + // Native runs carry no search attribute values yet. return Err(refuse( StatusCode::UNPROCESSABLE_ENTITY, - "unsupported_schedule_policy", + "schedule_search_attributes_unavailable", )); } - let max_runs = match body.get("max_runs").filter(|value| !value.is_null()) { - None => None, - Some(value) if value.as_i64() == Some(1) => Some(1), - Some(_) => { - return Err(refuse( - StatusCode::UNPROCESSABLE_ENTITY, - "unsupported_schedule_quota", - )); - } - }; - let action = &body["action"]; - reject_fields( - action, - &[ - "workflow_type", - "task_queue", - "input", - "execution_timeout_seconds", - "run_timeout_seconds", - ], - )?; - text(action, "workflow_type")?; - text(action, "task_queue")?; - envelope(action, "input")?; - for (field, expected) in [ - ("execution_timeout_seconds", 3600), - ("run_timeout_seconds", 600), - ] { - if action[field].as_i64() != Some(expected) { - return Err(refuse( - StatusCode::UNPROCESSABLE_ENTITY, - "unsupported_schedule_action_timeout", - )); - } + let action = normalize_action_timeouts(&validated["action"]); + if action + .get("input") + .is_some_and(|input| !input.is_null()) + { + super::envelope(&action, "input")?; } Ok(Definition { - schedule_id: schedule_id.to_owned(), - spec: body["spec"].clone(), - action: action.clone(), - max_runs, - next_fire: next(&body["spec"], at)?, + schedule_id: validated["schedule_id"].as_str().map(str::to_owned), + spec: validated["spec"].clone(), + action, + overlap_policy: validated["overlap_policy"] + .as_str() + .filter(|policy| OVERLAP_POLICIES.contains(policy)) + .unwrap_or("skip") + .to_owned(), + memo: Some(validated["memo"].clone()).filter(|memo| !empty(memo)), + jitter_seconds: integer(&validated["jitter_seconds"]).unwrap_or(0), + max_runs: integer(&validated["max_runs"]), + note: validated["note"].as_str().map(str::to_owned), + paused: validated["paused"].as_bool().unwrap_or(false) + || validated["paused"].as_i64() == Some(1) + || validated["paused"].as_str() == Some("1"), }) } +/// `computeNextFireAtWithJitter`: the next fire time after `after`, plus up +/// to `jitter` random seconds. PHP raises a cron evaluation failure; native +/// reports it as an invalid spec. +pub(crate) fn next_fire(spec: &Value, jitter: i64, after: DateTime) -> Result>> { + let at = jiff::Timestamp::new(after.timestamp(), after.timestamp_subsec_nanos() as i32) + .map_err(|_| refuse(StatusCode::UNPROCESSABLE_ENTITY, "invalid_schedule_spec"))?; + let next = schedule_spec::next_fire(spec, at).map_err(|error| RuntimeError::Protocol { + status: StatusCode::UNPROCESSABLE_ENTITY, + response: json!({"message":format!("Schedule spec could not be evaluated ({}).", error.php_class()), + "reason":"invalid_schedule_spec"}), + })?; + Ok(next.map(|next| { + let seconds = next.as_second() + if jitter > 0 { random_up_to(jitter) } else { 0 }; + DateTime::from_timestamp(seconds, next.subsec_nanosecond() as u32).unwrap_or(after) + })) +} + +/// A uniform integer in `0..=limit`, like PHP's `random_int(0, $limit)`. +fn random_up_to(limit: i64) -> i64 { + let mut bytes = [0u8; 8]; + if getrandom::fill(&mut bytes).is_err() { + return 0; + } + (u64::from_le_bytes(bytes) % (limit as u64 + 1)) as i64 +} + #[cfg(test)] mod tests { use super::*; - use serde_json::json; - #[test] - fn utc_interval_preserves_phase_and_never_returns_the_current_boundary() { - for every in ["PT2S", "P1D"] { - let spec = json!({"intervals":[{"every":every}],"timezone":"UTC"}); - let period = period(&spec).unwrap(); - for instant in [ - "2026-10-10T00:00:00Z", - "2026-10-10T00:00:00.000001Z", - "2026-10-10T23:59:59.999999Z", - ] { - let now = DateTime::parse_from_rfc3339(instant) - .unwrap() - .with_timezone(&Utc); - let next = next(&spec, now).unwrap(); - assert!(next > now); - assert_eq!(next.timestamp().rem_euclid(period), 0); - assert!(next - now <= chrono::Duration::seconds(period)); - } + fn refusal(body: Value) -> Value { + match definition(&body) { + Err(RuntimeError::Protocol { response, .. }) => response, + Err(RuntimeError::Refused { reason, .. }) => json!({"reason":reason}), + Err(other) => panic!("{other:?}"), + Ok(_) => panic!("accepted {body}"), } } #[test] - fn unqualified_calendar_offset_and_timezone_rules_are_refused() { - for spec in [ - json!({"intervals":[{"every":"P1M"}],"timezone":"UTC"}), - json!({"intervals":[{"every":"PT2S","offset":"PT1S"}],"timezone":"UTC"}), - json!({"intervals":[{"every":"PT2S"}],"timezone":"Europe/Budapest"}), - json!({"cron_expressions":["* * * * *"],"timezone":"UTC"}), - ] { - assert!(period(&spec).is_err()); - } + fn creation_follows_php_validation_and_normalization() { + let created = definition(&json!({"schedule_id":" nightly ","spec":{"cron_expressions":["0 3 * * *"],"timezone":"Europe/Budapest"}, + "action":{"workflow_type":"echo","workflow_execution_timeout":"90","workflow_run_timeout":30}, + "overlap_policy":"buffer_one","jitter_seconds":"5","max_runs":3,"memo":{},"paused":"1","note":" "})) + .unwrap(); + assert_eq!(created.schedule_id.as_deref(), Some("nightly")); + assert_eq!( + created.action, + json!({"workflow_type":"echo","execution_timeout_seconds":90,"run_timeout_seconds":30}) + ); + assert_eq!(created.overlap_policy, "buffer_one"); + assert_eq!((created.jitter_seconds, created.max_runs), (5, Some(3))); + assert!(created.memo.is_none() && created.note.is_none() && created.paused); + assert_eq!( + refusal(json!({}))["errors"], + json!({"spec":["The spec field is required."],"action":["The action field is required."], + "action.workflow_type":["The action.workflow type field is required."]}) + ); + assert_eq!( + refusal(json!({"spec":{"intervals":[{"every":"PT1H"}]},"action":{"workflow_type":"echo"},"search_attributes":{"team":"ops"}}))["reason"], + "schedule_search_attributes_unavailable" + ); + let large = json!({"spec":{"intervals":[{"every":"PT1H"}]},"action":{"workflow_type":"echo"},"memo":{"text":"é".repeat(50000)}}); + assert_eq!(refusal(large)["reason"], "memo_too_large"); + } + + #[test] + fn spec_problems_use_php_messages() { + assert_eq!( + spec_problem(&json!({})).as_deref(), + Some("Schedule spec must include at least one cron_expression or interval.") + ); + assert_eq!( + spec_problem(&json!({"cron_expressions":"0 3 * * *"})).as_deref(), + Some("Schedule spec cron_expressions/intervals must be arrays.") + ); + assert_eq!( + spec_problem(&json!({"cron_expressions":["0 3 * *"]})).as_deref(), + Some("Invalid cron expression: [0 3 * *].") + ); + assert_eq!(spec_problem(&json!({"intervals":[{"every":"PT1H"}]})), None); + } + + #[test] + fn php_json_length_counts_escapes() { + assert_eq!(php_json_len(&json!({"a":"\u{e9}/"})), 16); // {"a":"\\u00e9\\/"} + assert_eq!(php_json_len(&json!([1, "x", {}, []])), r#"[1,"x",[],[]]"#.len()); } } diff --git a/rust/tests/execution/namespaces.rs b/rust/tests/execution/namespaces.rs index 986aa32f3..a0481c2de 100644 --- a/rust/tests/execution/namespaces.rs +++ b/rust/tests/execution/namespaces.rs @@ -547,7 +547,7 @@ async fn same_worker_id_isolated_claims_completion_and_deregistration_by_namespa scoped(&app, "alpha", "GET", &history_path, Value::Null).await, before ); - for uri in ["/api/workflows", "/api/schedules/unqualified"] { + for uri in ["/api/workflows"] { let unavailable = scoped(&app, "alpha", "GET", uri, Value::Null).await; assert_eq!(unavailable.0, StatusCode::UNPROCESSABLE_ENTITY); assert_eq!( diff --git a/rust/tests/execution/schedules.rs b/rust/tests/execution/schedules.rs index 28bff1548..ba58a3733 100644 --- a/rust/tests/execution/schedules.rs +++ b/rust/tests/execution/schedules.rs @@ -355,3 +355,45 @@ async fn scheduled_occurrence_survives_restart_and_competing_nodes_admit_one_ori b.close().await; database.remove().await; } + +#[tokio::test] +async fn schedules_are_namespace_scoped_and_fire_into_their_own_namespace() { + use super::namespaces::{create as create_namespace, scoped}; + let database = TestDatabase::new().await; + let runtime = database.open().await.unwrap(); + let app = router(runtime.clone()); + create_namespace(&app, "alpha").await; + let body = json!({"schedule_id":"shared","spec":{"cron_expressions":["0 3 1 1 *"],"timezone":"Europe/Budapest"}, + "action":action(),"overlap_policy":"allow_all"}); + for namespace in ["alpha", "default"] { + let created = scoped(&app, namespace, "POST", "/api/schedules", body.clone()).await; + assert_eq!(created.0, StatusCode::CREATED, "{}", created.1); + } + let described = scoped(&app, "alpha", "GET", "/api/schedules/shared", Value::Null).await; + assert_eq!(described.0, StatusCode::OK, "{}", described.1); + assert_eq!(described.1["namespace"], "alpha"); + // 03:00 in Budapest on January 1 is 02:00 UTC. + assert!(described.1["next_fire_at"].as_str().unwrap().ends_with("-01-01T02:00:00+00:00")); + // Deleting one namespace's schedule leaves the other's. + let deleted = scoped(&app, "alpha", "DELETE", "/api/schedules/shared", Value::Null).await; + assert_eq!(deleted.0, StatusCode::OK, "{}", deleted.1); + let gone = scoped(&app, "alpha", "GET", "/api/schedules/shared", Value::Null).await; + assert_eq!(gone.0, StatusCode::NOT_FOUND); + assert_eq!(gone.1["message"], "Schedule [shared] not found in namespace [alpha]."); + let kept = request(&app, "GET", "/api/schedules/shared", Value::Null).await; + assert_eq!((kept.0, kept.1["status"].clone()), (StatusCode::OK, json!("active"))); + // A trigger starts its run in the schedule's namespace only. + let other = scoped(&app, "alpha", "POST", "/api/schedules", + json!({"schedule_id":"alpha-only","spec":{"intervals":[{"every":"P1D"}]},"action":action()})).await; + assert_eq!(other.0, StatusCode::CREATED, "{}", other.1); + let triggered = scoped(&app, "alpha", "POST", "/api/schedules/alpha-only/trigger", json!({})).await; + assert_eq!(triggered.1["outcome"], "triggered", "{}", triggered.1); + let workflow = triggered.1["workflow_id"].as_str().unwrap(); + let path = format!("/api/workflows/{workflow}"); + assert_eq!(scoped(&app, "alpha", "GET", &path, Value::Null).await.0, StatusCode::OK); + assert_eq!(request(&app, "GET", &path, Value::Null).await.0, StatusCode::NOT_FOUND); + let missing = request(&app, "POST", "/api/schedules/alpha-only/trigger", json!({})).await; + assert_eq!(missing.0, StatusCode::NOT_FOUND); + runtime.close().await; + database.remove().await; +} diff --git a/rust/tests/fixtures/cron/php-2.5.14-next-fire.jsonl b/rust/tests/fixtures/cron/php-2.5.14-next-fire.jsonl new file mode 100644 index 000000000..39f0c22a6 --- /dev/null +++ b/rust/tests/fixtures/cron/php-2.5.14-next-fire.jsonl @@ -0,0 +1,600 @@ +{"spec":{"cron_expressions":["0 12 29 2 *"],"timezone":"Mars/Base"},"after":"2026-02-09T09:29:21.684233Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P0D"},{"every":"P0Y2MT30H78M"}],"timezone":"America/New_York"},"after":"2028-02-07T21:46:03.192404Z","next":"2028-03-29T13:06:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","0 3 * * *"],"timezone":"EST"},"after":"2027-12-20T21:26:46.708009Z","next":"2027-12-20T22:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","0 12 29 2 *"],"intervals":[{"every":"P10M2W18D"}]},"after":"2028-08-23T21:03:01.395881Z","next":"2028-08-23T22:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P0W"}],"timezone":""},"after":"2028-02-19T09:21:48.608503Z","next":null} +{"spec":{"cron_expressions":["@hourly","0 0 1 * *"],"timezone":"Europe/Budapest"},"after":"2028-03-16T09:24:31.179399Z","next":"2028-03-16T10:00:00.000000Z"} +{"spec":{"timezone":""},"after":"2027-12-15T02:33:48.376137Z","next":null} +{"spec":{"cron_expressions":["0 3 * * *","0 0 1 * *"],"intervals":[{"every":"P0Y39DT82M"},{"every":"P3WT760S"}],"timezone":"+05:30"},"after":"2027-03-17T15:25:19.081189Z","next":"2027-03-17T21:30:00.000000Z"} +{"spec":{"intervals":[{"every":"P6M7DT44H"},{"every":"P14D"}],"timezone":"Australia/Lord_Howe"},"after":"2027-03-17T10:24:27.201519Z","next":"2027-03-25T00:00:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?"],"timezone":""},"after":"2028-08-03T14:15:14.715307Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P1W23DT971S"},{"every":"PT10H"}]},"after":"2025-10-07T05:41:13.166011Z","next":"2025-10-07T08:00:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","bad cron"]},"after":"2028-04-10T03:51:37.879190Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"PT16H7164S","offset":"PT45H13M"}],"timezone":"+05:30"},"after":"2028-06-03T12:05:40.359424Z","next":"2028-06-03T18:34:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","0 0 1 * *"],"intervals":[{"every":"P3WT0H3755S","offset":"P1YT28H1386S"}],"timezone":"UTC"},"after":"2027-05-11T22:31:42.785856Z","next":"2027-05-29T17:32:11.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"timezone":"Mars/Base"},"after":"2028-12-26T08:39:30.997893Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"PT6304S"},{"every":"P0W28DT15H","offset":"P2D1W"}],"timezone":"UTC"},"after":"2028-12-11T03:32:27.175651Z","next":"2028-12-11T04:21:20.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","0 0 1 * *"],"intervals":[{"every":"PT30M1129S","offset":"PT19H"},{"every":"PT83M169S","offset":"P9MT40M2161S"}],"timezone":"UTC"},"after":"2028-08-20T12:23:20.576017Z","next":"2028-08-20T12:30:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"timezone":"Australia/Lord_Howe"},"after":"2027-06-23T01:12:38.155251Z","next":"2028-02-29T01:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT1121S"}],"timezone":"America/New_York"},"after":"2025-05-24T05:09:59.133868Z","next":"2025-05-24T05:17:39.000000Z"} +{"spec":{"intervals":[{"every":"PT2366S"},{"every":"P31DT6580S"}],"timezone":"Asia/Kolkata"},"after":"2026-10-01T16:20:50.446536Z","next":"2026-10-01T16:38:40.000000Z"} +{"spec":{"cron_expressions":["bad cron",""],"timezone":"America/New_York"},"after":"2027-06-22T12:18:37.280503Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P8M1WT50H50M"}],"timezone":"Australia/Lord_Howe"},"after":"2026-04-24T07:51:17.148229Z","next":"2026-08-11T19:10:00.000000Z"} +{"spec":{"intervals":[{"every":"P7M","offset":"P2Y9DT41H"}],"timezone":"Mars/Base"},"after":"2025-11-18T20:32:25.471373Z","next":"2026-01-27T17:00:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","0 12 29 2 *"],"timezone":"+05:30"},"after":"2025-11-26T19:57:59.982784Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 9 * * 1-5"]},"after":"2026-10-18T02:53:41.643187Z","next":"2026-10-19T09:00:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"P5MT41M"},{"every":"P3WT1M359S"}],"timezone":"Mars/Base"},"after":"2026-12-22T10:12:08.919035Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["0 12 29 2 *","0 0 1 * *"],"intervals":[{"every":"P0YT39H1494S"}],"timezone":"Mars/Base"},"after":"2026-10-01T00:49:10.193998Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["0 12 29 2 *","0 12 29 2 *"],"intervals":[{"every":"P2Y4D"},{"every":"P2DT1921S"}],"timezone":"Australia/Lord_Howe"},"after":"2026-04-19T09:01:55.922271Z","next":"2026-04-21T02:17:29.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","30 2 * * SUN"],"intervals":[{"every":"P15DT48M"}],"timezone":"Australia/Lord_Howe"},"after":"2027-07-03T02:05:52.166470Z","next":"2027-07-03T02:15:00.000000Z"} +{"spec":{"intervals":[{"every":"P0YT24H7132S"},{"every":"P0D"}],"timezone":"Asia/Kolkata"},"after":"2027-12-18T02:17:58.192301Z","next":"2027-12-18T06:36:32.000000Z"} +{"spec":{"timezone":"EST"},"after":"2028-03-18T15:40:10.783988Z","next":null} +{"spec":{"cron_expressions":["30 2 * * SUN","bad cron"],"intervals":[{"every":"PT303S"},{"every":"PT53M637S","offset":"P3W"}],"timezone":""},"after":"2026-12-23T13:29:09.082410Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P33D"}]},"after":"2027-07-26T07:12:48.904833Z","next":"2027-08-24T00:00:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","bad cron"],"intervals":[{"every":"PT14H"}],"timezone":"America/New_York"},"after":"2025-02-18T14:29:12.170410Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P0WT24H"}],"timezone":"Europe/Budapest"},"after":"2028-01-20T19:03:56.187132Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"PT0S"},{"every":"P8M1W","offset":"PT10H"}],"timezone":"Asia/Kolkata"},"after":"2028-07-17T18:25:06.530828Z","next":"2028-11-01T10:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT5803S"}]},"after":"2027-05-13T01:37:32.581499Z","next":"2027-05-13T02:43:34.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?","bad cron"],"intervals":[{"every":"P2Y"},{"every":"P0WT45M"}],"timezone":"America/New_York"},"after":"2028-02-20T07:25:36.838272Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"PT2002S"}],"timezone":"EST"},"after":"2025-03-29T13:26:40.292059Z","next":"2025-03-29T13:30:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P1D ","offset":"PT2H"},{"every":"PT1327S","offset":"P2Y14M"}],"timezone":"Europe/Budapest"},"after":"2027-02-02T00:49:07.776196Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 3 * * *"],"intervals":[{"every":"P1WT5473S"}],"timezone":"America/New_York"},"after":"2026-07-20T04:33:34.275358Z","next":"2026-07-20T07:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"timezone":"UTC"},"after":"2025-09-02T14:36:10.957055Z","next":"2025-09-03T03:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P2WT12M6553S"},{"every":"PT6M","offset":""}],"timezone":"Australia/Lord_Howe"},"after":"2028-02-23T17:47:09.731427Z","next":"2028-02-23T17:48:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 3 * * *"],"intervals":[{"every":"P2Y"}],"timezone":""},"after":"2028-06-22T12:16:29.346585Z","error":"DateInvalidTimeZoneException"} +{"spec":{"timezone":"Europe/Budapest"},"after":"2026-09-19T13:27:57.572792Z","next":null} +{"spec":{"cron_expressions":["0 9 * * 1-5","*/15 * * * *"],"intervals":[{"every":"P24DT50H89M6622S","offset":"P0Y7MT47H"}],"timezone":"+05:30"},"after":"2027-01-14T18:32:42.092624Z","next":"2027-01-14T18:45:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P1Y35D"}],"timezone":"Europe/Budapest"},"after":"2026-08-07T22:55:31.459048Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"PT3447S","offset":"P1WT14H"},{"every":"P0WT15H","offset":"P0Y10M9DT90M"}],"timezone":"Mars/Base"},"after":"2025-04-07T02:32:26.217195Z","next":"2025-04-07T03:22:12.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"PT639S","offset":"PT1970S"}]},"after":"2025-06-08T15:15:16.360987Z","next":"2025-06-08T15:21:44.000000Z"} +{"spec":{"timezone":"America/New_York"},"after":"2027-08-20T20:24:40.689560Z","next":null} +{"spec":{"cron_expressions":["","0 0 1 * *"],"timezone":"EST"},"after":"2025-03-24T19:19:49.674151Z","next":"2025-04-01T05:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P1MT47H29M","offset":"P0MT6771S"},{"every":"PT1319S"}],"timezone":"Australia/Lord_Howe"},"after":"2028-10-19T05:48:21.714653Z","next":"2028-10-19T06:01:36.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT71M"},{"every":"P6MT43H84M4807S"}],"timezone":"Asia/Kolkata"},"after":"2026-12-29T22:43:53.203631Z","next":"2026-12-29T22:53:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 3 * * *"],"intervals":[{"every":"P2WT5852S"}]},"after":"2026-08-31T08:28:55.219966Z","next":"2026-08-31T09:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 0 1 * *"],"intervals":[{"every":"P1YT74M208S"},{"every":"PT17H","offset":"P1W2D"}],"timezone":"+05:30"},"after":"2028-05-02T05:00:31.168474Z","next":"2028-05-02T11:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P1M1W0DT5875S"}],"timezone":"Australia/Lord_Howe"},"after":"2025-06-04T07:37:18.370045Z","next":"2025-07-07T04:40:25.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","0 3 * * *"],"intervals":[{"every":"P1W16DT28H","offset":"P1MT15H72M"},{"every":"P36D"}]},"after":"2025-04-15T15:29:32.536226Z","next":"2025-04-15T15:30:00.000000Z"} +{"spec":{"intervals":[{"every":"PT40H"},{"every":"P17DT32H52M"}]},"after":"2027-03-02T19:10:56.326103Z","next":"2027-03-03T00:00:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P7M0W16DT76M7185S","offset":"P1Y0W20DT1M"},{"every":"P2D1W"}],"timezone":"UTC"},"after":"2026-07-23T10:05:53.139117Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"PT21H56M4886S"}],"timezone":"+05:30"},"after":"2027-01-11T09:33:13.258129Z","next":"2027-01-11T12:29:04.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P10000000000000000000D"}]},"after":"2026-09-09T23:04:56.976613Z","next":"2028-02-29T12:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P2Y39DT13H"}],"timezone":"EST"},"after":"2028-09-18T12:28:37.154784Z","next":"2028-12-29T04:00:00.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P1YT43H","offset":"PT30H"},{"every":"P2Y1W26DT52M"}],"timezone":"Europe/Budapest"},"after":"2025-04-22T19:13:38.965049Z","next":"2026-03-29T14:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"P7M1W2DT31H"},{"every":"P10000000000000000000D"}],"timezone":"UTC"},"after":"2028-08-02T02:33:47.952177Z","next":"2028-08-02T09:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","30 2 * * SUN"],"intervals":[{"every":"PT290S"},{"every":"P3M"}],"timezone":"Asia/Kolkata"},"after":"2025-09-17T23:04:00.338112Z","next":"2025-09-17T23:08:40.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"PT1H"},{"every":"PT49H"}],"timezone":"Europe/Budapest"},"after":"2028-06-03T07:44:27.309552Z","next":"2028-06-03T08:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P5MT18H","offset":"P2D1W"},{"every":"P8MT41H27M"}],"timezone":"UTC"},"after":"2026-01-13T14:18:49.661599Z","next":"2026-02-18T00:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"timezone":"Europe/Budapest"},"after":"2027-05-11T19:42:53.913592Z","next":"2027-05-12T01:00:00.000000Z"} +{"spec":[],"after":"2027-02-16T12:56:37.387766Z","next":null} +{"spec":{"intervals":[{"every":"PT47H4M6730S","offset":"P2Y11MT9H"}],"timezone":"+05:30"},"after":"2026-01-21T09:23:20.792750Z","next":"2026-01-22T18:39:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","*/15 * * * *"],"timezone":"UTC"},"after":"2028-05-14T07:53:50.895097Z","next":"2028-05-14T08:00:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?"],"intervals":[{"every":"P14MT2335S"}],"timezone":"Australia/Lord_Howe"},"after":"2026-01-30T11:41:13.604252Z","next":"2026-01-30T17:05:00.000000Z"} +{"spec":{"intervals":[{"every":"P1YT76M","offset":"PT79M"},{"every":"P3W"}],"timezone":"+05:30"},"after":"2028-09-25T05:36:42.848528Z","next":"2028-10-05T00:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"timezone":"Asia/Kolkata"},"after":"2027-02-24T21:06:47.956260Z","next":"2027-02-25T03:30:00.000000Z"} +{"spec":[],"after":"2028-04-14T03:36:28.375994Z","next":null} +{"spec":{"cron_expressions":["0 3 * * *","0 9 * * 1-5"],"intervals":[{"every":"PT6H","offset":"P6DT84M"}],"timezone":"Australia/Lord_Howe"},"after":"2028-05-12T11:30:44.395347Z","next":"2028-05-12T13:24:00.000000Z"} +{"spec":{"intervals":[{"every":"P24D"},{"every":"P2WT4279S"}],"timezone":"UTC"},"after":"2027-12-30T23:05:47.325379Z","next":"2028-01-03T16:25:32.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P1YT43M"},{"every":"P29D"}]},"after":"2026-02-08T08:54:23.812658Z","next":"2026-02-08T09:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT3583S"}],"timezone":""},"after":"2025-01-22T23:26:16.675758Z","error":"DateInvalidTimeZoneException"} +{"spec":{"timezone":"Europe/Budapest"},"after":"2026-05-30T23:23:52.266131Z","next":null} +{"spec":{"cron_expressions":["*/15 * * * *"],"timezone":"EST"},"after":"2028-02-14T23:50:14.470614Z","next":"2028-02-15T00:00:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *",""],"intervals":[{"every":"P0YT1H","offset":"P0WT8H"}],"timezone":""},"after":"2027-06-07T19:53:22.969234Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"timezone":"Asia/Kolkata"},"after":"2028-05-29T03:34:56.514041Z","next":"2028-06-03T21:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","0 0 1 * *"]},"after":"2027-08-15T11:40:57.096896Z","next":"2027-08-16T03:00:00.000000Z"} +{"spec":{"timezone":""},"after":"2028-12-17T02:50:39.664623Z","next":null} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P0M0W"}],"timezone":"Australia/Lord_Howe"},"after":"2028-05-15T17:00:17.591055Z","next":"2028-05-15T17:30:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?"],"timezone":"+05:30"},"after":"2026-10-02T12:30:13.817300Z","next":"2026-10-30T22:35:00.000000Z"} +{"spec":{"cron_expressions":[""],"timezone":"Australia/Lord_Howe"},"after":"2025-10-11T11:52:59.497618Z","next":null} +{"spec":{"cron_expressions":["30 2 * * SUN"],"intervals":[{"every":"P3DT72M4810S","offset":"PT26H"}]},"after":"2026-11-06T16:56:55.714163Z","next":"2026-11-08T02:30:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P1W0DT2478S","offset":"PT1H1D"}],"timezone":"Australia/Lord_Howe"},"after":"2025-08-26T04:20:23.672791Z","next":"2025-08-26T16:30:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?","5 4 L * ?"],"timezone":"America/New_York"},"after":"2028-09-26T17:09:54.285672Z","next":"2028-09-30T08:05:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *","0 12 29 2 *"],"intervals":[{"every":"PT41H"},{"every":"P2YT768S","offset":"PT74M"}]},"after":"2027-08-10T09:35:48.829688Z","next":"2027-08-11T13:00:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"]},"after":"2026-12-18T02:37:10.737793Z","next":"2027-01-01T00:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT2280S"}],"timezone":"Asia/Kolkata"},"after":"2027-04-10T18:08:32.387529Z","next":"2027-04-10T18:18:00.000000Z"} +{"spec":{"intervals":[{"every":"P2YT3H1M6031S"},{"every":"PT4H5264S"}],"timezone":"Europe/Budapest"},"after":"2027-11-27T21:21:56.470736Z","next":"2027-11-27T22:50:56.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?"],"intervals":[{"every":"PT24H73M"}],"timezone":"Asia/Kolkata"},"after":"2028-06-29T21:20:34.587113Z","next":"2028-06-29T22:35:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?","5 4 L * ?"],"timezone":"Australia/Lord_Howe"},"after":"2026-11-09T12:55:56.491228Z","next":"2026-11-29T17:05:00.000000Z"} +{"spec":{"cron_expressions":["","5 4 L * ?"]},"after":"2027-03-30T00:24:20.232551Z","next":"2027-03-31T04:05:00.000000Z"} +{"spec":{"timezone":"UTC"},"after":"2028-04-08T18:25:16.244290Z","next":null} +{"spec":{"intervals":[{"every":"P0000-00-00T00:00:01"}],"timezone":""},"after":"2025-10-16T15:22:04.351586Z","next":"2025-10-16T15:22:05.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P24DT13H","offset":"PT73M"},{"every":"PT0001H","offset":"PT179S"}]},"after":"2027-12-21T07:35:16.004358Z","next":"2027-12-21T08:00:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?"],"intervals":[{"every":"P2Y2W"}]},"after":"2026-10-23T14:21:07.012334Z","next":"2026-10-31T04:05:00.000000Z"} +{"spec":{"intervals":[{"every":"PT2H334S"}],"timezone":"+05:30"},"after":"2027-06-07T14:07:16.241567Z","next":"2027-06-07T14:37:20.000000Z"} +{"spec":{"timezone":"UTC"},"after":"2025-12-18T08:15:38.145916Z","next":null} +{"spec":{"cron_expressions":["0 9 * * 1-5","30 2 * * SUN"],"intervals":[{"every":"P1W19D"},{"every":"P1Y2WT27H4475S"}],"timezone":"+05:30"},"after":"2026-05-31T12:48:26.420842Z","next":"2026-06-01T03:30:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","bad cron"],"intervals":[{"every":"P1Y14DT16H","offset":"P7DT18H33M1276S"}],"timezone":"+05:30"},"after":"2025-07-29T21:21:24.540313Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"PT3498S"},{"every":"P1YT32M"}],"timezone":"+05:30"},"after":"2025-03-13T22:30:44.038395Z","next":"2025-03-13T22:47:36.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"PT0001H"},{"every":"PT2039S"}],"timezone":"America/New_York"},"after":"2026-01-07T13:11:28.272076Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 3 * * *"]},"after":"2025-05-31T22:03:34.191444Z","next":"2025-06-01T03:00:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","0 0 1 * *"],"intervals":[{"every":"P1Y3M"},{"every":"PT3415S"}],"timezone":"UTC"},"after":"2027-09-05T14:55:07.050258Z","next":"2027-09-05T15:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT0S"},{"every":"P3M"}],"timezone":"+05:30"},"after":"2028-10-09T18:24:27.498224Z","next":"2028-11-22T00:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P2Y4DT773S"}],"timezone":"+05:30"},"after":"2028-01-24T08:19:31.383572Z","next":"2028-04-12T06:13:37.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"timezone":"America/New_York"},"after":"2025-10-01T11:56:40.740893Z","next":"2025-10-05T06:30:00.000000Z"} +{"spec":{"intervals":[{"every":"P2WT41H","offset":"P2Y21DT3094S"},{"every":"PT10H"}],"timezone":"Europe/Budapest"},"after":"2027-05-23T03:04:39.679839Z","next":"2027-05-23T06:00:00.000000Z"} +{"spec":{"timezone":"America/New_York"},"after":"2027-10-17T15:41:49.694977Z","next":null} +{"spec":{"cron_expressions":["0 0 1 * *"],"timezone":"Asia/Kolkata"},"after":"2028-07-30T19:04:59.738919Z","next":"2028-07-31T18:30:00.000000Z"} +{"spec":{"intervals":[{"every":"P1YT40H78M514S"},{"every":"P2M2W36D","offset":"PT27H"}],"timezone":"America/New_York"},"after":"2026-11-08T11:01:41.556930Z","next":"2026-12-04T03:00:00.000000Z"} +{"spec":{"timezone":"EST"},"after":"2025-06-03T22:27:12.880236Z","next":null} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 12 29 2 *"],"intervals":[{"every":"PT88M"}]},"after":"2025-08-30T11:13:44.507694Z","next":"2025-08-30T12:08:00.000000Z"} +{"spec":{"intervals":[{"every":"P1Y25DT62M2348S"}],"timezone":"Mars/Base"},"after":"2025-04-09T12:19:42.487503Z","next":"2025-07-14T15:38:56.000000Z"} +{"spec":{"cron_expressions":["bad cron","bad cron"],"intervals":[{"every":"P1D ","offset":"P2M3W"}]},"after":"2027-01-02T13:47:16.080317Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["@hourly"],"timezone":"UTC"},"after":"2025-03-16T19:30:27.356624Z","next":"2025-03-16T20:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"intervals":[{"every":"P14M2WT0M","offset":"P2WT12H"}]},"after":"2028-05-23T09:23:53.091973Z","next":"2028-05-28T02:30:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"PT598S"},{"every":"PT1831S"}],"timezone":"Europe/Budapest"},"after":"2027-09-21T19:11:22.110112Z","next":"2027-09-21T19:14:06.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P0YT15H43M170S"},{"every":"P0Y4M3W11DT16M"}],"timezone":"Europe/Budapest"},"after":"2027-07-20T06:58:47.675672Z","next":"2027-07-20T19:58:20.000000Z"} +{"spec":{"cron_expressions":["bad cron","5 4 L * ?"]},"after":"2025-07-10T23:30:27.226467Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 0 1 * *"],"timezone":"EST"},"after":"2028-11-09T10:57:35.176656Z","next":"2028-11-12T07:30:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?","0 12 29 2 *"],"intervals":[{"every":"P6M"}],"timezone":"Mars/Base"},"after":"2025-03-27T03:09:00.188831Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P2YT7H68M695S"},{"every":"P35DT14H9M3784S"}],"timezone":"+05:30"},"after":"2027-06-24T21:01:28.960090Z","next":"2027-07-24T16:39:20.000000Z"} +{"spec":{"cron_expressions":["@hourly","30 2 * * SUN"],"intervals":[{"every":"PT103S"},{"every":"P9M38DT5311S"}],"timezone":"Australia/Lord_Howe"},"after":"2025-05-28T15:37:44.387552Z","next":"2025-05-28T15:38:53.000000Z"} +{"spec":{"cron_expressions":["bad cron","0 3 * * *"]},"after":"2026-03-17T14:15:19.440496Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 3 * * *"]},"after":"2026-04-12T21:59:46.884289Z","next":"2026-04-13T03:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *","bad cron"],"intervals":[{"every":"PT11M3588S"},{"every":"P20DT4H","offset":"PT4H3382S"}],"timezone":"Mars/Base"},"after":"2026-11-13T21:04:35.653360Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["*/15 * * * *"]},"after":"2026-01-04T17:21:32.077077Z","next":"2026-01-04T17:30:00.000000Z"} +{"spec":{"cron_expressions":["",""],"intervals":[{"every":"P2YT27M"},{"every":"PT1794S"}],"timezone":"America/New_York"},"after":"2027-08-29T17:43:41.476597Z","next":"2027-08-29T18:05:06.000000Z"} +{"spec":{"intervals":[{"every":"P2WT6397S"},{"every":"P2WT3548S"}]},"after":"2028-05-24T02:57:10.948932Z","next":"2028-05-24T09:03:32.000000Z"} +{"spec":{"intervals":[{"every":"PT38M"},{"every":"P1YT17H","offset":"P13MT77M6384S"}],"timezone":"UTC"},"after":"2026-06-13T19:14:32.588261Z","next":"2026-06-13T19:46:00.000000Z"} +{"spec":[],"after":"2028-09-22T15:14:26.289793Z","next":null} +{"spec":{"cron_expressions":["bad cron","0 9 * * 1-5"],"timezone":"Australia/Lord_Howe"},"after":"2027-08-31T06:25:26.494049Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P1WT43H75M"}],"timezone":"EST"},"after":"2026-11-07T07:23:59.467085Z","next":"2026-11-08T03:00:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"timezone":"UTC"},"after":"2026-03-16T23:16:31.228343Z","next":"2026-04-01T00:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT49H"},{"every":"P1M3WT3H"}],"timezone":""},"after":"2026-07-02T18:03:55.386196Z","next":"2026-07-03T04:00:00.000000Z"} +{"spec":[],"after":"2027-07-27T11:41:54.241202Z","next":null} +{"spec":{"cron_expressions":[""],"timezone":"UTC"},"after":"2026-05-02T09:32:22.118361Z","next":null} +{"spec":{"timezone":"UTC"},"after":"2028-07-09T22:40:59.549216Z","next":null} +{"spec":{"intervals":[{"every":"P0Y2MT2H35M","offset":"P0M1W"},{"every":"P0WT7H88M","offset":"PT1112S"}],"timezone":"Australia/Lord_Howe"},"after":"2025-11-22T04:50:05.367146Z","next":"2025-11-22T04:54:32.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"]},"after":"2028-12-22T04:58:51.739494Z","next":"2029-01-01T00:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT2364S","offset":"P2WT90M5716S"}],"timezone":"UTC"},"after":"2026-12-11T01:19:52.293340Z","next":"2026-12-11T01:35:40.000000Z"} +{"spec":{"timezone":"Mars/Base"},"after":"2027-09-19T22:12:45.136293Z","next":null} +{"spec":{"timezone":"America/New_York"},"after":"2026-09-18T06:39:18.367556Z","next":null} +{"spec":{"cron_expressions":["@hourly","0 0 1 * *"],"intervals":[{"every":"PT18H"},{"every":"PT1161S"}]},"after":"2026-12-27T07:44:22.248728Z","next":"2026-12-27T07:47:33.000000Z"} +{"spec":{"intervals":[{"every":"PT47M","offset":"PT282S"},{"every":"P2YT71M"}],"timezone":"Australia/Lord_Howe"},"after":"2028-08-20T21:00:07.662430Z","next":"2028-08-20T21:23:42.000000Z"} +{"spec":[],"after":"2025-04-07T06:51:13.358558Z","next":null} +{"spec":{"cron_expressions":["0 0 1 * *","0 9 * * 1-5"],"intervals":[{"every":"PT2820S"},{"every":"P0Y39DT61M3433S"}],"timezone":"America/New_York"},"after":"2028-12-25T20:54:46.442088Z","next":"2028-12-25T21:16:00.000000Z"} +{"spec":{"intervals":[{"every":"PT78M"}],"timezone":"UTC"},"after":"2025-03-07T09:55:20.908120Z","next":"2025-03-07T10:36:00.000000Z"} +{"spec":{"intervals":[{"every":"PT22M379S","offset":"P10MT3H"}],"timezone":"EST"},"after":"2026-10-27T12:14:05.048299Z","next":"2026-10-27T12:41:14.000000Z"} +{"spec":{"intervals":[{"every":"PT4H"}],"timezone":"Mars/Base"},"after":"2028-08-13T08:51:24.952073Z","next":"2028-08-13T12:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P1Y","offset":"P1Y10MT29H61M"},{"every":"P1Y13MT20S"}],"timezone":"+05:30"},"after":"2025-04-22T03:07:48.115154Z","next":"2025-10-15T06:01:00.000000Z"} +{"spec":{"cron_expressions":["","30 2 * * SUN"],"intervals":[{"every":"P1YT31M"}]},"after":"2026-01-14T07:37:34.963712Z","next":"2026-01-18T02:30:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","0 3 * * *"],"timezone":""},"after":"2026-08-19T05:26:50.430612Z","error":"DateInvalidTimeZoneException"} +{"spec":{"timezone":"EST"},"after":"2028-04-17T06:51:07.261214Z","next":null} +{"spec":{"cron_expressions":["0 9 * * 1-5","5 4 L * ?"],"intervals":[{"every":"P1Y3M"}],"timezone":"America/New_York"},"after":"2028-09-14T11:33:25.873440Z","next":"2028-09-14T13:00:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"P2Y","offset":"P0000-00-00T00:00:01"}]},"after":"2026-04-21T19:35:44.643125Z","next":"2026-05-01T00:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","30 2 * * SUN"],"intervals":[{"every":"P1Y3WT5820S"}],"timezone":"UTC"},"after":"2026-05-23T16:41:46.283178Z","next":"2026-05-24T02:30:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","@hourly"],"timezone":"EST"},"after":"2027-01-25T16:01:54.455971Z","next":"2027-01-25T17:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P10MT28H3388S","offset":"PT5978S"}]},"after":"2026-09-04T21:06:44.688771Z","next":"2026-11-26T06:35:50.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","30 2 * * SUN"],"intervals":[{"every":"p1d"}],"timezone":"Australia/Lord_Howe"},"after":"2027-02-13T07:05:04.672930Z","next":"2027-02-13T07:15:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"P1YT49H6279S"},{"every":"PT1855S"}],"timezone":"UTC"},"after":"2027-06-05T08:26:41.707111Z","next":"2027-06-05T08:30:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","bad cron"],"intervals":[{"every":"P10000000000000000000D","offset":"P1Y4DT1508S"}]},"after":"2028-01-10T11:01:47.905985Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["@hourly","@hourly"],"intervals":[{"every":"P4M1D"}],"timezone":"America/New_York"},"after":"2028-08-21T00:14:45.933969Z","next":"2028-08-21T01:00:00.000000Z"} +{"spec":{"cron_expressions":["","30 2 * * SUN"],"intervals":[{"every":"PT33H"}]},"after":"2028-09-08T00:50:57.190736Z","next":"2028-09-09T06:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT1031S"},{"every":"P0Y","offset":"P8M"}],"timezone":"America/New_York"},"after":"2026-07-09T19:08:06.719351Z","next":"2026-07-09T19:20:45.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","@hourly"],"intervals":[{"every":"PT7H"},{"every":"P2W16DT6643S"}]},"after":"2026-04-01T00:44:31.015730Z","next":"2026-04-01T01:00:00.000000Z"} +{"spec":{"intervals":[{"every":"p1d"}],"timezone":"America/New_York"},"after":"2026-11-02T01:30:32.374592Z","next":null} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"P1Y"},{"every":"PT1263S"}],"timezone":"Europe/Budapest"},"after":"2027-04-18T19:28:48.702431Z","next":"2027-04-18T19:30:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?",""],"intervals":[{"every":"P10000000000000000000D"},{"every":"P37DT26H"}],"timezone":"Mars/Base"},"after":"2026-12-05T14:34:07.107324Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["@hourly","0 9 * * 1-5"],"timezone":"+05:30"},"after":"2026-04-05T04:13:06.749465Z","next":"2026-04-05T04:30:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","bad cron"],"intervals":[{"every":"P0Y38DT36M"}]},"after":"2025-02-15T06:33:06.646846Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P10DT24M"},{"every":"P1MT9H3574S","offset":"PT1285S"}],"timezone":"+05:30"},"after":"2025-02-04T10:09:02.830622Z","next":"2025-02-04T11:36:00.000000Z"} +{"spec":{"timezone":"America/New_York"},"after":"2028-01-30T14:28:26.867003Z","next":null} +{"spec":{"intervals":[{"every":"P0Y"},{"every":"P1W6DT24H"}],"timezone":""},"after":"2027-09-16T17:34:46.701421Z","next":"2027-09-23T00:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 3 * * *"],"intervals":[{"every":"PT0S"}],"timezone":"America/New_York"},"after":"2026-12-25T17:39:21.139614Z","next":"2026-12-26T08:00:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"P13M10DT6H"},{"every":"PT9M"}],"timezone":"+05:30"},"after":"2027-04-26T15:02:21.996166Z","next":"2027-04-26T15:09:00.000000Z"} +{"spec":{"timezone":"Australia/Lord_Howe"},"after":"2025-11-22T04:44:14.866184Z","next":null} +{"spec":{"cron_expressions":["*/15 * * * *"],"timezone":"Asia/Kolkata"},"after":"2025-11-09T06:08:16.426273Z","next":"2025-11-09T06:15:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"timezone":"Mars/Base"},"after":"2025-02-07T19:00:30.824249Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["0 3 * * *","bad cron"],"timezone":"+05:30"},"after":"2028-03-17T11:11:34.043760Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["5 4 L * ?"],"intervals":[{"every":"P5M","offset":"P0YT5599S"},{"every":"P2YT23H22M"}]},"after":"2026-04-16T20:11:11.548571Z","next":"2026-04-30T04:05:00.000000Z"} +{"spec":{"cron_expressions":["","@hourly"],"timezone":"Europe/Budapest"},"after":"2027-11-21T00:48:10.798476Z","next":"2027-11-21T01:00:00.000000Z"} +{"spec":[],"after":"2026-05-19T13:25:34.926627Z","next":null} +{"spec":[],"after":"2025-07-02T13:16:49.841335Z","next":null} +{"spec":{"cron_expressions":["5 4 L * ?"],"timezone":"Australia/Lord_Howe"},"after":"2025-12-15T04:52:26.906823Z","next":"2025-12-30T17:05:00.000000Z"} +{"spec":{"intervals":[{"every":"PT12H56M"}],"timezone":"Europe/Budapest"},"after":"2027-05-16T05:24:43.238155Z","next":"2027-05-16T16:40:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","@hourly"]},"after":"2027-06-20T17:01:54.547722Z","next":"2027-06-20T18:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","5 4 L * ?"],"intervals":[{"every":"P0D"},{"every":"PT15H56M"}],"timezone":"Mars/Base"},"after":"2025-07-10T13:01:34.097668Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["bad cron"]},"after":"2025-11-22T02:28:47.208865Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P1DT"},{"every":"P16DT171S","offset":"PT5497S"}],"timezone":"UTC"},"after":"2025-07-07T13:59:54.754036Z","next":"2025-07-07T14:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P1YT6377S","offset":"P2Y3WT87M"},{"every":"P1WT41H","offset":"PT5967S"}],"timezone":"Asia/Kolkata"},"after":"2028-10-18T22:41:06.863948Z","next":"2028-10-26T12:39:27.000000Z"} +{"spec":{"timezone":"Mars/Base"},"after":"2028-11-27T01:00:50.961735Z","next":null} +{"spec":{"cron_expressions":["0 0 1 * *"]},"after":"2025-03-23T00:58:04.286253Z","next":"2025-04-01T00:00:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","0 0 1 * *"],"intervals":[{"every":"P0WT37H","offset":"P3MT1786S"},{"every":"PT64M","offset":"P1YT77M"}]},"after":"2027-11-10T21:22:53.385961Z","next":"2027-11-10T22:05:00.000000Z"} +{"spec":{"intervals":[{"every":"P1Y14M","offset":"PT539S"},{"every":"P19D","offset":"P3MT7M4385S"}],"timezone":"UTC"},"after":"2027-09-20T21:01:21.389915Z","next":"2027-09-24T01:20:05.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"PT6H33M"},{"every":"P2Y3M8D"}]},"after":"2026-09-26T07:06:40.804278Z","next":"2026-09-26T13:30:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P2YT4037S"}],"timezone":"Australia/Lord_Howe"},"after":"2027-04-13T10:21:35.098325Z","next":"2027-04-13T10:30:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"PT661S"},{"every":"PT12M"}],"timezone":"America/New_York"},"after":"2027-03-21T20:37:17.728379Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["@hourly","5 4 L * ?"],"timezone":"America/New_York"},"after":"2027-04-27T15:06:01.336689Z","next":"2027-04-27T16:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 12 29 2 *"],"timezone":"+05:30"},"after":"2025-06-25T10:22:04.314053Z","next":"2025-06-26T03:30:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"intervals":[{"every":"P3MT10H"}],"timezone":"Australia/Lord_Howe"},"after":"2025-06-20T11:37:54.014879Z","next":"2025-06-21T16:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT2926S","offset":"P14M1W"},{"every":"P10MT87M"}],"timezone":""},"after":"2026-04-19T09:05:14.306835Z","next":"2026-04-19T09:13:10.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"timezone":""},"after":"2026-12-09T20:43:35.085404Z","error":"DateInvalidTimeZoneException"} +{"spec":{"timezone":"Australia/Lord_Howe"},"after":"2027-10-01T08:37:14.968155Z","next":null} +{"spec":{"timezone":"Asia/Kolkata"},"after":"2025-05-19T15:15:09.829744Z","next":null} +{"spec":{"cron_expressions":["0 0 1 * *","0 3 * * *"],"intervals":[{"every":"P1M22DT16H"},{"every":"P2M3WT11H"}]},"after":"2027-07-25T08:39:48.059771Z","next":"2027-07-26T03:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","0 0 1 * *"],"timezone":"Europe/Budapest"},"after":"2027-01-31T16:46:15.098593Z","next":"2027-01-31T23:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","30 2 * * SUN"],"intervals":[{"every":" P1D"}],"timezone":"UTC"},"after":"2025-05-30T19:45:25.730448Z","next":"2025-05-31T00:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 0 1 * *"],"intervals":[{"every":"PT1679S"}]},"after":"2025-04-23T08:05:42.645380Z","next":"2025-04-23T08:14:15.000000Z"} +{"spec":{"intervals":[{"every":"PT50H"},{"every":"PT2506S","offset":"P3M8D"}],"timezone":"+05:30"},"after":"2027-09-06T20:52:13.573710Z","next":"2027-09-06T21:11:44.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P1Y"},{"every":"PT38M","offset":"PT20H"}],"timezone":"Australia/Lord_Howe"},"after":"2026-05-14T16:49:28.472159Z","next":"2026-05-14T16:52:00.000000Z"} +{"spec":{"cron_expressions":["","*/15 * * * *"],"timezone":"EST"},"after":"2028-06-03T10:03:41.468336Z","next":"2028-06-03T10:15:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"P19DT48H"},{"every":"PT"}],"timezone":"Asia/Kolkata"},"after":"2028-11-19T04:07:50.889215Z","next":"2028-11-30T18:30:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","5 4 L * ?"],"intervals":[{"every":"P3M3W4DT1911S"},{"every":"P1YT36H5863S","offset":"P25D"}],"timezone":"Asia/Kolkata"},"after":"2026-03-15T22:48:17.312055Z","next":"2026-03-15T23:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P1Y"}]},"after":"2027-04-24T08:42:37.232804Z","next":"2027-12-18T00:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P1Y3WT7H72M"}],"timezone":"UTC"},"after":"2026-10-11T01:05:44.863602Z","next":"2027-02-13T10:48:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","@hourly"],"timezone":""},"after":"2028-06-07T21:21:00.707932Z","error":"DateInvalidTimeZoneException"} +{"spec":{"timezone":"+05:30"},"after":"2025-03-10T12:50:32.346852Z","next":null} +{"spec":{"intervals":[{"every":"P2YT36M2062S"},{"every":"PT3206S"}]},"after":"2025-07-25T04:24:27.007658Z","next":"2025-07-25T04:51:48.000000Z"} +{"spec":{"intervals":[{"every":"P0WT6970S"},{"every":"P0YT21H5M","offset":"P36D"}],"timezone":"+05:30"},"after":"2027-10-14T18:21:28.985741Z","next":"2027-10-14T18:56:30.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P2WT71M3889S","offset":"P-1D"}]},"after":"2026-04-03T07:06:17.931867Z","next":"2026-04-03T08:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"timezone":""},"after":"2025-09-30T19:10:26.746532Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["5 4 L * ?",""],"timezone":"Australia/Lord_Howe"},"after":"2025-04-02T10:52:02.889782Z","next":"2025-04-29T17:35:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P0000-00-00T00:00:01"}],"timezone":"+05:30"},"after":"2027-02-02T18:17:55.945007Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"PT26H2352S","offset":"P1Y0W29DT85M"},{"every":"PT0S"}]},"after":"2026-04-02T14:04:52.320925Z","next":"2026-04-02T14:15:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"]},"after":"2026-07-27T10:22:12.554345Z","next":"2028-02-29T12:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P0DT48H"}]},"after":"2027-01-11T23:02:03.334959Z","next":"2027-01-12T00:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P11MT37H74M6508S","offset":"P10M"},{"every":"PT1657S"}],"timezone":"America/New_York"},"after":"2028-08-18T10:00:15.446742Z","next":"2028-08-18T10:10:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"]},"after":"2027-12-30T11:36:18.476223Z","next":"2028-01-02T02:30:00.000000Z"} +{"spec":{"intervals":[{"every":"P0Y0WT14H63M"}],"timezone":"Europe/Budapest"},"after":"2026-05-20T03:38:21.495944Z","next":"2026-05-20T10:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *","0 12 29 2 *"],"intervals":[{"every":"P2Y11MT4291S","offset":"PT784S"},{"every":"PT2567S"}]},"after":"2028-08-06T16:58:36.383785Z","next":"2028-08-06T17:15:24.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P27DT38H","offset":"P1Y5MT5515S"}]},"after":"2026-05-31T18:45:36.926881Z","next":"2026-06-05T03:31:55.000000Z"} +{"spec":{"cron_expressions":["@hourly","@hourly"],"intervals":[{"every":"P2WT2796S"}],"timezone":""},"after":"2028-02-14T06:16:57.010823Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["0 0 1 * *","0 3 * * *"],"intervals":[{"every":"P26DT2297S"}],"timezone":"Asia/Kolkata"},"after":"2026-09-02T15:28:45.643289Z","next":"2026-09-02T21:30:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","bad cron"],"intervals":[{"every":"PT3234S"},{"every":"PT1341S"}],"timezone":"UTC"},"after":"2027-03-04T04:36:31.463173Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P1YT85M4502S","offset":"P14DT0M"},{"every":"P3W"}],"timezone":"Mars/Base"},"after":"2025-08-14T23:31:04.261301Z","next":"2025-08-28T00:00:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P0W37DT6309S"},{"every":"P0Y15DT42H82M4932S"}],"timezone":"EST"},"after":"2026-11-12T01:57:28.816136Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P17DT42H2667S"}],"timezone":"EST"},"after":"2025-10-05T17:31:59.736568Z","next":"2025-10-16T05:48:15.000000Z"} +{"spec":{"intervals":[{"every":"PT0H85M","offset":"P1Y"},{"every":"P11M18DT0H2851S","offset":"P13MT1H75M4758S"}],"timezone":"America/New_York"},"after":"2027-01-11T23:57:46.247981Z","next":"2027-01-12T01:10:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT15M","offset":"PT68M4338S"},{"every":"PT3543S"}],"timezone":"EST"},"after":"2028-07-16T17:29:38.972844Z","next":"2028-07-16T17:35:18.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *",""],"timezone":""},"after":"2028-03-02T22:43:27.071404Z","error":"DateInvalidTimeZoneException"} +{"spec":{"timezone":"+05:30"},"after":"2026-05-13T08:43:50.050083Z","next":null} +{"spec":{"cron_expressions":["5 4 L * ?"]},"after":"2026-11-12T03:31:06.739811Z","next":"2026-11-30T04:05:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"intervals":[{"every":"P0YT6610S"}],"timezone":"Mars/Base"},"after":"2028-09-22T10:00:02.582702Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["*/15 * * * *","5 4 L * ?"],"timezone":"Australia/Lord_Howe"},"after":"2028-07-03T01:46:37.640319Z","next":"2028-07-03T02:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT1836S","offset":"PT2590S"},{"every":"P7M3W"}],"timezone":"UTC"},"after":"2026-07-15T22:08:44.987884Z","next":"2026-07-15T22:19:10.000000Z"} +{"spec":{"cron_expressions":["","0 9 * * 1-5"],"intervals":[{"every":"P0001-00-02T00:00:00"},{"every":"P1Y4DT1813S","offset":"P0DT4573S"}],"timezone":"America/New_York"},"after":"2026-09-30T05:32:07.867223Z","next":"2026-09-30T13:00:00.000000Z"} +{"spec":{"cron_expressions":["","*/15 * * * *"],"intervals":[{"every":"P1Y11M","offset":"PT5620S"},{"every":"P22D"}],"timezone":"Europe/Budapest"},"after":"2025-11-18T23:04:30.215113Z","next":"2025-11-18T23:15:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"PT2773S"}],"timezone":"Australia/Lord_Howe"},"after":"2025-02-26T15:14:15.785187Z","next":"2025-02-26T15:39:30.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"timezone":"UTC"},"after":"2025-09-08T04:30:23.984658Z","next":"2025-09-08T05:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 0 1 * *"],"timezone":"Europe/Budapest"},"after":"2028-12-29T10:16:56.559580Z","next":"2028-12-31T01:30:00.000000Z"} +{"spec":{"intervals":[{"every":"PT778S"},{"every":"P1Y10M"}],"timezone":"Europe/Budapest"},"after":"2028-01-06T04:46:27.602969Z","next":"2028-01-06T04:46:50.000000Z"} +{"spec":{"timezone":"Europe/Budapest"},"after":"2027-04-06T01:53:38.378676Z","next":null} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"P14M"},{"every":"P18D"}],"timezone":"America/New_York"},"after":"2025-05-03T17:44:45.343398Z","next":"2025-05-05T13:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P3M3W26DT26M4938S"},{"every":"P33DT19H6247S"}],"timezone":"Europe/Budapest"},"after":"2028-11-10T21:31:20.827259Z","next":"2028-11-15T14:54:05.000000Z"} +{"spec":{"intervals":[{"every":"P0D","offset":"P14MT9H5257S"}]},"after":"2028-04-24T06:52:01.134666Z","next":null} +{"spec":{"intervals":[{"every":"P0Y2WT6H"},{"every":"P2WT158S","offset":"P0D"}],"timezone":""},"after":"2028-11-13T03:30:28.021980Z","next":"2028-11-15T06:00:00.000000Z"} +{"spec":{"timezone":"+05:30"},"after":"2028-08-13T09:21:41.706576Z","next":null} +{"spec":{"cron_expressions":["@hourly"],"timezone":"Australia/Lord_Howe"},"after":"2028-04-06T19:02:35.208845Z","next":"2028-04-06T19:30:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"intervals":[{"every":"P1Y0WT39H7137S"}],"timezone":"Europe/Budapest"},"after":"2027-05-12T06:47:55.663694Z","next":"2027-05-16T00:30:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"P1WT54M"}],"timezone":"Mars/Base"},"after":"2025-06-28T11:41:26.754948Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P2Y3WT1672S"}]},"after":"2027-01-16T21:05:46.136744Z","next":"2027-07-29T13:00:16.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","0 3 * * *"],"timezone":"Asia/Kolkata"},"after":"2027-01-03T09:23:03.015597Z","next":"2027-01-03T21:30:00.000000Z"} +{"spec":{"intervals":[{"every":"P6MT1376S","offset":"PT1005S"}],"timezone":"UTC"},"after":"2026-06-14T13:45:53.097101Z","next":"2026-09-05T20:14:05.000000Z"} +{"spec":{"cron_expressions":["@hourly","30 2 * * SUN"],"intervals":[{"every":"PT3184S"}],"timezone":"America/New_York"},"after":"2028-12-26T07:02:28.175818Z","next":"2028-12-26T07:54:40.000000Z"} +{"spec":{"timezone":"UTC"},"after":"2026-06-06T02:59:20.729849Z","next":null} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"PT2575S","offset":"P0WT25H255S"}]},"after":"2026-02-26T21:17:23.619421Z","next":"2026-02-26T21:21:20.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?","0 12 29 2 *"],"intervals":[{"every":"P10MT29H2643S"},{"every":"P2Y2WT52M5892S"}],"timezone":""},"after":"2026-06-23T22:07:15.509825Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["30 2 * * SUN","30 2 * * SUN"],"intervals":[{"every":"PT1.5S"}]},"after":"2027-12-11T18:11:11.447463Z","next":"2027-12-12T02:30:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","bad cron"],"timezone":"America/New_York"},"after":"2026-08-18T11:02:42.000211Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["","0 3 * * *"]},"after":"2028-03-05T14:53:40.915003Z","next":"2028-03-06T03:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P7M2DT2760S","offset":"P2W"},{"every":"P40DT23H64M"}],"timezone":"+05:30"},"after":"2027-05-13T17:18:18.483080Z","next":"2027-05-14T10:04:00.000000Z"} +{"spec":{"cron_expressions":["","0 0 1 * *"],"intervals":[{"every":"P5M"},{"every":"PT3360S","offset":"PT"}]},"after":"2028-08-14T16:48:34.199692Z","next":"2028-08-14T16:56:00.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"PT7063S"}]},"after":"2026-06-08T13:24:51.398549Z","next":"2026-06-08T14:19:47.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","bad cron"],"intervals":[{"every":"P27DT46H79M3381S"},{"every":"P0YT12M"}],"timezone":"Europe/Budapest"},"after":"2027-05-20T04:33:25.787358Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["5 4 L * ?","*/15 * * * *"],"intervals":[{"every":"P0W25DT25M1382S","offset":"P1YT61M"}],"timezone":"EST"},"after":"2026-09-06T22:24:40.086473Z","next":"2026-09-06T22:30:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","30 2 * * SUN"],"intervals":[{"every":"PT20M"}],"timezone":"+05:30"},"after":"2025-02-07T12:54:37.780454Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["bad cron","0 12 29 2 *"],"intervals":[{"every":"P-1D"}],"timezone":"America/New_York"},"after":"2028-03-12T18:55:30.626285Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 3 * * *","5 4 L * ?"]},"after":"2025-09-10T03:37:15.157467Z","next":"2025-09-11T03:00:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?"],"timezone":"Australia/Lord_Howe"},"after":"2028-02-10T04:00:32.749257Z","next":"2028-02-28T17:05:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","0 0 1 * *"],"intervals":[{"every":"P2YT37H","offset":"P0Y"}],"timezone":"Europe/Budapest"},"after":"2026-02-20T03:50:17.946148Z","next":"2026-02-20T04:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P0001-00-02T00:00:00"},{"every":"P0YT4515S","offset":"P9M3WT86M1621S"}]},"after":"2026-02-24T13:29:29.615777Z","next":"2026-02-24T14:39:46.000000Z"} +{"spec":{"timezone":"Asia/Kolkata"},"after":"2028-02-12T19:40:41.573202Z","next":null} +{"spec":{"intervals":[{"every":"P4M13DT1134S"}]},"after":"2026-07-06T06:12:54.486276Z","next":"2026-10-24T01:08:24.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 3 * * *"],"intervals":[{"every":"P1WT4H82M4361S","offset":"PT1H0M"}],"timezone":""},"after":"2025-04-19T07:30:53.387137Z","error":"DateInvalidTimeZoneException"} +{"spec":[],"after":"2027-06-08T11:08:26.880261Z","next":null} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT2017S","offset":"P29DT13M"},{"every":"P35D"}],"timezone":"+05:30"},"after":"2027-03-10T12:07:23.696530Z","next":"2027-03-10T12:29:21.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","5 4 L * ?"],"intervals":[{"every":"P1H","offset":"P2Y1WT1762S"}],"timezone":"EST"},"after":"2025-11-23T19:13:50.968843Z","next":"2025-11-30T09:05:00.000000Z"} +{"spec":{"intervals":[{"every":"P0WT11H2M"}]},"after":"2028-12-02T09:51:05.986142Z","next":"2028-12-02T12:24:00.000000Z"} +{"spec":{"intervals":[{"every":"P3WT27H"},{"every":"P11MT31H","offset":"P1YT1015S"}],"timezone":"Europe/Budapest"},"after":"2027-02-19T20:28:44.692764Z","next":"2027-03-09T00:00:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","0 12 29 2 *"],"intervals":[{"every":"P26D","offset":"P3WT64M4072S"},{"every":"PT83M"}],"timezone":""},"after":"2026-12-23T16:27:56.727124Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P12M0W32DT43H5978S","offset":"PT6M"},{"every":"P1Y4M8DT35H24M"}]},"after":"2026-10-11T23:16:34.702546Z","next":"2026-11-10T22:48:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *","0 0 1 * *"]},"after":"2025-11-26T12:57:50.592965Z","next":"2025-12-01T00:00:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P17DT33H47M"}],"timezone":"Asia/Kolkata"},"after":"2027-02-28T08:02:01.193376Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P1Y14M1W","offset":"PT4254S"},{"every":"PT60M6499S","offset":"PT3335S"}],"timezone":""},"after":"2027-12-06T10:24:22.555205Z","next":"2027-12-06T11:53:38.000000Z"} +{"spec":{"timezone":"+05:30"},"after":"2027-01-31T00:27:59.659286Z","next":null} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"PT2230S"},{"every":"P2Y13M","offset":"P12D"}],"timezone":"America/New_York"},"after":"2025-04-18T07:44:07.900674Z","next":"2025-04-18T08:10:50.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"timezone":"EST"},"after":"2027-08-15T03:46:49.865937Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P24DT2361S","offset":"P1W"}],"timezone":"Europe/Budapest"},"after":"2026-12-02T10:40:18.138782Z","next":"2026-12-03T15:17:45.000000Z"} +{"spec":{"cron_expressions":["bad cron",""],"intervals":[{"every":"P25DT10H36M"}],"timezone":"+05:30"},"after":"2025-05-25T17:38:44.058260Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["@hourly","0 3 * * *"],"intervals":[{"every":"P1Y","offset":"PT21H"},{"every":"PT12H"}],"timezone":"UTC"},"after":"2026-09-29T21:36:21.772996Z","next":"2026-09-29T22:00:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *",""],"timezone":"+05:30"},"after":"2025-03-14T12:15:10.893408Z","next":"2025-03-31T18:30:00.000000Z"} +{"spec":{"timezone":"EST"},"after":"2026-10-30T11:31:38.151792Z","next":null} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P"},{"every":"PT1H1D"}]},"after":"2026-06-20T02:23:02.676005Z","next":"2028-02-29T12:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *",""],"intervals":[{"every":"P2Y6MT65M"}],"timezone":"UTC"},"after":"2027-01-05T19:13:26.746558Z","next":"2027-04-23T00:55:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"]},"after":"2028-05-12T16:42:40.852450Z","next":"2028-05-12T16:45:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"timezone":"America/New_York"},"after":"2026-04-12T17:13:45.002188Z","next":"2026-04-12T18:00:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","0 9 * * 1-5"],"timezone":"UTC"},"after":"2026-06-22T15:01:01.549774Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["*/15 * * * *","@hourly"],"timezone":"Asia/Kolkata"},"after":"2026-10-24T17:49:42.988910Z","next":"2026-10-24T18:00:00.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P1Y14MT2564S"}]},"after":"2025-07-08T02:54:42.438598Z","next":"2025-11-18T18:31:04.000000Z"} +{"spec":{"cron_expressions":["","0 9 * * 1-5"],"intervals":[{"every":"P10DT68M2844S","offset":"P2W"},{"every":"P0M3WT31H26M"}],"timezone":"Mars/Base"},"after":"2028-04-24T01:53:46.502007Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["5 4 L * ?","bad cron"],"intervals":[{"every":"PT15H"}],"timezone":"Australia/Lord_Howe"},"after":"2028-06-06T17:15:28.914994Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P2D1W"},{"every":"PT39M","offset":"P0YT15H75M"}],"timezone":""},"after":"2026-08-01T01:49:18.040844Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["bad cron","bad cron"],"intervals":[{"every":"P2Y13M0D"}],"timezone":"EST"},"after":"2025-06-02T08:18:51.515593Z","error":"InvalidArgumentException"} +{"spec":{"timezone":"Europe/Budapest"},"after":"2028-10-03T19:28:42.711159Z","next":null} +{"spec":{"cron_expressions":["0 12 29 2 *",""],"timezone":"Asia/Kolkata"},"after":"2028-05-04T05:43:36.605712Z","next":"2032-02-29T06:30:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","bad cron"],"intervals":[{"every":"PT82M5189S"},{"every":"PT30H"}]},"after":"2028-10-22T09:13:21.689430Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P0M3D"},{"every":"P12D"}],"timezone":"Mars/Base"},"after":"2028-10-01T17:17:24.931279Z","next":"2028-10-02T00:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","5 4 L * ?"]},"after":"2025-09-22T17:56:23.343505Z","next":"2025-09-28T02:30:00.000000Z"} +{"spec":{"intervals":[{"every":"P0YT26H"}],"timezone":""},"after":"2028-06-05T20:53:27.828648Z","next":"2028-06-06T16:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","0 0 1 * *"],"intervals":[{"every":"PT56M","offset":"P2Y7M14DT19H2813S"},{"every":"P2M2W24D","offset":"PT1942S"}]},"after":"2028-02-07T08:32:59.196619Z","next":"2028-02-07T09:00:00.000000Z"} +{"spec":[],"after":"2026-02-05T22:37:46.075230Z","next":null} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"P17D"},{"every":"PT15H"}]},"after":"2027-08-09T00:07:53.007469Z","next":"2027-08-09T09:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"timezone":""},"after":"2026-05-21T20:23:28.044173Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT76M","offset":"P1Y5DT0H"}],"timezone":"Australia/Lord_Howe"},"after":"2025-02-15T16:22:50.709285Z","next":"2025-02-15T16:44:00.000000Z"} +{"spec":[],"after":"2028-06-03T13:07:12.296777Z","next":null} +{"spec":{"intervals":[{"every":"P2Y0WT17H30M4320S","offset":"P4M0WT19H74M5148S"},{"every":"PT2985S"}]},"after":"2028-06-18T02:03:03.456788Z","next":"2028-06-18T02:45:00.000000Z"} +{"spec":{"timezone":"UTC"},"after":"2026-11-28T09:13:17.290511Z","next":null} +{"spec":[],"after":"2025-01-11T23:30:24.551344Z","next":null} +{"spec":{"cron_expressions":["0 3 * * *","0 12 29 2 *"],"intervals":[{"every":"P0YT75M","offset":"PT110S"}],"timezone":"America/New_York"},"after":"2027-12-18T22:20:00.794731Z","next":"2027-12-18T22:31:50.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 12 29 2 *"],"intervals":[{"every":"P0YT49H1983S","offset":"PT1308S"}],"timezone":"Mars/Base"},"after":"2027-03-11T14:16:51.934207Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P0M2W"},{"every":"P3W"}],"timezone":"UTC"},"after":"2025-05-24T14:20:12.350841Z","next":"2025-06-05T00:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"]},"after":"2027-02-09T14:55:34.314491Z","next":"2027-02-10T09:00:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P1YT38H5590S","offset":"P0Y0M2WT48H61M6837S"},{"every":"P39DT6564S"}],"timezone":"Europe/Budapest"},"after":"2027-03-16T19:51:12.457792Z","error":"InvalidArgumentException"} +{"spec":{"timezone":"America/New_York"},"after":"2028-01-12T21:58:45.089881Z","next":null} +{"spec":{"cron_expressions":["0 0 1 * *"],"timezone":"+05:30"},"after":"2026-02-26T19:34:34.752186Z","next":"2026-02-28T18:30:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"P0WT55M","offset":"p1d"}]},"after":"2028-04-03T09:24:55.424400Z","next":"2028-04-03T09:35:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","@hourly"],"intervals":[{"every":"P8M10DT1659S"}],"timezone":"Europe/Budapest"},"after":"2028-07-23T21:31:58.580462Z","next":"2028-07-23T22:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P14M31D"},{"every":"PT6161S","offset":"P4M"}],"timezone":"Mars/Base"},"after":"2028-07-16T18:20:04.017594Z","next":"2028-07-16T19:06:49.000000Z"} +{"spec":{"intervals":[{"every":"P0W38DT42H"}]},"after":"2027-08-08T08:37:58.719755Z","next":"2027-09-06T12:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT4890S"}],"timezone":"EST"},"after":"2026-09-19T21:34:57.528178Z","next":"2026-09-19T22:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P1Y6M27DT26H6878S"},{"every":"PT1917S"}],"timezone":"Mars/Base"},"after":"2025-02-11T23:04:08.836547Z","next":"2025-02-11T23:06:27.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P10DT3583S"}]},"after":"2027-02-27T15:55:16.816079Z","next":"2027-03-09T06:10:40.000000Z"} +{"spec":{"intervals":[{"every":"P13MT5H68M","offset":"P37DT10H84M"}],"timezone":"Mars/Base"},"after":"2028-09-27T01:39:35.009516Z","next":"2028-11-13T12:44:00.000000Z"} +{"spec":{"intervals":[{"every":"PT2260S"},{"every":"P2WT37H4040S"}],"timezone":"EST"},"after":"2025-03-28T00:57:04.956252Z","next":"2025-03-28T01:14:00.000000Z"} +{"spec":[],"after":"2026-02-14T12:12:29.042953Z","next":null} +{"spec":{"intervals":[{"every":"P2Y9M3WT68M","offset":"P2Y0WT34H"}]},"after":"2028-08-28T21:49:45.649810Z","next":"2030-09-16T09:48:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"P","offset":"P0Y20DT28M"}],"timezone":"Mars/Base"},"after":"2027-07-14T09:25:37.801341Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P0Y1W28DT1902S"},{"every":"PT25M"}],"timezone":"UTC"},"after":"2027-02-03T12:12:10.354387Z","next":"2027-02-03T12:25:00.000000Z"} +{"spec":{"intervals":[{"every":"P2YT2H"}]},"after":"2025-07-22T05:55:58.202946Z","next":"2025-12-20T08:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT4M","offset":"P14M21DT43H16M"}]},"after":"2025-10-26T02:49:29.367377Z","next":"2025-10-26T02:52:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P3W26DT27H58M4430S"},{"every":"PT48M"}],"timezone":"America/New_York"},"after":"2026-01-24T17:03:38.342263Z","next":"2026-01-24T17:36:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT2318S","offset":"P7M32D"},{"every":"PT1.5S","offset":"P0Y1W10DT2779S"}],"timezone":"Mars/Base"},"after":"2025-04-15T23:54:54.775905Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["@hourly","30 2 * * SUN"],"intervals":[{"every":"P","offset":"P2Y1WT46H"},{"every":"PT90M","offset":"P7M0WT90M"}],"timezone":"Asia/Kolkata"},"after":"2025-07-07T09:21:30.832055Z","next":"2025-07-07T09:30:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"PT35H","offset":"P3W"},{"every":"P3M","offset":"P0W"}],"timezone":""},"after":"2025-08-05T13:39:47.480038Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P2Y"}],"timezone":"Australia/Lord_Howe"},"after":"2027-01-19T17:26:57.856138Z","next":"2027-01-20T16:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P1MT25H"},{"every":"P0WT3252S"}],"timezone":"UTC"},"after":"2027-01-15T01:57:28.837817Z","next":"2027-01-15T02:05:48.000000Z"} +{"spec":{"cron_expressions":["@hourly","0 12 29 2 *"],"intervals":[{"every":"P4MT2283S","offset":"P2YT8H32M"}],"timezone":"America/New_York"},"after":"2028-01-30T06:33:10.210035Z","next":"2028-01-30T07:00:00.000000Z"} +{"spec":{"timezone":""},"after":"2028-04-05T20:15:02.057570Z","next":null} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"P3W","offset":"P24DT17H6657S"}],"timezone":"Australia/Lord_Howe"},"after":"2028-09-04T09:04:05.323208Z","next":"2028-09-04T22:30:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?"],"timezone":""},"after":"2028-05-18T20:36:07.157216Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["bad cron","0 3 * * *"],"intervals":[{"every":"PT23H"},{"every":"PT19H21M","offset":"P0YT6735S"}]},"after":"2026-08-22T21:38:46.402801Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 3 * * *"],"intervals":[{"every":"P0W","offset":"P9MT87M"}],"timezone":"UTC"},"after":"2025-12-30T19:59:28.748808Z","next":"2025-12-31T03:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","0 9 * * 1-5"],"intervals":[{"every":"P0W14DT32M"}],"timezone":"Europe/Budapest"},"after":"2026-07-21T16:47:19.415310Z","next":"2026-07-22T01:00:00.000000Z"} +{"spec":{"cron_expressions":["","0 9 * * 1-5"],"intervals":[{"every":"P01D"}],"timezone":"EST"},"after":"2025-08-23T18:42:19.820471Z","next":"2025-08-24T00:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT290S"}],"timezone":""},"after":"2028-02-01T17:24:09.518820Z","next":"2028-02-01T17:24:30.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"timezone":"Asia/Kolkata"},"after":"2025-09-18T22:19:52.766568Z","next":"2025-09-30T18:30:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *",""],"intervals":[{"every":"P1DT","offset":"P21D"}],"timezone":"+05:30"},"after":"2025-06-19T02:11:28.883773Z","next":"2025-06-30T18:30:00.000000Z"} +{"spec":{"timezone":"Europe/Budapest"},"after":"2025-05-04T20:18:25.525518Z","next":null} +{"spec":{"cron_expressions":["30 2 * * SUN"],"intervals":[{"every":"P2W37DT73M"},{"every":"PT280S"}]},"after":"2028-12-30T22:37:18.978187Z","next":"2028-12-30T22:37:20.000000Z"} +{"spec":{"intervals":[{"every":"P7M"},{"every":"P7MT1H78M","offset":"PT60M"}],"timezone":"Australia/Lord_Howe"},"after":"2025-06-25T10:58:24.572656Z","next":"2025-10-09T00:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","*/15 * * * *"],"intervals":[{"every":"PT710S"}]},"after":"2026-10-27T21:23:11.704232Z","next":"2026-10-27T21:24:00.000000Z"} +{"spec":{"timezone":"Asia/Kolkata"},"after":"2025-12-19T15:46:08.887758Z","next":null} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P4M","offset":"PT49H3849S"}],"timezone":"UTC"},"after":"2026-08-05T09:36:56.132476Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"PT681S","offset":"PT32H218S"},{"every":"P0M0W"}],"timezone":"Europe/Budapest"},"after":"2025-06-13T15:21:48.473463Z","next":"2025-06-13T15:27:47.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?","0 12 29 2 *"],"timezone":"Asia/Kolkata"},"after":"2028-12-03T19:27:24.708727Z","next":"2028-12-30T22:35:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"PT13H"},{"every":" P1D"}],"timezone":"America/New_York"},"after":"2025-08-21T14:23:33.418404Z","next":"2025-08-21T17:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT1H1D"}],"timezone":"Australia/Lord_Howe"},"after":"2026-06-03T10:27:06.424615Z","next":null} +{"spec":{"timezone":"America/New_York"},"after":"2028-09-06T22:50:35.297934Z","next":null} +{"spec":{"intervals":[{"every":"P20DT42H64M367S","offset":"P37DT177S"},{"every":"PT16M6580S"}],"timezone":"America/New_York"},"after":"2026-04-06T19:14:26.598740Z","next":"2026-04-06T19:35:20.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","0 12 29 2 *"],"intervals":[{"every":"P32D","offset":"P1D "},{"every":"P0Y26DT7M"}],"timezone":""},"after":"2026-07-15T22:21:09.814577Z","error":"DateInvalidTimeZoneException"} +{"spec":[],"after":"2027-06-27T18:49:22.862856Z","next":null} +{"spec":{"cron_expressions":["30 2 * * SUN","*/15 * * * *"],"timezone":"Australia/Lord_Howe"},"after":"2025-11-30T14:58:32.313439Z","next":"2025-11-30T15:00:00.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P1YT5918S"},{"every":"PT38H33M","offset":"P34D"}],"timezone":"+05:30"},"after":"2028-04-11T08:44:33.544721Z","next":"2028-04-12T07:03:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","*/15 * * * *"],"intervals":[{"every":"PT3795S"},{"every":"P0W33DT41H","offset":"PT473S"}],"timezone":"EST"},"after":"2027-02-28T21:52:28.665375Z","next":"2027-02-28T22:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *","0 0 1 * *"],"intervals":[{"every":"P6D","offset":"PT1702S"},{"every":"PT9H"}],"timezone":"Mars/Base"},"after":"2027-05-29T13:03:58.913042Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P39DT858S"}]},"after":"2028-11-07T02:09:20.460706Z","next":"2028-12-15T11:33:36.000000Z"} +{"spec":{"timezone":"UTC"},"after":"2028-04-30T05:30:51.590818Z","next":null} +{"spec":{"intervals":[{"every":"P10000000000000000000D","offset":"P32DT30H5494S"}],"timezone":"+05:30"},"after":"2025-08-27T10:46:27.602244Z","next":null} +{"spec":{"timezone":"+05:30"},"after":"2027-05-13T07:09:58.083532Z","next":null} +{"spec":{"cron_expressions":[""],"timezone":"UTC"},"after":"2025-11-04T22:27:47.081161Z","next":null} +{"spec":{"cron_expressions":["5 4 L * ?"],"timezone":"Australia/Lord_Howe"},"after":"2028-03-21T00:06:50.016769Z","next":"2028-03-30T17:05:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"intervals":[{"every":"P1WT1585S","offset":"P14MT35H6058S"},{"every":"P3WT87M"}],"timezone":"Europe/Budapest"},"after":"2025-12-18T00:23:47.684916Z","next":"2025-12-21T01:30:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"PT4179S"},{"every":"P0Y"}],"timezone":"UTC"},"after":"2026-01-26T15:36:49.126614Z","next":"2026-01-26T15:55:27.000000Z"} +{"spec":{"timezone":"Mars/Base"},"after":"2025-09-16T08:19:19.226970Z","next":null} +{"spec":{"cron_expressions":["0 0 1 * *","0 9 * * 1-5"],"intervals":[{"every":"P0Y","offset":"P2Y"},{"every":"PT61M4517S","offset":"P2WT6816S"}],"timezone":"Europe/Budapest"},"after":"2026-05-29T12:30:50.939478Z","next":"2026-05-29T14:38:47.000000Z"} +{"spec":{"cron_expressions":["bad cron","bad cron"],"intervals":[{"every":"P39DT6H384S"},{"every":"P20DT27H"}],"timezone":"Mars/Base"},"after":"2025-03-07T00:30:21.003180Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["5 4 L * ?","0 3 * * *"],"timezone":"UTC"},"after":"2026-08-22T00:55:33.193573Z","next":"2026-08-22T03:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *","0 9 * * 1-5"],"timezone":"Asia/Kolkata"},"after":"2025-08-16T10:36:51.323995Z","next":"2025-08-18T03:30:00.000000Z"} +{"spec":{"intervals":[{"every":"P7M","offset":"P1D "},{"every":"P1M1WT1902S"}],"timezone":"America/New_York"},"after":"2027-04-19T10:36:54.086094Z","next":"2027-05-16T11:02:12.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?"],"timezone":"EST"},"after":"2028-01-15T02:44:37.614243Z","next":"2028-01-31T09:05:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"timezone":"Europe/Budapest"},"after":"2026-07-02T09:38:41.561526Z","next":"2026-07-05T00:30:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P9MT507S"}],"timezone":""},"after":"2025-05-07T07:12:52.164454Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["5 4 L * ?"],"intervals":[{"every":"PT47H5235S"}],"timezone":"EST"},"after":"2025-04-25T00:11:32.296346Z","next":"2025-04-25T08:50:45.000000Z"} +{"spec":{"cron_expressions":["","@hourly"],"intervals":[{"every":"P0001-00-02T00:00:00"}]},"after":"2027-03-01T16:20:10.099185Z","next":"2027-03-01T17:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","0 9 * * 1-5"],"intervals":[{"every":"P1Y"},{"every":"PT15H3056S","offset":"P"}]},"after":"2028-06-05T07:20:19.977361Z","next":"2028-06-05T09:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"timezone":"EST"},"after":"2025-01-22T22:53:16.394003Z","next":"2025-01-22T23:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"timezone":"Asia/Kolkata"},"after":"2026-12-22T02:45:03.120749Z","next":"2028-02-29T06:30:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"timezone":"UTC"},"after":"2027-07-31T06:34:00.577717Z","next":"2027-07-31T07:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P0Y31DT2H"}],"timezone":"Australia/Lord_Howe"},"after":"2025-03-10T15:57:53.143767Z","next":"2025-03-26T02:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *",""],"intervals":[{"every":"PT21H","offset":"PT36H52M"}]},"after":"2028-05-14T03:55:18.487731Z","next":"2028-05-14T06:52:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","0 3 * * *"],"intervals":[{"every":"P2YT49H"}],"timezone":"Asia/Kolkata"},"after":"2026-09-15T23:10:32.398680Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["5 4 L * ?","0 3 * * *"],"intervals":[{"every":"PT58M6403S"},{"every":"PT0001H","offset":"P8D"}],"timezone":"UTC"},"after":"2026-09-01T04:32:20.268467Z","next":"2026-09-01T05:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P2MT23H53M"},{"every":"P0WT52M"}],"timezone":"Asia/Kolkata"},"after":"2028-05-18T14:59:37.518692Z","next":"2028-05-18T15:16:00.000000Z"} +{"spec":{"timezone":"Europe/Budapest"},"after":"2025-01-25T10:14:28.816783Z","next":null} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 12 29 2 *"],"intervals":[{"every":" P1D"},{"every":"P2YT1213S"}]},"after":"2025-08-27T08:05:10.744539Z","next":"2025-08-27T09:00:00.000000Z"} +{"spec":{"timezone":"Australia/Lord_Howe"},"after":"2026-03-24T00:52:36.997737Z","next":null} +{"spec":{"intervals":[{"every":"PT1H1D"}]},"after":"2025-09-28T09:34:24.019639Z","next":null} +{"spec":{"cron_expressions":["5 4 L * ?"],"intervals":[{"every":"PT3180S","offset":"PT3095S"},{"every":"PT36M5891S"}],"timezone":"Europe/Budapest"},"after":"2028-07-01T04:16:33.927473Z","next":"2028-07-01T04:19:03.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P0Y"}]},"after":"2028-01-18T13:50:58.920347Z","next":null} +{"spec":{"intervals":[{"every":"P10M"}],"timezone":"EST"},"after":"2025-01-18T19:05:57.867674Z","next":"2025-11-08T00:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P21DT46H"}],"timezone":"Mars/Base"},"after":"2028-03-08T12:21:03.412222Z","next":"2028-03-23T16:00:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"PT1360S"},{"every":"P1Y11M2WT11H"}],"timezone":"America/New_York"},"after":"2026-04-17T13:18:27.891305Z","next":"2026-04-17T13:20:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"]},"after":"2027-06-16T01:40:37.235997Z","next":"2027-06-20T02:30:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","0 0 1 * *"],"intervals":[{"every":"P1W"},{"every":"P1WT50H84M"}],"timezone":"Europe/Budapest"},"after":"2026-08-05T21:06:29.049442Z","next":"2026-08-05T22:00:00.000000Z"} +{"spec":{"cron_expressions":["","@hourly"],"intervals":[{"every":"P7M1WT18H65M5161S"}]},"after":"2026-07-04T07:04:23.124652Z","next":"2026-07-04T08:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","0 0 1 * *"],"timezone":"Asia/Kolkata"},"after":"2026-03-27T01:44:50.751481Z","next":"2026-03-27T21:30:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","bad cron"],"intervals":[{"every":"P14MT1234S"}]},"after":"2028-05-20T05:56:53.160699Z","error":"InvalidArgumentException"} +{"spec":{"timezone":"+05:30"},"after":"2025-05-19T14:42:07.228540Z","next":null} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P9M35DT37H1054S"}],"timezone":"America/New_York"},"after":"2026-11-30T21:25:50.844234Z","next":"2026-12-01T08:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 9 * * 1-5"],"timezone":"+05:30"},"after":"2027-10-13T15:49:56.846238Z","next":"2027-10-14T03:30:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 9 * * 1-5"],"intervals":[{"every":"P1W11DT41H51M"},{"every":"P2MT56M","offset":"P0001-00-02T00:00:00"}]},"after":"2025-12-19T06:03:33.787931Z","next":"2025-12-19T09:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","bad cron"],"timezone":"UTC"},"after":"2026-03-11T15:18:05.561693Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["bad cron"],"timezone":"Asia/Kolkata"},"after":"2027-02-19T16:41:34.793050Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"intervals":[{"every":"P2M34DT21M","offset":"PT0H2068S"},{"every":"P1YT16H"}],"timezone":"Mars/Base"},"after":"2027-02-10T17:28:21.760356Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P2D1W","offset":"PT5H38M"}]},"after":"2027-09-06T10:44:19.577934Z","next":null} +{"spec":{"cron_expressions":["@hourly",""],"intervals":[{"every":"P0WT6H4375S"}]},"after":"2025-02-19T11:37:15.995304Z","next":"2025-02-19T12:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"PT21H"},{"every":"PT48H"}],"timezone":"Asia/Kolkata"},"after":"2026-05-15T09:24:35.154539Z","next":"2026-05-15T18:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P6MT23H76M"},{"every":"PT2327S"}],"timezone":"Mars/Base"},"after":"2028-06-22T02:52:12.417648Z","next":"2028-06-22T02:52:32.000000Z"} +{"spec":{"timezone":"Mars/Base"},"after":"2025-03-24T15:00:12.366398Z","next":null} +{"spec":{"intervals":[{"every":"PT352S"}]},"after":"2027-08-07T09:12:13.590173Z","next":"2027-08-07T09:15:44.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"P1W2D"},{"every":"P0Y18DT16H59M"}],"timezone":"Mars/Base"},"after":"2027-06-09T12:56:16.785554Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["bad cron"],"timezone":"Europe/Budapest"},"after":"2027-01-31T13:26:12.757105Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P10000000000000000000D"},{"every":"P1Y","offset":"PT42H4414S"}],"timezone":""},"after":"2025-05-19T06:15:19.873782Z","next":"2025-12-19T19:13:34.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?","0 0 1 * *"],"intervals":[{"every":"PT80M3878S","offset":" P1D"}]},"after":"2025-11-08T09:35:26.065396Z","next":"2025-11-08T10:14:38.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"PT2957S"},{"every":"P7M19DT1127S"}],"timezone":"Asia/Kolkata"},"after":"2025-04-16T01:27:54.634186Z","next":"2025-04-16T02:02:59.000000Z"} +{"spec":{"intervals":[{"every":"P32DT12H52M"}],"timezone":"+05:30"},"after":"2025-02-06T23:25:25.115886Z","next":"2025-02-20T20:28:00.000000Z"} +{"spec":{"timezone":"+05:30"},"after":"2028-06-29T16:22:17.749149Z","next":null} +{"spec":{"intervals":[{"every":"P0YT47M"}],"timezone":"Mars/Base"},"after":"2026-12-03T02:58:56.016708Z","next":"2026-12-03T03:45:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"timezone":"Europe/Budapest"},"after":"2028-12-21T02:58:32.751986Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P1WT24H89M","offset":"PT1.5S"},{"every":"P10M11DT49H3395S","offset":"P3WT446S"}]},"after":"2026-12-11T11:27:37.257261Z","next":"2026-12-12T11:00:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","@hourly"],"intervals":[{"every":"PT33M5297S","offset":"PT59M"},{"every":"PT52M"}],"timezone":"Australia/Lord_Howe"},"after":"2027-03-10T12:10:30.418249Z","next":"2027-03-10T12:15:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"timezone":"Asia/Kolkata"},"after":"2027-03-25T08:23:50.295558Z","next":"2027-03-26T03:30:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","0 9 * * 1-5"]},"after":"2028-08-20T23:08:35.468998Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P0W5D"}]},"after":"2025-10-04T03:14:25.526172Z","next":"2025-10-09T00:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT3007S","offset":"PT44H"},{"every":"P27DT49H"}],"timezone":"America/New_York"},"after":"2027-02-12T21:06:31.528839Z","next":"2027-02-12T21:16:37.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P3M4DT2855S"}],"timezone":"Europe/Budapest"},"after":"2028-11-10T23:45:32.730300Z","next":"2028-12-15T13:36:35.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","*/15 * * * *"],"intervals":[{"every":"P3WT24H6608S"},{"every":"PT8M","offset":"P14MT17M"}],"timezone":"UTC"},"after":"2026-06-19T09:44:32.385273Z","next":"2026-06-19T09:45:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"]},"after":"2028-07-09T19:45:23.909735Z","next":"2028-08-01T00:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 12 29 2 *"],"intervals":[{"every":"P6MT73M"}],"timezone":"EST"},"after":"2026-12-29T11:06:06.183637Z","next":"2027-01-03T07:30:00.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P11M1W","offset":"P0YT6619S"}],"timezone":"+05:30"},"after":"2028-12-08T00:46:53.242429Z","next":"2029-01-19T01:50:19.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","0 0 1 * *"],"timezone":"Australia/Lord_Howe"},"after":"2025-12-23T23:00:08.534955Z","next":"2025-12-31T13:00:00.000000Z"} +{"spec":{"timezone":"UTC"},"after":"2027-07-07T02:04:39.748455Z","next":null} +{"spec":{"cron_expressions":["@hourly","bad cron"],"intervals":[{"every":"P1Y3M"},{"every":"PT11M"}]},"after":"2025-08-13T18:28:10.001052Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"PT15M","offset":"P13M2W40DT5614S"},{"every":"P1W"}]},"after":"2027-06-20T09:36:27.964154Z","next":"2027-06-20T09:48:34.000000Z"} +{"spec":{"timezone":"America/New_York"},"after":"2026-05-27T13:27:37.280399Z","next":null} +{"spec":{"intervals":[{"every":"PT841S"},{"every":"P2Y1W2DT16H"}],"timezone":"America/New_York"},"after":"2026-08-11T08:11:47.676166Z","next":"2026-08-11T08:17:01.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P9M24DT36H31M","offset":"PT2911S"}]},"after":"2026-11-11T04:32:03.768302Z","next":"2026-11-11T05:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","0 0 1 * *"],"intervals":[{"every":"P1W"}],"timezone":"+05:30"},"after":"2026-12-10T04:10:14.107477Z","next":"2026-12-10T04:30:00.000000Z"} +{"spec":{"timezone":"America/New_York"},"after":"2025-01-20T12:38:15.731407Z","next":null} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P2WT240S","offset":"PT0H3359S"},{"every":"P1YT12M4718S","offset":"PT1799S"}],"timezone":"America/New_York"},"after":"2025-09-03T13:12:27.729164Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"timezone":"America/New_York"},"after":"2028-12-11T20:58:08.791187Z","next":"2028-12-17T07:30:00.000000Z"} +{"spec":{"timezone":"Mars/Base"},"after":"2028-08-23T11:10:48.787613Z","next":null} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P14M"},{"every":"PT887S"}],"timezone":"Australia/Lord_Howe"},"after":"2028-11-03T07:03:39.212632Z","next":"2028-11-03T07:14:21.000000Z"} +{"spec":{"intervals":[{"every":"P2YT87M1244S"}],"timezone":"America/New_York"},"after":"2026-05-07T14:24:59.522654Z","next":"2027-12-20T04:04:16.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"PT1.5S"}],"timezone":"Europe/Budapest"},"after":"2027-11-04T16:15:30.391066Z","next":"2027-11-05T02:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 0 1 * *"],"intervals":[{"every":"P2D1W","offset":"P7D"}]},"after":"2027-09-24T14:49:07.429099Z","next":"2027-09-26T02:30:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"PT13H79M2379S"},{"every":"P0001-00-02T00:00:00"}],"timezone":"UTC"},"after":"2026-12-27T06:24:43.629134Z","next":"2026-12-27T08:33:27.000000Z"} +{"spec":{"timezone":"Europe/Budapest"},"after":"2027-05-12T17:09:10.245632Z","next":null} +{"spec":{"cron_expressions":["0 12 29 2 *","0 12 29 2 *"],"intervals":[{"every":"PT365S","offset":"PT6012S"},{"every":"P1M33D"}],"timezone":"UTC"},"after":"2027-05-06T18:35:26.651698Z","next":"2027-05-06T18:36:02.000000Z"} +{"spec":{"cron_expressions":[""],"timezone":"Mars/Base"},"after":"2026-03-10T10:39:07.478198Z","next":null} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT5H3741S"}],"timezone":""},"after":"2028-01-05T18:20:34.888588Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P4M0WT2322S"}]},"after":"2026-07-21T16:28:15.662861Z","next":"2026-07-21T17:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P0000-00-00T00:00:01"},{"every":"P0M26D","offset":"PT46H5M"}],"timezone":""},"after":"2027-06-04T00:58:09.456761Z","next":"2027-06-04T00:58:10.000000Z"} +{"spec":{"timezone":"+05:30"},"after":"2027-01-27T09:08:05.832968Z","next":null} +{"spec":{"cron_expressions":["bad cron"],"timezone":"Australia/Lord_Howe"},"after":"2028-12-25T04:26:27.107672Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["@hourly","0 12 29 2 *"],"timezone":"EST"},"after":"2028-05-29T01:31:21.413723Z","next":"2028-05-29T02:00:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"P0WT42H","offset":"PT1199S"}]},"after":"2028-01-04T18:16:16.915036Z","next":"2028-01-04T18:30:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *","5 4 L * ?"],"intervals":[{"every":"P1W2D"},{"every":"PT679S"}],"timezone":""},"after":"2028-03-04T03:20:11.545837Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["bad cron","bad cron"],"intervals":[{"every":"PT1447S"},{"every":"P21DT63M3717S"}],"timezone":"+05:30"},"after":"2025-12-26T02:29:16.499383Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 12 29 2 *",""],"timezone":"+05:30"},"after":"2027-07-15T01:40:42.095387Z","next":"2028-02-29T06:30:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 0 1 * *"],"intervals":[{"every":"P1YT5H4M"}],"timezone":"+05:30"},"after":"2028-04-19T02:09:53.218402Z","next":"2028-04-19T03:30:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P0000-00-00T00:00:01","offset":"P3WT3M"},{"every":"P0YT88M4546S"}],"timezone":"Europe/Budapest"},"after":"2028-10-07T22:05:54.257137Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["@hourly","0 9 * * 1-5"],"intervals":[{"every":"P0YT6H24M6322S"}],"timezone":"EST"},"after":"2028-10-26T01:31:52.303616Z","next":"2028-10-26T02:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","0 12 29 2 *"],"intervals":[{"every":"PT30H","offset":"PT5H"},{"every":"P1YT14M"}]},"after":"2027-06-02T15:17:32.785214Z","next":"2027-06-03T03:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P0Y3W"}]},"after":"2026-05-28T21:25:27.136769Z","next":"2026-05-28T22:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P7DT3H4M1770S","offset":"P2YT8H43M"},{"every":"P3WT78M646S","offset":"P0Y1W27DT4353S"}],"timezone":"Europe/Budapest"},"after":"2025-01-29T10:37:45.084511Z","next":"2025-02-04T14:28:30.000000Z"} +{"spec":{"timezone":"America/New_York"},"after":"2025-04-12T14:27:45.093767Z","next":null} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"PT2059S"},{"every":"P0WT1487S"}],"timezone":"UTC"},"after":"2027-04-28T14:09:01.570182Z","next":"2027-04-28T14:15:00.000000Z"} +{"spec":{"intervals":[{"every":"P2Y13M3WT23M2798S"}],"timezone":"UTC"},"after":"2028-04-30T19:08:55.882884Z","next":"2029-05-10T22:03:02.000000Z"} +{"spec":{"timezone":"+05:30"},"after":"2027-02-17T20:04:45.834270Z","next":null} +{"spec":{"timezone":"UTC"},"after":"2025-02-17T10:29:07.447213Z","next":null} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P12MT2744S"}],"timezone":"Europe/Budapest"},"after":"2026-04-10T12:32:48.747533Z","next":"2026-04-11T01:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly","0 12 29 2 *"],"intervals":[{"every":"P2Y0DT62M"}],"timezone":"Australia/Lord_Howe"},"after":"2026-09-23T18:09:40.405162Z","next":"2026-09-23T18:30:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","@hourly"],"timezone":"EST"},"after":"2026-11-12T23:46:25.107691Z","next":"2026-11-13T00:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P2YT84M","offset":"P1D "},{"every":"P1YT42H1751S","offset":"P2Y11M25DT3333S"}],"timezone":""},"after":"2025-09-09T16:54:14.086905Z","next":"2025-12-20T15:12:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","@hourly"],"timezone":"Europe/Budapest"},"after":"2027-08-10T09:28:31.100791Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["","@hourly"],"timezone":"+05:30"},"after":"2026-07-03T05:24:12.839482Z","next":"2026-07-03T05:30:00.000000Z"} +{"spec":{"intervals":[{"every":"P1YT32M6644S"}]},"after":"2026-06-15T16:00:41.356533Z","next":"2026-12-23T15:35:48.000000Z"} +{"spec":{"intervals":[{"every":"P6M","offset":"PT48S"},{"every":"PT6H","offset":"P5MT22H25M"}],"timezone":"Australia/Lord_Howe"},"after":"2028-12-19T18:07:36.150317Z","next":"2028-12-19T22:25:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"timezone":"EST"},"after":"2026-03-31T00:05:56.526078Z","next":"2026-03-31T14:00:00.000000Z"} +{"spec":{"intervals":[{"every":"PT1603S"}],"timezone":"Australia/Lord_Howe"},"after":"2025-05-21T10:52:55.835917Z","next":"2025-05-21T11:17:21.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"PT32M"}]},"after":"2025-09-04T12:46:58.433206Z","next":"2025-09-04T12:48:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?","0 9 * * 1-5"],"intervals":[{"every":"P1W4DT18M4172S"},{"every":"P2WT2H5M"}]},"after":"2025-03-07T22:26:01.676230Z","next":"2025-03-10T09:00:00.000000Z"} +{"spec":{"timezone":"Europe/Budapest"},"after":"2028-06-02T01:03:28.593956Z","next":null} +{"spec":{"cron_expressions":["bad cron","5 4 L * ?"],"intervals":[{"every":"P12M11DT5606S"}]},"after":"2027-02-20T15:51:28.737245Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P2Y0W"},{"every":"P1YT22H"}]},"after":"2026-11-21T20:57:43.086715Z","next":"2027-02-08T06:00:00.000000Z"} +{"spec":{"cron_expressions":["","0 9 * * 1-5"],"intervals":[{"every":"PT43M"}],"timezone":"Asia/Kolkata"},"after":"2026-07-21T01:21:39.190170Z","next":"2026-07-21T01:55:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"P16DT11H"}],"timezone":"Europe/Budapest"},"after":"2025-10-10T17:20:39.897813Z","next":"2025-10-14T10:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P1Y1WT76M"}],"timezone":"Australia/Lord_Howe"},"after":"2028-11-27T17:25:11.888760Z","next":"2028-11-28T16:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"P2MT16H57M","offset":"P2WT17H"}]},"after":"2028-04-09T01:18:43.454806Z","next":"2028-04-09T02:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN","0 0 1 * *"],"timezone":"UTC"},"after":"2026-09-20T08:31:47.183541Z","next":"2026-09-27T02:30:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"timezone":""},"after":"2025-12-28T01:17:20.637191Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["bad cron",""],"intervals":[{"every":"P6M"},{"every":"P0Y19DT21M"}],"timezone":"Asia/Kolkata"},"after":"2028-12-26T13:46:01.187704Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["@hourly","0 9 * * 1-5"],"intervals":[{"every":"P7MT55M","offset":"P2YT10H"}],"timezone":"Australia/Lord_Howe"},"after":"2026-12-29T06:37:33.194389Z","next":"2026-12-29T07:00:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"PT2311S"},{"every":"P7MT28H23M3499S","offset":"P1H"}],"timezone":"+05:30"},"after":"2027-06-21T07:21:11.470798Z","next":"2027-06-21T07:49:43.000000Z"} +{"spec":{"timezone":"+05:30"},"after":"2026-09-24T13:51:06.096924Z","next":null} +{"spec":{"intervals":[{"every":"P0Y3WT81M"},{"every":"P13M28DT53M"}],"timezone":"Australia/Lord_Howe"},"after":"2028-02-23T12:54:04.920105Z","next":"2028-03-02T18:09:00.000000Z"} +{"spec":{"intervals":[{"every":"P8DT27H4252S"},{"every":"P10000000000000000000D","offset":"P11M0WT3H5951S"}],"timezone":"Asia/Kolkata"},"after":"2026-03-24T06:35:01.245998Z","next":"2026-03-29T01:30:28.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"intervals":[{"every":"P0WT36M","offset":"P2YT31H5432S"}]},"after":"2028-01-27T05:04:39.934361Z","next":"2028-01-27T05:30:32.000000Z"} +{"spec":{"intervals":[{"every":"P7MT14M"}],"timezone":"Europe/Budapest"},"after":"2026-05-08T07:51:47.649487Z","next":"2026-12-03T23:06:00.000000Z"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P1YT39H"}],"timezone":"Asia/Kolkata"},"after":"2026-01-22T04:12:30.162444Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 9 * * 1-5","bad cron"],"intervals":[{"every":"P9M3W"},{"every":"P15D","offset":"P18D"}],"timezone":"Mars/Base"},"after":"2028-11-06T02:07:55.441990Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P0WT4725S"},{"every":"P3WT648S","offset":"P2Y24DT4083S"}],"timezone":"EST"},"after":"2025-02-08T11:26:44.253213Z","next":"2025-02-08T12:22:30.000000Z"} +{"spec":{"cron_expressions":["bad cron","0 3 * * *"]},"after":"2028-08-09T08:48:08.237585Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P4M","offset":"PT86M"}],"timezone":"EST"},"after":"2027-12-04T22:26:08.716976Z","next":"2028-02-26T01:26:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P13M3WT3261S"}],"timezone":"America/New_York"},"after":"2026-01-19T19:09:33.174773Z","next":"2026-01-20T08:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P3M39DT46H77M"}],"timezone":"UTC"},"after":"2026-03-10T15:01:50.491354Z","next":"2026-04-19T07:29:00.000000Z"} +{"spec":{"cron_expressions":["0 0 1 * *"],"timezone":"Asia/Kolkata"},"after":"2026-06-29T04:16:30.557734Z","next":"2026-06-30T18:30:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"timezone":"Asia/Kolkata"},"after":"2026-08-02T13:04:47.187823Z","next":"2026-08-08T21:00:00.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"intervals":[{"every":"PT2860S"},{"every":"PT2638S","offset":"PT"}],"timezone":"Europe/Budapest"},"after":"2028-04-27T02:21:47.408712Z","next":"2028-04-27T02:35:20.000000Z"} +{"spec":{"cron_expressions":[""],"timezone":"+05:30"},"after":"2026-12-30T01:24:35.149947Z","next":null} +{"spec":{"intervals":[{"every":"P2WT41M5551S"}],"timezone":"America/New_York"},"after":"2025-09-26T18:33:13.884679Z","next":"2025-10-02T23:31:35.000000Z"} +{"spec":{"cron_expressions":["bad cron","@hourly"],"timezone":"+05:30"},"after":"2027-02-24T10:55:17.432973Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 0 1 * *","30 2 * * SUN"],"intervals":[{"every":"P1W"},{"every":"P13D"}],"timezone":"Australia/Lord_Howe"},"after":"2026-02-20T09:48:01.738348Z","next":"2026-02-21T15:30:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *","@hourly"],"intervals":[{"every":"PT80M","offset":"P0YT30M657S"},{"every":"P1H","offset":"P1WT51M"}],"timezone":"America/New_York"},"after":"2027-05-03T13:49:46.486180Z","next":"2027-05-03T14:00:00.000000Z"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P2W"},{"every":"P1WT27H"}],"timezone":"Australia/Lord_Howe"},"after":"2027-02-17T04:38:08.197029Z","next":"2027-02-24T03:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"],"intervals":[{"every":"PT66M6672S"},{"every":"P"}],"timezone":"+05:30"},"after":"2028-09-07T04:05:51.807222Z","next":"2028-09-07T04:27:36.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","@hourly"],"intervals":[{"every":"P2YT20H"},{"every":"PT2H16M"}],"timezone":"+05:30"},"after":"2026-09-04T02:58:08.866458Z","next":"2026-09-04T03:00:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"]},"after":"2025-04-17T18:48:32.751543Z","next":"2025-04-20T02:30:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?"],"intervals":[{"every":"P0D"},{"every":"PT33H34M"}]},"after":"2026-10-24T03:55:17.295775Z","next":"2026-10-25T04:38:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","bad cron"],"intervals":[{"every":"PT2966S"}],"timezone":"Europe/Budapest"},"after":"2028-02-19T13:53:14.677613Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 0 1 * *","bad cron"],"intervals":[{"every":"PT51M5078S"}]},"after":"2026-08-14T17:39:52.890454Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 0 1 * *"],"timezone":"Mars/Base"},"after":"2027-05-21T20:10:13.143227Z","error":"DateInvalidTimeZoneException"} +{"spec":{"timezone":"Australia/Lord_Howe"},"after":"2027-04-05T00:11:09.055188Z","next":null} +{"spec":{"cron_expressions":["@hourly"]},"after":"2025-01-06T16:23:14.305637Z","next":"2025-01-06T17:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *","0 3 * * *"]},"after":"2028-12-04T20:24:10.233883Z","next":"2028-12-05T03:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *","0 12 29 2 *"],"intervals":[{"every":"P3W"}],"timezone":"+05:30"},"after":"2027-07-31T02:00:51.896288Z","next":"2027-08-12T00:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","30 2 * * SUN"]},"after":"2027-07-26T19:49:36.503106Z","next":"2027-07-27T09:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *",""]},"after":"2025-09-16T05:20:46.388159Z","next":"2025-09-17T03:00:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5","0 0 1 * *"],"intervals":[{"every":"P1YT13H3172S","offset":"PT3117S"},{"every":"PT15M"}],"timezone":"America/New_York"},"after":"2026-01-04T12:00:10.769130Z","next":"2026-01-04T12:15:00.000000Z"} +{"spec":{"cron_expressions":["30 2 * * SUN"]},"after":"2026-08-31T22:28:14.121490Z","next":"2026-09-06T02:30:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","5 4 L * ?"],"intervals":[{"every":"P0Y0WT86M"},{"every":"P2Y2M3W16DT27M","offset":"PT2120S"}],"timezone":"+05:30"},"after":"2027-07-15T21:26:34.731017Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["0 0 1 * *","0 3 * * *"],"intervals":[{"every":"P1YT90M"},{"every":"PT5018S","offset":"P1Y9MT37H71M4532S"}],"timezone":"Asia/Kolkata"},"after":"2027-12-21T21:30:06.658712Z","next":"2027-12-21T21:53:10.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"],"intervals":[{"every":"P0YT6366S","offset":"P2Y1W"}],"timezone":"Asia/Kolkata"},"after":"2026-03-24T21:41:26.622374Z","next":"2026-03-24T21:45:00.000000Z"} +{"spec":{"cron_expressions":["","*/15 * * * *"],"intervals":[{"every":"P2W"}],"timezone":"EST"},"after":"2027-11-17T15:00:41.485866Z","next":"2027-11-17T15:15:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *","5 4 L * ?"],"intervals":[{"every":"P1W33D","offset":"P12MT378S"}],"timezone":"Mars/Base"},"after":"2028-04-19T05:22:05.237645Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["","0 9 * * 1-5"]},"after":"2026-10-07T10:43:41.406206Z","next":"2026-10-08T09:00:00.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"timezone":"Europe/Budapest"},"after":"2025-11-19T08:15:58.552197Z","next":"2028-02-29T11:00:00.000000Z"} +{"spec":{"intervals":[{"every":"P0YT48H5728S"},{"every":"P0D"}],"timezone":"Asia/Kolkata"},"after":"2027-10-02T05:05:34.508435Z","next":"2027-10-03T19:39:12.000000Z"} +{"spec":{"cron_expressions":["@hourly","bad cron"],"intervals":[{"every":"P0WT22M"},{"every":"PT623S","offset":"P12MT4H"}]},"after":"2026-06-12T03:09:06.754174Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":["5 4 L * ?"],"intervals":[{"every":"P2WT23H"},{"every":"P1YT435S"}]},"after":"2025-02-24T20:15:53.177304Z","next":"2025-02-28T04:05:00.000000Z"} +{"spec":{"intervals":[{"every":"PT1551S"},{"every":"PT2326S","offset":"P2YT41H1729S"}]},"after":"2028-12-05T19:57:34.522907Z","next":"2028-12-05T20:10:57.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *","*/15 * * * *"],"timezone":"UTC"},"after":"2027-04-02T19:21:37.829761Z","next":"2027-04-02T19:30:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P0Y1WT9H"}],"timezone":"America/New_York"},"after":"2027-04-16T09:59:04.666378Z","next":"2027-04-17T07:00:00.000000Z"} +{"spec":{"cron_expressions":["bad cron","*/15 * * * *"],"intervals":[{"every":"PT18H231S","offset":"P1D "}],"timezone":"EST"},"after":"2028-11-05T01:22:28.342817Z","error":"InvalidArgumentException"} +{"spec":{"intervals":[{"every":"P5MT3453S"},{"every":"P2WT37M"}]},"after":"2026-05-04T07:48:00.274407Z","next":"2026-05-16T17:16:00.000000Z"} +{"spec":{"intervals":[{"every":"PT9M","offset":"P2Y"}]},"after":"2026-03-09T07:12:49.000324Z","next":"2026-03-09T07:21:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *",""],"intervals":[{"every":"PT1474S"},{"every":"P13MT28M2342S","offset":"PT6295S"}]},"after":"2026-05-10T13:27:21.459455Z","next":"2026-05-10T13:30:00.000000Z"} +{"spec":{"cron_expressions":["5 4 L * ?","0 12 29 2 *"],"intervals":[{"every":"PT25H1355S","offset":"P2WT31H"}]},"after":"2025-10-22T08:58:36.990677Z","next":"2025-10-23T01:45:20.000000Z"} +{"spec":{"cron_expressions":["@hourly"],"timezone":"Australia/Lord_Howe"},"after":"2028-01-17T08:55:20.444833Z","next":"2028-01-17T09:00:00.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"PT1.5S"}],"timezone":"Mars/Base"},"after":"2025-05-07T15:51:22.591059Z","error":"DateInvalidTimeZoneException"} +{"spec":{"intervals":[{"every":"P0Y31DT32H6046S","offset":"P38DT19H"},{"every":"P6MT78M6815S","offset":"PT1.5S"}],"timezone":"Australia/Lord_Howe"},"after":"2027-07-15T04:53:26.159920Z","next":"2027-08-06T03:16:48.000000Z"} +{"spec":{"intervals":[{"every":"PT6849S"},{"every":"P2WT23H","offset":"P20DT19H"}]},"after":"2027-05-07T12:45:07.017482Z","next":"2027-05-07T13:46:12.000000Z"} +{"spec":{"cron_expressions":["0 3 * * *"],"intervals":[{"every":"P0YT41H17M3312S"}],"timezone":"UTC"},"after":"2027-12-29T23:08:49.524608Z","next":"2027-12-30T03:00:00.000000Z"} +{"spec":{"cron_expressions":["*/15 * * * *"],"timezone":"Europe/Budapest"},"after":"2026-09-27T08:30:11.537157Z","next":"2026-09-27T08:45:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"P0WT5H3021S"}]},"after":"2028-04-30T05:58:26.585620Z","next":"2028-04-30T10:37:45.000000Z"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"timezone":""},"after":"2027-12-17T10:20:14.550543Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["0 12 29 2 *"],"intervals":[{"every":"P2YT45H41M6210S","offset":"P6MT17H75M"},{"every":"PT44H"}],"timezone":""},"after":"2028-07-05T05:13:13.359070Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["0 9 * * 1-5","*/15 * * * *"],"timezone":"+05:30"},"after":"2028-11-21T09:28:50.578338Z","next":"2028-11-21T09:30:00.000000Z"} +{"spec":{"cron_expressions":["0 9 * * 1-5"],"intervals":[{"every":"P37DT6105S","offset":"PT0001H"},{"every":"PT840S","offset":"P0WT41M"}],"timezone":""},"after":"2028-03-01T10:19:48.814592Z","error":"DateInvalidTimeZoneException"} +{"spec":{"cron_expressions":["bad cron"],"intervals":[{"every":"P13M1WT4544S"},{"every":"P12M"}],"timezone":"EST"},"after":"2027-12-22T21:06:27.540112Z","error":"InvalidArgumentException"} +{"spec":{"cron_expressions":[""],"intervals":[{"every":"P0WT37M","offset":"P13MT13H"}],"timezone":"America/New_York"},"after":"2025-04-21T20:58:32.363793Z","next":"2025-04-21T21:06:00.000000Z"} diff --git a/rust/tests/fixtures/cron/php-2.5.14-next-run.jsonl b/rust/tests/fixtures/cron/php-2.5.14-next-run.jsonl new file mode 100644 index 000000000..708b428b1 --- /dev/null +++ b/rust/tests/fixtures/cron/php-2.5.14-next-run.jsonl @@ -0,0 +1,1200 @@ +{"cron":"26,43 23-23/4 13 9,10 2-2","after":"2025-10-25T23:11:57.000000Z","tz":"Europe/Dublin","next":"2025-10-28T23:26:00.000000Z"} +{"cron":"23-54 */4 28 */6 FRI","after":"2028-10-05T04:30:59.000000Z","tz":"America/Sao_Paulo","next":"2029-01-05T03:23:00.000000Z"} +{"cron":"1 14 10-21 */2 */4","after":"2028-02-11T20:06:17.000000Z","tz":"UTC","next":"2028-03-02T14:01:00.000000Z"} +{"cron":"*/16 7,23 16-19 6-8 4L","after":"2028-06-08T08:59:36.000000Z","tz":"Europe/Dublin","next":"2028-06-16T06:00:00.000000Z"} +{"cron":"9 11-14 ? * */6","after":"2027-10-30T23:08:55.000000Z","tz":"Europe/Budapest","next":"2027-10-31T10:09:00.000000Z"} +{"cron":"46,38 */16 L 11 ?","after":"2024-03-31T00:38:02.000000Z","tz":"Europe/Dublin","next":"2024-11-30T00:38:00.000000Z"} +{"cron":"58-58 5-8 ? APR *","after":"2028-11-30T04:19:35.000000Z","tz":"Pacific/Apia","next":"2029-03-31T16:58:00.000000Z"} +{"cron":"* 10,7 L 5 7-7","after":"2028-03-25T20:43:39.000000Z","tz":"Asia/Beirut","next":"2028-05-01T07:00:00.000000Z"} +{"cron":"* 10 3 */1 ?","after":"2028-02-18T01:28:32.000000Z","tz":"Pacific/Apia","next":"2028-03-02T21:00:00.000000Z"} +{"cron":"30 2-17 12-28 * ?","after":"2028-02-28T22:41:35.000000Z","tz":"Pacific/Chatham","next":"2028-03-11T12:45:00.000000Z"} +{"cron":"@annually","after":"2028-04-01T14:25:03.000000Z","tz":"Pacific/Chatham","next":"2028-12-31T10:15:00.000000Z"} +{"cron":"*/53 9-19 5-5 OCT 3,4","after":"2026-08-30T15:12:07.500000Z","tz":"America/Sao_Paulo","next":"2026-10-01T12:00:00.000000Z"} +{"cron":"23 10,1 * 12-12 7,4","after":"2026-05-02T03:03:08.000000Z","tz":"Pacific/Apia","next":"2026-12-02T21:23:00.000000Z"} +{"cron":"29 4 L FEB TUE","after":"2025-09-18T12:48:34.000000Z","tz":"America/Sao_Paulo","next":"2026-02-03T07:29:00.000000Z"} +{"cron":"* * */25 4,7 0#4","after":"2026-03-29T01:31:21.000000Z","tz":"Europe/London","next":"2026-03-31T23:00:00.000000Z"} +{"cron":"54-58 4-18 ? 5-12/5 1-4/5","after":"2028-12-19T11:06:48.000000Z","tz":"Europe/London","next":"2029-05-07T03:54:00.000000Z"} +{"cron":"31 */9 5,15 1,7 7-7/1","after":"2024-03-31T00:08:22.500000Z","tz":"Europe/Dublin","next":"2024-06-30T23:31:00.000000Z"} +{"cron":"48 */7 30 NOV 0-7/3","after":"2024-11-03T02:47:10.000000Z","tz":"America/St_Johns","next":"2024-11-06T04:18:00.000000Z"} +{"cron":"41,43 21-21/5 */28 1 3L","after":"2026-02-19T13:52:38.000000Z","tz":"Europe/Dublin","next":"2027-01-01T21:41:00.000000Z"} +{"cron":"50 11 1,23 7-11/7 1L","after":"2024-03-10T08:32:03.000000Z","tz":"America/New_York","next":"2024-07-01T15:50:00.000000Z"} +{"cron":"* */25 * * 3,1","after":"2026-04-30T22:22:02.000000Z","tz":"America/St_Johns","next":"2026-05-04T03:30:00.000000Z"} +{"cron":"11 5 4-21 NOV *","after":"2026-04-30T21:35:20.000000Z","tz":"Europe/London","next":"2026-11-04T05:11:00.000000Z"} +{"cron":"29-52 23 10-22/3 10,2 7#2","after":"2027-02-28T21:55:35.000000Z","tz":"Pacific/Apia","next":"2027-10-10T10:29:00.000000Z"} +{"cron":"11,59 */25 */27 1 *","after":"2026-11-01T05:54:40.000000Z","tz":"America/New_York","next":"2027-01-01T06:11:00.000000Z"} +{"cron":"@yearly","after":"2024-10-26T19:49:55.000000Z","tz":"Asia/Beirut","next":"2024-12-31T22:00:00.000000Z"} +{"cron":"55,6 2-3/7 */28 SEP */1","after":"2026-12-31T21:45:46.000000Z","tz":"Asia/Kolkata","next":"2027-08-31T20:36:00.000000Z"} +{"cron":"@midnight","after":"2025-08-14T17:08:45.500000Z","tz":"UTC","next":"2025-08-15T00:00:00.000000Z"} +{"cron":"55 21-23 8,17 12-12/7 2-7/7","after":"2027-02-28T21:10:45.500000Z","tz":"UTC","next":"2027-12-07T21:55:00.000000Z"} +{"cron":"46,49 14-16 31 * ?","after":"2025-05-01T09:12:58.000000Z","tz":"America/Havana","next":"2025-05-31T18:46:00.000000Z"} +{"cron":"@yearly","after":"2028-09-14T18:51:55.000000Z","tz":"Asia/Tehran","next":"2028-12-31T20:30:00.000000Z"} +{"cron":"* 6-20/1 22W 5-10 */6","after":"2026-12-09T13:46:12.000000Z","tz":"Europe/Dublin","next":"2027-05-01T05:00:00.000000Z"} +{"cron":"@annually","after":"2028-10-07T02:31:55.500000Z","tz":"Europe/London","next":"2029-01-01T00:00:00.000000Z"} +{"cron":"55 10,16 6-28 JAN */5","after":"2028-03-25T23:28:41.500000Z","tz":"Europe/Budapest","next":"2029-01-05T09:55:00.000000Z"} +{"cron":"16 0,18 12,20 */13 *","after":"2026-09-03T22:05:08.000000Z","tz":"America/Santiago","next":"2027-02-12T03:16:00.000000Z"} +{"cron":"12 19-20/3 ? 9-11 4L","after":"2027-10-17T18:43:13.000000Z","tz":"Europe/Budapest","next":"2027-10-28T17:12:00.000000Z"} +{"cron":"13-37/1 10,2 3,29 9-12/4 5-6/3","after":"2026-12-31T21:34:26.500000Z","tz":"Pacific/Apia","next":"2027-09-02T21:13:00.000000Z"} +{"cron":"@DAILY","after":"2028-05-28T07:39:12.000000Z","tz":"Pacific/Apia","next":"2028-05-28T11:00:00.000000Z"} +{"cron":"@DAILY","after":"2025-06-13T05:45:16.000000Z","tz":"UTC","next":"2025-06-14T00:00:00.000000Z"} +{"cron":"41 2 13 * 5-7","after":"2027-02-28T21:49:46.000000Z","tz":"Asia/Tehran","next":"2027-03-04T23:11:00.000000Z"} +{"cron":"5-59/4 7 * 9-12 2,1","after":"2026-12-31T23:11:19.000000Z","tz":"Pacific/Chatham","next":"2027-09-05T18:20:00.000000Z"} +{"cron":"12,17 23 2-15 5,9 */9","after":"2027-03-24T05:47:40.500000Z","tz":"Europe/Dublin","next":"2027-05-02T22:12:00.000000Z"} +{"cron":"10 14 12 5,12 3,5","after":"2028-08-29T13:21:53.000000Z","tz":"America/Sao_Paulo","next":"2028-12-01T17:10:00.000000Z"} +{"cron":"*/12 14-16/1 ? */10 ?","after":"2026-01-31T22:53:42.000000Z","tz":"Asia/Kolkata","error":"InvalidArgumentException"} +{"cron":"58-59/6 */7 29 6,12 */9","after":"2027-04-12T18:51:22.000000Z","tz":"Asia/Beirut","next":"2027-06-06T21:58:00.000000Z"} +{"cron":"*/49 4-20 ? 6-12 5#3","after":"2025-11-02T04:01:48.000000Z","tz":"America/St_Johns","next":"2025-11-21T07:30:00.000000Z"} +{"cron":"7-14/3 * 12,10 4-6 *","after":"2026-05-01T00:08:01.000000Z","tz":"Asia/Beirut","next":"2026-05-09T21:07:00.000000Z"} +{"cron":"35 13 10-18 OCT 4L","after":"2025-03-09T04:00:49.000000Z","tz":"America/Havana","next":"2025-10-10T17:35:00.000000Z"} +{"cron":"* 13,6 L 7 3#2","after":"2025-11-02T04:28:00.000000Z","tz":"America/New_York","next":"2026-07-08T17:00:00.000000Z"} +{"cron":"@midnight","after":"2026-11-14T01:52:02.000000Z","tz":"America/Sao_Paulo","next":"2026-11-14T03:00:00.000000Z"} +{"cron":"22-28/6 15 L */9 *","after":"2025-03-07T07:45:57.000000Z","tz":"Asia/Kolkata","next":"2025-10-31T09:52:00.000000Z"} +{"cron":"* 6 1 9-11/7 2-5/4","after":"2024-10-27T01:10:36.000000Z","tz":"Europe/Budapest","next":"2025-09-01T04:00:00.000000Z"} +{"cron":"21-24/6 */15 11 */5 */1","after":"2027-10-30T21:47:23.500000Z","tz":"Asia/Beirut","next":"2027-10-31T22:21:00.000000Z"} +{"cron":"39,15 2-19/2 26-30/2 MAY 7","after":"2026-10-14T13:16:54.500000Z","tz":"America/Santiago","next":"2027-05-02T06:15:00.000000Z"} +{"cron":"16 19-19/1 31-31 9 3-3/5","after":"2026-03-29T02:01:16.500000Z","tz":"Europe/London","next":"2026-09-02T18:16:00.000000Z"} +{"cron":"22-41 9,17 ? 4,12 1,1","after":"2027-02-09T21:09:22.000000Z","tz":"Asia/Kolkata","next":"2027-04-05T03:52:00.000000Z"} +{"cron":"54 * */22 8,7 */7","after":"2026-04-30T21:05:22.500000Z","tz":"Asia/Tehran","next":"2026-06-30T21:24:00.000000Z"} +{"cron":"43-54 20,2 29-29 8,4 *","after":"2026-06-29T11:48:05.000000Z","tz":"UTC","next":"2026-08-29T20:43:00.000000Z"} +{"cron":"39-54 18-22/5 L 2-11/5 ?","after":"2027-11-08T08:56:07.000000Z","tz":"UTC","next":"2028-02-29T18:39:00.000000Z"} +{"cron":"*/43 15 31 2-11/3 *","after":"2028-04-20T18:47:49.000000Z","tz":"America/Havana","next":"2028-05-31T19:00:00.000000Z"} +{"cron":"21-28/7 9-19 18 3,8 1,7","after":"2024-03-30T23:45:18.000000Z","tz":"Antarctica/Troll","next":"2024-03-31T08:21:00.000000Z"} +{"cron":"59-59 18-20 21 2,4 3L","after":"2025-04-10T20:04:29.500000Z","tz":"UTC","next":"2025-04-21T18:59:00.000000Z"} +{"cron":"* 20-22/7 ? MAR TUE","after":"2025-03-22T18:06:23.000000Z","tz":"UTC","next":"2025-03-25T20:00:00.000000Z"} +{"cron":"49 */25 3-11/5 DEC *","after":"2025-11-02T06:03:28.000000Z","tz":"America/New_York","next":"2025-12-03T06:49:00.000000Z"} +{"cron":"54-56 7 29-31 * 3-6/3","after":"2027-03-14T06:00:02.000000Z","tz":"America/St_Johns","next":"2027-03-17T10:24:00.000000Z"} +{"cron":"52 5 17 */1 6L","after":"2026-07-13T04:21:06.000000Z","tz":"Europe/London","next":"2026-07-17T04:52:00.000000Z"} +{"cron":"28-45/1 15-17/7 28-29/1 2-8 SAT","after":"2026-10-25T00:22:09.000000Z","tz":"Europe/Dublin","next":"2027-02-06T15:28:00.000000Z"} +{"cron":"*/17 10,19 */8 JAN 6-7/1","after":"2026-01-31T21:25:07.000000Z","tz":"Antarctica/Troll","next":"2027-01-01T10:00:00.000000Z"} +{"cron":"* */15 21W */6 2#5","after":"2026-03-08T07:04:11.000000Z","tz":"America/New_York","next":"2026-07-21T04:00:00.000000Z"} +{"cron":"22 8 * 1-1 */2","after":"2027-03-27T23:27:09.000000Z","tz":"Europe/Dublin","next":"2028-01-01T08:22:00.000000Z"} +{"cron":"38,7 8 26 * ?","after":"2026-02-25T19:00:29.000000Z","tz":"Europe/Dublin","next":"2026-02-26T08:07:00.000000Z"} +{"cron":"20 * 5-10 11-11/1 3L","after":"2025-03-09T05:47:05.500000Z","tz":"America/St_Johns","next":"2025-11-05T03:50:00.000000Z"} +{"cron":"41 11 6W 6-6 *","after":"2028-08-09T04:14:05.000000Z","tz":"Asia/Kolkata","next":"2029-06-06T06:11:00.000000Z"} +{"cron":"26-48 * 10 * ?","after":"2025-10-26T02:21:08.000000Z","tz":"Antarctica/Troll","next":"2025-11-10T00:26:00.000000Z"} +{"cron":"15-35 10 * */11 2#4","after":"2028-08-07T21:03:46.000000Z","tz":"UTC","next":"2028-12-26T10:15:00.000000Z"} +{"cron":"50 20 31 4 7#3","after":"2028-02-29T21:00:34.000000Z","tz":"Asia/Beirut","next":"2028-04-16T17:50:00.000000Z"} +{"cron":"26-45/7 * */25 */13 0","after":"2025-11-04T00:25:49.500000Z","tz":"Asia/Tehran","next":"2026-01-31T20:56:00.000000Z"} +{"cron":"9,53 20-20/1 10-14 * 7-7","after":"2026-12-31T23:57:44.500000Z","tz":"Antarctica/Troll","next":"2027-01-01T20:09:00.000000Z"} +{"cron":"@monthly","after":"2026-04-30T21:13:00.000000Z","tz":"Pacific/Chatham","next":"2026-05-31T11:15:00.000000Z"} +{"cron":"3-31 6 17W 2-7/1 5#5","after":"2026-02-01T00:22:40.500000Z","tz":"America/Sao_Paulo","next":"2026-02-17T09:03:00.000000Z"} +{"cron":"39 16,3 L 6 6-6","after":"2028-01-07T02:25:30.000000Z","tz":"UTC","next":"2028-06-03T16:39:00.000000Z"} +{"cron":"1-29 */3 ? 11 6","after":"2028-02-29T23:55:52.000000Z","tz":"America/St_Johns","next":"2028-11-04T02:31:00.000000Z"} +{"cron":"43 22-23 ? */9 0","after":"2026-07-30T23:15:51.000000Z","tz":"Asia/Beirut","next":"2026-10-04T19:43:00.000000Z"} +{"cron":"49 * 3W 1-11 7L","after":"2025-10-26T01:22:35.000000Z","tz":"Europe/Dublin","next":"2025-10-26T01:49:00.000000Z"} +{"cron":"* */25 15,2 10 6L","after":"2026-12-31T23:27:05.000000Z","tz":"America/St_Johns","next":"2027-10-02T03:30:00.000000Z"} +{"cron":"* */15 25-27/5 AUG *","after":"2027-03-06T19:55:42.000000Z","tz":"Asia/Tehran","next":"2027-08-24T20:30:00.000000Z"} +{"cron":"48-49 7-9 29,4 */1 5-6/5","after":"2028-05-09T20:48:11.000000Z","tz":"Asia/Kolkata","next":"2028-05-12T02:18:00.000000Z"} +{"cron":"*/26 */6 2 */10 ?","after":"2027-02-28T23:41:56.000000Z","tz":"Asia/Beirut","next":"2027-11-01T22:00:00.000000Z"} +{"cron":"14,26 19 5-29/2 1-9 */7","after":"2026-04-04T14:08:34.000000Z","tz":"Pacific/Chatham","next":"2026-04-05T06:29:00.000000Z"} +{"cron":"38 */3 * */7 MON","after":"2028-10-28T23:41:46.000000Z","tz":"Europe/Budapest","next":"2028-12-31T23:38:00.000000Z"} +{"cron":"53 8,20 9,31 JAN 4,0","after":"2025-09-11T16:01:15.000000Z","tz":"Asia/Tehran","next":"2026-01-01T05:23:00.000000Z"} +{"cron":"18-42 13 28W 11-12/7 MON","after":"2028-01-05T11:01:34.000000Z","tz":"Pacific/Apia","next":"2028-11-06T00:18:00.000000Z"} +{"cron":"14 */10 * 4-7/5 */6","after":"2026-03-29T02:09:39.500000Z","tz":"Europe/Budapest","next":"2026-04-03T22:14:00.000000Z"} +{"cron":"19 3 6 */4 1L","after":"2024-11-03T03:14:58.000000Z","tz":"America/Havana","next":"2025-01-06T08:19:00.000000Z"} +{"cron":"*/42 * 21-31 SEP ?","after":"2026-01-29T06:32:51.500000Z","tz":"Australia/Lord_Howe","next":"2026-09-20T13:30:00.000000Z"} +{"cron":"* 1,12 * * 1#3","after":"2028-02-29T00:49:15.000000Z","tz":"America/Santiago","next":"2028-03-20T04:00:00.000000Z"} +{"cron":"*/50 8,1 20-20/2 * 7","after":"2028-06-29T17:14:03.000000Z","tz":"America/St_Johns","next":"2028-07-02T10:30:00.000000Z"} +{"cron":"3-46 13 13 5,12 6-7/3","after":"2025-11-04T04:44:47.000000Z","tz":"Asia/Beirut","next":"2025-12-06T11:03:00.000000Z"} +{"cron":"39 11-11 L 7-11/3 *","after":"2027-01-01T00:02:51.000000Z","tz":"Pacific/Chatham","next":"2027-07-30T22:54:00.000000Z"} +{"cron":"@daily","after":"2027-03-27T23:55:19.000000Z","tz":"Europe/Dublin","next":"2027-03-28T00:00:00.000000Z"} +{"cron":"*/42 0,12 * APR 4-6","after":"2024-09-08T04:01:46.000000Z","tz":"America/Santiago","next":"2025-04-03T03:00:00.000000Z"} +{"cron":"43 8 16 10,7 2-7/5","after":"2028-03-01T00:13:31.000000Z","tz":"Australia/Lord_Howe","next":"2028-07-01T22:13:00.000000Z"} +{"cron":"1 23-23/2 17-28/5 MAR 5","after":"2026-06-07T11:54:41.000000Z","tz":"Asia/Kolkata","next":"2027-03-05T17:31:00.000000Z"} +{"cron":"18-39 16 18 * */1","after":"2024-11-03T05:52:56.000000Z","tz":"America/St_Johns","next":"2024-11-03T19:48:00.000000Z"} +{"cron":"49,18 13,11 16,12 7 6#4","after":"2026-07-16T09:27:44.000000Z","tz":"Asia/Kolkata","next":"2026-07-25T07:48:00.000000Z"} +{"cron":"51-52/3 5,4 11 7-7/5 ?","after":"2026-10-10T04:59:30.500000Z","tz":"UTC","next":"2027-07-11T05:51:00.000000Z"} +{"cron":"* 20 20 3-12 5-7","after":"2027-03-01T00:35:14.500000Z","tz":"Europe/Budapest","next":"2027-03-05T19:00:00.000000Z"} +{"cron":"23-34/2 * ? * 3,1","after":"2026-05-01T00:23:49.500000Z","tz":"Asia/Tehran","next":"2026-05-03T20:53:00.000000Z"} +{"cron":"29-53 16,23 8-30 * 2L","after":"2025-11-02T03:58:17.000000Z","tz":"America/St_Johns","next":"2025-11-08T19:59:00.000000Z"} +{"cron":"* 9-11 9 */12 */7","after":"2026-03-08T06:44:06.000000Z","tz":"America/Havana","next":"2027-01-03T14:00:00.000000Z"} +{"cron":"7-24 15 */19 7-10/7 3#1","after":"2027-03-15T04:21:17.000000Z","tz":"Asia/Tehran","next":"2027-07-01T11:37:00.000000Z"} +{"cron":"* 14 9-29 10-11 3#3","after":"2025-04-05T15:07:41.000000Z","tz":"Australia/Lord_Howe","next":"2025-10-09T03:00:00.000000Z"} +{"cron":"6 15-21 11 */3 6L","after":"2026-01-31T23:51:21.000000Z","tz":"America/Santiago","next":"2026-02-01T00:06:00.000000Z"} +{"cron":"14-55/7 21 17,22 */8 3,1","after":"2027-03-28T00:58:35.000000Z","tz":"Antarctica/Troll","next":"2027-09-01T19:14:00.000000Z"} +{"cron":"* 11-11 29 3,5 *","after":"2024-04-06T14:12:40.000000Z","tz":"Pacific/Chatham","next":"2024-05-28T22:15:00.000000Z"} +{"cron":"* 4-11/5 20,3 OCT TUE","after":"2026-03-08T05:34:53.000000Z","tz":"America/New_York","next":"2026-10-03T08:00:00.000000Z"} +{"cron":"23 1,17 L 10 3#5","after":"2028-02-28T22:06:47.500000Z","tz":"Pacific/Apia","next":"2028-10-30T12:23:00.000000Z"} +{"cron":"12,17 * 9 1 */7","after":"2026-09-17T13:34:55.000000Z","tz":"UTC","next":"2027-01-03T00:12:00.000000Z"} +{"cron":"29 */7 L 5 0","after":"2027-09-01T17:22:42.000000Z","tz":"Pacific/Chatham","next":"2028-05-06T11:44:00.000000Z"} +{"cron":"29,17 11-23/4 10-30 SEP ?","after":"2026-03-28T22:24:10.500000Z","tz":"Asia/Beirut","next":"2026-09-10T08:17:00.000000Z"} +{"cron":"30-56 */9 21-24 4-12 THU","after":"2028-03-01T00:05:15.000000Z","tz":"Asia/Beirut","next":"2028-04-05T21:30:00.000000Z"} +{"cron":"* */5 */32 9-11/6 7,2","after":"2026-10-03T14:06:57.500000Z","tz":"Australia/Lord_Howe","next":"2027-09-01T13:30:00.000000Z"} +{"cron":"37 9,10 */4 * ?","after":"2026-01-31T23:17:36.000000Z","tz":"America/Sao_Paulo","next":"2026-02-01T12:37:00.000000Z"} +{"cron":"* 18 15W 8-11/4 0-2","after":"2025-03-29T20:44:01.000000Z","tz":"Asia/Beirut","next":"2025-08-03T15:00:00.000000Z"} +{"cron":"51-58/5 */10 16,17 4-7/2 1-4","after":"2026-01-31T22:15:55.000000Z","tz":"UTC","next":"2026-04-01T00:51:00.000000Z"} +{"cron":"*/36 21 15-25 6 5-6/2","after":"2025-03-29T10:22:19.000000Z","tz":"Pacific/Chatham","next":"2025-06-06T08:15:00.000000Z"} +{"cron":"@monthly","after":"2027-04-04T04:49:39.500000Z","tz":"America/Santiago","next":"2027-05-01T04:00:00.000000Z"} +{"cron":"* 1,6 */5 6,5 3-6","after":"2028-02-28T21:49:14.500000Z","tz":"Europe/Dublin","next":"2028-05-01T00:00:00.000000Z"} +{"cron":"19,47 * 7-27 * */2","after":"2026-04-30T22:57:57.000000Z","tz":"Asia/Kolkata","next":"2026-05-01T18:49:00.000000Z"} +{"cron":"*/7 5-10/2 ? 4-10 5,1","after":"2027-09-05T05:11:02.500000Z","tz":"America/Santiago","next":"2027-09-06T08:00:00.000000Z"} +{"cron":"2-47/7 2 L 10,8 4L","after":"2028-09-03T05:38:29.500000Z","tz":"America/Santiago","next":"2028-10-26T05:02:00.000000Z"} +{"cron":"55-55 6-22/7 11-14/2 */9 3,4","after":"2025-11-02T06:38:39.000000Z","tz":"America/New_York","next":"2026-01-01T11:55:00.000000Z"} +{"cron":"15 * 10W AUG 4-6","after":"2026-01-31T22:21:23.000000Z","tz":"Antarctica/Troll","next":"2026-07-31T22:15:00.000000Z"} +{"cron":"6 8,20 29,3 */6 5","after":"2028-02-29T22:59:43.500000Z","tz":"America/New_York","next":"2028-07-03T12:06:00.000000Z"} +{"cron":"54-57/2 23,23 L 3 6","after":"2025-03-30T02:02:14.000000Z","tz":"Europe/Budapest","next":"2025-03-31T21:54:00.000000Z"} +{"cron":"35 0 17,24 OCT 0,5","after":"2026-04-30T22:33:04.000000Z","tz":"America/St_Johns","next":"2026-10-02T03:05:00.000000Z"} +{"cron":"*/30 16,15 11-20/6 9-10 *","after":"2026-11-01T03:25:49.000000Z","tz":"America/Havana","next":"2027-09-11T20:00:00.000000Z"} +{"cron":"16 0-3/3 * SEP ?","after":"2027-03-28T00:40:51.000000Z","tz":"Europe/Dublin","next":"2027-08-31T23:16:00.000000Z"} +{"cron":"45 7-21/2 16,18 2-7/6 MON","after":"2025-11-30T01:43:51.000000Z","tz":"Europe/Dublin","next":"2026-02-02T07:45:00.000000Z"} +{"cron":"36-59/3 22-23 */25 NOV 2","after":"2024-04-06T13:08:17.000000Z","tz":"Pacific/Chatham","next":"2024-11-01T08:51:00.000000Z"} +{"cron":"56 */4 24-25/5 1-7/5 5,1","after":"2026-12-31T23:58:15.500000Z","tz":"Pacific/Chatham","next":"2027-01-01T03:11:00.000000Z"} +{"cron":"14 6-14/4 ? 3-10/3 0L","after":"2025-08-10T17:58:45.500000Z","tz":"Asia/Kolkata","next":"2025-09-28T00:44:00.000000Z"} +{"cron":"58 8-19/3 * 7 0","after":"2024-10-27T02:05:46.000000Z","tz":"Europe/Budapest","next":"2025-07-06T06:58:00.000000Z"} +{"cron":"11-12/3 * ? * */5","after":"2026-05-01T00:33:50.000000Z","tz":"America/St_Johns","next":"2026-05-01T02:41:00.000000Z"} +{"cron":"@DAILY","after":"2026-10-24T22:30:20.500000Z","tz":"Asia/Beirut","next":"2026-10-25T22:00:00.000000Z"} +{"cron":"* */22 */32 7-7 7#4","after":"2024-11-03T02:46:30.500000Z","tz":"America/St_Johns","next":"2025-07-02T02:30:00.000000Z"} +{"cron":"15-59/7 9-19/5 ? 12-12/2 ?","after":"2028-06-28T16:08:41.000000Z","tz":"UTC","error":"InvalidArgumentException"} +{"cron":"17-28 7 L 1,1 1,3","after":"2027-09-30T15:03:26.000000Z","tz":"Pacific/Apia","next":"2028-01-02T18:17:00.000000Z"} +{"cron":"26-54/2 * 30,14 10-11/3 */4","after":"2026-04-30T22:31:10.000000Z","tz":"UTC","next":"2026-10-01T00:26:00.000000Z"} +{"cron":"41-55/1 15 22-29 * 7-7/2","after":"2027-10-30T19:25:18.000000Z","tz":"Asia/Beirut","next":"2027-11-02T13:41:00.000000Z"} +{"cron":"@yearly","after":"2028-02-29T21:15:07.000000Z","tz":"Pacific/Chatham","next":"2028-12-31T10:15:00.000000Z"} +{"cron":"24-47 9-22/3 14,16 7,10 0-6","after":"2027-03-01T00:37:29.000000Z","tz":"Australia/Lord_Howe","next":"2027-06-30T22:54:00.000000Z"} +{"cron":"* 20-21 ? NOV 5-7/1","after":"2026-12-31T22:13:11.000000Z","tz":"Europe/Dublin","next":"2027-11-05T20:00:00.000000Z"} +{"cron":"* */20 7-13/7 * 7","after":"2028-03-20T11:15:34.000000Z","tz":"America/Santiago","next":"2028-03-26T03:00:00.000000Z"} +{"cron":"0-2/5 * 28W 6-11/1 4#4","after":"2024-04-06T14:26:29.000000Z","tz":"Pacific/Chatham","next":"2024-06-26T11:15:00.000000Z"} +{"cron":"12,43 * 6 5-8/5 6-6/3","after":"2028-02-29T22:04:55.000000Z","tz":"Australia/Lord_Howe","next":"2028-05-05T13:42:00.000000Z"} +{"cron":"12 9-9 25 10-12 7#5","after":"2026-12-31T22:27:22.000000Z","tz":"Europe/Budapest","next":"2027-10-25T07:12:00.000000Z"} +{"cron":"24 * ? 12-12/7 *","after":"2026-01-31T21:41:21.500000Z","tz":"America/New_York","next":"2026-12-01T05:24:00.000000Z"} +{"cron":"26 * 26-30 5 */1","after":"2026-09-26T15:19:25.000000Z","tz":"Pacific/Chatham","next":"2027-04-30T11:41:00.000000Z"} +{"cron":"42 14,22 L 12-12 2-4/3","after":"2026-04-30T23:26:21.000000Z","tz":"Asia/Beirut","next":"2026-12-01T12:42:00.000000Z"} +{"cron":"21-36 15-18/5 18W * ?","after":"2028-03-25T23:25:47.000000Z","tz":"Antarctica/Troll","next":"2028-04-18T13:21:00.000000Z"} +{"cron":"32 */7 12 12 5-5","after":"2026-10-25T00:50:12.500000Z","tz":"Europe/London","next":"2026-12-04T00:32:00.000000Z"} +{"cron":"41-41 17 6,20 AUG ?","after":"2025-10-25T19:13:04.000000Z","tz":"Asia/Beirut","next":"2026-08-06T14:41:00.000000Z"} +{"cron":"* 13 29W */13 1L","after":"2026-10-25T02:37:34.000000Z","tz":"Europe/Budapest","next":"2027-02-01T12:00:00.000000Z"} +{"cron":"26-35 23-23/3 L * 4,5","after":"2026-10-25T01:50:36.000000Z","tz":"Europe/Dublin","next":"2026-10-29T23:26:00.000000Z"} +{"cron":"53 * ? NOV 7-7/7","after":"2026-04-05T01:07:18.500000Z","tz":"America/Santiago","next":"2026-11-01T03:53:00.000000Z"} +{"cron":"35,51 * L * 1#1","after":"2027-04-01T04:21:24.000000Z","tz":"Pacific/Apia","next":"2027-04-04T11:35:00.000000Z"} +{"cron":"*/16 19 */10 3,9 5#3","after":"2026-10-25T01:44:53.000000Z","tz":"Europe/Budapest","next":"2027-03-01T18:00:00.000000Z"} +{"cron":"*/41 15 20-26/2 8-9/4 ?","after":"2028-02-28T23:19:35.500000Z","tz":"Europe/London","next":"2028-08-20T14:00:00.000000Z"} +{"cron":"* 11-13/3 * 11-11/6 1-6","after":"2027-02-26T11:41:29.000000Z","tz":"Europe/Budapest","next":"2027-11-01T10:00:00.000000Z"} +{"cron":"*/57 8 20 5 4","after":"2028-02-28T23:29:13.000000Z","tz":"Antarctica/Troll","next":"2028-05-04T06:00:00.000000Z"} +{"cron":"1 * ? 1,1 1,4","after":"2027-06-03T18:10:55.000000Z","tz":"Antarctica/Troll","next":"2028-01-03T00:01:00.000000Z"} +{"cron":"24-55/3 15-15/1 L */13 4","after":"2027-11-04T17:19:30.000000Z","tz":"Asia/Kolkata","next":"2028-02-03T09:54:00.000000Z"} +{"cron":"* 21 17W */2 *","after":"2026-12-31T22:12:46.000000Z","tz":"Europe/Budapest","next":"2027-01-18T20:00:00.000000Z"} +{"cron":"*/2 9-14/6 28,7 * 0-5/3","after":"2025-03-09T08:15:35.000000Z","tz":"America/New_York","next":"2025-03-09T13:00:00.000000Z"} +{"cron":"30,49 */13 L */5 2-7/3","after":"2025-04-10T18:32:40.000000Z","tz":"Asia/Tehran","next":"2025-06-02T21:00:00.000000Z"} +{"cron":"57-59/4 10-15 L 4-6/3 */8","after":"2025-11-20T23:09:11.000000Z","tz":"UTC","next":"2026-04-05T10:57:00.000000Z"} +{"cron":"37,17 12-19/7 */15 NOV 3,0","after":"2028-08-05T02:52:13.000000Z","tz":"America/Sao_Paulo","next":"2028-11-01T15:17:00.000000Z"} +{"cron":"* * 2-5 */12 2L","after":"2026-11-01T04:38:09.000000Z","tz":"America/New_York","next":"2027-01-02T05:00:00.000000Z"} +{"cron":"*/30 12,7 27 7-11 */3","after":"2028-07-14T08:47:29.000000Z","tz":"Europe/Dublin","next":"2028-07-15T11:00:00.000000Z"} +{"cron":"48 1-19 13-30/7 8-9/4 6-7/6","after":"2028-03-01T00:29:54.500000Z","tz":"America/Havana","next":"2028-08-05T05:48:00.000000Z"} +{"cron":"*/2 2-2/1 11 6-11 7,1","after":"2026-11-05T16:38:37.000000Z","tz":"America/New_York","next":"2026-11-08T07:00:00.000000Z"} +{"cron":"14-19/4 18,6 */32 JUN 0#4","after":"2028-02-28T23:07:38.000000Z","tz":"Asia/Tehran","next":"2028-06-02T14:44:00.000000Z"} +{"cron":"52-54/1 20,6 27 3 FRI","after":"2025-10-25T23:38:04.500000Z","tz":"Europe/Dublin","next":"2026-03-06T20:52:00.000000Z"} +{"cron":"14 17-17/1 16 3-9/3 0-6","after":"2026-12-31T21:28:21.500000Z","tz":"Pacific/Chatham","next":"2027-03-01T03:29:00.000000Z"} +{"cron":"22 */13 14,12 8,10 */6","after":"2025-12-24T08:24:14.000000Z","tz":"Asia/Kolkata","next":"2026-07-31T18:52:00.000000Z"} +{"cron":"28-49/7 3-21/5 * OCT 6,1","after":"2026-12-31T21:54:16.500000Z","tz":"Australia/Lord_Howe","next":"2027-10-01T16:58:00.000000Z"} +{"cron":"51 22-23/7 */15 12-12/4 5-5/6","after":"2027-01-22T07:12:28.000000Z","tz":"Asia/Kolkata","next":"2027-12-01T17:21:00.000000Z"} +{"cron":"5,6 * 1-3 * *","after":"2027-03-14T05:31:41.000000Z","tz":"America/Havana","next":"2027-04-01T04:05:00.000000Z"} +{"cron":"19-20 20,9 L * */2","after":"2025-09-23T05:01:51.500000Z","tz":"Pacific/Apia","next":"2025-09-23T07:19:00.000000Z"} +{"cron":"*/23 */6 21-23 6-6/3 7","after":"2028-03-12T04:22:10.000000Z","tz":"America/Havana","next":"2028-06-04T04:00:00.000000Z"} +{"cron":"45-49 5,1 */1 */6 4L","after":"2025-11-04T22:27:37.000000Z","tz":"America/Sao_Paulo","next":"2026-01-01T08:45:00.000000Z"} +{"cron":"40-58 21 13-18/4 AUG 3L","after":"2027-10-31T00:50:01.500000Z","tz":"Antarctica/Troll","next":"2028-08-13T19:40:00.000000Z"} +{"cron":"@annually","after":"2027-01-01T00:18:40.500000Z","tz":"Asia/Beirut","next":"2027-12-31T22:00:00.000000Z"} +{"cron":"6 2-2 20-24 2-2 7","after":"2027-11-07T05:38:32.000000Z","tz":"America/New_York","next":"2028-02-06T07:06:00.000000Z"} +{"cron":"26,38 * 11-21/2 3,10 3,3","after":"2026-03-08T05:43:23.500000Z","tz":"America/Havana","next":"2026-03-11T04:26:00.000000Z"} +{"cron":"54 0 * 12-12 2","after":"2026-05-07T10:29:47.000000Z","tz":"Pacific/Chatham","next":"2026-11-30T11:09:00.000000Z"} +{"cron":"* 23 25-26/3 12 ?","after":"2027-03-01T22:53:04.000000Z","tz":"Asia/Kolkata","next":"2027-12-25T17:30:00.000000Z"} +{"cron":"7,37 19-22/1 20 * 0-7/3","after":"2028-10-29T02:25:57.000000Z","tz":"Antarctica/Troll","next":"2028-11-01T19:07:00.000000Z"} +{"cron":"40-49/1 2,21 L 6 ?","after":"2027-01-01T00:59:04.000000Z","tz":"Antarctica/Troll","next":"2027-06-30T00:40:00.000000Z"} +{"cron":"*/51 * 1W 5 *","after":"2026-09-06T02:56:51.000000Z","tz":"America/Santiago","next":"2027-05-03T04:00:00.000000Z"} +{"cron":"22 * 17-24/2 */6 3,3","after":"2028-11-05T06:48:02.000000Z","tz":"America/New_York","next":"2029-01-03T05:22:00.000000Z"} +{"cron":"53-59/5 22-22/6 10 * *","after":"2027-02-14T05:22:36.500000Z","tz":"Pacific/Chatham","next":"2027-03-10T09:08:00.000000Z"} +{"cron":"1 * 18W 11-11/2 */3","after":"2027-09-01T12:50:08.000000Z","tz":"UTC","next":"2027-11-03T00:01:00.000000Z"} +{"cron":"* * 12,31 */11 ?","after":"2028-06-25T05:41:26.000000Z","tz":"Asia/Kolkata","next":"2028-12-11T18:30:00.000000Z"} +{"cron":"41-53 5,17 19-27/2 * 1L","after":"2026-03-29T02:00:40.000000Z","tz":"Antarctica/Troll","next":"2026-03-30T03:41:00.000000Z"} +{"cron":"* 21-21/2 */11 */12 6","after":"2025-03-30T01:38:12.000000Z","tz":"Antarctica/Troll","next":"2026-01-01T21:00:00.000000Z"} +{"cron":"7,41 5 L NOV SAT","after":"2028-08-13T02:39:38.000000Z","tz":"Europe/London","next":"2028-11-04T05:07:00.000000Z"} +{"cron":"48 11 18 1-9 5#3","after":"2026-06-11T02:33:13.000000Z","tz":"America/Sao_Paulo","next":"2026-06-18T14:48:00.000000Z"} +{"cron":"39-50 */15 2-23/7 JUN 7-7/2","after":"2025-04-24T03:01:03.000000Z","tz":"America/New_York","next":"2025-06-02T04:39:00.000000Z"} +{"cron":"* 8,13 3 7-10 5","after":"2027-01-31T10:00:06.000000Z","tz":"Europe/London","next":"2027-07-02T07:00:00.000000Z"} +{"cron":"20-30/2 22 27W 9,6 */2","after":"2027-02-28T22:42:01.000000Z","tz":"Australia/Lord_Howe","next":"2027-06-01T11:50:00.000000Z"} +{"cron":"1-51 1-18 21 */6 ?","after":"2025-04-06T02:32:21.000000Z","tz":"America/Santiago","next":"2025-07-21T05:01:00.000000Z"} +{"cron":"11-12/6 3-7/2 L */14 */1","after":"2025-10-26T00:01:18.000000Z","tz":"Asia/Tehran","next":"2026-02-28T23:41:00.000000Z"} +{"cron":"@monthly","after":"2024-09-28T13:09:10.000000Z","tz":"Pacific/Chatham","next":"2024-09-30T10:15:00.000000Z"} +{"cron":"1-30/5 12 ? 8-8 3-6","after":"2026-12-31T23:29:06.000000Z","tz":"Antarctica/Troll","next":"2027-08-04T10:01:00.000000Z"} +{"cron":"*/57 10,17 20,13 */2 MON","after":"2027-02-28T22:47:23.000000Z","tz":"Asia/Beirut","next":"2027-03-01T08:00:00.000000Z"} +{"cron":"32,14 4 16-29 5-10 */7","after":"2028-03-12T05:02:16.000000Z","tz":"America/St_Johns","next":"2028-05-07T06:44:00.000000Z"} +{"cron":"39-51 4-7 23W */1 ?","after":"2026-10-05T06:12:13.000000Z","tz":"America/New_York","next":"2026-10-23T08:39:00.000000Z"} +{"cron":"*/29 * 5W 2 2","after":"2025-09-23T11:45:47.500000Z","tz":"Asia/Kolkata","next":"2026-02-02T18:30:00.000000Z"} +{"cron":"12 16 24-29 11 4","after":"2026-04-30T22:56:16.000000Z","tz":"Asia/Beirut","next":"2026-11-05T14:12:00.000000Z"} +{"cron":"@monthly","after":"2027-03-26T01:39:15.000000Z","tz":"America/Sao_Paulo","next":"2027-04-01T03:00:00.000000Z"} +{"cron":"34 9,2 15-29/6 */11 5-6","after":"2027-03-01T00:22:26.000000Z","tz":"America/Sao_Paulo","next":"2027-12-03T12:34:00.000000Z"} +{"cron":"*/8 * L 8,11 *","after":"2025-03-30T00:27:34.000000Z","tz":"Antarctica/Troll","next":"2025-08-30T22:00:00.000000Z"} +{"cron":"40 1-22/5 17 * 2","after":"2028-12-03T17:25:47.500000Z","tz":"Europe/Dublin","next":"2028-12-05T01:40:00.000000Z"} +{"cron":"*/14 21 25,31 */3 */2","after":"2028-11-06T09:55:38.000000Z","tz":"Pacific/Apia","next":"2029-01-02T08:00:00.000000Z"} +{"cron":"* */1 2-26/6 */4 */1","after":"2028-02-29T22:24:25.000000Z","tz":"Antarctica/Troll","next":"2028-04-30T22:00:00.000000Z"} +{"cron":"*/43 3 12-28 3-9 ?","after":"2026-04-25T15:52:03.000000Z","tz":"Australia/Lord_Howe","next":"2026-04-25T16:30:00.000000Z"} +{"cron":"7-26/3 3-15/2 7 AUG ?","after":"2025-04-05T12:02:28.000000Z","tz":"Pacific/Chatham","next":"2025-08-06T14:22:00.000000Z"} +{"cron":"49 21-21 14 FEB 3","after":"2028-03-26T02:59:15.000000Z","tz":"Europe/Budapest","next":"2029-02-07T20:49:00.000000Z"} +{"cron":"51,33 15,5 5,20 10 7-7","after":"2028-11-05T12:05:36.000000Z","tz":"Pacific/Apia","next":"2029-10-01T02:33:00.000000Z"} +{"cron":"56 * 9 */10 7#5","after":"2027-11-07T05:40:23.000000Z","tz":"America/Havana","next":"2027-11-09T05:56:00.000000Z"} +{"cron":"* 21 13-26/4 5,12 7","after":"2025-11-02T06:36:11.000000Z","tz":"America/New_York","next":"2025-12-08T02:00:00.000000Z"} +{"cron":"@annually","after":"2025-04-06T02:29:50.000000Z","tz":"America/Santiago","next":"2026-01-01T03:00:00.000000Z"} +{"cron":"30 1-16/7 22 */2 1-1/1","after":"2027-07-01T22:11:17.000000Z","tz":"America/Santiago","next":"2027-07-05T05:30:00.000000Z"} +{"cron":"17 4 14 7-7/1 2","after":"2026-11-27T02:11:14.000000Z","tz":"Asia/Beirut","next":"2027-07-06T01:17:00.000000Z"} +{"cron":"1-1 */9 24 */7 */7","after":"2028-02-29T23:48:39.500000Z","tz":"Asia/Tehran","next":"2028-08-05T20:31:00.000000Z"} +{"cron":"16,23 8-20 1-29 2,3 */4","after":"2027-02-28T23:43:42.000000Z","tz":"Europe/Budapest","next":"2027-03-01T07:16:00.000000Z"} +{"cron":"45 9 6 FEB 2-5/1","after":"2026-12-31T22:37:41.000000Z","tz":"UTC","next":"2027-02-02T09:45:00.000000Z"} +{"cron":"21 22-23/4 * 9-12/5 */5","after":"2025-03-15T06:41:28.000000Z","tz":"UTC","next":"2025-09-05T22:21:00.000000Z"} +{"cron":"54,40 15-15 */21 JAN 3-5","after":"2027-02-28T23:36:27.500000Z","tz":"America/New_York","next":"2028-01-01T20:40:00.000000Z"} +{"cron":"* 17-19 30-30/3 */9 2L","after":"2028-02-29T23:05:07.000000Z","tz":"America/Sao_Paulo","next":"2028-10-30T20:00:00.000000Z"} +{"cron":"*/19 9 27-30/3 9-10 2L","after":"2027-06-11T04:08:38.000000Z","tz":"Asia/Kolkata","next":"2027-09-27T03:30:00.000000Z"} +{"cron":"*/34 0 15 5-8 2,2","after":"2025-01-02T00:59:27.500000Z","tz":"America/St_Johns","next":"2025-05-06T02:30:00.000000Z"} +{"cron":"*/38 16,5 8 FEB 1L","after":"2027-09-29T10:49:48.000000Z","tz":"America/St_Johns","next":"2028-02-08T19:30:00.000000Z"} +{"cron":"19-47/5 * */14 */9 2-7","after":"2027-11-07T04:45:34.000000Z","tz":"America/Havana","next":"2028-01-01T05:19:00.000000Z"} +{"cron":"*/60 */4 27,22 4,6 3,7","after":"2028-09-03T08:05:12.000000Z","tz":"America/Sao_Paulo","next":"2029-04-01T03:00:00.000000Z"} +{"cron":"3-11 23 */19 */1 *","after":"2027-11-07T05:56:25.500000Z","tz":"America/New_York","next":"2027-11-21T04:03:00.000000Z"} +{"cron":"58 * */31 11 */4","after":"2027-09-25T12:41:05.000000Z","tz":"Pacific/Chatham","next":"2027-10-31T11:13:00.000000Z"} +{"cron":"*/15 * 15 11,9 *","after":"2026-01-06T08:09:38.500000Z","tz":"Antarctica/Troll","next":"2026-09-14T22:00:00.000000Z"} +{"cron":"24 1,5 */2 */11 1,5","after":"2026-04-05T01:13:31.000000Z","tz":"America/Santiago","next":"2026-12-01T04:24:00.000000Z"} +{"cron":"16-54/3 13-17 20 8-8/5 0","after":"2026-05-22T20:27:45.500000Z","tz":"UTC","next":"2026-08-02T13:16:00.000000Z"} +{"cron":"51 10-17 1 9-12 ?","after":"2028-05-02T14:04:09.000000Z","tz":"Asia/Beirut","next":"2028-09-01T07:51:00.000000Z"} +{"cron":"24,45 10 L 6 SUN","after":"2026-11-29T07:11:38.500000Z","tz":"Asia/Beirut","next":"2027-06-06T07:24:00.000000Z"} +{"cron":"*/21 13,21 13,3 */1 ?","after":"2026-05-11T01:04:41.500000Z","tz":"Europe/Dublin","next":"2026-05-13T12:00:00.000000Z"} +{"cron":"54-58/2 0,22 15 3 0-0","after":"2027-01-01T00:31:42.000000Z","tz":"Antarctica/Troll","next":"2027-03-01T00:54:00.000000Z"} +{"cron":"* 22-22 L 4-12 ?","after":"2027-03-10T00:03:01.000000Z","tz":"Europe/London","next":"2027-04-30T21:00:00.000000Z"} +{"cron":"*/9 2-9 ? FEB 4#2","after":"2028-07-12T07:29:08.000000Z","tz":"America/New_York","next":"2029-02-08T07:00:00.000000Z"} +{"cron":"* 18,10 */27 7,3 2L","after":"2025-09-27T12:14:43.000000Z","tz":"Pacific/Chatham","next":"2026-03-01T04:15:00.000000Z"} +{"cron":"39-51 2 25 JUL 0","after":"2027-10-31T00:26:11.000000Z","tz":"Europe/London","next":"2028-07-02T01:39:00.000000Z"} +{"cron":"* */13 L */2 *","after":"2028-10-29T00:40:39.000000Z","tz":"Europe/London","next":"2028-11-30T00:00:00.000000Z"} +{"cron":"55 * 20,13 5-8/2 3#2","after":"2026-04-30T22:13:56.500000Z","tz":"Asia/Tehran","next":"2026-05-12T21:25:00.000000Z"} +{"cron":"37 * 20,16 */12 2-7/6","after":"2028-10-16T10:21:33.000000Z","tz":"Australia/Lord_Howe","next":"2029-01-01T13:37:00.000000Z"} +{"cron":"55,31 */23 */24 OCT TUE","after":"2025-01-24T00:54:34.000000Z","tz":"UTC","next":"2025-10-01T00:31:00.000000Z"} +{"cron":"18 * 3,7 5-6/1 4L","after":"2026-01-31T23:48:38.000000Z","tz":"UTC","next":"2026-05-03T00:18:00.000000Z"} +{"cron":"37 19,14 18-31 MAR ?","after":"2028-02-19T10:39:46.500000Z","tz":"Antarctica/Troll","next":"2028-03-18T19:37:00.000000Z"} +{"cron":"* 10-13/7 */5 JUN 7-7/6","after":"2025-07-27T05:35:47.000000Z","tz":"Asia/Tehran","next":"2026-06-01T06:30:00.000000Z"} +{"cron":"59,14 3,20 1 5 *","after":"2026-04-30T21:27:09.000000Z","tz":"Pacific/Chatham","next":"2026-05-01T07:29:00.000000Z"} +{"cron":"26-39 15-22 2W */8 7L","after":"2026-07-11T19:58:26.500000Z","tz":"UTC","next":"2026-09-02T15:26:00.000000Z"} +{"cron":"11 */19 5-13/1 3 0","after":"2024-10-27T02:13:08.500000Z","tz":"Antarctica/Troll","next":"2025-03-02T00:11:00.000000Z"} +{"cron":"* 10-14 18 7-9/4 4L","after":"2025-07-14T12:39:41.000000Z","tz":"Pacific/Apia","next":"2025-07-17T21:00:00.000000Z"} +{"cron":"*/30 */21 15-17/2 5,6 */9","after":"2028-06-17T03:08:29.500000Z","tz":"Asia/Beirut","next":"2028-06-17T18:00:00.000000Z"} +{"cron":"30-58/3 12 */8 * 5#3","after":"2026-10-25T02:27:42.500000Z","tz":"Europe/London","next":"2026-10-25T12:30:00.000000Z"} +{"cron":"*/41 3 21W 2-7 ?","after":"2026-07-01T20:06:38.000000Z","tz":"UTC","next":"2026-07-21T03:00:00.000000Z"} +{"cron":"43 10-19 ? */9 *","after":"2027-07-31T11:42:01.000000Z","tz":"Asia/Kolkata","next":"2027-10-01T05:13:00.000000Z"} +{"cron":"13 9 * * 1-1/2","after":"2026-05-01T00:56:43.000000Z","tz":"America/New_York","next":"2026-05-04T13:13:00.000000Z"} +{"cron":"31-32/2 19 L * 5#4","after":"2024-10-27T00:41:55.500000Z","tz":"Europe/Dublin","next":"2024-10-31T19:31:00.000000Z"} +{"cron":"46 4,8 */12 SEP ?","after":"2027-10-09T05:19:01.000000Z","tz":"Australia/Lord_Howe","next":"2028-08-31T18:16:00.000000Z"} +{"cron":"19 * */12 */5 3-4","after":"2024-03-30T22:30:11.000000Z","tz":"Asia/Beirut","next":"2024-05-31T21:19:00.000000Z"} +{"cron":"3,52 */2 */32 */1 *","after":"2024-03-30T20:38:23.000000Z","tz":"Asia/Beirut","next":"2024-04-01T21:03:00.000000Z"} +{"cron":"44 */22 17-24/5 10-11 2-4/1","after":"2025-03-09T07:05:49.000000Z","tz":"America/New_York","next":"2025-10-01T04:44:00.000000Z"} +{"cron":"50-54 * 3,15 1-6/7 0L","after":"2028-03-26T02:07:40.500000Z","tz":"Antarctica/Troll","next":"2029-01-03T00:50:00.000000Z"} +{"cron":"* 15-20 9W 6-10 *","after":"2027-02-28T23:31:36.000000Z","tz":"Australia/Lord_Howe","next":"2027-06-09T04:30:00.000000Z"} +{"cron":"5,26 */7 13-24 * 6-6","after":"2025-03-30T01:13:53.000000Z","tz":"Antarctica/Troll","next":"2025-04-04T22:05:00.000000Z"} +{"cron":"14 22-22 23-25 8-12 7","after":"2025-09-13T15:03:56.500000Z","tz":"Europe/Dublin","next":"2025-09-14T21:14:00.000000Z"} +{"cron":"* * 28 MAR ?","after":"2027-05-31T15:48:37.500000Z","tz":"Asia/Tehran","next":"2028-03-27T20:30:00.000000Z"} +{"cron":"34,21 * 17,14 9-9 4L","after":"2028-02-29T23:57:58.000000Z","tz":"Europe/Dublin","next":"2028-09-13T23:21:00.000000Z"} +{"cron":"54,10 9 L JUL 5","after":"2027-07-11T13:32:21.500000Z","tz":"America/Sao_Paulo","next":"2027-07-16T12:10:00.000000Z"} +{"cron":"36-47/5 16,14 3W 1 *","after":"2028-11-24T04:20:09.500000Z","tz":"Europe/Budapest","next":"2029-01-03T15:36:00.000000Z"} +{"cron":"53-53/2 14 20,29 JUN 6","after":"2028-03-12T04:22:22.000000Z","tz":"America/St_Johns","next":"2028-06-03T17:23:00.000000Z"} +{"cron":"*/46 11-18/1 21,27 * *","after":"2027-10-31T01:30:58.000000Z","tz":"Antarctica/Troll","next":"2027-11-21T11:00:00.000000Z"} +{"cron":"* 23 12 6-6 4-4","after":"2026-05-12T03:12:28.000000Z","tz":"Europe/Budapest","next":"2026-06-04T21:00:00.000000Z"} +{"cron":"@monthly","after":"2028-06-12T03:35:51.500000Z","tz":"UTC","next":"2028-07-01T00:00:00.000000Z"} +{"cron":"25 11-17/3 ? 1-7/7 */3","after":"2025-05-26T10:01:17.000000Z","tz":"Europe/London","next":"2026-01-03T11:25:00.000000Z"} +{"cron":"* */20 20-27 * 4","after":"2026-04-30T23:48:12.500000Z","tz":"Europe/Dublin","next":"2026-05-06T23:00:00.000000Z"} +{"cron":"55,18 3,8 * */11 6-7/6","after":"2027-03-27T23:37:16.000000Z","tz":"Antarctica/Troll","next":"2027-12-04T03:18:00.000000Z"} +{"cron":"* 9 18 */9 7-7/5","after":"2028-10-29T02:47:05.000000Z","tz":"Europe/Budapest","next":"2029-01-05T08:00:00.000000Z"} +{"cron":"*/8 23-23/7 11 SEP *","after":"2028-02-02T03:08:05.500000Z","tz":"Australia/Lord_Howe","next":"2028-09-11T12:30:00.000000Z"} +{"cron":"7 23 L 8-12/1 1#3","after":"2026-12-31T22:09:46.000000Z","tz":"Europe/Dublin","next":"2026-12-31T23:07:00.000000Z"} +{"cron":"28 8 18 */4 ?","after":"2026-10-22T12:07:23.500000Z","tz":"America/Sao_Paulo","next":"2027-01-18T11:28:00.000000Z"} +{"cron":"31 * 8W 12 */7","after":"2025-10-10T19:07:23.000000Z","tz":"Asia/Kolkata","next":"2025-12-06T19:01:00.000000Z"} +{"cron":"52 * 20-23/6 AUG 0L","after":"2028-11-05T04:48:11.000000Z","tz":"America/New_York","next":"2029-08-20T04:52:00.000000Z"} +{"cron":"46-51/4 * 27,15 12-12/6 7","after":"2025-11-19T10:34:30.000000Z","tz":"Europe/London","next":"2025-12-07T00:46:00.000000Z"} +{"cron":"* * */14 6 WED","after":"2027-03-01T00:47:11.000000Z","tz":"Europe/London","next":"2027-05-31T23:00:00.000000Z"} +{"cron":"26 20,3 6-9/3 DEC ?","after":"2024-04-06T13:39:30.000000Z","tz":"Australia/Lord_Howe","next":"2024-12-06T09:26:00.000000Z"} +{"cron":"22,39 8 L NOV 7#3","after":"2025-04-06T04:30:02.000000Z","tz":"America/Santiago","next":"2025-11-16T11:22:00.000000Z"} +{"cron":"54-57/3 * 17 * 7L","after":"2027-11-23T01:14:59.000000Z","tz":"America/New_York","next":"2027-11-28T05:54:00.000000Z"} +{"cron":"37 2-2 13-21 11-11/7 2-4/7","after":"2028-03-01T00:03:52.000000Z","tz":"Asia/Kolkata","next":"2028-11-06T21:07:00.000000Z"} +{"cron":"4,5 */13 20W * 3-5/6","after":"2028-06-07T01:00:08.000000Z","tz":"Pacific/Apia","next":"2028-06-13T11:04:00.000000Z"} +{"cron":"7 6 24-30/4 11-12 5-6/1","after":"2024-03-31T01:30:32.500000Z","tz":"Antarctica/Troll","next":"2024-11-01T06:07:00.000000Z"} +{"cron":"* 2,2 13-29 * 6#1","after":"2027-05-30T12:29:04.000000Z","tz":"Pacific/Chatham","next":"2027-06-04T13:15:00.000000Z"} +{"cron":"54 * 10W OCT 7#5","after":"2028-01-24T17:21:44.500000Z","tz":"America/Santiago","next":"2028-10-10T03:54:00.000000Z"} +{"cron":"2 */23 24 2-2 6-6/4","after":"2027-02-28T23:27:14.500000Z","tz":"Europe/Budapest","next":"2028-02-04T23:02:00.000000Z"} +{"cron":"40 0 24 12-12 2-4/3","after":"2028-02-29T00:31:31.500000Z","tz":"Europe/Dublin","next":"2028-12-05T00:40:00.000000Z"} +{"cron":"0-26/6 19-23 1,24 2-4/6 4-5","after":"2026-09-06T04:28:21.000000Z","tz":"America/Santiago","next":"2027-02-01T22:00:00.000000Z"} +{"cron":"* 2 * 4-11 4#1","after":"2025-12-19T08:16:08.500000Z","tz":"America/Sao_Paulo","next":"2026-04-02T05:00:00.000000Z"} +{"cron":"39,37 * 19-26/2 */7 *","after":"2028-04-25T06:44:51.000000Z","tz":"Asia/Kolkata","next":"2028-08-18T19:07:00.000000Z"} +{"cron":"8-33 * 30W */10 5-7/2","after":"2027-11-07T03:34:15.000000Z","tz":"America/Havana","next":"2027-11-07T05:08:00.000000Z"} +{"cron":"0-52/7 12-18 31-31/4 6 0,0","after":"2025-04-05T15:08:35.000000Z","tz":"Australia/Lord_Howe","next":"2025-06-01T01:30:00.000000Z"} +{"cron":"@hourly","after":"2028-07-27T20:26:03.000000Z","tz":"America/Sao_Paulo","next":"2028-07-27T21:00:00.000000Z"} +{"cron":"9 18,6 17,30 * 0-0","after":"2024-10-27T00:01:33.000000Z","tz":"Antarctica/Troll","next":"2024-10-27T18:09:00.000000Z"} +{"cron":"12 */16 19,19 4-6/7 SUN","after":"2027-10-02T19:51:27.000000Z","tz":"America/St_Johns","next":"2028-04-02T02:42:00.000000Z"} +{"cron":"20-24/1 */18 14-18/4 */14 ?","after":"2027-02-28T21:02:19.500000Z","tz":"America/Santiago","next":"2027-03-14T03:20:00.000000Z"} +{"cron":"20-43/3 23 9 7-12/1 3#3","after":"2028-02-29T23:36:53.500000Z","tz":"Pacific/Apia","next":"2028-07-09T10:20:00.000000Z"} +{"cron":"11 */6 16 4-11 3-3/4","after":"2028-02-28T21:47:43.000000Z","tz":"Europe/Budapest","next":"2028-04-04T22:11:00.000000Z"} +{"cron":"*/58 21 ? * 6,1","after":"2027-03-15T16:08:34.000000Z","tz":"America/Santiago","next":"2027-03-16T00:00:00.000000Z"} +{"cron":"40 15-23/2 * * *","after":"2027-05-10T03:25:51.000000Z","tz":"America/Sao_Paulo","next":"2027-05-10T18:40:00.000000Z"} +{"cron":"32 0 30 10,10 5#2","after":"2028-03-12T05:07:44.500000Z","tz":"America/New_York","next":"2028-10-13T04:32:00.000000Z"} +{"cron":"7-54/6 20,9 L 1 *","after":"2028-03-07T16:45:58.000000Z","tz":"Pacific/Apia","next":"2029-01-31T07:07:00.000000Z"} +{"cron":"14-17 6-11/7 13-18/6 4-8 ?","after":"2028-03-25T23:35:32.000000Z","tz":"Europe/Dublin","next":"2028-04-13T05:14:00.000000Z"} +{"cron":"31,20 13 10-26/4 11 5","after":"2028-03-25T23:00:31.000000Z","tz":"Asia/Beirut","next":"2028-11-03T11:20:00.000000Z"} +{"cron":"22-26/3 * 16W */4 */3","after":"2027-03-01T00:20:48.000000Z","tz":"America/Santiago","next":"2027-05-01T04:22:00.000000Z"} +{"cron":"16 10,7 L 12,7 *","after":"2027-12-21T14:31:00.000000Z","tz":"Pacific/Chatham","next":"2027-12-30T20:31:00.000000Z"} +{"cron":"36 * 19-25/5 * 6","after":"2026-10-03T16:40:10.000000Z","tz":"Australia/Lord_Howe","next":"2026-10-09T13:36:00.000000Z"} +{"cron":"34-58/4 */7 15 * ?","after":"2026-01-31T23:15:17.000000Z","tz":"America/New_York","next":"2026-02-15T05:34:00.000000Z"} +{"cron":"16,11 4,19 20-22 * */1","after":"2028-02-28T22:08:23.000000Z","tz":"America/Sao_Paulo","next":"2028-02-28T22:11:00.000000Z"} +{"cron":"6,24 * 6 2-12/5 2#2","after":"2027-12-22T02:44:57.500000Z","tz":"Asia/Beirut","next":"2028-02-05T22:06:00.000000Z"} +{"cron":"@hourly","after":"2027-02-28T22:21:02.000000Z","tz":"Pacific/Chatham","next":"2027-02-28T23:15:00.000000Z"} +{"cron":"57 */5 15-19/4 4-6 TUE","after":"2025-10-26T01:39:56.000000Z","tz":"Europe/Budapest","next":"2026-04-06T22:57:00.000000Z"} +{"cron":"58 * L 4,1 0-5/5","after":"2027-03-27T21:25:43.000000Z","tz":"Asia/Beirut","next":"2027-04-01T21:58:00.000000Z"} +{"cron":"50-51/7 0 21W 2 *","after":"2027-01-01T00:13:17.000000Z","tz":"America/New_York","next":"2027-02-22T05:50:00.000000Z"} +{"cron":"@midnight","after":"2024-09-28T15:09:09.000000Z","tz":"Pacific/Chatham","next":"2024-09-29T10:15:00.000000Z"} +{"cron":"* 15 24-24 11-12/1 3L","after":"2026-06-28T07:18:00.000000Z","tz":"Asia/Kolkata","next":"2026-11-24T09:30:00.000000Z"} +{"cron":"37 10 5 12 5#1","after":"2028-04-01T14:54:33.000000Z","tz":"Pacific/Chatham","next":"2028-11-30T20:52:00.000000Z"} +{"cron":"46 14-17/5 */23 9 3L","after":"2025-08-08T13:11:16.000000Z","tz":"Asia/Tehran","next":"2025-09-01T11:16:00.000000Z"} +{"cron":"29-59/3 23 12W 7,12 5","after":"2027-05-22T10:51:39.000000Z","tz":"Pacific/Chatham","next":"2027-07-02T10:44:00.000000Z"} +{"cron":"* 0 */15 OCT 4#3","after":"2027-07-27T12:58:17.000000Z","tz":"America/Havana","next":"2027-10-01T04:00:00.000000Z"} +{"cron":"*/50 * 29W 5-5 THU","after":"2027-03-27T23:08:04.000000Z","tz":"Europe/Dublin","next":"2027-05-05T23:00:00.000000Z"} +{"cron":"* 2-6/6 29 4 */4","after":"2028-09-03T04:45:57.000000Z","tz":"America/Santiago","next":"2029-04-01T05:00:00.000000Z"} +{"cron":"*/60 11-18/7 10 */10 TUE","after":"2028-11-05T03:20:42.500000Z","tz":"America/St_Johns","next":"2028-11-07T14:30:00.000000Z"} +{"cron":"45,11 19 5 * 2L","after":"2024-03-30T22:20:59.000000Z","tz":"Asia/Beirut","next":"2024-04-05T16:11:00.000000Z"} +{"cron":"17 7,20 * 12 2-2","after":"2028-03-12T06:59:49.500000Z","tz":"America/St_Johns","next":"2028-12-05T10:47:00.000000Z"} +{"cron":"* */4 L * *","after":"2026-03-07T05:57:42.000000Z","tz":"Australia/Lord_Howe","next":"2026-03-30T13:00:00.000000Z"} +{"cron":"* 7-19 */15 3-12 ?","after":"2027-01-30T19:55:52.000000Z","tz":"Antarctica/Troll","next":"2027-03-01T07:00:00.000000Z"} +{"cron":"34 4,22 7 MAY 3","after":"2025-11-25T01:26:41.000000Z","tz":"America/Sao_Paulo","next":"2026-05-06T07:34:00.000000Z"} +{"cron":"10,57 */12 3-6/5 FEB 7-7/3","after":"2026-04-05T02:18:01.500000Z","tz":"America/Santiago","next":"2027-02-03T03:10:00.000000Z"} +{"cron":"59-59 14,8 24 */5 */2","after":"2028-07-15T14:49:10.000000Z","tz":"Asia/Kolkata","next":"2028-11-02T09:29:00.000000Z"} +{"cron":"5 */13 19,2 1-7 6L","after":"2026-12-31T19:07:02.000000Z","tz":"Asia/Tehran","next":"2027-01-01T20:35:00.000000Z"} +{"cron":"8 */22 2 * 1,6","after":"2027-02-28T23:21:28.000000Z","tz":"Europe/Dublin","next":"2027-03-01T00:08:00.000000Z"} +{"cron":"3-51 16-23 */5 6-12 2-4","after":"2028-02-29T00:23:22.000000Z","tz":"Pacific/Chatham","next":"2028-06-01T03:18:00.000000Z"} +{"cron":"14-46 9 L 12 0,7","after":"2028-10-18T22:25:41.000000Z","tz":"America/Santiago","next":"2028-12-03T12:14:00.000000Z"} +{"cron":"3 */23 5-26 2 2-6","after":"2026-01-04T14:57:38.000000Z","tz":"America/Santiago","next":"2026-02-03T03:03:00.000000Z"} +{"cron":"47 */14 L 7-12/4 ?","after":"2026-04-04T15:11:35.000000Z","tz":"Pacific/Chatham","next":"2026-07-30T12:02:00.000000Z"} +{"cron":"58 * 9 8,9 4","after":"2026-01-31T21:03:27.000000Z","tz":"Antarctica/Troll","next":"2026-08-05T22:58:00.000000Z"} +{"cron":"@midnight","after":"2025-03-30T00:57:08.000000Z","tz":"Europe/London","next":"2025-03-30T23:00:00.000000Z"} +{"cron":"*/2 8 6-23 * 6-6","after":"2025-02-24T20:19:55.500000Z","tz":"Asia/Kolkata","next":"2025-03-01T02:30:00.000000Z"} +{"cron":"*/15 21-21 23-29 3-7/4 2","after":"2026-01-31T22:08:51.000000Z","tz":"Europe/Budapest","next":"2026-03-03T20:00:00.000000Z"} +{"cron":"37-37/7 13,7 ? JAN ?","after":"2025-03-29T20:41:31.000000Z","tz":"Asia/Beirut","error":"InvalidArgumentException"} +{"cron":"51-59 17 */12 8-10/2 SUN","after":"2027-03-28T02:02:19.500000Z","tz":"Europe/Budapest","next":"2027-08-01T15:51:00.000000Z"} +{"cron":"*/45 * 1 * 5#1","after":"2028-03-01T00:24:35.500000Z","tz":"Pacific/Apia","next":"2028-03-01T00:45:00.000000Z"} +{"cron":"* 14-20/7 * 7,11 0#4","after":"2028-01-22T18:26:44.000000Z","tz":"Pacific/Apia","next":"2028-07-23T01:00:00.000000Z"} +{"cron":"22-48 10-10 13,6 MAR MON","after":"2028-04-18T13:19:48.000000Z","tz":"UTC","next":"2029-03-05T10:22:00.000000Z"} +{"cron":"56,8 * 15-23 4 4-4","after":"2026-01-31T21:49:06.000000Z","tz":"Europe/London","next":"2026-04-01T23:08:00.000000Z"} +{"cron":"17,11 5 24-31 3,2 2","after":"2027-08-31T04:40:26.000000Z","tz":"America/Havana","next":"2028-02-01T10:11:00.000000Z"} +{"cron":"48-49/6 */16 8-27 2-5/3 0-1/6","after":"2026-02-01T00:09:56.000000Z","tz":"Asia/Beirut","next":"2026-02-01T14:48:00.000000Z"} +{"cron":"36 0-7/3 25 8,10 *","after":"2027-10-31T01:55:17.500000Z","tz":"Antarctica/Troll","next":"2028-08-24T22:36:00.000000Z"} +{"cron":"21-30/6 */2 */26 8-8 5L","after":"2028-09-23T14:33:25.000000Z","tz":"Pacific/Chatham","next":"2029-07-31T11:36:00.000000Z"} +{"cron":"9,35 20 9 1-10/1 4#3","after":"2026-01-31T23:23:59.000000Z","tz":"Europe/London","next":"2026-02-09T20:09:00.000000Z"} +{"cron":"19 18-19/6 ? 1 ?","after":"2025-07-31T10:50:40.000000Z","tz":"UTC","error":"InvalidArgumentException"} +{"cron":"* */17 ? MAR 0-5","after":"2026-12-31T22:01:48.000000Z","tz":"Asia/Kolkata","next":"2027-02-28T18:30:00.000000Z"} +{"cron":"50-51/1 11,3 25,4 */13 2-7","after":"2027-12-24T02:45:24.000000Z","tz":"UTC","next":"2028-02-01T11:50:00.000000Z"} +{"cron":"19-20/2 */8 24 */7 */1","after":"2024-10-26T23:08:42.000000Z","tz":"Europe/London","next":"2025-01-01T00:19:00.000000Z"} +{"cron":"1,35 8-10 7 JAN 3,6","after":"2027-11-28T19:08:55.000000Z","tz":"UTC","next":"2028-01-01T08:01:00.000000Z"} +{"cron":"33-59/5 20-21 ? 4-9/5 */8","after":"2028-01-13T20:36:28.000000Z","tz":"Asia/Kolkata","next":"2028-04-02T15:03:00.000000Z"} +{"cron":"* 20,1 16,18 4-10 7-7","after":"2027-04-27T19:11:47.000000Z","tz":"UTC","next":"2027-04-27T20:00:00.000000Z"} +{"cron":"*/27 */24 12W 12-12 */9","after":"2026-01-31T21:01:44.500000Z","tz":"America/St_Johns","next":"2026-12-07T03:30:00.000000Z"} +{"cron":"19,50 11 4 5-9/6 THU","after":"2028-02-28T21:36:49.000000Z","tz":"Antarctica/Troll","next":"2028-05-04T09:19:00.000000Z"} +{"cron":"4 10 L 5,3 SUN","after":"2028-10-13T09:36:16.000000Z","tz":"Pacific/Apia","next":"2029-03-03T21:04:00.000000Z"} +{"cron":"58-59/1 21,9 5,10 6 4","after":"2027-01-01T00:25:51.000000Z","tz":"Asia/Tehran","next":"2027-06-03T18:28:00.000000Z"} +{"cron":"* */20 5-26 6-6 ?","after":"2024-03-10T05:18:37.000000Z","tz":"America/New_York","next":"2024-06-05T04:00:00.000000Z"} +{"cron":"32 * 8,25 10 *","after":"2026-12-31T23:35:52.500000Z","tz":"America/New_York","next":"2027-10-08T04:32:00.000000Z"} +{"cron":"* 20 L 8,1 0-2/3","after":"2027-11-07T03:06:53.500000Z","tz":"America/Havana","next":"2028-01-03T01:00:00.000000Z"} +{"cron":"55 * * OCT 5#1","after":"2026-12-31T23:01:28.000000Z","tz":"Pacific/Apia","next":"2027-09-30T11:55:00.000000Z"} +{"cron":"*/48 9 23-29/3 * 7,4","after":"2026-04-05T02:42:32.000000Z","tz":"America/Santiago","next":"2026-04-05T13:00:00.000000Z"} +{"cron":"59-59 20 14 4,1 SUN","after":"2025-10-25T23:56:15.000000Z","tz":"Europe/Dublin","next":"2026-01-04T20:59:00.000000Z"} +{"cron":"10 19,21 5-6/3 2-2 5","after":"2027-11-03T19:08:37.000000Z","tz":"America/New_York","next":"2028-02-05T00:10:00.000000Z"} +{"cron":"55,18 0-17/4 ? 6,9 5","after":"2028-12-03T08:43:37.000000Z","tz":"Pacific/Chatham","next":"2029-05-31T11:33:00.000000Z"} +{"cron":"2 4 * 11-12 3-6/1","after":"2026-01-31T21:25:04.000000Z","tz":"America/Santiago","next":"2026-11-04T07:02:00.000000Z"} +{"cron":"*/61 * 2 11 2#1","after":"2026-01-31T21:04:48.500000Z","tz":"Asia/Kolkata","next":"2026-11-01T18:31:00.000000Z"} +{"cron":"@yearly","after":"2027-03-01T00:57:31.000000Z","tz":"Asia/Tehran","next":"2027-12-31T20:30:00.000000Z"} +{"cron":"*/45 */2 4 */5 2-3/5","after":"2028-02-28T21:46:20.500000Z","tz":"America/Havana","next":"2028-06-04T04:00:00.000000Z"} +{"cron":"30-44 5-11/4 7-14/6 12-12/3 4","after":"2028-10-29T00:06:59.000000Z","tz":"Antarctica/Troll","next":"2028-12-07T05:30:00.000000Z"} +{"cron":"17 */1 19W */6 4-6/1","after":"2027-10-30T23:20:54.000000Z","tz":"Europe/Budapest","next":"2027-12-31T23:17:00.000000Z"} +{"cron":"* 11 20 * 2#4","after":"2026-04-30T23:19:31.000000Z","tz":"Europe/Budapest","next":"2026-05-20T09:00:00.000000Z"} +{"cron":"*/43 0-19 * 12-12/3 ?","after":"2026-12-31T22:13:11.000000Z","tz":"UTC","next":"2027-12-01T00:00:00.000000Z"} +{"cron":"39-39 2-19/5 ? 11-11/5 5#2","after":"2026-12-31T22:22:46.500000Z","tz":"America/Havana","next":"2027-11-12T07:39:00.000000Z"} +{"cron":"* * 12W 10-10 */9","after":"2026-10-24T19:16:59.000000Z","tz":"Asia/Beirut","next":"2026-10-25T22:00:00.000000Z"} +{"cron":"* * 9,6 FEB */4","after":"2027-10-29T10:13:26.000000Z","tz":"Asia/Kolkata","next":"2028-02-02T18:30:00.000000Z"} +{"cron":"38-54/1 */5 5W */3 ?","after":"2028-03-26T01:26:54.000000Z","tz":"Europe/London","next":"2028-04-04T23:38:00.000000Z"} +{"cron":"5-26 2-12 21-29/5 2-2/1 3#3","after":"2028-06-06T08:25:58.000000Z","tz":"UTC","next":"2029-02-21T02:05:00.000000Z"} +{"cron":"*/8 * 10W 5-12 3#5","after":"2026-08-06T16:56:54.000000Z","tz":"UTC","next":"2026-08-10T00:00:00.000000Z"} +{"cron":"41,49 */24 * * ?","after":"2028-02-29T22:22:17.000000Z","tz":"Australia/Lord_Howe","next":"2028-03-01T13:41:00.000000Z"} +{"cron":"*/57 */6 */24 9-9/5 1#1","after":"2028-03-25T23:38:58.000000Z","tz":"Europe/London","next":"2028-08-31T23:00:00.000000Z"} +{"cron":"59 0-16/1 14,28 * *","after":"2025-10-04T17:03:05.500000Z","tz":"Australia/Lord_Howe","next":"2025-10-13T13:59:00.000000Z"} +{"cron":"28 15 18-31 1-9/3 ?","after":"2025-04-04T13:20:16.500000Z","tz":"America/Havana","next":"2025-04-18T19:28:00.000000Z"} +{"cron":"44-44 16 7W * 1L","after":"2025-07-25T20:11:54.000000Z","tz":"America/Sao_Paulo","next":"2025-07-28T19:44:00.000000Z"} +{"cron":"*/57 17,12 2,11 11,4 4L","after":"2025-01-29T23:04:46.000000Z","tz":"Asia/Tehran","next":"2025-04-02T13:30:00.000000Z"} +{"cron":"*/12 0,20 2W 7,11 1-5","after":"2026-04-30T21:28:42.000000Z","tz":"Europe/Dublin","next":"2026-06-30T23:00:00.000000Z"} +{"cron":"57 16-21 19,31 3,12 0#4","after":"2025-05-15T22:04:30.500000Z","tz":"Asia/Tehran","next":"2025-12-19T13:27:00.000000Z"} +{"cron":"* */7 L */5 1#3","after":"2027-04-03T15:33:47.000000Z","tz":"Australia/Lord_Howe","next":"2027-06-20T13:30:00.000000Z"} +{"cron":"39-59/3 */11 17 9-9/7 WED","after":"2028-02-29T22:31:10.500000Z","tz":"America/New_York","next":"2028-09-06T04:39:00.000000Z"} +{"cron":"58 6 19-24/1 10-12/7 */2","after":"2025-03-24T10:24:50.500000Z","tz":"America/New_York","next":"2025-10-02T10:58:00.000000Z"} +{"cron":"* 20 10W 6-9 4#4","after":"2028-09-01T10:05:50.000000Z","tz":"America/Santiago","next":"2028-09-11T23:00:00.000000Z"} +{"cron":"25-46 */20 17W MAR *","after":"2025-09-07T03:26:45.500000Z","tz":"America/Santiago","next":"2026-03-17T03:25:00.000000Z"} +{"cron":"32-34 16-23/5 11,15 8-8/4 FRI","after":"2025-08-11T06:53:19.000000Z","tz":"Asia/Kolkata","next":"2025-08-11T11:02:00.000000Z"} +{"cron":"31 22,2 16 * */3","after":"2026-11-01T04:12:11.000000Z","tz":"America/Havana","next":"2026-11-02T03:31:00.000000Z"} +{"cron":"20 17,17 5-26 4-10/1 6-7/6","after":"2028-01-05T03:23:25.000000Z","tz":"America/St_Johns","next":"2028-04-01T19:50:00.000000Z"} +{"cron":"23 19,1 9 FEB 3L","after":"2027-06-08T16:26:22.000000Z","tz":"UTC","next":"2028-02-09T19:23:00.000000Z"} +{"cron":"48 23-23 */20 * 5,4","after":"2028-10-29T01:49:42.000000Z","tz":"Europe/Dublin","next":"2028-11-01T23:48:00.000000Z"} +{"cron":"28-30/1 12 17-17 4-12 2","after":"2025-07-03T14:14:24.500000Z","tz":"Asia/Tehran","next":"2025-07-08T08:58:00.000000Z"} +{"cron":"*/56 0-1 13,2 JUL */5","after":"2028-11-05T06:58:45.000000Z","tz":"America/New_York","next":"2029-07-01T04:00:00.000000Z"} +{"cron":"*/1 16-22 17-27 * MON","after":"2027-06-10T15:58:13.000000Z","tz":"Asia/Tehran","next":"2027-06-14T12:30:00.000000Z"} +{"cron":"*/61 23-23 15-28 * THU","after":"2028-09-23T15:17:36.500000Z","tz":"Pacific/Chatham","next":"2028-09-24T09:16:00.000000Z"} +{"cron":"*/17 17,15 16,28 */11 */2","after":"2027-05-18T19:07:28.000000Z","tz":"Asia/Kolkata","next":"2027-12-02T11:30:00.000000Z"} +{"cron":"31-54/1 * * JAN 2#3","after":"2024-11-03T05:17:01.000000Z","tz":"America/New_York","next":"2025-01-21T05:31:00.000000Z"} +{"cron":"36 */24 3W 7 3,2","after":"2027-03-28T02:44:11.000000Z","tz":"Europe/Dublin","next":"2027-07-01T23:36:00.000000Z"} +{"cron":"*/9 4 19-31/2 7 7-7/6","after":"2027-08-04T09:55:14.000000Z","tz":"America/Sao_Paulo","next":"2028-07-01T07:00:00.000000Z"} +{"cron":"10 0 7W 12-12/2 *","after":"2025-04-30T07:21:14.000000Z","tz":"Asia/Kolkata","next":"2025-12-07T18:40:00.000000Z"} +{"cron":"* 16 ? 1-1 *","after":"2026-12-31T23:05:47.000000Z","tz":"Europe/London","next":"2027-01-01T16:00:00.000000Z"} +{"cron":"*/56 0 11-30 JUN WED","after":"2028-10-29T00:53:29.000000Z","tz":"Europe/London","next":"2029-06-05T23:00:00.000000Z"} +{"cron":"33-36 7-21/5 ? DEC 3,6","after":"2026-06-21T22:43:36.000000Z","tz":"Europe/Budapest","next":"2026-12-02T06:33:00.000000Z"} +{"cron":"12 10 * */5 0,5","after":"2026-05-01T00:17:24.000000Z","tz":"Europe/London","next":"2026-06-05T09:12:00.000000Z"} +{"cron":"43-58/2 23-23 L 4 7-7/3","after":"2026-08-21T00:02:49.000000Z","tz":"America/New_York","next":"2027-04-04T03:43:00.000000Z"} +{"cron":"44-49 0,1 * * 2,1","after":"2027-02-28T21:40:11.500000Z","tz":"UTC","next":"2027-03-01T00:44:00.000000Z"} +{"cron":"43-56 * 14 10-10 4L","after":"2026-12-31T23:56:03.000000Z","tz":"Pacific/Chatham","next":"2027-10-13T10:58:00.000000Z"} +{"cron":"4,49 * 29 2-6 */9","after":"2025-12-02T03:53:41.000000Z","tz":"Pacific/Apia","next":"2026-02-01T11:04:00.000000Z"} +{"cron":"59,54 * 7-23/1 5 ?","after":"2028-07-25T11:16:20.500000Z","tz":"UTC","next":"2029-05-07T00:54:00.000000Z"} +{"cron":"*/6 20 8-24 1 TUE","after":"2026-03-08T06:25:36.000000Z","tz":"America/New_York","next":"2027-01-06T01:00:00.000000Z"} +{"cron":"@weekly","after":"2028-02-29T21:15:12.000000Z","tz":"America/New_York","next":"2028-03-05T05:00:00.000000Z"} +{"cron":"23 13-17 */23 */3 */7","after":"2028-11-05T03:33:31.500000Z","tz":"America/Havana","next":"2029-01-01T18:23:00.000000Z"} +{"cron":"58 */3 27-30 * 7-7/3","after":"2026-10-27T08:42:06.000000Z","tz":"Asia/Tehran","next":"2026-10-27T09:28:00.000000Z"} +{"cron":"8 23 27 4-7/5 ?","after":"2025-06-22T06:20:42.000000Z","tz":"Asia/Tehran","next":"2026-04-27T19:38:00.000000Z"} +{"cron":"30,8 0-23 ? * 4","after":"2026-05-01T00:51:35.500000Z","tz":"America/Sao_Paulo","next":"2026-05-01T01:08:00.000000Z"} +{"cron":"*/26 10-11/3 13,12 1 7#4","after":"2028-02-19T21:41:40.000000Z","tz":"Pacific/Chatham","next":"2029-01-11T20:15:00.000000Z"} +{"cron":"9-44/1 19 30W */14 1,0","after":"2024-11-03T06:58:23.000000Z","tz":"America/Havana","next":"2025-03-03T00:09:00.000000Z"} +{"cron":"29-43/3 */24 5W 7,12 7,4","after":"2028-02-29T21:51:04.000000Z","tz":"Asia/Kolkata","next":"2028-07-01T18:59:00.000000Z"} +{"cron":"5,51 19 ? * 0","after":"2026-08-20T06:48:41.000000Z","tz":"Pacific/Apia","next":"2026-08-23T06:05:00.000000Z"} +{"cron":"* * ? * */5","after":"2026-04-30T23:58:42.000000Z","tz":"Pacific/Chatham","next":"2026-04-30T23:59:00.000000Z"} +{"cron":"19 23 9-17 5-12/2 2-2/1","after":"2026-01-31T23:06:35.000000Z","tz":"Asia/Beirut","next":"2026-05-05T20:19:00.000000Z"} +{"cron":"* * */1 6 6","after":"2027-02-28T22:32:17.000000Z","tz":"Australia/Lord_Howe","next":"2027-05-31T13:30:00.000000Z"} +{"cron":"* */18 L 8 6","after":"2026-03-29T00:04:31.500000Z","tz":"Europe/Dublin","next":"2026-07-31T23:00:00.000000Z"} +{"cron":"* */20 */9 */4 4-4","after":"2027-12-15T12:12:24.000000Z","tz":"America/Sao_Paulo","next":"2028-01-01T03:00:00.000000Z"} +{"cron":"9 */9 L 3 5-5","after":"2026-10-24T19:25:10.000000Z","tz":"Asia/Beirut","next":"2027-03-04T22:09:00.000000Z"} +{"cron":"58-59/4 22,13 * 4,7 *","after":"2027-10-30T19:28:36.000000Z","tz":"Asia/Beirut","next":"2028-04-01T19:58:00.000000Z"} +{"cron":"4,5 3 16W 1,11 *","after":"2026-09-30T10:11:03.000000Z","tz":"UTC","next":"2026-11-16T03:04:00.000000Z"} +{"cron":"10,10 0 27 APR 5L","after":"2026-04-30T22:34:29.500000Z","tz":"Asia/Beirut","next":"2027-04-26T21:10:00.000000Z"} +{"cron":"@midnight","after":"2028-03-26T00:22:25.000000Z","tz":"Europe/London","next":"2028-03-26T23:00:00.000000Z"} +{"cron":"0-24/4 21-22/4 17-20/7 */10 2,1","after":"2027-07-03T16:06:45.000000Z","tz":"America/Sao_Paulo","next":"2027-11-02T00:00:00.000000Z"} +{"cron":"* 0-3/2 6-16 DEC 6,0","after":"2026-02-22T12:11:01.000000Z","tz":"Asia/Tehran","next":"2026-12-04T20:30:00.000000Z"} +{"cron":"17 17 * * *","after":"2026-03-29T01:42:40.500000Z","tz":"Europe/Dublin","next":"2026-03-29T16:17:00.000000Z"} +{"cron":"55,31 1-1/7 14-22 * 0#5","after":"2024-11-03T04:02:53.000000Z","tz":"America/New_York","next":"2024-11-14T06:31:00.000000Z"} +{"cron":"*/15 22-23 */16 AUG FRI","after":"2024-09-08T02:23:00.000000Z","tz":"America/Santiago","next":"2025-08-02T02:00:00.000000Z"} +{"cron":"34-47/2 13 7,20 JUL TUE","after":"2025-10-11T17:39:05.000000Z","tz":"America/St_Johns","next":"2026-07-07T16:04:00.000000Z"} +{"cron":"46,14 16 30W 3-9/2 5#2","after":"2028-03-01T00:55:29.500000Z","tz":"Pacific/Chatham","next":"2028-03-10T02:29:00.000000Z"} +{"cron":"41-49/3 9-19 */18 6 *","after":"2027-03-14T07:20:54.000000Z","tz":"America/New_York","next":"2027-06-01T13:41:00.000000Z"} +{"cron":"7-56/2 18,8 8-28 */11 ?","after":"2026-11-01T05:12:05.500000Z","tz":"America/Havana","next":"2026-12-08T23:07:00.000000Z"} +{"cron":"4 * ? 6 */5","after":"2028-05-21T11:08:44.500000Z","tz":"America/Sao_Paulo","next":"2028-06-02T03:04:00.000000Z"} +{"cron":"40-44/2 20 20W * 2L","after":"2028-03-01T00:44:55.000000Z","tz":"Europe/London","next":"2028-03-20T20:40:00.000000Z"} +{"cron":"53,52 */14 ? 3-11 1,6","after":"2028-09-30T13:43:23.000000Z","tz":"Australia/Lord_Howe","next":"2028-10-01T13:52:00.000000Z"} +{"cron":"34-46 21-22/4 21-29 */5 0#4","after":"2026-04-04T13:38:27.000000Z","tz":"Pacific/Chatham","next":"2026-06-21T08:49:00.000000Z"} +{"cron":"21,19 17,18 9 * SAT","after":"2027-03-28T00:22:17.000000Z","tz":"Europe/Budapest","next":"2027-04-03T15:19:00.000000Z"} +{"cron":"11-32 22-22/3 9 JAN MON","after":"2026-05-23T20:49:06.000000Z","tz":"Asia/Tehran","next":"2027-01-04T18:41:00.000000Z"} +{"cron":"30,7 21-21 ? SEP WED","after":"2028-09-28T01:49:24.000000Z","tz":"America/Sao_Paulo","next":"2029-09-06T00:07:00.000000Z"} +{"cron":"*/7 16-23/3 7W 12-12 *","after":"2026-01-31T22:00:45.500000Z","tz":"UTC","next":"2026-12-07T16:00:00.000000Z"} +{"cron":"59,44 * * */12 3,7","after":"2025-05-25T08:26:17.000000Z","tz":"Asia/Kolkata","next":"2026-01-03T19:14:00.000000Z"} +{"cron":"*/52 7-8 5 MAR 3,4","after":"2027-11-07T07:31:41.000000Z","tz":"America/New_York","next":"2028-03-01T12:00:00.000000Z"} +{"cron":"35 18 4-6/7 7-8 1-6","after":"2024-03-31T01:14:08.500000Z","tz":"Antarctica/Troll","next":"2024-07-01T16:35:00.000000Z"} +{"cron":"42 20 * APR 2L","after":"2028-09-02T07:22:16.000000Z","tz":"Europe/Budapest","next":"2029-04-24T18:42:00.000000Z"} +{"cron":"31-31/1 4,23 13 12-12/1 ?","after":"2026-04-05T01:56:09.000000Z","tz":"America/Santiago","next":"2026-12-13T07:31:00.000000Z"} +{"cron":"@weekly","after":"2026-01-31T22:33:09.000000Z","tz":"UTC","next":"2026-02-01T00:00:00.000000Z"} +{"cron":"56,2 3 16 6 FRI","after":"2027-03-01T00:56:41.000000Z","tz":"Australia/Lord_Howe","next":"2027-06-03T16:32:00.000000Z"} +{"cron":"46-50 */24 18 9-11/7 */6","after":"2028-02-10T16:19:59.000000Z","tz":"Europe/Budapest","next":"2028-09-01T22:46:00.000000Z"} +{"cron":"15-55 16,13 7-10/7 8,10 */5","after":"2027-01-28T20:07:45.000000Z","tz":"America/New_York","next":"2027-08-01T20:15:00.000000Z"} +{"cron":"56 0,13 12W 3,1 4L","after":"2026-12-31T23:48:06.000000Z","tz":"Europe/Dublin","next":"2027-01-12T00:56:00.000000Z"} +{"cron":"59 * ? 11-11 7","after":"2024-04-06T15:01:59.000000Z","tz":"Pacific/Chatham","next":"2024-11-02T11:14:00.000000Z"} +{"cron":"4,15 5 */8 */13 *","after":"2026-09-26T13:22:12.000000Z","tz":"Pacific/Chatham","next":"2027-01-31T15:19:00.000000Z"} +{"cron":"5-46/7 19 ? */5 MON","after":"2027-10-31T00:24:11.000000Z","tz":"Europe/London","next":"2027-11-01T19:05:00.000000Z"} +{"cron":"12 * */2 3-11 5","after":"2026-04-05T20:23:22.000000Z","tz":"Pacific/Apia","next":"2026-04-06T11:12:00.000000Z"} +{"cron":"1-33 10 18 * */8","after":"2025-06-21T22:29:13.000000Z","tz":"Pacific/Chatham","next":"2025-06-28T21:16:00.000000Z"} +{"cron":"50 * */22 2 MON","after":"2025-09-21T17:21:01.000000Z","tz":"America/Santiago","next":"2026-02-01T03:50:00.000000Z"} +{"cron":"11-37 20 23W */8 FRI","after":"2025-03-09T05:04:20.500000Z","tz":"America/Havana","next":"2025-09-06T00:11:00.000000Z"} +{"cron":"*/50 18 * JAN *","after":"2026-01-31T23:07:08.000000Z","tz":"Europe/London","next":"2027-01-01T18:00:00.000000Z"} +{"cron":"* 6,12 2-10 2 4-5/6","after":"2024-04-07T04:06:14.000000Z","tz":"America/Santiago","next":"2025-02-02T09:00:00.000000Z"} +{"cron":"5,54 */3 * 8,8 ?","after":"2027-03-01T00:49:00.500000Z","tz":"America/Havana","next":"2027-08-01T04:05:00.000000Z"} +{"cron":"* 19 L DEC MON","after":"2024-10-27T02:12:18.000000Z","tz":"Europe/Budapest","next":"2024-12-02T18:00:00.000000Z"} +{"cron":"@monthly","after":"2028-01-08T13:28:12.500000Z","tz":"UTC","next":"2028-02-01T00:00:00.000000Z"} +{"cron":"16 7 8,24 10-12/6 5","after":"2027-03-01T00:29:53.000000Z","tz":"Australia/Lord_Howe","next":"2027-09-30T20:46:00.000000Z"} +{"cron":"*/20 * ? NOV *","after":"2028-04-02T01:41:39.000000Z","tz":"America/Santiago","next":"2028-11-01T03:00:00.000000Z"} +{"cron":"29,45 * 30W * 4-5/1","after":"2027-12-16T16:21:33.000000Z","tz":"America/Sao_Paulo","next":"2027-12-16T16:29:00.000000Z"} +{"cron":"3-28/7 22 9 11,4 THU","after":"2028-06-16T14:55:46.500000Z","tz":"Asia/Kolkata","next":"2028-11-02T16:33:00.000000Z"} +{"cron":"10-52/1 15-21 28-31 1,2 4","after":"2026-04-30T21:12:51.000000Z","tz":"Asia/Kolkata","next":"2027-01-07T09:40:00.000000Z"} +{"cron":"17-45 3 22-31 */14 */9","after":"2027-11-07T02:40:23.000000Z","tz":"America/St_Johns","next":"2028-03-06T06:47:00.000000Z"} +{"cron":"*/51 2,8 L 7,3 3-3","after":"2025-04-14T06:58:30.000000Z","tz":"Asia/Kolkata","next":"2025-07-01T20:30:00.000000Z"} +{"cron":"* 4-15/6 L 4 2-3","after":"2028-02-29T23:26:21.000000Z","tz":"Asia/Beirut","next":"2028-04-04T01:00:00.000000Z"} +{"cron":"16,22 0-21/7 * */5 7#3","after":"2026-11-01T07:04:20.000000Z","tz":"America/New_York","next":"2026-11-15T05:16:00.000000Z"} +{"cron":"* 22 9,19 8-9 THU","after":"2028-12-12T06:27:26.500000Z","tz":"Asia/Tehran","next":"2029-08-02T18:30:00.000000Z"} +{"cron":"39 10,4 19,16 11,12 1-2","after":"2027-10-11T00:11:26.000000Z","tz":"UTC","next":"2027-11-01T10:39:00.000000Z"} +{"cron":"56 15 5W 8-10 */8","after":"2028-02-29T21:57:05.000000Z","tz":"America/Havana","next":"2028-08-04T19:56:00.000000Z"} +{"cron":"47-54 * 10W 11-11/1 1#4","after":"2027-02-28T22:14:02.000000Z","tz":"America/Havana","next":"2027-11-10T05:47:00.000000Z"} +{"cron":"*/1 7 27-27 FEB TUE","after":"2025-04-05T15:53:27.500000Z","tz":"Australia/Lord_Howe","next":"2026-02-02T20:00:00.000000Z"} +{"cron":"* 4 * 8,11 *","after":"2026-04-30T21:19:43.500000Z","tz":"Asia/Beirut","next":"2026-08-01T01:00:00.000000Z"} +{"cron":"45 19,6 ? */4 WED","after":"2028-02-29T21:06:10.000000Z","tz":"America/Santiago","next":"2028-05-03T23:45:00.000000Z"} +{"cron":"47-47/5 14-19 26-27 7-12 0","after":"2024-10-27T00:25:50.000000Z","tz":"Antarctica/Troll","next":"2024-10-27T14:47:00.000000Z"} +{"cron":"* 17 25,18 */14 ?","after":"2025-03-03T07:31:10.000000Z","tz":"Europe/London","next":"2025-03-18T17:00:00.000000Z"} +{"cron":"49-57 7,16 * 7-12/7 7-7","after":"2025-09-27T13:20:38.000000Z","tz":"Pacific/Chatham","next":"2026-06-30T19:04:00.000000Z"} +{"cron":"39-54/1 * 23-29 OCT 7-7","after":"2027-10-01T03:36:33.000000Z","tz":"Asia/Beirut","next":"2027-10-01T03:39:00.000000Z"} +{"cron":"35 1-2/3 21W 8 5L","after":"2026-10-24T23:22:11.000000Z","tz":"Europe/London","next":"2027-08-20T00:35:00.000000Z"} +{"cron":"53 * * 11-11/4 7-7","after":"2024-10-26T21:40:54.000000Z","tz":"Asia/Beirut","next":"2024-10-31T22:53:00.000000Z"} +{"cron":"*/25 11,4 L MAY */4","after":"2027-10-31T01:22:07.000000Z","tz":"Antarctica/Troll","next":"2028-05-04T09:00:00.000000Z"} +{"cron":"35 * 27-28 10-11 0,2","after":"2026-12-31T22:06:06.000000Z","tz":"America/St_Johns","next":"2027-10-03T03:05:00.000000Z"} +{"cron":"*/49 * ? */10 2","after":"2026-04-30T22:11:29.500000Z","tz":"America/St_Johns","next":"2026-11-03T03:30:00.000000Z"} +{"cron":"* 0 28-28/5 3-3 6","after":"2024-03-30T23:12:23.000000Z","tz":"Europe/Budapest","next":"2025-02-28T23:00:00.000000Z"} +{"cron":"4 8-21/5 16-18/1 DEC *","after":"2028-03-12T08:44:31.000000Z","tz":"America/New_York","next":"2028-12-16T13:04:00.000000Z"} +{"cron":"34 16-17/2 24,27 9-11/6 2L","after":"2026-01-10T19:59:31.000000Z","tz":"Asia/Beirut","next":"2026-09-24T13:34:00.000000Z"} +{"cron":"41 * L * TUE","after":"2025-07-30T03:18:38.000000Z","tz":"Antarctica/Troll","next":"2025-07-30T22:41:00.000000Z"} +{"cron":"*/34 8 ? * 5,3","after":"2024-09-08T02:34:19.000000Z","tz":"America/Santiago","next":"2024-09-11T11:00:00.000000Z"} +{"cron":"9,35 2-19 19,1 */8 2#5","after":"2026-10-25T00:28:06.000000Z","tz":"Europe/London","next":"2027-01-01T02:09:00.000000Z"} +{"cron":"16 17 ? 1-12/5 */7","after":"2027-08-07T17:37:00.000000Z","tz":"Europe/London","next":"2027-11-07T17:16:00.000000Z"} +{"cron":"37 12 L 5-10 1","after":"2027-12-24T18:44:02.500000Z","tz":"America/Sao_Paulo","next":"2028-05-01T15:37:00.000000Z"} +{"cron":"11 * */15 */11 7,1","after":"2028-10-29T02:59:30.000000Z","tz":"Antarctica/Troll","next":"2028-12-01T00:11:00.000000Z"} +{"cron":"51-59/7 20-22 13 * SUN","after":"2026-04-30T22:06:12.000000Z","tz":"Asia/Beirut","next":"2026-05-03T17:51:00.000000Z"} +{"cron":"2-2/6 */1 12,4 3,7 2-3/3","after":"2027-10-02T13:51:14.000000Z","tz":"Australia/Lord_Howe","next":"2028-03-03T13:02:00.000000Z"} +{"cron":"10,56 * 8,30 * ?","after":"2028-02-28T22:54:58.000000Z","tz":"Pacific/Apia","next":"2028-03-07T11:10:00.000000Z"} +{"cron":"27-29/2 14-17 L 4-12/6 */7","after":"2026-04-05T03:05:18.500000Z","tz":"America/Santiago","next":"2026-04-05T18:27:00.000000Z"} +{"cron":"* 8-8/6 14-22 7-11/3 4#3","after":"2027-03-01T00:44:41.000000Z","tz":"America/Havana","next":"2027-07-14T12:00:00.000000Z"} +{"cron":"29,27 9,14 6 SEP 6#4","after":"2026-10-08T10:10:13.000000Z","tz":"Australia/Lord_Howe","next":"2027-09-05T22:57:00.000000Z"} +{"cron":"31,49 11 26W */10 0-7/5","after":"2026-11-25T15:26:16.000000Z","tz":"America/New_York","next":"2026-11-26T16:31:00.000000Z"} +{"cron":"13 5,5 4-10 JAN 1-7","after":"2028-02-28T21:20:31.000000Z","tz":"Asia/Beirut","next":"2029-01-01T03:13:00.000000Z"} +{"cron":"43,34 * 8 5-9 */1","after":"2026-03-08T06:48:28.000000Z","tz":"America/St_Johns","next":"2026-05-01T03:04:00.000000Z"} +{"cron":"29 19-19/6 L 5,7 2-2/3","after":"2028-07-21T22:43:44.000000Z","tz":"Asia/Beirut","next":"2028-07-25T16:29:00.000000Z"} +{"cron":"23 1,6 * 3,10 7L","after":"2024-03-30T23:46:12.000000Z","tz":"Europe/Budapest","next":"2024-03-31T00:23:00.000000Z"} +{"cron":"*/27 2,3 20,2 8 ?","after":"2027-10-30T21:53:21.000000Z","tz":"Asia/Beirut","next":"2028-08-01T23:00:00.000000Z"} +{"cron":"40 * ? */9 1#4","after":"2027-07-10T06:21:58.000000Z","tz":"Asia/Kolkata","next":"2027-10-24T19:10:00.000000Z"} +{"cron":"53,45 * ? 1 4,5","after":"2028-07-12T23:19:42.000000Z","tz":"Pacific/Apia","next":"2029-01-03T11:45:00.000000Z"} +{"cron":"* */6 30-30 1-2 1#3","after":"2026-04-30T23:42:15.000000Z","tz":"Europe/Dublin","next":"2027-01-18T00:00:00.000000Z"} +{"cron":"41 11-20/1 L */11 2-6","after":"2028-11-13T07:23:25.000000Z","tz":"Europe/London","next":"2028-12-01T11:41:00.000000Z"} +{"cron":"31 3 L 6,3 ?","after":"2026-07-04T19:06:16.000000Z","tz":"Asia/Tehran","next":"2027-03-31T00:01:00.000000Z"} +{"cron":"30 1 L FEB 7-7/5","after":"2025-04-05T15:45:16.000000Z","tz":"Pacific/Chatham","next":"2026-02-05T11:45:00.000000Z"} +{"cron":"41-43/3 20-20 7-13 11,9 0-2","after":"2027-07-05T09:51:39.500000Z","tz":"Pacific/Apia","next":"2027-09-05T07:41:00.000000Z"} +{"cron":"30 */9 */12 7-11 0-3","after":"2028-08-07T17:36:54.000000Z","tz":"America/Sao_Paulo","next":"2028-08-07T21:30:00.000000Z"} +{"cron":"19-40 */11 ? 5,8 0-4/3","after":"2027-07-09T23:43:37.500000Z","tz":"UTC","next":"2027-08-01T00:19:00.000000Z"} +{"cron":"45 5-9 ? */7 6","after":"2027-01-01T00:30:09.000000Z","tz":"UTC","next":"2027-01-02T05:45:00.000000Z"} +{"cron":"39-50 14 25-31/7 3 2#3","after":"2028-08-26T15:50:19.000000Z","tz":"Asia/Kolkata","next":"2029-03-20T09:09:00.000000Z"} +{"cron":"25 * 23-27/1 12-12 7L","after":"2024-04-06T13:54:51.000000Z","tz":"Australia/Lord_Howe","next":"2024-12-22T13:25:00.000000Z"} +{"cron":"* 22 ? * 6-7","after":"2026-01-24T16:15:00.000000Z","tz":"Pacific/Apia","next":"2026-01-25T09:00:00.000000Z"} +{"cron":"39,52 * ? * 4#1","after":"2024-10-27T00:55:04.000000Z","tz":"Europe/Budapest","next":"2024-11-06T23:39:00.000000Z"} +{"cron":"*/58 1-20 4 1-11/5 */8","after":"2026-03-28T23:48:10.000000Z","tz":"Antarctica/Troll","next":"2026-06-03T23:00:00.000000Z"} +{"cron":"3,5 3-21 15,1 */4 */2","after":"2028-08-22T15:59:32.000000Z","tz":"Australia/Lord_Howe","next":"2028-08-31T16:33:00.000000Z"} +{"cron":"46 6 7-20/6 9,9 6-7","after":"2028-11-06T07:34:21.500000Z","tz":"Asia/Kolkata","next":"2029-09-01T01:16:00.000000Z"} +{"cron":"17 20,17 20W 5 SAT","after":"2027-01-01T00:30:47.000000Z","tz":"Pacific/Apia","next":"2027-05-01T07:17:00.000000Z"} +{"cron":"* */16 14,25 JUN */9","after":"2026-04-30T23:21:41.000000Z","tz":"UTC","next":"2026-06-01T00:00:00.000000Z"} +{"cron":"9 * */7 */12 0-6","after":"2024-10-27T01:02:04.000000Z","tz":"Europe/Dublin","next":"2025-01-01T00:09:00.000000Z"} +{"cron":"15-43 5-12/3 1,2 11,10 SAT","after":"2026-12-31T21:46:29.000000Z","tz":"America/St_Johns","next":"2027-10-01T07:45:00.000000Z"} +{"cron":"17 7-17/2 30 */3 2L","after":"2027-09-05T04:37:58.000000Z","tz":"America/Santiago","next":"2027-10-26T10:17:00.000000Z"} +{"cron":"34 7 29-30/3 8 7","after":"2026-05-24T05:01:06.000000Z","tz":"UTC","next":"2026-08-02T07:34:00.000000Z"} +{"cron":"43-59/4 21 24 3 1L","after":"2026-04-04T14:26:18.000000Z","tz":"Pacific/Chatham","next":"2027-03-24T07:58:00.000000Z"} +{"cron":"* */3 4-8 SEP 7,4","after":"2028-08-06T12:30:53.000000Z","tz":"Asia/Kolkata","next":"2028-09-02T18:30:00.000000Z"} +{"cron":"4-52/3 0,7 30-31 7 */6","after":"2027-07-09T20:27:39.000000Z","tz":"America/Havana","next":"2027-07-10T04:04:00.000000Z"} +{"cron":"49 8 L 8 */4","after":"2026-03-28T23:36:03.000000Z","tz":"Europe/Dublin","next":"2026-08-02T07:49:00.000000Z"} +{"cron":"3 21 L 11-11/7 0,5","after":"2025-10-26T01:35:15.500000Z","tz":"Europe/London","next":"2025-11-02T21:03:00.000000Z"} +{"cron":"*/47 * 27 4-9 3-5/1","after":"2027-10-02T15:57:20.000000Z","tz":"Australia/Lord_Howe","next":"2028-04-04T13:30:00.000000Z"} +{"cron":"6 21 L JUN 6#3","after":"2024-11-03T02:49:54.500000Z","tz":"America/St_Johns","next":"2025-06-21T23:36:00.000000Z"} +{"cron":"42 15 19-28 * *","after":"2024-11-03T05:23:54.000000Z","tz":"America/St_Johns","next":"2024-11-19T19:12:00.000000Z"} +{"cron":"33-55 * 23 * ?","after":"2025-10-04T16:51:30.000000Z","tz":"Australia/Lord_Howe","next":"2025-10-22T13:33:00.000000Z"} +{"cron":"34,4 14-17 8 10 *","after":"2028-02-29T23:51:18.000000Z","tz":"Asia/Beirut","next":"2028-10-08T11:04:00.000000Z"} +{"cron":"* * * 2,4 4#4","after":"2026-03-08T06:58:06.500000Z","tz":"America/Havana","next":"2026-04-23T04:00:00.000000Z"} +{"cron":"*/59 0-17/5 */29 11 4-5","after":"2028-01-29T20:36:31.000000Z","tz":"Pacific/Apia","next":"2028-10-31T11:00:00.000000Z"} +{"cron":"*/43 12 ? AUG 5","after":"2028-02-29T00:32:22.500000Z","tz":"America/Havana","next":"2028-08-04T16:00:00.000000Z"} +{"cron":"8 16,19 */6 8-10/6 1#5","after":"2027-10-02T16:17:01.500000Z","tz":"Australia/Lord_Howe","next":"2028-08-01T05:38:00.000000Z"} +{"cron":"42 3,23 7,29 3 *","after":"2028-11-25T12:26:53.000000Z","tz":"America/Santiago","next":"2029-03-07T06:42:00.000000Z"} +{"cron":"18-50/6 10 13,14 */7 *","after":"2025-01-24T09:05:48.500000Z","tz":"UTC","next":"2025-08-13T10:18:00.000000Z"} +{"cron":"41,48 */14 23,12 */5 */4","after":"2026-03-08T07:24:31.000000Z","tz":"America/New_York","next":"2026-06-04T04:41:00.000000Z"} +{"cron":"*/46 1-1/7 24-26 * *","after":"2028-02-29T21:57:34.500000Z","tz":"America/St_Johns","next":"2028-03-24T03:30:00.000000Z"} +{"cron":"13 */12 13W 7 SUN","after":"2027-04-03T12:31:09.000000Z","tz":"Pacific/Chatham","next":"2027-07-03T11:28:00.000000Z"} +{"cron":"* 21 25-31/5 */10 4#1","after":"2026-01-31T21:17:41.000000Z","tz":"America/Sao_Paulo","next":"2026-11-06T00:00:00.000000Z"} +{"cron":"28-52/1 5 2 JUN 7L","after":"2025-05-02T21:25:42.000000Z","tz":"Asia/Kolkata","next":"2025-06-01T23:58:00.000000Z"} +{"cron":"3-30/6 */21 */28 * ?","after":"2026-01-25T11:46:18.000000Z","tz":"America/Sao_Paulo","next":"2026-01-29T03:03:00.000000Z"} +{"cron":"40-42 */25 ? 11 1L","after":"2025-03-09T03:41:36.500000Z","tz":"America/St_Johns","next":"2025-11-24T05:10:00.000000Z"} +{"cron":"* 0 L APR FRI","after":"2024-04-06T14:26:37.000000Z","tz":"Pacific/Chatham","next":"2024-04-11T11:15:00.000000Z"} +{"cron":"54 14-22/5 31W 11,7 *","after":"2026-11-01T06:49:39.000000Z","tz":"UTC","next":"2026-11-01T14:54:00.000000Z"} +{"cron":"57-58 15,19 L JAN *","after":"2028-09-02T18:58:20.000000Z","tz":"Asia/Kolkata","next":"2029-01-31T10:27:00.000000Z"} +{"cron":"3 */22 9,28 */12 5-5","after":"2024-11-03T07:12:59.000000Z","tz":"America/New_York","next":"2025-01-03T05:03:00.000000Z"} +{"cron":"43-52 * L 4 ?","after":"2028-10-29T02:59:37.500000Z","tz":"Europe/Budapest","next":"2029-04-29T22:43:00.000000Z"} +{"cron":"59,14 20 */12 11-12 1L","after":"2027-02-28T23:03:43.000000Z","tz":"Pacific/Chatham","next":"2027-11-01T06:29:00.000000Z"} +{"cron":"38 6-17 14 MAY 4L","after":"2028-09-30T13:27:36.500000Z","tz":"America/St_Johns","next":"2029-05-14T09:08:00.000000Z"} +{"cron":"56 10 ? DEC */1","after":"2026-06-17T02:52:37.000000Z","tz":"America/Sao_Paulo","next":"2026-12-01T13:56:00.000000Z"} +{"cron":"50 12-17/2 27-27/4 6-11/3 ?","after":"2028-07-25T21:35:40.000000Z","tz":"America/Sao_Paulo","next":"2028-09-27T15:50:00.000000Z"} +{"cron":"*/32 2-6/3 * * 2#2","after":"2027-03-01T00:36:09.000000Z","tz":"Asia/Kolkata","next":"2027-03-08T20:30:00.000000Z"} +{"cron":"8 5 */3 * 1#4","after":"2026-06-30T10:19:36.000000Z","tz":"Pacific/Apia","next":"2026-06-30T16:08:00.000000Z"} +{"cron":"* 3 */33 5-12/4 *","after":"2028-02-29T22:11:36.000000Z","tz":"Antarctica/Troll","next":"2028-05-03T01:00:00.000000Z"} +{"cron":"57-57/2 17-18/7 * 2 2","after":"2024-10-05T15:19:31.000000Z","tz":"Australia/Lord_Howe","next":"2025-02-04T06:57:00.000000Z"} +{"cron":"29-58 19,7 15-23/2 */8 7-7/3","after":"2026-02-26T18:04:39.000000Z","tz":"Asia/Kolkata","next":"2026-09-02T13:59:00.000000Z"} +{"cron":"*/32 2-6 10 */9 3,1","after":"2028-03-01T00:08:44.000000Z","tz":"Antarctica/Troll","next":"2028-10-02T00:00:00.000000Z"} +{"cron":"0 3-21 11 1-9/2 ?","after":"2028-10-29T01:49:58.000000Z","tz":"Europe/London","next":"2029-01-11T03:00:00.000000Z"} +{"cron":"*/29 5-10 4-19 11,7 6L","after":"2025-03-02T02:23:42.000000Z","tz":"Europe/London","next":"2025-07-04T04:00:00.000000Z"} +{"cron":"*/36 5 26-31/3 6-6/2 0-6","after":"2025-10-04T16:33:31.000000Z","tz":"Australia/Lord_Howe","next":"2026-05-31T18:30:00.000000Z"} +{"cron":"56 17-23/7 11W 4-10/6 3#2","after":"2027-02-28T22:41:48.500000Z","tz":"Pacific/Apia","next":"2027-04-12T04:56:00.000000Z"} +{"cron":"* 17-18/4 5-8 1,7 */9","after":"2026-01-31T22:16:37.000000Z","tz":"UTC","next":"2026-07-05T17:00:00.000000Z"} +{"cron":"* 21-23 19,6 */7 6L","after":"2028-01-08T04:34:56.000000Z","tz":"Europe/London","next":"2028-01-19T21:00:00.000000Z"} +{"cron":"*/48 2,6 26W 7-9/5 */1","after":"2026-10-25T00:15:01.500000Z","tz":"Europe/Dublin","next":"2027-07-01T01:00:00.000000Z"} +{"cron":"49,13 1-2 24W MAY 2-7","after":"2025-11-15T07:26:25.000000Z","tz":"America/St_Johns","next":"2026-05-01T03:43:00.000000Z"} +{"cron":"30,0 * 23-25 2-2/6 3","after":"2026-10-25T01:41:33.000000Z","tz":"Antarctica/Troll","next":"2027-02-03T00:00:00.000000Z"} +{"cron":"34-37 20 24W 2-4/1 6","after":"2026-12-31T22:54:04.000000Z","tz":"America/Sao_Paulo","next":"2027-02-06T23:34:00.000000Z"} +{"cron":"* 1 L 1 5L","after":"2026-04-30T23:17:23.000000Z","tz":"Pacific/Chatham","next":"2027-01-28T11:15:00.000000Z"} +{"cron":"37 3 ? DEC 3#5","after":"2027-02-03T07:32:52.000000Z","tz":"America/Sao_Paulo","next":"2027-12-29T06:37:00.000000Z"} +{"cron":"42 20-23/3 L 3,5 SUN","after":"2028-02-28T21:11:46.000000Z","tz":"Australia/Lord_Howe","next":"2028-03-05T09:42:00.000000Z"} +{"cron":"*/28 * 29W 10-10/4 7-7","after":"2025-04-06T04:09:49.000000Z","tz":"America/Santiago","next":"2025-10-01T03:00:00.000000Z"} +{"cron":"20-36 16-18/5 7-15 12-12/3 ?","after":"2026-07-04T16:32:58.000000Z","tz":"Pacific/Apia","next":"2026-12-07T03:20:00.000000Z"} +{"cron":"38 17-23/5 L 5,2 0-6","after":"2027-05-14T09:51:51.000000Z","tz":"Asia/Beirut","next":"2027-05-14T14:38:00.000000Z"} +{"cron":"23-54/4 14,7 16,27 5-6 *","after":"2028-10-26T02:15:45.000000Z","tz":"Australia/Lord_Howe","next":"2029-05-16T03:53:00.000000Z"} +{"cron":"*/49 7 7 8,7 2L","after":"2026-03-08T03:33:24.500000Z","tz":"America/St_Johns","next":"2026-07-07T09:30:00.000000Z"} +{"cron":"46,46 3-17/2 3 3-6/1 5#2","after":"2026-06-26T19:18:05.500000Z","tz":"Pacific/Apia","next":"2027-03-02T14:46:00.000000Z"} +{"cron":"4-12 23 */25 12,8 */9","after":"2026-06-03T10:26:25.500000Z","tz":"Pacific/Chatham","next":"2026-08-01T10:19:00.000000Z"} +{"cron":"25-41/6 5 ? 7 FRI","after":"2026-12-31T23:02:48.000000Z","tz":"America/Havana","next":"2027-07-02T09:25:00.000000Z"} +{"cron":"3 */7 */9 12 WED","after":"2026-03-09T20:56:48.000000Z","tz":"Pacific/Apia","next":"2026-11-30T11:03:00.000000Z"} +{"cron":"59-59/6 13,1 19 12 *","after":"2028-03-26T00:59:03.500000Z","tz":"Europe/Dublin","next":"2028-12-19T13:59:00.000000Z"} +{"cron":"55-57/2 16-22 * AUG *","after":"2025-09-27T12:06:21.000000Z","tz":"Pacific/Chatham","next":"2026-08-01T04:10:00.000000Z"} +{"cron":"9-41/3 7-12/5 L 1-12 3","after":"2027-03-27T22:29:40.500000Z","tz":"Asia/Beirut","next":"2027-03-31T04:09:00.000000Z"} +{"cron":"17-56 19-20 * 12-12/6 3-4","after":"2026-12-31T21:21:21.000000Z","tz":"America/St_Johns","next":"2026-12-31T22:47:00.000000Z"} +{"cron":"17 * L */13 */5","after":"2028-02-28T21:17:10.000000Z","tz":"America/New_York","next":"2028-02-29T05:17:00.000000Z"} +{"cron":"54 9 14 5 6","after":"2025-10-25T20:19:59.000000Z","tz":"Asia/Beirut","next":"2026-05-02T06:54:00.000000Z"} +{"cron":"26-53/7 5 19 */9 1","after":"2026-11-01T05:25:49.000000Z","tz":"America/New_York","next":"2027-01-04T10:26:00.000000Z"} +{"cron":"18 1 6 * 0L","after":"2026-12-31T23:47:43.000000Z","tz":"Australia/Lord_Howe","next":"2027-01-05T14:18:00.000000Z"} +{"cron":"24 12-23 16W */12 *","after":"2026-12-31T22:23:33.500000Z","tz":"America/Sao_Paulo","next":"2027-01-15T15:24:00.000000Z"} +{"cron":"29,57 9-23 ? FEB 3,5","after":"2028-02-28T23:20:27.500000Z","tz":"America/New_York","next":"2029-02-02T14:29:00.000000Z"} +{"cron":"45-59 12 */29 3-9 */6","after":"2026-03-29T00:17:29.500000Z","tz":"Antarctica/Troll","next":"2026-03-30T10:45:00.000000Z"} +{"cron":"*/50 10 * 8-12/6 7L","after":"2025-03-26T23:47:48.000000Z","tz":"Asia/Kolkata","next":"2025-08-31T04:30:00.000000Z"} +{"cron":"@daily","after":"2025-12-11T22:19:42.000000Z","tz":"Asia/Tehran","next":"2025-12-12T20:30:00.000000Z"} +{"cron":"15-53 0 */23 1 THU","after":"2027-06-07T19:27:16.000000Z","tz":"Asia/Kolkata","next":"2027-12-31T18:45:00.000000Z"} +{"cron":"* 20,4 L 5-5/1 1-3/7","after":"2025-11-26T17:36:21.000000Z","tz":"Asia/Kolkata","next":"2026-05-04T14:30:00.000000Z"} +{"cron":"50-50 */9 17 5,8 6-7/3","after":"2028-10-29T00:58:44.500000Z","tz":"Antarctica/Troll","next":"2029-05-04T22:50:00.000000Z"} +{"cron":"*/43 14 ? */4 4","after":"2025-07-09T20:34:40.000000Z","tz":"Asia/Tehran","next":"2025-09-04T10:30:00.000000Z"} +{"cron":"* 14 L * 0L","after":"2028-02-28T21:29:34.000000Z","tz":"Asia/Kolkata","next":"2028-02-29T08:30:00.000000Z"} +{"cron":"29 9-15 14-31/1 12,4 *","after":"2025-03-09T06:45:06.500000Z","tz":"America/St_Johns","next":"2025-04-14T11:59:00.000000Z"} +{"cron":"29 19-23 11 * ?","after":"2027-09-14T04:03:24.000000Z","tz":"Australia/Lord_Howe","next":"2027-10-11T08:29:00.000000Z"} +{"cron":"*/28 23,11 28-30 * 4","after":"2025-04-14T15:37:07.500000Z","tz":"UTC","next":"2025-04-17T23:00:00.000000Z"} +{"cron":"27-36/5 11 L 4 2-2/6","after":"2026-02-07T06:55:57.000000Z","tz":"UTC","next":"2026-04-07T11:27:00.000000Z"} +{"cron":"46-55/5 0-14 * 9-11/2 4,6","after":"2025-03-30T00:46:36.000000Z","tz":"Europe/Dublin","next":"2025-09-03T23:46:00.000000Z"} +{"cron":"20 15-16 18,6 1 0,6","after":"2028-03-01T00:42:08.500000Z","tz":"Europe/Dublin","next":"2029-01-06T15:20:00.000000Z"} +{"cron":"26,12 2-22/3 */28 4-4 3#5","after":"2028-03-01T00:03:16.000000Z","tz":"America/Sao_Paulo","next":"2028-04-01T05:12:00.000000Z"} +{"cron":"10-24/1 21 15W FEB 2-4/7","after":"2028-05-25T03:41:03.500000Z","tz":"Asia/Beirut","next":"2029-02-06T19:10:00.000000Z"} +{"cron":"57 23 12,5 2-7 1,5","after":"2026-02-01T00:18:59.000000Z","tz":"Antarctica/Troll","next":"2026-02-02T23:57:00.000000Z"} +{"cron":"* 20-21/6 L 5 5L","after":"2028-11-24T02:19:48.000000Z","tz":"Pacific/Apia","next":"2029-05-25T07:00:00.000000Z"} +{"cron":"*/36 */23 L 1 5-5","after":"2024-04-06T13:09:36.000000Z","tz":"Pacific/Chatham","next":"2025-01-02T10:15:00.000000Z"} +{"cron":"37 16,8 3W 3-10 ?","after":"2027-03-27T23:31:08.500000Z","tz":"Europe/Dublin","next":"2027-04-02T15:37:00.000000Z"} +{"cron":"*/44 * * * 2-6/2","after":"2024-04-07T03:31:55.000000Z","tz":"America/Santiago","next":"2024-04-07T03:44:00.000000Z"} +{"cron":"31 4,11 25-29 * ?","after":"2024-03-10T04:07:19.000000Z","tz":"America/St_Johns","next":"2024-03-25T07:01:00.000000Z"} +{"cron":"37-57 */24 30 4 */5","after":"2028-04-02T02:16:49.000000Z","tz":"America/Santiago","next":"2028-04-02T04:37:00.000000Z"} +{"cron":"55,36 9-14/5 17-24 */3 SAT","after":"2026-08-09T14:46:00.000000Z","tz":"UTC","next":"2026-10-03T09:36:00.000000Z"} +{"cron":"39 * 1-26 */6 7,0","after":"2025-09-12T15:22:12.000000Z","tz":"Asia/Tehran","next":"2025-12-31T21:09:00.000000Z"} +{"cron":"54-56 * 23,4 */9 5-5/3","after":"2025-10-15T13:30:25.000000Z","tz":"Antarctica/Troll","next":"2025-10-16T22:54:00.000000Z"} +{"cron":"39 3,11 7W 12-12/2 4-4/6","after":"2028-02-28T21:52:53.000000Z","tz":"Asia/Beirut","next":"2028-12-07T01:39:00.000000Z"} +{"cron":"39,46 8-16 16W */13 0L","after":"2026-10-25T02:15:08.500000Z","tz":"Europe/Budapest","next":"2027-02-16T07:39:00.000000Z"} +{"cron":"30-30/2 18-22/1 31,30 1-5 TUE","after":"2027-09-17T19:17:37.000000Z","tz":"Asia/Beirut","next":"2028-01-04T16:30:00.000000Z"} +{"cron":"*/55 10,17 * FEB ?","after":"2027-03-27T20:12:02.000000Z","tz":"Asia/Beirut","next":"2028-02-01T08:00:00.000000Z"} +{"cron":"@daily","after":"2025-04-18T07:19:22.500000Z","tz":"America/New_York","next":"2025-04-19T04:00:00.000000Z"} +{"cron":"54,17 16 11 */13 *","after":"2026-02-01T00:17:02.000000Z","tz":"Australia/Lord_Howe","next":"2026-02-11T05:17:00.000000Z"} +{"cron":"* */15 */29 6-7/1 1L","after":"2028-07-28T14:44:39.000000Z","tz":"Pacific/Apia","next":"2028-07-29T11:00:00.000000Z"} +{"cron":"36-43 * 1-4 * 3-5/7","after":"2026-01-31T21:21:28.000000Z","tz":"UTC","next":"2026-02-01T00:36:00.000000Z"} +{"cron":"1,2 */4 * * */4","after":"2027-07-11T12:19:32.000000Z","tz":"Europe/Dublin","next":"2027-07-11T15:01:00.000000Z"} +{"cron":"7-35/7 23 28-30 10-12 4#4","after":"2027-02-03T07:26:29.000000Z","tz":"America/Santiago","next":"2027-10-29T02:07:00.000000Z"} +{"cron":"59 * 27 11 ?","after":"2026-02-03T14:16:34.000000Z","tz":"UTC","next":"2026-11-27T00:59:00.000000Z"} +{"cron":"25-59 19 * 10-12 5L","after":"2026-12-31T22:14:36.000000Z","tz":"Europe/Dublin","next":"2027-10-29T18:25:00.000000Z"} +{"cron":"*/46 12,14 * 7-10 2L","after":"2026-03-29T00:25:49.000000Z","tz":"Europe/London","next":"2026-07-28T11:00:00.000000Z"} +{"cron":"30 * 6-20 9-12 5-5","after":"2025-10-04T15:53:50.000000Z","tz":"Australia/Lord_Howe","next":"2025-10-05T13:30:00.000000Z"} +{"cron":"59-59/5 6,5 16-23 * 6","after":"2026-12-31T21:21:44.000000Z","tz":"Europe/Dublin","next":"2027-01-02T06:59:00.000000Z"} +{"cron":"45 13,10 25W */13 3-3","after":"2026-04-30T23:20:01.500000Z","tz":"America/Havana","next":"2027-02-03T18:45:00.000000Z"} +{"cron":"6-41/2 19 7,1 5-11 7","after":"2025-01-05T23:59:10.000000Z","tz":"UTC","next":"2025-05-01T19:06:00.000000Z"} +{"cron":"49-57 18-18/1 19,10 * 1","after":"2025-05-02T00:01:06.000000Z","tz":"America/Sao_Paulo","next":"2025-05-05T21:49:00.000000Z"} +{"cron":"50-51/7 16,22 7 FEB THU","after":"2027-02-18T11:18:42.000000Z","tz":"UTC","next":"2027-02-18T16:50:00.000000Z"} +{"cron":"* 8-12/1 14W */3 3,3","after":"2026-03-06T04:21:00.000000Z","tz":"Europe/Budapest","next":"2026-04-01T06:00:00.000000Z"} +{"cron":"36 10,23 ? 8-8 */8","after":"2027-05-12T16:02:29.000000Z","tz":"Australia/Lord_Howe","next":"2027-08-01T00:06:00.000000Z"} +{"cron":"*/34 15 4 7-12 ?","after":"2027-01-01T00:54:55.000000Z","tz":"Pacific/Chatham","next":"2027-07-04T02:15:00.000000Z"} +{"cron":"23 1-14 30 5-9/2 0-4","after":"2026-04-04T13:39:51.500000Z","tz":"Pacific/Chatham","next":"2026-05-02T12:38:00.000000Z"} +{"cron":"32-58 */4 13-20/3 SEP 7,6","after":"2025-03-15T03:55:46.500000Z","tz":"America/St_Johns","next":"2025-09-06T03:02:00.000000Z"} +{"cron":"26 3 */5 7-11 3,6","after":"2025-01-19T19:25:57.000000Z","tz":"Asia/Kolkata","next":"2025-06-30T21:56:00.000000Z"} +{"cron":"*/11 0,21 19W APR 7,4","after":"2026-09-24T10:41:29.000000Z","tz":"Europe/Dublin","next":"2027-03-31T23:00:00.000000Z"} +{"cron":"47-52/4 19,22 29 JUN 4-5/6","after":"2026-03-08T03:05:24.000000Z","tz":"America/Havana","next":"2026-06-04T23:47:00.000000Z"} +{"cron":"56-57 12,15 3 11-12/7 5,0","after":"2027-02-28T22:14:00.500000Z","tz":"UTC","next":"2027-11-03T12:56:00.000000Z"} +{"cron":"* 3-22/5 3-28/4 FEB 6#5","after":"2028-02-08T23:50:28.000000Z","tz":"Asia/Kolkata","next":"2028-02-10T21:30:00.000000Z"} +{"cron":"4-39 0 14W 5-8 5L","after":"2025-12-16T16:45:56.000000Z","tz":"Asia/Beirut","next":"2026-05-13T21:04:00.000000Z"} +{"cron":"51-52 15 * OCT 5L","after":"2028-02-29T00:58:47.000000Z","tz":"America/Sao_Paulo","next":"2028-10-27T18:51:00.000000Z"} +{"cron":"13 */24 L AUG 4,1","after":"2027-04-03T14:01:29.500000Z","tz":"Australia/Lord_Howe","next":"2027-08-01T13:43:00.000000Z"} +{"cron":"12 1 4-25 3,11 2L","after":"2025-11-02T03:51:51.000000Z","tz":"America/St_Johns","next":"2025-11-04T04:42:00.000000Z"} +{"cron":"45 21-23/2 5-29 * ?","after":"2028-03-01T00:36:24.000000Z","tz":"Antarctica/Troll","next":"2028-03-05T21:45:00.000000Z"} +{"cron":"23-33 * 22 * 7-7","after":"2026-11-21T17:09:11.000000Z","tz":"Asia/Kolkata","next":"2026-11-21T17:53:00.000000Z"} +{"cron":"25 23 24-25/4 FEB 0#4","after":"2028-02-29T22:08:36.000000Z","tz":"Asia/Tehran","next":"2029-02-24T19:55:00.000000Z"} +{"cron":"14-28 22 * 6-7 3,7","after":"2026-04-30T21:40:29.000000Z","tz":"America/St_Johns","next":"2026-06-04T00:44:00.000000Z"} +{"cron":"33-58 * */21 JUN 4-5/2","after":"2025-04-05T16:51:07.000000Z","tz":"Australia/Lord_Howe","next":"2025-05-31T14:03:00.000000Z"} +{"cron":"46-48/7 20 17 * 1-3","after":"2027-12-23T15:09:17.000000Z","tz":"Europe/Budapest","next":"2027-12-27T19:46:00.000000Z"} +{"cron":"38-41 16 17 */1 *","after":"2025-09-27T13:26:57.000000Z","tz":"Pacific/Chatham","next":"2025-10-17T02:53:00.000000Z"} +{"cron":"*/32 * 19W 3,3 */4","after":"2026-04-30T21:28:49.500000Z","tz":"America/Santiago","next":"2027-03-04T03:00:00.000000Z"} +{"cron":"26 23 15-19/1 */6 3-5","after":"2028-03-01T00:56:51.500000Z","tz":"Antarctica/Troll","next":"2028-07-05T21:26:00.000000Z"} +{"cron":"*/45 8 ? FEB 6-7/7","after":"2026-02-12T15:13:33.000000Z","tz":"UTC","next":"2026-02-14T08:00:00.000000Z"} +{"cron":"5-37 9 * 4,5 6L","after":"2025-04-16T03:58:40.500000Z","tz":"Antarctica/Troll","next":"2025-04-26T07:05:00.000000Z"} +{"cron":"* */5 11 NOV 3,4","after":"2025-11-02T06:46:31.000000Z","tz":"America/New_York","next":"2025-11-05T05:00:00.000000Z"} +{"cron":"53,16 23 3-9 4 6,5","after":"2025-12-24T21:05:48.000000Z","tz":"Pacific/Chatham","next":"2026-04-03T09:31:00.000000Z"} +{"cron":"* 0 L 4-5 7-7","after":"2027-02-28T23:58:47.000000Z","tz":"Europe/Budapest","next":"2027-03-31T22:00:00.000000Z"} +{"cron":"40 22,16 L * 6-7/1","after":"2025-10-26T02:16:20.000000Z","tz":"Europe/Budapest","next":"2025-10-26T21:40:00.000000Z"} +{"cron":"49-58 */8 19 * 0#2","after":"2025-10-26T01:43:30.000000Z","tz":"Europe/Dublin","next":"2025-11-09T00:49:00.000000Z"} +{"cron":"56,28 0,21 29W 11-11/5 0-0","after":"2025-03-10T07:30:39.000000Z","tz":"Pacific/Apia","next":"2025-10-31T11:28:00.000000Z"} +{"cron":"53 17-18/4 ? */14 0#3","after":"2027-05-13T15:39:32.000000Z","tz":"Australia/Lord_Howe","next":"2028-03-19T06:53:00.000000Z"} +{"cron":"34 */18 */20 * 0L","after":"2027-09-18T00:07:00.000000Z","tz":"America/Sao_Paulo","next":"2027-09-21T03:34:00.000000Z"} +{"cron":"52,51 */16 ? */3 THU","after":"2028-03-26T00:59:46.000000Z","tz":"Antarctica/Troll","next":"2028-04-05T22:51:00.000000Z"} +{"cron":"13,25 21,0 ? */13 0","after":"2028-03-01T00:54:51.000000Z","tz":"America/New_York","next":"2029-02-04T05:13:00.000000Z"} +{"cron":"54,9 17-22/3 29W 6 *","after":"2026-04-04T19:28:07.000000Z","tz":"Asia/Kolkata","next":"2026-06-29T11:39:00.000000Z"} +{"cron":"57,18 6-22 30-31 AUG 3","after":"2026-07-08T03:25:13.000000Z","tz":"Antarctica/Troll","next":"2026-08-05T04:18:00.000000Z"} +{"cron":"17,34 23,18 4 APR 6","after":"2024-04-06T13:11:54.000000Z","tz":"Pacific/Chatham","next":"2024-04-13T10:32:00.000000Z"} +{"cron":"12-26 22 21-24/5 11,9 7-7/7","after":"2028-01-19T06:32:29.000000Z","tz":"UTC","next":"2028-09-03T22:12:00.000000Z"} +{"cron":"30,39 0 */9 SEP ?","after":"2027-10-31T01:58:39.500000Z","tz":"Antarctica/Troll","next":"2028-08-31T22:30:00.000000Z"} +{"cron":"34 20,16 22W 1-8/1 ?","after":"2025-12-05T15:05:06.000000Z","tz":"UTC","next":"2026-01-22T20:34:00.000000Z"} +{"cron":"* 19 27,31 7 7","after":"2025-08-31T06:21:46.000000Z","tz":"America/Sao_Paulo","next":"2026-07-05T22:00:00.000000Z"} +{"cron":"*/25 23-23/6 L 7-10 */4","after":"2025-07-08T12:34:35.000000Z","tz":"Asia/Kolkata","next":"2025-07-10T17:30:00.000000Z"} +{"cron":"* 15 17 */12 */9","after":"2028-06-14T00:30:25.000000Z","tz":"Asia/Tehran","next":"2029-01-01T11:30:00.000000Z"} +{"cron":"13 9 26 10,5 ?","after":"2028-12-29T03:08:54.500000Z","tz":"America/Sao_Paulo","next":"2029-05-26T12:13:00.000000Z"} +{"cron":"22 11 L */11 ?","after":"2027-01-01T00:54:35.000000Z","tz":"Asia/Kolkata","next":"2027-01-31T05:52:00.000000Z"} +{"cron":"* 3 12,9 12 */4","after":"2028-12-08T02:22:16.000000Z","tz":"UTC","next":"2028-12-09T03:00:00.000000Z"} +{"cron":"48 19-19 15,31 10,12 *","after":"2024-03-30T22:15:13.000000Z","tz":"Asia/Beirut","next":"2024-10-15T16:48:00.000000Z"} +{"cron":"53-58/3 * 24 */5 THU","after":"2027-03-27T23:27:43.000000Z","tz":"Europe/Dublin","next":"2027-06-02T23:53:00.000000Z"} +{"cron":"3-16 12 30 JUL ?","after":"2024-03-10T06:10:21.000000Z","tz":"America/New_York","next":"2024-07-30T16:03:00.000000Z"} +{"cron":"59-59/7 */15 26 7,8 5","after":"2024-03-10T06:53:46.000000Z","tz":"America/New_York","next":"2024-07-05T04:59:00.000000Z"} +{"cron":"15-30/4 * 14W 1 1-3","after":"2028-12-03T05:03:01.000000Z","tz":"America/St_Johns","next":"2029-01-01T03:45:00.000000Z"} +{"cron":"2-44/5 11,21 L */5 4","after":"2027-08-06T01:08:22.000000Z","tz":"America/Sao_Paulo","next":"2027-11-04T14:02:00.000000Z"} +{"cron":"* 11,9 14-14 5-6 MON","after":"2028-03-01T00:01:07.500000Z","tz":"Australia/Lord_Howe","next":"2028-05-01T00:30:00.000000Z"} +{"cron":"* 10,21 3-28/7 */9 6,3","after":"2024-10-27T02:51:21.000000Z","tz":"Europe/Budapest","next":"2024-10-30T09:00:00.000000Z"} +{"cron":"* 23 20-31/6 1-7 *","after":"2026-02-01T00:08:21.000000Z","tz":"Asia/Beirut","next":"2026-02-20T21:00:00.000000Z"} +{"cron":"20-21/7 8-16/7 23W 10-11/4 3-6/5","after":"2026-04-30T21:18:48.500000Z","tz":"Antarctica/Troll","next":"2026-10-07T06:20:00.000000Z"} +{"cron":"39,38 9 30 1,8 1L","after":"2028-03-25T23:30:43.500000Z","tz":"Europe/London","next":"2028-08-28T08:38:00.000000Z"} +{"cron":"16 * 28W * *","after":"2025-05-16T14:52:57.000000Z","tz":"Asia/Beirut","next":"2025-05-27T21:16:00.000000Z"} +{"cron":"54 */10 2W JUN 1-7","after":"2025-02-03T07:50:49.500000Z","tz":"Europe/Budapest","next":"2025-05-31T22:54:00.000000Z"} +{"cron":"52-58/3 5,19 22 7-10/1 5L","after":"2024-03-31T02:24:29.000000Z","tz":"Europe/Dublin","next":"2024-07-22T04:52:00.000000Z"} +{"cron":"15 23 19-27/3 8,6 FRI","after":"2028-12-09T12:02:28.500000Z","tz":"Asia/Tehran","next":"2029-06-01T19:45:00.000000Z"} +{"cron":"47 5-6/4 ? 4,3 0","after":"2027-10-31T01:19:21.000000Z","tz":"Antarctica/Troll","next":"2028-03-05T05:47:00.000000Z"} +{"cron":"53-55 */23 31-31 * 3-6/2","after":"2026-12-31T23:51:50.000000Z","tz":"America/Santiago","next":"2027-01-01T02:53:00.000000Z"} +{"cron":"48,30 12,21 */21 6-12/2 7#2","after":"2027-08-02T17:52:29.000000Z","tz":"Europe/Dublin","next":"2027-08-08T11:30:00.000000Z"} +{"cron":"51-52/4 5 30W DEC 0-7","after":"2026-10-03T13:33:09.500000Z","tz":"Australia/Lord_Howe","next":"2026-11-30T18:51:00.000000Z"} +{"cron":"36 8,2 18 1-10/2 5-5","after":"2026-11-01T05:22:57.000000Z","tz":"America/St_Johns","next":"2027-01-01T12:06:00.000000Z"} +{"cron":"12,10 */10 10,24 3-9 */6","after":"2028-03-01T00:26:27.500000Z","tz":"Europe/Budapest","next":"2028-03-03T23:10:00.000000Z"} +{"cron":"17-48/2 18 14 4-10 *","after":"2028-02-28T23:02:12.000000Z","tz":"Australia/Lord_Howe","next":"2028-04-14T07:47:00.000000Z"} +{"cron":"* * 27-30 6-7/5 *","after":"2028-09-09T04:02:12.000000Z","tz":"Asia/Tehran","next":"2029-06-26T20:30:00.000000Z"} +{"cron":"24-39/5 19-20/5 ? 4 7-7","after":"2028-11-05T04:08:55.500000Z","tz":"America/Havana","next":"2029-04-01T23:24:00.000000Z"} +{"cron":"8-26/6 * ? 7-9 ?","after":"2024-09-08T04:50:19.000000Z","tz":"America/Santiago","error":"InvalidArgumentException"} +{"cron":"2-46 14 ? 7,2 FRI","after":"2027-01-03T08:13:30.000000Z","tz":"Asia/Kolkata","next":"2027-02-05T08:32:00.000000Z"} +{"cron":"39,46 21-21/5 16,12 JUN 0","after":"2028-11-05T06:00:10.500000Z","tz":"America/New_York","next":"2029-06-04T01:39:00.000000Z"} +{"cron":"38,25 * 23-29 */10 ?","after":"2028-11-10T21:49:56.000000Z","tz":"Asia/Beirut","next":"2028-11-22T22:25:00.000000Z"} +{"cron":"51-56/7 16-23/1 27 8 THU","after":"2024-03-31T02:59:12.000000Z","tz":"Europe/Dublin","next":"2024-08-01T15:51:00.000000Z"} +{"cron":"32,46 15 ? */12 4-6/1","after":"2027-04-03T12:59:30.000000Z","tz":"Pacific/Chatham","next":"2028-01-01T01:47:00.000000Z"} +{"cron":"51-56 9-21/2 2W */6 6-7","after":"2028-03-26T00:47:36.500000Z","tz":"Europe/Budapest","next":"2028-07-01T07:51:00.000000Z"} +{"cron":"*/59 21,3 L 12-12/5 3L","after":"2026-04-30T00:54:56.000000Z","tz":"America/St_Johns","next":"2026-12-31T00:30:00.000000Z"} +{"cron":"5,46 * 22 10-11/4 *","after":"2028-04-01T12:59:17.000000Z","tz":"Pacific/Chatham","next":"2028-10-21T10:20:00.000000Z"} +{"cron":"@monthly","after":"2024-10-27T01:39:23.000000Z","tz":"Antarctica/Troll","next":"2024-11-01T00:00:00.000000Z"} +{"cron":"* * * 3-9 6L","after":"2025-12-03T08:43:10.000000Z","tz":"Asia/Kolkata","next":"2026-03-27T18:30:00.000000Z"} +{"cron":"@annually","after":"2028-02-28T22:01:48.000000Z","tz":"Pacific/Apia","next":"2028-12-31T11:00:00.000000Z"} +{"cron":"6 14-20 * 5-7 FRI","after":"2027-06-25T21:02:21.000000Z","tz":"Pacific/Chatham","next":"2027-07-02T01:21:00.000000Z"} +{"cron":"53,16 7-23/5 2 10,4 *","after":"2027-10-03T16:36:52.000000Z","tz":"America/Sao_Paulo","next":"2028-04-02T10:16:00.000000Z"} +{"cron":"*/57 * * SEP 5-7","after":"2026-04-09T19:47:19.000000Z","tz":"Europe/London","next":"2026-09-03T23:00:00.000000Z"} +{"cron":"4,43 2 L 3-3 7,0","after":"2028-03-01T00:30:59.000000Z","tz":"America/St_Johns","next":"2028-03-05T05:34:00.000000Z"} +{"cron":"6-54/2 0,15 L 9,7 *","after":"2027-07-31T08:15:30.500000Z","tz":"Australia/Lord_Howe","next":"2027-09-29T13:36:00.000000Z"} +{"cron":"@yearly","after":"2025-04-06T02:22:56.000000Z","tz":"America/Santiago","next":"2026-01-01T03:00:00.000000Z"} +{"cron":"27 22 29-31 3,11 7#2","after":"2025-09-06T18:44:57.000000Z","tz":"Asia/Beirut","next":"2025-11-09T20:27:00.000000Z"} +{"cron":"9-55 */5 9 * 6","after":"2025-10-26T02:38:02.000000Z","tz":"Europe/Dublin","next":"2025-11-01T00:09:00.000000Z"} +{"cron":"10 * L 9 */8","after":"2027-05-16T01:59:58.000000Z","tz":"Europe/Dublin","next":"2027-09-04T23:10:00.000000Z"} +{"cron":"*/35 14-20/1 ? 12-12 0,5","after":"2028-08-05T19:41:19.500000Z","tz":"Asia/Tehran","next":"2028-12-01T10:30:00.000000Z"} +{"cron":"* * 20W * ?","after":"2026-09-22T08:34:26.500000Z","tz":"America/Havana","next":"2026-10-20T04:00:00.000000Z"} +{"cron":"*/42 */6 1-25 10-10 0L","after":"2028-02-28T22:57:09.000000Z","tz":"Antarctica/Troll","next":"2028-09-30T22:00:00.000000Z"} +{"cron":"*/23 */16 ? 2,1 */3","after":"2027-03-01T00:41:03.000000Z","tz":"America/Sao_Paulo","next":"2028-01-01T03:00:00.000000Z"} +{"cron":"57-59 18,7 * 6 3#4","after":"2025-10-26T02:42:21.000000Z","tz":"Europe/London","next":"2026-06-24T17:57:00.000000Z"} +{"cron":"*/5 7-14 ? 8-8/2 *","after":"2028-10-28T23:19:37.500000Z","tz":"Europe/London","next":"2029-08-01T06:00:00.000000Z"} +{"cron":"* 23 29-31/6 3-9/6 1-4/4","after":"2028-11-01T17:46:12.000000Z","tz":"UTC","next":"2029-03-05T23:00:00.000000Z"} +{"cron":"* 5 */1 9 5L","after":"2024-11-03T05:32:27.000000Z","tz":"America/St_Johns","next":"2025-09-01T07:30:00.000000Z"} +{"cron":"45-48 0-4/2 16-19/3 MAR 5L","after":"2027-03-01T00:33:21.000000Z","tz":"UTC","next":"2027-03-16T00:45:00.000000Z"} +{"cron":"48 10-17 * * SAT","after":"2026-01-25T02:50:28.500000Z","tz":"America/New_York","next":"2026-01-31T15:48:00.000000Z"} +{"cron":"* 7-20 ? AUG 0#1","after":"2028-03-26T01:25:43.500000Z","tz":"Antarctica/Troll","next":"2028-08-06T05:00:00.000000Z"} +{"cron":"*/19 17 31W 11,10 5L","after":"2027-02-08T17:15:26.000000Z","tz":"Europe/London","next":"2027-10-29T16:00:00.000000Z"} +{"cron":"12,13 9-20 30-30/4 7-11/6 4-6/3","after":"2025-08-30T20:09:34.500000Z","tz":"UTC","next":"2026-07-02T09:12:00.000000Z"} +{"cron":"15 22 11-31 10-12 1#1","after":"2024-03-31T02:37:06.000000Z","tz":"Antarctica/Troll","next":"2024-10-07T20:15:00.000000Z"} +{"cron":"*/37 2-2 6,16 2-2 2-6","after":"2026-01-31T21:24:08.000000Z","tz":"America/Sao_Paulo","next":"2026-02-03T05:00:00.000000Z"} +{"cron":"* */17 2,25 9 SUN","after":"2024-03-10T05:02:05.000000Z","tz":"America/New_York","next":"2024-09-01T04:00:00.000000Z"} +{"cron":"5 8,21 2 5 5#4","after":"2025-03-09T05:19:56.500000Z","tz":"America/St_Johns","next":"2025-05-02T10:35:00.000000Z"} +{"cron":"38 14-20/2 26-31 MAR 5-7","after":"2027-03-01T00:18:06.000000Z","tz":"Asia/Kolkata","next":"2027-03-05T09:08:00.000000Z"} +{"cron":"36-36 17-23/6 ? MAY SAT","after":"2026-04-04T13:58:55.000000Z","tz":"Pacific/Chatham","next":"2026-05-02T04:51:00.000000Z"} +{"cron":"30-33/2 18-22 L FEB */2","after":"2027-01-01T00:59:53.000000Z","tz":"Asia/Kolkata","next":"2027-02-02T13:00:00.000000Z"} +{"cron":"47 7 14 8-12 2-2","after":"2028-05-07T00:35:30.000000Z","tz":"Pacific/Apia","next":"2028-07-31T18:47:00.000000Z"} +{"cron":"27,19 11 L * WED","after":"2028-02-29T00:18:03.000000Z","tz":"Asia/Kolkata","next":"2028-02-29T05:49:00.000000Z"} +{"cron":"*/44 9 10-12 9 6,1","after":"2026-03-29T02:15:32.000000Z","tz":"Europe/London","next":"2026-09-05T08:00:00.000000Z"} +{"cron":"*/51 */12 1 3,5 5","after":"2024-04-07T03:14:21.500000Z","tz":"America/Santiago","next":"2024-05-01T04:00:00.000000Z"} +{"cron":"* 19-19 ? 12-12/1 3-5/2","after":"2026-04-30T22:32:09.500000Z","tz":"Australia/Lord_Howe","next":"2026-12-02T08:00:00.000000Z"} +{"cron":"* 0 23W * SAT","after":"2025-03-29T21:15:33.000000Z","tz":"Asia/Beirut","next":"2025-04-04T21:00:00.000000Z"} +{"cron":"@hourly","after":"2024-09-08T03:35:15.500000Z","tz":"America/Santiago","next":"2024-09-08T04:00:00.000000Z"} +{"cron":"15 * ? 10 */4","after":"2027-03-14T05:37:42.000000Z","tz":"America/New_York","next":"2027-10-03T04:15:00.000000Z"} +{"cron":"26-45 10-10 1 */2 SUN","after":"2025-07-22T05:25:23.000000Z","tz":"UTC","next":"2025-07-27T10:26:00.000000Z"} +{"cron":"21 12-16/7 10 */8 *","after":"2028-02-29T22:45:54.000000Z","tz":"Europe/Dublin","next":"2028-09-10T11:21:00.000000Z"} +{"cron":"6-21/3 * 3 4-9/5 */8","after":"2027-03-06T00:14:48.000000Z","tz":"Antarctica/Troll","next":"2027-04-02T22:06:00.000000Z"} +{"cron":"48-54 15,20 1 6,1 5#3","after":"2026-10-25T00:29:10.500000Z","tz":"Europe/London","next":"2027-01-01T15:48:00.000000Z"} +{"cron":"54,37 4-15 6 APR 7L","after":"2026-05-03T03:13:15.000000Z","tz":"America/Sao_Paulo","next":"2027-04-06T07:37:00.000000Z"} +{"cron":"4,17 9 8 */4 5-6/3","after":"2025-10-28T08:26:43.500000Z","tz":"Asia/Kolkata","next":"2026-01-02T03:34:00.000000Z"} +{"cron":"12 5,13 18W 11-12 */5","after":"2024-03-10T03:37:25.000000Z","tz":"America/St_Johns","next":"2024-11-01T07:42:00.000000Z"} +{"cron":"3 */7 * * 7#3","after":"2028-10-28T20:01:20.000000Z","tz":"Asia/Beirut","next":"2028-11-18T22:03:00.000000Z"} +{"cron":"24 */6 * 11-11 */1","after":"2027-05-01T02:24:39.500000Z","tz":"America/Santiago","next":"2027-11-01T03:24:00.000000Z"} +{"cron":"*/13 19,13 3-27/6 11,3 */8","after":"2028-02-28T21:04:59.000000Z","tz":"Europe/Dublin","next":"2028-03-03T19:00:00.000000Z"} +{"cron":"*/7 13 25,10 OCT MON","after":"2028-05-31T05:21:23.000000Z","tz":"UTC","next":"2028-10-02T13:00:00.000000Z"} +{"cron":"24,18 */14 ? 12,10 7-7","after":"2026-10-03T14:44:21.500000Z","tz":"Australia/Lord_Howe","next":"2026-10-04T03:18:00.000000Z"} +{"cron":"46 3 4 */9 6-6","after":"2028-12-21T10:57:29.500000Z","tz":"America/St_Johns","next":"2029-01-04T07:16:00.000000Z"} +{"cron":"@midnight","after":"2027-03-14T03:52:34.000000Z","tz":"America/St_Johns","next":"2027-03-15T02:30:00.000000Z"} +{"cron":"35-38/4 20-22/5 23 JAN 0#2","after":"2028-10-28T23:36:57.000000Z","tz":"Europe/Budapest","next":"2029-01-14T19:35:00.000000Z"} +{"cron":"* 2 14 4-6/1 4-4","after":"2028-03-25T23:38:11.000000Z","tz":"Europe/Budapest","next":"2028-04-06T00:00:00.000000Z"} +{"cron":"52,37 6-17 ? * 4L","after":"2028-03-12T19:33:08.500000Z","tz":"Asia/Beirut","next":"2028-03-30T03:37:00.000000Z"} +{"cron":"2-27/3 17,10 5 MAY TUE","after":"2025-01-12T16:35:03.000000Z","tz":"Asia/Kolkata","next":"2025-05-05T11:32:00.000000Z"} +{"cron":"46 13 31,29 3-7/3 */7","after":"2028-07-24T23:03:12.000000Z","tz":"UTC","next":"2029-03-04T13:46:00.000000Z"} +{"cron":"*/4 19-20/5 2W FEB 4L","after":"2024-11-03T03:57:30.500000Z","tz":"America/Havana","next":"2025-02-04T00:00:00.000000Z"} +{"cron":"@weekly","after":"2028-03-01T00:01:33.000000Z","tz":"America/Havana","next":"2028-03-05T05:00:00.000000Z"} +{"cron":"35 * 26-27/3 * 4#2","after":"2024-10-27T00:45:50.000000Z","tz":"Europe/Budapest","next":"2024-11-13T23:35:00.000000Z"} +{"cron":"9 0 1-2/2 11-12 6L","after":"2027-04-04T02:47:14.000000Z","tz":"America/Santiago","next":"2027-11-01T03:09:00.000000Z"} +{"cron":"* 14-16/7 */5 */10 1,3","after":"2025-11-02T12:38:19.000000Z","tz":"America/Sao_Paulo","next":"2025-11-03T17:00:00.000000Z"} +{"cron":"25 16,5 * 3-12/5 1#4","after":"2027-09-05T03:54:21.500000Z","tz":"America/Santiago","next":"2028-03-27T19:25:00.000000Z"} +{"cron":"19-22/6 12 15-15/5 3-6 1#3","after":"2026-10-29T11:44:52.000000Z","tz":"America/Sao_Paulo","next":"2027-03-15T15:19:00.000000Z"} +{"cron":"6 22 */21 10,7 3#1","after":"2027-02-28T22:33:03.000000Z","tz":"Asia/Kolkata","next":"2027-07-01T16:36:00.000000Z"} +{"cron":"@monthly","after":"2028-03-01T00:52:10.500000Z","tz":"Asia/Kolkata","next":"2028-03-31T18:30:00.000000Z"} +{"cron":"46 20-22/1 7-17 */2 *","after":"2026-12-31T21:28:32.000000Z","tz":"Australia/Lord_Howe","next":"2027-01-07T09:46:00.000000Z"} +{"cron":"25 4,2 2,11 * 3-4/5","after":"2028-02-28T22:56:56.500000Z","tz":"Pacific/Apia","next":"2028-02-29T15:25:00.000000Z"} +{"cron":"30,48 5 11-30/4 4-7 *","after":"2027-12-08T02:45:20.000000Z","tz":"America/St_Johns","next":"2028-04-11T08:00:00.000000Z"} +{"cron":"33-33 11-17 26-30/5 */1 0L","after":"2024-03-31T02:31:14.000000Z","tz":"Europe/Dublin","next":"2024-03-31T10:33:00.000000Z"} +{"cron":"18,23 0 16W 11-11/7 WED","after":"2028-02-28T21:03:40.500000Z","tz":"America/Sao_Paulo","next":"2028-11-01T03:18:00.000000Z"} +{"cron":"* 2-8/4 7,8 9-12 4-6/3","after":"2025-04-06T02:56:59.000000Z","tz":"America/Santiago","next":"2025-09-04T06:00:00.000000Z"} +{"cron":"* 11 * JUL 6,7","after":"2028-02-29T22:05:56.000000Z","tz":"Europe/London","next":"2028-07-01T10:00:00.000000Z"} +{"cron":"58-59 12,13 ? FEB 3-3/6","after":"2028-06-11T12:02:56.000000Z","tz":"Asia/Kolkata","next":"2029-02-07T07:28:00.000000Z"} +{"cron":"@annually","after":"2026-03-08T13:15:50.000000Z","tz":"Asia/Tehran","next":"2026-12-31T20:30:00.000000Z"} +{"cron":"19,38 5-15/3 1-19/2 4-9 *","after":"2028-09-17T03:24:04.000000Z","tz":"UTC","next":"2028-09-17T05:19:00.000000Z"} +{"cron":"29 20,19 14W 8-11 *","after":"2028-04-01T13:15:42.000000Z","tz":"Australia/Lord_Howe","next":"2028-08-14T09:59:00.000000Z"} +{"cron":"@annually","after":"2028-10-28T23:34:28.000000Z","tz":"Europe/Dublin","next":"2029-01-01T00:00:00.000000Z"} +{"cron":"*/44 4 11-30 3 *","after":"2027-11-23T19:24:01.000000Z","tz":"Pacific/Apia","next":"2028-03-10T15:00:00.000000Z"} +{"cron":"* 22 ? * 7-7/2","after":"2025-04-05T15:27:54.000000Z","tz":"Pacific/Chatham","next":"2025-04-08T09:15:00.000000Z"} +{"cron":"46 */15 3-30/2 2-9/1 */8","after":"2027-11-07T05:16:59.500000Z","tz":"America/Havana","next":"2028-02-03T05:46:00.000000Z"} +{"cron":"55-59/2 */25 * 11-11/4 */9","after":"2024-03-10T04:27:21.000000Z","tz":"America/Havana","next":"2024-11-04T06:55:00.000000Z"} +{"cron":"* 16-21/3 */26 */8 5-6/5","after":"2028-01-29T11:21:37.000000Z","tz":"America/Havana","next":"2028-09-01T20:00:00.000000Z"} +{"cron":"54-56/2 * 19 1,10 TUE","after":"2025-04-05T12:44:58.500000Z","tz":"Pacific/Chatham","next":"2025-10-06T11:09:00.000000Z"} +{"cron":"38 */5 12,3 * ?","after":"2024-04-07T03:46:38.000000Z","tz":"America/Santiago","next":"2024-04-12T04:38:00.000000Z"} +{"cron":"46 6-21/5 22W 2-6 2,5","after":"2025-03-19T13:34:01.000000Z","tz":"Pacific/Chatham","next":"2025-03-20T17:01:00.000000Z"} +{"cron":"22-25/3 18-18/4 22 */7 SUN","after":"2026-11-29T17:37:58.000000Z","tz":"America/Santiago","next":"2027-01-03T21:22:00.000000Z"} +{"cron":"0-2 0 */22 9-10 ?","after":"2024-10-27T00:38:55.000000Z","tz":"Europe/Budapest","next":"2025-08-31T22:00:00.000000Z"} +{"cron":"4 1,15 L */5 WED","after":"2026-06-22T13:09:34.500000Z","tz":"America/Havana","next":"2026-06-24T05:04:00.000000Z"} +{"cron":"10,15 6-23/6 ? 3-9 */9","after":"2024-10-05T14:47:02.000000Z","tz":"Australia/Lord_Howe","next":"2025-03-02T19:10:00.000000Z"} +{"cron":"28-33/7 21,7 15 6-7 4","after":"2027-03-14T08:02:33.500000Z","tz":"America/New_York","next":"2027-06-04T01:28:00.000000Z"} +{"cron":"*/24 1 31,14 * 6#4","after":"2028-02-28T22:47:53.500000Z","tz":"Europe/Budapest","next":"2028-03-14T00:00:00.000000Z"} +{"cron":"@yearly","after":"2028-02-28T23:22:47.000000Z","tz":"America/Santiago","next":"2029-01-01T03:00:00.000000Z"} +{"cron":"21,34 17,2 5,25 */13 6-7/5","after":"2028-09-06T17:43:46.000000Z","tz":"Asia/Kolkata","next":"2029-02-03T11:51:00.000000Z"} +{"cron":"7 23,1 20 * 7#2","after":"2024-09-28T12:05:48.000000Z","tz":"Pacific/Chatham","next":"2024-10-13T09:22:00.000000Z"} +{"cron":"19 14-17 * 3-8 7-7","after":"2028-02-29T22:41:05.000000Z","tz":"Asia/Kolkata","next":"2028-03-01T08:49:00.000000Z"} +{"cron":"48,46 12 * 4-8/4 3-5/5","after":"2025-03-08T16:11:22.000000Z","tz":"Europe/London","next":"2025-04-02T11:46:00.000000Z"} +{"cron":"*/45 */22 L 2 */8","after":"2026-06-19T08:42:26.000000Z","tz":"Asia/Tehran","next":"2027-02-06T20:30:00.000000Z"} +{"cron":"27-45/7 17-19 3,4 10,10 SUN","after":"2027-02-28T23:13:46.000000Z","tz":"Asia/Tehran","next":"2027-10-03T13:57:00.000000Z"} +{"cron":"56-57/2 * 22W 4,12 4-4","after":"2026-04-30T22:01:40.000000Z","tz":"Europe/London","next":"2026-04-30T22:56:00.000000Z"} +{"cron":"*/61 */9 14 */10 2#4","after":"2025-06-16T03:06:26.000000Z","tz":"Asia/Tehran","next":"2025-11-13T20:31:00.000000Z"} +{"cron":"57 22 8W 10,5 6#2","after":"2025-06-26T03:29:35.500000Z","tz":"America/Sao_Paulo","next":"2025-10-09T01:57:00.000000Z"} +{"cron":"51 */10 L 11-11 ?","after":"2026-06-16T16:45:49.500000Z","tz":"America/New_York","next":"2026-11-30T05:51:00.000000Z"} +{"cron":"53,28 * 25W 6 6-7","after":"2026-01-31T21:40:46.000000Z","tz":"Australia/Lord_Howe","next":"2026-06-05T13:58:00.000000Z"} +{"cron":"26 6,6 4 */8 7#4","after":"2026-07-23T06:37:40.000000Z","tz":"Pacific/Apia","next":"2026-09-03T17:26:00.000000Z"} +{"cron":"* 2-12/4 21-25/4 11-12/2 ?","after":"2026-01-14T14:40:33.500000Z","tz":"Pacific/Apia","next":"2026-11-20T13:00:00.000000Z"} +{"cron":"4 * * */10 SUN","after":"2025-10-26T02:49:36.500000Z","tz":"Europe/Dublin","next":"2025-11-02T00:04:00.000000Z"} +{"cron":"* 12-13/4 * 4 3","after":"2028-02-28T22:28:48.000000Z","tz":"Asia/Beirut","next":"2028-04-05T09:00:00.000000Z"} +{"cron":"49-55/2 9-20 ? 7,2 *","after":"2025-03-29T23:57:19.000000Z","tz":"Europe/Dublin","next":"2025-07-01T08:49:00.000000Z"} +{"cron":"43-59/5 * 30,29 1-8 1L","after":"2027-09-25T03:23:38.000000Z","tz":"America/New_York","next":"2028-01-29T05:43:00.000000Z"} +{"cron":"51-59 * 20-29 * *","after":"2028-03-12T06:41:40.500000Z","tz":"America/Havana","next":"2028-03-20T04:51:00.000000Z"} +{"cron":"51 * 6-18/3 5,5 FRI","after":"2026-12-31T23:43:51.000000Z","tz":"Australia/Lord_Howe","next":"2027-05-05T14:21:00.000000Z"} +{"cron":"48 2 */26 */13 WED","after":"2025-06-01T17:35:44.000000Z","tz":"Pacific/Apia","next":"2026-01-31T13:48:00.000000Z"} +{"cron":"*/6 13 3,9 3-7/6 *","after":"2026-12-31T22:01:09.000000Z","tz":"Pacific/Chatham","next":"2027-03-02T23:15:00.000000Z"} +{"cron":"12-30/7 6 9-26/6 * 5","after":"2026-05-01T00:29:01.000000Z","tz":"Pacific/Apia","next":"2026-05-07T17:12:00.000000Z"} +{"cron":"12,14 1-19 * FEB 0","after":"2026-07-17T17:06:52.000000Z","tz":"Europe/Budapest","next":"2027-02-07T00:12:00.000000Z"} +{"cron":"* 19 13-27/1 6 2-6/7","after":"2027-02-10T08:37:04.000000Z","tz":"UTC","next":"2027-06-01T19:00:00.000000Z"} +{"cron":"*/3 8,9 16,20 10-10 6","after":"2026-09-16T10:26:20.000000Z","tz":"America/Santiago","next":"2026-10-03T11:00:00.000000Z"} +{"cron":"49,54 * */19 8-9/5 1","after":"2028-06-27T20:18:09.500000Z","tz":"Asia/Kolkata","next":"2028-07-31T19:19:00.000000Z"} +{"cron":"16 19 19-25 FEB */8","after":"2025-11-15T20:58:00.000000Z","tz":"UTC","next":"2026-02-01T19:16:00.000000Z"} +{"cron":"43-56/6 15 20-23/3 2,4 1,0","after":"2026-12-31T22:28:48.000000Z","tz":"America/St_Johns","next":"2027-02-01T19:13:00.000000Z"} +{"cron":"* 18 12-23 * 2-2/6","after":"2028-08-19T18:50:03.000000Z","tz":"Pacific/Apia","next":"2028-08-20T05:00:00.000000Z"} +{"cron":"44 22-22 * 6-12/6 6-6","after":"2025-10-25T23:09:11.000000Z","tz":"Antarctica/Troll","next":"2025-12-06T22:44:00.000000Z"} +{"cron":"22-57 2 14W 2-10/2 6#2","after":"2027-10-31T00:59:50.000000Z","tz":"Europe/London","next":"2028-02-12T02:22:00.000000Z"} +{"cron":"46 21 14W */7 */7","after":"2028-12-21T15:12:08.000000Z","tz":"Asia/Tehran","next":"2029-01-07T18:16:00.000000Z"} +{"cron":"*/16 * 11-21/7 */14 */6","after":"2025-03-30T10:45:28.000000Z","tz":"America/Santiago","next":"2025-03-30T10:48:00.000000Z"} +{"cron":"14-51/7 11,4 5 JUL 7#1","after":"2026-04-30T22:48:35.000000Z","tz":"Asia/Tehran","next":"2026-07-05T07:44:00.000000Z"} +{"cron":"8,47 */10 4W 2,6 4,0","after":"2026-05-01T00:52:52.000000Z","tz":"America/Santiago","next":"2026-06-04T04:08:00.000000Z"} +{"cron":"31,31 10-16 8,13 */12 */9","after":"2025-11-28T19:53:31.000000Z","tz":"Asia/Kolkata","next":"2026-01-05T05:01:00.000000Z"} +{"cron":"46-51 */14 26 APR */2","after":"2025-04-05T16:34:01.000000Z","tz":"Australia/Lord_Howe","next":"2025-04-06T04:16:00.000000Z"} +{"cron":"31,59 */7 21W FEB 5","after":"2028-11-07T10:00:41.500000Z","tz":"UTC","next":"2029-02-02T00:31:00.000000Z"} +{"cron":"54 9 * */12 2#2","after":"2027-11-16T10:56:20.000000Z","tz":"Asia/Kolkata","next":"2028-01-11T04:24:00.000000Z"} +{"cron":"54,30 20 30,22 */1 ?","after":"2026-10-04T08:35:08.500000Z","tz":"Europe/Budapest","next":"2026-10-22T18:30:00.000000Z"} +{"cron":"29 18-20 ? */9 5,2","after":"2026-01-31T22:48:04.500000Z","tz":"Europe/Budapest","next":"2026-10-02T16:29:00.000000Z"} +{"cron":"14-34/5 8 19 OCT 5,7","after":"2028-02-28T21:09:20.500000Z","tz":"Pacific/Chatham","next":"2028-09-30T18:29:00.000000Z"} +{"cron":"*/46 0-4/3 20-22/3 4 3L","after":"2027-07-25T10:18:51.000000Z","tz":"Europe/Budapest","next":"2028-04-19T22:00:00.000000Z"} +{"cron":"22 0-8 7-21/2 JUL ?","after":"2028-08-19T20:22:37.000000Z","tz":"America/Havana","next":"2029-07-07T04:22:00.000000Z"} +{"cron":"18-34 14 7 * *","after":"2026-04-30T22:49:03.000000Z","tz":"Pacific/Chatham","next":"2026-05-07T01:33:00.000000Z"} +{"cron":"20,35 2 26W APR 7L","after":"2027-12-23T00:52:14.000000Z","tz":"Pacific/Chatham","next":"2028-04-25T13:35:00.000000Z"} +{"cron":"41-42 8 ? * 0-4","after":"2028-04-17T13:30:49.500000Z","tz":"UTC","next":"2028-04-18T08:41:00.000000Z"} +{"cron":"57,26 20 16 11-11/6 */2","after":"2026-03-28T20:13:19.000000Z","tz":"Asia/Beirut","next":"2026-11-01T18:26:00.000000Z"} +{"cron":"*/36 2-13 6 * 0,1","after":"2028-03-26T01:55:32.000000Z","tz":"Europe/London","next":"2028-03-26T02:00:00.000000Z"} +{"cron":"*/5 17-22 22-30 MAR 1#2","after":"2025-12-05T15:09:04.000000Z","tz":"Pacific/Apia","next":"2026-03-09T04:00:00.000000Z"} +{"cron":"28 16 17,13 * 6#1","after":"2026-04-30T21:17:35.000000Z","tz":"Pacific/Chatham","next":"2026-05-02T03:43:00.000000Z"} +{"cron":"36 */9 3,29 FEB 3","after":"2025-04-14T20:24:38.500000Z","tz":"Europe/Budapest","next":"2026-02-02T23:36:00.000000Z"} +{"cron":"* 11-20/6 */22 */4 4-6/5","after":"2028-02-29T17:52:03.000000Z","tz":"Europe/Budapest","next":"2028-05-01T09:00:00.000000Z"} +{"cron":"* * ? 1,3 ?","after":"2027-10-31T00:17:14.000000Z","tz":"Antarctica/Troll","error":"InvalidArgumentException"} +{"cron":"49 * * 4,8 1L","after":"2028-02-29T22:27:12.000000Z","tz":"Pacific/Apia","next":"2028-04-23T11:49:00.000000Z"} +{"cron":"18 15-23/5 25-28/2 DEC ?","after":"2026-05-01T00:11:38.500000Z","tz":"America/New_York","next":"2026-12-25T20:18:00.000000Z"} +{"cron":"57 */11 20-23/7 5-11/3 */4","after":"2026-02-01T00:18:16.000000Z","tz":"America/St_Johns","next":"2026-05-03T03:27:00.000000Z"} +{"cron":"48-48 */6 ? JAN */1","after":"2028-02-28T23:05:43.500000Z","tz":"Europe/Budapest","next":"2028-12-31T23:48:00.000000Z"} +{"cron":"12 0 13 1 *","after":"2025-03-30T00:36:18.000000Z","tz":"Europe/Dublin","next":"2026-01-13T00:12:00.000000Z"} +{"cron":"45 * 15W 1,6 7","after":"2028-02-28T22:41:13.500000Z","tz":"Asia/Beirut","next":"2028-06-03T21:45:00.000000Z"} +{"cron":"*/50 18,0 2 11-11/4 1#5","after":"2025-03-30T01:50:31.000000Z","tz":"Antarctica/Troll","next":"2025-11-02T00:00:00.000000Z"} +{"cron":"45 11-17/3 7 */2 1#1","after":"2024-04-06T14:13:40.000000Z","tz":"Pacific/Chatham","next":"2024-05-05T23:00:00.000000Z"} +{"cron":"35-55 */23 L 9 1","after":"2026-11-01T05:03:57.000000Z","tz":"America/St_Johns","next":"2027-09-06T03:05:00.000000Z"} +{"cron":"6,56 * L * ?","after":"2026-07-01T00:17:02.500000Z","tz":"Europe/London","next":"2026-07-30T23:06:00.000000Z"} +{"cron":"*/19 13-20/3 16,12 */7 7","after":"2024-04-07T01:01:30.000000Z","tz":"America/Santiago","next":"2024-08-04T17:00:00.000000Z"} +{"cron":"28 */12 7 * 1-6","after":"2027-02-28T22:08:22.000000Z","tz":"Pacific/Apia","next":"2027-02-28T23:28:00.000000Z"} +{"cron":"46 1-5 L 9 *","after":"2028-02-09T16:29:57.000000Z","tz":"America/Havana","next":"2028-09-30T05:46:00.000000Z"} +{"cron":"11 4 16,12 3-11/1 0,2","after":"2027-08-03T23:09:01.000000Z","tz":"America/Sao_Paulo","next":"2027-08-08T07:11:00.000000Z"} +{"cron":"19 13,2 ? 6,4 7L","after":"2026-09-17T19:18:02.000000Z","tz":"Australia/Lord_Howe","next":"2027-04-25T02:49:00.000000Z"} +{"cron":"29 20-23 L 8 0-7","after":"2027-07-06T19:27:40.500000Z","tz":"Antarctica/Troll","next":"2027-08-01T18:29:00.000000Z"} +{"cron":"1 */24 */6 7,2 0,1","after":"2024-03-31T00:38:44.000000Z","tz":"Europe/Dublin","next":"2024-06-30T23:01:00.000000Z"} +{"cron":"36-59 */6 13,19 12-12 *","after":"2025-10-25T23:31:38.000000Z","tz":"Europe/Dublin","next":"2025-12-13T00:36:00.000000Z"} +{"cron":"37 15 26 SEP 7-7","after":"2026-12-31T23:31:07.000000Z","tz":"Pacific/Chatham","next":"2027-09-01T02:52:00.000000Z"} +{"cron":"*/7 3 L 6-7/3 ?","after":"2026-12-25T11:52:07.000000Z","tz":"Asia/Tehran","next":"2027-06-29T23:30:00.000000Z"} +{"cron":"52 9 26-27 11-12 ?","after":"2024-03-10T06:51:25.000000Z","tz":"America/Havana","next":"2024-11-26T14:52:00.000000Z"} +{"cron":"17 20 14 2-5/6 0-0/4","after":"2027-02-28T22:39:06.000000Z","tz":"Asia/Tehran","next":"2028-02-06T16:47:00.000000Z"} +{"cron":"*/41 15-18 L NOV */1","after":"2027-02-01T14:50:11.500000Z","tz":"Pacific/Chatham","next":"2027-11-01T01:15:00.000000Z"} +{"cron":"26-26/1 6-17 L */10 6","after":"2028-06-17T07:16:14.000000Z","tz":"Asia/Kolkata","next":"2028-11-04T00:56:00.000000Z"} +{"cron":"17 1 29-30 */12 WED","after":"2025-09-26T19:00:35.500000Z","tz":"Asia/Beirut","next":"2026-01-06T23:17:00.000000Z"} +{"cron":"0-19 6 */29 */1 1L","after":"2024-09-08T04:38:54.000000Z","tz":"America/Santiago","next":"2024-09-30T09:00:00.000000Z"} +{"cron":"24-50 22 2,5 4-10 7-7","after":"2027-04-03T14:25:01.000000Z","tz":"Australia/Lord_Howe","next":"2027-04-04T11:54:00.000000Z"} +{"cron":"17 17-20/1 28-30 APR 0L","after":"2026-02-01T00:23:49.000000Z","tz":"America/New_York","next":"2026-04-26T21:17:00.000000Z"} +{"cron":"1 * L 11-12 ?","after":"2028-04-01T15:34:47.000000Z","tz":"Pacific/Chatham","next":"2028-11-29T10:16:00.000000Z"} +{"cron":"@yearly","after":"2025-07-11T17:13:31.000000Z","tz":"Pacific/Apia","next":"2025-12-31T11:00:00.000000Z"} +{"cron":"* 21-22/4 ? 10 */5","after":"2026-09-29T13:38:42.000000Z","tz":"America/Santiago","next":"2026-10-03T00:00:00.000000Z"} +{"cron":"13 10,7 * 10-11 *","after":"2026-03-08T05:37:42.000000Z","tz":"America/St_Johns","next":"2026-10-01T12:43:00.000000Z"} +{"cron":"29-30 11-14/2 ? APR 0-2","after":"2027-01-11T15:17:40.500000Z","tz":"Pacific/Chatham","next":"2027-04-03T22:44:00.000000Z"} +{"cron":"5 4 25 */13 *","after":"2027-10-23T15:16:30.000000Z","tz":"America/Havana","next":"2028-02-25T09:05:00.000000Z"} +{"cron":"10,17 0 2,29 * ?","after":"2026-02-01T00:30:08.000000Z","tz":"Asia/Kolkata","next":"2026-02-01T18:40:00.000000Z"} +{"cron":"23,31 18-21 */33 10 */4","after":"2028-03-12T07:23:39.000000Z","tz":"America/St_Johns","next":"2028-10-01T20:53:00.000000Z"} +{"cron":"* 16,3 3W 8-12/4 FRI","after":"2025-01-25T01:27:58.500000Z","tz":"Europe/London","next":"2025-08-01T15:00:00.000000Z"} +{"cron":"6-14/2 */10 L 10,9 TUE","after":"2026-10-25T01:51:46.000000Z","tz":"Europe/Dublin","next":"2026-10-27T00:06:00.000000Z"} +{"cron":"* 11-20/4 13 8-10 SAT","after":"2026-05-01T00:14:22.000000Z","tz":"Antarctica/Troll","next":"2026-08-01T09:00:00.000000Z"} +{"cron":"*/28 9 4-17 5 ?","after":"2025-12-02T01:47:28.500000Z","tz":"Asia/Kolkata","next":"2026-05-04T03:30:00.000000Z"} +{"cron":"23 21 19-21 6-10/1 2#3","after":"2025-10-04T13:34:21.000000Z","tz":"Australia/Lord_Howe","next":"2025-10-19T10:23:00.000000Z"} +{"cron":"23-23/7 19-19/4 ? 8-10 ?","after":"2027-01-01T00:15:58.500000Z","tz":"America/Sao_Paulo","error":"InvalidArgumentException"} +{"cron":"41-54 2 15W * *","after":"2027-10-31T01:03:04.000000Z","tz":"Europe/Budapest","next":"2027-11-15T01:41:00.000000Z"} +{"cron":"37 13-19/6 27W */7 3L","after":"2028-02-28T23:23:25.500000Z","tz":"Australia/Lord_Howe","next":"2028-08-28T03:07:00.000000Z"} +{"cron":"19-55/2 3-7/2 */29 1-10/6 4-6","after":"2026-03-28T23:04:40.500000Z","tz":"Europe/Dublin","next":"2026-07-01T02:19:00.000000Z"} +{"cron":"23 22 22-28/6 11-12 MON","after":"2026-06-10T11:27:14.000000Z","tz":"Asia/Kolkata","next":"2026-11-02T16:53:00.000000Z"} +{"cron":"8-50 * 17 6,4 SAT","after":"2027-01-09T23:48:07.000000Z","tz":"Asia/Beirut","next":"2027-04-02T21:08:00.000000Z"} +{"cron":"16-41/3 5,7 7-25/2 */1 THU","after":"2026-09-09T19:14:57.000000Z","tz":"Australia/Lord_Howe","next":"2026-09-09T20:46:00.000000Z"} +{"cron":"36-56 11 24-26/4 6-7 4,5","after":"2027-02-28T23:43:20.500000Z","tz":"Australia/Lord_Howe","next":"2027-06-03T01:06:00.000000Z"} +{"cron":"51-53 4 L 4 0-2","after":"2026-08-15T03:01:47.000000Z","tz":"America/Sao_Paulo","next":"2027-04-04T07:51:00.000000Z"} +{"cron":"45-58/7 9-19/4 6 12-12 WED","after":"2027-01-01T00:04:28.000000Z","tz":"Europe/Budapest","next":"2027-12-01T08:45:00.000000Z"} +{"cron":"50 21-22 5-28/2 8 FRI","after":"2026-12-13T13:38:17.000000Z","tz":"America/New_York","next":"2027-08-06T01:50:00.000000Z"} +{"cron":"37-40/1 9 ? 9-10/3 0","after":"2027-07-27T18:33:36.000000Z","tz":"Asia/Kolkata","next":"2027-09-05T04:07:00.000000Z"} +{"cron":"24,44 3 */12 APR 2#2","after":"2025-10-25T23:10:32.500000Z","tz":"Europe/Budapest","next":"2026-04-01T01:24:00.000000Z"} +{"cron":"58-59/5 5,4 L 5-10/6 0-2/3","after":"2026-09-03T04:17:47.000000Z","tz":"UTC","next":"2027-05-02T05:58:00.000000Z"} +{"cron":"*/48 7-10/1 * 5-6 *","after":"2026-02-07T17:01:39.500000Z","tz":"Pacific/Apia","next":"2026-04-30T18:00:00.000000Z"} +{"cron":"49,34 20 L JUN */4","after":"2027-03-28T00:36:42.500000Z","tz":"Europe/Budapest","next":"2027-06-03T18:34:00.000000Z"} +{"cron":"27 7 4-16 3-4/3 ?","after":"2025-03-09T07:13:15.000000Z","tz":"America/St_Johns","next":"2025-03-09T09:57:00.000000Z"} +{"cron":"10-26/6 11,0 ? * 5#1","after":"2028-02-29T21:30:24.000000Z","tz":"Europe/Dublin","next":"2028-03-03T00:10:00.000000Z"} +{"cron":"59-59 * ? 6-6 1-1/1","after":"2025-04-05T14:24:30.000000Z","tz":"Pacific/Chatham","next":"2025-06-01T12:14:00.000000Z"} +{"cron":"21 0 20W 10-10 ?","after":"2026-05-02T20:42:18.000000Z","tz":"America/New_York","next":"2026-10-20T04:21:00.000000Z"} +{"cron":"*/52 * 31 3,2 *","after":"2026-11-04T00:53:19.000000Z","tz":"America/New_York","next":"2027-03-31T04:00:00.000000Z"} +{"cron":"@yearly","after":"2027-03-28T02:11:58.000000Z","tz":"Europe/Dublin","next":"2028-01-01T00:00:00.000000Z"} +{"cron":"14 6 ? 11 0-5/3","after":"2025-09-26T18:31:01.000000Z","tz":"Europe/Dublin","next":"2025-11-02T06:14:00.000000Z"} +{"cron":"* */3 ? * 6-6","after":"2025-08-01T00:10:57.000000Z","tz":"Asia/Tehran","next":"2025-08-01T20:30:00.000000Z"} +{"cron":"40,25 15 * 8-9 7-7/1","after":"2026-01-31T23:37:30.000000Z","tz":"Europe/Dublin","next":"2026-08-01T14:25:00.000000Z"} +{"cron":"54 23 25 */14 1L","after":"2025-03-29T23:50:36.000000Z","tz":"Antarctica/Troll","next":"2025-03-31T21:54:00.000000Z"} +{"cron":"33-51 1 29 * *","after":"2026-07-21T23:31:04.000000Z","tz":"America/New_York","next":"2026-07-29T05:33:00.000000Z"} +{"cron":"0 17 5W * 1,2","after":"2026-12-31T22:06:58.000000Z","tz":"Pacific/Chatham","next":"2027-01-04T03:15:00.000000Z"} +{"cron":"43 13 L 9-10/6 4,4","after":"2024-03-30T23:51:14.000000Z","tz":"Antarctica/Troll","next":"2024-09-05T11:43:00.000000Z"} +{"cron":"*/60 10,22 8W 10 3L","after":"2027-03-01T00:37:20.000000Z","tz":"Europe/Budapest","next":"2027-10-08T08:00:00.000000Z"} +{"cron":"39 */21 L JUN 7#3","after":"2026-04-24T01:34:27.000000Z","tz":"Asia/Tehran","next":"2026-06-20T21:09:00.000000Z"} +{"cron":"33 */15 6 2 *","after":"2025-03-09T07:21:03.000000Z","tz":"America/St_Johns","next":"2026-02-06T04:03:00.000000Z"} +{"cron":"11 3 12 */3 ?","after":"2026-11-01T05:23:14.000000Z","tz":"America/New_York","next":"2027-01-12T08:11:00.000000Z"} +{"cron":"*/48 * 18W * 5-6/3","after":"2025-10-04T16:49:00.500000Z","tz":"Australia/Lord_Howe","next":"2025-10-09T13:00:00.000000Z"} +{"cron":"* 18,0 4 3-9 6L","after":"2028-11-05T04:51:10.000000Z","tz":"America/St_Johns","next":"2029-03-04T03:30:00.000000Z"} +{"cron":"15 8,15 ? AUG *","after":"2027-09-25T15:21:45.000000Z","tz":"Pacific/Chatham","next":"2028-07-31T19:30:00.000000Z"} +{"cron":"* */2 * * THU","after":"2028-12-27T19:26:44.500000Z","tz":"Pacific/Apia","next":"2028-12-27T19:27:00.000000Z"} +{"cron":"14,20 */4 ? DEC *","after":"2028-05-28T10:54:36.000000Z","tz":"UTC","next":"2028-12-01T00:14:00.000000Z"} +{"cron":"31 21 11W 7-12 7#1","after":"2027-10-12T02:58:18.000000Z","tz":"Pacific/Apia","next":"2027-11-07T08:31:00.000000Z"} +{"cron":"4,44 1-20 L * 7L","after":"2027-03-14T05:44:14.000000Z","tz":"America/St_Johns","next":"2027-03-28T03:34:00.000000Z"} +{"cron":"* 2 */30 12-12 0,0","after":"2028-02-28T21:23:34.500000Z","tz":"Asia/Kolkata","next":"2028-11-30T20:30:00.000000Z"} +{"cron":"53-55/4 */1 * */14 6L","after":"2026-03-08T07:21:10.500000Z","tz":"America/St_Johns","next":"2026-03-28T03:23:00.000000Z"} +{"cron":"17-48 7-16 13-31/1 */13 *","after":"2026-04-30T22:52:57.000000Z","tz":"Pacific/Chatham","next":"2027-02-12T17:32:00.000000Z"} +{"cron":"33-42/2 2,11 L 8,12 */7","after":"2025-04-12T03:41:54.000000Z","tz":"UTC","next":"2025-08-03T02:33:00.000000Z"} +{"cron":"46,31 21,22 */27 10-10/5 TUE","after":"2025-03-09T04:43:49.000000Z","tz":"America/Havana","next":"2025-10-02T01:31:00.000000Z"} +{"cron":"@weekly","after":"2027-12-09T22:46:37.500000Z","tz":"Europe/Budapest","next":"2027-12-11T23:00:00.000000Z"} +{"cron":"3 13 * 5-12/7 ?","after":"2025-06-03T07:28:55.000000Z","tz":"Asia/Kolkata","next":"2025-12-01T07:33:00.000000Z"} +{"cron":"* 9 ? 5-12 7#5","after":"2028-10-29T02:29:24.500000Z","tz":"Europe/Dublin","next":"2028-10-29T09:00:00.000000Z"} +{"cron":"36 11 7-23 10 2,3","after":"2027-12-13T08:00:26.000000Z","tz":"America/Sao_Paulo","next":"2028-10-03T14:36:00.000000Z"} +{"cron":"@annually","after":"2026-07-02T12:20:09.000000Z","tz":"Asia/Kolkata","next":"2026-12-31T18:30:00.000000Z"} +{"cron":"33-41 */22 29 2-8/1 0","after":"2026-12-31T23:28:38.000000Z","tz":"America/Havana","next":"2027-02-07T05:33:00.000000Z"} +{"cron":"11-55 2-2 13-22/3 6 */2","after":"2026-09-13T03:07:51.000000Z","tz":"Asia/Tehran","next":"2027-05-31T22:41:00.000000Z"} +{"cron":"30-35/3 3 25 */11 WED","after":"2024-03-10T05:22:58.000000Z","tz":"America/St_Johns","next":"2024-12-04T07:00:00.000000Z"} +{"cron":"13-17 11-14/1 20 * 7-7/3","after":"2025-04-24T01:34:08.500000Z","tz":"Europe/Budapest","next":"2025-04-26T09:13:00.000000Z"} +{"cron":"49-50 15,0 18,30 6 5-7","after":"2024-03-31T00:45:12.000000Z","tz":"Antarctica/Troll","next":"2024-05-31T22:49:00.000000Z"} +{"cron":"13-36/1 */3 ? 2 */8","after":"2026-03-29T00:56:34.500000Z","tz":"Europe/Budapest","next":"2027-02-06T23:13:00.000000Z"} +{"cron":"*/9 */19 23 * 6,0","after":"2028-03-12T06:50:16.000000Z","tz":"America/Havana","next":"2028-03-12T23:00:00.000000Z"} +{"cron":"41 * 13,21 8,12 6-7/5","after":"2026-04-30T22:09:16.000000Z","tz":"Europe/Dublin","next":"2026-07-31T23:41:00.000000Z"} +{"cron":"*/36 5-8 L JUN 2,2","after":"2028-10-29T02:46:21.000000Z","tz":"Antarctica/Troll","next":"2029-06-05T03:00:00.000000Z"} +{"cron":"* 14 16 9,4 4L","after":"2026-06-19T04:06:24.500000Z","tz":"Asia/Beirut","next":"2026-09-16T11:00:00.000000Z"} +{"cron":"31,13 23,4 30 12-12/3 1-4","after":"2025-08-25T17:08:59.500000Z","tz":"UTC","next":"2025-12-01T23:13:00.000000Z"} +{"cron":"18,18 14 L 7,1 1L","after":"2026-04-05T04:55:53.000000Z","tz":"America/Santiago","next":"2026-07-27T18:18:00.000000Z"} +{"cron":"8-34 17 30 7 3,2","after":"2026-10-03T12:15:41.000000Z","tz":"America/New_York","next":"2027-07-06T21:08:00.000000Z"} +{"cron":"20-32/4 20-21/5 15,24 */12 0-1","after":"2028-07-08T10:31:56.000000Z","tz":"America/Havana","next":"2029-01-02T01:20:00.000000Z"} +{"cron":"18,26 */12 20-24/3 */11 1","after":"2028-01-11T07:01:56.000000Z","tz":"America/Havana","next":"2028-01-17T05:18:00.000000Z"} +{"cron":"*/10 22,17 * MAY */4","after":"2025-01-06T19:08:45.000000Z","tz":"Asia/Kolkata","next":"2025-05-01T16:30:00.000000Z"} +{"cron":"25,18 * L */5 5#2","after":"2028-02-29T00:20:53.000000Z","tz":"UTC","next":"2028-06-09T00:18:00.000000Z"} +{"cron":"* 18,18 */21 MAR 1#1","after":"2027-01-01T00:42:07.000000Z","tz":"UTC","next":"2027-03-01T18:00:00.000000Z"} +{"cron":"56 7 9 FEB */1","after":"2025-04-05T13:23:22.000000Z","tz":"Australia/Lord_Howe","next":"2026-01-31T20:56:00.000000Z"} +{"cron":"*/25 4-7 23,17 * ?","after":"2028-01-06T16:46:48.000000Z","tz":"America/Sao_Paulo","next":"2028-01-17T07:00:00.000000Z"} +{"cron":"1-24 22,21 25-29/1 */10 *","after":"2028-03-22T15:03:45.000000Z","tz":"Pacific/Apia","next":"2028-11-25T09:01:00.000000Z"} +{"cron":"*/33 7 */23 3-3 */2","after":"2026-01-31T22:19:47.000000Z","tz":"Europe/Dublin","next":"2026-03-01T07:00:00.000000Z"} +{"cron":"14 22 L 11,9 0,5","after":"2025-12-13T21:51:14.500000Z","tz":"Pacific/Apia","next":"2026-09-04T09:14:00.000000Z"} +{"cron":"*/25 15 4-13 2 0L","after":"2024-10-27T02:57:56.000000Z","tz":"Antarctica/Troll","next":"2025-02-04T15:00:00.000000Z"} +{"cron":"* */3 17-22/7 * 4L","after":"2028-02-28T21:04:08.500000Z","tz":"America/Havana","next":"2028-03-17T04:00:00.000000Z"} +{"cron":"42-47/2 8-13 3 3-3/5 ?","after":"2026-09-13T15:33:57.000000Z","tz":"Pacific/Apia","next":"2027-03-02T19:42:00.000000Z"} +{"cron":"38,39 * 7-31 8,11 4-6","after":"2025-10-08T23:34:44.000000Z","tz":"America/Sao_Paulo","next":"2025-11-01T03:38:00.000000Z"} +{"cron":"38 3-8/7 18,14 */2 3#5","after":"2026-01-01T22:28:31.000000Z","tz":"Asia/Kolkata","next":"2026-01-13T22:08:00.000000Z"} +{"cron":"32 3 3W 9-9 5","after":"2028-12-15T09:35:46.500000Z","tz":"Pacific/Apia","next":"2029-09-02T14:32:00.000000Z"} +{"cron":"45-54 21 * APR 1L","after":"2026-10-12T04:14:55.500000Z","tz":"Pacific/Apia","next":"2027-04-26T08:45:00.000000Z"} +{"cron":"7-17/4 6-13 17 10-10/3 ?","after":"2024-10-05T17:26:28.500000Z","tz":"Australia/Lord_Howe","next":"2024-10-16T19:07:00.000000Z"} +{"cron":"10 */20 */6 11-12/2 *","after":"2027-02-28T22:12:33.000000Z","tz":"Asia/Kolkata","next":"2027-10-31T18:40:00.000000Z"} +{"cron":"*/39 2-2 * SEP 1-1","after":"2027-05-08T15:51:40.000000Z","tz":"America/Santiago","next":"2027-09-06T05:00:00.000000Z"} +{"cron":"33 18,6 26,19 7-11 */9","after":"2025-03-09T06:14:33.000000Z","tz":"America/New_York","next":"2025-07-07T22:33:00.000000Z"} +{"cron":"12-35 16,21 * 12-12 */3","after":"2026-12-20T21:25:31.000000Z","tz":"America/Havana","next":"2026-12-20T21:26:00.000000Z"} +{"cron":"14 13 */1 4,8 SUN","after":"2026-10-03T16:38:32.000000Z","tz":"Australia/Lord_Howe","next":"2027-04-01T02:14:00.000000Z"} +{"cron":"58-59/4 * ? * 1,3","after":"2028-02-28T23:50:55.000000Z","tz":"America/Havana","next":"2028-02-28T23:58:00.000000Z"} +{"cron":"*/9 */2 30-31/1 12,2 6L","after":"2027-07-06T20:43:50.000000Z","tz":"Europe/Dublin","next":"2027-12-25T00:00:00.000000Z"} +{"cron":"31 */23 * * 6-6","after":"2025-06-26T08:11:01.000000Z","tz":"America/Sao_Paulo","next":"2025-06-28T03:31:00.000000Z"} +{"cron":"34 * 27-28 * *","after":"2027-02-28T23:56:07.000000Z","tz":"Asia/Kolkata","next":"2027-03-26T19:04:00.000000Z"} +{"cron":"28 5-22/2 */1 4 5","after":"2026-12-31T21:55:48.000000Z","tz":"Europe/Budapest","next":"2027-04-01T03:28:00.000000Z"} +{"cron":"24 5 L 5 4#1","after":"2025-06-30T01:50:41.500000Z","tz":"America/Havana","next":"2026-05-07T09:24:00.000000Z"} +{"cron":"12 * * */10 */6","after":"2027-02-28T21:57:20.000000Z","tz":"Asia/Tehran","next":"2027-11-05T20:42:00.000000Z"} +{"cron":"43,29 * ? 3,1 6-7/7","after":"2026-01-31T23:26:14.000000Z","tz":"Asia/Kolkata","next":"2026-03-06T18:59:00.000000Z"} +{"cron":"27-45 */21 15-27/1 */2 0L","after":"2026-01-01T13:00:43.500000Z","tz":"America/Santiago","next":"2026-01-15T03:27:00.000000Z"} +{"cron":"47 11 27-28 8,2 2","after":"2026-08-20T02:52:14.000000Z","tz":"Antarctica/Troll","next":"2026-08-25T09:47:00.000000Z"} +{"cron":"40-47/7 7-11/1 20-29/6 11,4 5-7/3","after":"2027-02-28T22:08:44.000000Z","tz":"Asia/Tehran","next":"2027-04-02T04:10:00.000000Z"} +{"cron":"39,37 21-21 20 2 *","after":"2028-09-15T17:14:30.000000Z","tz":"America/New_York","next":"2029-02-21T02:37:00.000000Z"} +{"cron":"19 * 11 JUL 3-3/5","after":"2026-04-30T23:16:17.000000Z","tz":"America/Havana","next":"2026-07-01T04:19:00.000000Z"} +{"cron":"* * 10-21 7 4","after":"2027-10-02T16:10:39.000000Z","tz":"Australia/Lord_Howe","next":"2028-07-05T13:30:00.000000Z"} +{"cron":"*/13 3-12/5 */18 * 2-5","after":"2025-03-30T01:33:12.000000Z","tz":"Europe/Budapest","next":"2025-04-01T01:00:00.000000Z"} +{"cron":"* 6-16/4 L SEP 3L","after":"2024-04-06T16:55:14.000000Z","tz":"Australia/Lord_Howe","next":"2024-09-24T19:30:00.000000Z"} +{"cron":"*/57 */21 28-30/2 */6 1#5","after":"2027-04-04T03:22:05.000000Z","tz":"America/Santiago","next":"2027-07-28T04:00:00.000000Z"} +{"cron":"14-26 1-7 10-17/3 */4 3-3/5","after":"2026-06-25T07:53:00.000000Z","tz":"Pacific/Apia","next":"2026-09-01T12:14:00.000000Z"} +{"cron":"10,34 2-13 23W */11 5#4","after":"2025-05-11T06:08:51.500000Z","tz":"Asia/Tehran","next":"2025-12-22T22:40:00.000000Z"} +{"cron":"* * */30 * 7L","after":"2026-03-29T01:39:34.500000Z","tz":"Europe/Budapest","next":"2026-03-29T01:40:00.000000Z"} +{"cron":"17 14,22 23W 10,2 */7","after":"2028-11-23T20:21:19.000000Z","tz":"Asia/Kolkata","next":"2029-02-04T08:47:00.000000Z"} +{"cron":"*/16 1 5-24/1 * */3","after":"2028-09-02T20:16:04.000000Z","tz":"Pacific/Apia","next":"2028-09-04T12:00:00.000000Z"} +{"cron":"27,2 1-1 ? * 2-2","after":"2028-02-29T21:38:22.000000Z","tz":"Asia/Kolkata","next":"2028-03-06T19:32:00.000000Z"} +{"cron":"46 17-23 ? NOV 0#2","after":"2028-11-05T06:14:32.500000Z","tz":"America/New_York","next":"2028-11-12T22:46:00.000000Z"} +{"cron":"*/29 10 12 * 5-5/5","after":"2027-11-14T06:34:00.000000Z","tz":"Antarctica/Troll","next":"2027-11-19T10:00:00.000000Z"} +{"cron":"29-55/6 4,10 ? NOV */4","after":"2024-10-05T13:48:26.000000Z","tz":"Australia/Lord_Howe","next":"2024-11-02T17:29:00.000000Z"} +{"cron":"* 0-9 * 6-7/1 *","after":"2024-11-03T05:58:43.000000Z","tz":"America/Havana","next":"2025-06-01T04:00:00.000000Z"} +{"cron":"48 10 24 */2 2L","after":"2025-09-07T05:57:29.000000Z","tz":"America/Santiago","next":"2025-09-24T13:48:00.000000Z"} +{"cron":"0 17-19 */27 1-8/4 4","after":"2027-03-25T09:04:51.000000Z","tz":"America/Havana","next":"2027-05-01T21:00:00.000000Z"} +{"cron":"*/35 5-11/3 * */5 */2","after":"2028-10-29T00:34:34.000000Z","tz":"Europe/Dublin","next":"2028-11-02T05:00:00.000000Z"} +{"cron":"34 * * */7 0L","after":"2027-03-14T06:18:58.000000Z","tz":"America/Havana","next":"2027-08-29T04:34:00.000000Z"} +{"cron":"31-54 19 24 * 3#4","after":"2026-03-29T02:40:23.000000Z","tz":"Europe/Dublin","next":"2026-04-22T18:31:00.000000Z"} +{"cron":"* 8,20 2,17 OCT 1,1","after":"2027-04-13T15:07:52.500000Z","tz":"Pacific/Apia","next":"2027-10-01T19:00:00.000000Z"} +{"cron":"40 11,1 14,26 9-11/3 ?","after":"2028-12-04T18:35:00.000000Z","tz":"UTC","next":"2029-09-14T11:40:00.000000Z"} +{"cron":"31-56/2 22-22/5 18W * ?","after":"2027-10-30T20:40:57.500000Z","tz":"Asia/Beirut","next":"2027-11-18T20:31:00.000000Z"} +{"cron":"29 15 * 10 THU","after":"2026-03-29T01:17:26.000000Z","tz":"Europe/Dublin","next":"2026-10-01T14:29:00.000000Z"} +{"cron":"58 20-23 24W 2-9/7 0-1","after":"2025-03-09T07:28:49.500000Z","tz":"America/New_York","next":"2025-09-02T00:58:00.000000Z"} +{"cron":"15 4-23 * 5,1 1-5","after":"2027-02-28T22:26:00.000000Z","tz":"Asia/Tehran","next":"2027-05-03T00:45:00.000000Z"} +{"cron":"12 11 31-31 4 3-6","after":"2026-11-01T05:10:38.000000Z","tz":"America/St_Johns","next":"2027-04-01T13:42:00.000000Z"} +{"cron":"7,3 7 10 * *","after":"2028-02-28T22:31:09.500000Z","tz":"America/St_Johns","next":"2028-03-10T10:33:00.000000Z"} +{"cron":"* 16 15-31 10-10/2 2-6","after":"2028-03-12T03:44:23.500000Z","tz":"America/Havana","next":"2028-10-03T20:00:00.000000Z"} +{"cron":"52 * 11 */14 5#3","after":"2027-01-04T23:35:23.500000Z","tz":"UTC","next":"2027-03-11T00:52:00.000000Z"} +{"cron":"58-58/3 9,5 * 3,8 */7","after":"2026-12-31T21:39:11.000000Z","tz":"America/Sao_Paulo","next":"2027-03-07T12:58:00.000000Z"} +{"cron":"0 23-23/4 27,13 8-11/7 0-1/1","after":"2026-04-30T21:55:29.000000Z","tz":"Antarctica/Troll","next":"2026-08-02T21:00:00.000000Z"} +{"cron":"51-54/2 20-21/4 14 3,4 0L","after":"2026-12-31T21:13:42.000000Z","tz":"Europe/Dublin","next":"2027-03-14T20:51:00.000000Z"} +{"cron":"*/2 * */28 */6 7-7/5","after":"2028-10-25T16:40:41.500000Z","tz":"Asia/Kolkata","next":"2028-12-31T18:30:00.000000Z"} +{"cron":"14 1 */18 1-7/1 0,1","after":"2025-10-25T23:38:38.500000Z","tz":"Europe/Dublin","next":"2026-01-01T01:14:00.000000Z"} +{"cron":"*/38 18,13 5-14 6,11 */4","after":"2027-10-31T00:33:57.000000Z","tz":"Europe/Budapest","next":"2027-11-04T17:00:00.000000Z"} +{"cron":"*/61 * ? 11 0L","after":"2027-10-07T05:24:46.000000Z","tz":"Antarctica/Troll","next":"2027-11-28T00:01:00.000000Z"} +{"cron":"58-59/4 */24 6 12-12/1 ?","after":"2028-11-05T06:48:30.000000Z","tz":"America/New_York","next":"2028-12-06T05:58:00.000000Z"} +{"cron":"* 19 20 JUN ?","after":"2028-08-29T22:50:10.000000Z","tz":"Asia/Kolkata","next":"2029-06-20T13:30:00.000000Z"} +{"cron":"* 10-16/5 L FEB *","after":"2027-10-30T19:49:36.000000Z","tz":"Europe/Dublin","next":"2028-02-29T10:00:00.000000Z"} +{"cron":"45,15 17-17 ? 12 5-7","after":"2026-01-20T20:18:02.500000Z","tz":"America/St_Johns","next":"2026-12-04T20:45:00.000000Z"} +{"cron":"8 23-23 19,18 9,10 5-6","after":"2026-01-31T21:07:45.000000Z","tz":"America/Havana","next":"2026-09-05T03:08:00.000000Z"} +{"cron":"49 22 28,31 JUL 0-0/2","after":"2025-01-29T21:28:50.500000Z","tz":"America/St_Johns","next":"2025-07-07T01:19:00.000000Z"} +{"cron":"15,18 8-10/1 ? 11-12/7 5,6","after":"2026-10-31T00:59:09.000000Z","tz":"Australia/Lord_Howe","next":"2026-11-05T21:15:00.000000Z"} +{"cron":"32 13-15 16 8-12 7#4","after":"2028-11-05T07:52:27.000000Z","tz":"America/New_York","next":"2028-11-16T18:32:00.000000Z"} +{"cron":"*/19 11 * FEB *","after":"2027-09-25T20:57:46.000000Z","tz":"Australia/Lord_Howe","next":"2028-02-01T00:00:00.000000Z"} +{"cron":"20-35/7 1 30 8,10 6,3","after":"2027-07-07T09:04:34.000000Z","tz":"Asia/Tehran","next":"2027-08-03T21:50:00.000000Z"} +{"cron":"39,26 14 1W 7,4 ?","after":"2027-10-27T23:15:59.500000Z","tz":"Europe/Budapest","next":"2028-04-03T12:26:00.000000Z"} +{"cron":"24 18-18 ? 11,6 */4","after":"2025-08-07T22:24:13.500000Z","tz":"Europe/Budapest","next":"2025-11-02T17:24:00.000000Z"} +{"cron":"17-50 16-19 4W MAR 5","after":"2026-12-31T22:37:50.500000Z","tz":"UTC","next":"2027-03-04T16:17:00.000000Z"} +{"cron":"@daily","after":"2025-08-09T03:38:14.000000Z","tz":"UTC","next":"2025-08-10T00:00:00.000000Z"} +{"cron":"56-58 21-22/4 22W */9 ?","after":"2026-01-31T21:45:19.000000Z","tz":"America/Havana","next":"2026-10-23T01:56:00.000000Z"} +{"cron":"42 9-18 14 8,7 */2","after":"2026-05-12T14:39:31.000000Z","tz":"UTC","next":"2026-07-02T09:42:00.000000Z"} +{"cron":"29-47/7 14,15 13,29 DEC FRI","after":"2024-11-03T03:31:20.500000Z","tz":"America/Havana","next":"2024-12-06T19:29:00.000000Z"} +{"cron":"46,55 4-21/2 20 12-12/3 3-7/4","after":"2028-02-16T07:48:43.500000Z","tz":"Australia/Lord_Howe","next":"2028-12-02T17:46:00.000000Z"} +{"cron":"*/21 23-23/3 * 6-10 ?","after":"2028-02-28T23:48:17.000000Z","tz":"America/Santiago","next":"2028-06-02T03:00:00.000000Z"} +{"cron":"*/2 * 3 * ?","after":"2027-02-28T23:14:04.000000Z","tz":"America/Sao_Paulo","next":"2027-03-03T03:00:00.000000Z"} +{"cron":"*/3 0-22/6 ? * SAT","after":"2026-05-23T07:06:54.000000Z","tz":"Pacific/Chatham","next":"2026-05-29T11:15:00.000000Z"} +{"cron":"36-46/1 22 22-26/3 4,1 5,0","after":"2025-04-02T09:45:41.000000Z","tz":"Europe/London","next":"2025-04-04T21:36:00.000000Z"} +{"cron":"51-54 6-14/2 L 2 2-2/2","after":"2027-11-07T06:25:33.000000Z","tz":"America/St_Johns","next":"2028-02-01T10:21:00.000000Z"} +{"cron":"51-53/4 11 * 8-9 ?","after":"2027-06-16T11:24:57.000000Z","tz":"Asia/Kolkata","next":"2027-08-01T06:21:00.000000Z"} +{"cron":"27-38/5 21,1 8,8 9 ?","after":"2028-04-02T10:15:57.000000Z","tz":"Asia/Kolkata","next":"2028-09-08T15:57:00.000000Z"} +{"cron":"*/24 10-10/1 1-9 3,1 1#1","after":"2025-09-05T03:00:28.500000Z","tz":"America/Sao_Paulo","next":"2026-01-01T13:00:00.000000Z"} +{"cron":"29-48/4 21,0 * AUG 1#5","after":"2028-09-03T04:44:09.000000Z","tz":"America/Santiago","next":"2032-08-30T04:29:00.000000Z"} +{"cron":"27 21 * 12-12/2 *","after":"2027-11-24T19:11:12.000000Z","tz":"America/Santiago","next":"2027-12-02T00:27:00.000000Z"} +{"cron":"46-54/6 0-3 31-31 12,2 3L","after":"2025-11-24T18:18:03.000000Z","tz":"America/St_Johns","next":"2025-12-31T04:16:00.000000Z"} +{"cron":"10,31 8-9 15 4-7/1 6","after":"2024-10-27T01:11:17.000000Z","tz":"Europe/Budapest","next":"2025-04-05T06:10:00.000000Z"} +{"cron":"* 16-20/2 ? NOV */2","after":"2028-09-30T15:35:26.000000Z","tz":"Australia/Lord_Howe","next":"2028-11-02T05:00:00.000000Z"} +{"cron":"32,27 2-5 14-24 FEB 3-4","after":"2025-10-19T13:47:52.000000Z","tz":"America/Sao_Paulo","next":"2026-02-04T05:27:00.000000Z"} +{"cron":"*/16 23-23 13W 8-11/4 0#1","after":"2028-03-01T01:00:25.000000Z","tz":"America/St_Johns","next":"2028-08-07T01:30:00.000000Z"} +{"cron":"11 5-10 * 5-6/2 4-4","after":"2026-03-29T00:48:39.000000Z","tz":"Europe/Budapest","next":"2026-05-07T03:11:00.000000Z"} +{"cron":"23-44 */15 L * FRI","after":"2028-02-29T21:44:58.500000Z","tz":"Asia/Beirut","next":"2028-03-02T22:23:00.000000Z"} +{"cron":"39-50 16-17 */29 */11 3,2","after":"2028-02-28T23:36:41.000000Z","tz":"UTC","next":"2028-12-01T16:39:00.000000Z"} +{"cron":"48 12-12/4 28-29 10-10/7 4,3","after":"2025-04-05T14:55:39.500000Z","tz":"Pacific/Chatham","next":"2025-09-30T23:03:00.000000Z"} +{"cron":"*/53 13-13 18-21/3 6-9/4 ?","after":"2026-04-30T23:39:54.500000Z","tz":"America/St_Johns","next":"2026-06-18T15:30:00.000000Z"} +{"cron":"38-43/1 3 18,16 SEP *","after":"2026-01-31T23:02:38.000000Z","tz":"Europe/Dublin","next":"2026-09-16T02:38:00.000000Z"} +{"cron":"*/17 22,10 L 7-11/1 3L","after":"2028-04-19T15:19:36.500000Z","tz":"Antarctica/Troll","next":"2028-07-26T20:00:00.000000Z"} +{"cron":"25-49/7 1 */10 3,9 4","after":"2027-01-01T00:56:36.500000Z","tz":"Asia/Tehran","next":"2027-02-28T21:55:00.000000Z"} +{"cron":"4 6 2W * 3-3","after":"2027-03-01T00:58:00.500000Z","tz":"America/Havana","next":"2027-03-02T11:04:00.000000Z"} +{"cron":"47,56 12 */22 */1 6,0","after":"2027-11-09T01:32:53.000000Z","tz":"Asia/Beirut","next":"2027-11-13T10:47:00.000000Z"} +{"cron":"* 18-22 15 8-10/2 7-7","after":"2025-12-02T12:24:41.000000Z","tz":"Pacific/Chatham","next":"2026-08-01T05:15:00.000000Z"} +{"cron":"55-56 * 8 * SUN","after":"2025-05-16T10:58:09.000000Z","tz":"Pacific/Chatham","next":"2025-05-17T12:10:00.000000Z"} +{"cron":"* */17 19-20/3 1-7 0,2","after":"2026-12-31T21:30:15.000000Z","tz":"Pacific/Chatham","next":"2027-01-02T10:15:00.000000Z"} +{"cron":"17 19 ? 12-12/5 SUN","after":"2028-04-12T20:36:18.500000Z","tz":"Asia/Kolkata","next":"2028-12-03T13:47:00.000000Z"} +{"cron":"33-46/1 0-1 24 4 6L","after":"2028-02-29T21:49:10.000000Z","tz":"Europe/London","next":"2028-04-23T23:33:00.000000Z"} +{"cron":"58 18 L 2,8 *","after":"2028-03-12T07:22:01.000000Z","tz":"America/St_Johns","next":"2028-08-31T21:28:00.000000Z"} +{"cron":"24-58 22,2 30 6-12 3,6","after":"2026-01-31T21:14:29.000000Z","tz":"Asia/Beirut","next":"2026-06-03T19:24:00.000000Z"} +{"cron":"*/16 20 1W */10 7-7","after":"2026-12-31T22:53:57.000000Z","tz":"America/Sao_Paulo","next":"2027-01-01T23:00:00.000000Z"} +{"cron":"@yearly","after":"2026-09-06T02:05:42.000000Z","tz":"America/Santiago","next":"2027-01-01T03:00:00.000000Z"} +{"cron":"40,23 19 13 11-12 *","after":"2024-03-10T03:53:20.000000Z","tz":"America/St_Johns","next":"2024-11-13T22:53:00.000000Z"} +{"cron":"46-48/3 3-18 3 5 2-6","after":"2025-01-15T07:16:39.000000Z","tz":"Europe/London","next":"2025-05-01T02:46:00.000000Z"} +{"cron":"59-59 * * 7-11/5 6","after":"2026-01-31T22:36:12.000000Z","tz":"America/Santiago","next":"2026-07-04T04:59:00.000000Z"} +{"cron":"19-44 2,5 ? */7 */5","after":"2027-04-04T07:12:41.500000Z","tz":"Europe/London","next":"2027-08-01T01:19:00.000000Z"} +{"cron":"24 6-13/5 13 */3 5","after":"2026-03-28T22:29:47.000000Z","tz":"Asia/Beirut","next":"2026-04-03T03:24:00.000000Z"} +{"cron":"* 20-21/4 5,29 5,11 2-3","after":"2026-09-28T11:36:39.000000Z","tz":"America/Santiago","next":"2026-11-03T23:00:00.000000Z"} +{"cron":"* * * 4-11/6 */3","after":"2027-10-30T21:29:57.000000Z","tz":"Asia/Beirut","next":"2027-10-30T21:30:00.000000Z"} +{"cron":"29-40/3 17-23 * 9-12 */3","after":"2027-06-20T12:22:28.000000Z","tz":"America/New_York","next":"2027-09-01T21:29:00.000000Z"} +{"cron":"57,56 10-14 L 9-9/2 */8","after":"2024-11-03T07:28:31.500000Z","tz":"America/New_York","next":"2025-09-07T14:56:00.000000Z"} +{"cron":"0 * 23 8 5L","after":"2025-06-16T09:04:27.000000Z","tz":"America/Santiago","next":"2025-08-23T04:00:00.000000Z"} +{"cron":"32-59/4 5-13 13-25 */10 MON","after":"2026-12-31T22:16:17.000000Z","tz":"Asia/Beirut","next":"2027-01-04T03:32:00.000000Z"} +{"cron":"52 0,18 */10 * */1","after":"2026-04-30T21:53:03.000000Z","tz":"Asia/Kolkata","next":"2026-05-01T13:22:00.000000Z"} +{"cron":"24 21 16W 11 6-7/2","after":"2026-04-05T04:42:50.500000Z","tz":"America/Santiago","next":"2026-11-08T00:24:00.000000Z"} +{"cron":"8-12 18 17 */4 ?","after":"2025-03-08T04:28:10.500000Z","tz":"America/St_Johns","next":"2025-05-17T20:38:00.000000Z"} +{"cron":"15 19 9-12/3 */5 */8","after":"2027-10-30T23:36:55.000000Z","tz":"Europe/Budapest","next":"2027-11-07T18:15:00.000000Z"} +{"cron":"59-59 6-14 14-25 SEP */5","after":"2026-09-26T12:37:54.000000Z","tz":"Pacific/Chatham","next":"2026-09-26T17:14:00.000000Z"} +{"cron":"53-56/2 23,22 */22 7,3 ?","after":"2026-12-31T23:04:24.000000Z","tz":"America/New_York","next":"2027-03-02T04:53:00.000000Z"} +{"cron":"41,34 5-16/3 */4 OCT 1-2","after":"2027-06-04T20:07:45.000000Z","tz":"Asia/Tehran","next":"2027-10-01T02:04:00.000000Z"} +{"cron":"* 20 26W 4-7 0-7","after":"2026-04-30T23:40:55.000000Z","tz":"America/Sao_Paulo","next":"2026-04-30T23:41:00.000000Z"} +{"cron":"* 6-15 20,26 1-3/5 ?","after":"2027-02-28T22:28:16.500000Z","tz":"UTC","next":"2028-01-20T06:00:00.000000Z"} +{"cron":"0-25 13 20 10-10 *","after":"2027-10-30T20:25:09.000000Z","tz":"Asia/Beirut","next":"2028-10-20T10:00:00.000000Z"} +{"cron":"23,0 */24 2,11 * 5-5/5","after":"2028-08-13T00:45:35.500000Z","tz":"Asia/Kolkata","next":"2028-08-17T18:30:00.000000Z"} +{"cron":"36-47 16-23/1 */33 5,5 6L","after":"2028-11-11T06:42:45.000000Z","tz":"America/Havana","next":"2029-05-03T20:36:00.000000Z"} +{"cron":"* * 26 3 6-6/1","after":"2024-10-27T01:37:52.500000Z","tz":"Europe/Budapest","next":"2025-02-28T23:00:00.000000Z"} +{"cron":"*/59 */12 ? 3-4 7-7","after":"2025-01-15T04:43:26.000000Z","tz":"Europe/London","next":"2025-03-01T00:00:00.000000Z"} +{"cron":"* * * 10 0,5","after":"2026-01-31T23:29:10.000000Z","tz":"Europe/Budapest","next":"2026-10-01T22:00:00.000000Z"} +{"cron":"58 15-22/6 */6 6 1,1","after":"2027-02-28T22:01:23.500000Z","tz":"Asia/Beirut","next":"2027-06-01T12:58:00.000000Z"} +{"cron":"* 1-21 ? 3 *","after":"2026-01-31T23:43:31.000000Z","tz":"America/Sao_Paulo","next":"2026-03-01T04:00:00.000000Z"} +{"cron":"* * 13 */7 3#4","after":"2026-02-01T00:41:06.500000Z","tz":"Asia/Beirut","next":"2026-08-12T21:00:00.000000Z"} +{"cron":"39-44 17-18/3 29,31 10-11 ?","after":"2027-09-22T00:17:43.000000Z","tz":"Asia/Tehran","next":"2027-10-29T14:09:00.000000Z"} +{"cron":"39 */23 6-15 * 1","after":"2027-09-03T00:48:28.000000Z","tz":"Asia/Kolkata","next":"2027-09-05T19:09:00.000000Z"} +{"cron":"36 * */7 * 3","after":"2025-05-10T04:35:10.000000Z","tz":"America/Sao_Paulo","next":"2025-05-14T03:36:00.000000Z"} +{"cron":"28 14 */3 5-10 7#2","after":"2028-09-30T13:59:32.000000Z","tz":"Asia/Tehran","next":"2028-10-01T10:58:00.000000Z"} +{"cron":"22,13 12,15 24,28 5 3L","after":"2028-06-07T21:09:35.500000Z","tz":"Asia/Beirut","next":"2029-05-24T09:13:00.000000Z"} +{"cron":"54-54/6 3-9 7-24 1-8 0,3","after":"2028-02-29T21:28:26.000000Z","tz":"America/Sao_Paulo","next":"2028-03-01T06:54:00.000000Z"} +{"cron":"0 21,15 L 2 4","after":"2028-03-26T02:29:44.500000Z","tz":"Europe/Dublin","next":"2029-02-01T21:00:00.000000Z"} +{"cron":"9-23 */7 */28 8,3 4-6","after":"2025-10-04T16:32:01.000000Z","tz":"Australia/Lord_Howe","next":"2026-02-28T13:09:00.000000Z"} +{"cron":"* 17 * * 0-2/5","after":"2026-05-11T05:12:56.000000Z","tz":"UTC","next":"2026-05-17T17:00:00.000000Z"} +{"cron":"57-59/1 3,0 25 10,8 6L","after":"2027-06-25T17:16:26.500000Z","tz":"Pacific/Apia","next":"2027-08-24T11:57:00.000000Z"} +{"cron":"19-39/3 */14 L */4 MON","after":"2028-10-28T23:50:33.000000Z","tz":"Europe/Budapest","next":"2028-12-31T23:19:00.000000Z"} +{"cron":"*/24 3,8 18,30 4,6 7-7/4","after":"2024-04-07T01:11:40.000000Z","tz":"America/Santiago","next":"2024-04-11T07:00:00.000000Z"} +{"cron":"30-44/1 4-13/5 * 3,3 THU","after":"2027-09-30T15:21:07.000000Z","tz":"Asia/Kolkata","next":"2028-03-01T23:00:00.000000Z"} +{"cron":"*/2 */21 14-14 */4 SAT","after":"2026-04-05T01:41:26.000000Z","tz":"America/Santiago","next":"2026-05-02T04:00:00.000000Z"} +{"cron":"37 4 1-6/1 4-11/2 5-5/1","after":"2027-10-02T16:54:10.000000Z","tz":"Australia/Lord_Howe","next":"2027-10-02T17:37:00.000000Z"} +{"cron":"* * 29 11-12 2-2/6","after":"2028-03-01T00:14:10.000000Z","tz":"Europe/Budapest","next":"2028-11-06T23:00:00.000000Z"} +{"cron":"4 7 28-29 */8 0,3","after":"2025-11-02T04:57:30.500000Z","tz":"America/St_Johns","next":"2026-01-04T10:34:00.000000Z"} +{"cron":"* * 29-29 6,5 */9","after":"2028-03-25T22:30:15.000000Z","tz":"Asia/Beirut","next":"2028-04-30T21:00:00.000000Z"} +{"cron":"* 23 * 7-12/5 7","after":"2026-09-29T15:33:59.000000Z","tz":"Asia/Tehran","next":"2026-12-06T19:30:00.000000Z"} +{"cron":"3 1-2/1 ? OCT MON","after":"2025-10-03T16:50:14.000000Z","tz":"Asia/Beirut","next":"2025-10-05T22:03:00.000000Z"} +{"cron":"26-46 21,20 24,12 3-4 */8","after":"2027-03-27T23:38:21.000000Z","tz":"Europe/London","next":"2027-03-28T20:26:00.000000Z"} +{"cron":"23,52 */25 5-28 12 5,3","after":"2026-03-21T14:00:21.000000Z","tz":"America/St_Johns","next":"2026-12-02T04:53:00.000000Z"} +{"cron":"* 7-17/4 * 11 2#2","after":"2028-12-19T11:40:18.000000Z","tz":"America/Sao_Paulo","next":"2029-11-13T10:00:00.000000Z"} +{"cron":"*/24 * 25,13 */12 *","after":"2026-01-31T21:11:05.000000Z","tz":"Asia/Tehran","next":"2027-01-12T20:30:00.000000Z"} +{"cron":"7 3 */25 DEC 7-7","after":"2027-08-01T13:15:41.500000Z","tz":"Asia/Tehran","next":"2027-11-30T23:37:00.000000Z"} diff --git a/rust/tests/fixtures/cron/php-2.5.14-zone-names.jsonl b/rust/tests/fixtures/cron/php-2.5.14-zone-names.jsonl new file mode 100644 index 000000000..0b735800c --- /dev/null +++ b/rust/tests/fixtures/cron/php-2.5.14-zone-names.jsonl @@ -0,0 +1,1068 @@ +["Africa/Abidjan","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["africa/abidjan","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["AFRICA/ABIDJAN","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Accra","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Addis_Ababa","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Africa/Algiers","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Asmara","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Africa/Asmera","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Africa/Bamako","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Bangui","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["africa/bangui","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Banjul","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Bissau","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Blantyre","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Brazzaville","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["AFRICA/BRAZZAVILLE","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Bujumbura","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Cairo","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T22:15Z"] +["Africa/Ceuta","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Conakry","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Dakar","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Dar_es_Salaam","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Africa/Djibouti","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Africa/Douala","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Freetown","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["AFRICA/FREETOWN","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Gaborone","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Harare","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Johannesburg","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Juba","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Kampala","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Africa/Khartoum","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["africa/khartoum","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Kigali","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Kinshasa","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Lagos","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Libreville","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Lome","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["AFRICA/LOME","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Luanda","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Lubumbashi","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["africa/lubumbashi","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Lusaka","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Malabo","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Maputo","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Maseru","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Mbabane","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Mogadishu","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Africa/Monrovia","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["africa/monrovia","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Nairobi","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Africa/Ndjamena","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["AFRICA/NDJAMENA","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Niamey","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Nouakchott","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Ouagadougou","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Porto-Novo","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Sao_Tome","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["africa/sao_tome","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Timbuktu","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Africa/Tripoli","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Africa/Tunis","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Africa/Windhoek","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["America/Adak","2026-07-01T21:00Z","2026-03-08T12:30Z","2026-10-25T10:15Z"] +["America/Anchorage","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["AMERICA/ANCHORAGE","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["America/Anguilla","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["america/anguilla","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Antigua","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Araguaina","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/Buenos_Aires","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/Catamarca","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/ComodRivadavia","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/Cordoba","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/Jujuy","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["america/argentina/jujuy","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/La_Rioja","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/Mendoza","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/Rio_Gallegos","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["AMERICA/ARGENTINA/RIO_GALLEGOS","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/Salta","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/San_Juan","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/San_Luis","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/Tucuman","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["america/argentina/tucuman","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Argentina/Ushuaia","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Aruba","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Asuncion","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Atikokan","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Atka","2026-07-01T21:00Z","2026-03-08T12:30Z","2026-10-25T10:15Z"] +["America/Bahia","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Bahia_Banderas","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["america/bahia_banderas","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["AMERICA/BAHIA_BANDERAS","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Barbados","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Belem","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Belize","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Blanc-Sablon","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Boa_Vista","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Bogota","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Boise","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["america/boise","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["America/Buenos_Aires","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Cambridge_Bay","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["America/Campo_Grande","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Cancun","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["AMERICA/CANCUN","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Caracas","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Catamarca","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Cayenne","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["america/cayenne","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Cayman","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Chicago","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Chihuahua","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Ciudad_Juarez","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["America/Coral_Harbour","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Cordoba","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Costa_Rica","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["america/costa_rica","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Coyhaique","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["AMERICA/COYHAIQUE","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Creston","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["America/Cuiaba","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Curacao","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Danmarkshavn","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["America/Dawson","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["America/Dawson_Creek","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["america/dawson_creek","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["America/Denver","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["America/Detroit","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Dominica","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Edmonton","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["America/Eirunepe","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["AMERICA/EIRUNEPE","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/El_Salvador","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Ensenada","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["america/ensenada","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["America/Fort_Nelson","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["America/Fort_Wayne","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Fortaleza","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Glace_Bay","2026-07-01T15:00Z","2026-03-08T06:30Z","2026-10-25T04:15Z"] +["America/Godthab","2026-07-01T13:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["America/Goose_Bay","2026-07-01T15:00Z","2026-03-08T06:30Z","2026-10-25T04:15Z"] +["America/Grand_Turk","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["america/grand_turk","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Grenada","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Guadeloupe","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["AMERICA/GUADELOUPE","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Guatemala","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Guayaquil","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Guyana","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Halifax","2026-07-01T15:00Z","2026-03-08T06:30Z","2026-10-25T04:15Z"] +["America/Havana","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["america/havana","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Hermosillo","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["America/Indiana/Indianapolis","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Indiana/Knox","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Indiana/Marengo","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Indiana/Petersburg","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Indiana/Tell_City","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["AMERICA/INDIANA/TELL_CITY","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Indiana/Vevay","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["america/indiana/vevay","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Indiana/Vincennes","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Indiana/Winamac","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Indianapolis","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Inuvik","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["America/Iqaluit","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Jamaica","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Jujuy","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["america/jujuy","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Juneau","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["America/Kentucky/Louisville","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Kentucky/Monticello","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["AMERICA/KENTUCKY/MONTICELLO","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Knox_IN","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Kralendijk","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/La_Paz","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Lima","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["america/lima","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Los_Angeles","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["America/Louisville","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Lower_Princes","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Maceio","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Managua","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Manaus","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Marigot","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["america/marigot","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["AMERICA/MARIGOT","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Martinique","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Matamoros","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Mazatlan","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["America/Mendoza","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Menominee","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Merida","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Metlakatla","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["america/metlakatla","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["America/Mexico_City","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Miquelon","2026-07-01T14:00Z","2026-03-08T05:30Z","2026-10-25T03:15Z"] +["America/Moncton","2026-07-01T15:00Z","2026-03-08T06:30Z","2026-10-25T04:15Z"] +["America/Monterrey","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["AMERICA/MONTERREY","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Montevideo","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Montreal","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Montserrat","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["america/montserrat","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Nassau","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/New_York","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Nipigon","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Nome","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["America/Noronha","2026-07-01T14:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["America/North_Dakota/Beulah","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/North_Dakota/Center","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["america/north_dakota/center","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/North_Dakota/New_Salem","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["AMERICA/NORTH_DAKOTA/NEW_SALEM","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Nuuk","2026-07-01T13:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["America/Ojinaga","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Panama","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Pangnirtung","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Paramaribo","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Phoenix","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["america/phoenix","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["America/Port-au-Prince","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Port_of_Spain","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Porto_Acre","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Porto_Velho","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Puerto_Rico","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["AMERICA/PUERTO_RICO","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Punta_Arenas","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Rainy_River","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["america/rainy_river","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Rankin_Inlet","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Recife","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Regina","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Resolute","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Rio_Branco","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["America/Rosario","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Santa_Isabel","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["america/santa_isabel","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["America/Santarem","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Santiago","2026-07-01T16:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["AMERICA/SANTIAGO","2026-07-01T16:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Santo_Domingo","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Sao_Paulo","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["America/Scoresbysund","2026-07-01T13:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["America/Shiprock","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["America/Sitka","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["america/sitka","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["America/St_Barthelemy","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/St_Johns","2026-07-01T14:30Z","2026-03-08T06:00Z","2026-10-25T03:45Z"] +["America/St_Kitts","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/St_Lucia","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/St_Thomas","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/St_Vincent","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["AMERICA/ST_VINCENT","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Swift_Current","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["america/swift_current","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Tegucigalpa","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["America/Thule","2026-07-01T15:00Z","2026-03-08T06:30Z","2026-10-25T04:15Z"] +["America/Thunder_Bay","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Tijuana","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["America/Toronto","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["America/Tortola","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Vancouver","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["america/vancouver","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["America/Virgin","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["America/Whitehorse","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["America/Winnipeg","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["AMERICA/WINNIPEG","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["America/Yakutat","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["America/Yellowknife","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["Antarctica/Casey","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Antarctica/Davis","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["antarctica/davis","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Antarctica/DumontDUrville","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Antarctica/Macquarie","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Antarctica/Mawson","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Antarctica/McMurdo","2026-07-02T00:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Antarctica/Palmer","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["Antarctica/Rothera","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["Antarctica/South_Pole","2026-07-02T00:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["antarctica/south_pole","2026-07-02T00:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["ANTARCTICA/SOUTH_POLE","2026-07-02T00:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Antarctica/Syowa","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Antarctica/Troll","2026-07-02T10:00Z","2026-03-09T02:30Z","2026-10-26T01:15Z"] +["Antarctica/Vostok","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Arctic/Longyearbyen","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Asia/Aden","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Asia/Almaty","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Amman","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["asia/amman","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Asia/Anadyr","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Asia/Aqtau","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Aqtobe","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Ashgabat","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["ASIA/ASHGABAT","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Ashkhabad","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Atyrau","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Baghdad","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["asia/baghdad","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Asia/Bahrain","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Asia/Baku","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Asia/Bangkok","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Barnaul","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Beirut","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Asia/Bishkek","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Asia/Brunei","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["asia/brunei","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Calcutta","2026-07-02T06:30Z","2026-03-08T21:00Z","2026-10-25T19:45Z"] +["ASIA/CALCUTTA","2026-07-02T06:30Z","2026-03-08T21:00Z","2026-10-25T19:45Z"] +["Asia/Chita","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Asia/Choibalsan","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Chongqing","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Chungking","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Colombo","2026-07-02T06:30Z","2026-03-08T21:00Z","2026-10-25T19:45Z"] +["Asia/Dacca","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["asia/dacca","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Asia/Damascus","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Asia/Dhaka","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Asia/Dili","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Asia/Dubai","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Asia/Dushanbe","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["ASIA/DUSHANBE","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Famagusta","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Asia/Gaza","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["asia/gaza","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Asia/Harbin","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Hebron","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Asia/Ho_Chi_Minh","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Hong_Kong","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Hovd","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Irkutsk","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Istanbul","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["asia/istanbul","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Asia/Jakarta","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Jayapura","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["ASIA/JAYAPURA","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Asia/Jerusalem","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Asia/Kabul","2026-07-02T07:30Z","2026-03-08T22:00Z","2026-10-25T20:45Z"] +["Asia/Kamchatka","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Asia/Karachi","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Kashgar","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["asia/kashgar","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Asia/Kathmandu","2026-07-02T06:15Z","2026-03-08T20:45Z","2026-10-25T19:30Z"] +["Asia/Katmandu","2026-07-02T06:15Z","2026-03-08T20:45Z","2026-10-25T19:30Z"] +["Asia/Khandyga","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Asia/Kolkata","2026-07-02T06:30Z","2026-03-08T21:00Z","2026-10-25T19:45Z"] +["Asia/Krasnoyarsk","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Kuala_Lumpur","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["ASIA/KUALA_LUMPUR","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Kuching","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["asia/kuching","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Kuwait","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Asia/Macao","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Macau","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Magadan","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Asia/Makassar","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Manila","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Muscat","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["asia/muscat","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Asia/Nicosia","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Asia/Novokuznetsk","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Novosibirsk","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["ASIA/NOVOSIBIRSK","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Omsk","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Asia/Oral","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Phnom_Penh","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Pontianak","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["asia/pontianak","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Pyongyang","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Asia/Qatar","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Asia/Qostanay","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Qyzylorda","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Rangoon","2026-07-02T05:30Z","2026-03-08T20:00Z","2026-10-25T18:45Z"] +["Asia/Riyadh","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Asia/Saigon","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["asia/saigon","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["ASIA/SAIGON","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Sakhalin","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Asia/Samarkand","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Seoul","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Asia/Shanghai","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Singapore","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Srednekolymsk","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Asia/Taipei","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["asia/taipei","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Tashkent","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Tbilisi","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Asia/Tehran","2026-07-02T08:30Z","2026-03-08T23:00Z","2026-10-25T21:45Z"] +["Asia/Tel_Aviv","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["ASIA/TEL_AVIV","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Asia/Thimbu","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Asia/Thimphu","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Asia/Tokyo","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["asia/tokyo","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Asia/Tomsk","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Ujung_Pandang","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Ulaanbaatar","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Ulan_Bator","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Asia/Urumqi","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Asia/Ust-Nera","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Asia/Vientiane","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["asia/vientiane","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Asia/Vladivostok","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["ASIA/VLADIVOSTOK","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Asia/Yakutsk","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Asia/Yangon","2026-07-02T05:30Z","2026-03-08T20:00Z","2026-10-25T18:45Z"] +["Asia/Yekaterinburg","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Asia/Yerevan","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Atlantic/Azores","2026-07-01T12:00Z","2026-03-09T03:30Z","2026-10-25T01:15Z"] +["Atlantic/Bermuda","2026-07-01T15:00Z","2026-03-08T06:30Z","2026-10-25T04:15Z"] +["atlantic/bermuda","2026-07-01T15:00Z","2026-03-08T06:30Z","2026-10-25T04:15Z"] +["Atlantic/Canary","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Atlantic/Cape_Verde","2026-07-01T13:00Z","2026-03-09T03:30Z","2026-10-25T02:15Z"] +["Atlantic/Faeroe","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Atlantic/Faroe","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Atlantic/Jan_Mayen","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["ATLANTIC/JAN_MAYEN","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Atlantic/Madeira","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Atlantic/Reykjavik","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["atlantic/reykjavik","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Atlantic/South_Georgia","2026-07-01T14:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["Atlantic/St_Helena","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Atlantic/Stanley","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["Australia/ACT","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/Adelaide","2026-07-02T02:30Z","2026-03-08T16:00Z","2026-10-25T14:45Z"] +["Australia/Brisbane","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Australia/Broken_Hill","2026-07-02T02:30Z","2026-03-08T16:00Z","2026-10-25T14:45Z"] +["australia/broken_hill","2026-07-02T02:30Z","2026-03-08T16:00Z","2026-10-25T14:45Z"] +["Australia/Canberra","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/Currie","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["AUSTRALIA/CURRIE","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/Darwin","2026-07-02T02:30Z","2026-03-08T17:00Z","2026-10-25T15:45Z"] +["Australia/Eucla","2026-07-02T03:15Z","2026-03-08T17:45Z","2026-10-25T16:30Z"] +["Australia/Hobart","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/LHI","2026-07-02T01:30Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/Lindeman","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["australia/lindeman","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Australia/Lord_Howe","2026-07-02T01:30Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/Melbourne","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/North","2026-07-02T02:30Z","2026-03-08T17:00Z","2026-10-25T15:45Z"] +["Australia/NSW","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/Perth","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Australia/Queensland","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["AUSTRALIA/QUEENSLAND","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Australia/South","2026-07-02T02:30Z","2026-03-08T16:00Z","2026-10-25T14:45Z"] +["australia/south","2026-07-02T02:30Z","2026-03-08T16:00Z","2026-10-25T14:45Z"] +["Australia/Sydney","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/Tasmania","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/Victoria","2026-07-02T02:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Australia/West","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Australia/Yancowinna","2026-07-02T02:30Z","2026-03-08T16:00Z","2026-10-25T14:45Z"] +["Brazil/Acre","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["Brazil/DeNoronha","2026-07-01T14:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["brazil/denoronha","2026-07-01T14:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["Brazil/East","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["Brazil/West","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["Canada/Atlantic","2026-07-01T15:00Z","2026-03-08T06:30Z","2026-10-25T04:15Z"] +["CANADA/ATLANTIC","2026-07-01T15:00Z","2026-03-08T06:30Z","2026-10-25T04:15Z"] +["Canada/Central","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["Canada/Eastern","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["Canada/Mountain","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["Canada/Newfoundland","2026-07-01T14:30Z","2026-03-08T06:00Z","2026-10-25T03:45Z"] +["canada/newfoundland","2026-07-01T14:30Z","2026-03-08T06:00Z","2026-10-25T03:45Z"] +["Canada/Pacific","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["Canada/Saskatchewan","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["Canada/Yukon","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["CET","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Chile/Continental","2026-07-01T16:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["Chile/EasterIsland","2026-07-01T18:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["CST6CDT","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["cst6cdt","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["CST6CDT","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["Cuba","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["EET","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Egypt","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T22:15Z"] +["Eire","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["EST","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["EST5EDT","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["Etc/GMT","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["etc/gmt","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Etc/GMT+0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Etc/GMT+1","2026-07-01T13:00Z","2026-03-09T03:30Z","2026-10-25T02:15Z"] +["Etc/GMT+10","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["Etc/GMT+11","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["ETC/GMT+11","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Etc/GMT+12","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Etc/GMT+2","2026-07-01T14:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["Etc/GMT+3","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["etc/gmt+3","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["Etc/GMT+4","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["Etc/GMT+5","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["Etc/GMT+6","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["Etc/GMT+7","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["Etc/GMT+8","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["Etc/GMT+9","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["Etc/GMT-0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["etc/gmt-0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Etc/GMT-1","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["ETC/GMT-1","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Etc/GMT-10","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Etc/GMT-11","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Etc/GMT-12","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Etc/GMT-13","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Etc/GMT-14","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["Etc/GMT-2","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["etc/gmt-2","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Etc/GMT-3","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Etc/GMT-4","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Etc/GMT-5","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Etc/GMT-6","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Etc/GMT-7","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["ETC/GMT-7","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Etc/GMT-8","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Etc/GMT-9","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["etc/gmt-9","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Etc/GMT0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Etc/Greenwich","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Etc/UCT","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Etc/Universal","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Etc/UTC","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Etc/Zulu","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Europe/Amsterdam","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["europe/amsterdam","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Andorra","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Astrakhan","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["EUROPE/ASTRAKHAN","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Europe/Athens","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Belfast","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Europe/Belgrade","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Berlin","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Bratislava","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["europe/bratislava","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Brussels","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Bucharest","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Budapest","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Busingen","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Chisinau","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Copenhagen","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["EUROPE/COPENHAGEN","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Dublin","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["europe/dublin","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Europe/Gibraltar","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Guernsey","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Europe/Helsinki","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Isle_of_Man","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Europe/Istanbul","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Europe/Jersey","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Europe/Kaliningrad","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["europe/kaliningrad","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Kiev","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Kirov","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Europe/Kyiv","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["EUROPE/KYIV","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Lisbon","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Europe/Ljubljana","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/London","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Europe/Luxembourg","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["europe/luxembourg","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Madrid","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Malta","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Mariehamn","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Minsk","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Europe/Monaco","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Moscow","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Europe/Nicosia","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["europe/nicosia","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["EUROPE/NICOSIA","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Oslo","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Paris","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Podgorica","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Prague","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Riga","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Rome","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Samara","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["europe/samara","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Europe/San_Marino","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Sarajevo","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Saratov","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Europe/Simferopol","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["EUROPE/SIMFEROPOL","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Europe/Skopje","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Sofia","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Stockholm","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["europe/stockholm","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Tallinn","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Tirane","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Tiraspol","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Ulyanovsk","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Europe/Uzhgorod","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Vaduz","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Vatican","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["europe/vatican","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Vienna","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["EUROPE/VIENNA","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Vilnius","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Volgograd","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Europe/Warsaw","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Zagreb","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Zaporozhye","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Europe/Zurich","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["europe/zurich","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Factory","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["GB","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["GB-Eire","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["GMT","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["GMT+0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["GMT+0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["GMT-0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["GMT0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["gmt0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Greenwich","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Hongkong","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["HST","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["Iceland","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Indian/Antananarivo","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Indian/Chagos","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["Indian/Christmas","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["indian/christmas","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["Indian/Cocos","2026-07-02T05:30Z","2026-03-08T20:00Z","2026-10-25T18:45Z"] +["Indian/Comoro","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["INDIAN/COMORO","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Indian/Kerguelen","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Indian/Mahe","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Indian/Maldives","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["Indian/Mauritius","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Indian/Mayotte","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["indian/mayotte","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["Indian/Reunion","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["Iran","2026-07-02T08:30Z","2026-03-08T23:00Z","2026-10-25T21:45Z"] +["Israel","2026-07-02T09:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["Jamaica","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["Japan","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Kwajalein","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["KWAJALEIN","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Libya","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["libya","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["MET","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Mexico/BajaNorte","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["Mexico/BajaSur","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["Mexico/General","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["MST","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["MST7MDT","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["Navajo","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["navajo","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["NZ","2026-07-02T00:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["NZ-CHAT","2026-07-01T23:15Z","2026-03-08T12:45Z","2026-10-25T11:30Z"] +["Pacific/Apia","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["PACIFIC/APIA","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Auckland","2026-07-02T00:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Bougainville","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Pacific/Chatham","2026-07-01T23:15Z","2026-03-08T12:45Z","2026-10-25T11:30Z"] +["Pacific/Chuuk","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["pacific/chuuk","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Pacific/Easter","2026-07-01T18:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["Pacific/Efate","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Pacific/Enderbury","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Fakaofo","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Fiji","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Pacific/Funafuti","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Pacific/Galapagos","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["pacific/galapagos","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["PACIFIC/GALAPAGOS","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["Pacific/Gambier","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["Pacific/Guadalcanal","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Pacific/Guam","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Pacific/Honolulu","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["Pacific/Johnston","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["Pacific/Kanton","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Kiritimati","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["pacific/kiritimati","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["Pacific/Kosrae","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Pacific/Kwajalein","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Pacific/Majuro","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Pacific/Marquesas","2026-07-01T21:30Z","2026-03-08T12:00Z","2026-10-25T10:45Z"] +["PACIFIC/MARQUESAS","2026-07-01T21:30Z","2026-03-08T12:00Z","2026-10-25T10:45Z"] +["Pacific/Midway","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Nauru","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Pacific/Niue","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["pacific/niue","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Norfolk","2026-07-02T01:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Pacific/Noumea","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Pacific/Pago_Pago","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Palau","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Pacific/Pitcairn","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["Pacific/Pohnpei","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Pacific/Ponape","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["pacific/ponape","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["Pacific/Port_Moresby","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["PACIFIC/PORT_MORESBY","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Pacific/Rarotonga","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["Pacific/Saipan","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Pacific/Samoa","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Tahiti","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["Pacific/Tarawa","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Pacific/Tongatapu","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["pacific/tongatapu","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["Pacific/Truk","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Pacific/Wake","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Pacific/Wallis","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Pacific/Yap","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["Poland","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["POLAND","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Portugal","2026-07-01T11:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["PRC","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["prc","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["PST8PDT","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["ROC","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["ROK","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["Singapore","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["Turkey","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["UCT","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Universal","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["universal","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["US/Alaska","2026-07-01T20:00Z","2026-03-08T11:30Z","2026-10-25T09:15Z"] +["US/Aleutian","2026-07-01T21:00Z","2026-03-08T12:30Z","2026-10-25T10:15Z"] +["US/ALEUTIAN","2026-07-01T21:00Z","2026-03-08T12:30Z","2026-10-25T10:15Z"] +["US/Arizona","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["US/Central","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["US/East-Indiana","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["US/Eastern","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["US/Hawaii","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["us/hawaii","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["US/Indiana-Starke","2026-07-01T17:00Z","2026-03-08T08:30Z","2026-10-25T06:15Z"] +["US/Michigan","2026-07-01T16:00Z","2026-03-08T07:30Z","2026-10-25T05:15Z"] +["US/Mountain","2026-07-01T18:00Z","2026-03-08T09:30Z","2026-10-25T07:15Z"] +["US/Pacific","2026-07-01T19:00Z","2026-03-08T10:30Z","2026-10-25T08:15Z"] +["US/Samoa","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["UTC","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["UTC","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["W-SU","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["w-su","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["WET","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Zulu","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["acdt","2026-07-02T01:30Z","2026-03-08T16:00Z","2026-10-25T14:45Z"] +["ACDT","2026-07-02T01:30Z","2026-03-08T16:00Z","2026-10-25T14:45Z"] +["acst","2026-07-02T02:30Z","2026-03-08T17:00Z","2026-10-25T15:45Z"] +["ACST","2026-07-02T02:30Z","2026-03-08T17:00Z","2026-10-25T15:45Z"] +["addt","2026-07-01T14:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["ADDT","2026-07-01T14:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["adt","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["ADT","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["aedt","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["AEDT","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["aest","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["AEST","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["ahdt","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["AHDT","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["ahst","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["AHST","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["akdt","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["AKDT","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["akst","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["AKST","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["amt","2026-07-01T15:50Z","2026-03-08T06:20Z","2026-10-25T05:05Z"] +["AMT","2026-07-01T15:50Z","2026-03-08T06:20Z","2026-10-25T05:05Z"] +["apt","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["APT","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["ast","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["AST","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["awdt","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["AWDT","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["awst","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["AWST","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["awt","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["AWT","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["bdst","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["BDST","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["bdt","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["BDT","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["bmt","2026-07-01T15:58Z","2026-03-08T06:28Z","2026-10-25T05:13Z"] +["BMT","2026-07-01T15:58Z","2026-03-08T06:28Z","2026-10-25T05:13Z"] +["bst","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["BST","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["cast","2026-07-02T02:30Z","2026-03-08T17:00Z","2026-10-25T15:45Z"] +["CAST","2026-07-02T02:30Z","2026-03-08T17:00Z","2026-10-25T15:45Z"] +["cat","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["CAT","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["cddt","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["CDDT","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["cdt","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["CDT","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["cemt","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["CEMT","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["cest","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["CEST","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["cet","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["CET","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["cmt","2026-07-01T16:16Z","2026-03-08T06:46Z","2026-10-25T05:31Z"] +["CMT","2026-07-01T16:16Z","2026-03-08T06:46Z","2026-10-25T05:31Z"] +["cpt","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["CPT","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["cst","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["CST","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["cwt","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["CWT","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["chst","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["CHST","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["dmt","2026-07-01T12:25Z","2026-03-09T02:55Z","2026-10-25T01:40Z"] +["DMT","2026-07-01T12:25Z","2026-03-09T02:55Z","2026-10-25T01:40Z"] +["eat","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["EAT","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["eddt","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["EDDT","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["edt","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["EDT","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["eest","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["EEST","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["eet","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["EET","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["emt","2026-07-01T19:17Z","2026-03-08T09:47Z","2026-10-25T08:32Z"] +["EMT","2026-07-01T19:17Z","2026-03-08T09:47Z","2026-10-25T08:32Z"] +["ept","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["EPT","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["est","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["EST","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["ewt","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["EWT","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["ffmt","2026-07-01T16:04Z","2026-03-08T06:34Z","2026-10-25T05:19Z"] +["FFMT","2026-07-01T16:04Z","2026-03-08T06:34Z","2026-10-25T05:19Z"] +["fmt","2026-07-01T13:07Z","2026-03-09T03:37Z","2026-10-25T02:22Z"] +["FMT","2026-07-01T13:07Z","2026-03-09T03:37Z","2026-10-25T02:22Z"] +["gdt","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["GDT","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["gmt","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["GMT","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["gst","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["GST","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["hdt","2026-07-01T21:30Z","2026-03-08T12:00Z","2026-10-25T10:45Z"] +["HDT","2026-07-01T21:30Z","2026-03-08T12:00Z","2026-10-25T10:45Z"] +["hkst","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["HKST","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["hkt","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["HKT","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["hmt","2026-07-01T17:29Z","2026-03-08T07:59Z","2026-10-25T06:44Z"] +["HMT","2026-07-01T17:29Z","2026-03-08T07:59Z","2026-10-25T06:44Z"] +["hpt","2026-07-01T21:30Z","2026-03-08T12:00Z","2026-10-25T10:45Z"] +["HPT","2026-07-01T21:30Z","2026-03-08T12:00Z","2026-10-25T10:45Z"] +["hst","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["HST","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["hwt","2026-07-01T21:30Z","2026-03-08T12:00Z","2026-10-25T10:45Z"] +["HWT","2026-07-01T21:30Z","2026-03-08T12:00Z","2026-10-25T10:45Z"] +["iddt","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["IDDT","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["idt","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["IDT","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["imt","2026-07-02T05:02Z","2026-03-08T19:32Z","2026-10-25T18:17Z"] +["IMT","2026-07-02T05:02Z","2026-03-08T19:32Z","2026-10-25T18:17Z"] +["ist","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["IST","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["jdt","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["JDT","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["jmt","2026-07-02T09:39Z","2026-03-09T00:09Z","2026-10-25T22:54Z"] +["JMT","2026-07-02T09:39Z","2026-03-09T00:09Z","2026-10-25T22:54Z"] +["jst","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["JST","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["kdt","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["KDT","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["kmt","2026-07-01T10:24Z","2026-03-09T00:54Z","2026-10-25T23:39Z"] +["KMT","2026-07-01T10:24Z","2026-03-09T00:54Z","2026-10-25T23:39Z"] +["kst","2026-07-02T03:30Z","2026-03-08T18:00Z","2026-10-25T16:45Z"] +["KST","2026-07-02T03:30Z","2026-03-08T18:00Z","2026-10-25T16:45Z"] +["lst","2026-07-02T09:23Z","2026-03-08T23:53Z","2026-10-25T22:38Z"] +["LST","2026-07-02T09:23Z","2026-03-08T23:53Z","2026-10-25T22:38Z"] +["mddt","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["MDDT","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["mdst","2026-07-02T07:28Z","2026-03-08T21:58Z","2026-10-25T20:43Z"] +["MDST","2026-07-02T07:28Z","2026-03-08T21:58Z","2026-10-25T20:43Z"] +["mdt","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["MDT","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["mest","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["MEST","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["met","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["MET","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["mmt","2026-07-02T09:29Z","2026-03-08T23:59Z","2026-10-25T22:44Z"] +["MMT","2026-07-02T09:29Z","2026-03-08T23:59Z","2026-10-25T22:44Z"] +["mpt","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["MPT","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["msd","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["MSD","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["msk","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["MSK","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["mst","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["MST","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["mwt","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["MWT","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["nddt","2026-07-01T13:30Z","2026-03-09T04:00Z","2026-10-25T02:45Z"] +["NDDT","2026-07-01T13:30Z","2026-03-09T04:00Z","2026-10-25T02:45Z"] +["ndt","2026-07-01T14:30Z","2026-03-08T05:01Z","2026-10-25T03:45Z"] +["NDT","2026-07-01T14:30Z","2026-03-08T05:01Z","2026-10-25T03:45Z"] +["npt","2026-07-01T14:30Z","2026-03-09T05:00Z","2026-10-25T03:45Z"] +["NPT","2026-07-01T14:30Z","2026-03-09T05:00Z","2026-10-25T03:45Z"] +["nst","2026-07-01T15:30Z","2026-03-08T06:00Z","2026-10-25T04:45Z"] +["NST","2026-07-01T15:30Z","2026-03-08T06:00Z","2026-10-25T04:45Z"] +["nwt","2026-07-01T14:30Z","2026-03-09T05:00Z","2026-10-25T03:45Z"] +["NWT","2026-07-01T14:30Z","2026-03-09T05:00Z","2026-10-25T03:45Z"] +["nzdt","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["NZDT","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["nzmt","2026-07-02T00:30Z","2026-03-08T15:00Z","2026-10-25T13:45Z"] +["NZMT","2026-07-02T00:30Z","2026-03-08T15:00Z","2026-10-25T13:45Z"] +["nzst","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["NZST","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["pddt","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["PDDT","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["pdt","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["PDT","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["pkst","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["PKST","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["pkt","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["PKT","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["plmt","2026-07-02T04:53Z","2026-03-08T19:23Z","2026-10-25T18:08Z"] +["PLMT","2026-07-02T04:53Z","2026-03-08T19:23Z","2026-10-25T18:08Z"] +["pmt","2026-07-01T15:40Z","2026-03-08T06:10Z","2026-10-25T04:55Z"] +["PMT","2026-07-01T15:40Z","2026-03-08T06:10Z","2026-10-25T04:55Z"] +["ppmt","2026-07-01T16:49Z","2026-03-08T07:19Z","2026-10-25T06:04Z"] +["PPMT","2026-07-01T16:49Z","2026-03-08T07:19Z","2026-10-25T06:04Z"] +["ppt","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["PPT","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["pst","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["PST","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["pwt","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["PWT","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["qmt","2026-07-01T17:14Z","2026-03-08T07:44Z","2026-10-25T06:29Z"] +["QMT","2026-07-01T17:14Z","2026-03-08T07:44Z","2026-10-25T06:29Z"] +["rmt","2026-07-01T10:23Z","2026-03-09T00:53Z","2026-10-25T23:38Z"] +["RMT","2026-07-01T10:23Z","2026-03-09T00:53Z","2026-10-25T23:38Z"] +["sast","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["SAST","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["sdmt","2026-07-01T16:40Z","2026-03-08T07:10Z","2026-10-25T05:55Z"] +["SDMT","2026-07-01T16:40Z","2026-03-08T07:10Z","2026-10-25T05:55Z"] +["sjmt","2026-07-01T17:36Z","2026-03-08T08:06Z","2026-10-25T06:51Z"] +["SJMT","2026-07-01T17:36Z","2026-03-08T08:06Z","2026-10-25T06:51Z"] +["smt","2026-07-01T15:51Z","2026-03-08T06:21Z","2026-10-25T05:06Z"] +["SMT","2026-07-01T15:51Z","2026-03-08T06:21Z","2026-10-25T05:06Z"] +["sst","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["SST","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["tbmt","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["TBMT","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["tmt","2026-07-02T08:34Z","2026-03-08T23:04Z","2026-10-25T21:49Z"] +["TMT","2026-07-02T08:34Z","2026-03-08T23:04Z","2026-10-25T21:49Z"] +["uct","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["UCT","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["utc","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["UTC","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["wast","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["WAST","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["wat","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["WAT","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["wemt","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["WEMT","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["west","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["WEST","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["wet","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["WET","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["wib","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["WIB","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["wita","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["WITA","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["wit","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["WIT","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["wmt","2026-07-01T10:36Z","2026-03-09T01:06Z","2026-10-25T23:51Z"] +["WMT","2026-07-01T10:36Z","2026-03-09T01:06Z","2026-10-25T23:51Z"] +["yddt","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["YDDT","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["ydt","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["YDT","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["ypt","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["YPT","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["yst","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["YST","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["ywt","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["YWT","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["a","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["A","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["b","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["B","2026-07-02T10:00Z","2026-03-09T00:30Z","2026-10-25T23:15Z"] +["c","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["C","2026-07-02T09:00Z","2026-03-08T23:30Z","2026-10-25T22:15Z"] +["d","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["D","2026-07-02T08:00Z","2026-03-08T22:30Z","2026-10-25T21:15Z"] +["e","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["E","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["f","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["F","2026-07-02T06:00Z","2026-03-08T20:30Z","2026-10-25T19:15Z"] +["g","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["G","2026-07-02T05:00Z","2026-03-08T19:30Z","2026-10-25T18:15Z"] +["h","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["H","2026-07-02T04:00Z","2026-03-08T18:30Z","2026-10-25T17:15Z"] +["i","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["I","2026-07-02T03:00Z","2026-03-08T17:30Z","2026-10-25T16:15Z"] +["k","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["K","2026-07-02T02:00Z","2026-03-08T16:30Z","2026-10-25T15:15Z"] +["l","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["L","2026-07-02T01:00Z","2026-03-08T15:30Z","2026-10-25T14:15Z"] +["m","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["M","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["n","2026-07-01T13:00Z","2026-03-09T03:30Z","2026-10-25T02:15Z"] +["N","2026-07-01T13:00Z","2026-03-09T03:30Z","2026-10-25T02:15Z"] +["o","2026-07-01T14:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["O","2026-07-01T14:00Z","2026-03-09T04:30Z","2026-10-25T03:15Z"] +["p","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["P","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["q","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["Q","2026-07-01T16:00Z","2026-03-08T06:30Z","2026-10-25T05:15Z"] +["r","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["R","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["s","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["S","2026-07-01T18:00Z","2026-03-08T08:30Z","2026-10-25T07:15Z"] +["t","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["T","2026-07-01T19:00Z","2026-03-08T09:30Z","2026-10-25T08:15Z"] +["u","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["U","2026-07-01T20:00Z","2026-03-08T10:30Z","2026-10-25T09:15Z"] +["v","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["V","2026-07-01T21:00Z","2026-03-08T11:30Z","2026-10-25T10:15Z"] +["w","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["W","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["x","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["X","2026-07-01T23:00Z","2026-03-08T13:30Z","2026-10-25T12:15Z"] +["y","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["Y","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["z","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Z","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["+5","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["+05","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["-12","2026-07-02T00:00Z","2026-03-08T14:30Z","2026-10-25T13:15Z"] +["+0530","2026-07-02T06:30Z","2026-03-08T21:00Z","2026-10-25T19:45Z"] +["-0330","2026-07-01T15:30Z","2026-03-08T06:00Z","2026-10-25T04:45Z"] +["+05:30","2026-07-02T06:30Z","2026-03-08T21:00Z","2026-10-25T19:45Z"] +["+5:30","2026-07-02T06:30Z","2026-03-08T21:00Z","2026-10-25T19:45Z"] +["+5:3","2026-07-02T06:57Z","2026-03-08T21:27Z","2026-10-25T20:12Z"] +["+053000","2026-07-02T06:30Z","2026-03-08T21:00Z","2026-10-25T19:45Z"] +["-05:30:15","2026-07-01T17:30Z","2026-03-08T08:00Z","2026-10-25T06:45Z"] +["+25","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["+100","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["GMT+1","2026-07-01T11:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["GMT-3","2026-07-01T15:00Z","2026-03-08T05:30Z","2026-10-25T04:15Z"] +["GMT+05:30","2026-07-02T06:30Z","2026-03-08T21:00Z","2026-10-25T19:45Z"] +["UTC+3","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["Z","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["z","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["UTC","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["utc","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Utc","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["GMT","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +[" UTC","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["(UTC)","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["UTC)","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["(Europe/Budapest)","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +[" Europe/Budapest","2026-07-02T10:00Z","2026-03-09T01:30Z","2026-10-26T00:15Z"] +["Europe/Budapest ","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +[" ","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["+","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["-","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["+5x","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["Mars/Base","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["Europe","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["UTC+0","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["GMT0","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["Etc/GMT-14","2026-07-01T22:00Z","2026-03-08T12:30Z","2026-10-25T11:15Z"] +["((UTC))","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["(UTC))","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["(UTC","2026-07-01T12:00Z","2026-03-09T02:30Z","2026-10-25T01:15Z"] +["EST)","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] +["(EST)","2026-07-01T17:00Z","2026-03-08T07:30Z","2026-10-25T06:15Z"] +["(+05:00)","2026-07-02T07:00Z","2026-03-08T21:30Z","2026-10-25T20:15Z"] +["+05:00)","DateInvalidTimeZoneException","DateInvalidTimeZoneException","DateInvalidTimeZoneException"] diff --git a/scripts/conformance/cron-oracle/README.md b/scripts/conformance/cron-oracle/README.md new file mode 100644 index 000000000..5f7cc4b40 --- /dev/null +++ b/scripts/conformance/cron-oracle/README.md @@ -0,0 +1,33 @@ +# Published PHP cron and schedule corpora + +The native schedule engine (`rust/src/runtime/schedule_cron.rs`, +`schedule_zones.rs` and `schedule_spec.rs`) is checked against answers that +published PHP Server 2.5.14 itself computes. These scripts produce them inside +the frozen image, which bundles `dragonmantank/cron-expression` 3.6.0, PHP +8.3.35 and its time zone database 2026.1: + +```sh +image=ghcr.io/durable-workflow/server@sha256:28fa0f3968ea253c727aad7fe583bb61c788553f1e4b96ece6ea4570d20faf42 +run() { docker run --rm --pull=never --network none --user 1000:1000 --read-only \ + -v "$PWD/scripts/conformance/cron-oracle:/probe:ro" -e SKIP_ZONES=Africa/Casablanca,Africa/El_Aaiun \ + --entrypoint php "$image" "/probe/$1" "${@:2}"; } +run next-run.php 7 2000 | head -1200 > rust/tests/fixtures/cron/php-2.5.14-next-run.jsonl +run zone-names.php > rust/tests/fixtures/cron/php-2.5.14-zone-names.jsonl +run next-fire.php 5 1500 | head -600 > rust/tests/fixtures/cron/php-2.5.14-next-fire.jsonl +``` + +- `next-run.php SEED COUNT`: random expressions (names, ranges, steps, lists, + `L`, `W`, `#`, `?` and aliases) at instants clustered around daylight-saving + changes, month ends and leap days, in fifteen zones, with PHP's next run or + exception class. +- `zone-names.php`: every identifier PHP lists, with links, case variants, + every abbreviation, numeric offsets and refused spellings, each with PHP's + answers for three fixed cases. +- `next-fire.php SEED COUNT`: whole schedule specifications (cron expressions, + intervals with offsets, zones and malformed rules) with + `WorkflowSchedule::computeNextFireAt`. + +The zones skipped above, and America/Edmonton, Inuvik, Vancouver and +Winnipeg, have rules that changed after PHP's database. Native uses the newer +bundled IANA data (2026e), so those zones legitimately differ there and are +not in the corpora. Larger seeds were checked locally before committing. diff --git a/scripts/conformance/cron-oracle/next-fire.php b/scripts/conformance/cron-oracle/next-fire.php new file mode 100644 index 000000000..6131ac26e --- /dev/null +++ b/scripts/conformance/cron-oracle/next-fire.php @@ -0,0 +1,48 @@ + $a[mt_rand(0, count($a) - 1)]; +$n = fn (int $max) => (string) mt_rand(0, $max); +$duration = function () use ($pick, $n): string { + if (mt_rand(0, 9) === 0) { + return $pick(['P', 'PT', 'p1d', 'P1DT', 'PT1H1D', 'P2D1W', 'PT1.5S', 'P0001-00-02T00:00:00', 'P-1D', ' P1D', 'P1D ', + 'P0D', 'PT0S', 'P10000000000000000000D', 'P01D', 'PT0001H', 'P1W2D', 'P1H', '', 'P0000-00-00T00:00:01']); + } + $date = ''; $time = ''; + foreach (['Y' => 2, 'M' => 14, 'W' => 3, 'D' => 40] as $unit => $max) { + if (mt_rand(0, 3) === 0) { $date .= $n($max).$unit; } + } + foreach (['H' => 50, 'M' => 90, 'S' => 7200] as $unit => $max) { + if (mt_rand(0, 2) === 0) { $time .= $n($max).$unit; } + } + if ($date === '' && $time === '') { $time = (mt_rand(1, 3600)).'S'; } + return 'P'.$date.($time === '' ? '' : 'T'.$time); +}; +$crons = ['0 3 * * *', '*/15 * * * *', '30 2 * * SUN', '0 0 1 * *', '@hourly', '5 4 L * ?', '0 9 * * 1-5', '0 12 29 2 *', 'bad cron', '']; +$zones = ['UTC', 'Europe/Budapest', 'America/New_York', 'Australia/Lord_Howe', 'Asia/Kolkata', 'EST', '+05:30', 'Mars/Base', '']; +for ($i = 0; $i < (int) $argv[2]; ++$i) { + $spec = []; + if (mt_rand(0, 2) > 0) { + $spec['cron_expressions'] = array_map(fn () => $pick($crons), range(1, mt_rand(1, 2))); + } + if (mt_rand(0, 2) > 0) { + $spec['intervals'] = array_map(fn () => array_filter(['every' => $duration(), + 'offset' => mt_rand(0, 2) === 0 ? $duration() : null], fn ($v) => $v !== null), range(1, mt_rand(1, 2))); + } + if (mt_rand(0, 3) > 0) { $spec['timezone'] = $pick($zones); } + $after = sprintf('%.6F', mt_rand(strtotime('2025-01-01'), strtotime('2028-12-31')) + mt_rand(0, 999999) / 1e6); + $at = DateTimeImmutable::createFromFormat('U.u', $after)->format('Y-m-d\TH:i:s.u\Z'); + $case = ['spec' => $spec, 'after' => $at]; + try { + $schedule = new WorkflowSchedule(); + $schedule->spec = $spec; + $next = $schedule->computeNextFireAt(new Carbon\Carbon($at)); + $case['next'] = $next === null ? null : Carbon\Carbon::instance($next)->utc()->format('Y-m-d\TH:i:s.u\Z'); + } catch (Throwable $e) { + $case['error'] = get_class($e); + } + echo json_encode($case, JSON_UNESCAPED_SLASHES), "\n"; +} diff --git a/scripts/conformance/cron-oracle/next-run.php b/scripts/conformance/cron-oracle/next-run.php new file mode 100644 index 000000000..99b348664 --- /dev/null +++ b/scripts/conformance/cron-oracle/next-run.php @@ -0,0 +1,63 @@ + $a[mt_rand(0, count($a) - 1)]; +$field = function (int $min, int $max, array $names = [], bool $dow = false, bool $dom = false) use ($pick) { + $n = fn () => (string) mt_rand($min, $max); + $name = fn () => $names ? $pick($names) : $n(); + $one = function () use ($pick, $n, $name, $min, $max, $dow, $dom) { + switch (mt_rand(0, $dow || $dom ? 9 : 6)) { + case 0: return '*'; + case 1: return $n(); + case 2: $a = mt_rand($min, $max); $b = mt_rand($a, $max); return "$a-$b"; + case 3: return '*/' . mt_rand(1, $max + 2); + case 4: $a = mt_rand($min, $max); $b = mt_rand($a, $max); return "$a-$b/" . mt_rand(1, 7); + case 5: return $name(); + case 6: return $n() . ',' . $n(); + case 7: return $dow ? mt_rand(0, 7) . 'L' : 'L'; + case 8: return $dow ? mt_rand(0, 7) . '#' . mt_rand(1, 5) : mt_rand(1, 31) . 'W'; + default: return '?'; + } + }; + return $one(); +}; +$months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']; +$days = ['MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN']; +$aliases = ['@yearly', '@annually', '@monthly', '@weekly', '@daily', '@midnight', '@hourly', '@DAILY']; +for ($i = 0; $i < $count; ++$i) { + $tz = $pick($zones); + if (mt_rand(0, 19) === 0) { + $cron = $pick($aliases); + } else { + $cron = implode(' ', [$field(0, 59), $field(0, 23), $field(1, 31, [], false, true), + $field(1, 12, $months), $field(0, 7, $days, true)]); + } + // Instants near a transition of this zone, a month end or a leap day. + $zone = new DateTimeZone($tz); + $transitions = $zone->getTransitions(strtotime('2024-01-01'), strtotime('2029-01-01')); + $choice = mt_rand(0, 3); + if ($choice < 2 && count($transitions) > 1) { + $base = $pick(array_slice($transitions, 1))['ts']; + } elseif ($choice === 2) { + $base = strtotime($pick(['2028-02-28', '2028-02-29', '2027-02-28', '2026-12-31', '2026-04-30', '2026-01-31']) . ' 23:00:00 UTC'); + } else { + $base = mt_rand(strtotime('2025-01-01'), strtotime('2028-12-31')); + } + $after = $base + mt_rand(-7200, 7200) + mt_rand(0, 1) * mt_rand(0, 59) + (mt_rand(0, 3) === 0 ? 0.5 : 0); + $at = DateTimeImmutable::createFromFormat('U.u', sprintf('%.6F', $after)); + $case = ['cron' => $cron, 'after' => $at->format('Y-m-d\TH:i:s.u\Z'), 'tz' => $tz]; + try { + $next = (new Cron\CronExpression($cron))->getNextRunDate($at, 0, false, $tz); + $case['next'] = $next->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d\TH:i:s.u\Z'); + } catch (Throwable $e) { + $case['error'] = get_class($e); + } + echo json_encode($case, JSON_UNESCAPED_SLASHES), "\n"; +} diff --git a/scripts/conformance/cron-oracle/zone-names.php b/scripts/conformance/cron-oracle/zone-names.php new file mode 100644 index 000000000..69902e152 --- /dev/null +++ b/scripts/conformance/cron-oracle/zone-names.php @@ -0,0 +1,33 @@ + $id) { + if (in_array($id, $skip, true)) { continue; } + $names[] = $id; + if ($i % 7 === 0) { $names[] = strtolower($id); } + if ($i % 11 === 0) { $names[] = strtoupper($id); } +} +foreach (array_keys(DateTimeZone::listAbbreviations()) as $abbr) { $names[] = $abbr; $names[] = strtoupper($abbr); } +foreach (['+5', '+05', '-12', '+0530', '-0330', '+05:30', '+5:30', '+5:3', '+053000', '-05:30:15', '+25', '+100', + 'GMT+1', 'GMT-3', 'GMT+05:30', 'UTC+3', 'Z', 'z', 'UTC', 'utc', 'Utc', 'GMT', ' UTC', '(UTC)', 'UTC)', '(Europe/Budapest)', + ' Europe/Budapest', 'Europe/Budapest ', '', ' ', '+', '-', '+5x', 'Mars/Base', 'Europe', 'UTC+0', 'GMT0', 'Etc/GMT-14', '((UTC))', '(UTC))', '(UTC', 'EST)', '(EST)', '(+05:00)', '+05:00)'] as $extra) { + $names[] = $extra; +} +$cases = [['0 12 * * *', '2026-07-01T10:17:00.000000Z'], ['30 2 * * *', '2026-03-08T05:00:00.000000Z'], + ['15 1 * * *', '2026-10-25T00:20:00.000000Z']]; +foreach ($names as $tz) { + $results = []; + foreach ($cases as [$cron, $after]) { + try { + $next = (new Cron\CronExpression($cron))->getNextRunDate(new DateTimeImmutable($after), 0, false, $tz); + $results[] = $next->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d\TH:i\Z'); + } catch (Throwable $e) { + $results[] = get_class($e); + } + } + echo json_encode([$tz, ...$results], JSON_UNESCAPED_SLASHES), "\n"; +} diff --git a/scripts/conformance/server-parity/contract.mjs b/scripts/conformance/server-parity/contract.mjs index ea2ab4287..f3ff6667f 100644 --- a/scripts/conformance/server-parity/contract.mjs +++ b/scripts/conformance/server-parity/contract.mjs @@ -12,6 +12,7 @@ import {checkWorkerDeregistration} from './worker-deregistration-contract.mjs'; import {checkLateWorkflowCompletion} from './late-workflow-completion-contract.mjs'; import {checkSearchAttributes} from './search-attribute-contract.mjs'; import {checkStorageTest} from './storage-test-contract.mjs'; +import {checkSchedulePolicies} from './schedule-policies-contract.mjs'; import {checkWorkerListing} from './worker-listing-contract.mjs'; import {checkWorkerReplacement} from './worker-replacement-contract.mjs'; import {checkPatchDeployment, checkPublishedPatchArtifacts, checkPatchPackageObservation} from './patch-deployment-contract.mjs'; @@ -137,6 +138,7 @@ export function checkObservation(fixture, observation, workflowId, artifacts) { const lateWorkflowCompletion = fixture.late_workflow_completion ? checkLateWorkflowCompletion(fixture, observation, workflowId) : null; const searchAttributes = fixture.search_attributes ? checkSearchAttributes(fixture, observation, workflowId) : null; const storageTest = fixture.storage_test ? checkStorageTest(fixture, observation, workflowId) : null; + const schedulePolicies = fixture.schedule_policies ? checkSchedulePolicies(fixture, observation, workflowId) : null; const workerListing = fixture.worker_listing ? checkWorkerListing(fixture, observation, workflowId) : null; const workerReplacement = fixture.worker_replacement ? checkWorkerReplacement(fixture, observation, workflowId) : null; const patchDeployment = fixture.patch_deployment ? (fixture.patch_deployment.legacy_marker_history @@ -679,6 +681,7 @@ export function checkObservation(fixture, observation, workflowId, artifacts) { ...(lateWorkflowCompletion ? {late_workflow_completion: lateWorkflowCompletion} : {}), ...(searchAttributes ? {search_attributes: searchAttributes} : {}), ...(storageTest ? {storage_test: storageTest} : {}), + ...(schedulePolicies ? {schedule_policies: schedulePolicies} : {}), ...(workerListing ? {worker_listing: workerListing} : {}), ...(workerReplacement ? {worker_replacement: workerReplacement} : {}), ...(patchDeployment ? {patch_deployment: patchDeployment} : {}), diff --git a/scripts/conformance/server-parity/probe.php b/scripts/conformance/server-parity/probe.php index b69395bb3..dcd99ac1d 100644 --- a/scripts/conformance/server-parity/probe.php +++ b/scripts/conformance/server-parity/probe.php @@ -430,6 +430,7 @@ function httpObservation(array $fixture, string $workflowId, string $namespace, $lateWorkflowCompletion = isset($fixture['late_workflow_completion']) ? finishHttpLateWorkflowCompletion($client, $fixture, $workflowId) : null; $searchAttributes = isset($fixture['search_attributes']) ? finishHttpSearchAttributes($client, $fixture, $workflowId, $url) : null; $storageTest = isset($fixture['storage_test']) ? finishHttpStorageTest($client, $fixture, $workflowId, $url) : null; + $schedulePolicies = isset($fixture['schedule_policies']) ? finishHttpSchedulePolicies($client, $fixture, $workflowId, $url) : null; $workerListing = isset($fixture['worker_listing']) ? finishHttpWorkerListing($client, $fixture, $workflowId) : null; $workerReplacement = isset($fixture['worker_replacement']) ? finishHttpWorkerReplacement($client, $fixture, $workflowId) : null; $waitingHistory = isset($fixture['waiting_for_history']) ? finishHttpWaitingHistory($client, $fixture, $workflowId, $queue, $url) : null; @@ -521,6 +522,7 @@ function httpObservation(array $fixture, string $workflowId, string $namespace, 'late_workflow_completion' => $lateWorkflowCompletion, 'search_attributes' => $searchAttributes, 'storage_test' => $storageTest, + 'schedule_policies' => $schedulePolicies, 'worker_listing' => $workerListing, 'worker_replacement' => $workerReplacement, 'waiting_for_history' => $waitingHistory, @@ -820,6 +822,7 @@ function embeddedObservation(array $fixture, string $workflowId, string $namespa require __DIR__.'/namespace-probe.php'; require __DIR__.'/search-attribute-probe.php'; require __DIR__.'/storage-test-probe.php'; + require __DIR__.'/schedule-policies-probe.php'; require __DIR__.'/worker-deregistration-probe.php'; require __DIR__.'/late-workflow-completion-probe.php'; require __DIR__.'/worker-listing-probe.php'; @@ -972,6 +975,9 @@ function embeddedObservation(array $fixture, string $workflowId, string $namespa if ($mode === 'embedded' && isset($fixture['storage_test'])) { $observation['storage_test'] = ['applicable' => false, 'reason' => 'embedded_has_no_http_storage_diagnostic']; } + if ($mode === 'embedded' && isset($fixture['schedule_policies'])) { + $observation['schedule_policies'] = ['applicable' => false, 'reason' => 'embedded_has_no_http_schedules']; + } $observation['mode'] = $mode; $observation['typed_output'] = typedValue($observation['output']); if ($mode === 'embedded' && isset($fixture['waiting_for_history'])) { diff --git a/scripts/conformance/server-parity/schedule-policies-contract.mjs b/scripts/conformance/server-parity/schedule-policies-contract.mjs new file mode 100644 index 000000000..d58e80f61 --- /dev/null +++ b/scripts/conformance/server-parity/schedule-policies-contract.mjs @@ -0,0 +1,85 @@ +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; + +// Published PHP's reviewed, normalized responses to every step. +const EXPECTED = JSON.parse(readFileSync(new URL('../../../tests/Fixtures/ServerParityExpected/schedule-policies.json', import.meta.url))); + +// Generated identities, times, audit fingerprints, route names and peer +// addresses differ between servers and runs. Next fire times are checked on +// their own below, against the published cron corpus semantics. +const VOLATILE = [/^\d{4}-\d{2}-\d{2}T/, /^[0-9A-Za-z]{26}$/, /^sha256:[0-9a-f]{64}$/, /^generated::/, + /^api\.schedules\./, /^\d+\.\d+\.\d+\.\d+$/]; + +// The run's own namespace and task queue read as placeholders, wherever they appear. +export function normalized(value, names = {}) { + if (Array.isArray(value)) return value.map(item => normalized(item, names)); + if (value && typeof value === 'object') { + return Object.fromEntries(Object.entries(value).map(([field, item]) => [field, normalized(item, names)])); + } + if (typeof value !== 'string') return value; + if (VOLATILE.some(pattern => pattern.test(value))) return '@v'; + return Object.entries(names).reduce((text, [name, placeholder]) => text.replaceAll(name, placeholder), value); +} + +/// The reviewed status of each expanded step, in order. +const POLICY = [201, 200, 200, 200, 200]; +export const STATUSES = [422, 422, 201, 200, 409, 422, 422, 201, 200, 200, 422, 200, 200, 422, 404, + ...POLICY, ...POLICY, ...POLICY, ...POLICY, ...POLICY, ...POLICY, + 201, 200, 200, 404, 200, 200, 422, 200, 200, 200, 200, 200, 200, 404, 200]; + +/// The reviewed trigger outcomes of each overlap policy, three triggers each. +const POLICY_OUTCOMES = { + skip: ['triggered', 'skipped', 'skipped'], + buffer_one: ['triggered', 'buffered', 'buffer_full'], + buffer_all: ['triggered', 'buffered', 'buffered'], + allow_all: ['triggered', 'triggered', 'triggered'], + cancel_other: ['triggered', 'triggered', 'triggered'], + terminate_other: ['triggered', 'triggered', 'triggered'], +}; + +export function checkSchedulePolicies(fixture, observation, workflowId) { + const state = observation.schedule_policies; + assert.ok(state && typeof state === 'object', 'actual schedule policy observation'); + if (observation.mode === 'embedded') { + assert.deepStrictEqual(state, {applicable: false, reason: 'embedded_has_no_http_schedules'}, + 'embedded schedule surface explicitly inapplicable'); + return {scope: 'schedule_policies'}; + } + const namespace = `${workflowId}-schedules`.toLowerCase(); + assert.deepStrictEqual([state.applicable, state.namespace], [true, namespace], 'schedule policy namespace'); + assert.ok(Number.isInteger(state.retries) && state.retries >= 0, 'recorded lock retries'); + const raw = state.steps; + const names = {[namespace]: '@namespace', [state.queue]: '@queue'}; + const steps = raw.map(step => ({request: step.request, status: step.status, response: normalized(step.response, names)})); + assert.deepStrictEqual(steps.map(step => step.status), STATUSES, 'schedule step statuses'); + const at = request => steps.filter(step => step.request === request); + // Laravel names expanded attributes raw and others with spaces. + assert.equal(steps[1].response.message, 'The spec.timezone field must be a string. (and 9 more errors)', 'validation summary'); + assert.deepStrictEqual(steps[1].response.errors['spec.cron_expressions.0'], + ['The spec.cron_expressions.0 field must be a string.'], 'expanded attribute name'); + assert.deepStrictEqual(steps[1].response.errors['action.workflow_type'], + ['The action.workflow type field is required.'], 'plain attribute name'); + assert.equal(steps[4].response.reason, 'schedule_already_exists', 'duplicate schedule'); + assert.equal(steps[5].response.reason, 'invalid_cron_expression', 'invalid cron expression'); + assert.equal(steps[6].response.reason, 'invalid_schedule_spec', 'empty specification'); + const yearly = steps[3].response; + assert.match(raw[3].response.next_fire_at, /^\d{4}-01-01T02:00:00\+00:00$/, 'yearly 03:00 Budapest fire time'); + assert.equal(yearly.note, 'yearly report', 'trimmed note'); + assert.deepStrictEqual(yearly.memo, {owner: 'λ'}, 'schedule memo'); + const mixed = at(`GET schedules/mixed`); + assert.deepStrictEqual([mixed[0].response.status, mixed[1].response.status], ['paused', 'active'], 'paused creation resumed'); + assert.deepStrictEqual(mixed[1].response.note, 'back', 'resume note'); + assert.deepStrictEqual(mixed[1].response.action, {workflow_type: fixture.workflow_type, task_queue: '@queue', + execution_timeout_seconds: 90, run_timeout_seconds: 30}, 'action timeout aliases'); + for (const [policy, outcomes] of Object.entries(POLICY_OUTCOMES)) { + const id = `policy-${policy.replaceAll('_', '-')}`; + assert.deepStrictEqual(at(`POST schedules/${id}/trigger`).slice(0, 3).map(step => step.response.outcome), outcomes, + `${policy} trigger outcomes`); + } + const quota = at('POST schedules/quota/trigger'); + assert.deepStrictEqual(quota.map(step => step.response.outcome), ['triggered', 'triggered'], 'two quota runs'); + const skipped = at('POST schedules/policy-skip/trigger').at(-1).response; + assert.deepStrictEqual([skipped.outcome, skipped.reason], ['skipped', 'status_not_triggerable'], 'paused trigger'); + assert.deepStrictEqual(steps, EXPECTED, 'every response matches the reviewed published PHP responses'); + return {scope: 'schedule_policies'}; +} diff --git a/scripts/conformance/server-parity/schedule-policies-probe.php b/scripts/conformance/server-parity/schedule-policies-probe.php new file mode 100644 index 000000000..9bd0b7956 --- /dev/null +++ b/scripts/conformance/server-parity/schedule-policies-probe.php @@ -0,0 +1,73 @@ +createNamespace($namespace, 'schedule policies λ', 30); + $queue = strtolower($workflowId.'-queue'); + $input = ['codec' => 'avro', 'blob' => $definition['input_blob']]; + $token = getenv('DW_PARITY_TOKEN') ?: null; + $http = new SchedulePoliciesHttp(['base_uri' => rtrim($url, '/').'/api/', 'http_errors' => false, + 'connect_timeout' => 1, 'timeout' => 30]); + $fill = static function (mixed $value) use (&$fill, $queue, $input): mixed { + if ($value === '@queue') { + return $queue; + } + if ($value === '@input') { + return $input; + } + + return is_array($value) ? array_map($fill, $value) : $value; + }; + $retries = 0; + $send = static function (string $method, string $path, mixed $body = null) use ($http, $token, $namespace, &$retries): array { + for ($attempt = 1; ; $attempt++) { + $response = $http->request($method, $path, ['headers' => array_filter([ + 'Authorization' => $token === null ? null : 'Bearer '.$token, + 'Accept' => 'application/json', 'X-Namespace' => $namespace, + 'X-Durable-Workflow-Control-Plane-Version' => '2', + ]), ...($body === null ? [] : ['json' => $body === [] ? (object) [] : $body])]); + $text = (string) $response->getBody(); + // Published PHP on SQLite can lose a write race with its own + // maintenance roles. The whole operation rolls back first. + if ($response->getStatusCode() === 500 && str_contains($text, 'database is locked') && $attempt < 5) { + $retries++; + usleep(200000 * $attempt); + + continue; + } + + return ['request' => "$method $path", 'status' => $response->getStatusCode(), + 'response' => json_decode($text, true, flags: JSON_THROW_ON_ERROR)]; + } + }; + $steps = []; + foreach ($definition['steps'] as $step) { + if ($step[0] === '@policy') { + $policy = $step[1]; + $id = 'policy-'.str_replace('_', '-', $policy); + $steps[] = $send('POST', 'schedules', ['schedule_id' => $id, 'spec' => ['cron_expressions' => ['0 0 1 1 *']], + 'action' => ['workflow_type' => $fixture['workflow_type'], 'task_queue' => $queue, 'input' => $input], + 'overlap_policy' => $policy]); + for ($trigger = 0; $trigger < 3; $trigger++) { + $steps[] = $send('POST', "schedules/$id/trigger", []); + } + $steps[] = $send('GET', "schedules/$id"); + + continue; + } + $steps[] = $send($step[0], $step[1], array_key_exists(2, $step) ? $fill($step[2]) : null); + } + + return ['applicable' => true, 'namespace' => $namespace, 'queue' => $queue, 'retries' => $retries, 'steps' => $steps]; +} diff --git a/tests/Fixtures/ServerParity/schedule-policies.json b/tests/Fixtures/ServerParity/schedule-policies.json new file mode 100644 index 000000000..8a6f3c1d4 --- /dev/null +++ b/tests/Fixtures/ServerParity/schedule-policies.json @@ -0,0 +1,56 @@ +{ + "schema": "durable-workflow.server-parity-fixture/v1", + "id": "schedule-policies", + "review_state": "reviewed", + "workflow_type": "parity.v1.echo", + "input": {"message": "schedule policies λ", "count": 9007199254740993, "optional": null}, + "typed_value": {"type": "map", "value": { + "message": {"type": "string", "value": "schedule policies λ"}, + "count": {"type": "int64", "value": "9007199254740993"}, + "optional": {"type": "null", "value": null} + }}, + "activity": false, + "expected_events": ["StartAccepted", "WorkflowStarted", "WorkflowCompleted"], + "schedule_policies": { + "input_blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==", + "steps": [ + ["POST", "schedules", {}], + ["POST", "schedules", {"spec": {"cron_expressions": [5, "x"], "intervals": [{"offset": "x"}], "timezone": 7}, "action": {"workflow_type": ""}, "overlap_policy": "sometimes", "jitter_seconds": 4000, "max_runs": 0, "memo": "x", "paused": "maybe", "note": 7}], + ["POST", "schedules", {"schedule_id": "yearly", "spec": {"cron_expressions": ["0 3 1 1 *"], "timezone": "Europe/Budapest"}, "action": {"workflow_type": "parity.v1.echo", "task_queue": "@queue", "input": "@input"}, "memo": {"owner": "λ"}, "note": " yearly report "}], + ["GET", "schedules/yearly"], + ["POST", "schedules", {"schedule_id": "yearly", "spec": {"cron_expressions": ["0 4 1 1 *"]}, "action": {"workflow_type": "parity.v1.echo"}}], + ["POST", "schedules", {"schedule_id": "invalid-cron", "spec": {"cron_expressions": ["0 3 * *"]}, "action": {"workflow_type": "parity.v1.echo"}}], + ["POST", "schedules", {"schedule_id": "empty-spec", "spec": {"timezone": "UTC"}, "action": {"workflow_type": "parity.v1.echo"}}], + ["POST", "schedules", {"schedule_id": "mixed", "spec": {"cron_expressions": ["15 3 1 1 *", "@yearly"], "intervals": [{"every": "P400D", "offset": "PT5H"}], "timezone": "America/New_York"}, "action": {"workflow_type": "parity.v1.echo", "task_queue": "@queue", "workflow_execution_timeout": 90, "workflow_run_timeout": "30"}, "overlap_policy": "buffer_all", "max_runs": "4", "paused": true}], + ["GET", "schedules/mixed"], + ["POST", "schedules/mixed/pause", {"note": "audit hold"}], + ["POST", "schedules/mixed/resume", {"note": 7}], + ["POST", "schedules/mixed/resume", {"note": "back"}], + ["GET", "schedules/mixed"], + ["POST", "schedules/mixed/trigger", {"overlap_policy": "never"}], + ["POST", "schedules/missing/pause", {}], + ["@policy", "skip"], + ["@policy", "buffer_one"], + ["@policy", "buffer_all"], + ["@policy", "allow_all"], + ["@policy", "cancel_other"], + ["@policy", "terminate_other"], + ["POST", "schedules", {"schedule_id": "quota", "spec": {"cron_expressions": ["0 0 1 1 *"]}, "action": {"workflow_type": "parity.v1.echo", "task_queue": "@queue", "input": "@input"}, "overlap_policy": "allow_all", "max_runs": 2}], + ["POST", "schedules/quota/trigger", {}], + ["POST", "schedules/quota/trigger", {}], + ["GET", "schedules/quota"], + ["GET", "schedules/quota/history?limit=2"], + ["GET", "schedules/quota/history?limit=2&after_sequence=2"], + ["GET", "schedules/quota/history?after_sequence=x"], + ["POST", "schedules/policy-skip/trigger", {"overlap_policy": "allow_all"}], + ["POST", "schedules/policy-skip/pause", {}], + ["POST", "schedules/policy-skip/pause", {"note": "again"}], + ["POST", "schedules/policy-skip/trigger", {}], + ["GET", "schedules/policy-skip"], + ["DELETE", "schedules/policy-skip"], + ["DELETE", "schedules/policy-skip"], + ["GET", "schedules/policy-skip/history?after_sequence=7"] + ] + }, + "contract": "The HTTP schedule surface of a fresh namespace. Laravel validation names, messages and counts for creation; duplicate, invalid-cron and empty-spec refusals; a yearly cron schedule in Europe/Budapest and a paused buffer_all schedule mixing two cron expressions with an offset interval in America/New_York, each with PHP's next fire time; pausing and resuming with notes; manual triggers of all six overlap policies three times while no worker runs the started workflows, with buffering, a full buffer and closing the other run; a two-run quota that deletes the schedule; an overlap override, pausing twice and a skipped paused trigger; history pages; and deletion. Every response matches published PHP's apart from generated identities and wall-clock times other than next fire times. Published PHP on SQLite can fail a trigger with database is locked against its own maintenance roles; the whole trigger rolls back, so the probe retries exactly that failure, which native must never need. Scheduler-driven fires, update, listing and backfill are separate fixtures. Embedded applications have no HTTP schedule surface." +} diff --git a/tests/Fixtures/ServerParityExpected/schedule-policies.json b/tests/Fixtures/ServerParityExpected/schedule-policies.json new file mode 100644 index 000000000..591ce30db --- /dev/null +++ b/tests/Fixtures/ServerParityExpected/schedule-policies.json @@ -0,0 +1,1677 @@ +[ + { + "request": "POST schedules", + "status": 422, + "response": { + "message": "The spec field is required. (and 2 more errors)", + "reason": "validation_failed", + "errors": { + "spec": [ + "The spec field is required." + ], + "action": [ + "The action field is required." + ], + "action.workflow_type": [ + "The action.workflow type field is required." + ] + }, + "validation_errors": { + "spec": [ + "The spec field is required." + ], + "action": [ + "The action field is required." + ], + "action.workflow_type": [ + "The action.workflow type field is required." + ] + } + } + }, + { + "request": "POST schedules", + "status": 422, + "response": { + "message": "The spec.timezone field must be a string. (and 9 more errors)", + "reason": "validation_failed", + "errors": { + "spec.timezone": [ + "The spec.timezone field must be a string." + ], + "action.workflow_type": [ + "The action.workflow type field is required." + ], + "overlap_policy": [ + "The selected overlap policy is invalid." + ], + "jitter_seconds": [ + "The jitter seconds field must not be greater than 3600." + ], + "max_runs": [ + "The max runs field must be at least 1." + ], + "memo": [ + "The memo field must be an array." + ], + "paused": [ + "The paused field must be true or false." + ], + "note": [ + "The note field must be a string." + ], + "spec.cron_expressions.0": [ + "The spec.cron_expressions.0 field must be a string." + ], + "spec.intervals.0.every": [ + "The spec.intervals.0.every field is required when spec.intervals is present." + ] + }, + "validation_errors": { + "spec.timezone": [ + "The spec.timezone field must be a string." + ], + "action.workflow_type": [ + "The action.workflow type field is required." + ], + "overlap_policy": [ + "The selected overlap policy is invalid." + ], + "jitter_seconds": [ + "The jitter seconds field must not be greater than 3600." + ], + "max_runs": [ + "The max runs field must be at least 1." + ], + "memo": [ + "The memo field must be an array." + ], + "paused": [ + "The paused field must be true or false." + ], + "note": [ + "The note field must be a string." + ], + "spec.cron_expressions.0": [ + "The spec.cron_expressions.0 field must be a string." + ], + "spec.intervals.0.every": [ + "The spec.intervals.0.every field is required when spec.intervals is present." + ] + } + } + }, + { + "request": "POST schedules", + "status": 201, + "response": { + "schedule_id": "yearly", + "outcome": "created" + } + }, + { + "request": "GET schedules/yearly", + "status": 200, + "response": { + "schedule_id": "yearly", + "spec": { + "timezone": "Europe/Budapest", + "cron_expressions": [ + "0 3 1 1 *" + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "input": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==" + } + }, + "overlap_policy": "skip", + "state": { + "status": "active", + "paused": false, + "note": "yearly report" + }, + "info": { + "next_fire": "@v", + "last_fire": null, + "fires_count": 0, + "failures_count": 0, + "recent_actions": [], + "buffered_actions": [], + "skipped_trigger_count": 0, + "last_skip_reason": null, + "last_skipped_at": null + }, + "memo": { + "owner": "λ" + }, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "active", + "paused": false, + "note": "yearly report", + "fires_count": 0, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": null, + "paused_at": null, + "jitter_seconds": 0, + "max_runs": null, + "remaining_actions": null, + "latest_workflow_instance_id": null + } + }, + { + "request": "POST schedules", + "status": 409, + "response": { + "message": "Schedule [yearly] already exists in namespace [@namespace].", + "reason": "schedule_already_exists", + "schedule_id": "yearly" + } + }, + { + "request": "POST schedules", + "status": 422, + "response": { + "message": "Invalid cron expression: [0 3 * *].", + "reason": "invalid_cron_expression", + "schedule_id": "invalid-cron", + "errors": { + "spec.cron_expressions": [ + "Invalid cron expression: [0 3 * *]." + ] + } + } + }, + { + "request": "POST schedules", + "status": 422, + "response": { + "message": "Schedule spec must include at least one cron_expression or interval.", + "reason": "invalid_schedule_spec", + "schedule_id": "empty-spec", + "errors": { + "spec": [ + "Schedule spec must include at least one cron_expression or interval." + ] + } + } + }, + { + "request": "POST schedules", + "status": 201, + "response": { + "schedule_id": "mixed", + "outcome": "created" + } + }, + { + "request": "GET schedules/mixed", + "status": 200, + "response": { + "schedule_id": "mixed", + "spec": { + "timezone": "America/New_York", + "cron_expressions": [ + "15 3 1 1 *", + "@yearly" + ], + "intervals": [ + { + "every": "P400D", + "offset": "PT5H" + } + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "execution_timeout_seconds": 90, + "run_timeout_seconds": 30 + }, + "overlap_policy": "buffer_all", + "state": { + "status": "paused", + "paused": true, + "note": null + }, + "info": { + "next_fire": "@v", + "last_fire": null, + "fires_count": 0, + "failures_count": 0, + "recent_actions": [], + "buffered_actions": [], + "skipped_trigger_count": 0, + "last_skip_reason": null, + "last_skipped_at": null + }, + "memo": null, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "paused", + "paused": true, + "note": null, + "fires_count": 0, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": null, + "paused_at": "@v", + "jitter_seconds": 0, + "max_runs": 4, + "remaining_actions": 4, + "latest_workflow_instance_id": null + } + }, + { + "request": "POST schedules/mixed/pause", + "status": 200, + "response": { + "schedule_id": "mixed", + "outcome": "paused" + } + }, + { + "request": "POST schedules/mixed/resume", + "status": 422, + "response": { + "message": "The note field must be a string.", + "reason": "validation_failed", + "errors": { + "note": [ + "The note field must be a string." + ] + }, + "validation_errors": { + "note": [ + "The note field must be a string." + ] + } + } + }, + { + "request": "POST schedules/mixed/resume", + "status": 200, + "response": { + "schedule_id": "mixed", + "outcome": "resumed" + } + }, + { + "request": "GET schedules/mixed", + "status": 200, + "response": { + "schedule_id": "mixed", + "spec": { + "timezone": "America/New_York", + "cron_expressions": [ + "15 3 1 1 *", + "@yearly" + ], + "intervals": [ + { + "every": "P400D", + "offset": "PT5H" + } + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "execution_timeout_seconds": 90, + "run_timeout_seconds": 30 + }, + "overlap_policy": "buffer_all", + "state": { + "status": "active", + "paused": false, + "note": "back" + }, + "info": { + "next_fire": "@v", + "last_fire": null, + "fires_count": 0, + "failures_count": 0, + "recent_actions": [], + "buffered_actions": [], + "skipped_trigger_count": 0, + "last_skip_reason": null, + "last_skipped_at": null + }, + "memo": null, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "active", + "paused": false, + "note": "back", + "fires_count": 0, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": null, + "paused_at": null, + "jitter_seconds": 0, + "max_runs": 4, + "remaining_actions": 4, + "latest_workflow_instance_id": null + } + }, + { + "request": "POST schedules/mixed/trigger", + "status": 422, + "response": { + "message": "The selected overlap policy is invalid.", + "reason": "validation_failed", + "errors": { + "overlap_policy": [ + "The selected overlap policy is invalid." + ] + }, + "validation_errors": { + "overlap_policy": [ + "The selected overlap policy is invalid." + ] + } + } + }, + { + "request": "POST schedules/missing/pause", + "status": 404, + "response": { + "message": "Schedule [missing] not found in namespace [@namespace].", + "reason": "schedule_not_found" + } + }, + { + "request": "POST schedules", + "status": 201, + "response": { + "schedule_id": "policy-skip", + "outcome": "created" + } + }, + { + "request": "POST schedules/policy-skip/trigger", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-skip/trigger", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "outcome": "skipped", + "reason": "overlap_policy_skip" + } + }, + { + "request": "POST schedules/policy-skip/trigger", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "outcome": "skipped", + "reason": "overlap_policy_skip" + } + }, + { + "request": "GET schedules/policy-skip", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "spec": { + "cron_expressions": [ + "0 0 1 1 *" + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "input": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==" + } + }, + "overlap_policy": "skip", + "state": { + "status": "active", + "paused": false, + "note": null + }, + "info": { + "next_fire": "@v", + "last_fire": "@v", + "fires_count": 1, + "failures_count": 0, + "recent_actions": [ + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + } + ], + "buffered_actions": [], + "skipped_trigger_count": 2, + "last_skip_reason": "overlap_policy_skip", + "last_skipped_at": "@v" + }, + "memo": null, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "active", + "paused": false, + "note": null, + "fires_count": 1, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": "@v", + "paused_at": null, + "jitter_seconds": 0, + "max_runs": null, + "remaining_actions": null, + "latest_workflow_instance_id": "@v" + } + }, + { + "request": "POST schedules", + "status": 201, + "response": { + "schedule_id": "policy-buffer-one", + "outcome": "created" + } + }, + { + "request": "POST schedules/policy-buffer-one/trigger", + "status": 200, + "response": { + "schedule_id": "policy-buffer-one", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-buffer-one/trigger", + "status": 200, + "response": { + "schedule_id": "policy-buffer-one", + "outcome": "buffered", + "buffer_depth": 1 + } + }, + { + "request": "POST schedules/policy-buffer-one/trigger", + "status": 200, + "response": { + "schedule_id": "policy-buffer-one", + "outcome": "buffer_full", + "reason": "Previous workflow is still running and buffer is at capacity." + } + }, + { + "request": "GET schedules/policy-buffer-one", + "status": 200, + "response": { + "schedule_id": "policy-buffer-one", + "spec": { + "cron_expressions": [ + "0 0 1 1 *" + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "input": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==" + } + }, + "overlap_policy": "buffer_one", + "state": { + "status": "active", + "paused": false, + "note": null + }, + "info": { + "next_fire": "@v", + "last_fire": "@v", + "fires_count": 1, + "failures_count": 0, + "recent_actions": [ + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + } + ], + "buffered_actions": [ + { + "buffered_at": "@v" + } + ], + "skipped_trigger_count": 1, + "last_skip_reason": "buffer_full", + "last_skipped_at": "@v" + }, + "memo": null, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "active", + "paused": false, + "note": null, + "fires_count": 1, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": "@v", + "paused_at": null, + "jitter_seconds": 0, + "max_runs": null, + "remaining_actions": null, + "latest_workflow_instance_id": "@v" + } + }, + { + "request": "POST schedules", + "status": 201, + "response": { + "schedule_id": "policy-buffer-all", + "outcome": "created" + } + }, + { + "request": "POST schedules/policy-buffer-all/trigger", + "status": 200, + "response": { + "schedule_id": "policy-buffer-all", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-buffer-all/trigger", + "status": 200, + "response": { + "schedule_id": "policy-buffer-all", + "outcome": "buffered", + "buffer_depth": 1 + } + }, + { + "request": "POST schedules/policy-buffer-all/trigger", + "status": 200, + "response": { + "schedule_id": "policy-buffer-all", + "outcome": "buffered", + "buffer_depth": 2 + } + }, + { + "request": "GET schedules/policy-buffer-all", + "status": 200, + "response": { + "schedule_id": "policy-buffer-all", + "spec": { + "cron_expressions": [ + "0 0 1 1 *" + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "input": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==" + } + }, + "overlap_policy": "buffer_all", + "state": { + "status": "active", + "paused": false, + "note": null + }, + "info": { + "next_fire": "@v", + "last_fire": "@v", + "fires_count": 1, + "failures_count": 0, + "recent_actions": [ + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + } + ], + "buffered_actions": [ + { + "buffered_at": "@v" + }, + { + "buffered_at": "@v" + } + ], + "skipped_trigger_count": 0, + "last_skip_reason": null, + "last_skipped_at": null + }, + "memo": null, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "active", + "paused": false, + "note": null, + "fires_count": 1, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": "@v", + "paused_at": null, + "jitter_seconds": 0, + "max_runs": null, + "remaining_actions": null, + "latest_workflow_instance_id": "@v" + } + }, + { + "request": "POST schedules", + "status": 201, + "response": { + "schedule_id": "policy-allow-all", + "outcome": "created" + } + }, + { + "request": "POST schedules/policy-allow-all/trigger", + "status": 200, + "response": { + "schedule_id": "policy-allow-all", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-allow-all/trigger", + "status": 200, + "response": { + "schedule_id": "policy-allow-all", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-allow-all/trigger", + "status": 200, + "response": { + "schedule_id": "policy-allow-all", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "GET schedules/policy-allow-all", + "status": 200, + "response": { + "schedule_id": "policy-allow-all", + "spec": { + "cron_expressions": [ + "0 0 1 1 *" + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "input": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==" + } + }, + "overlap_policy": "allow_all", + "state": { + "status": "active", + "paused": false, + "note": null + }, + "info": { + "next_fire": "@v", + "last_fire": "@v", + "fires_count": 3, + "failures_count": 0, + "recent_actions": [ + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + }, + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + }, + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + } + ], + "buffered_actions": [], + "skipped_trigger_count": 0, + "last_skip_reason": null, + "last_skipped_at": null + }, + "memo": null, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "active", + "paused": false, + "note": null, + "fires_count": 3, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": "@v", + "paused_at": null, + "jitter_seconds": 0, + "max_runs": null, + "remaining_actions": null, + "latest_workflow_instance_id": "@v" + } + }, + { + "request": "POST schedules", + "status": 201, + "response": { + "schedule_id": "policy-cancel-other", + "outcome": "created" + } + }, + { + "request": "POST schedules/policy-cancel-other/trigger", + "status": 200, + "response": { + "schedule_id": "policy-cancel-other", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-cancel-other/trigger", + "status": 200, + "response": { + "schedule_id": "policy-cancel-other", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-cancel-other/trigger", + "status": 200, + "response": { + "schedule_id": "policy-cancel-other", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "GET schedules/policy-cancel-other", + "status": 200, + "response": { + "schedule_id": "policy-cancel-other", + "spec": { + "cron_expressions": [ + "0 0 1 1 *" + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "input": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==" + } + }, + "overlap_policy": "cancel_other", + "state": { + "status": "active", + "paused": false, + "note": null + }, + "info": { + "next_fire": "@v", + "last_fire": "@v", + "fires_count": 3, + "failures_count": 0, + "recent_actions": [ + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + }, + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + }, + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + } + ], + "buffered_actions": [], + "skipped_trigger_count": 0, + "last_skip_reason": null, + "last_skipped_at": null + }, + "memo": null, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "active", + "paused": false, + "note": null, + "fires_count": 3, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": "@v", + "paused_at": null, + "jitter_seconds": 0, + "max_runs": null, + "remaining_actions": null, + "latest_workflow_instance_id": "@v" + } + }, + { + "request": "POST schedules", + "status": 201, + "response": { + "schedule_id": "policy-terminate-other", + "outcome": "created" + } + }, + { + "request": "POST schedules/policy-terminate-other/trigger", + "status": 200, + "response": { + "schedule_id": "policy-terminate-other", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-terminate-other/trigger", + "status": 200, + "response": { + "schedule_id": "policy-terminate-other", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-terminate-other/trigger", + "status": 200, + "response": { + "schedule_id": "policy-terminate-other", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "GET schedules/policy-terminate-other", + "status": 200, + "response": { + "schedule_id": "policy-terminate-other", + "spec": { + "cron_expressions": [ + "0 0 1 1 *" + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "input": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==" + } + }, + "overlap_policy": "terminate_other", + "state": { + "status": "active", + "paused": false, + "note": null + }, + "info": { + "next_fire": "@v", + "last_fire": "@v", + "fires_count": 3, + "failures_count": 0, + "recent_actions": [ + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + }, + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + }, + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + } + ], + "buffered_actions": [], + "skipped_trigger_count": 0, + "last_skip_reason": null, + "last_skipped_at": null + }, + "memo": null, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "active", + "paused": false, + "note": null, + "fires_count": 3, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": "@v", + "paused_at": null, + "jitter_seconds": 0, + "max_runs": null, + "remaining_actions": null, + "latest_workflow_instance_id": "@v" + } + }, + { + "request": "POST schedules", + "status": 201, + "response": { + "schedule_id": "quota", + "outcome": "created" + } + }, + { + "request": "POST schedules/quota/trigger", + "status": 200, + "response": { + "schedule_id": "quota", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/quota/trigger", + "status": 200, + "response": { + "schedule_id": "quota", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "GET schedules/quota", + "status": 404, + "response": { + "message": "Schedule [quota] not found in namespace [@namespace].", + "reason": "schedule_not_found" + } + }, + { + "request": "GET schedules/quota/history?limit=2", + "status": 200, + "response": { + "schedule_id": "quota", + "namespace": "@namespace", + "events": [ + { + "id": "@v", + "sequence": 1, + "event_type": "ScheduleCreated", + "payload": { + "spec": { + "cron_expressions": [ + "0 0 1 1 *" + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "input": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==" + } + }, + "overlap_policy": "allow_all", + "next_fire_at": "@v", + "command_context": { + "source": "control_plane", + "context": { + "caller": { + "type": "server", + "label": "Standalone Server" + }, + "auth": { + "status": "authorized", + "method": "token", + "role": "admin", + "roles": [ + "admin" + ], + "principal": { + "subject": "legacy-token", + "roles": [ + "admin" + ], + "claims": { + "legacy_credential": true + }, + "legacy_full_access": true + } + }, + "request": { + "method": "POST", + "path": "/api/schedules", + "route_name": "@v", + "ip": "@v", + "user_agent": "GuzzleHttp/8", + "fingerprint": "@v" + }, + "server": { + "operation_id": "@v", + "namespace": "@namespace", + "workflow_id": "quota", + "command": "schedule.create", + "metadata": [] + }, + "principal": { + "type": "auth:token", + "id": "legacy-token", + "label": "Admin" + } + } + }, + "schedule": { + "id": "@v", + "schedule_id": "quota", + "namespace": "@namespace", + "status": "active", + "overlap_policy": "allow_all", + "next_fire_at": "@v", + "fires_count": 0, + "skipped_trigger_count": 0 + } + }, + "workflow_instance_id": null, + "workflow_run_id": null, + "recorded_at": "@v" + }, + { + "id": "@v", + "sequence": 2, + "event_type": "ScheduleTriggered", + "payload": { + "workflow_instance_id": "@v", + "workflow_run_id": "@v", + "outcome": "scheduled", + "effective_overlap_policy": "allow_all", + "trigger_number": 1, + "command_context": { + "source": "control_plane", + "context": { + "caller": { + "type": "server", + "label": "Standalone Server" + }, + "auth": { + "status": "authorized", + "method": "token", + "role": "admin", + "roles": [ + "admin" + ], + "principal": { + "subject": "legacy-token", + "roles": [ + "admin" + ], + "claims": { + "legacy_credential": true + }, + "legacy_full_access": true + } + }, + "request": { + "method": "POST", + "path": "/api/schedules/quota/trigger", + "route_name": "@v", + "ip": "@v", + "user_agent": "GuzzleHttp/8", + "fingerprint": "@v" + }, + "server": { + "operation_id": "@v", + "namespace": "@namespace", + "workflow_id": "quota", + "command": "schedule.trigger", + "metadata": [] + }, + "principal": { + "type": "auth:token", + "id": "legacy-token", + "label": "Admin" + } + } + }, + "schedule": { + "id": "@v", + "schedule_id": "quota", + "namespace": "@namespace", + "status": "active", + "overlap_policy": "allow_all", + "next_fire_at": "@v", + "fires_count": 0, + "skipped_trigger_count": 0 + } + }, + "workflow_instance_id": "@v", + "workflow_run_id": "@v", + "recorded_at": "@v" + } + ], + "next_cursor": 2, + "has_more": true + } + }, + { + "request": "GET schedules/quota/history?limit=2&after_sequence=2", + "status": 200, + "response": { + "schedule_id": "quota", + "namespace": "@namespace", + "events": [ + { + "id": "@v", + "sequence": 3, + "event_type": "ScheduleTriggered", + "payload": { + "workflow_instance_id": "@v", + "workflow_run_id": "@v", + "outcome": "scheduled", + "effective_overlap_policy": "allow_all", + "trigger_number": 2, + "command_context": { + "source": "control_plane", + "context": { + "caller": { + "type": "server", + "label": "Standalone Server" + }, + "auth": { + "status": "authorized", + "method": "token", + "role": "admin", + "roles": [ + "admin" + ], + "principal": { + "subject": "legacy-token", + "roles": [ + "admin" + ], + "claims": { + "legacy_credential": true + }, + "legacy_full_access": true + } + }, + "request": { + "method": "POST", + "path": "/api/schedules/quota/trigger", + "route_name": "@v", + "ip": "@v", + "user_agent": "GuzzleHttp/8", + "fingerprint": "@v" + }, + "server": { + "operation_id": "@v", + "namespace": "@namespace", + "workflow_id": "quota", + "command": "schedule.trigger", + "metadata": [] + }, + "principal": { + "type": "auth:token", + "id": "legacy-token", + "label": "Admin" + } + } + }, + "schedule": { + "id": "@v", + "schedule_id": "quota", + "namespace": "@namespace", + "status": "active", + "overlap_policy": "allow_all", + "next_fire_at": "@v", + "last_fired_at": "@v", + "fires_count": 1, + "skipped_trigger_count": 0, + "latest_workflow_instance_id": "@v" + } + }, + "workflow_instance_id": "@v", + "workflow_run_id": "@v", + "recorded_at": "@v" + }, + { + "id": "@v", + "sequence": 4, + "event_type": "ScheduleDeleted", + "payload": { + "reason": "max_runs_exhausted", + "deleted_at": "@v", + "command_context": { + "source": "control_plane", + "context": { + "caller": { + "type": "server", + "label": "Standalone Server" + }, + "auth": { + "status": "authorized", + "method": "token", + "role": "admin", + "roles": [ + "admin" + ], + "principal": { + "subject": "legacy-token", + "roles": [ + "admin" + ], + "claims": { + "legacy_credential": true + }, + "legacy_full_access": true + } + }, + "request": { + "method": "POST", + "path": "/api/schedules/quota/trigger", + "route_name": "@v", + "ip": "@v", + "user_agent": "GuzzleHttp/8", + "fingerprint": "@v" + }, + "server": { + "operation_id": "@v", + "namespace": "@namespace", + "workflow_id": "quota", + "command": "schedule.trigger", + "metadata": [] + }, + "principal": { + "type": "auth:token", + "id": "legacy-token", + "label": "Admin" + } + } + }, + "schedule": { + "id": "@v", + "schedule_id": "quota", + "namespace": "@namespace", + "status": "deleted", + "overlap_policy": "allow_all", + "last_fired_at": "@v", + "deleted_at": "@v", + "fires_count": 2, + "skipped_trigger_count": 0, + "latest_workflow_instance_id": "@v" + } + }, + "workflow_instance_id": null, + "workflow_run_id": null, + "recorded_at": "@v" + } + ], + "next_cursor": null, + "has_more": false + } + }, + { + "request": "GET schedules/quota/history?after_sequence=x", + "status": 422, + "response": { + "message": "after_sequence must be a non-negative integer.", + "reason": "invalid_after_sequence" + } + }, + { + "request": "POST schedules/policy-skip/trigger", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "outcome": "triggered", + "workflow_id": "@v", + "run_id": "@v" + } + }, + { + "request": "POST schedules/policy-skip/pause", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "outcome": "paused" + } + }, + { + "request": "POST schedules/policy-skip/pause", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "outcome": "paused" + } + }, + { + "request": "POST schedules/policy-skip/trigger", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "outcome": "skipped", + "reason": "status_not_triggerable" + } + }, + { + "request": "GET schedules/policy-skip", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "spec": { + "cron_expressions": [ + "0 0 1 1 *" + ] + }, + "action": { + "workflow_type": "parity.v1.echo", + "task_queue": "@queue", + "input": { + "codec": "avro", + "blob": "wwHioz3/VYAiNwwCBIKAgICAgIAgAA==" + } + }, + "overlap_policy": "skip", + "state": { + "status": "paused", + "paused": true, + "note": "again" + }, + "info": { + "next_fire": "@v", + "last_fire": "@v", + "fires_count": 2, + "failures_count": 0, + "recent_actions": [ + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + }, + { + "workflow_id": "@v", + "run_id": "@v", + "outcome": "scheduled", + "fired_at": "@v" + } + ], + "buffered_actions": [], + "skipped_trigger_count": 3, + "last_skip_reason": "status_not_triggerable", + "last_skipped_at": "@v" + }, + "memo": null, + "search_attributes": null, + "created_at": "@v", + "updated_at": "@v", + "namespace": "@namespace", + "status": "paused", + "paused": true, + "note": "again", + "fires_count": 2, + "failures_count": 0, + "next_fire_at": "@v", + "last_fired_at": "@v", + "paused_at": "@v", + "jitter_seconds": 0, + "max_runs": null, + "remaining_actions": null, + "latest_workflow_instance_id": "@v" + } + }, + { + "request": "DELETE schedules/policy-skip", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "outcome": "deleted" + } + }, + { + "request": "DELETE schedules/policy-skip", + "status": 404, + "response": { + "message": "Schedule [policy-skip] not found in namespace [@namespace].", + "reason": "schedule_not_found" + } + }, + { + "request": "GET schedules/policy-skip/history?after_sequence=7", + "status": 200, + "response": { + "schedule_id": "policy-skip", + "namespace": "@namespace", + "events": [ + { + "id": "@v", + "sequence": 8, + "event_type": "ScheduleTriggerSkipped", + "payload": { + "reason": "status_not_triggerable", + "skipped_trigger_count": 3, + "last_skipped_at": "@v", + "command_context": { + "source": "control_plane", + "context": { + "caller": { + "type": "server", + "label": "Standalone Server" + }, + "auth": { + "status": "authorized", + "method": "token", + "role": "admin", + "roles": [ + "admin" + ], + "principal": { + "subject": "legacy-token", + "roles": [ + "admin" + ], + "claims": { + "legacy_credential": true + }, + "legacy_full_access": true + } + }, + "request": { + "method": "POST", + "path": "/api/schedules/policy-skip/trigger", + "route_name": "@v", + "ip": "@v", + "user_agent": "GuzzleHttp/8", + "fingerprint": "@v" + }, + "server": { + "operation_id": "@v", + "namespace": "@namespace", + "workflow_id": "policy-skip", + "command": "schedule.trigger", + "metadata": [] + }, + "principal": { + "type": "auth:token", + "id": "legacy-token", + "label": "Admin" + } + } + }, + "schedule": { + "id": "@v", + "schedule_id": "policy-skip", + "namespace": "@namespace", + "status": "paused", + "overlap_policy": "skip", + "next_fire_at": "@v", + "last_fired_at": "@v", + "paused_at": "@v", + "last_skip_reason": "status_not_triggerable", + "last_skipped_at": "@v", + "fires_count": 2, + "skipped_trigger_count": 3, + "latest_workflow_instance_id": "@v", + "note": "again" + } + }, + "workflow_instance_id": null, + "workflow_run_id": null, + "recorded_at": "@v" + }, + { + "id": "@v", + "sequence": 9, + "event_type": "ScheduleDeleted", + "payload": { + "reason": "deleted", + "deleted_at": "@v", + "command_context": { + "source": "control_plane", + "context": { + "caller": { + "type": "server", + "label": "Standalone Server" + }, + "auth": { + "status": "authorized", + "method": "token", + "role": "admin", + "roles": [ + "admin" + ], + "principal": { + "subject": "legacy-token", + "roles": [ + "admin" + ], + "claims": { + "legacy_credential": true + }, + "legacy_full_access": true + } + }, + "request": { + "method": "DELETE", + "path": "/api/schedules/policy-skip", + "route_name": "@v", + "ip": "@v", + "user_agent": "GuzzleHttp/8", + "fingerprint": "@v" + }, + "server": { + "operation_id": "@v", + "namespace": "@namespace", + "workflow_id": "policy-skip", + "command": "schedule.delete", + "metadata": [] + }, + "principal": { + "type": "auth:token", + "id": "legacy-token", + "label": "Admin" + } + } + }, + "schedule": { + "id": "@v", + "schedule_id": "policy-skip", + "namespace": "@namespace", + "status": "deleted", + "overlap_policy": "skip", + "last_fired_at": "@v", + "paused_at": "@v", + "deleted_at": "@v", + "last_skip_reason": "status_not_triggerable", + "last_skipped_at": "@v", + "fires_count": 2, + "skipped_trigger_count": 3, + "latest_workflow_instance_id": "@v", + "note": "again" + } + }, + "workflow_instance_id": null, + "workflow_run_id": null, + "recorded_at": "@v" + } + ], + "next_cursor": null, + "has_more": false + } + } +] diff --git a/tests/Unit/ServerParitySchedulePoliciesContractTest.mjs b/tests/Unit/ServerParitySchedulePoliciesContractTest.mjs new file mode 100644 index 000000000..0c4ad9817 --- /dev/null +++ b/tests/Unit/ServerParitySchedulePoliciesContractTest.mjs @@ -0,0 +1,48 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import {checkSchedulePolicies} from '../../scripts/conformance/server-parity/schedule-policies-contract.mjs'; + +const fixture = JSON.parse(readFileSync(new URL('../Fixtures/ServerParity/schedule-policies.json', import.meta.url))); +const expected = JSON.parse(readFileSync(new URL('../Fixtures/ServerParityExpected/schedule-policies.json', import.meta.url))); +const workflowId = 'TEST-schedule-policies'; +function model() { + const steps = structuredClone(expected); + // Generated values as a server reports them before normalization. + steps[3].response.next_fire_at = '2027-01-01T02:00:00+00:00'; + steps[3].response.created_at = '2026-10-21T05:00:00+00:00'; + return {mode: 'http', schedule_policies: {applicable: true, namespace: 'test-schedule-policies-schedules', + queue: 'test-schedule-policies-queue', retries: 0, steps: steps.map(step => JSON.parse(JSON.stringify(step) + .replaceAll('@namespace', 'test-schedule-policies-schedules').replaceAll('@queue', 'test-schedule-policies-queue')))}}; +} +const projection = {scope: 'schedule_policies'}; +const at = (observation, request) => observation.schedule_policies.steps.filter(step => step.request === request); + +test('published PHP responses pass', () => assert.deepStrictEqual(checkSchedulePolicies(fixture, model(), workflowId), projection)); +test('retried lock failures pass', () => { + const observation = model(); + observation.schedule_policies.retries = 2; + assert.deepStrictEqual(checkSchedulePolicies(fixture, observation, workflowId), projection); +}); +test('embedded explicitly declares the HTTP surface inapplicable', () => assert.deepStrictEqual(checkSchedulePolicies(fixture, + {mode: 'embedded', schedule_policies: {applicable: false, reason: 'embedded_has_no_http_schedules'}}, workflowId), projection)); +for (const [label, mutate] of [ + ['another namespace', o => { o.schedule_policies.namespace = 'default'; }], + ['unrecorded retries', o => { delete o.schedule_policies.retries; }], + ['a missing step', o => { o.schedule_policies.steps.pop(); }], + ['another status', o => { o.schedule_policies.steps[4].status = 422; }], + ['a changed validation message', o => { o.schedule_policies.steps[1].response.message = 'The spec field is required.'; }], + ['another yearly fire time', o => { o.schedule_policies.steps[3].response.next_fire_at = '2027-01-01T03:00:00+00:00'; }], + ['an untrimmed note', o => { o.schedule_policies.steps[3].response.note = ' yearly report '; }], + ['a buffer that never fills', o => { at(o, 'POST schedules/policy-buffer-one/trigger')[2].response.outcome = 'buffered'; }], + ['a skip policy that starts another run', o => { at(o, 'POST schedules/policy-skip/trigger')[1].response.outcome = 'triggered'; }], + ['a paused trigger that runs', o => { at(o, 'POST schedules/policy-skip/trigger').at(-1).response.reason = 'overlap_policy_skip'; }], + ['an action without its timeout aliases', o => { delete at(o, 'GET schedules/mixed')[1].response.action.run_timeout_seconds; }], + ['a different audit reason', o => { o.schedule_policies.steps.at(-1).response.events[0].payload.reason = 'deleted'; }], +]) { + test(`rejects ${label}`, () => { + const observation = model(); + mutate(observation); + assert.throws(() => checkSchedulePolicies(fixture, observation, workflowId)); + }); +}