Skip to content

treat all offsets as invalid for gaps in civil time #212

@BurntSushi

Description

@BurntSushi

Jiff currently has this behavior:

use jiff::Zoned;

fn main() -> anyhow::Result<()> {
    let zdt: Zoned = "2024-03-10T02:30-05[US/Eastern]".parse()?;
    assert_eq!(zdt.to_string(), "2024-03-10T03:30:00-04:00[US/Eastern]");
    let zdt: Zoned = "2024-03-10T02:30-04[US/Eastern]".parse()?;
    assert_eq!(zdt.to_string(), "2024-03-10T01:30:00-05:00[US/Eastern]");

    Ok(())
}

However, this does not match Temporal's behavior:

>> Temporal.ZonedDateTime.from("2024-03-10T02:30-05[US/Eastern]").toString()
Uncaught RangeError: Offset -05:00 is invalid for 2024-03-10T02:30:00 in US/Eastern
    InterpretISODateTimeOffset ecmascript.mjs:1467
    ToTemporalZonedDateTime ecmascript.mjs:1531
    from zoneddatetime.mjs:478
    <anonymous> debugger eval code:1
>> Temporal.ZonedDateTime.from("2024-03-10T02:30-04[US/Eastern]").toString()
Uncaught RangeError: Offset -04:00 is invalid for 2024-03-10T02:30:00 in US/Eastern
    InterpretISODateTimeOffset ecmascript.mjs:1467
    ToTemporalZonedDateTime ecmascript.mjs:1531
    from zoneddatetime.mjs:478
    <anonymous> debugger eval code:1
[ecmascript.mjs:1467:10](https://tc39.es/polyfill/lib/ecmascript.mjs)

I believe this was intentional on my part:

jiff/src/tz/offset.rs

Lines 1590 to 1616 in 94e8270

Err(err!(
"datetime {dt} could not resolve to timestamp \
since 'reject' conflict resolution was chosen, and \
because datetime has offset {given}, but the time \
zone {tzname} for the given datetime falls in a gap \
between offsets {before} and {after}, neither of which \
match the offset",
tzname = tz.diagnostic_name(),
))
}
Fold { before, after } if given != before && given != after => {
Err(err!(
"datetime {dt} could not resolve to timestamp \
since 'reject' conflict resolution was chosen, and \
because datetime has offset {given}, but the time \
zone {tzname} for the given datetime falls in a fold \
between offsets {before} and {after}, neither of which \
match the offset",
tzname = tz.diagnostic_name(),
))
}
Gap { .. } | Fold { .. } => {
let kind = Unambiguous { offset: given };
Ok(AmbiguousTimestamp::new(dt, kind).into_ambiguous_zoned(tz))
}
}
}

And indeed, I filed an issue about this here: tc39/proposal-temporal#2892

I find myself agreeing more with Temporal's behavior here. In particular, Temporal returning an error here is meant to catch once-valid-but-no-longer datetime strings because of DST changes. And especially the conservative stance I find appealing: it would be better to return an error here since converting that into a non-error is easier than the reverse.

So for jiff 0.2, I'll plan to turn the cases above into errors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    breaking changeIssues that require a breaking change for resolution.enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions