Skip to content

Commit 90e045c

Browse files
authored
Merge pull request #9 from devries/unix-leap-time
Unix leap second table issue
2 parents 0fc30f8 + b142804 commit 90e045c

File tree

4 files changed

+58
-2
lines changed

4 files changed

+58
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## Unreleased
2+
- Fix issue with leap second tables being recorded in unix leap seconds
3+
rather than unix seconds.
4+
- Add tests to test exact placement of leap seconds
5+
16
## v1.1.0 - 2025-09-23
27
- Add `tzcalendar.atomic_difference` to calculate the actual difference between
38
two timestamps including leap seconds.

src/tzif/database.gleam

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,16 @@ pub fn leap_seconds(
201201

202202
let #(ts_seconds, _) = timestamp.to_unix_seconds_and_nanoseconds(ts)
203203

204+
// Converting the leapseconds to unix time rather than monotonic time, but the "right/"
205+
// timezones assume monotonic time. I am assuming timestamp represents
206+
// unix time.
204207
case list.length(tzdata.fields.leapsecond_values) {
205208
0 -> Error(InfoNotFound)
206209
_ -> {
207210
tzdata.fields.leapsecond_values
211+
|> list.map(fn(ls_tuple) { #(ls_tuple.0 - ls_tuple.1 + 1, ls_tuple.1) })
208212
|> list.fold_until(Ok(0), fn(acc, leap_second_info) {
209-
case leap_second_info.0 < ts_seconds {
213+
case leap_second_info.0 <= ts_seconds {
210214
True -> list.Continue(Ok(leap_second_info.1))
211215
False -> list.Stop(acc)
212216
}

src/tzif/tzcalendar.gleam

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,30 @@ pub fn to_calendar(
119119
///
120120
/// This
121121
/// returns a `TzDatabaseError` if there is an issue finding time zone information.
122+
///
123+
/// # Example
124+
///
125+
/// ```gleam
126+
/// import gleam/time/calendar
127+
/// import tzif/database
128+
///
129+
/// let assert Ok(db) = database.load_from_os()
130+
///
131+
/// from_calendar(
132+
/// calendar.Date(2025, calendar.November, 2),
133+
/// calendar.TimeOfDay(1, 30, 0, 0),
134+
/// "America/New_York",
135+
/// db,
136+
/// )
137+
/// // Ok([Timestamp(1762061400, 0), Timestamp(1762065000, 0)])
122138
pub fn from_calendar(
123139
date: Date,
124140
time: TimeOfDay,
125141
zone_name: String,
126142
db: TzDatabase,
127143
) -> Result(List(Timestamp), database.TzDatabaseError) {
128144
// Assume no shift will be more than 24 hours
129-
let ts_utc = timestamp.from_calendar(date, time, duration.seconds(0))
145+
let ts_utc = timestamp.from_calendar(date, time, calendar.utc_offset)
130146

131147
// What are the offsets at +/- the 24 hour window
132148
use before_zone <- result.try(

test/tzif/tzcalendar_test.gleam

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,3 +292,34 @@ pub fn atomic_difference_test() {
292292
assert tzcalendar.atomic_difference(middle, late, "right/UTC", db)
293293
== Ok(duration.seconds(1_104_537_612))
294294
}
295+
296+
pub fn atomic_difference_one_second_test() {
297+
let db = get_database()
298+
299+
let before =
300+
timestamp.from_calendar(
301+
calendar.Date(2016, calendar.December, 31),
302+
calendar.TimeOfDay(23, 59, 59, 0),
303+
calendar.utc_offset,
304+
)
305+
let after =
306+
timestamp.from_calendar(
307+
calendar.Date(2017, calendar.January, 1),
308+
calendar.TimeOfDay(0, 0, 0, 0),
309+
calendar.utc_offset,
310+
)
311+
312+
assert timestamp.difference(before, after) == duration.seconds(1)
313+
314+
assert tzcalendar.atomic_difference(before, after, "right/UTC", db)
315+
== Ok(duration.seconds(2))
316+
}
317+
318+
pub fn atomic_difference_no_leap_second_test() {
319+
let db = get_database()
320+
let start = timestamp.from_unix_seconds(0)
321+
let end = timestamp.from_unix_seconds(644_241_600)
322+
323+
assert tzcalendar.atomic_difference(start, end, "UTC", db)
324+
== Error(database.InfoNotFound)
325+
}

0 commit comments

Comments
 (0)