Skip to content

Commit 5608e49

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 261b6ef commit 5608e49

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
@@ -2055,46 +2055,39 @@ term nif_erlang_timestamp_0(Context *ctx, int argc, term argv[])
20552055

20562056
term nif_calendar_system_time_to_universal_time_2(Context *ctx, int argc, term argv[])
20572057
{
2058-
UNUSED(ctx);
20592058
UNUSED(argc);
20602059

2061-
struct timespec ts;
2060+
if (UNLIKELY(!term_is_int64(argv[0]))) {
2061+
RAISE_ERROR(BADARG_ATOM);
2062+
}
20622063
avm_int64_t value = term_maybe_unbox_int64(argv[0]);
20632064

2065+
avm_int64_t divisor;
20642066
if (argv[1] == SECOND_ATOM) {
2065-
ts.tv_sec = (time_t) value;
2066-
ts.tv_nsec = 0;
2067-
2067+
divisor = 1;
20682068
} else if (argv[1] == MILLISECOND_ATOM) {
2069-
ts.tv_sec = (time_t) (value / 1000);
2070-
ts.tv_nsec = (value % 1000) * 1000000;
2071-
2069+
divisor = 1000;
20722070
} else if (argv[1] == MICROSECOND_ATOM) {
2073-
ts.tv_sec = (time_t) (value / 1000000);
2074-
ts.tv_nsec = (value % 1000000) * 1000;
2075-
2071+
divisor = INT64_C(1000000);
20762072
} else if (argv[1] == NANOSECOND_ATOM || argv[1] == NATIVE_ATOM) {
2077-
ts.tv_sec = (time_t) (value / INT64_C(1000000000));
2078-
ts.tv_nsec = value % INT64_C(1000000000);
2079-
2073+
divisor = INT64_C(1000000000);
20802074
} else if (term_is_int64(argv[1])) {
2081-
avm_int64_t parts_per_second = term_maybe_unbox_int64(argv[1]);
2082-
if (UNLIKELY(parts_per_second <= 0)) {
2083-
RAISE_ERROR(BADARG_ATOM);
2084-
}
2085-
if (UNLIKELY(!term_is_int64(argv[0]))) {
2075+
divisor = term_maybe_unbox_int64(argv[1]);
2076+
if (UNLIKELY(divisor <= 0)) {
20862077
RAISE_ERROR(BADARG_ATOM);
20872078
}
2088-
ts.tv_sec = (time_t) (value / parts_per_second);
2089-
if ((value % parts_per_second) < 0) {
2090-
ts.tv_sec -= 1;
2091-
}
2092-
ts.tv_nsec = 0;
2093-
20942079
} else {
20952080
RAISE_ERROR(BADARG_ATOM);
20962081
}
20972082

2083+
// Floor division: round negative fractional seconds toward negative infinity
2084+
avm_int64_t quotient = value / divisor;
2085+
avm_int64_t remainder = value % divisor;
2086+
struct timespec ts = {
2087+
.tv_sec = (time_t) (quotient - (remainder < 0)),
2088+
.tv_nsec = 0
2089+
};
2090+
20982091
struct tm broken_down_time;
20992092
return build_datetime_from_tm(ctx, gmtime_r(&ts.tv_sec, &broken_down_time));
21002093
}

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)