{"record":{"id":"70b5155eb1cef85f","repo":"nautechsystems/nautilus_trader","slug":"fx-session-boundary-must-be-a-unique-local-time","errorCode":null,"errorMessage":"FX session boundary must be a unique local time","messagePattern":"FX session boundary must be a unique local time","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/trading/src/sessions.rs","lineNumber":157,"sourceCode":"    if local_now.time() > session_time {\n        date = date\n            .checked_add(Span::new().days(1))\n            .expect(\"FX session date must be representable\");\n    }\n\n    let weekend_days = match date.weekday() {\n        Weekday::Saturday => 2,\n        Weekday::Sunday => 1,\n        _ => 0,\n    };\n    date = date\n        .checked_add(Span::new().days(weekend_days))\n        .expect(\"FX session date must be representable\");\n\n    timezone\n        .to_ambiguous_timestamp(date.to_datetime(session_time))\n        .unambiguous()\n        .expect(\"FX session boundary must be a unique local time\")\n}\n\nfn fx_prev_boundary(local_now: &Zoned, session_time: Time) -> Timestamp {\n    let timezone = local_now.time_zone().clone();\n    let mut date = local_now.date();\n\n    if local_now.time() < session_time {\n        date = date\n            .checked_sub(Span::new().days(1))\n            .expect(\"FX session date must be representable\");\n    }\n\n    let weekend_days = match date.weekday() {\n        Weekday::Saturday => 1,\n        Weekday::Sunday => 2,\n        _ => 0,\n    };\n    date = date","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/trading/src/sessions.rs#L139-L175","documentation":"`fx_next_boundary` converts the target local date-time to a timestamp and calls `unambiguous()`, panicking if the local time is ambiguous — i.e. it occurs twice due to a DST fall-back transition in the session's timezone. The library refuses to guess which of the two instants is meant. This can be genuinely reachable if a session time equals an DST transition instant.","triggerScenarios":"Calling `fx_next_start`/`fx_next_end` where the computed session local datetime (e.g. 05:00 Sydney) lands exactly inside a DST fall-back repeat window of the session timezone (Australia/Sydney, Asia/Tokyo, Europe/London, or America/New_York — only zones with fall-back transitions matter).","commonSituations":"Custom session times configured to exactly 1:00–2:00 AM local in DST-observing zones on transition days (e.g. 01:30 America/New_York on the November fall-back Sunday); unusual `ForexSession` variants with a `session_time` overlapping the transition.","solutions":["Choose session times that avoid DST transition windows (never 00:30–02:30 local in zones observing DST, especially the 01:00–02:00 repeat hour).","If a specific instant is required, resolve ambiguity explicitly with jiff's `earlier()`/`later()` on the `AmbiguousTimestamp` rather than using the panicking helper.","Check whether the ambiguous date corresponds to a known DST transition and handle that day specially in your scheduling logic.","Report the configuration that produced it upstream if the default session times are affected."],"exampleFix":"// before\nlet boundary = fx_next_start(session, &now); // panics on ambiguous local time\n\n// after (explicit disambiguation with jiff)\nlet ambiguous = tz.to_ambiguous_timestamp(date.to_datetime(session_time))?;\nlet boundary = ambiguous.earlier().ok_or_else(|| anyhow!(\"no earlier instant\"))?.timestamp();","handlingStrategy":"validation","validationCode":"// Avoid session times that can hit DST fall-back repeat windows (01:00-02:00 local)\nfn session_time_dst_safe(t: jiff::civil::Time) -> bool {\n    let h = t.hour();\n    !(h == 1) // 01:xx local is the risky repeat hour in US/EU fall-back transitions\n}","typeGuard":null,"tryCatchPattern":"// Panics cannot be caught; if you need a 01:xx session time, resolve ambiguity yourself\nlet ambiguous = tz.to_ambiguous_timestamp(dt)?;\nlet ts = match ambiguous.earlier() {\n    Some(z) => z.timestamp(),\n    None => anyhow::bail!(\"session time is DST-ambiguous; pick a different session_time\"),\n};","preventionTips":["Configure session times away from 01:00–02:00 local in DST-observing session zones (Sydney, London, New York).","Know the fall-back dates (US: 1st Sunday Nov; EU: last Sunday Oct; AU: 1st Sunday Apr) and audit schedules near them.","Compute boundaries explicitly with jiff's AmbiguousTimestamp::earlier/later for full control."],"tags":["rust","panic","dst","timezone","ambiguity"],"backgroundTag":"invalid-argument-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}