mirror of
https://github.com/Ed94/Odin.git
synced 2026-06-25 15:05:00 -07:00
72c15d7699
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.
412 lines
9.8 KiB
Odin
412 lines
9.8 KiB
Odin
package time
|
|
|
|
import "base:intrinsics"
|
|
import dt "core:time/datetime"
|
|
|
|
Duration :: distinct i64
|
|
|
|
Nanosecond :: Duration(1)
|
|
Microsecond :: 1000 * Nanosecond
|
|
Millisecond :: 1000 * Microsecond
|
|
Second :: 1000 * Millisecond
|
|
Minute :: 60 * Second
|
|
Hour :: 60 * Minute
|
|
|
|
MIN_DURATION :: Duration(-1 << 63)
|
|
MAX_DURATION :: Duration(1<<63 - 1)
|
|
|
|
IS_SUPPORTED :: _IS_SUPPORTED
|
|
|
|
Time :: struct {
|
|
_nsec: i64, // Measured in UNIX nanonseconds
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
Stopwatch :: struct {
|
|
running: bool,
|
|
_start_time: Tick,
|
|
_accumulation: Duration,
|
|
}
|
|
|
|
now :: proc "contextless" () -> Time {
|
|
return _now()
|
|
}
|
|
|
|
sleep :: proc "contextless" (d: Duration) {
|
|
_sleep(d)
|
|
}
|
|
|
|
stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
|
|
if !stopwatch.running {
|
|
stopwatch._start_time = tick_now()
|
|
stopwatch.running = true
|
|
}
|
|
}
|
|
|
|
stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) {
|
|
if stopwatch.running {
|
|
stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now())
|
|
stopwatch.running = false
|
|
}
|
|
}
|
|
|
|
stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) {
|
|
stopwatch._accumulation = {}
|
|
stopwatch.running = false
|
|
}
|
|
|
|
stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
|
|
if !stopwatch.running {
|
|
return stopwatch._accumulation
|
|
}
|
|
return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now())
|
|
}
|
|
|
|
diff :: proc "contextless" (start, end: Time) -> Duration {
|
|
d := end._nsec - start._nsec
|
|
return Duration(d)
|
|
}
|
|
|
|
since :: proc "contextless" (start: Time) -> Duration {
|
|
return diff(start, now())
|
|
}
|
|
|
|
duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 {
|
|
return i64(d)
|
|
}
|
|
duration_microseconds :: proc "contextless" (d: Duration) -> f64 {
|
|
return duration_seconds(d) * 1e6
|
|
}
|
|
duration_milliseconds :: proc "contextless" (d: Duration) -> f64 {
|
|
return duration_seconds(d) * 1e3
|
|
}
|
|
duration_seconds :: proc "contextless" (d: Duration) -> f64 {
|
|
sec := d / Second
|
|
nsec := d % Second
|
|
return f64(sec) + f64(nsec)/1e9
|
|
}
|
|
duration_minutes :: proc "contextless" (d: Duration) -> f64 {
|
|
min := d / Minute
|
|
nsec := d % Minute
|
|
return f64(min) + f64(nsec)/(60*1e9)
|
|
}
|
|
duration_hours :: proc "contextless" (d: Duration) -> f64 {
|
|
hour := d / Hour
|
|
nsec := d % Hour
|
|
return f64(hour) + f64(nsec)/(60*60*1e9)
|
|
}
|
|
|
|
duration_round :: proc "contextless" (d, m: Duration) -> Duration {
|
|
_less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool {
|
|
return u64(x)+u64(x) < u64(y)
|
|
}
|
|
|
|
if m <= 0 {
|
|
return d
|
|
}
|
|
|
|
r := d % m
|
|
if d < 0 {
|
|
r = -r
|
|
if _less_than_half(r, m) {
|
|
return d + r
|
|
}
|
|
if d1 := d-m+r; d1 < d {
|
|
return d1
|
|
}
|
|
return MIN_DURATION
|
|
}
|
|
if _less_than_half(r, m) {
|
|
return d - r
|
|
}
|
|
if d1 := d+m-r; d1 > d {
|
|
return d1
|
|
}
|
|
return MAX_DURATION
|
|
}
|
|
|
|
duration_truncate :: proc "contextless" (d, m: Duration) -> Duration {
|
|
return d if m <= 0 else d - d%m
|
|
}
|
|
|
|
date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) {
|
|
year, month, day, _ = _abs_date(_time_abs(t), true)
|
|
return
|
|
}
|
|
|
|
year :: proc "contextless" (t: Time) -> (year: int) {
|
|
year, _, _, _ = _date(t, true)
|
|
return
|
|
}
|
|
|
|
month :: proc "contextless" (t: Time) -> (month: Month) {
|
|
_, month, _, _ = _date(t, true)
|
|
return
|
|
}
|
|
|
|
day :: proc "contextless" (t: Time) -> (day: int) {
|
|
_, _, day, _ = _date(t, true)
|
|
return
|
|
}
|
|
|
|
weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) {
|
|
abs := _time_abs(t)
|
|
sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK
|
|
return Weekday(int(sec) / SECONDS_PER_DAY)
|
|
}
|
|
|
|
clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch }
|
|
|
|
clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) {
|
|
return clock_from_seconds(_time_abs(t))
|
|
}
|
|
|
|
clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) {
|
|
return clock_from_seconds(u64(d/1e9))
|
|
}
|
|
|
|
clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) {
|
|
return clock_from_duration(stopwatch_duration(s))
|
|
}
|
|
|
|
clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
|
|
sec = int(nsec % SECONDS_PER_DAY)
|
|
hour = sec / SECONDS_PER_HOUR
|
|
sec -= hour * SECONDS_PER_HOUR
|
|
min = sec / SECONDS_PER_MINUTE
|
|
sec -= min * SECONDS_PER_MINUTE
|
|
return
|
|
}
|
|
|
|
read_cycle_counter :: proc "contextless" () -> u64 {
|
|
return u64(intrinsics.read_cycle_counter())
|
|
}
|
|
|
|
unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
|
|
sec, nsec := sec, nsec
|
|
if nsec < 0 || nsec >= 1e9 {
|
|
n := nsec / 1e9
|
|
sec += n
|
|
nsec -= n * 1e9
|
|
if nsec < 0 {
|
|
nsec += 1e9
|
|
sec -= 1
|
|
}
|
|
}
|
|
return Time{(sec*1e9 + nsec)}
|
|
}
|
|
|
|
to_unix_seconds :: time_to_unix
|
|
time_to_unix :: proc "contextless" (t: Time) -> i64 {
|
|
return t._nsec/1e9
|
|
}
|
|
|
|
to_unix_nanoseconds :: time_to_unix_nano
|
|
time_to_unix_nano :: proc "contextless" (t: Time) -> i64 {
|
|
return t._nsec
|
|
}
|
|
|
|
time_add :: proc "contextless" (t: Time, d: Duration) -> Time {
|
|
return Time{t._nsec + i64(d)}
|
|
}
|
|
|
|
// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/
|
|
//
|
|
// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case.
|
|
// On Windows it depends but is comparable with regular sleep in the worst case.
|
|
// To get the same kind of accuracy as on Linux, have your program call `win32.time_begin_period(1)` to
|
|
// tell Windows to use a more accurate timer for your process.
|
|
accurate_sleep :: proc "contextless" (d: Duration) {
|
|
to_sleep, estimate, mean, m2, count: Duration
|
|
|
|
to_sleep = d
|
|
estimate = 5 * Millisecond
|
|
mean = 5 * Millisecond
|
|
count = 1
|
|
|
|
for to_sleep > estimate {
|
|
start := tick_now()
|
|
sleep(1 * Millisecond)
|
|
|
|
observed := tick_since(start)
|
|
to_sleep -= observed
|
|
|
|
count += 1
|
|
|
|
delta := observed - mean
|
|
mean += delta / count
|
|
m2 += delta * (observed - mean)
|
|
stddev := intrinsics.sqrt(f64(m2) / f64(count - 1))
|
|
estimate = mean + Duration(stddev)
|
|
}
|
|
|
|
start := tick_now()
|
|
for to_sleep > tick_since(start) {
|
|
// prevent the spinlock from taking the thread hostage, still accurate enough
|
|
_yield()
|
|
// NOTE: it might be possible that it yields for too long, in that case it should spinlock freely for a while
|
|
// TODO: needs actual testing done to check if that's the case
|
|
}
|
|
}
|
|
|
|
ABSOLUTE_ZERO_YEAR :: i64(-292277022399) // Day is chosen so that 2001-01-01 is Monday in the calculations
|
|
ABSOLUTE_TO_INTERNAL :: i64(-9223371966579724800) // i64((ABSOLUTE_ZERO_YEAR - 1) * 365.2425 * SECONDS_PER_DAY);
|
|
INTERNAL_TO_ABSOLUTE :: -ABSOLUTE_TO_INTERNAL
|
|
|
|
UNIX_TO_INTERNAL :: i64((1969*365 + 1969/4 - 1969/100 + 1969/400) * SECONDS_PER_DAY)
|
|
INTERNAL_TO_UNIX :: -UNIX_TO_INTERNAL
|
|
|
|
WALL_TO_INTERNAL :: i64((1884*365 + 1884/4 - 1884/100 + 1884/400) * SECONDS_PER_DAY)
|
|
INTERNAL_TO_WALL :: -WALL_TO_INTERNAL
|
|
|
|
UNIX_TO_ABSOLUTE :: UNIX_TO_INTERNAL + INTERNAL_TO_ABSOLUTE
|
|
ABSOLUTE_TO_UNIX :: -UNIX_TO_ABSOLUTE
|
|
|
|
|
|
@(private)
|
|
_date :: proc "contextless" (t: Time, full: bool) -> (year: int, month: Month, day: int, yday: int) {
|
|
year, month, day, yday = _abs_date(_time_abs(t), full)
|
|
return
|
|
}
|
|
|
|
@(private)
|
|
_time_abs :: proc "contextless" (t: Time) -> u64 {
|
|
return u64(t._nsec/1e9 + UNIX_TO_ABSOLUTE)
|
|
}
|
|
|
|
@(private)
|
|
_abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Month, day: int, yday: int) {
|
|
d := abs / SECONDS_PER_DAY
|
|
|
|
// 400 year cycles
|
|
n := d / DAYS_PER_400_YEARS
|
|
y := 400 * n
|
|
d -= DAYS_PER_400_YEARS * n
|
|
|
|
// Cut-off 100 year cycles
|
|
n = d / DAYS_PER_100_YEARS
|
|
n -= n >> 2
|
|
y += 100 * n
|
|
d -= DAYS_PER_100_YEARS * n
|
|
|
|
// Cut-off 4 year cycles
|
|
n = d / DAYS_PER_4_YEARS
|
|
y += 4 * n
|
|
d -= DAYS_PER_4_YEARS * n
|
|
|
|
n = d / 365
|
|
n -= n >> 2
|
|
y += n
|
|
d -= 365 * n
|
|
|
|
year = int(i64(y) + ABSOLUTE_ZERO_YEAR)
|
|
yday = int(d)
|
|
|
|
if !full {
|
|
return
|
|
}
|
|
|
|
day = yday
|
|
|
|
if is_leap_year(year) {
|
|
switch {
|
|
case day > 31+29-1:
|
|
day -= 1
|
|
case day == 31+29-1:
|
|
month = .February
|
|
day = 29
|
|
return
|
|
}
|
|
}
|
|
|
|
month = Month(day / 31)
|
|
end := int(days_before[int(month)+1])
|
|
begin: int
|
|
if day >= end {
|
|
(^int)(&month)^ += 1
|
|
begin = end
|
|
} else {
|
|
begin = int(days_before[month])
|
|
}
|
|
(^int)(&month)^ += 1 // January is 1
|
|
day = day - begin + 1
|
|
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}}
|
|
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}
|
|
|
|
is_leap_year :: proc "contextless" (year: int) -> (leap: bool) {
|
|
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
|
|
}
|
|
|
|
days_before := [?]i32{
|
|
0,
|
|
31,
|
|
31 + 28,
|
|
31 + 28 + 31,
|
|
31 + 28 + 31 + 30,
|
|
31 + 28 + 31 + 30 + 31,
|
|
31 + 28 + 31 + 30 + 31 + 30,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
|
|
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
|
|
}
|
|
|
|
|
|
SECONDS_PER_MINUTE :: 60
|
|
SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE
|
|
SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR
|
|
SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY
|
|
DAYS_PER_400_YEARS :: 365*400 + 97
|
|
DAYS_PER_100_YEARS :: 365*100 + 24
|
|
DAYS_PER_4_YEARS :: 365*4 + 1
|