Skip to content

Commit

Permalink
std.time.Date: store time type with the date
Browse files Browse the repository at this point in the history
  • Loading branch information
Vexu committed Dec 13, 2023
1 parent 28883dd commit 27d490c
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 47 deletions.
65 changes: 52 additions & 13 deletions lib/std/time.zig
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ pub const Date = struct {
/// year, year 0000 is equal to 1 BCE
year: i32,

utc_timestamp: i64,
tt: TimeZone.TimeType,

pub const Weekday = enum(u3) {
monday = 1,
tuesday = 2,
Expand All @@ -128,6 +131,14 @@ pub const Date = struct {
"Saturday",
"Sunday",
};

pub fn shortName(w: Weekday) []const u8 {
return names[@intFromEnum(w) - 1][0..3];
}

pub fn longName(w: Weekday) []const u8 {
return names[@intFromEnum(w) - 1];
}
};

pub const Month = enum(u4) {
Expand Down Expand Up @@ -158,6 +169,14 @@ pub const Date = struct {
"November",
"December",
};

pub fn shortName(m: Month) []const u8 {
return names[@intFromEnum(m) - 1][0..3];
}

pub fn longName(m: Month) []const u8 {
return names[@intFromEnum(m) - 1];
}
};

/// Get current date in the system's local time.
Expand All @@ -168,11 +187,11 @@ pub const Date = struct {
var tz = try localtime(sf.allocator());
defer tz.deinit(sf.allocator());

return fromTimestamp(tz.project(utc_timestamp));
return fromTimestamp(utc_timestamp, tz.project(utc_timestamp));
}

/// Convert timestamp in milliseconds to a Date.
pub fn fromTimestamp(milliseconds: i64) Date {
pub fn fromTimestamp(utc_timestamp: i64, tt: TimeZone.TimeType) Date {
// Ported from musl, which is licensed under the MIT license:
// https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT

Expand All @@ -183,7 +202,7 @@ pub const Date = struct {
const days_per_100y = 365 * 100 + 24;
const days_per_4y = 365 * 4 + 1;

const seconds = @divTrunc(milliseconds, 1000) - leapoch;
const seconds = @divTrunc(utc_timestamp, 1000) + tt.offset - leapoch;
var days = @divTrunc(seconds, 86400);
var rem_seconds = @as(i32, @truncate(@rem(seconds, 86400)));
if (rem_seconds < 0) {
Expand Down Expand Up @@ -248,7 +267,9 @@ pub const Date = struct {
.hour = @intCast(@divTrunc(rem_seconds, 3600)),
.minute = @intCast(@rem(@divTrunc(rem_seconds, 60), 60)),
.second = @intCast(@rem(rem_seconds, 60)),
.millisecond = @intCast(@rem(milliseconds, 1000)),
.millisecond = @intCast(@rem(utc_timestamp, 1000)),
.utc_timestamp = utc_timestamp,
.tt = tt,
};
}

Expand All @@ -272,7 +293,7 @@ pub const Date = struct {
return std.math.order(self.millisecond, other.millisecond);
}

pub const default_fmt = "%Y-%m-%dT%H%c%M%c%S";
pub const default_fmt = "%Y-%m-%dT%H%c%M%c%S%z";

/// %a Abbreviated weekday name (Sun)
/// %A Full weekday name (Sunday)
Expand All @@ -290,6 +311,8 @@ pub const Date = struct {
/// %S Second (00-60)
/// %y Year, last two digits (00-99)
/// %Y Year
/// %z UTC offset in the form ±HHMM
/// %Z Time zone name.
/// %% A % sign
pub fn format(
date: Date,
Expand Down Expand Up @@ -317,10 +340,10 @@ pub const Date = struct {
fmt_char = false;

switch (c) {
'a' => try writer.writeAll(Weekday.names[@intFromEnum(date.week_day) - 1][0..3]),
'A' => try writer.writeAll(Weekday.names[@intFromEnum(date.week_day) - 1]),
'b' => try writer.writeAll(Month.names[@intFromEnum(date.month) - 1][0..3]),
'B' => try writer.writeAll(Month.names[@intFromEnum(date.month) - 1]),
'a' => try writer.writeAll(date.week_day.shortName()),
'A' => try writer.writeAll(date.week_day.longName()),
'b' => try writer.writeAll(date.month.shortName()),
'B' => try writer.writeAll(date.month.longName()),
'c' => try writer.writeAll(":"),
'm' => try std.fmt.formatInt(@intFromEnum(date.month), 10, .lower, .{ .width = 2, .fill = '0' }, writer),
'd' => try std.fmt.formatInt(date.day, 10, .lower, .{ .width = 2, .fill = '0' }, writer),
Expand Down Expand Up @@ -349,6 +372,16 @@ pub const Date = struct {
try writer.writeAll("%");
begin = i + 1;
},
'z' => {
const sign = "+-"[@intFromBool(date.tt.offset < 0)];
try writer.writeByte(sign);
const abs = @abs(date.tt.offset);
const hours = @divFloor(abs, 3600);
const minutes = @rem(@divFloor(abs, 60), 60);
try std.fmt.formatInt(hours, 10, .lower, .{ .width = 2, .fill = '0' }, writer);
try std.fmt.formatInt(minutes, 10, .lower, .{ .width = 2, .fill = '0' }, writer);
},
'Z' => try writer.writeAll(date.tt.name()),
else => @compileError("Unknown format character: " ++ [_]u8{fmt[i]}),
}
}
Expand All @@ -363,7 +396,7 @@ pub const Date = struct {
};

test Date {
const date = Date.fromTimestamp(1560870105000);
const date = Date.fromTimestamp(1560870105000, TimeZone.TimeType.UTC);

try testing.expect(date.millisecond == 0);
try testing.expect(date.second == 45);
Expand All @@ -377,17 +410,22 @@ test Date {
}

test "Date.format all" {
const date = Date.fromTimestamp(1560816105000);
const date = Date.fromTimestamp(1560816105000, TimeZone.TimeType.UTC);
var buf: [100]u8 = undefined;
const result = try std.fmt.bufPrint(&buf, "{%a %A %b %B %m %d %y %Y %I %p %H%c%M%c%S.%s %j %%}", .{date});
try testing.expectEqualStrings("Tue Tuesday Jun June 06 18 19 2019 12 AM 00:01:45.000 169 %", result);
}

test "Date.format no format" {
const date = Date.fromTimestamp(1560870105000);
const EEST: TimeZone.TimeType = .{
.offset = 10800,
.flags = 0,
.name_data = "EEST\x00\x00".*,
};
const date = Date.fromTimestamp(1560870105000, EEST);
var buf: [100]u8 = undefined;
const result = try std.fmt.bufPrint(&buf, "{}", .{date});
try testing.expectEqualStrings("2019-06-18T15:01:45", result);
try std.testing.expectEqualStrings("2019-06-18T18:01:45+0300", result);
}

test "sleep" {
Expand Down Expand Up @@ -668,4 +706,5 @@ test "Timer + Instant" {

test {
_ = epoch;
_ = TimeZone;
}
70 changes: 36 additions & 34 deletions lib/std/time/TimeZone.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,35 @@ const TimeZone = @This();

pub const Transition = struct {
ts: i64,
timetype: *Timetype,
timetype: *TimeType,
};

pub const Timetype = struct {
pub const TimeType = struct {
offset: i32,
flags: u8,
name_data: [6:0]u8,

pub fn name(self: *const Timetype) [:0]const u8 {
pub fn name(self: *const TimeType) [:0]const u8 {
return std.mem.sliceTo(self.name_data[0..], 0);
}

pub fn isDst(self: Timetype) bool {
pub fn isDst(self: TimeType) bool {
return (self.flags & 0x01) > 0;
}

pub fn standardTimeIndicator(self: Timetype) bool {
pub fn standardTimeIndicator(self: TimeType) bool {
return (self.flags & 0x02) > 0;
}

pub fn utIndicator(self: Timetype) bool {
pub fn utIndicator(self: TimeType) bool {
return (self.flags & 0x04) > 0;
}

pub const UTC = TimeType{
.offset = 0,
.flags = 0,
.name_data = "UTC\x00\x00\x00".*,
};
};

pub const Leapsecond = struct {
Expand All @@ -36,7 +42,7 @@ pub const Leapsecond = struct {
};

transitions: []const Transition,
timetypes: []const Timetype,
timetypes: []const TimeType,
leapseconds: []const Leapsecond,
footer: ?[]const u8,

Expand Down Expand Up @@ -99,7 +105,7 @@ fn parseBlock(allocator: std.mem.Allocator, reader: anytype, header: Header, leg
errdefer allocator.free(leapseconds);
var transitions = try allocator.alloc(Transition, header.counts.timecnt);
errdefer allocator.free(transitions);
var timetypes = try allocator.alloc(Timetype, header.counts.typecnt);
var timetypes = try allocator.alloc(TimeType, header.counts.typecnt);
errdefer allocator.free(timetypes);

// Parse transition types
Expand Down Expand Up @@ -219,30 +225,27 @@ pub fn deinit(self: *TimeZone, allocator: std.mem.Allocator) void {
}

/// Project UTC timestamp to this time zone.
pub fn project(self: TimeZone, seconds: i64) i64 {
const offset = offset: {
var left: usize = 0;
var right: usize = self.transitions.len;

// Adapted from std.sort.binarySearch.
var mid: usize = 0;
while (left < right) {
// Avoid overflowing in the midpoint calculation
mid = left + (right - left) / 2;
// Compare the key with the midpoint element
switch (std.math.order(seconds, self.transitions[mid].ts)) {
.eq => break :offset self.transitions[mid].timetype.offset,
.gt => left = mid + 1,
.lt => right = mid,
}
pub fn project(self: TimeZone, seconds: i64) TimeType {
var left: usize = 0;
var right: usize = self.transitions.len;

// Adapted from std.sort.binarySearch.
var mid: usize = 0;
while (left < right) {
// Avoid overflowing in the midpoint calculation
mid = left + (right - left) / 2;
// Compare the key with the midpoint element
switch (std.math.order(seconds, self.transitions[mid].ts)) {
.eq => return self.transitions[mid].timetype.*,
.gt => left = mid + 1,
.lt => right = mid,
}
if (self.transitions[mid].ts > seconds) {
break :offset self.transitions[mid - 1].timetype.offset;
} else {
break :offset self.transitions[mid].timetype.offset;
}
};
return seconds + offset;
}
if (self.transitions[mid].ts > seconds) {
return self.transitions[mid - 1].timetype.*;
} else {
return self.transitions[mid].timetype.*;
}
}

test project {
Expand All @@ -253,9 +256,8 @@ test project {
defer tz.deinit(std.testing.allocator);

const timestamp: i64 = 1702475641;
const expected = timestamp + 3600;
const projected = tz.project(timestamp);
try std.testing.expectEqual(expected, projected);
const expected_offset: i32 = 3600;
try std.testing.expectEqual(expected_offset, tz.project(timestamp).offset);
}

test "slim" {
Expand Down

0 comments on commit 27d490c

Please sign in to comment.