Browse Source

Change Ordinal from int to i64

Jeroen van Rijn 1 year ago
parent
commit
9c144dd24f

+ 18 - 27
core/time/datetime/constants.odin

@@ -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}

+ 29 - 15
core/time/datetime/datetime.odin

@@ -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
 

+ 9 - 9
core/time/datetime/internal.odin

@@ -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

+ 3 - 3
core/time/datetime/validation.odin

@@ -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

+ 28 - 28
core/time/rfc3339.odin

@@ -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)

+ 5 - 2
core/time/time.odin

@@ -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)
 }
 

+ 4 - 3
tests/core/time/test_core_time.odin

@@ -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)
 }