Verified Commit 7f0cb46b authored by Yorick Peterse's avatar Yorick Peterse 🌴

Reorganise and test std::time

std::time is now broken up into two modules:

1. std::time, which provides time related types.
2. std::time::duration, which provides the Duration type.

This separation allows for the use of module methods to construct a
Duration using different time units, instead of using methods defined on
Duration itself. This means that instead of this:

    import std::time::Duration

    Duration.from_seconds(5)

You now write this:

    import std::time::duration

    duration.from_seconds(5)

== Renaming MonotonicTime to Instant

MonotonicTime has been renamed to "Instant", which is a bit shorter and
much easier to type. A new method has been added as well: "elapsed".
This method can be used to measure the time that has elapsed since the
Instant was created.

== Adding and subtracting SystemTime and Instant

The methods + and - for SystemTime and Instant no longer accept a
ToFloat, instead they require a Duration. Accepting a ToFloat would
allow you to add a SystemTime to another SystemTime, or add an Instant
to an Instant. Neither make sense, so instead we now require the use of
a Duration. This means that instead of this:

    import std::time

    time.now + 5

You now have to write this:

    import std::time
    import std::time::duration

    time.now + duration.from_seconds(5)

== Tests

Finally, both std::time and std::time::duration have tests. These tests
uncovered a few bugs in the implementation of SystemTime, which have
been resolved.
parent f9c07ef6
Pipeline #41277834 passed with stages
in 15 minutes and 11 seconds
......@@ -3,7 +3,7 @@
#! The `Formatter` trait is used by a `Runner` to display test results as they
#! finish, and to display a summary once all tests have been executed.
import std::test::test::Test
import std::time::Duration
import std::time::duration::Duration
## A type for formatting the results of tests.
trait Formatter {
......
......@@ -10,7 +10,7 @@ import std::string_buffer::StringBuffer
import std::test::error::TestFailure
import std::test::formatter::Formatter
import std::test::test::Test
import std::time::Duration
import std::time::duration::Duration
## The minimum amount of time (in seconds) the test suite should run, before
## displaying the execution time in seconds.
......
......@@ -21,7 +21,8 @@ import std::process::(self, Receiver, Sender)
import std::test::config::Configuration
import std::test::formatter::Formatter
import std::test::test::Test
import std::time::(self, Duration)
import std::time
import std::time::duration
import std::vm
## The exit status to use if one or more tests failed to run.
......@@ -119,7 +120,7 @@ object RunTests impl Command {
## Once this method has been called, no new tests can be registered.
def run(state: RunnerState) {
let mut pending = 0
let start_time = time.monotonic
let start_time = time.instant
let last_index = state.tests.length - 1
let executed = []
let failed = []
......@@ -152,7 +153,7 @@ object RunTests impl Command {
}
}
let duration = Duration.from_seconds((time.monotonic - start_time).to_float)
let duration = start_time.elapsed
failed.empty?.if_false {
state.formatter.failures(failed)
......
#! Types and methods for dealing with time.
import std::conversion::(ToFloat, ToInteger)
import std::error::StandardError
import std::operators::(Add, Subtract)
import std::operators::(Smaller, Greater, GreaterOrEqual, SmallerOrEqual, Equal)
import std::operators::(
Add, Equal, Greater, GreaterOrEqual, Smaller, SmallerOrEqual, Subtract
)
import std::time::constants::*
## Error thrown when a `String` could not be parsed into a `SystemTime`.
let ParseError = StandardError
## A span of time measured in seconds.
##
## A `Duration` can be used to measure the span of time without having to worry
## about casting the time to different scales yourself. A `Duration` can be
## created using various scales such as seconds and milliseconds.
object Duration impl
ToInteger,
ToFloat,
Add!(Self),
Subtract!(Self),
Smaller,
Greater,
SmallerOrEqual,
GreaterOrEqual {
## Creates a new `Duration` from the given number of seconds.
##
## # Examples
##
## Creating a `Duration` using an `Integer`:
##
## import std::time::Duration
##
## Duration.from_seconds(10)
##
## Creating a `Duration` using a `Float`:
##
## import std::time::Duration
##
## Duration.from_seconds(10.5)
def from_seconds(seconds: ToFloat) -> Self {
new(seconds.to_float)
}
## Creates a new `Duration` from the given number of milliseconds.
##
## # Examples
##
## Creating a `Duration` using an `Integer`:
##
## import std::time::Duration
##
## Duration.from_milliseconds(10)
##
## Creating a `Duration` using a `Float`:
##
## import std::time::Duration
##
## Duration.from_milliseconds(10.5)
def from_milliseconds(milliseconds: ToFloat) -> Self {
new(milliseconds.to_float / MILLISEC_TO_SEC)
}
## Creates a new `Duration` from the given number of nanoseconds.
##
## # Examples
##
## Creating a `Duration` using an `Integer`:
##
## import std::time::Duration
##
## Duration.from_nanoseconds(10)
##
## Creating a `Duration` using a `Float`:
##
## import std::time::Duration
##
## Duration.from_nanoseconds(10.5)
def from_nanoseconds(nanoseconds: ToFloat) -> Self {
new(nanoseconds.to_float / NANOSEC_TO_SEC)
}
def init(seconds: Float) {
let @seconds = seconds
}
## Returns the duration in seconds.
##
## # Examples
##
## Getting the seconds in a `Duration`:
##
## import std::time::Duration
##
## let duration = Duration.from_seconds(5)
##
## duration.as_seconds # => 5.0
def as_seconds -> Float {
@seconds
}
## Returns the duration in milliseconds.
##
## # Examples
##
## Getting the milliseconds in a `Duration`:
##
## import std::time::Duration
##
## let duration = Duration.from_seconds(5)
##
## duration.as_milliseconds # => 5000.0
def as_milliseconds -> Float {
@seconds * MILLISEC_TO_SEC
}
## Returns the duration in microseconds.
##
## # Examples
##
## Getting the microseconds in a `Duration`:
##
## import std::time::Duration
##
## let duration = Duration.from_seconds(5)
##
## duration.as_microseconds # => 5000000.0
def as_microseconds -> Float {
@seconds * MICROSEC_TO_SEC
}
## Returns the duration in nanoseconds.
##
## # Examples
##
## Getting the nanoseconds in a `Duration`:
##
## import std::time::Duration
##
## let duration = Duration.from_seconds(5)
##
## duration.as_nanoseconds # => 5000000000.0
def as_nanoseconds -> Float {
@seconds * NANOSEC_TO_SEC
}
## Returns the number of seconds in the `Duration`.
def to_integer -> Integer {
@seconds.to_integer
}
## Returns the number of seconds in the `Duration`.
def to_float -> Float {
@seconds
}
## Adds two `Duration` objects together.
##
## # Examples
##
## Adding two `Duration` objects:
##
## import std::time::Duration
##
## let first = Duration.from_seconds(5)
## let second = Duration.from_seconds(2)
## let third = first + second
##
## third.as_seconds # => 7.0
def +(other: Self) -> Self {
Duration.new(@seconds + other.as_seconds)
}
## Subtracts the given `Duration` from `self`.
##
## # Examples
##
## Subtracting a `Duration` from another `Duration`
##
## import std::time::Duration
##
## let first = Duration.from_seconds(5)
## let second = Duration.from_seconds(2)
## let third = first - second
##
## third.as_seconds # => 3.0
def -(other: Self) -> Self {
Duration.new(@seconds - other.as_seconds)
}
## Returns `True` if `self` is smaller than the given `Duration`.
##
## # Examples
##
## Comparing two `Duration` objects:
##
## import std::time::Duration
##
## Duration.from_seconds(5) < Duration.from_seconds(10) # => True
def <(other: Self) -> Boolean {
@seconds < other.as_seconds
}
## Returns `True` if `self` is greater than the given `Duration`.
##
## # Examples
##
## Comparing two `Duration` objects:
##
## import std::time::Duration
##
## Duration.from_seconds(5) > Duration.from_seconds(10) # => False
def >(other: Self) -> Boolean {
@seconds > other.as_seconds
}
## Returns `True` if `self` is smaller than or equal to the given `Duration`.
##
## # Examples
##
## Comparing two `Duration` objects:
##
## import std::time::Duration
##
## Duration.from_seconds(5) <= Duration.from_seconds(10) # => True
## Duration.from_seconds(5) <= Duration.from_seconds(5) # => True
def <=(other: Self) -> Boolean {
@seconds <= other.as_seconds
}
## Returns `True` if `self` is greater than or equal to the given `Duration`.
##
## # Examples
##
## Comparing two `Duration` objects:
##
## import std::time::Duration
##
## Duration.from_seconds(5) >= Duration.from_seconds(10) # => False
## Duration.from_seconds(5) >= Duration.from_seconds(5) # => True
def >=(other: Self) -> Boolean {
@seconds >= other.as_seconds
}
}
import std::time::duration::Duration
## An object representing the current system time.
##
......@@ -249,12 +12,12 @@ object Duration impl
## the Unix epoch. Due to the use of the system clock an instance of
## `SystemTime` can be influenced by time zone changes, clock adjustments, or
## leap seconds. If you need a monotonically increasing clock you should use
## `MonotonicTime` instead.
## `Instant` instead.
object SystemTime impl
ToInteger,
ToFloat,
Add!(ToFloat),
Subtract!(ToFloat),
Add!(Duration),
Subtract!(Duration),
Smaller,
SmallerOrEqual,
Greater,
......@@ -263,7 +26,7 @@ object SystemTime impl
def init(
year = 0,
month = 1 ,
month = 1,
day = 1,
hour = 0,
minute = 0,
......@@ -322,16 +85,43 @@ object SystemTime impl
##
## Per ISO 8601 the first day of the week starts on Monday, not Sunday.
def day_of_week -> Integer {
let day_of_week = days_since_unix_epoch % DAYS_PER_WEEK
day_of_week.zero?.if true: {
DAYS_PER_WEEK
}, false: {
day_of_week + 1
}
}
## Returns the day of the year from 1 to 366.
# January 1st, 1970 (our anchor date) was on a Thursday. We add 3 so that
# Monday (3 days before Thursday) becomes the anchor date.
#
# We later on add 1 since the % operator will return 0 for Monday (since its
# the first value in the range), but week days range from 1 to 7; not 0 to
# 6.
#
# The following table should help illustrate this:
#
# | Date | Day of week | days_since_unix_epoch
# |:-----------|:------------|:----------------------
# | 1969-12-29 | Monday | -3
# | 1969-12-30 | Tuesday | -2
# | 1969-12-31 | Wednesday | -1
# | 1970-01-01 | Thursday | 0
# | 1970-01-02 | Friday | 1
# | 1970-01-03 | Saturday | 2
# | 1970-01-04 | Sunday | 3
#
# For these dates, the calculations would be as follows:
#
# | Date | Calculation | Simplified | Return value
# |:-----------|:-------------------|:------------|:------------
# | 1969-12-29 | ((-3 + 3) % 7) + 1 | (0 % 7) + 1 | 1
# | 1969-12-30 | ((-2 + 3) % 7) + 1 | (1 % 7) + 1 | 2
# | 1969-12-31 | ((-1 + 3) % 7) + 1 | (2 % 7) + 1 | 3
# | 1970-01-01 | ((0 + 3) % 7) + 1 | (3 % 7) + 1 | 4
# | 1970-01-02 | ((1 + 3) % 7) + 1 | (4 % 7) + 1 | 5
# | 1970-01-03 | ((2 + 3) % 7) + 1 | (5 % 7) + 1 | 6
# | 1970-01-04 | ((3 + 3) % 7) + 1 | (6 % 7) + 1 | 7
days_since_unix_epoch + 3
% DAYS_PER_WEEK
+ 1
}
## Returns the day of the year from 1 to 366 for leap years, and from 1 to 365
## for regular years.
def day_of_year -> Integer {
let table = leap_year?
.if true: {
......@@ -340,12 +130,13 @@ object SystemTime impl
DAYS_SINCE_JANUARY_NORMAL
}
(*table[month - 1]) + day - 1
(*table[month - 1]) + day
}
## Returns the number of days between `self` and the Unix epoch.
##
## The returned `Integer` is always a positive value.
## The returned `Integer` is negative if `self` is before the Unix epoch, and
## positive for a value that is on or after the Unix epoch.
def days_since_unix_epoch -> Integer {
let years = year - UNIX_EPOCH_YEAR
.absolute
......@@ -354,14 +145,20 @@ object SystemTime impl
- (years / 100)
+ (years / 400)
let days = years
# The number of days since the Unix epoch, up until the first day of the
# year.
let days_until_year_start = years
* DAYS_PER_NORMAL_YEAR
+ leap_days
# The number of days since the start of this year, with a value of 0
# representing the first day of the year.
let days_since_year_start = day_of_year - 1
before_unix_epoch?.if true: {
days - day_of_year
0 - (days_until_year_start - days_since_year_start)
}, false: {
days + day_of_year
days_until_year_start + days_since_year_start
}
}
......@@ -396,8 +193,8 @@ object SystemTime impl
##
## This value will be negative if `self` is a `SystemTime` before the Unix
## epoch.
def seconds_since_epoch -> Integer {
let day_sec = days_since_unix_epoch * SECS_PER_DAY
def seconds_since_unix_epoch -> Integer {
let day_sec = days_since_unix_epoch.absolute * SECS_PER_DAY
let time_sec = (hour * SECS_PER_HOUR) + (minute * SECS_PER_MIN) + second
let seconds = before_unix_epoch?.if true: {
0 - (day_sec - time_sec)
......@@ -417,13 +214,13 @@ object SystemTime impl
## Returns the timestamp since the Unix epoch.
def to_integer -> Integer {
seconds_since_epoch
seconds_since_unix_epoch
}
## Returns the timestamp since the Unix epoch, the including fractional
## seconds.
def to_float -> Float {
seconds_since_epoch.to_float + sub_second
seconds_since_unix_epoch.to_float + sub_second
}
## Adds the given number of seconds to `self`, returning the result as a new
......@@ -433,16 +230,11 @@ object SystemTime impl
##
## Adding a `Duration` to a `SystemTime`:
##
## import std::time::(self, Duration, SystemTime)
##
## time.now + Duration.from_seconds(5)
##
## Adding an `Integer` to a `SystemTime`:
##
## import std::time
## import std::time::duration
##
## time.now + 5
def +(other: ToFloat) -> Self {
## time.now + duration.from_seconds(5)
def +(other: Duration) -> Self {
let timestamp = to_float + other.to_float
utc?.if true: {
......@@ -459,16 +251,11 @@ object SystemTime impl
##
## Subtracting a `Duration` from a `SystemTime`:
##
## import std::time::(self, Duration, SystemTime)
##
## time.now - Duration.from_seconds(5)
##
## Subtracting an `Integer` from a `SystemTime`:
##
## import std::time
## import std::time::duration
##
## time.now - 5
def -(other: ToFloat) -> Self {
## time.now - duration.from_seconds(5)
def -(other: Duration) -> Self {
let timestamp = to_float - other.to_float
utc?.if true: {
......@@ -491,13 +278,7 @@ object SystemTime impl
##
## before < after # => True
def <(other: Self) -> Boolean {
(year < other.year)
.and { month < other.month }
.and { day < other.day }
.and { hour < other.hour }
.and { minute < other.minute }
.and { second < other.second }
.and { sub_second < other.sub_second }
to_float < other.to_float
}
## Returns `True` if `self` is smaller than or equal to the given
......@@ -515,13 +296,7 @@ object SystemTime impl
## before <= after # => True
## before <= before # => True
def <=(other: Self) -> Boolean {
(year <= other.year)
.and { month <= other.month }
.and { day <= other.day }
.and { hour <= other.hour }
.and { minute <= other.minute }
.and { second <= other.second }
.and { sub_second <= other.sub_second }
to_float <= other.to_float
}
## Returns `True` if `self` is greater than the given `SystemTime`.
......@@ -537,13 +312,7 @@ object SystemTime impl
##
## after > before # => True
def >(other: Self) -> Boolean {
(year > other.year)
.and { month > other.month }
.and { day > other.day }
.and { hour > other.hour }
.and { minute > other.minute }
.and { second > other.second }
.and { sub_second > other.sub_second }
to_float > other.to_float
}
## Returns `True` if `self` is greater than or equal to the given
......@@ -561,13 +330,7 @@ object SystemTime impl
## after >= before # => True
## after >= after # => True
def >=(other: Self) -> Boolean {
(year >= other.year)
.and { month >= other.month }
.and { day >= other.day }
.and { hour >= other.hour }
.and { minute >= other.minute }
.and { second >= other.second }
.and { sub_second >= other.sub_second }
to_float >= other.to_float
}
## Returns `True` if `self` and the given `SystemTime` are equal to each
......@@ -583,32 +346,25 @@ object SystemTime impl
##
## now == now # => True
def ==(other: Self) -> Boolean {
year == other.year
.and { month == other.month }
.and { day == other.day }
.and { hour == other.hour }
.and { minute == other.minute }
.and { second == other.second }
.and { sub_second == other.sub_second }
.and { utc_offset == other.utc_offset }
to_float == other.to_float
}
}
## A monotonically increasing clock.
##
## `MonotonicTime` objects represent the number of seconds relative to an
## unspecified epoch. Unlike a system clock a monotonic clock never decreases,
## making it useful for tasks such as measuring the execution time of a block of
## code.
object MonotonicTime impl
## `Instant` objects represent the number of seconds relative to an unspecified
## epoch. Unlike a system clock a monotonic clock never decreases, making it
## useful for tasks such as measuring the execution time of a block of code.
object Instant impl
ToInteger,
ToFloat,
Add!(ToFloat),
Subtract!(ToFloat),
Add!(Duration),
Subtract!(Duration),
Smaller,
SmallerOrEqual,
Greater,
GreaterOrEqual {
GreaterOrEqual,
Equal {
def init(time = _INKOC.time_monotonic) {
let @time = time
......@@ -625,109 +381,137 @@ object MonotonicTime impl
}
## Adds the given number of seconds to `self`, returning the result as a new
## `MonotonicTime`.
## `Instant`.
##
## # Examples
##
## Adding a `Duration` to a `MonotonicTime`:
##
## import std::time::(Duration, MonotonicTime)
##
## MonotonicTime.new + Duration.from_seconds(5)
## Adding a `Duration` to a `Instant`:
##
## Adding an `Integer` to a `MonotonicTime`:
##
## import std::time::MonotonicTime
## import std::time
## import std::time::duration
##
## MonotonicTime.now + 5
def +(other: ToFloat) -> Self {
MonotonicTime.new(@time + other.to_float)
## time.instant + duration.from_seconds(5)
def +(other: Duration) -> Self {
Instant.new(@time + other.to_float)
}
## Subtracts the given number of seconds from `self`, returning the result as
## a new `MonotonicTime`.
##
## Subtracting a `Duration` from a `MonotonicTime`:
## a new `Instant`.
##
## import std::time::(Duration, MonotonicTime)
## Subtracting a `Duration` from a `Instant`:
##
## MonotonicTime.new - Duration.from_seconds(5)
##
## Subtracting an `Integer` from a `MonotonicTime`:
##
## import std::time::MonotonicTime
## import std::time
## import std::time::duration
##
## MonotonicTime.now - 5
def -(other: ToFloat) -> Self {
MonotonicTime.new(@time - other.to_float)
## time.instant - duration.from_seconds(5)
def -(other: Duration) -> Self {
Instant.new(@time - other.to_float)
}
## Returns `True` if `self` is smaller than the given `MonotonicTime`.
## Returns `True` if `self` is smaller than the given `Instant`.
##
## # Examples
##
## Comparing two `MonotonicTime` objects:
## Comparing two `Instant` objects:
##
## import std::time::MonotonicTime
## import std::time
## import std::time::duration
##
## let before = MonotonicTime.now
## let after = before + 5
## let before = time.instant
## let after = before + duration.from_seconds(5)
##
## before < after # => True
def <(other: Self) -> Boolean {
to_float < other.to_float
@time < other.to_float
}
## Returns `True` if `self` is smaller than or equal to the given
## `MonotonicTime`.
## `Instant`.
##
## # Examples
##
## Comparing two `MonotonicTime` objects:
## Comparing two `Instant` objects:
##
## import std::time::MonotonicTime
## import std::time
## import std::time::duration
##
## let before = MonotonicTime.now
## let after = before + 5
## let before = time.instant
## let after = before + duration.from_seconds(5)
##
## before <= after # => True
## before <= before # => True
def <=(other: Self) -> Boolean {
to_float <= other.to_float
@time <= other.to_float
}
## Returns `True` if `self` is greater than the given `MonotonicTime`.
## Returns `True` if `self` is greater than the given `Instant`.
##
## # Examples
##
## Comparing two `MonotonicTime` objects:
## Comparing two `Instant` objects:
##
## import std::time::MonotonicTime
## import std::time
## import std::time::duration
##
## let before = MonotonicTime.now
## let after = before + 5
## let before = time.instant
## let after = before + duration.from_seconds(5)
##
## after > before # => True
def >(other: Self) -> Boolean {
to_float > other.to_float
@time > other.to_float
}
## Returns `True` if `self` is greater than or equal to the given
## `MonotonicTime`.
## `Instant`.
##
## # Examples
##
## Comparing two `MonotonicTime` objects:
## Comparing two `Instant` objects:
##
## import std::time::MonotonicTime
## import std::time
## import std::time::duration
##
## let before = MonotonicTime.now
## let after = before + 5
## let before = time.instant
## let after = before + duration.from_seconds(5)
##
## after >= before # => True
## after >= after # => True
def >=(other: Self) -> Boolean {
to_float >= other.to_float
@time >= other.to_float
}
## Returns a `Duration` measuring the time elapsed since the point in time
## that `self` represents.
##
## # Examples
##
## Obtaining the time elapsed:
##
## import std::process
## import std::time
## import std::time::duration
##
## let start = time.instant
##
## process.suspend(duration.from_seconds(1))
##
## start.elapsed.to_integer # => 1
def elapsed -> Duration {
Duration.new(_INKOC.time_monotonic - @time)
}
## Returns `True` if `self` is equal to the given `Instant`.
##
## # Examples
##
## Comparing two `Instant` objects:
##
## import std::time::Instant
##
## Instant.new(1.0) == Instant.new(1.0) # => True
## Instant.new(1.0) == Instant.new(2.0) # => False
def ==(other: Self) -> Boolean {
@time == other.to_float
}
}
......@@ -770,17 +554,17 @@ def utc -> SystemTime {
from_utc_timestamp(_INKOC.time_system)
}
## Returns a new `MonotonicTime`.
## Returns a new `Instant`.
##
## # Examples
##
## Creating a new `MonotonicTime`:
## Creating a new `Instant`:
##
## import std::time
##
## time.monotonic
def monotonic -> MonotonicTime {