Skip to content

Commit 77941f2

Browse files
committed
Time: support nanosecond/native unit
Signed-off-by: Peter M <petermm@gmail.com>
1 parent 209835d commit 77941f2

File tree

9 files changed

+178
-11
lines changed

9 files changed

+178
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7878
- Added `network:sta_status/0` to get the current connection state of the sta interface.
7979
- Added ESP32 `-DATOMVM_ELIXIR_SUPPORT=on` configuration option
8080
- Added support for ESP32 development builds to include NVS partition data at build time
81+
- Added `nanosecond` and `native` time unit support to `erlang:system_time/1`, `erlang:monotonic_time/1`, and `calendar:system_time_to_universal_time/2`
8182

8283
### Changed
8384

doc/src/programmers-guide.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -679,20 +679,22 @@ Use [`erlang:timestamp/0`](./apidocs/erlang/estdlib/erlang.md#timestamp0) to get
679679
{MegaSecs, Secs, MicroSecs} = erlang:timestamp().
680680
```
681681

682-
Use [`erlang:system_time/1`](./apidocs/erlang/estdlib/erlang.md#system_time1) to obtain the seconds, milliseconds or microseconds since the UNIX epoch (Midnight, Jan 1, 1970, UTC):
682+
Use [`erlang:system_time/1`](./apidocs/erlang/estdlib/erlang.md#system_time1) to obtain the seconds, milliseconds, microseconds or nanoseconds since the UNIX epoch (Midnight, Jan 1, 1970, UTC):
683683

684684
```erlang
685685
Seconds = erlang:system_time(second).
686686
MilliSeconds = erlang:system_time(millisecond).
687687
MicroSeconds = erlang:system_time(microsecond).
688+
NanoSeconds = erlang:system_time(nanosecond).
688689
```
689690

690-
Use [`erlang:monotonic_time/1`](./apidocs/erlang/estdlib/erlang.md#monotonic_time1) to obtain a (possibly not strictly) monotonically increasing time measurement. Use the same time units to convert to seconds, milliseconds, or microseconds:
691+
Use [`erlang:monotonic_time/1`](./apidocs/erlang/estdlib/erlang.md#monotonic_time1) to obtain a (possibly not strictly) monotonically increasing time measurement. Use the same time units to convert to seconds, milliseconds, microseconds or nanoseconds:
691692

692693
```erlang
693694
Seconds = erlang:monotonic_time(second).
694695
MilliSeconds = erlang:monotonic_time(millisecond).
695696
MicroSeconds = erlang:monotonic_time(microsecond).
697+
NanoSeconds = erlang:monotonic_time(nanosecond).
696698
```
697699

698700
```{caution}
@@ -722,7 +724,7 @@ permission to set the system time.
722724

723725
On the ESP32 platform, you can use the Wifi network to set the system time automatically. For information about how to set system time on the ESP32 using SNTP, see the [Network Programming Guide](./network-programming-guide.md).
724726

725-
To convert a time (in seconds, milliseconds, or microseconds from the UNIX epoch) to a date-time, use the [`calendar:system_time_to_universal_time/2`](./apidocs/erlang/estdlib/calendar.md#system_time_to_universal_time2) function. For example,
727+
To convert a time (in seconds, milliseconds, microseconds, or nanoseconds from the UNIX epoch) to a date-time, use the [`calendar:system_time_to_universal_time/2`](./apidocs/erlang/estdlib/calendar.md#system_time_to_universal_time2) function. For example,
726728

727729
```erlang
728730
Milliseconds = ... %% get milliseconds from the UNIX epoch
@@ -731,7 +733,7 @@ Milliseconds = ... %% get milliseconds from the UNIX epoch
731733
} = calendar:system_time_to_universal_time(Milliseconds, millisecond).
732734
```
733735

734-
Valid time units are `second`, `millisecond`, and `microsecond`.
736+
Valid time units are `second`, `millisecond`, `microsecond`, and `nanosecond`.
735737

736738
### Date and Time
737739

libs/estdlib/src/erlang.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@
155155
-type atom_encoding() :: latin1 | utf8 | unicode.
156156

157157
-type mem_type() :: binary.
158-
-type time_unit() :: second | millisecond | microsecond.
158+
-type time_unit() :: second | millisecond | microsecond | nanosecond | native.
159159
-type timestamp() :: {
160160
MegaSecs :: non_neg_integer(), Secs :: non_neg_integer(), MicroSecs :: non_neg_integer
161161
}.

src/libAtomVM/defaultatoms.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ X(ATOMVM_VERSION_ATOM, "\xE", "atomvm_version")
9797
X(SECOND_ATOM, "\x6", "second")
9898
X(MILLISECOND_ATOM, "\xB", "millisecond")
9999
X(MICROSECOND_ATOM, "\xB", "microsecond")
100+
X(NANOSECOND_ATOM, "\xA", "nanosecond")
100101

101102
X(INFINITY_ATOM, "\x8", "infinity")
102103
X(TIMEOUT_VALUE_ATOM, "\xD", "timeout_value")

src/libAtomVM/nifs.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,6 +1726,9 @@ term nif_erlang_monotonic_time_1(Context *ctx, int argc, term argv[])
17261726
} else if (argv[0] == MICROSECOND_ATOM) {
17271727
return make_maybe_boxed_int64(ctx, ((int64_t) ts.tv_sec) * 1000000UL + ts.tv_nsec / 1000UL);
17281728

1729+
} else if (argv[0] == NANOSECOND_ATOM || argv[0] == NATIVE_ATOM) {
1730+
return make_maybe_boxed_int64(ctx, ((int64_t) ts.tv_sec) * 1000000000ULL + ts.tv_nsec);
1731+
17291732
} else {
17301733
RAISE_ERROR(BADARG_ATOM);
17311734
}
@@ -1748,6 +1751,9 @@ term nif_erlang_system_time_1(Context *ctx, int argc, term argv[])
17481751
} else if (argv[0] == MICROSECOND_ATOM) {
17491752
return make_maybe_boxed_int64(ctx, ((int64_t) ts.tv_sec) * 1000000UL + ts.tv_nsec / 1000UL);
17501753

1754+
} else if (argv[0] == NANOSECOND_ATOM || argv[0] == NATIVE_ATOM) {
1755+
return make_maybe_boxed_int64(ctx, ((int64_t) ts.tv_sec) * 1000000000ULL + ts.tv_nsec);
1756+
17511757
} else {
17521758
RAISE_ERROR(BADARG_ATOM);
17531759
}
@@ -1876,6 +1882,10 @@ term nif_calendar_system_time_to_universal_time_2(Context *ctx, int argc, term a
18761882
ts.tv_sec = (time_t) (value / 1000000);
18771883
ts.tv_nsec = (value % 1000000) * 1000;
18781884

1885+
} else if (argv[1] == NANOSECOND_ATOM || argv[1] == NATIVE_ATOM) {
1886+
ts.tv_sec = (time_t) (value / 1000000000);
1887+
ts.tv_nsec = (value % 1000000000) * 1;
1888+
18791889
} else {
18801890
RAISE_ERROR(BADARG_ATOM);
18811891
}

src/platforms/esp32/test/main/test_erl_sources/test_monotonic_time.erl

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,51 @@
2323

2424
start() ->
2525
ok = test_monotonic_time(),
26+
ok = test_monotonic_time_nanosecond(),
27+
ok = test_monotonic_time_native(),
28+
ok = test_time_unit_ratios(),
2629
ok.
2730

2831
test_monotonic_time() ->
2932
test_monotonic_time([500, 1000, 1500, 2500, 5000]).
3033

34+
test_monotonic_time_nanosecond() ->
35+
DelayMs = 200,
36+
T1 = erlang:monotonic_time(nanosecond),
37+
timer:sleep(DelayMs),
38+
T2 = erlang:monotonic_time(nanosecond),
39+
Diff = T2 - T1,
40+
true = Diff >= DelayMs * 1000000,
41+
ok.
42+
43+
test_monotonic_time_native() ->
44+
DelayMs = 200,
45+
T1 = erlang:monotonic_time(native),
46+
timer:sleep(DelayMs),
47+
T2 = erlang:monotonic_time(native),
48+
Diff = T2 - T1,
49+
true = Diff >= DelayMs * 1000000,
50+
ok.
51+
52+
% Readings coarsest-to-finest: finer value is always taken later so
53+
% finer >= coarser * scale. Upper bound allows 1 ms between calls.
54+
test_time_unit_ratios() ->
55+
S = erlang:monotonic_time(second),
56+
Ms = erlang:monotonic_time(millisecond),
57+
Us = erlang:monotonic_time(microsecond),
58+
Ns = erlang:monotonic_time(nanosecond),
59+
60+
true = Ms >= S * 1000,
61+
true = Ms < S * 1000 + 1000,
62+
63+
true = Us >= Ms * 1000,
64+
true = Us < Ms * 1000 + 1000000,
65+
66+
true = Ns >= Us * 1000,
67+
true = Ns < Us * 1000 + 1000000,
68+
69+
ok.
70+
3171
test_monotonic_time([]) ->
3272
ok;
3373
test_monotonic_time([Delay | Tail]) ->

src/platforms/rp2/tests/test_erl_sources/test_clocks.erl

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,31 @@
2525
-define(BEFORE_START_DATE, 1693769340).
2626

2727
start() ->
28-
test_clock(system_time, fun() -> erlang:system_time(millisecond) end),
29-
test_clock(monotonic_time, fun() -> erlang:monotonic_time(millisecond) end),
28+
test_clock(system_time, fun() -> erlang:system_time(millisecond) end, 100, 500),
29+
test_clock(monotonic_time, fun() -> erlang:monotonic_time(millisecond) end, 100, 500),
30+
test_clock(
31+
system_time_nanosecond, fun() -> erlang:system_time(nanosecond) end, 100000000, 500000000
32+
),
33+
test_clock(
34+
monotonic_time_nanosecond,
35+
fun() -> erlang:monotonic_time(nanosecond) end,
36+
100000000,
37+
500000000
38+
),
39+
test_clock(system_time_native, fun() -> erlang:system_time(native) end, 100000000, 500000000),
40+
test_clock(
41+
monotonic_time_native, fun() -> erlang:monotonic_time(native) end, 100000000, 500000000
42+
),
3043
Date = erlang:universaltime(),
3144
if
3245
Date < ?START_DATE ->
3346
pico:rtc_set_datetime(?START_DATE);
3447
true ->
3548
ok
3649
end,
37-
test_clock(system_time_after_set_rtc, fun() -> erlang:system_time(millisecond) end),
50+
ok = test_time_unit_ratios(system_time, fun(Unit) -> erlang:system_time(Unit) end),
51+
ok = test_time_unit_ratios(monotonic_time, fun(Unit) -> erlang:monotonic_time(Unit) end),
52+
test_clock(system_time_after_set_rtc, fun() -> erlang:system_time(millisecond) end, 100, 500),
3853
NewDate = erlang:universaltime(),
3954
if
4055
NewDate >= ?START_DATE -> ok;
@@ -50,14 +65,33 @@ start() ->
5065
end,
5166
ok.
5267

53-
test_clock(Case, Fun) ->
68+
% Readings coarsest-to-finest: finer value is always taken later so
69+
% finer >= coarser * scale. Upper bound allows 1 ms between calls.
70+
test_time_unit_ratios(Case, Fun) ->
71+
S = Fun(second),
72+
Ms = Fun(millisecond),
73+
Us = Fun(microsecond),
74+
Ns = Fun(nanosecond),
75+
76+
Ms >= S * 1000 orelse throw({ratio_fail, Case, second_to_ms, S, Ms}),
77+
Ms < S * 1000 + 1000 orelse throw({ratio_upper, Case, second_to_ms, S, Ms}),
78+
79+
Us >= Ms * 1000 orelse throw({ratio_fail, Case, ms_to_us, Ms, Us}),
80+
Us < Ms * 1000 + 1000000 orelse throw({ratio_upper, Case, ms_to_us, Ms, Us}),
81+
82+
Ns >= Us * 1000 orelse throw({ratio_fail, Case, us_to_ns, Us, Ns}),
83+
Ns < Us * 1000 + 1000000 orelse throw({ratio_upper, Case, us_to_ns, Us, Ns}),
84+
85+
ok.
86+
87+
test_clock(Case, Fun, MinDelta, MaxDelta) ->
5488
StartTime = Fun(),
5589
receive
5690
after 100 -> ok
5791
end,
5892
EndTime = Fun(),
5993
Delta = EndTime - StartTime,
6094
if
61-
Delta >= 100 andalso Delta < 500 -> ok;
95+
Delta >= MinDelta andalso Delta < MaxDelta -> ok;
6296
true -> throw({unexpected_delta, Delta, Case, StartTime, EndTime})
6397
end.

tests/erlang_tests/test_monotonic_time.erl

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,33 @@ start() ->
2828
after 1 -> ok
2929
end,
3030
T2 = erlang:monotonic_time(millisecond),
31-
test_diff(T2 - T1).
31+
1 = test_diff(T2 - T1),
32+
33+
N1 = erlang:monotonic_time(nanosecond),
34+
receive
35+
after 1 -> ok
36+
end,
37+
N2 = erlang:monotonic_time(nanosecond),
38+
true = is_integer(N2 - N1) andalso (N2 - N1) >= 0,
39+
40+
ok = test_native_monotonic_time(),
41+
42+
1.
3243

3344
test_diff(X) when is_integer(X) andalso X >= 0 ->
3445
1;
3546
test_diff(X) when X < 0 ->
3647
0.
48+
49+
-if(?OTP_RELEASE >= 22).
50+
test_native_monotonic_time() ->
51+
Na1 = erlang:monotonic_time(native),
52+
receive
53+
after 1 -> ok
54+
end,
55+
Na2 = erlang:monotonic_time(native),
56+
true = is_integer(Na2 - Na1) andalso (Na2 - Na1) >= 0,
57+
ok.
58+
-else.
59+
test_native_monotonic_time() -> ok.
60+
-endif.

tests/erlang_tests/test_system_time.erl

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ start() ->
2626
ok = test_system_time(second, 1001),
2727
ok = test_system_time(millisecond, 10),
2828
ok = test_system_time(microsecond, 1),
29+
ok = test_system_time(nanosecond, 1),
30+
ok = test_native_system_time(),
31+
32+
ok = test_time_unit_ratios(),
2933

3034
ok = expect(fun() -> erlang:system_time(not_a_time_unit) end, badarg),
3135

@@ -40,6 +44,34 @@ test_system_time(Unit, SleepMs) ->
4044
true = (After > Before),
4145
ok.
4246

47+
test_system_time_unit(_Name, Fun) ->
48+
T = Fun(),
49+
true = is_integer(T) andalso T > 0,
50+
ok.
51+
52+
% Readings are taken coarsest-to-finest so the finer value is always >= the
53+
% coarser value * its scale factor. The upper-bound allows for at most 1 ms
54+
% of wall-clock time between consecutive calls.
55+
test_time_unit_ratios() ->
56+
S = erlang:system_time(second),
57+
Ms = erlang:system_time(millisecond),
58+
Us = erlang:system_time(microsecond),
59+
Ns = erlang:system_time(nanosecond),
60+
61+
true = Ms >= S * 1000,
62+
% within 1 s of each other
63+
true = Ms < S * 1000 + 1000,
64+
65+
true = Us >= Ms * 1000,
66+
% within 1 ms
67+
true = Us < Ms * 1000 + 1000000,
68+
69+
true = Ns >= Us * 1000,
70+
% within 1 ms
71+
true = Ns < Us * 1000 + 1000000,
72+
73+
ok.
74+
4375
verify_system_time_value(M) when is_integer(M) andalso M > 0 ->
4476
M.
4577

@@ -76,4 +108,27 @@ test_system_time_to_universal_time() ->
76108

77109
{{1969, 12, 31}, {23, 59, 59}} = calendar:system_time_to_universal_time(-1, second),
78110

111+
{{1970, 1, 1}, {0, 0, 0}} = calendar:system_time_to_universal_time(0, nanosecond),
112+
{{1970, 1, 1}, {0, 0, 0}} = calendar:system_time_to_universal_time(1, nanosecond),
113+
{{1970, 1, 1}, {0, 0, 1}} = calendar:system_time_to_universal_time(1000000000, nanosecond),
114+
{{1970, 1, 1}, {0, 0, 1}} = calendar:system_time_to_universal_time(1001000000, nanosecond),
115+
{{2023, 7, 8}, {20, 19, 39}} = calendar:system_time_to_universal_time(
116+
1688847579000000000, nanosecond
117+
),
118+
119+
ok = test_native_universal_time(),
120+
121+
ok.
122+
123+
-if(?OTP_RELEASE >= 22).
124+
test_native_system_time() ->
125+
ok = test_system_time(native, 1).
126+
127+
test_native_universal_time() ->
128+
{{1970, 1, 1}, {0, 0, 0}} = calendar:system_time_to_universal_time(0, native),
129+
{{1970, 1, 1}, {0, 0, 1}} = calendar:system_time_to_universal_time(1000000000, native),
79130
ok.
131+
-else.
132+
test_native_system_time() -> ok.
133+
test_native_universal_time() -> ok.
134+
-endif.

0 commit comments

Comments
 (0)