[time]: Document all functions

This commit is contained in:
flysand7
2024-07-20 18:48:50 +11:00
parent 7237f9c9f8
commit b3ca2d5e0a
4 changed files with 512 additions and 44 deletions
+75 -13
View File
@@ -3,23 +3,62 @@ package time
import dt "core:time/datetime" 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. Parse an ISO 8601 string into a time with UTC offset applied to it.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
// Leap seconds are smeared into 23:59:59. 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) { iso8601_to_time_utc :: proc(iso_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
offset: int offset: int
res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap) res, offset, consumed = iso8601_to_time_and_offset(iso_datetime, is_leap)
res._nsec += (i64(-offset) * i64(Minute)) res._nsec += (i64(-offset) * i64(Minute))
return res, consumed 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 Parse an ISO 8601 string into a time and a UTC offset in minutes.
// Note: Only 4-digit years are accepted.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. This procedure parses an ISO 8601 string of roughly the following format:
// Leap seconds are smeared into 23:59:59.
```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) { 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) moment, offset, leap_second, count := iso8601_to_components(iso_datetime)
if count == 0 { 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 Parse an ISO 8601 string into a datetime and a UTC offset in minutes.
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
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) { 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) moment, offset, count, leap_second, ok := _iso8601_to_components(iso_datetime)
if !ok { if !ok {
+81 -11
View File
@@ -3,18 +3,39 @@ package time
import "base:runtime" import "base:runtime"
import "base:intrinsics" import "base:intrinsics"
/*
Type representing monotonic time, useful for measuring durations.
*/
Tick :: struct { Tick :: struct {
_nsec: i64, // relative amount _nsec: i64, // relative amount
} }
/*
Obtain the current tick.
*/
tick_now :: proc "contextless" () -> Tick { tick_now :: proc "contextless" () -> Tick {
return _tick_now() return _tick_now()
} }
/*
Obtain the difference between ticks.
*/
tick_diff :: proc "contextless" (start, end: Tick) -> Duration { tick_diff :: proc "contextless" (start, end: Tick) -> Duration {
d := end._nsec - start._nsec d := end._nsec - start._nsec
return Duration(d) 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 { tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
d: Duration d: Duration
t := tick_now() t := tick_now()
@@ -25,17 +46,21 @@ tick_lap_time :: proc "contextless" (prev: ^Tick) -> Duration {
return d return d
} }
/*
Obtain the duration since last tick.
*/
tick_since :: proc "contextless" (start: Tick) -> Duration { tick_since :: proc "contextless" (start: Tick) -> Duration {
return tick_diff(start, tick_now()) return tick_diff(start, tick_now())
} }
/*
Capture the duration the code in the current scope takes to execute.
*/
@(deferred_in_out=_tick_duration_end) @(deferred_in_out=_tick_duration_end)
SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick { SCOPED_TICK_DURATION :: proc "contextless" (d: ^Duration) -> Tick {
return tick_now() return tick_now()
} }
_tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) { _tick_duration_end :: proc "contextless" (d: ^Duration, t: Tick) {
d^ = tick_since(t) 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 { has_invariant_tsc :: proc "contextless" () -> bool {
when ODIN_ARCH == .amd64 { when ODIN_ARCH == .amd64 {
return x86_has_invariant_tsc() return x86_has_invariant_tsc()
@@ -70,6 +102,17 @@ has_invariant_tsc :: proc "contextless" () -> bool {
return false 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) { tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool) {
if !has_invariant_tsc() { if !has_invariant_tsc() {
return 0, false return 0, false
@@ -93,37 +136,64 @@ tsc_frequency :: proc "contextless" (fallback_sleep := 2 * Second) -> (u64, bool
return hz, true return hz, true
} }
/* // Benchmark helpers
Benchmark helpers
*/
/*
Errors returned by the `benchmark()` procedure.
*/
Benchmark_Error :: enum { Benchmark_Error :: enum {
Okay = 0, Okay = 0,
Allocation_Error, Allocation_Error,
} }
/*
Options for benchmarking.
*/
Benchmark_Options :: struct { 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), 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), 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), teardown: #type proc(options: ^Benchmark_Options, allocator: runtime.Allocator) -> (err: Benchmark_Error),
// Field to be used by `bench()` procedure for any purpose.
rounds: int, rounds: int,
// Field to be used by `bench()` procedure for any purpose.
bytes: int, bytes: int,
// Field to be used by `bench()` procedure for any purpose.
input: []u8, input: []u8,
// `bench()` writes to specify the count of elements processed.
count: int, count: int,
// `bench()` writes to specify the number of bytes processed.
processed: int, processed: int,
// `bench()` can write the output slice here.
output: []u8, // Unused for hash benchmarks output: []u8, // Unused for hash benchmarks
// `bench()` can write the output hash here.
hash: u128, hash: u128,
// `benchmark()` procedure will output the duration of benchmark
/*
Performance
*/
duration: Duration, 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, 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, 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) { benchmark :: proc(options: ^Benchmark_Options, allocator := context.allocator) -> (err: Benchmark_Error) {
assert(options != nil) assert(options != nil)
assert(options.bench != nil) assert(options.bench != nil)
+80 -12
View File
@@ -4,10 +4,33 @@ package time
import dt "core:time/datetime" 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. Parse an RFC 3339 string into time with a UTC offset applied to it.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second.
// Leap seconds are smeared into 23:59:59. 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) { rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res: Time, consumed: int) {
offset: int offset: int
@@ -16,11 +39,34 @@ rfc3339_to_time_utc :: proc(rfc_datetime: string, is_leap: ^bool = nil) -> (res:
return res, consumed 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 Parse an RFC 3339 string into a time and a UTC offset in minutes.
// Note: Only 4-digit years are accepted.
// Optional pointer to boolean `is_leap` will return `true` if the moment was a leap second. This procedure parses the specified RFC 3339 strings of roughly the following
// Leap seconds are smeared into 23:59:59. 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) { 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) moment, offset, leap_second, count := rfc3339_to_components(rfc_datetime)
if count == 0 { 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 Parse an RFC 3339 string into a datetime and a UTC offset in minutes.
// Performs no validation on whether components are valid, e.g. it'll return hour = 25 if that's what it's given
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) { 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) moment, offset, count, leap_second, ok := _rfc3339_to_components(rfc_datetime)
if !ok { if !ok {
+276 -8
View File
@@ -3,24 +3,72 @@ package time
import "base:intrinsics" import "base:intrinsics"
import dt "core:time/datetime" import dt "core:time/datetime"
/*
Type representing duration, with nanosecond precision.
*/
Duration :: distinct i64 Duration :: distinct i64
/*
The duration equal to one nanosecond (1e-9 seconds).
*/
Nanosecond :: Duration(1) Nanosecond :: Duration(1)
/*
The duration equal to one microsecond (1e-6 seconds).
*/
Microsecond :: 1000 * Nanosecond Microsecond :: 1000 * Nanosecond
/*
The duration equal to one millisecond (1e-3 seconds).
*/
Millisecond :: 1000 * Microsecond Millisecond :: 1000 * Microsecond
/*
The duration equal to one second.
*/
Second :: 1000 * Millisecond Second :: 1000 * Millisecond
/*
The duration equal to one minute (60 seconds).
*/
Minute :: 60 * Second Minute :: 60 * Second
/*
The duration equal to one hour (3600 seconds).
*/
Hour :: 60 * Minute Hour :: 60 * Minute
/*
Minimum representable duration.
*/
MIN_DURATION :: Duration(-1 << 63) MIN_DURATION :: Duration(-1 << 63)
/*
Maximum representable duration.
*/
MAX_DURATION :: Duration(1<<63 - 1) MAX_DURATION :: Duration(1<<63 - 1)
/*
Value specifying whether the time procedures are supported by the current
platform.
*/
IS_SUPPORTED :: _IS_SUPPORTED 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 { Time :: struct {
_nsec: i64, // Measured in UNIX nanonseconds _nsec: i64, // Measured in UNIX nanonseconds
} }
/*
Type representing a month.
*/
Month :: enum int { Month :: enum int {
January = 1, January = 1,
February, February,
@@ -36,6 +84,9 @@ Month :: enum int {
December, December,
} }
/*
Type representing a weekday.
*/
Weekday :: enum int { Weekday :: enum int {
Sunday = 0, Sunday = 0,
Monday, Monday,
@@ -46,20 +97,37 @@ Weekday :: enum int {
Saturday, 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 { Stopwatch :: struct {
running: bool, running: bool,
_start_time: Tick, _start_time: Tick,
_accumulation: Duration, _accumulation: Duration,
} }
/*
Obtain the current time.
*/
now :: proc "contextless" () -> Time { now :: proc "contextless" () -> Time {
return _now() return _now()
} }
/*
Sleep for the specified duration.
*/
sleep :: proc "contextless" (d: Duration) { sleep :: proc "contextless" (d: Duration) {
_sleep(d) _sleep(d)
} }
/*
Start the stopwatch.
*/
stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
if !stopwatch.running { if !stopwatch.running {
stopwatch._start_time = tick_now() stopwatch._start_time = tick_now()
@@ -67,6 +135,9 @@ stopwatch_start :: proc "contextless" (stopwatch: ^Stopwatch) {
} }
} }
/*
Stop the stopwatch.
*/
stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) { stopwatch_stop :: proc "contextless" (stopwatch: ^Stopwatch) {
if stopwatch.running { if stopwatch.running {
stopwatch._accumulation += tick_diff(stopwatch._start_time, tick_now()) 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_reset :: proc "contextless" (stopwatch: ^Stopwatch) {
stopwatch._accumulation = {} stopwatch._accumulation = {}
stopwatch.running = false 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 { stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
if !stopwatch.running { if !stopwatch.running {
return stopwatch._accumulation return stopwatch._accumulation
@@ -86,40 +167,92 @@ stopwatch_duration :: proc "contextless" (stopwatch: Stopwatch) -> Duration {
return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now()) return stopwatch._accumulation + tick_diff(stopwatch._start_time, tick_now())
} }
/*
Calculate the duration elapsed between two times.
*/
diff :: proc "contextless" (start, end: Time) -> Duration { diff :: proc "contextless" (start, end: Time) -> Duration {
d := end._nsec - start._nsec d := end._nsec - start._nsec
return Duration(d) return Duration(d)
} }
/*
Calculate the duration elapsed since a specific time.
*/
since :: proc "contextless" (start: Time) -> Duration { since :: proc "contextless" (start: Time) -> Duration {
return diff(start, now()) return diff(start, now())
} }
/*
Obtain the number of nanoseconds in a duration.
*/
duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 {
return i64(d) return i64(d)
} }
/*
Obtain the number of microseconds in a duration.
*/
duration_microseconds :: proc "contextless" (d: Duration) -> f64 { duration_microseconds :: proc "contextless" (d: Duration) -> f64 {
return duration_seconds(d) * 1e6 return duration_seconds(d) * 1e6
} }
/*
Obtain the number of milliseconds in a duration.
*/
duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { duration_milliseconds :: proc "contextless" (d: Duration) -> f64 {
return duration_seconds(d) * 1e3 return duration_seconds(d) * 1e3
} }
/*
Obtain the number of seconds in a duration.
*/
duration_seconds :: proc "contextless" (d: Duration) -> f64 { duration_seconds :: proc "contextless" (d: Duration) -> f64 {
sec := d / Second sec := d / Second
nsec := d % Second nsec := d % Second
return f64(sec) + f64(nsec)/1e9 return f64(sec) + f64(nsec)/1e9
} }
/*
Obtain the number of minutes in a duration.
*/
duration_minutes :: proc "contextless" (d: Duration) -> f64 { duration_minutes :: proc "contextless" (d: Duration) -> f64 {
min := d / Minute min := d / Minute
nsec := d % Minute nsec := d % Minute
return f64(min) + f64(nsec)/(60*1e9) return f64(min) + f64(nsec)/(60*1e9)
} }
/*
Obtain the number of hours in a duration.
*/
duration_hours :: proc "contextless" (d: Duration) -> f64 { duration_hours :: proc "contextless" (d: Duration) -> f64 {
hour := d / Hour hour := d / Hour
nsec := d % Hour nsec := d % Hour
return f64(hour) + f64(nsec)/(60*60*1e9) 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 { duration_round :: proc "contextless" (d, m: Duration) -> Duration {
_less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool { _less_than_half :: #force_inline proc "contextless" (x, y: Duration) -> bool {
return u64(x)+u64(x) < u64(y) return u64(x)+u64(x) < u64(y)
@@ -149,50 +282,103 @@ duration_round :: proc "contextless" (d, m: Duration) -> Duration {
return MAX_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 { duration_truncate :: proc "contextless" (d, m: Duration) -> Duration {
return d if m <= 0 else d - d%m 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) { date :: proc "contextless" (t: Time) -> (year: int, month: Month, day: int) {
year, month, day, _ = _abs_date(_time_abs(t), true) year, month, day, _ = _abs_date(_time_abs(t), true)
return return
} }
/*
Obtain the year of the date specified by time.
*/
year :: proc "contextless" (t: Time) -> (year: int) { year :: proc "contextless" (t: Time) -> (year: int) {
year, _, _, _ = _date(t, true) year, _, _, _ = _date(t, true)
return return
} }
/*
Obtain the month of the date specified by time.
*/
month :: proc "contextless" (t: Time) -> (month: Month) { month :: proc "contextless" (t: Time) -> (month: Month) {
_, month, _, _ = _date(t, true) _, month, _, _ = _date(t, true)
return return
} }
/*
Obtain the day of the date specified by time.
*/
day :: proc "contextless" (t: Time) -> (day: int) { day :: proc "contextless" (t: Time) -> (day: int) {
_, _, day, _ = _date(t, true) _, _, day, _ = _date(t, true)
return return
} }
/*
Obtain the week day of the date specified by time.
*/
weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) { weekday :: proc "contextless" (t: Time) -> (weekday: Weekday) {
abs := _time_abs(t) abs := _time_abs(t)
sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK sec := (abs + u64(Weekday.Monday) * SECONDS_PER_DAY) % SECONDS_PER_WEEK
return Weekday(int(sec) / SECONDS_PER_DAY) 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 } 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) { clock_from_time :: proc "contextless" (t: Time) -> (hour, min, sec: int) {
return clock_from_seconds(_time_abs(t)) 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) { clock_from_duration :: proc "contextless" (d: Duration) -> (hour, min, sec: int) {
return clock_from_seconds(u64(d/1e9)) 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) { clock_from_stopwatch :: proc "contextless" (s: Stopwatch) -> (hour, min, sec: int) {
return clock_from_duration(stopwatch_duration(s)) 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) { clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
sec = int(nsec % SECONDS_PER_DAY) sec = int(nsec % SECONDS_PER_DAY)
hour = sec / SECONDS_PER_HOUR hour = sec / SECONDS_PER_HOUR
@@ -202,10 +388,16 @@ clock_from_seconds :: proc "contextless" (nsec: u64) -> (hour, min, sec: int) {
return return
} }
/*
Read the timestamp counter of the CPU.
*/
read_cycle_counter :: proc "contextless" () -> u64 { read_cycle_counter :: proc "contextless" () -> u64 {
return u64(intrinsics.read_cycle_counter()) return u64(intrinsics.read_cycle_counter())
} }
/*
Obtain time from unix seconds and unix nanoseconds.
*/
unix :: proc "contextless" (sec: i64, nsec: i64) -> Time { unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
sec, nsec := sec, nsec sec, nsec := sec, nsec
if nsec < 0 || nsec >= 1e9 { if nsec < 0 || nsec >= 1e9 {
@@ -220,31 +412,59 @@ unix :: proc "contextless" (sec: i64, nsec: i64) -> Time {
return Time{(sec*1e9 + nsec)} return Time{(sec*1e9 + nsec)}
} }
/*
Obtain time from unix nanoseconds.
*/
from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time { from_nanoseconds :: #force_inline proc "contextless" (nsec: i64) -> Time {
return Time{nsec} return Time{nsec}
} }
/*
Alias for `time_to_unix`.
*/
to_unix_seconds :: time_to_unix to_unix_seconds :: time_to_unix
/*
Obtain the unix seconds from a time.
*/
time_to_unix :: proc "contextless" (t: Time) -> i64 { time_to_unix :: proc "contextless" (t: Time) -> i64 {
return t._nsec/1e9 return t._nsec/1e9
} }
/*
Alias for `time_to_unix_nano`.
*/
to_unix_nanoseconds :: 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 { time_to_unix_nano :: proc "contextless" (t: Time) -> i64 {
return t._nsec return t._nsec
} }
/*
Add duration to a time.
*/
time_add :: proc "contextless" (t: Time, d: Duration) -> Time { time_add :: proc "contextless" (t: Time, d: Duration) -> Time {
return Time{t._nsec + i64(d)} return Time{t._nsec + i64(d)}
} }
// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/ /*
// Accurate sleep
// 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. This procedure sleeps for the duration specified by `d`, very accurately.
// 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. **Note**: Implementation borrowed from: [this source](https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/)
// Additionally your program should call `windows.timeEndPeriod(1)` once you're done with `accurate_sleep`.
**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) { accurate_sleep :: proc "contextless" (d: Duration) {
to_sleep, estimate, mean, m2, count: 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 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) { 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) this_date, err := dt.components_to_datetime(year, month, day, hour, minute, second, nsec)
if err != .None { 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) 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) { compound_to_time :: proc "contextless" (datetime: dt.DateTime) -> (t: Time, ok: bool) {
unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}} unix_epoch := dt.DateTime{{1970, 1, 1}, {0, 0, 0, 0}}
delta, err := dt.sub(datetime, unix_epoch) 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 return Time{_nsec=i64(nanoseconds)}, true
} }
/*
Convert datetime components into time.
*/
datetime_to_time :: proc{components_to_time, compound_to_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) { is_leap_year :: proc "contextless" (year: int) -> (leap: bool) {
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) 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) @(rodata)
days_before := [?]i32{ days_before := [?]i32{
0, 0,
@@ -410,11 +652,37 @@ days_before := [?]i32{
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, 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 SECONDS_PER_MINUTE :: 60
/*
Number of seconds in an hour (without leap seconds).
*/
SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE SECONDS_PER_HOUR :: 60 * SECONDS_PER_MINUTE
/*
Number of seconds in a day (without leap seconds).
*/
SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR SECONDS_PER_DAY :: 24 * SECONDS_PER_HOUR
/*
Number of seconds in a week (without leap seconds).
*/
SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY SECONDS_PER_WEEK :: 7 * SECONDS_PER_DAY
/*
Days in 400 years, with leap days.
*/
DAYS_PER_400_YEARS :: 365*400 + 97 DAYS_PER_400_YEARS :: 365*400 + 97
/*
Days in 100 years, with leap days.
*/
DAYS_PER_100_YEARS :: 365*100 + 24 DAYS_PER_100_YEARS :: 365*100 + 24
/*
Days in 4 years, with leap days.
*/
DAYS_PER_4_YEARS :: 365*4 + 1 DAYS_PER_4_YEARS :: 365*4 + 1