mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-14 15:11:25 -07:00
Merge pull request #3292 from Kelimion/rfc3339
Add WiP datetime package and tests.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package datetime
|
||||
|
||||
// Ordinal 1 = Midnight Monday, January 1, 1 A.D. (Gregorian)
|
||||
// | Midnight Monday, January 3, 1 A.D. (Julian)
|
||||
Ordinal :: i64
|
||||
EPOCH :: Ordinal(1)
|
||||
|
||||
// Minimum and maximum dates and ordinals. Chosen for safe roundtripping.
|
||||
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: i64,
|
||||
month: i8,
|
||||
day: i8,
|
||||
}
|
||||
|
||||
Time :: struct {
|
||||
hour: i8,
|
||||
minute: i8,
|
||||
second: i8,
|
||||
nano: i32,
|
||||
}
|
||||
|
||||
DateTime :: struct {
|
||||
using date: Date,
|
||||
using time: Time,
|
||||
}
|
||||
|
||||
Delta :: struct {
|
||||
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 i8 {
|
||||
January = 1,
|
||||
February,
|
||||
March,
|
||||
April,
|
||||
May,
|
||||
June,
|
||||
July,
|
||||
August,
|
||||
September,
|
||||
October,
|
||||
November,
|
||||
December,
|
||||
}
|
||||
|
||||
Weekday :: enum i8 {
|
||||
Sunday = 0,
|
||||
Monday,
|
||||
Tuesday,
|
||||
Wednesday,
|
||||
Thursday,
|
||||
Friday,
|
||||
Saturday,
|
||||
}
|
||||
|
||||
@(private)
|
||||
MONTH_DAYS :: [?]i8{-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
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" (#any_int year, #any_int month, #any_int day: i64) -> (ordinal: Ordinal, err: Error) {
|
||||
validate(year, month, day) or_return
|
||||
return unsafe_date_to_ordinal({year, i8(month), i8(day)}), .None
|
||||
}
|
||||
|
||||
// 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" (#any_int year, #any_int month, #any_int day: i64) -> (date: Date, err: Error) {
|
||||
validate(year, month, day) or_return
|
||||
return Date{i64(year), i8(month), i8(day)}, .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) {
|
||||
validate(hour, minute, second, nanos) or_return
|
||||
return Time{i8(hour), i8(minute), i8(second), i32(nanos)}, .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
|
||||
}
|
||||
|
||||
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 := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second)
|
||||
seconds_b := i64(b.hour) * 3600 + i64(b.minute) * 60 + i64(b.second)
|
||||
|
||||
delta = Delta{ord_a - ord_b, seconds_a - seconds_b, i64(a.nano) - i64(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: i64) -> (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 := i64(a.hour) * 3600 + i64(a.minute) * 60 + i64(a.second)
|
||||
a_delta := Delta{days=days, seconds=a_seconds, nanos=i64(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
|
||||
|
||||
hour, rem := divmod(sum_delta.seconds, 3600)
|
||||
minute, second := divmod(rem, 60)
|
||||
|
||||
datetime.time = components_to_time(hour, minute, second, sum_delta.nanos) or_return
|
||||
return
|
||||
}
|
||||
add :: proc{add_days_to_date, add_delta_to_date, add_delta_to_datetime}
|
||||
|
||||
day_number :: proc "contextless" (date: Date) -> (day_number: i64, 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: 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" (#any_int year: i64, #any_int month: i8) -> (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 = i64(month_days[month])
|
||||
if month == 2 && is_leap_year(year) {
|
||||
day += 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
new_year :: proc "contextless" (#any_int year: i64) -> (new_year: Date, err: Error) {
|
||||
validate(year, 1, 1) or_return
|
||||
return {year, 1, 1}, .None
|
||||
}
|
||||
|
||||
year_end :: proc "contextless" (#any_int year: i64) -> (year_end: Date, err: Error) {
|
||||
validate(year, 12, 31) or_return
|
||||
return {year, 12, 31}, .None
|
||||
}
|
||||
|
||||
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
|
||||
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], _ = components_to_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: i64
|
||||
|
||||
// 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 * i64(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 += i64(date.day)
|
||||
return
|
||||
}
|
||||
|
||||
unsafe_ordinal_to_year :: proc "contextless" (ordinal: Ordinal) -> (year: i64, day_ordinal: i64) {
|
||||
// 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 := i8(floor_div((12 * (prior_days + correction) + 373), 367))
|
||||
day := i8(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: i64) -> (res: i64) {
|
||||
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: i64) -> (res: i64) {
|
||||
if a == b {
|
||||
return x
|
||||
}
|
||||
return a + ((x - a) %% (b - a))
|
||||
}
|
||||
|
||||
// x mod [1..b]
|
||||
adjusted_remainder :: proc "contextless" (x, b: i64) -> (res: i64) {
|
||||
m := x %% b
|
||||
return b if m == 0 else m
|
||||
}
|
||||
|
||||
gcd :: proc "contextless" (x, y: i64) -> (res: i64) {
|
||||
if y == 0 {
|
||||
return x
|
||||
}
|
||||
|
||||
m := x %% y
|
||||
return gcd(y, m)
|
||||
}
|
||||
|
||||
lcm :: proc "contextless" (x, y: i64) -> (res: i64) {
|
||||
return x * y / gcd(x, y)
|
||||
}
|
||||
|
||||
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: 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)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
smallest :: proc "contextless" (k: i64, cond: proc "contextless" (n: i64) -> bool) -> (d: i64) {
|
||||
k := k
|
||||
for !cond(k) {
|
||||
k += 1
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
biggest :: proc "contextless" (k: i64, cond: proc "contextless" (n: i64) -> bool) -> (d: i64) {
|
||||
k := k
|
||||
for !cond(k) {
|
||||
k -= 1
|
||||
}
|
||||
return k
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package datetime
|
||||
|
||||
// Validation helpers
|
||||
is_leap_year :: proc "contextless" (#any_int year: i64) -> (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" (#any_int year, #any_int month, #any_int day: i64) -> (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 > i64(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) {
|
||||
return validate(time.hour, time.minute, time.second, time.nano)
|
||||
}
|
||||
|
||||
validate_hour_minute_second :: proc "contextless" (#any_int hour, #any_int minute, #any_int second, #any_int nano: i64) -> (err: Error) {
|
||||
if hour < 0 || hour > 23 {
|
||||
return .Invalid_Hour
|
||||
}
|
||||
if minute < 0 || minute > 59 {
|
||||
return .Invalid_Minute
|
||||
}
|
||||
if second < 0 || second > 59 {
|
||||
return .Invalid_Second
|
||||
}
|
||||
if nano < 0 || 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_hour_minute_second,
|
||||
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, leap_second, count := rfc3339_to_components(rfc_datetime)
|
||||
if count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
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, is_leap: bool, consumed: int) {
|
||||
moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
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) -> (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], 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
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
return res, utc_offset, count, is_leap, 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
|
||||
}
|
||||
+27
-52
@@ -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,35 @@ _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
|
||||
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
|
||||
}
|
||||
_is_leap_year :: proc "contextless" (year: int) -> bool {
|
||||
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
||||
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