diff --git a/CHANGELOG.md b/CHANGELOG.md index 46ed3ac..b9fc8b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## Unreleased +- Fix issue with leap second tables being recorded in unix leap seconds + rather than unix seconds. +- Add tests to test exact placement of leap seconds + ## v1.1.0 - 2025-09-23 - Add `tzcalendar.atomic_difference` to calculate the actual difference between two timestamps including leap seconds. diff --git a/src/tzif/database.gleam b/src/tzif/database.gleam index 56954e3..3126f24 100644 --- a/src/tzif/database.gleam +++ b/src/tzif/database.gleam @@ -201,12 +201,16 @@ pub fn leap_seconds( let #(ts_seconds, _) = timestamp.to_unix_seconds_and_nanoseconds(ts) + // Converting the leapseconds to unix time rather than monotonic time, but the "right/" + // timezones assume monotonic time. I am assuming timestamp represents + // unix time. case list.length(tzdata.fields.leapsecond_values) { 0 -> Error(InfoNotFound) _ -> { tzdata.fields.leapsecond_values + |> list.map(fn(ls_tuple) { #(ls_tuple.0 - ls_tuple.1 + 1, ls_tuple.1) }) |> list.fold_until(Ok(0), fn(acc, leap_second_info) { - case leap_second_info.0 < ts_seconds { + case leap_second_info.0 <= ts_seconds { True -> list.Continue(Ok(leap_second_info.1)) False -> list.Stop(acc) } diff --git a/src/tzif/tzcalendar.gleam b/src/tzif/tzcalendar.gleam index b3a5825..3d9dee2 100644 --- a/src/tzif/tzcalendar.gleam +++ b/src/tzif/tzcalendar.gleam @@ -119,6 +119,22 @@ pub fn to_calendar( /// /// This /// returns a `TzDatabaseError` if there is an issue finding time zone information. +/// +/// # Example +/// +/// ```gleam +/// import gleam/time/calendar +/// import tzif/database +/// +/// let assert Ok(db) = database.load_from_os() +/// +/// from_calendar( +/// calendar.Date(2025, calendar.November, 2), +/// calendar.TimeOfDay(1, 30, 0, 0), +/// "America/New_York", +/// db, +/// ) +/// // Ok([Timestamp(1762061400, 0), Timestamp(1762065000, 0)]) pub fn from_calendar( date: Date, time: TimeOfDay, @@ -126,7 +142,7 @@ pub fn from_calendar( db: TzDatabase, ) -> Result(List(Timestamp), database.TzDatabaseError) { // Assume no shift will be more than 24 hours - let ts_utc = timestamp.from_calendar(date, time, duration.seconds(0)) + let ts_utc = timestamp.from_calendar(date, time, calendar.utc_offset) // What are the offsets at +/- the 24 hour window use before_zone <- result.try( diff --git a/test/tzif/tzcalendar_test.gleam b/test/tzif/tzcalendar_test.gleam index 90e1265..f6c4bf3 100644 --- a/test/tzif/tzcalendar_test.gleam +++ b/test/tzif/tzcalendar_test.gleam @@ -292,3 +292,34 @@ pub fn atomic_difference_test() { assert tzcalendar.atomic_difference(middle, late, "right/UTC", db) == Ok(duration.seconds(1_104_537_612)) } + +pub fn atomic_difference_one_second_test() { + let db = get_database() + + let before = + timestamp.from_calendar( + calendar.Date(2016, calendar.December, 31), + calendar.TimeOfDay(23, 59, 59, 0), + calendar.utc_offset, + ) + let after = + timestamp.from_calendar( + calendar.Date(2017, calendar.January, 1), + calendar.TimeOfDay(0, 0, 0, 0), + calendar.utc_offset, + ) + + assert timestamp.difference(before, after) == duration.seconds(1) + + assert tzcalendar.atomic_difference(before, after, "right/UTC", db) + == Ok(duration.seconds(2)) +} + +pub fn atomic_difference_no_leap_second_test() { + let db = get_database() + let start = timestamp.from_unix_seconds(0) + let end = timestamp.from_unix_seconds(644_241_600) + + assert tzcalendar.atomic_difference(start, end, "UTC", db) + == Error(database.InfoNotFound) +}