Skip to content

Commit 3dea013

Browse files
committed
backport timezone fixes from 0.9.x to 0.8.x
1 parent 4d8a5a4 commit 3dea013

File tree

4 files changed

+48
-17
lines changed

4 files changed

+48
-17
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# 🚀 Changelog
22

3+
## 0.8.10 (2025-10-16)
4+
5+
Backport of timezone fixes from 0.9.x to 0.8.x that affected some timezones
6+
far into the future or past
7+
38
## 0.8.9 (2025-09-21)
49

510
- Fixed not all test files included in source distribution (#266)

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ maintainers = [
77
{name = "Arie Bovenberg", email = "a.c.bovenberg@gmail.com"},
88
]
99
readme = "README.md"
10-
version = "0.8.9"
10+
version = "0.8.10"
1111
license = "MIT"
1212
description = "Modern datetime library for Python"
1313
requires-python = ">=3.9"

pysrc/whenever/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
)
3232

3333

34-
__version__ = "0.8.9"
34+
__version__ = "0.8.10"
3535

3636
reset_tzpath() # populate the tzpath once at startup
3737

src/tz/tzif.rs

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,19 @@ impl TZif {
6868
// If there's no POSIX TZ string, use the last offset.
6969
// There's not much else we can do.
7070
.unwrap_or_else(|| {
71-
let (offset, _) = self
71+
let (prev_offset, last_shift) = self
7272
.offsets_by_local
7373
.last()
74-
// Safe: We've ensured during parsing that there's at least one entry
74+
// SAFETY: We've ensured during parsing that there's at least one entry
7575
// if there's no POSIX TZ string.
7676
.unwrap()
7777
.1;
78-
Ambiguity::Unambiguous(offset)
78+
Ambiguity::Unambiguous(
79+
prev_offset
80+
.shift(last_shift)
81+
// SAFETY: last_shift was calculated from prev_offset itself
82+
.unwrap(),
83+
)
7984
})
8085
}
8186
}
@@ -268,7 +273,8 @@ fn load_transitions(
268273
offsets: &[Offset],
269274
indices: &[u8],
270275
) -> Option<Vec<(EpochSecs, Offset)>> {
271-
let mut result = Vec::with_capacity(indices.len());
276+
let mut result = Vec::with_capacity(indices.len() + 1);
277+
result.push((EpochSecs::MIN, *offsets.first()?)); // Ensure correct initial offset
272278
for (&idx, &epoch) in indices.iter().zip(transition_times) {
273279
let &offset = offsets.get(usize::from(idx))?;
274280
result.push((epoch, offset));
@@ -393,12 +399,12 @@ mod tests {
393399
#[test]
394400
fn test_no_magic_header() {
395401
// empty
396-
assert_eq!(parse(b"", "Foo").unwrap_err(), ErrorCause::Header);
402+
assert_eq!(parse(b"", "").unwrap_err(), ErrorCause::Header);
397403
// too small
398-
assert_eq!(parse(b"TZi", "Foo").unwrap_err(), ErrorCause::Header);
404+
assert_eq!(parse(b"TZi", "").unwrap_err(), ErrorCause::Header);
399405
// wrong magic value
400406
assert_eq!(
401-
parse(b"this-is-not-tzif-file", "Foo").unwrap_err(),
407+
parse(b"this-is-not-tzif-file", "").unwrap_err(),
402408
ErrorCause::Header
403409
);
404410
}
@@ -430,8 +436,11 @@ mod tests {
430436
#[test]
431437
fn test_utc() {
432438
const TZ_UTC: &[u8] = include_bytes!("../../tests/tzif/UTC.tzif");
433-
let tzif = parse(TZ_UTC, "UTC").unwrap();
434-
assert_eq!(tzif.offsets_by_utc, &[]);
439+
let tzif = parse(TZ_UTC, "").unwrap();
440+
assert_eq!(
441+
tzif.offsets_by_utc,
442+
&[(EpochSecs::MIN, 0.try_into().unwrap())]
443+
);
435444
assert_eq!(tzif.end, posix::parse(b"UTC0"));
436445

437446
assert_eq!(
@@ -447,8 +456,11 @@ mod tests {
447456
#[test]
448457
fn test_fixed() {
449458
const TZ_FIXED: &[u8] = include_bytes!("../../tests/tzif/GMT-13.tzif");
450-
let tzif = parse(TZ_FIXED, "GMT-13").unwrap();
451-
assert_eq!(tzif.offsets_by_utc, &[]);
459+
let tzif = parse(TZ_FIXED, "").unwrap();
460+
assert_eq!(
461+
tzif.offsets_by_utc,
462+
&[(EpochSecs::MIN, (13 * 3_600).try_into().unwrap())]
463+
);
452464
assert_eq!(tzif.end, posix::parse(b"<+13>-13"));
453465

454466
assert_eq!(
@@ -465,7 +477,7 @@ mod tests {
465477
fn test_v1() {
466478
// A TZif file using the old version 1 format.
467479
const TZ_V1: &[u8] = include_bytes!("../../tests/tzif/Paris_v1.tzif");
468-
let tzif = parse(TZ_V1, "Europe/Paris").unwrap();
480+
let tzif = parse(TZ_V1, "").unwrap();
469481
assert!(!tzif.offsets_by_utc.is_empty());
470482
assert_eq!(tzif.end, None);
471483

@@ -474,13 +486,17 @@ mod tests {
474486
tzif.offset_for_instant(EpochSecs::new_unchecked(3155760000)),
475487
3600.try_into().unwrap()
476488
);
489+
assert_eq!(
490+
tzif.ambiguity_for_local(EpochSecs::new_unchecked(3155760000)),
491+
unambig(3600)
492+
);
477493
}
478494

479495
// Thanks to Jiff for the test tzif file
480496
#[test]
481497
fn test_clamp_transitions_to_range() {
482498
const TZ_OUT_OF_RANGE: &[u8] = include_bytes!("../../tests/tzif/Sydney_widerange.tzif");
483-
let tzif = parse(TZ_OUT_OF_RANGE, "Australia/Sydney").unwrap();
499+
let tzif = parse(TZ_OUT_OF_RANGE, "").unwrap();
484500
assert!(!tzif.offsets_by_utc.is_empty());
485501
assert_eq!(
486502
tzif.offset_for_instant(EpochSecs::MIN),
@@ -492,10 +508,20 @@ mod tests {
492508
);
493509
}
494510

511+
#[test]
512+
fn test_implicit_initial_offset() {
513+
const TZ_HON: &[u8] = include_bytes!("../../tests/tzif/Honolulu.tzif");
514+
let tzif = parse(TZ_HON, "").unwrap();
515+
assert_eq!(
516+
tzif.offset_for_instant(EpochSecs::new_unchecked(-3_000_000_000)),
517+
Offset::new_unchecked(-37886),
518+
);
519+
}
520+
495521
#[test]
496522
fn test_last_transition_is_gap() {
497523
const TZ_HON: &[u8] = include_bytes!("../../tests/tzif/Honolulu.tzif");
498-
let tzif = parse(TZ_HON, "Pacific/Honolulu").unwrap();
524+
let tzif = parse(TZ_HON, "").unwrap();
499525
assert_eq!(tzif.end, posix::parse(b"HST10"));
500526
assert_eq!(
501527
tzif.offset_for_instant(EpochSecs::new_unchecked(-712150201)),
@@ -535,7 +561,7 @@ mod tests {
535561
#[test]
536562
fn test_typical_tzif_example() {
537563
const TZ_AMS: &[u8] = include_bytes!("../../tests/tzif/Amsterdam.tzif");
538-
let tzif = parse(TZ_AMS, "Europe/Amsterdam").unwrap();
564+
let tzif = parse(TZ_AMS, "").unwrap();
539565
assert_eq!(tzif.end, posix::parse(b"CET-1CEST,M3.5.0,M10.5.0/3"));
540566

541567
let utc_cases = &[

0 commit comments

Comments
 (0)