Skip to content

Commit 301f38b

Browse files
committed
Add support for erlang:localtime/0 and introduce erlang:localtime/1
`erlang:localtime/1` returns the local time for a given timezone in a thread safe way by using POSIX TZ environment variable. Signed-off-by: Paul Guyot <[email protected]>
1 parent aafa6c7 commit 301f38b

File tree

8 files changed

+144
-16
lines changed

8 files changed

+144
-16
lines changed

src/libAtomVM/globalcontext.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ GlobalContext *globalcontext_new()
146146
glb->online_schedulers = smp_get_online_processors();
147147
glb->running_schedulers = 0;
148148
glb->waiting_scheduler = false;
149+
150+
smp_spinlock_init(&glb->env_spinlock);
149151
#endif
150152
glb->scheduler_stop_all = false;
151153

src/libAtomVM/globalcontext.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ struct GlobalContext
116116
bool scheduler_stop_all;
117117
#endif
118118

119+
#ifndef AVM_NO_SMP
120+
SpinLock env_spinlock;
121+
#endif
122+
119123
void *platform_data;
120124
};
121125

src/libAtomVM/nifs.c

Lines changed: 69 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ static term nif_erlang_system_time_1(Context *ctx, int argc, term argv[]);
132132
static term nif_erlang_tuple_to_list_1(Context *ctx, int argc, term argv[]);
133133
static term nif_erlang_list_to_tuple_1(Context *ctx, int argc, term argv[]);
134134
static term nif_erlang_universaltime_0(Context *ctx, int argc, term argv[]);
135+
static term nif_erlang_localtime(Context *ctx, int argc, term argv[]);
135136
static term nif_erlang_timestamp_0(Context *ctx, int argc, term argv[]);
136137
static term nif_erts_debug_flat_size(Context *ctx, int argc, term argv[]);
137138
static term nif_erlang_process_flag(Context *ctx, int argc, term argv[]);
@@ -470,6 +471,12 @@ static const struct Nif universaltime_nif =
470471
.nif_ptr = nif_erlang_universaltime_0
471472
};
472473

474+
static const struct Nif localtime_nif =
475+
{
476+
.base.type = NIFFunctionType,
477+
.nif_ptr = nif_erlang_localtime
478+
};
479+
473480
static const struct Nif timestamp_nif =
474481
{
475482
.base.type = NIFFunctionType,
@@ -1382,12 +1389,8 @@ term nif_erlang_system_time_1(Context *ctx, int argc, term argv[])
13821389
}
13831390
}
13841391

1385-
term nif_erlang_universaltime_0(Context *ctx, int argc, term argv[])
1392+
static term build_datetime_from_tm(Context *ctx, struct tm *broken_down_time)
13861393
{
1387-
UNUSED(ctx);
1388-
UNUSED(argc);
1389-
UNUSED(argv);
1390-
13911394
// 4 = size of date/time tuple, 3 size of date time tuple
13921395
if (UNLIKELY(memory_ensure_free_opt(ctx, 3 + 4 + 4, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
13931396
RAISE_ERROR(OUT_OF_MEMORY_ATOM);
@@ -1396,24 +1399,75 @@ term nif_erlang_universaltime_0(Context *ctx, int argc, term argv[])
13961399
term time_tuple = term_alloc_tuple(3, &ctx->heap);
13971400
term date_time_tuple = term_alloc_tuple(2, &ctx->heap);
13981401

1402+
term_put_tuple_element(date_tuple, 0, term_from_int32(1900 + broken_down_time->tm_year));
1403+
term_put_tuple_element(date_tuple, 1, term_from_int32(broken_down_time->tm_mon + 1));
1404+
term_put_tuple_element(date_tuple, 2, term_from_int32(broken_down_time->tm_mday));
1405+
1406+
term_put_tuple_element(time_tuple, 0, term_from_int32(broken_down_time->tm_hour));
1407+
term_put_tuple_element(time_tuple, 1, term_from_int32(broken_down_time->tm_min));
1408+
term_put_tuple_element(time_tuple, 2, term_from_int32(broken_down_time->tm_sec));
1409+
1410+
term_put_tuple_element(date_time_tuple, 0, date_tuple);
1411+
term_put_tuple_element(date_time_tuple, 1, time_tuple);
1412+
1413+
return date_time_tuple;
1414+
}
1415+
1416+
term nif_erlang_universaltime_0(Context *ctx, int argc, term argv[])
1417+
{
1418+
UNUSED(argc);
1419+
UNUSED(argv);
1420+
13991421
struct timespec ts;
14001422
sys_time(&ts);
14011423

14021424
struct tm broken_down_time;
1403-
gmtime_r(&ts.tv_sec, &broken_down_time);
1425+
return build_datetime_from_tm(ctx, gmtime_r(&ts.tv_sec, &broken_down_time));
1426+
}
14041427

1405-
term_put_tuple_element(date_tuple, 0, term_from_int32(1900 + broken_down_time.tm_year));
1406-
term_put_tuple_element(date_tuple, 1, term_from_int32(broken_down_time.tm_mon + 1));
1407-
term_put_tuple_element(date_tuple, 2, term_from_int32(broken_down_time.tm_mday));
1428+
term nif_erlang_localtime(Context *ctx, int argc, term argv[])
1429+
{
1430+
char *tz;
1431+
if (argc == 1) {
1432+
int ok;
1433+
tz = interop_term_to_string(argv[0], &ok);
1434+
if (UNLIKELY(!ok)) {
1435+
RAISE_ERROR(BADARG_ATOM);
1436+
}
1437+
} else {
1438+
tz = NULL;
1439+
}
14081440

1409-
term_put_tuple_element(time_tuple, 0, term_from_int32(broken_down_time.tm_hour));
1410-
term_put_tuple_element(time_tuple, 1, term_from_int32(broken_down_time.tm_min));
1411-
term_put_tuple_element(time_tuple, 2, term_from_int32(broken_down_time.tm_sec));
1441+
struct timespec ts;
1442+
sys_time(&ts);
14121443

1413-
term_put_tuple_element(date_time_tuple, 0, date_tuple);
1414-
term_put_tuple_element(date_time_tuple, 1, time_tuple);
1444+
struct tm storage;
1445+
struct tm *localtime;
14151446

1416-
return date_time_tuple;
1447+
#ifndef AVM_NO_SMP
1448+
smp_spinlock_lock(&ctx->global->env_spinlock);
1449+
#endif
1450+
if (tz) {
1451+
char *oldtz = getenv("TZ");
1452+
setenv("TZ", tz, 1);
1453+
tzset();
1454+
localtime = localtime_r(&ts.tv_sec, &storage);
1455+
if (oldtz) {
1456+
setenv("TZ", oldtz, 1);
1457+
} else {
1458+
unsetenv("TZ");
1459+
}
1460+
} else {
1461+
// Call tzset to handle DST changes
1462+
tzset();
1463+
localtime = localtime_r(&ts.tv_sec, &storage);
1464+
}
1465+
#ifndef AVM_NO_SMP
1466+
smp_spinlock_unlock(&ctx->global->env_spinlock);
1467+
#endif
1468+
1469+
free(tz);
1470+
return build_datetime_from_tm(ctx, localtime);
14171471
}
14181472

14191473
term nif_erlang_timestamp_0(Context *ctx, int argc, term argv[])

src/libAtomVM/nifs.gperf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ erlang:monotonic_time/1, &monotonic_time_nif
9292
erlang:system_time/1, &system_time_nif
9393
erlang:tuple_to_list/1, &tuple_to_list_nif
9494
erlang:universaltime/0, &universaltime_nif
95+
erlang:localtime/0, &localtime_nif
96+
erlang:localtime/1, &localtime_nif
9597
erlang:timestamp/0, &timestamp_nif
9698
erlang:process_flag/2, &process_flag_nif
9799
erlang:process_flag/3, &process_flag_nif

src/platforms/esp32/test/main/test_erl_sources/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,20 @@ endfunction()
3838

3939
compile_erlang(test_socket)
4040
compile_erlang(test_time_and_processes)
41+
compile_erlang(test_tz)
4142

4243
add_custom_command(
4344
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/esp32_test_modules.avm"
4445
COMMAND HostAtomVM-prefix/src/HostAtomVM-build/tools/packbeam/PackBEAM -i esp32_test_modules.avm
4546
HostAtomVM-prefix/src/HostAtomVM-build/libs/atomvmlib.avm
4647
test_socket.beam
4748
test_time_and_processes.beam
49+
test_tz.beam
4850
DEPENDS
4951
HostAtomVM
5052
"${CMAKE_CURRENT_BINARY_DIR}/test_socket.beam"
5153
"${CMAKE_CURRENT_BINARY_DIR}/test_time_and_processes.beam"
54+
"${CMAKE_CURRENT_BINARY_DIR}/test_tz.beam"
5255
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
5356
VERBATIM
5457
)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2023 Paul Guyot <[email protected]>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License");
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
21+
-module(test_tz).
22+
-export([start/0]).
23+
24+
start() ->
25+
UTCTime1 = erlang:universaltime(),
26+
LocalTimeUTC = erlang:localtime("UTC"),
27+
UTCTime2 = erlang:universaltime(),
28+
true = UTCTime1 =< LocalTimeUTC andalso LocalTimeUTC =< UTCTime2,
29+
ParisTime = erlang:localtime("CET-1CEST,M3.5.0,M10.5.0/3"),
30+
UTCTime3 = erlang:universaltime(),
31+
true = UTCTime2 =/= ParisTime andalso UTCTime3 =/= ParisTime,
32+
ok.

src/platforms/esp32/test/main/test_main.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ TEST_CASE("test_time_and_processes", "[test_run]")
183183
TEST_ASSERT(term_to_int(ret_value) == 6);
184184
}
185185

186+
TEST_CASE("test_tz", "[test_run]")
187+
{
188+
term ret_value = avm_test_case("test_tz.beam");
189+
TEST_ASSERT(ret_value == OK_ATOM);
190+
}
191+
186192
#ifndef AVM_NO_SMP
187193
TEST_CASE("atomvm_smp_0", "[smp]")
188194
{

tests/erlang_tests/datetime.erl

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,35 @@
2323
-export([start/0]).
2424

2525
start() ->
26+
3 = test_localtime(),
27+
3 = test_universaltime(),
28+
case erlang:system_info(machine) of
29+
"BEAM" ->
30+
% BEAM currently has no API to get the localtime for a given TZ
31+
% https://github.com/erlang/otp/issues/7228
32+
ok;
33+
_ ->
34+
ok = test_timezone()
35+
end,
36+
3.
37+
38+
test_localtime() ->
39+
{Date, Time} = erlang:localtime(),
40+
test_date(Date) + test_time(Time).
41+
42+
test_universaltime() ->
2643
{Date, Time} = erlang:universaltime(),
2744
test_date(Date) + test_time(Time).
2845

29-
test_date({Y, M, D}) when Y >= 2018 andalso M >= 1 andalso M =< 12 andalso D >= 1 andalso D =< 31 ->
46+
test_timezone() ->
47+
UTCDate = erlang:universaltime(),
48+
ParisDate = erlang:localtime("Europe/Paris"),
49+
LondonDate = erlang:localtime("Europe/London"),
50+
true = ParisDate =/= UTCDate,
51+
true = ParisDate =/= LondonDate,
52+
ok.
53+
54+
test_date({Y, M, D}) when Y >= 2023 andalso M >= 1 andalso M =< 12 andalso D >= 1 andalso D =< 31 ->
3055
1.
3156

3257
test_time({HOUR, MIN, SEC}) when

0 commit comments

Comments
 (0)