Skip to content

Commit f1effb2

Browse files
committed
std.time: include leap seconds in timezone projection
1 parent ea54ee9 commit f1effb2

File tree

2 files changed

+68
-39
lines changed

2 files changed

+68
-39
lines changed

lib/std/time.zig

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ pub const DateTime = struct {
112112

113113
/// UNIX timestamp in nanoseconds
114114
nano_timestamp: i128,
115-
tt: TimeZone.TimeType,
115+
116+
/// The time zone to which `nano_timestamp` was projected to.
117+
tz_projection: TimeZone.Projection,
116118

117119
pub const Weekday = enum(u3) {
118120
monday = 1,
@@ -181,6 +183,9 @@ pub const DateTime = struct {
181183
};
182184

183185
/// Get current date and time in the system's local time.
186+
/// On non-Windows systems this loads the system's local time
187+
/// time zone database from the filesystem and may allocate
188+
/// so when called repeated `nowTz` should be preferred.
184189
pub fn now(allocator: std.mem.Allocator) LocalTimeError!DateTime {
185190
const nano_timestamp = nanoTimestamp();
186191

@@ -202,7 +207,6 @@ pub const DateTime = struct {
202207
@intCast(tzi.StandardName[4]),
203208
@intCast(tzi.StandardName[5]),
204209
},
205-
.flags = 0,
206210
.offset = tzi.Bias,
207211
});
208212
}
@@ -215,8 +219,13 @@ pub const DateTime = struct {
215219
return fromTimestamp(nano_timestamp, tz.project(@intCast(@divFloor(nano_timestamp, ns_per_s))));
216220
}
217221

222+
pub fn nowTz(tz: *const TimeZone) DateTime {
223+
const nano_timestamp = nanoTimestamp();
224+
return fromTimestamp(nano_timestamp, tz.project(@intCast(@divFloor(nano_timestamp, ns_per_s))));
225+
}
226+
218227
/// Convert UNIX timestamp in nanoseconds to a DateTime.
219-
pub fn fromTimestamp(nano_timestamp: i128, tt: TimeZone.TimeType) DateTime {
228+
pub fn fromTimestamp(nano_timestamp: i128, tz_projection: TimeZone.Projection) DateTime {
220229
// Ported from musl, which is licensed under the MIT license:
221230
// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT
222231

@@ -228,7 +237,7 @@ pub const DateTime = struct {
228237
const days_per_4y = 365 * 4 + 1;
229238

230239
const seconds_unadjusted: i64 = @intCast(@divFloor(nano_timestamp, ns_per_s));
231-
const seconds = seconds_unadjusted + tt.offset - leapoch;
240+
const seconds = seconds_unadjusted + tz_projection.offset - leapoch;
232241
var days = @divTrunc(seconds, 86400);
233242
var rem_seconds = @as(i32, @truncate(@rem(seconds, 86400)));
234243
if (rem_seconds < 0) {
@@ -295,7 +304,7 @@ pub const DateTime = struct {
295304
.second = @intCast(@rem(rem_seconds, 60)),
296305
.nanosecond = @intCast(@rem(nano_timestamp, ns_per_s)),
297306
.nano_timestamp = nano_timestamp,
298-
.tt = tt,
307+
.tz_projection = tz_projection,
299308
};
300309
}
301310

@@ -372,7 +381,7 @@ pub const DateTime = struct {
372381
'H' => try std.fmt.formatInt(date.hour, 10, .lower, .{ .width = 2, .fill = '0' }, writer),
373382
'M' => try std.fmt.formatInt(date.minute, 10, .lower, .{ .width = 2, .fill = '0' }, writer),
374383
'S' => try std.fmt.formatInt(date.second, 10, .lower, .{ .width = 2, .fill = '0' }, writer),
375-
's' => try std.fmt.formatInt(date.millisecond, 10, .lower, .{ .width = 3, .fill = '0' }, writer),
384+
's' => try std.fmt.formatInt(@divFloor(date.nanosecond, ns_per_ms), 10, .lower, .{ .width = 3, .fill = '0' }, writer),
376385
'j' => try std.fmt.formatInt(date.year_day, 10, .lower, .{ .width = 3, .fill = '0' }, writer),
377386
'p' => if (date.hour < 12) {
378387
try writer.writeAll("AM");
@@ -384,15 +393,15 @@ pub const DateTime = struct {
384393
begin = i + 1;
385394
},
386395
'z' => {
387-
const sign = "+-"[@intFromBool(date.tt.offset < 0)];
396+
const sign = "+-"[@intFromBool(date.tz_projection.offset < 0)];
388397
try writer.writeByte(sign);
389-
const abs = @abs(date.tt.offset);
398+
const abs = @abs(date.tz_projection.offset);
390399
const hours = @divFloor(abs, 3600);
391400
const minutes = @rem(@divFloor(abs, 60), 60);
392401
try std.fmt.formatInt(hours, 10, .lower, .{ .width = 2, .fill = '0' }, writer);
393402
try std.fmt.formatInt(minutes, 10, .lower, .{ .width = 2, .fill = '0' }, writer);
394403
},
395-
'Z' => try writer.writeAll(date.tt.name()),
404+
'Z' => try writer.writeAll(date.tz_projection.name()),
396405
else => @compileError("Unknown format character: " ++ [_]u8{fmt[i]}),
397406
}
398407
}
@@ -406,8 +415,13 @@ pub const DateTime = struct {
406415
}
407416
};
408417

418+
const null_projection: TimeZone.Projection = .{
419+
.offset = 0,
420+
.name_data = "TEST\x00\x00".*,
421+
};
422+
409423
test "DateTime basic usage" {
410-
const dt = DateTime.fromTimestamp(1560870105 * ns_per_s, TimeZone.TimeType.UTC);
424+
const dt = DateTime.fromTimestamp(1560870105 * ns_per_s, null_projection);
411425

412426
try testing.expect(dt.second == 45);
413427
try testing.expect(dt.minute == 1);
@@ -420,16 +434,15 @@ test "DateTime basic usage" {
420434
}
421435

422436
test "DateTime.format all" {
423-
const dt = DateTime.fromTimestamp(1560816105 * ns_per_s, TimeZone.TimeType.UTC);
437+
const dt = DateTime.fromTimestamp(1560816105 * ns_per_s, null_projection);
424438
var buf: [100]u8 = undefined;
425439
const result = try std.fmt.bufPrint(&buf, "{%a %A %b %B %m %d %y %Y %I %p %H%c%M%c%S.%s %j %%}", .{dt});
426440
try testing.expectEqualStrings("Tue Tuesday Jun June 06 18 19 2019 12 AM 00:01:45.000 169 %", result);
427441
}
428442

429443
test "DateTime.format no format" {
430-
const EEST: TimeZone.TimeType = .{
444+
const EEST: TimeZone.Projection = .{
431445
.offset = 10800,
432-
.flags = 0,
433446
.name_data = "EEST\x00\x00".*,
434447
};
435448
const dt = DateTime.fromTimestamp(1560870105 * ns_per_s, EEST);

lib/std/time/TimeZone.zig

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ pub const TimeType = struct {
2828
pub fn utIndicator(self: TimeType) bool {
2929
return (self.flags & 0x04) > 0;
3030
}
31-
32-
pub const UTC = TimeType{
33-
.offset = 0,
34-
.flags = 0,
35-
.name_data = "UTC\x00\x00\x00".*,
36-
};
3731
};
3832

3933
pub const Leapsecond = struct {
@@ -224,28 +218,50 @@ pub fn deinit(self: *TimeZone, allocator: std.mem.Allocator) void {
224218
allocator.free(self.timetypes);
225219
}
226220

221+
pub const Projection = struct {
222+
/// Offset from UNIX timestamp including leap seconds.
223+
offset: i32,
224+
name_data: [6:0]u8,
225+
226+
pub fn name(self: *const TimeType) [:0]const u8 {
227+
return std.mem.sliceTo(&self.name_data, 0);
228+
}
229+
};
230+
227231
/// Project UTC timestamp to this time zone.
228-
pub fn project(self: TimeZone, seconds: i64) TimeType {
229-
var left: usize = 0;
230-
var right: usize = self.transitions.len;
231-
232-
// Adapted from std.sort.binarySearch.
233-
var mid: usize = 0;
234-
while (left < right) {
235-
// Avoid overflowing in the midpoint calculation
236-
mid = left + (right - left) / 2;
237-
// Compare the key with the midpoint element
238-
switch (std.math.order(seconds, self.transitions[mid].ts)) {
239-
.eq => return self.transitions[mid].timetype.*,
240-
.gt => left = mid + 1,
241-
.lt => right = mid,
232+
pub fn project(self: TimeZone, seconds: i64) Projection {
233+
const index = index: {
234+
var left: usize = 0;
235+
var right: usize = self.transitions.len;
236+
237+
// Adapted from std.sort.binarySearch.
238+
var mid: usize = 0;
239+
while (left < right) {
240+
// Avoid overflowing in the midpoint calculation
241+
mid = left + (right - left) / 2;
242+
// Compare the key with the midpoint element
243+
switch (std.math.order(seconds, self.transitions[mid].ts)) {
244+
.eq => break :index mid,
245+
.gt => left = mid + 1,
246+
.lt => right = mid,
247+
}
242248
}
243-
}
244-
if (self.transitions[mid].ts > seconds) {
245-
return self.transitions[mid - 1].timetype.*;
246-
} else {
247-
return self.transitions[mid].timetype.*;
248-
}
249+
break :index mid - @intFromBool(self.transitions[mid].ts > seconds);
250+
};
251+
const leap_adjustment = leap: {
252+
var i = self.leapseconds.len;
253+
if (i == 0) break :leap 0;
254+
while (i > 0) {
255+
i -= 1;
256+
if (self.leapseconds[i].occurrence < seconds) break;
257+
}
258+
break :leap self.leapseconds[i].correction;
259+
};
260+
const tt = self.transitions[index].timetype;
261+
return .{
262+
.offset = tt.offset + leap_adjustment,
263+
.name_data = tt.name_data,
264+
};
249265
}
250266

251267
test project {

0 commit comments

Comments
 (0)