diff --git a/core/time/iso8601.odin b/core/time/iso8601.odin index 528e0b00a..f00107226 100644 --- a/core/time/iso8601.odin +++ b/core/time/iso8601.odin @@ -3,23 +3,62 @@ package time import dt "core:time/datetime" -// Parses an ISO 8601 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. +/* +Parse an ISO 8601 string into a time with UTC offset applied to it. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string. In case the timezone offset +is specified in the string, that timezone is applied to time. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time represented by `iso_datetime`, with UTC offset applied. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) { offset: int - res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap) res._nsec += (i64(-offset) * i64(Minute)) return res, consumed } -// Parses an ISO 8601 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. +/* +Parse an ISO 8601 string into a time and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns time, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed. +- `is_leap`: Optional output parameter, specifying if the moment was a leap second. + +**Returns**: +- `res`: The time in UTC. +- `utc_offset`: The UTC offset of the time, in minutes. +- `consumed`: Number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- Leap seconds are smeared into 23:59:59. +*/ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, utc_offset: int, consumed: int) { moment, offset, leap_second, count := iso8601_to_components(iso_datetime) if count == 0 { @@ -37,9 +76,32 @@ iso8601_to_time_and_offset :: proc(iso_datetime: string, is_leap: ^bool = nil) - } } -// Parses an ISO 8601 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 +/* +Parse an ISO 8601 string into a datetime and a UTC offset in minutes. + +This procedure parses an ISO 8601 string of roughly the following format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns datetime, in UTC represented by that string, and the UTC offset, in +minutes. + +**Inputs**: +- `iso_datetime`: The string to be parsed + +**Returns**: +- `res`: The parsed datetime, in UTC. +- `utc_offset`: The UTC offset, in minutes. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- This procedure performs no validation on whether components are valid, + e.g. it'll return hour = 25 if that's what it's given in the specified + string. +*/ iso8601_to_components :: proc(iso_datetime: string) -> (res: dt.DateTime, utc_offset: int, is_leap: bool, consumed: int) { moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime) if !ok { diff --git a/core/time/perf.odin b/core/time/perf.odin index 123d67eca..784d7acd6 100644 --- a/core/time/perf.odin +++ b/core/time/perf.odin @@ -3,18 +3,39 @@ package time import "base:runtime" import "base:intrinsics" +/* +Type representing monotonic time, useful for measuring durations. +*/ Tick :: struct { _nsec: i64, // relative amount } + +/* +Obtain the current tick. +*/ tick_now :: proc "contextless" () -> Tick { return _tick_now() } +/* +Obtain the difference between ticks. +*/ tick_diff :: proc "contextless" (start, end: Tick) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Incrementally obtain durations since last tick. + +This procedure returns the duration between the current tick and the tick +stored in `prev` pointer, and then stores the current tick in location, +specified by `prev`. If the prev pointer contains an zero-initialized tick, +then the returned duration is 0. + +This procedure is meant to be used in a loop, or in other scenarios, where one +might want to obtain time between multiple ticks at specific points. +*/ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { d: Duration t := tick_now() @@ -25,17 +46,21 @@ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration { return d } +/* +Obtain the duration since last tick. +*/ tick_since :: proc "contextless" (start: Tick) -> Duration { return tick_diff(start, tick_now()) } - +/* +Capture the duration the code in the current scope takes to execute. +*/ @(deferred_in_out=_tick_duration_end) SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick { return tick_now() } - _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) { d^ = tick_since(t) } @@ -62,6 +87,13 @@ when ODIN_OS != .Darwin && ODIN_OS != .Linux && ODIN_OS != .FreeBSD { } } +/* +Check if the CPU has invariant TSC. + +This procedure checks if the CPU contains an invariant TSC (Time stamp counter). +Invariant TSC is a feature of modern processors that allows them to run their +TSC at a fixed frequency, independent of ACPI state, and CPU frequency. +*/ has_invariant_tsc :: proc "contextless" () -> bool { when ODIN_ARCH == .amd64 { return x86_has_invariant_tsc() @@ -70,6 +102,17 @@ has_invariant_tsc :: proc "contextless" () -> bool { return false } +/* +Obtain the CPU's TSC frequency, in hertz. + +This procedure tries to obtain the CPU's TSC frequency in hertz. If the CPU +doesn't have an invariant TSC, this procedure returns with an error. Otherwise +an attempt is made to fetch the TSC frequency from the OS. If this fails, +the frequency is obtained by sleeping for the specified amount of time and +dividing the readings from TSC by the duration of the sleep. + +The duration of sleep can be controlled by `fallback_sleep` parameter. +*/ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) { if !has_invariant_tsc() { return 0, false @@ -93,37 +136,64 @@ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool return hz, true } -/* - Benchmark helpers -*/ +// Benchmark helpers +/* +Errors returned by the `benchmark()` procedure. +*/ Benchmark_Error :: enum { Okay = 0, Allocation_Error, } +/* +Options for benchmarking. +*/ Benchmark_Options :: struct { + // The initialization procedure. `benchmark()` will call this before taking measurements. setup: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The procedure to benchmark. bench: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), + // The deinitialization procedure. teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error), - + // Field to be used by `bench()` procedure for any purpose. rounds: int, + // Field to be used by `bench()` procedure for any purpose. bytes: int, + // Field to be used by `bench()` procedure for any purpose. input: []u8, - + // `bench()` writes to specify the count of elements processed. count: int, + // `bench()` writes to specify the number of bytes processed. processed: int, + // `bench()` can write the output slice here. output: []u8, // Unused for hash benchmarks + // `bench()` can write the output hash here. hash: u128, - - /* - Performance - */ + // `benchmark()` procedure will output the duration of benchmark duration: Duration, + // `benchmark()` procedure will output the average count of elements + // processed per second, using the `count` field of this struct. rounds_per_second: f64, + // `benchmark()` procedure will output the average number of megabytes + // processed per second, using the `processed` field of this struct. megabytes_per_second: f64, } +/* +Benchmark a procedure. + +This procedure produces a benchmark. The procedure specified in the `bench` +field of the `options` parameter will be benchmarked. The following metrics +can be obtained: + +- Run time of the procedure +- Number of elements per second processed on average +- Number of bytes per second this processed on average + +In order to obtain these metrics, the `bench()` procedure writes to `options` +struct the number of elements or bytes it has processed. +*/ benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) { assert(options != nil) assert(options.bench != nil) diff --git a/core/time/rfc3339.odin b/core/time/rfc3339.odin index 0a2d431b7..e4c6565d6 100644 --- a/core/time/rfc3339.odin +++ b/core/time/rfc3339.odin @@ -4,10 +4,33 @@ package time 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. +/* +Parse an RFC 3339 string into time with a UTC offset applied to it. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time that was represented by the RFC 3339 string, with the UTC +offset applied to it. + +**Inputs**: +- `rfc_datetime`: An RFC 3339 string to parse. +- `is_leap`: Optional output parameter specifying whether the moment was a leap + second. + +**Returns**: +- `res`: The time, with UTC offset applied, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the RFC 3339 string. + +**Notes**: +- Only 4-digit years are accepted. +- 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 @@ -16,11 +39,34 @@ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: 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. +/* +Parse an RFC 3339 string into a time and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the time, in UTC and a UTC offset, in minutes, that were represented +by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to be parsed. +- `is_leap`: Optional output parameter specifying whether the moment was a + leap second. + +**Returns**: +- `res`: The time, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `consumed`: The number of bytes consumed by parsing the string. + +**Notes**: +- Only 4-digit years are accepted. +- 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 { @@ -38,9 +84,31 @@ 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 +/* +Parse an RFC 3339 string into a datetime and a UTC offset in minutes. + +This procedure parses the specified RFC 3339 strings of roughly the following +format: + +```text +YYYY-MM-DD[Tt]HH:mm:ss[.nn][Zz][+-]HH:mm +``` + +And returns the datetime, in UTC and the UTC offset, in minutes, that were +represented by the RFC 3339 string. + +**Inputs**: +- `rfc_datetime`: The RFC 3339 string to parse. + +**Returns**: +- `res`: The datetime, in UTC, that was parsed from the RFC 3339 string. +- `utc_offset`: The UTC offset, in minutes, that was parsed from the RFC 3339 + string. +- `is_leap`: Specifies whether the moment was a leap second. +- `consumed`: Number of bytes consumed by parsing the string. + +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 { diff --git a/core/time/time.odin b/core/time/time.odin index 4ea5afc70..ef2bc0aed 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -3,24 +3,72 @@ package time import "base:intrinsics" import dt "core:time/datetime" +/* +Type representing duration, with nanosecond precision. +*/ Duration :: distinct i64 +/* +The duration equal to one nanosecond (1e-9 seconds). +*/ Nanosecond :: Duration(1) + +/* +The duration equal to one microsecond (1e-6 seconds). +*/ Microsecond :: 1000 * Nanosecond + +/* +The duration equal to one millisecond (1e-3 seconds). +*/ Millisecond :: 1000 * Microsecond + +/* +The duration equal to one second. +*/ Second :: 1000 * Millisecond + +/* +The duration equal to one minute (60 seconds). +*/ Minute :: 60 * Second + +/* +The duration equal to one hour (3600 seconds). +*/ Hour :: 60 * Minute +/* +Minimum representable duration. +*/ MIN_DURATION :: Duration(-1 << 63) + +/* +Maximum representable duration. +*/ MAX_DURATION :: Duration(1<<63 - 1) +/* +Value specifying whether the time procedures are supported by the current +platform. +*/ IS_SUPPORTED :: _IS_SUPPORTED +/* +Specifies time since the UNIX epoch, with nanosecond precision. + +Capable of representing any time within the following range: + +- `min: 1677-09-21 00:12:44.145224192 +0000 UTC` +- `max: 2262-04-11 23:47:16.854775807 +0000 UTC` +*/ Time :: struct { _nsec: i64, // Measured in UNIX nanonseconds } +/* +Type representing a month. +*/ Month :: enum int { January = 1, February, @@ -36,6 +84,9 @@ Month :: enum int { December, } +/* +Type representing a weekday. +*/ Weekday :: enum int { Sunday = 0, Monday, @@ -46,20 +97,37 @@ Weekday :: enum int { Saturday, } +/* +Type representing a stopwatch. + +The stopwatch is used for measuring the total time in multiple "runs". When the +stopwatch is started, it starts counting time. When the stopwatch is stopped, +the difference in time between the last start and the stop is added to the +total. When the stopwatch resets, the total is reset. +*/ Stopwatch :: struct { running: bool, _start_time: Tick, _accumulation: Duration, } +/* +Obtain the current time. +*/ now :: proc "contextless" () -> Time { return _now() } +/* +Sleep for the specified duration. +*/ sleep :: proc "contextless" (d: Duration) { _sleep(d) } +/* +Start the stopwatch. +*/ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { if !stopwatch.running { stopwatch._start_time = tick_now() @@ -67,6 +135,9 @@ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Stop the stopwatch. +*/ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { if stopwatch.running { stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now()) @@ -74,11 +145,21 @@ stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { } } +/* +Reset the stopwatch. +*/ stopwatch_reset :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch._accumulation = {} stopwatch.running = false } +/* +Obtain the total time, counted by the stopwatch. + +This procedure obtains the total time, counted by the stopwatch. If the stopwatch +isn't stopped at the time of calling this procedure, the time between the last +start and the current time is also accounted for. +*/ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { if !stopwatch.running { return stopwatch._accumulation @@ -86,40 +167,92 @@ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration { return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now()) } +/* +Calculate the duration elapsed between two times. +*/ diff :: proc "contextless" (start, end: Time) -> Duration { d := end._nsec - start._nsec return Duration(d) } +/* +Calculate the duration elapsed since a specific time. +*/ since :: proc "contextless" (start: Time) -> Duration { return diff(start, now()) } +/* +Obtain the number of nanoseconds in a duration. +*/ duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { return i64(d) } + +/* +Obtain the number of microseconds in a duration. +*/ duration_microseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e6 } + +/* +Obtain the number of milliseconds in a duration. +*/ duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e3 } + +/* +Obtain the number of seconds in a duration. +*/ duration_seconds :: proc "contextless" (d: Duration) -> f64 { sec := d / Second nsec := d % Second return f64(sec) + f64(nsec)/1e9 } + +/* +Obtain the number of minutes in a duration. +*/ duration_minutes :: proc "contextless" (d: Duration) -> f64 { min := d / Minute nsec := d % Minute return f64(min) + f64(nsec)/(60*1e9) } + +/* +Obtain the number of hours in a duration. +*/ duration_hours :: proc "contextless" (d: Duration) -> f64 { hour := d / Hour nsec := d % Hour return f64(hour) + f64(nsec)/(60*60*1e9) } +/* +Round a duration to a specific unit. + +This procedure rounds the duration to a specific unit. + +**Inputs**: +- `d`: The duration to round. +- `m`: The unit to round to. + +**Returns**: +- The duration `d`, rounded to the unit specified by `m`. + +**Example**: + +In order to obtain the rough amount of seconds in a duration, the following call +can be used: + +``` +time.duration_round(my_duration, time.Second) +``` + +**Note**: Any duration can be supplied as a unit. +*/ 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) @@ -149,50 +282,103 @@ duration_round :: proc "contextless" (d, m: Duration) -> Duration { return MAX_DURATION } +/* +Truncate the duration to the specified unit. + +This procedure truncates the duration `d` to the unit specified by `m`. + +**Inputs**: +- `d`: The duration to truncate. +- `m`: The unit to truncate to. + +**Returns**: +- The duration `d`, truncated to the unit specified by `m`. + +**Example**: + +In order to obtain the amount of whole seconds in a duration, the following call +can be used: + +``` +time.duration_round(my_duration, time.Second) +``` + +**Note**: Any duration can be supplied as a unit. +*/ duration_truncate :: proc "contextless" (d, m: Duration) -> Duration { return d if m <= 0 else d - d%m } +/* +Parse time into date components. +*/ date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) { year, month, day, _ = _abs_date(_time_abs(t), true) return } +/* +Obtain the year of the date specified by time. +*/ year :: proc "contextless" (t: Time) -> (year: int) { year, _, _, _ = _date(t, true) return } +/* +Obtain the month of the date specified by time. +*/ month :: proc "contextless" (t: Time) -> (month: Month) { _, month, _, _ = _date(t, true) return } +/* +Obtain the day of the date specified by time. +*/ day :: proc "contextless" (t: Time) -> (day: int) { _, _, day, _ = _date(t, true) return } +/* +Obtain the week day of the date specified by time. +*/ 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) } +/* +Obtain the time components from a time, a duration or a stopwatch's total. +*/ clock :: proc { clock_from_time, clock_from_duration, clock_from_stopwatch } +/* +Obtain the time components from a time. +*/ clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) { return clock_from_seconds(_time_abs(t)) } +/* +Obtain the time components from a duration. +*/ clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) { return clock_from_seconds(u64(d/1e9)) } +/* +Obtain the time components from a stopwatch's total. +*/ clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) { return clock_from_duration(stopwatch_duration(s)) } +/* +Obtain the time components from the number of seconds. +*/ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { sec = int(nsec % SECONDS_PER_DAY) hour = sec / SECONDS_PER_HOUR @@ -202,10 +388,16 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) { return } +/* +Read the timestamp counter of the CPU. +*/ read_cycle_counter :: proc "contextless" () -> u64 { return u64(intrinsics.read_cycle_counter()) } +/* +Obtain time from unix seconds and unix nanoseconds. +*/ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { sec, nsec := sec, nsec if nsec < 0 || nsec >= 1e9 { @@ -220,31 +412,59 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { return Time{(sec*1e9 + nsec)} } +/* +Obtain time from unix nanoseconds. +*/ from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time { return Time{nsec} } +/* +Alias for `time_to_unix`. +*/ to_unix_seconds :: time_to_unix + +/* +Obtain the unix seconds from a time. +*/ time_to_unix :: proc "contextless" (t: Time) -> i64 { return t._nsec/1e9 } +/* +Alias for `time_to_unix_nano`. +*/ to_unix_nanoseconds :: time_to_unix_nano + +/* +Obtain the unix nanoseconds from a time. +*/ time_to_unix_nano :: proc "contextless" (t: Time) -> i64 { return t._nsec } +/* +Add duration to a time. +*/ 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 `windows.timeBeginPeriod(1)` to -// tell Windows to use a more accurate timer for your process. -// Additionally your program should call `windows.timeEndPeriod(1)` once you're done with `accurate_sleep`. +/* +Accurate sleep + +This procedure sleeps for the duration specified by `d`, very accurately. + +**Note**: Implementation borrowed from: [this source](https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/) + +**Note(linux)**: The accuracy is within around 4µs (microseconds), in the worst case. + +**Note(windows)**: The accuracy 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 `windows.timeBeginPeriod(1)` to tell Windows to use a more accurate timer +for your process. Additionally your program should call `windows.timeEndPeriod(1)` +once you're done with `accurate_sleep`. +*/ accurate_sleep :: proc "contextless" (d: Duration) { to_sleep, estimate, mean, m2, count: Duration @@ -362,6 +582,13 @@ _abs_date :: proc "contextless" (abs: u64, full: bool) -> (year: int, month: Mon return } +/* +Convert datetime components into time. + +This procedure calculates the time from datetime components supplied in the +arguments to this procedure. If the datetime components don't represent a valid +datetime, the function returns `false` in the second argument. +*/ 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 { @@ -370,6 +597,12 @@ components_to_time :: proc "contextless" (#any_int year, #any_int month, #any_in return compound_to_time(this_date) } +/* +Convert datetime into time. + +If the datetime represents a time outside of a valid range, `false` is returned +as the second return value. See `Time` for the representable range. +*/ 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) @@ -387,12 +620,21 @@ compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: return Time{_nsec=i64(nanoseconds)}, true } +/* +Convert datetime components into time. +*/ datetime_to_time :: proc{components_to_time, compound_to_time} +/* +Check if a year is a leap year. +*/ is_leap_year :: proc "contextless" (year: int) -> (leap: bool) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } +/* +Days before each month in a year, not counting the leap day on february 29th. +*/ @(rodata) days_before := [?]i32{ 0, @@ -410,11 +652,37 @@ days_before := [?]i32{ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, } - +/* +Number of seconds in a minute (without leap seconds). +*/ SECONDS_PER_MINUTE :: 60 + +/* +Number of seconds in an hour (without leap seconds). +*/ SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE + +/* +Number of seconds in a day (without leap seconds). +*/ SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR + +/* +Number of seconds in a week (without leap seconds). +*/ SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY + +/* +Days in 400 years, with leap days. +*/ DAYS_PER_400_YEARS :: 365*400 + 97 + +/* +Days in 100 years, with leap days. +*/ DAYS_PER_100_YEARS :: 365*100 + 24 + +/* +Days in 4 years, with leap days. +*/ DAYS_PER_4_YEARS :: 365*4 + 1