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

View File

@@ -2,23 +2,14 @@ package datetime
// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian)
// | Midnight Monday, January 3, 1 A.D. (Julian)
Ordinal :: int
Ordinal :: i64
EPOCH :: Ordinal(1)
// Minimum and maximum dates and ordinals. Chosen for safe roundtripping.
when size_of(int) == 4 {
MIN_DATE :: Date{year = -5_879_608, month = 1, day = 1}
MAX_DATE :: Date{year = 5_879_608, month = 12, day = 31}
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)
}
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 {
None,
@@ -34,16 +25,16 @@ Error :: enum {
}
Date :: struct {
year: int,
month: int,
day: int,
year: i64,
month: i64,
day: i64,
}
Time :: struct {
hour: int,
minute: int,
second: int,
nano: int,
hour: i64,
minute: i64,
second: i64,
nano: i64,
}
DateTime :: struct {
@@ -52,12 +43,12 @@ DateTime :: struct {
}
Delta :: struct {
days: int,
seconds: int,
nanos: 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: i64, // that are then normalized within their respective ranges.
nanos: i64,
}
Month :: enum int {
Month :: enum i8 {
January = 1,
February,
March,
@@ -72,7 +63,7 @@ Month :: enum int {
December,
}
Weekday :: enum int {
Weekday :: enum i8 {
Sunday = 0,
Monday,
Tuesday,
@@ -83,4 +74,4 @@ Weekday :: enum int {
}
@(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}

View File

@@ -8,27 +8,41 @@ package datetime
import "base:intrinsics"
// Procedures that return an Ordinal
date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal, err: Error) {
validate(date) or_return
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})
}
// Procedures that return a Date
ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date, err: Error) {
validate(ordinal) or_return
return unsafe_ordinal_to_date(ordinal), .None
}
components_to_date :: proc "contextless" (year, month, day: int) -> (date: Date, err: Error) {
date = Date{year, month, day}
components_to_date :: proc "contextless" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) {
date = Date{i64(year), i64(month), i64(day)}
validate(date) or_return
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) {
d := ordinal_to_date(ordinal) or_return
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}
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 += days
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
r: int
r: i64
datetime.hour, r = divmod(sum_delta.seconds, 3600)
datetime.minute, datetime.second = divmod(r, 60)
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}
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
ord := unsafe_date_to_ordinal(date)
@@ -108,39 +122,39 @@ day_number :: proc "contextless" (date: Date) -> (day_number: int, err: Error) {
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
validate(date) or_return
delta := sub(date, Date{date.year, 12, 31}) or_return
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.
validate(Date{year, month, 1}) or_return
month_days := MONTH_DAYS
day = month_days[month]
day = i64(month_days[month])
if month == 2 && is_leap_year(year) {
day += 1
}
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}
validate(new_year) or_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}
validate(year_end) or_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)
days := 366 if is_leap else 365
@@ -154,7 +168,7 @@ year_range :: proc (year: int, allocator := context.allocator) -> (range: []Date
i := 0
for month in 1..=len(month_days) {
for day in 1..=month_days[month] {
range[i] = Date{year, month, day}
range[i], _ = components_to_date(year, month, day)
i += 1
}
}
@@ -167,7 +181,7 @@ normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err:
// Add original seconds to rolled over seconds.
seconds += delta.seconds
days: int
days: i64
// Distribute seconds into number of days and remaining seconds.
days, seconds = divmod(seconds, 24 * 3600)
@@ -213,7 +227,7 @@ unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal)
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
d0 := ordinal - EPOCH

View File

@@ -4,7 +4,7 @@ package datetime
import "base:intrinsics"
sign :: proc "contextless" (v: int) -> (res: int) {
sign :: proc "contextless" (v: i64) -> (res: i64) {
if v == 0 {
return 0
} else if v > 0 {
@@ -37,7 +37,7 @@ floor_div :: proc "contextless" (x, y: $T) -> (res: T)
}
// 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 {
return x
}
@@ -45,12 +45,12 @@ interval_mod :: proc "contextless" (x, a, b: int) -> (res: int) {
}
// 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
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 {
return x
}
@@ -59,18 +59,18 @@ gcd :: proc "contextless" (x, y: int) -> (res: int) {
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)
}
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 {
res += f(idx)
}
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
for idx := i; cond(idx); idx += 1 {
res *= f(idx)
@@ -78,7 +78,7 @@ product :: proc "contextless" (i: int, f: proc "contextless" (n: int) -> int, co
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
for !cond(k) {
k += 1
@@ -86,7 +86,7 @@ smallest :: proc "contextless" (k: int, cond: proc "contextless" (n: int) -> boo
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
for !cond(k) {
k -= 1

View File

@@ -1,7 +1,7 @@
package datetime
// 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)
}
@@ -9,7 +9,7 @@ validate_date :: proc "contextless" (date: Date) -> (err: Error) {
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 {
return .Invalid_Year
}
@@ -23,7 +23,7 @@ validate_year_month_day :: proc "contextless" (year, month, day: int) -> (err: E
days_this_month = 29
}
if day < 1 || day > days_this_month {
if day < 1 || day > i64(days_this_month) {
return .Invalid_Day
}
return .None

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.
// 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) {
moment, offset, count := rfc3339_to_components(rfc_datetime)
moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime)
if count == 0 {
return
}
// Leap second handling
if moment.minute == 59 && moment.second == 60 {
moment.second = 59
if is_leap != nil {
is_leap^ = true
}
if is_leap != nil {
is_leap^ = leap_second
}
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.
// 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
rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, consumed: int) {
count: int
moment, offset, ok := _rfc3339_to_components(rfc_datetime, &count)
rfc3339_to_components :: proc(rfc_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) {
moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime)
if !ok {
return
}
return moment, offset, count
return moment, offset, leap_second, count
}
// 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
@(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
(len(rfc_datetime) >= 20) or_return
// Scan and eat YYYY-MM-DD[Tt]
res.year = scan_digits(rfc_datetime[0:], "-", 4) or_return
res.month = scan_digits(rfc_datetime[5:], "-", 2) or_return
res.day = scan_digits(rfc_datetime[8:], "Tt", 2) or_return
// Scan and eat HH:MM:SS, leave separator
res.hour = scan_digits(rfc_datetime[11:], ":", 2) or_return
res.minute = scan_digits(rfc_datetime[14:], ":", 2) or_return
res.second = scan_digits(rfc_datetime[17:], "", 2) or_return
count := 19
// Scan and eat YYYY-MM-DD[Tt], then scan and eat HH:MM:SS, leave separator
year := scan_digits(rfc_datetime[0:], "-", 4) or_return
month := scan_digits(rfc_datetime[5:], "-", 2) or_return
day := scan_digits(rfc_datetime[8:], "Tt", 2) or_return
hour := scan_digits(rfc_datetime[11:], ":", 2) or_return
minute := scan_digits(rfc_datetime[14:], ":", 2) or_return
second := scan_digits(rfc_datetime[17:], "", 2) or_return
nanos := 0
count := 19
if rfc_datetime[count] == '.' {
// Scan hundredths. The string must be at least 4 bytes long (.hhZ)
(len(rfc_datetime[count:]) >= 4) or_return
hundredths := scan_digits(rfc_datetime[count+1:], "", 2) or_return
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
@@ -95,11 +99,7 @@ _rfc3339_to_components :: proc(rfc_datetime: string, consume_count: ^int = nil)
utc_offset *= -1 if rfc_datetime[count] == '-' else 1
count += 6
}
if consume_count != nil {
consume_count^ = count
}
return res, utc_offset, true
return res, utc_offset, count, is_leap, true
}
@(private)

View File

@@ -357,8 +357,11 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon
return
}
components_to_time :: proc "contextless" (year, month, day, hour, minute, second: int, nsec := int(0)) -> (t: Time, ok: bool) {
this_date := dt.DateTime{date={year, month, day}, time={hour, minute, second, nsec}}
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, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec)
if err != .None {
return
}
return compound_to_time(this_date)
}

View File

@@ -155,7 +155,8 @@ test_component_to_time_roundtrip :: proc(t: ^testing.T) {
days += 1
}
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",
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.hour == hh && moment.minute == mm && moment.second == ss
ok = moment.year == i64(YYYY) && moment.month == i64(MM) && moment.day == i64(DD)
ok &= moment.hour == i64(hh) && moment.minute == i64(mm) && moment.second == i64(ss)
expect(t, ok, expected)
}