From a89d22b291c4df103212756a27459bd9eb02a807 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Sat, 21 Sep 2024 00:47:21 -0400 Subject: [PATCH 1/4] add `time.time_to_rfc3339`, a printer to RFC3339 dates this is the counterpart to the existing parsing function `rfc3339_to_time_utc` and others. It prints the timestamp as a string, allocated dynamically. --- core/time/rfc3339.odin | 106 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index e4c6565d6..3ed149ad1 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -187,4 +187,108 @@ scan_digits :: proc(s: string, sep: string, count: int) -> (res: int, ok: bool) found_sep |= rune(s[count]) == v } return res, found_sep -} \ No newline at end of file +} + +/* +Serialize the timestamp as a RFC 3339 string. + +The boolean `ok` is false if the `time` is not a valid datetime, or if allocating the result string fails. + +**Inputs**: +- `utc_offset`: offset in minutes wrt UTC (ie. the timezone) +- `include_nanos`: whether to include nanoseconds in the result. +*/ +time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, allocator := context.allocator) -> (res: string, ok: bool) { + // convert to datetime + datetime := time_to_datetime(time) or_return + + if datetime.year < 0 || datetime.year >= 10_000 { return "", false } + + temp_string := [30]u8{} + offset : uint = 0 // offset in temp_string + + print_as_fixed_int :: proc(dst: []u8, offset: ^uint, width: i8, i: i64) { + i := i + width := width + for digit_idx in 0.. (res: i64, n_digits: i8) { + res = n + n_digits = 9 + for res % 10 == 0 { + res = res / 10 + n_digits -= 1 + } + return + } + + // pre-epoch times: turn, say, -400ms to +600ms for display + nanos := time._nsec % 1_000_000_000 + if nanos < 0 { + nanos += 1_000_000_000 + } + + if nanos != 0 && include_nanos { + temp_string[offset] = '.' + offset += 1 + + // remove trailing zeroes + nanos_nonzero, n_digits := strip_trailing_zeroes_nanos(nanos) + assert(nanos_nonzero != 0) + + // write digits, right-to-left + for digit_idx : i8 = n_digits-1; digit_idx >= 0; digit_idx -= 1 { + digit := u8(nanos_nonzero % 10) + temp_string[offset + uint(digit_idx)] = '0' + u8(digit) + nanos_nonzero /= 10 + } + offset += uint(n_digits) + } + + if utc_offset == 0 { + temp_string[offset] = 'Z' + offset += 1 + } else { + temp_string[offset] = utc_offset > 0 ? '+' : '-' + offset += 1; + utc_offset := abs(utc_offset) + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset / 60)) + temp_string[offset] = ':' + offset += 1 + print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset % 60)) + } + + res_as_slice, res_alloc := make_slice([]u8, len=offset, allocator = allocator) + if res_alloc != nil { + return "", false + } + + copy(res_as_slice, temp_string[:offset]) + + return string(res_as_slice), true +} From d08b3d3b82145faefe315d68350878c61b218b94 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Sat, 21 Sep 2024 00:48:39 -0400 Subject: [PATCH 2/4] add tests for time.time_to_rfc3339 --- tests/core/time/test_core_time.odin | 31 ++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index 51955b1c4..2ad3690d9 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -196,6 +196,35 @@ test_parse_rfc3339_string :: proc(t: ^testing.T) { } } +@test +test_print_rfc3339 :: proc(t: ^testing.T) { + TestCase :: struct { + printed: string, + time: i64, + utc_offset: int + }; + + tests :: [?]TestCase { + {"1985-04-12T23:20:50.52Z", 482196050520000000, 0}, + {"1985-04-12T23:20:50.52001905Z", 482196050520019050, 0}, + {"1996-12-19T16:39:57-08:00", 851013597000000000, -480}, + {"1996-12-20T00:39:57Z", 851042397000000000, 0}, + {"1937-01-01T12:00:27.87+00:20", -1041335972130000000, +20}, + }; + + for test in tests { + timestamp := time.Time { _nsec = test.time } + printed_timestamp, ok := time.time_to_rfc3339(time=timestamp, utc_offset=test.utc_offset) + defer delete_string(printed_timestamp) + + testing.expect(t, ok, "expected printing to work fine") + + testing.expectf( + t, printed_timestamp == test.printed, + "expected is %w, printed is %w", test.printed, printed_timestamp) + } +} + @test test_parse_iso8601_string :: proc(t: ^testing.T) { for test in iso8601_tests { @@ -318,4 +347,4 @@ date_component_roundtrip_test :: proc(t: ^testing.T, moment: dt.DateTime) { "Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss, ) -} \ No newline at end of file +} From 32e13f17aea7068b5621d21f97ef820c8dae16a4 Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Sat, 21 Sep 2024 21:08:35 -0400 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: flysand7 --- core/time/rfc3339.odin | 6 +++--- tests/core/time/test_core_time.odin | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index 3ed149ad1..9d816b3fa 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -204,8 +204,8 @@ time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, if datetime.year < 0 || datetime.year >= 10_000 { return "", false } - temp_string := [30]u8{} - offset : uint = 0 // offset in temp_string + temp_string := [36]u8{} + offset : uint = 0 print_as_fixed_int :: proc(dst: []u8, offset: ^uint, width: i8, i: i64) { i := i @@ -275,7 +275,7 @@ time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, offset += 1 } else { temp_string[offset] = utc_offset > 0 ? '+' : '-' - offset += 1; + offset += 1 utc_offset := abs(utc_offset) print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset / 60)) temp_string[offset] = ':' diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index 2ad3690d9..23044b72f 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -201,7 +201,7 @@ test_print_rfc3339 :: proc(t: ^testing.T) { TestCase :: struct { printed: string, time: i64, - utc_offset: int + utc_offset: int, }; tests :: [?]TestCase { @@ -221,7 +221,8 @@ test_print_rfc3339 :: proc(t: ^testing.T) { testing.expectf( t, printed_timestamp == test.printed, - "expected is %w, printed is %w", test.printed, printed_timestamp) + "expected is %w, printed is %w", test.printed, printed_timestamp, + ) } } From a1349d877601df166a6b4bb8a1a38126802dcc9d Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Sun, 22 Sep 2024 00:08:07 -0400 Subject: [PATCH 4/4] fix vet warnings --- core/time/rfc3339.odin | 4 +++- tests/core/time/test_core_time.odin | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index 9d816b3fa..20e8ea0bb 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -199,6 +199,8 @@ The boolean `ok` is false if the `time` is not a valid datetime, or if allocatin - `include_nanos`: whether to include nanoseconds in the result. */ time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, allocator := context.allocator) -> (res: string, ok: bool) { + utc_offset := utc_offset + // convert to datetime datetime := time_to_datetime(time) or_return @@ -276,7 +278,7 @@ time_to_rfc3339 :: proc(time: Time, utc_offset : int = 0, include_nanos := true, } else { temp_string[offset] = utc_offset > 0 ? '+' : '-' offset += 1 - utc_offset := abs(utc_offset) + utc_offset = abs(utc_offset) print_as_fixed_int(temp_string[:], &offset, 2, i64(utc_offset / 60)) temp_string[offset] = ':' offset += 1 diff --git a/tests/core/time/test_core_time.odin b/tests/core/time/test_core_time.odin index 23044b72f..424111aa3 100644 --- a/tests/core/time/test_core_time.odin +++ b/tests/core/time/test_core_time.odin @@ -202,7 +202,7 @@ test_print_rfc3339 :: proc(t: ^testing.T) { printed: string, time: i64, utc_offset: int, - }; + } tests :: [?]TestCase { {"1985-04-12T23:20:50.52Z", 482196050520000000, 0}, @@ -210,7 +210,7 @@ test_print_rfc3339 :: proc(t: ^testing.T) { {"1996-12-19T16:39:57-08:00", 851013597000000000, -480}, {"1996-12-20T00:39:57Z", 851042397000000000, 0}, {"1937-01-01T12:00:27.87+00:20", -1041335972130000000, +20}, - }; + } for test in tests { timestamp := time.Time { _nsec = test.time }