Skip to content

Commit 3c5b814

Browse files
Add Time::Instant (#16490)
Implements [RFC 15](crystal-lang/rfcs#15)
1 parent 1136464 commit 3c5b814

File tree

3 files changed

+162
-4
lines changed

3 files changed

+162
-4
lines changed

spec/std/time/instant_spec.cr

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
require "spec"
2+
3+
describe "Time.instant" do
4+
it "returns always increasing monotonic clock" do
5+
start = Time.instant
6+
loop do
7+
reading = Time.instant
8+
reading.should be >= start
9+
break if reading > start
10+
end
11+
end
12+
end
13+
14+
describe Time::Instant do
15+
describe "#<=>" do
16+
it "compares" do
17+
t1 = Time.instant
18+
sleep(1.nanosecond)
19+
t2 = Time.instant
20+
21+
(t1 <=> t2).should eq(-1)
22+
(t1 == t2).should be_false
23+
(t1 < t2).should be_true
24+
end
25+
end
26+
27+
describe "math" do
28+
it do
29+
t1 = Time.instant
30+
31+
(t1 + 1.second - 1.second).should eq t1
32+
end
33+
34+
it "associative" do
35+
t1 = Time.instant
36+
offset = 5.milliseconds
37+
38+
((t1 + offset) - t1).should eq (t1 - t1) + offset
39+
end
40+
41+
it "nanosecond precision" do
42+
t1 = Time.instant
43+
offset = 1.nanosecond
44+
45+
((t1 + offset) - t1).should eq offset
46+
end
47+
end
48+
49+
describe "#duration_since" do
50+
it "calculates" do
51+
t1 = Time.instant
52+
t2 = Time.instant
53+
duration = t2.duration_since(t1)
54+
55+
(t2 - duration).should eq(t1)
56+
(t1 + duration).should eq(t2)
57+
end
58+
59+
it "saturates" do
60+
t2 = Time.instant
61+
t1 = t2 - 1.second
62+
t1.duration_since(t2).should eq Time::Span::ZERO
63+
end
64+
end
65+
66+
describe "#elapsed" do
67+
it "calculates" do
68+
t1 = Time.instant - 12.microseconds
69+
t1.elapsed.should be >= 12.microseconds
70+
end
71+
end
72+
end

src/time.cr

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,13 @@ require "crystal/system/time"
194194
#
195195
# This monotonic clock should always be used for measuring elapsed time.
196196
#
197-
# A reading from this clock can be taken using `.monotonic`:
197+
# A reading from this clock can be taken using `.instant`:
198198
#
199199
# ```
200-
# t1 = Time.monotonic
200+
# t1 = Time.instant
201201
# # operation that takes 1 minute
202-
# t2 = Time.monotonic
203-
# t2 - t1 # => 1.minute (approximately)
202+
# t2 = Time.instant
203+
# t2.duration_since(t1) # => 1.minute (approximately)
204204
# ```
205205
#
206206
# The execution time of a block can be measured using `.measure`:

src/time/instant.cr

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Returns the current reading of the monotonic clock.
2+
#
3+
# The execution time of a block can be measured using `.measure`.
4+
def Time.instant : Time::Instant
5+
seconds, nanoseconds = Crystal::System::Time.monotonic
6+
Time::Instant.new(seconds: seconds, nanoseconds: nanoseconds)
7+
end
8+
9+
# `Time::Instant` represents a reading of a monotonic non-decreasing
10+
# clock for the purpose of measuring elapsed time or timing an event in the future.
11+
#
12+
# Instants are opaque values that have no public constructor or raw accessors by
13+
# default. They can only be obtained from the clock (`Time.instant`) and compared to one another.
14+
# The only useful values are differences between readings, represented as `Time::Span`.
15+
#
16+
# ## Clock properties
17+
#
18+
# Clock readings are guaranteed, barring platform bugs, to be no less than any
19+
# previous reading (monotonic clock).
20+
#
21+
# The measurement itself is expected to be fast (low latency) and precise within
22+
# nanosecond resolution. This means it might not provide the best available
23+
# accuracy for long-term measurements.
24+
#
25+
# The clock is not guaranteed to be steady. Ticks of the underlying clock might
26+
# vary in length. The clock may jump forwards or experience time dilation. But
27+
# it never goes backwards.
28+
#
29+
# The clock is expected to tick while the system is suspended. But this cannot be
30+
# guaranteed on all platforms.
31+
#
32+
# The clock is only guaranteed to apply to the current process. In practice, it is
33+
# usually relative to system boot, so should be interchangeable between processes.
34+
# But this is not guaranteed on all platforms.
35+
#
36+
# ## Example
37+
#
38+
# ```
39+
# start = Time.instant
40+
# # do something
41+
# elapsed = start.elapsed
42+
# ```
43+
#
44+
# `Time.measure(&)` is a convenient alternative for measuring elapsed time of an
45+
# individual code block.
46+
struct Time::Instant
47+
include Comparable(self)
48+
49+
# :nodoc:
50+
def initialize(@seconds : Int64, @nanoseconds : Int32)
51+
end
52+
53+
def -(other : self) : Time::Span
54+
Time::Span.new(seconds: @seconds - other.@seconds, nanoseconds: @nanoseconds - other.@nanoseconds)
55+
end
56+
57+
def +(other : Time::Span) : self
58+
span = Time::Span.new(seconds: @seconds, nanoseconds: @nanoseconds) + other
59+
Instant.new(span.@seconds, span.@nanoseconds)
60+
end
61+
62+
def -(other : Time::Span) : self
63+
span = Time::Span.new(seconds: @seconds, nanoseconds: @nanoseconds) - other
64+
Instant.new(span.@seconds, span.@nanoseconds)
65+
end
66+
67+
def <=>(other : self) : Int32
68+
cmp = @seconds <=> other.@seconds
69+
cmp = @nanoseconds <=> other.@nanoseconds if cmp == 0
70+
cmp
71+
end
72+
73+
# Returns the duration between `other` and `self`.
74+
#
75+
# The resulting duration is positive or zero.
76+
def duration_since(other : self) : Time::Span
77+
(self - other).clamp(Time::Span.zero..)
78+
end
79+
80+
# Returns the amount of time elapsed since `self`.
81+
#
82+
# The resulting duration is positive or zero.
83+
def elapsed : Time::Span
84+
Time.instant.duration_since(self)
85+
end
86+
end

0 commit comments

Comments
 (0)