Skip to content

Commit 89f799e

Browse files
committed
Use local timezone, not local UTC offset
With this change, we treat "local" as the local timezone, whereas before, we treated "local" as the local (current) UTC offset. The following behavior is shown for timezone Europe/Berlin, during winter. Before: UTC offset is used for parse_datetime >>> parse_datetime("2024-01-01T12:00:00Z") = Mon, 1 Jan 2024 12:00:00 +0000 [DateTime] After: >>> parse_datetime("2024-01-01T12:00:00Z") = Mon, 1 Jan 2024 13:00:00 +0100 [DateTime] Before: "Winter" UTC offset is used for datetime in summer >>> parse_datetime("2024-07-01T12:00:00Z") -> "local" = Mon, 1 Jul 2024 13:00:00 +0100 [DateTime] After: "Summer" UTC offset is used for datetime in summer >>> parse_datetime("2024-07-01T12:00:00Z") -> "local" = Mon, 1 Jul 2024 14:00:00 +0200 [DateTime] After: "Winter" UTC offset is used for datetime in winter >>> parse_datetime("2024-01-01T12:00:00Z") -> "local" = Mon, 1 Jan 2024 13:00:00 +0100 [DateTime]
1 parent 721ed32 commit 89f799e

File tree

6 files changed

+28
-3
lines changed

6 files changed

+28
-3
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

numbat/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ num-format = "0.4.4"
3131
walkdir = "2"
3232
chrono = "0.4.31"
3333
chrono-tz = "0.8.5"
34+
iana-time-zone = "0.1"
3435

3536
[features]
3637
default = ["fetch-exchangerates"]

numbat/src/datetime.rs

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use chrono::{DateTime, FixedOffset, Offset, TimeZone};
2+
use chrono_tz::Tz;
3+
4+
fn get_local_timezone() -> Option<Tz> {
5+
let tz_str = iana_time_zone::get_timezone().ok()?;
6+
tz_str.parse().ok()
7+
}
8+
9+
/// We use this function to get the UTC offset corresponding to
10+
/// the local timezone *at the specific instant in time* specified
11+
/// by 'dt', which might be different from the *current* UTC offset.
12+
///
13+
/// For example, in the timezone Europe/Berlin in 2024, we expect a
14+
/// UTC offset of +01:00 for 'dt's in winter, while we expect a UTC
15+
/// offset of +02:00 for 'dt's in summer, due to DST. If we were to
16+
/// use chronos 'Local', we would get a UTC offset depending on when
17+
/// we run the program.
18+
pub fn local_offset_for_datetime<O: TimeZone + Offset>(dt: &DateTime<O>) -> FixedOffset {
19+
get_local_timezone()
20+
.map(|tz| dt.with_timezone(&tz).offset().fix())
21+
.unwrap_or_else(|| dt.offset().fix())
22+
}

numbat/src/ffi.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -805,9 +805,9 @@ fn parse_datetime(args: &[Value]) -> Result<Value> {
805805
.or_else(|_| chrono::DateTime::parse_from_rfc2822(input))
806806
.map_err(RuntimeError::DateParsingError)?;
807807

808-
let offset = output.offset();
808+
let offset = crate::datetime::local_offset_for_datetime(&output);
809809

810-
Ok(Value::DateTime(output.into(), *offset))
810+
Ok(Value::DateTime(output.into(), offset))
811811
}
812812

813813
fn format_datetime(args: &[Value]) -> Result<Value> {

numbat/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod ast;
33
mod bytecode_interpreter;
44
mod column_formatter;
55
mod currency;
6+
mod datetime;
67
mod decorator;
78
pub mod diagnostic;
89
mod dimension;

numbat/src/vm.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ impl Vm {
697697
let lhs = self.pop_datetime();
698698

699699
let offset = if rhs == "local" {
700-
chrono::Local::now().offset().fix()
700+
crate::datetime::local_offset_for_datetime(&lhs)
701701
} else {
702702
let tz: chrono_tz::Tz = rhs
703703
.parse()

0 commit comments

Comments
 (0)