mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-03 14:18:47 +00:00
Add WiP datetime package and tests.
A new package `core:time/datetime` has been added which can represent moments much further in the past and future than `core:time`. It is based on *the* reference work on the subject, Calendrical Calculations Ultimate Edition, Reingold & Dershowitz. More procedures will be added to it in the future, to for example calculate the 3rd Thursday in March to figure out holidays. The package has been tested for more than a year and can handle dates 25 quadrillion years into the past and future with 64-bit day ordinals, or 5 million with 32-bit ones. This also fixes a longstanding bug where converting between YYYY-MM:DD hh:mm:ss and `time.Time` and back could result in a mismatch. RFC 3339 timestamps can now also be parsed using the `core:time` package.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package datetime
|
||||
|
||||
// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian)
|
||||
// | Midnight Monday, January 3, 1 A.D. (Julian)
|
||||
Ordinal :: int
|
||||
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)
|
||||
}
|
||||
|
||||
Error :: enum {
|
||||
None,
|
||||
Invalid_Year,
|
||||
Invalid_Month,
|
||||
Invalid_Day,
|
||||
Invalid_Hour,
|
||||
Invalid_Minute,
|
||||
Invalid_Second,
|
||||
Invalid_Nano,
|
||||
Invalid_Ordinal,
|
||||
Invalid_Delta,
|
||||
}
|
||||
|
||||
Date :: struct {
|
||||
year: int,
|
||||
month: int,
|
||||
day: int,
|
||||
}
|
||||
|
||||
Time :: struct {
|
||||
hour: int,
|
||||
minute: int,
|
||||
second: int,
|
||||
nano: int,
|
||||
}
|
||||
|
||||
DateTime :: struct {
|
||||
using date: Date,
|
||||
using time: Time,
|
||||
}
|
||||
|
||||
Delta :: struct {
|
||||
days: int,
|
||||
seconds: int,
|
||||
nanos: int,
|
||||
}
|
||||
|
||||
Month :: enum int {
|
||||
January = 1,
|
||||
February,
|
||||
March,
|
||||
April,
|
||||
May,
|
||||
June,
|
||||
July,
|
||||
August,
|
||||
September,
|
||||
October,
|
||||
November,
|
||||
December,
|
||||
}
|
||||
|
||||
Weekday :: enum int {
|
||||
Sunday = 0,
|
||||
Monday,
|
||||
Tuesday,
|
||||
Wednesday,
|
||||
Thursday,
|
||||
Friday,
|
||||
Saturday,
|
||||
}
|
||||
|
||||
@(private)
|
||||
MONTH_DAYS :: [?]int{-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
Calendrical conversions using a proleptic Gregorian calendar.
|
||||
|
||||
Implemented using formulas from: Calendrical Calculations Ultimate Edition, Reingold & Dershowitz
|
||||
*/
|
||||
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) {
|
||||
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}
|
||||
validate(date) or_return
|
||||
return date, .None
|
||||
}
|
||||
|
||||
ordinal_to_datetime :: proc "contextless" (ordinal: Ordinal) -> (datetime: DateTime, err: Error) {
|
||||
d := ordinal_to_date(ordinal) or_return
|
||||
return {Date(d), {}}, .None
|
||||
}
|
||||
|
||||
day_of_week :: proc "contextless" (ordinal: Ordinal) -> (day: Weekday) {
|
||||
return Weekday((ordinal - EPOCH) %% 7)
|
||||
}
|
||||
|
||||
subtract_dates :: proc "contextless" (a, b: Date) -> (delta: Delta, err: Error) {
|
||||
ord_a := date_to_ordinal(a) or_return
|
||||
ord_b := date_to_ordinal(b) or_return
|
||||
|
||||
delta = Delta{days=ord_a - ord_b}
|
||||
return
|
||||
}
|
||||
|
||||
subtract_datetimes :: proc "contextless" (a, b: DateTime) -> (delta: Delta, err: Error) {
|
||||
ord_a := date_to_ordinal(a) or_return
|
||||
ord_b := date_to_ordinal(b) or_return
|
||||
|
||||
validate(a.time) or_return
|
||||
validate(b.time) or_return
|
||||
|
||||
seconds_a := a.hour * 3600 + a.minute * 60 + a.second
|
||||
seconds_b := b.hour * 3600 + b.minute * 60 + b.second
|
||||
|
||||
delta = Delta{ord_a - ord_b, seconds_a - seconds_b, a.nano - b.nano}
|
||||
return
|
||||
}
|
||||
|
||||
subtract_deltas :: proc "contextless" (a, b: Delta) -> (delta: Delta, err: Error) {
|
||||
delta = Delta{a.days - b.days, a.seconds - b.seconds, a.nanos - b.nanos}
|
||||
delta = normalize_delta(delta) or_return
|
||||
return
|
||||
}
|
||||
sub :: proc{subtract_datetimes, subtract_dates, subtract_deltas}
|
||||
|
||||
add_days_to_date :: proc "contextless" (a: Date, days: int) -> (date: Date, err: Error) {
|
||||
ord := date_to_ordinal(a) or_return
|
||||
ord += days
|
||||
return ordinal_to_date(ord)
|
||||
}
|
||||
|
||||
add_delta_to_date :: proc "contextless" (a: Date, delta: Delta) -> (date: Date, err: Error) {
|
||||
ord := date_to_ordinal(a) or_return
|
||||
// Because the input is a Date, we add only the days from the Delta.
|
||||
ord += delta.days
|
||||
return ordinal_to_date(ord)
|
||||
}
|
||||
|
||||
add_delta_to_datetime :: proc "contextless" (a: DateTime, delta: Delta) -> (datetime: DateTime, err: Error) {
|
||||
days := date_to_ordinal(a) or_return
|
||||
|
||||
a_seconds := a.hour * 3600 + a.minute * 60 + a.second
|
||||
a_delta := Delta{days=days, seconds=a_seconds, nanos=a.nano}
|
||||
|
||||
sum_delta := Delta{days=a_delta.days + delta.days, seconds=a_delta.seconds + delta.seconds, nanos=a_delta.nanos + delta.nanos}
|
||||
sum_delta = normalize_delta(sum_delta) or_return
|
||||
|
||||
datetime.date = ordinal_to_date(sum_delta.days) or_return
|
||||
|
||||
r: int
|
||||
datetime.hour, r = divmod(sum_delta.seconds, 3600)
|
||||
datetime.minute, datetime.second = divmod(r, 60)
|
||||
datetime.nano = sum_delta.nanos
|
||||
|
||||
return
|
||||
}
|
||||
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) {
|
||||
validate(date) or_return
|
||||
|
||||
ord := unsafe_date_to_ordinal(date)
|
||||
_, day_number = unsafe_ordinal_to_year(ord)
|
||||
return
|
||||
}
|
||||
|
||||
days_remaining :: proc "contextless" (date: Date) -> (days_remaining: int, 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) {
|
||||
// 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]
|
||||
if month == 2 && is_leap_year(year) {
|
||||
day += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
new_year :: proc "contextless" (year: int) -> (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 = {year, 12, 31}
|
||||
validate(year_end) or_return
|
||||
return
|
||||
}
|
||||
|
||||
year_range :: proc (year: int, allocator := context.allocator) -> (range: []Date) {
|
||||
is_leap := is_leap_year(year)
|
||||
|
||||
days := 366 if is_leap else 365
|
||||
range = make([]Date, days, allocator)
|
||||
|
||||
month_days := MONTH_DAYS
|
||||
if is_leap {
|
||||
month_days[2] = 29
|
||||
}
|
||||
|
||||
i := 0
|
||||
for month in 1..=len(month_days) {
|
||||
for day in 1..=month_days[month] {
|
||||
range[i] = Date{year, month, day}
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
normalize_delta :: proc "contextless" (delta: Delta) -> (normalized: Delta, err: Error) {
|
||||
// Distribute nanos into seconds and remainder
|
||||
seconds, nanos := divmod(delta.nanos, 1e9)
|
||||
|
||||
// Add original seconds to rolled over seconds.
|
||||
seconds += delta.seconds
|
||||
days: int
|
||||
|
||||
// Distribute seconds into number of days and remaining seconds.
|
||||
days, seconds = divmod(seconds, 24 * 3600)
|
||||
|
||||
// Add original days
|
||||
days += delta.days
|
||||
|
||||
if days <= MIN_ORD || days >= MAX_ORD {
|
||||
return {}, .Invalid_Delta
|
||||
}
|
||||
return Delta{days, seconds, nanos}, .None
|
||||
}
|
||||
|
||||
// The following procedures don't check whether their inputs are in a valid range.
|
||||
// They're still exported for those who know their inputs have been validated.
|
||||
|
||||
unsafe_date_to_ordinal :: proc "contextless" (date: Date) -> (ordinal: Ordinal) {
|
||||
year_minus_one := date.year - 1
|
||||
|
||||
// Day before epoch
|
||||
ordinal = EPOCH - 1
|
||||
|
||||
// Add non-leap days
|
||||
ordinal += 365 * year_minus_one
|
||||
|
||||
// Add leap days
|
||||
ordinal += floor_div(year_minus_one, 4) // Julian-rule leap days
|
||||
ordinal -= floor_div(year_minus_one, 100) // Prior century years
|
||||
ordinal += floor_div(year_minus_one, 400) // Prior 400-multiple years
|
||||
ordinal += floor_div(367 * date.month - 362, 12) // Prior days this year
|
||||
|
||||
// Apply correction
|
||||
if date.month <= 2 {
|
||||
ordinal += 0
|
||||
} else if is_leap_year(date.year) {
|
||||
ordinal -= 1
|
||||
} else {
|
||||
ordinal -= 2
|
||||
}
|
||||
|
||||
// Add days
|
||||
ordinal += date.day
|
||||
return
|
||||
}
|
||||
|
||||
unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: int, day_ordinal: int) {
|
||||
// Days after epoch
|
||||
d0 := ordinal - EPOCH
|
||||
|
||||
// Number of 400-year cycles and remainder
|
||||
n400, d1 := divmod(d0, 146097)
|
||||
|
||||
// Number of 100-year cycles and remainder
|
||||
n100, d2 := divmod(d1, 36524)
|
||||
|
||||
// Number of 4-year cycles and remainder
|
||||
n4, d3 := divmod(d2, 1461)
|
||||
|
||||
// Number of remaining days
|
||||
n1, d4 := divmod(d3, 365)
|
||||
|
||||
year = 400 * n400 + 100 * n100 + 4 * n4 + n1
|
||||
|
||||
if n1 != 4 && n100 != 4 {
|
||||
day_ordinal = d4 + 1
|
||||
} else {
|
||||
day_ordinal = 366
|
||||
}
|
||||
|
||||
if n100 == 4 || n1 == 4 {
|
||||
return year, day_ordinal
|
||||
}
|
||||
return year + 1, day_ordinal
|
||||
}
|
||||
|
||||
unsafe_ordinal_to_date :: proc "contextless" (ordinal: Ordinal) -> (date: Date) {
|
||||
year, _ := unsafe_ordinal_to_year(ordinal)
|
||||
|
||||
prior_days := ordinal - unsafe_date_to_ordinal(Date{year, 1, 1})
|
||||
correction := Ordinal(2)
|
||||
|
||||
if ordinal < unsafe_date_to_ordinal(Date{year, 3, 1}) {
|
||||
correction = 0
|
||||
} else if is_leap_year(year) {
|
||||
correction = 1
|
||||
}
|
||||
|
||||
month := floor_div((12 * (prior_days + correction) + 373), 367)
|
||||
day := ordinal - unsafe_date_to_ordinal(Date{year, month, 1}) + 1
|
||||
|
||||
return {year, month, day}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package datetime
|
||||
|
||||
// Internal helper functions for calendrical conversions
|
||||
|
||||
import "base:intrinsics"
|
||||
|
||||
sign :: proc "contextless" (v: int) -> (res: int) {
|
||||
if v == 0 {
|
||||
return 0
|
||||
} else if v > 0 {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Caller has to ensure y != 0
|
||||
divmod :: proc "contextless" (x, y: $T, loc := #caller_location) -> (a: T, r: T)
|
||||
where intrinsics.type_is_integer(T) {
|
||||
a = x / y
|
||||
r = x % y
|
||||
if (r > 0 && y < 0) || (r < 0 && y > 0) {
|
||||
a -= 1
|
||||
r += y
|
||||
}
|
||||
return a, r
|
||||
}
|
||||
|
||||
// Divides and floors
|
||||
floor_div :: proc "contextless" (x, y: $T) -> (res: T)
|
||||
where intrinsics.type_is_integer(T) {
|
||||
res = x / y
|
||||
r := x % y
|
||||
if (r > 0 && y < 0) || (r < 0 && y > 0) {
|
||||
res -= 1
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Half open: x mod [1..b]
|
||||
interval_mod :: proc "contextless" (x, a, b: int) -> (res: int) {
|
||||
if a == b {
|
||||
return x
|
||||
}
|
||||
return a + ((x - a) %% (b - a))
|
||||
}
|
||||
|
||||
// x mod [1..b]
|
||||
adjusted_remainder :: proc "contextless" (x, b: int) -> (res: int) {
|
||||
m := x %% b
|
||||
return b if m == 0 else m
|
||||
}
|
||||
|
||||
gcd :: proc "contextless" (x, y: int) -> (res: int) {
|
||||
if y == 0 {
|
||||
return x
|
||||
}
|
||||
|
||||
m := x %% y
|
||||
return gcd(y, m)
|
||||
}
|
||||
|
||||
lcm :: proc "contextless" (x, y: int) -> (res: int) {
|
||||
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) {
|
||||
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) {
|
||||
res = 1
|
||||
for idx := i; cond(idx); idx += 1 {
|
||||
res *= f(idx)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
smallest :: proc "contextless" (k: int, cond: proc "contextless" (n: int) -> bool) -> (d: int) {
|
||||
k := k
|
||||
for !cond(k) {
|
||||
k += 1
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
biggest :: proc "contextless" (k: int, cond: proc "contextless" (n: int) -> bool) -> (d: int) {
|
||||
k := k
|
||||
for !cond(k) {
|
||||
k -= 1
|
||||
}
|
||||
return k
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package datetime
|
||||
|
||||
// Validation helpers
|
||||
is_leap_year :: proc "contextless" (year: int) -> (leap: bool) {
|
||||
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
|
||||
}
|
||||
|
||||
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) {
|
||||
if year < MIN_DATE.year || year > MAX_DATE.year {
|
||||
return .Invalid_Year
|
||||
}
|
||||
if month < 1 || month > 12 {
|
||||
return .Invalid_Month
|
||||
}
|
||||
|
||||
month_days := MONTH_DAYS
|
||||
days_this_month := month_days[month]
|
||||
if month == 2 && is_leap_year(year) {
|
||||
days_this_month = 29
|
||||
}
|
||||
|
||||
if day < 1 || day > days_this_month {
|
||||
return .Invalid_Day
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
validate_ordinal :: proc "contextless" (ordinal: Ordinal) -> (err: Error) {
|
||||
if ordinal < MIN_ORD || ordinal > MAX_ORD {
|
||||
return .Invalid_Ordinal
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate_time :: proc "contextless" (time: Time) -> (err: Error) {
|
||||
if time.hour < 0 || time.hour > 23 {
|
||||
return .Invalid_Hour
|
||||
}
|
||||
if time.minute < 0 || time.minute > 59 {
|
||||
return .Invalid_Minute
|
||||
}
|
||||
if time.second < 0 || time.second > 59 {
|
||||
return .Invalid_Second
|
||||
}
|
||||
if time.nano < 0 || time.nano > 1e9 {
|
||||
return .Invalid_Nano
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
validate_datetime :: proc "contextless" (using datetime: DateTime) -> (err: Error) {
|
||||
validate(date) or_return
|
||||
validate(time) or_return
|
||||
return .None
|
||||
}
|
||||
|
||||
validate :: proc{
|
||||
validate_date,
|
||||
validate_year_month_day,
|
||||
validate_ordinal,
|
||||
validate_time,
|
||||
validate_datetime,
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package time
|
||||
// Parsing RFC 3339 date/time strings into time.Time.
|
||||
// See https://www.rfc-editor.org/rfc/rfc3339 for the definition
|
||||
|
||||
import dt "core:time/datetime"
|
||||
|
||||
// Parses an RFC 3339 string and returns Time in UTC, with any UTC offset applied to it.
|
||||
// Only 4-digit years are accepted.
|
||||
// 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_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
|
||||
offset: int
|
||||
|
||||
res, offset, consumed = rfc3339_to_time_and_offset(rfc_datetime, is_leap)
|
||||
res._nsec += (i64(-offset) * i64(Minute))
|
||||
return res, consumed
|
||||
}
|
||||
|
||||
// Parses an RFC 3339 string and returns Time and a UTC offset in minutes.
|
||||
// e.g. 1985-04-12T23:20:50.52Z
|
||||
// Note: Only 4-digit years are accepted.
|
||||
// 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)
|
||||
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 _res, ok := datetime_to_time(moment.year, moment.month, moment.day, moment.hour, moment.minute, moment.second, moment.nano); !ok {
|
||||
return {}, 0, 0
|
||||
} else {
|
||||
return _res, offset, count
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
return moment, offset, 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) {
|
||||
// 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
|
||||
|
||||
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
|
||||
|
||||
res.nano = 10_000_000 * hundredths
|
||||
}
|
||||
|
||||
// Scan UTC offset
|
||||
switch rfc_datetime[count] {
|
||||
case 'Z':
|
||||
utc_offset = 0
|
||||
count += 1
|
||||
case '+', '-':
|
||||
(len(rfc_datetime[count:]) >= 6) or_return
|
||||
offset_hour := scan_digits(rfc_datetime[count+1:], ":", 2) or_return
|
||||
offset_minute := scan_digits(rfc_datetime[count+4:], "", 2) or_return
|
||||
|
||||
utc_offset = 60 * offset_hour + offset_minute
|
||||
utc_offset *= -1 if rfc_datetime[count] == '-' else 1
|
||||
count += 6
|
||||
}
|
||||
|
||||
if consume_count != nil {
|
||||
consume_count^ = count
|
||||
}
|
||||
return res, utc_offset, true
|
||||
}
|
||||
|
||||
@(private)
|
||||
scan_digits :: proc(s: string, sep: string, count: int) -> (res: int, ok: bool) {
|
||||
needed := count + min(1, len(sep))
|
||||
(len(s) >= needed) or_return
|
||||
|
||||
#no_bounds_check for i in 0..<count {
|
||||
if v := s[i]; v >= '0' && v <= '9' {
|
||||
res = res * 10 + int(v - '0')
|
||||
} else {
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
found_sep := len(sep) == 0
|
||||
#no_bounds_check for v in sep {
|
||||
found_sep |= rune(s[count]) == v
|
||||
}
|
||||
return res, found_sep
|
||||
}
|
||||
+26
-54
@@ -1,6 +1,7 @@
|
||||
package time
|
||||
|
||||
import "base:intrinsics"
|
||||
import "base:intrinsics"
|
||||
import dt "core:time/datetime"
|
||||
|
||||
Duration :: distinct i64
|
||||
|
||||
@@ -299,10 +300,6 @@ _time_abs :: proc "contextless" (t: Time) -> u64 {
|
||||
|
||||
@(private)
|
||||
_abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Month, day: int, yday: int) {
|
||||
_is_leap_year :: proc "contextless" (year: int) -> bool {
|
||||
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
||||
}
|
||||
|
||||
d := abs / SECONDS_PER_DAY
|
||||
|
||||
// 400 year cycles
|
||||
@@ -335,7 +332,7 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon
|
||||
|
||||
day = yday
|
||||
|
||||
if _is_leap_year(year) {
|
||||
if is_leap_year(year) {
|
||||
switch {
|
||||
case day > 31+29-1:
|
||||
day -= 1
|
||||
@@ -360,57 +357,32 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon
|
||||
return
|
||||
}
|
||||
|
||||
datetime_to_time :: proc "contextless" (year, month, day, hour, minute, second: int, nsec := int(0)) -> (t: Time, ok: bool) {
|
||||
divmod :: proc "contextless" (year: int, divisor: int) -> (div: int, mod: int) {
|
||||
if divisor <= 0 {
|
||||
intrinsics.debug_trap()
|
||||
}
|
||||
div = int(year / divisor)
|
||||
mod = year % divisor
|
||||
return
|
||||
}
|
||||
_is_leap_year :: proc "contextless" (year: int) -> bool {
|
||||
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
||||
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}}
|
||||
return compound_to_time(this_date)
|
||||
}
|
||||
|
||||
compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) {
|
||||
unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}}
|
||||
delta, err := dt.sub(datetime, unix_epoch)
|
||||
ok = err == .None
|
||||
|
||||
seconds := delta.days * 86_400 + delta.seconds
|
||||
nanoseconds := i128(seconds) * 1e9 + i128(delta.nanos)
|
||||
|
||||
// Can this moment be represented in i64 worth of nanoseconds?
|
||||
// min(Time): 1677-09-21 00:12:44.145224192 +0000 UTC
|
||||
// max(Time): 2262-04-11 23:47:16.854775807 +0000 UTC
|
||||
if nanoseconds < i128(min(i64)) || nanoseconds > i128(max(i64)) {
|
||||
return {}, false
|
||||
}
|
||||
return Time{_nsec=i64(nanoseconds)}, true
|
||||
}
|
||||
|
||||
datetime_to_time :: proc{components_to_time, compound_to_time}
|
||||
|
||||
ok = true
|
||||
|
||||
_y := year - 1970
|
||||
_m := month - 1
|
||||
_d := day - 1
|
||||
|
||||
if month < 1 || month > 12 {
|
||||
_m %= 12; ok = false
|
||||
}
|
||||
if day < 1 || day > 31 {
|
||||
_d %= 31; ok = false
|
||||
}
|
||||
|
||||
s := i64(0)
|
||||
div, mod := divmod(_y, 400)
|
||||
days := div * DAYS_PER_400_YEARS
|
||||
|
||||
div, mod = divmod(mod, 100)
|
||||
days += div * DAYS_PER_100_YEARS
|
||||
|
||||
div, mod = divmod(mod, 4)
|
||||
days += (div * DAYS_PER_4_YEARS) + (mod * 365)
|
||||
|
||||
days += int(days_before[_m]) + _d
|
||||
|
||||
if _is_leap_year(year) && _m >= 2 {
|
||||
days += 1
|
||||
}
|
||||
|
||||
s += i64(days) * SECONDS_PER_DAY
|
||||
s += i64(hour) * SECONDS_PER_HOUR
|
||||
s += i64(minute) * SECONDS_PER_MINUTE
|
||||
s += i64(second)
|
||||
|
||||
t._nsec = (s * 1e9) + i64(nsec)
|
||||
|
||||
return
|
||||
is_leap_year :: proc "contextless" (year: int) -> (leap: bool) {
|
||||
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
|
||||
}
|
||||
|
||||
days_before := [?]i32{
|
||||
|
||||
Reference in New Issue
Block a user