Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ISO8601 duration parser #39

Merged
merged 13 commits into from
Nov 4, 2024
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ Types of changes

## Unreleased

### Added

- ISO8601 duration parser

### Fixed

- default formatter of the Duration type to 'ISO8601 duration'-like string

## 2024-10-28, v0.4.1

### Added
Expand Down
2 changes: 1 addition & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const std = @import("std");
const builtin = @import("builtin");
const log = std.log.scoped(.zdt_build);

const zdt_version = std.SemanticVersion{ .major = 0, .minor = 4, .patch = 1 };
const zdt_version = std.SemanticVersion{ .major = 0, .minor = 4, .patch = 2 };

const example_files = [_][]const u8{
"ex_demo",
Expand Down
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = "zdt",
.version = "0.4.1",
.version = "0.4.2",
.paths = .{
"zdt.zig",
"lib", // anything from lib
Expand Down
4 changes: 2 additions & 2 deletions examples/ex_demo.zig
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ pub fn main() !void {
"Wall clock time difference: {s}\nAbsolute time difference: {s}\n",
.{ wall_diff, abs_diff },
);
// Wall clock time difference: PT09H00M00S
// Absolute time difference: PT00H00M00S
// Wall clock time difference: PT9H0M0S
// Absolute time difference: PT0H0M0S
}
9 changes: 9 additions & 0 deletions examples/ex_duration.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,26 @@ pub fn main() !void {
const now_utc = zdt.Datetime.nowUTC();
println("now, UTC : {s}", .{now_utc});
const past_midnight = try now_utc.floorTo(zdt.Duration.Timespan.day);

// difference between two datetimes expressed as Duration:
println(
"{d:.3} seconds have passed since midnight ({s})\n",
.{ now_utc.diff(past_midnight).totalSeconds(), past_midnight },
);

// Durations from Timespans:
const tomorrow = try now_utc.add(zdt.Duration.fromTimespanMultiple(1, zdt.Duration.Timespan.day));
println("tomorrow, same time : {s}", .{tomorrow});
println("tomorrow, same time, is {d} seconds away from now\n", .{tomorrow.diff(now_utc).asSeconds()});

// Timespan units range from nanoseconds to weeks:
const two_weeks_ago = try now_utc.sub(zdt.Duration.fromTimespanMultiple(2, zdt.Duration.Timespan.week));
println("two weeks ago : {s}", .{two_weeks_ago});

// ISO8601-duration parser on-board:
const one_wk_one_h = try zdt.Duration.fromISO8601Duration("P7DT1H");
const in_a_week = try now_utc.add(one_wk_one_h);
println("in a week and an hour : {s}", .{in_a_week});
}

fn println(comptime fmt: []const u8, args: anytype) void {
Expand Down
40 changes: 21 additions & 19 deletions lib/Datetime.zig
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,8 @@ const tzOpts = enum {
utc_offset,
};

// helper to specify either a time zone or a UTC offset
const tz_options = union(tzOpts) {
/// helper to specify either a time zone or a UTC offset:
pub const tz_options = union(tzOpts) {
tz: *const Timezone,
utc_offset: UTCoffset,
};
Expand Down Expand Up @@ -349,12 +349,13 @@ pub fn fromFields(fields: Fields) ZdtError!Datetime {
}
}

// If both guesses did not succeed, we have a non-existent datetime.
// this should give an error.
if (!dt_eq_guess_1 and !dt_eq_guess_2) return ZdtError.NonexistentDatetime;
// If we came here, either guess 1 or guess 2 is correct; guess 1 takes precedence.
if (dt_eq_guess_1) return dt_guess_1;
if (dt_eq_guess_2) return dt_guess_2;

// If we came here, either guess 1 or guess 2 is correct.
if (dt_eq_guess_1) return dt_guess_1 else return dt_guess_2;
// If both guesses did NOT succeed, we have a non-existent datetime.
// this should give an error.
return ZdtError.NonexistentDatetime;
}

/// Make a fields struct from a datetime.
Expand Down Expand Up @@ -485,7 +486,7 @@ pub fn isAware(dt: *const Datetime) bool {

/// true if no offset from UTC is defined
pub fn isNaive(dt: *const Datetime) bool {
return !dt.isAware();
return dt.utc_offset == null;
}

/// returns true if a datetime is located in daylight saving time.
Expand Down Expand Up @@ -627,8 +628,8 @@ pub fn diff(this: Datetime, other: Datetime) Duration {
return .{ .__sec = s, .__nsec = @intCast(ns) };
}

/// Calculate wall time difference between two timezone-aware datetimes.
/// If one of the datetimes is naive (no tz specified), this is considered an error.
/// Calculate wall time difference between two aware datetimes.
/// If one of the datetimes is naive (no time zone specified), this is considered an error.
///
/// Result is ('this' wall time - 'other' wall time) as a Duration.
pub fn diffWall(this: Datetime, other: Datetime) !Duration {
Expand All @@ -647,10 +648,11 @@ pub fn diffWall(this: Datetime, other: Datetime) !Duration {
}

/// Validate a datetime in terms of leap seconds;
/// checks if the datetime could be a leap second if .second is == 60.
/// Returns an error if the datetime has seconds == 60 but is NOT a leap second datetime.
pub fn validateLeap(this: *const Datetime) !void {
if (this.second != 60) return;
if (!cal.mightBeLeap(this.unix_sec)) return error.SecondOutOfRange;
if (cal.mightBeLeap(this.unix_sec)) return;
return error.SecondOutOfRange;
}

/// Difference in leap seconds between two datetimes.
Expand Down Expand Up @@ -780,17 +782,17 @@ pub fn fromString(string: []const u8, directives: []const u8) !Datetime {
return try str.tokenizeAndParse(string, directives);
}

/// Make a datetime from a string with an ISO8601-compatibel format.
/// Make a datetime from a string with an ISO8601-compatible format.
pub fn fromISO8601(string: []const u8) !Datetime {
// 9 digits of fractional seconds and hh:mm:ss UTC offset: 38 characters
// 9 digits of fractional seconds and ±hh:mm:ss UTC offset: 38 characters
if (string.len > 38)
return error.InvalidFormat;
// last character must be Z (UTC) or a digit
if (string[string.len - 1] != 'Z' and !std.ascii.isDigit(string[string.len - 1])) {
return error.InvalidFormat;
// last character must be Z (UTC) or a digit, otherwise the input is not ISO8601-compatible
if (string[string.len - 1] == 'Z' or std.ascii.isDigit(string[string.len - 1])) {
var idx: usize = 0; // assume datetime starts at beginning of string
return try Datetime.fromFields(try str.parseISO8601(string, &idx));
}
var idx: usize = 0; // assume datetime starts at beginning of string
return try Datetime.fromFields(try str.parseISO8601(string, &idx));
return error.InvalidFormat;
}

/// Format a datetime into a string
Expand Down
Loading