Skip to content

Commit 1850de9

Browse files
committed
Fix calendar:system_time_to_universal_time/2 input validation and negative rounding
Validate argv[0] is an integer before calling term_maybe_unbox_int64 to prevent interpreting arbitrary term data as an int64. Use floor division instead of C truncation for all time-unit branches so negative sub-second values round toward negative infinity, matching OTP semantics. For example calendar:system_time_to_universal_time(-1, millisecond) now correctly returns {{1969,12,31},{23,59,59}}. Add tests for negative calendar conversions and non-integer input. Signed-off-by: Peter M <petermm@gmail.com>
1 parent 51ad319 commit 1850de9

2 files changed

Lines changed: 25 additions & 25 deletions

File tree

src/libAtomVM/nifs.c

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,46 +2143,39 @@ term nif_erlang_timestamp_0(Context *ctx, int argc, term argv[])
21432143

21442144
term nif_calendar_system_time_to_universal_time_2(Context *ctx, int argc, term argv[])
21452145
{
2146-
UNUSED(ctx);
21472146
UNUSED(argc);
21482147

2149-
struct timespec ts;
2148+
if (UNLIKELY(!term_is_int64(argv[0]))) {
2149+
RAISE_ERROR(BADARG_ATOM);
2150+
}
21502151
avm_int64_t value = term_maybe_unbox_int64(argv[0]);
21512152

2153+
avm_int64_t divisor;
21522154
if (argv[1] == SECOND_ATOM) {
2153-
ts.tv_sec = (time_t) value;
2154-
ts.tv_nsec = 0;
2155-
2155+
divisor = 1;
21562156
} else if (argv[1] == MILLISECOND_ATOM) {
2157-
ts.tv_sec = (time_t) (value / 1000);
2158-
ts.tv_nsec = (value % 1000) * 1000000;
2159-
2157+
divisor = 1000;
21602158
} else if (argv[1] == MICROSECOND_ATOM) {
2161-
ts.tv_sec = (time_t) (value / 1000000);
2162-
ts.tv_nsec = (value % 1000000) * 1000;
2163-
2159+
divisor = INT64_C(1000000);
21642160
} else if (argv[1] == NANOSECOND_ATOM || argv[1] == NATIVE_ATOM) {
2165-
ts.tv_sec = (time_t) (value / INT64_C(1000000000));
2166-
ts.tv_nsec = value % INT64_C(1000000000);
2167-
2161+
divisor = INT64_C(1000000000);
21682162
} else if (term_is_int64(argv[1])) {
2169-
avm_int64_t parts_per_second = term_maybe_unbox_int64(argv[1]);
2170-
if (UNLIKELY(parts_per_second <= 0)) {
2171-
RAISE_ERROR(BADARG_ATOM);
2172-
}
2173-
if (UNLIKELY(!term_is_int64(argv[0]))) {
2163+
divisor = term_maybe_unbox_int64(argv[1]);
2164+
if (UNLIKELY(divisor <= 0)) {
21742165
RAISE_ERROR(BADARG_ATOM);
21752166
}
2176-
ts.tv_sec = (time_t) (value / parts_per_second);
2177-
if ((value % parts_per_second) < 0) {
2178-
ts.tv_sec -= 1;
2179-
}
2180-
ts.tv_nsec = 0;
2181-
21822167
} else {
21832168
RAISE_ERROR(BADARG_ATOM);
21842169
}
21852170

2171+
// Floor division: round negative fractional seconds toward negative infinity
2172+
avm_int64_t quotient = value / divisor;
2173+
avm_int64_t remainder = value % divisor;
2174+
struct timespec ts = {
2175+
.tv_sec = (time_t) (quotient - (remainder < 0)),
2176+
.tv_nsec = 0
2177+
};
2178+
21862179
struct tm broken_down_time;
21872180
return build_datetime_from_tm(ctx, gmtime_r(&ts.tv_sec, &broken_down_time));
21882181
}

tests/erlang_tests/test_system_time.erl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ test_system_time_to_universal_time() ->
131131
{{2023, 7, 8}, {20, 19, 39}} = calendar:system_time_to_universal_time(1688847579, second),
132132

133133
{{1969, 12, 31}, {23, 59, 59}} = calendar:system_time_to_universal_time(-1, second),
134+
{{1969, 12, 31}, {23, 59, 59}} = calendar:system_time_to_universal_time(-1, millisecond),
135+
{{1969, 12, 31}, {23, 59, 58}} = calendar:system_time_to_universal_time(-1001, millisecond),
136+
{{1969, 12, 31}, {23, 59, 59}} = calendar:system_time_to_universal_time(-1, microsecond),
137+
{{1969, 12, 31}, {23, 59, 59}} = calendar:system_time_to_universal_time(-1, nanosecond),
138+
{{1969, 12, 31}, {23, 59, 59}} = calendar:system_time_to_universal_time(-1, native),
139+
140+
ok = expect(fun() -> calendar:system_time_to_universal_time(not_an_integer, second) end, badarg),
134141

135142
ok = test_nanosecond_universal_time(),
136143
ok = test_native_universal_time(),

0 commit comments

Comments
 (0)