diff --git a/src/shacl2code/lang/templates/rust/Cargo.toml.j2 b/src/shacl2code/lang/templates/rust/Cargo.toml.j2 index ed83f2b4..c32a0ef2 100644 --- a/src/shacl2code/lang/templates/rust/Cargo.toml.j2 +++ b/src/shacl2code/lang/templates/rust/Cargo.toml.j2 @@ -8,7 +8,7 @@ version = "0.1.0" edition = "2021" [dependencies] -chrono = { version = "0.4", features = ["serde"] } +chrono = { version = "0.4.31", features = ["serde"] } lazy_static = "1.4" regex = "1" serde = { version = "1", features = ["derive"] } diff --git a/src/shacl2code/lang/templates/rust/lib.rs.j2 b/src/shacl2code/lang/templates/rust/lib.rs.j2 index 94dfd5e8..d3759b77 100644 --- a/src/shacl2code/lang/templates/rust/lib.rs.j2 +++ b/src/shacl2code/lang/templates/rust/lib.rs.j2 @@ -226,9 +226,9 @@ fn decode_datetime_string( "" => { let naive = NaiveDateTime::parse_from_str(base, "%Y-%m-%dT%H:%M:%S") .map_err(|e| Error::Decode(path.to_string(), format!("Invalid date time '{}': {}", s, e)))?; - let local_offset = *Local::now().offset(); - Ok(local_offset.from_local_datetime(&naive).single() - .ok_or_else(|| Error::Decode(path.to_string(), format!("Ambiguous local time '{}'", s)))?) + Ok(Local.from_local_datetime(&naive).single() + .ok_or_else(|| Error::Decode(path.to_string(), format!("Ambiguous local time '{}'", s)))? + .fixed_offset()) } _ => { let full = format!("{}{}", base, tz); diff --git a/tests/test_rust.py b/tests/test_rust.py index 7fdfe060..2d76d51c 100644 --- a/tests/test_rust.py +++ b/tests/test_rust.py @@ -10,6 +10,7 @@ import textwrap from enum import Enum from pathlib import Path +from typing import Callable, Optional import pytest @@ -117,7 +118,7 @@ def f(code_fragment, *, passes=True, progress=None): [dependencies] shacl_model = {{ path = "{test_lib}" }} serde_json = "1" - chrono = {{ version = "0.4", features = ["serde"] }} + chrono = {{ version = "0.4.31", features = ["serde"] }} """)) src = tmp_path / "src" / "main.rs" @@ -643,6 +644,32 @@ def test_datetime_decode(compile_test, value, expect): ), f"Test failed. Expected {s!r}, got {output.rstrip()!r}" +def test_datetime_decode_dst_transition( + compile_test: Callable[..., Optional[str]], monkeypatch: pytest.MonkeyPatch +) -> None: + """Offset must reflect the daylight saving time (DST) rule of the + parsed date, not of "now".""" + monkeypatch.setenv("TZ", "America/New_York") + + # Winter and summer require different offsets, so both must be resolved per-date, not from "now". + output = compile_test( + """\ + let path = Path::new(); + + let winter = decode_date_time("2024-01-15T12:00:00", &path)?; + println!("{}", encode_date_time(&winter)); + + let summer = decode_date_time("2024-07-15T12:00:00", &path)?; + println!("{}", encode_date_time(&summer)); + """, + ) + assert output is not None + + lines = output.splitlines() + assert lines[0] == "2024-01-15T12:00:00-05:00", "EST (no DST) offset expected" + assert lines[1] == "2024-07-15T12:00:00-04:00", "EDT (DST) offset expected" + + @timetests.datetimestamp_decode_tests() def test_datetimestamp_decode(compile_test, value, expect): output = compile_test( @@ -724,7 +751,7 @@ def test_extensible_context(compile_test, roundtrip): class _Prop: - def __init__(self, path, varname=None): + def __init__(self, path: str, varname: Optional[str] = None) -> None: self.path = path self.varname = varname if varname is not None else path