Change Ordinal from int to i64

This commit is contained in:
Jeroen van Rijn
2024-03-20 17:56:22 +01:00
parent 07ef969546
commit 9c144dd24f
7 changed files with 96 additions and 87 deletions
+18 -27
View File
@@ -2,23 +2,14 @@ package datetime
// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian) // Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian)
// | Midnight Monday, January 3, 1 A.D. (Julian) // | Midnight Monday, January 3, 1 A.D. (Julian)
Ordinal :: int Ordinal :: i64
EPOCH :: Ordinal(1) EPOCH :: Ordinal(1)
// Minimum and maximum dates and ordinals. Chosen for safe roundtripping. // Minimum and maximum dates and ordinals. Chosen for safe roundtripping.
when size_of(int) == 4 { MIN_DATE :: Date{year = -25_252_734_927_766_552, month = 1, day = 1}
MIN_DATE :: Date{year = -5_879_608, month = 1, day = 1} MAX_DATE :: Date{year = 25_252_734_927_766_552, month = 12, day = 31}
MAX_DATE :: Date{year = 5_879_608, month = 12, day = 31} MIN_ORD :: Ordinal(-9_223_372_036_854_775_234)
MAX_ORD :: Ordinal( 9_223_372_036_854_774_869)
MIN_ORD :: Ordinal(-2_147_483_090)
MAX_ORD :: Ordinal( 2_147_482_725)
} else {
MIN_DATE :: Date{year = -25_252_734_927_766_552, month = 1, day = 1}
MAX_DATE :: Date{year = 25_252_734_927_766_552, month = 12, day = 31}
MIN_ORD :: Ordinal(-9_223_372_036_854_775_234)
MAX_ORD :: Ordinal( 9_223_372_036_854_774_869)
}
Error :: enum { Error :: enum {
None, None,
@@ -34,16 +25,16 @@ Error :: enum {
} }
Date :: struct { Date :: struct {
year: int, year: i64,
month: int, month: i64,
day: int, day: i64,
} }
Time :: struct { Time :: struct {
hour: int, hour: i64,
minute: int, minute: i64,
second: int, second: i64,
nano: int, nano: i64,
} }
DateTime :: struct { DateTime :: struct {
@@ -52,12 +43,12 @@ DateTime :: struct {
} }
Delta :: struct { Delta :: struct {
days: int, days: i64, // These are all i64 because we can also use it to add a number of seconds or nanos to a moment,
seconds: int, seconds: i64, // that are then normalized within their respective ranges.
nanos: int, nanos: i64,
} }
Month :: enum int { Month :: enum i8 {
January = 1, January = 1,
February, February,
March, March,
@@ -72,7 +63,7 @@ Month :: enum int {
December, December,
} }
Weekday :: enum int { Weekday :: enum i8 {
Sunday = 0, Sunday = 0,
Monday, Monday,
Tuesday, Tuesday,
@@ -83,4 +74,4 @@ Weekday :: enum int {
} }
@(private) @(private)
MONTH_DAYS :: [?]int{-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} MONTH_DAYS :: [?]i8{-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+29 -15
View File
@@ -8,27 +8,41 @@ package datetime
import "base:intrinsics" import "base:intrinsics"
// Procedures that return an Ordinal // Procedures that return an Ordinal
date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) { date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) {
validate(date) or_return validate(date) or_return
return unsafe_date_to_ordinal(date), .None return unsafe_date_to_ordinal(date), .None
} }
components_to_ordinal :: proc "contextless" (year, month, day: int) -> (ordinal: Ordinal, err: Error) { components_to_ordinal :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) {
return date_to_ordinal(Date{year, month, day}) return date_to_ordinal(Date{year, month, day})
} }
// Procedures that return a Date // Procedures that return a Date
ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) { ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) {
validate(ordinal) or_return validate(ordinal) or_return
return unsafe_ordinal_to_date(ordinal), .None return unsafe_ordinal_to_date(ordinal), .None
} }
components_to_date :: proc "contextless" (year, month, day: int) -> (date: Date, err: Error) { components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) {
date = Date{year, month, day} date = Date{i64(year), i64(month), i64(day)}
validate(date) or_return validate(date) or_return
return date, .None return date, .None
} }
components_to_time :: proc "contextless" (#any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (time: Time, err: Error) {
time = Time{i64(hour), i64(minute), i64(second), i64(nanos)}
validate(time) or_return
return time, .None
}
components_to_datetime :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nanos := i64(0)) -> (datetime: DateTime, err: Error) {
date := components_to_date(year, month, day) or_return
time := components_to_time(hour, minute, second, nanos) or_return
return {date, time}, .None
}
ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) { ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) {
d := ordinal_to_date(ordinal) or_return d := ordinal_to_date(ordinal) or_return
return {Date(d), {}}, .None return {Date(d), {}}, .None
@@ -67,7 +81,7 @@ subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error
} }
sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas} sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas}
add_days_to_date :: proc "contextless" (a: Date, days: int) -> (date: Date, err: Error) { add_days_to_date :: proc "contextless" (a: Date, days: i64) -> (date: Date, err: Error) {
ord := date_to_ordinal(a) or_return ord := date_to_ordinal(a) or_return
ord += days ord += days
return ordinal_to_date(ord) return ordinal_to_date(ord)
@@ -91,7 +105,7 @@ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (date
datetime.date = ordinal_to_date(sum_delta.days) or_return datetime.date = ordinal_to_date(sum_delta.days) or_return
r: int r: i64
datetime.hour, r = divmod(sum_delta.seconds, 3600) datetime.hour, r = divmod(sum_delta.seconds, 3600)
datetime.minute, datetime.second = divmod(r, 60) datetime.minute, datetime.second = divmod(r, 60)
datetime.nano = sum_delta.nanos datetime.nano = sum_delta.nanos
@@ -100,7 +114,7 @@ add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (date
} }
add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime} add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime}
day_number :: proc "contextless" (date: Date) -> (day_number: int, err: Error) { day_number :: proc "contextless" (date: Date) -> (day_number: i64, err: Error) {
validate(date) or_return validate(date) or_return
ord := unsafe_date_to_ordinal(date) ord := unsafe_date_to_ordinal(date)
@@ -108,39 +122,39 @@ day_number :: proc "contextless" (date: Date) -> (day_number: int, err: Error) {
return return
} }
days_remaining :: proc "contextless" (date: Date) -> (days_remaining: int, err: Error) { days_remaining :: proc "contextless" (date: Date) -> (days_remaining: i64, err: Error) {
// Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year // Alternative formulation `day_number` subtracted from 365 or 366 depending on leap year
validate(date) or_return validate(date) or_return
delta := sub(date, Date{date.year, 12, 31}) or_return delta := sub(date, Date{date.year, 12, 31}) or_return
return delta.days, .None return delta.days, .None
} }
last_day_of_month :: proc "contextless" (year, month: int) -> (day: int, err: Error) { last_day_of_month :: proc "contextless" (year, month: i64) -> (day: i64, err: Error) {
// Not using formula 2.27 from the book. This is far simpler and gives the same answer. // Not using formula 2.27 from the book. This is far simpler and gives the same answer.
validate(Date{year, month, 1}) or_return validate(Date{year, month, 1}) or_return
month_days := MONTH_DAYS month_days := MONTH_DAYS
day = month_days[month] day = i64(month_days[month])
if month == 2 && is_leap_year(year) { if month == 2 && is_leap_year(year) {
day += 1 day += 1
} }
return return
} }
new_year :: proc "contextless" (year: int) -> (new_year: Date, err: Error) { new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) {
new_year = {year, 1, 1} new_year = {year, 1, 1}
validate(new_year) or_return validate(new_year) or_return
return return
} }
year_end :: proc "contextless" (year: int) -> (year_end: Date, err: Error) { year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) {
year_end = {year, 12, 31} year_end = {year, 12, 31}
validate(year_end) or_return validate(year_end) or_return
return return
} }
year_range :: proc (year: int, allocator := context.allocator) -> (range: []Date) { year_range :: proc (#any_int year: i64, allocator := context.allocator) -> (range: []Date) {
is_leap := is_leap_year(year) is_leap := is_leap_year(year)
days := 366 if is_leap else 365 days := 366 if is_leap else 365
@@ -154,7 +168,7 @@ year_range :: proc (year: int, allocator := context.allocator) -> (range: []Date
i := 0 i := 0
for month in 1..=len(month_days) { for month in 1..=len(month_days) {
for day in 1..=month_days[month] { for day in 1..=month_days[month] {
range[i] = Date{year, month, day} range[i], _ = components_to_date(year, month, day)
i += 1 i += 1
} }
} }
@@ -167,7 +181,7 @@ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err:
// Add original seconds to rolled over seconds. // Add original seconds to rolled over seconds.
seconds += delta.seconds seconds += delta.seconds
days: int days: i64
// Distribute seconds into number of days and remaining seconds. // Distribute seconds into number of days and remaining seconds.
days, seconds = divmod(seconds, 24 * 3600) days, seconds = divmod(seconds, 24 * 3600)
@@ -213,7 +227,7 @@ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal)
return return
} }
unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: int, day_ordinal: int) { unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) {
// Days after epoch // Days after epoch
d0 := ordinal - EPOCH d0 := ordinal - EPOCH
+9 -9
View File
@@ -4,7 +4,7 @@ package datetime
import "base:intrinsics" import "base:intrinsics"
sign :: proc "contextless" (v: int) -> (res: int) { sign :: proc "contextless" (v: i64) -> (res: i64) {
if v == 0 { if v == 0 {
return 0 return 0
} else if v > 0 { } else if v > 0 {
@@ -37,7 +37,7 @@ floor_div :: proc "contextless" (x, y: $T) -> (res: T)
} }
// Half open: x mod [1..b] // Half open: x mod [1..b]
interval_mod :: proc "contextless" (x, a, b: int) -> (res: int) { interval_mod :: proc "contextless" (x, a, b: i64) -> (res: i64) {
if a == b { if a == b {
return x return x
} }
@@ -45,12 +45,12 @@ interval_mod :: proc "contextless" (x, a, b: int) -> (res: int) {
} }
// x mod [1..b] // x mod [1..b]
adjusted_remainder :: proc "contextless" (x, b: int) -> (res: int) { adjusted_remainder :: proc "contextless" (x, b: i64) -> (res: i64) {
m := x %% b m := x %% b
return b if m == 0 else m return b if m == 0 else m
} }
gcd :: proc "contextless" (x, y: int) -> (res: int) { gcd :: proc "contextless" (x, y: i64) -> (res: i64) {
if y == 0 { if y == 0 {
return x return x
} }
@@ -59,18 +59,18 @@ gcd :: proc "contextless" (x, y: int) -> (res: int) {
return gcd(y, m) return gcd(y, m)
} }
lcm :: proc "contextless" (x, y: int) -> (res: int) { lcm :: proc "contextless" (x, y: i64) -> (res: i64) {
return x * y / gcd(x, y) return x * y / gcd(x, y)
} }
sum :: proc "contextless" (i: int, f: proc "contextless" (n: int) -> int, cond: proc "contextless" (n: int) -> bool) -> (res: int) { sum :: proc "contextless" (i: i64, f: proc "contextless" (n: i64) -> i64, cond: proc "contextless" (n: i64) -> bool) -> (res: i64) {
for idx := i; cond(idx); idx += 1 { for idx := i; cond(idx); idx += 1 {
res += f(idx) res += f(idx)
} }
return return
} }
product :: proc "contextless" (i: int, f: proc "contextless" (n: int) -> int, cond: proc "contextless" (n: int) -> bool) -> (res: int) { product :: proc "contextless" (i: i64, f: proc "contextless" (n: i64) -> i64, cond: proc "contextless" (n: i64) -> bool) -> (res: i64) {
res = 1 res = 1
for idx := i; cond(idx); idx += 1 { for idx := i; cond(idx); idx += 1 {
res *= f(idx) res *= f(idx)
@@ -78,7 +78,7 @@ product :: proc "contextless" (i: int, f: proc "contextless" (n: int) -> int, co
return return
} }
smallest :: proc "contextless" (k: int, cond: proc "contextless" (n: int) -> bool) -> (d: int) { smallest :: proc "contextless" (k: i64, cond: proc "contextless" (n: i64) -> bool) -> (d: i64) {
k := k k := k
for !cond(k) { for !cond(k) {
k += 1 k += 1
@@ -86,7 +86,7 @@ smallest :: proc "contextless" (k: int, cond: proc "contextless" (n: int) -> boo
return k return k
} }
biggest :: proc "contextless" (k: int, cond: proc "contextless" (n: int) -> bool) -> (d: int) { biggest :: proc "contextless" (k: i64, cond: proc "contextless" (n: i64) -> bool) -> (d: i64) {
k := k k := k
for !cond(k) { for !cond(k) {
k -= 1 k -= 1
+3 -3
View File
@@ -1,7 +1,7 @@
package datetime package datetime
// Validation helpers // Validation helpers
is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { is_leap_year :: proc "contextless" (#any_int year: i64) -> (leap: bool) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
} }
@@ -9,7 +9,7 @@ validate_date :: proc "contextless" (date: Date) -> (err: Error) {
return validate(date.year, date.month, date.day) return validate(date.year, date.month, date.day)
} }
validate_year_month_day :: proc "contextless" (year, month, day: int) -> (err: Error) { validate_year_month_day :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (err: Error) {
if year < MIN_DATE.year || year > MAX_DATE.year { if year < MIN_DATE.year || year > MAX_DATE.year {
return .Invalid_Year return .Invalid_Year
} }
@@ -23,7 +23,7 @@ validate_year_month_day :: proc "contextless" (year, month, day: int) -> (err: E
days_this_month = 29 days_this_month = 29
} }
if day < 1 || day > days_this_month { if day < 1 || day > i64(days_this_month) {
return .Invalid_Day return .Invalid_Day
} }
return .None return .None
+28 -28
View File
@@ -22,17 +22,13 @@ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res:
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. // Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
// Leap seconds are smeared into 23:59:59. // Leap seconds are smeared into 23:59:59.
rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) {
moment, offset, count := rfc3339_to_components(rfc_datetime) moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime)
if count == 0 { if count == 0 {
return return
} }
// Leap second handling if is_leap != nil {
if moment.minute == 59 && moment.second == 60 { is_leap^ = leap_second
moment.second = 59
if is_leap != nil {
is_leap^ = true
}
} }
if _res, ok := datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, moment.nano); !ok { if _res, ok := datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, moment.nano); !ok {
@@ -45,40 +41,48 @@ rfc3339_to_time_and_offset :: proc(rfc_datetime: string, is_leap: ^bool = nil) -
// Parses an RFC 3339 string and returns Time and a UTC offset in minutes. // Parses an RFC 3339 string and returns Time and a UTC offset in minutes.
// e.g. 1985-04-12T23:20:50.52Z // e.g. 1985-04-12T23:20:50.52Z
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given // Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, consumed: int) { rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) {
count: int moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime)
moment, offset, ok := _rfc3339_to_components(rfc_datetime, &count)
if !ok { if !ok {
return return
} }
return moment, offset, count return moment, offset, leap_second, count
} }
// Parses an RFC 3339 string and returns datetime.DateTime. // Parses an RFC 3339 string and returns datetime.DateTime.
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given // Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
@(private) @(private)
_rfc3339_to_components :: proc(rfc_datetime: string, consume_count: ^int = nil) -> (res: dt.DateTime, utc_offset: int, ok: bool) { _rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, consumed: int, is_leap: bool, ok: bool) {
// A compliant date is at minimum 20 characters long, e.g. YYYY-MM-DDThh:mm:ssZ // A compliant date is at minimum 20 characters long, e.g. YYYY-MM-DDThh:mm:ssZ
(len(rfc_datetime) >= 20) or_return (len(rfc_datetime) >= 20) or_return
// Scan and eat YYYY-MM-DD[Tt] // Scan and eat YYYY-MM-DD[Tt], then scan and eat HH:MM:SS, leave separator
res.year = scan_digits(rfc_datetime[0:], "-", 4) or_return year := scan_digits(rfc_datetime[0:], "-", 4) or_return
res.month = scan_digits(rfc_datetime[5:], "-", 2) or_return month := scan_digits(rfc_datetime[5:], "-", 2) or_return
res.day = scan_digits(rfc_datetime[8:], "Tt", 2) or_return day := scan_digits(rfc_datetime[8:], "Tt", 2) or_return
hour := scan_digits(rfc_datetime[11:], ":", 2) or_return
// Scan and eat HH:MM:SS, leave separator minute := scan_digits(rfc_datetime[14:], ":", 2) or_return
res.hour = scan_digits(rfc_datetime[11:], ":", 2) or_return second := scan_digits(rfc_datetime[17:], "", 2) or_return
res.minute = scan_digits(rfc_datetime[14:], ":", 2) or_return nanos := 0
res.second = scan_digits(rfc_datetime[17:], "", 2) or_return count := 19
count := 19
if rfc_datetime[count] == '.' { if rfc_datetime[count] == '.' {
// Scan hundredths. The string must be at least 4 bytes long (.hhZ) // Scan hundredths. The string must be at least 4 bytes long (.hhZ)
(len(rfc_datetime[count:]) >= 4) or_return (len(rfc_datetime[count:]) >= 4) or_return
hundredths := scan_digits(rfc_datetime[count+1:], "", 2) or_return hundredths := scan_digits(rfc_datetime[count+1:], "", 2) or_return
count += 3 count += 3
nanos = 10_000_000 * hundredths
}
res.nano = 10_000_000 * hundredths // Leap second handling
if minute == 59 && second == 60 {
second = 59
is_leap = true
}
err: dt.Error
if res, err = dt.components_to_datetime(year, month, day, hour, minute, second, nanos); err != .None {
return {}, 0, 0, false, false
} }
// Scan UTC offset // Scan UTC offset
@@ -95,11 +99,7 @@ _rfc3339_to_components :: proc(rfc_datetime: string, consume_count: ^int = nil)
utc_offset *= -1 if rfc_datetime[count] == '-' else 1 utc_offset *= -1 if rfc_datetime[count] == '-' else 1
count += 6 count += 6
} }
return res, utc_offset, count, is_leap, true
if consume_count != nil {
consume_count^ = count
}
return res, utc_offset, true
} }
@(private) @(private)
+5 -2
View File
@@ -357,8 +357,11 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon
return return
} }
components_to_time :: proc "contextless" (year, month, day, hour, minute, second: int, nsec := int(0)) -> (t: Time, ok: bool) { components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_int day, #any_int hour, #any_int minute, #any_int second: i64, #any_int nsec := i64(0)) -> (t: Time, ok: bool) {
this_date := dt.DateTime{date={year, month, day}, time={hour, minute, second, nsec}} this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec)
if err != .None {
return
}
return compound_to_time(this_date) return compound_to_time(this_date)
} }
+4 -3
View File
@@ -155,7 +155,8 @@ test_component_to_time_roundtrip :: proc(t: ^testing.T) {
days += 1 days += 1
} }
for day in 1..=days { for day in 1..=days {
date_component_roundtrip_test(t, {{year, month, day}, {0, 0, 0, 0}}) d, _ := dt.components_to_datetime(year, month, day, 0, 0, 0, 0)
date_component_roundtrip_test(t, d)
} }
} }
} }
@@ -171,7 +172,7 @@ date_component_roundtrip_test :: proc(t: ^testing.T, moment: dt.DateTime) {
expected := fmt.tprintf("Expected %4d-%2d-%2d %2d:%2d:%2d, got %4d-%2d-%2d %2d:%2d:%2d", expected := fmt.tprintf("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) moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, YYYY, MM, DD, hh, mm, ss)
ok = moment.year == YYYY && moment.month == int(MM) && moment.day == DD ok = moment.year == i64(YYYY) && moment.month == i64(MM) && moment.day == i64(DD)
ok &= moment.hour == hh && moment.minute == mm && moment.second == ss ok &= moment.hour == i64(hh) && moment.minute == i64(mm) && moment.second == i64(ss)
expect(t, ok, expected) expect(t, ok, expected)
} }