Skip to content

Commit

Permalink
Merge pull request #86 from abalazsik/master
Browse files Browse the repository at this point in the history
handle variable timezone offsets in dates due daylight saving
  • Loading branch information
sile authored Sep 16, 2024
2 parents 15f03bd + e0a8de4 commit 8c97402
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 3 deletions.
3 changes: 2 additions & 1 deletion rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
inline,
{platform_define, "^R[01][0-9]", 'NO_MAP_TYPE'},
{platform_define, "^(R|17)", 'NO_DIALYZER_SPEC'},
{d, 'MAP_ITER_ORDERED'}]}]},
{d, 'MAP_ITER_ORDERED'},
{d, 'TIME_MODULE', test_time_module}]}]},
{edown, [{edoc_opts, [{doclet, edown_doclet}]}, {deps, [edown]}]}]}.

{project_plugins, [covertool, rebar3_efmt]}.
Expand Down
4 changes: 3 additions & 1 deletion src/jsone.erl
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@
%% > jsone:encode({{2000, 3, 10}, {10, 3, 58}}, [{datetime_format, {iso8601, local}}]).
%% <<"\"2000-03-10T10:03:58+09:00\"">>
%%
%% % Also you can use {iso8601, local_dst} to properly calculate the timezone according to the daylight saving procedure. Consider using it, if the executing computer is located in a country that implements this procedure
%%
%% %
%% % Explicit TimeZone Offset
%% %
Expand All @@ -184,7 +186,7 @@
%% '''

-type datetime_format() :: iso8601.
-type timezone() :: utc | local | utc_offset_seconds().
-type timezone() :: utc | local | local_dst | utc_offset_seconds().
-type utc_offset_seconds() :: -86399..86399.

-type common_option() :: undefined_as_null.
Expand Down
16 changes: 16 additions & 0 deletions src/jsone_encode.erl
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,8 @@ parse_option([{datetime_format, Fmt} | T], Opt) ->
parse_option(T, Opt?OPT{datetime_format = {iso8601, 0}});
{iso8601, local} ->
parse_option(T, Opt?OPT{datetime_format = {iso8601, local_offset()}});
{iso8601, local_dst} ->
parse_option(T, Opt?OPT{datetime_format = {iso8601, local_offset_dst()}});
{iso8601, N} when -86400 < N, N < 86400 ->
parse_option(T, Opt?OPT{datetime_format = {iso8601, N}});
_ ->
Expand All @@ -600,3 +602,17 @@ local_offset() ->
UTC = {{1970, 1, 2}, {0, 0, 0}},
Local = calendar:universal_time_to_local_time({{1970, 1, 2}, {0, 0, 0}}),
calendar:datetime_to_gregorian_seconds(Local) - calendar:datetime_to_gregorian_seconds(UTC).


-ifndef(TIME_MODULE).

-define(TIME_MODULE, erlang).

-endif.


-spec local_offset_dst() -> jsone:utc_offset_seconds().
local_offset_dst() ->
LocalDateTime = ?TIME_MODULE:localtime(),
calendar:datetime_to_gregorian_seconds(LocalDateTime) -
calendar:datetime_to_gregorian_seconds(?TIME_MODULE:localtime_to_universaltime(LocalDateTime)).
3 changes: 2 additions & 1 deletion test/jsone_decode_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ decode_test_() ->
?assertEqual({ok, 0, <<"1">>}, jsone_decode:decode(<<"-01">>))
end},
{"integer can't begin with an explicit plus sign",
fun() -> ?assertMatch({error, {badarg, _}}, jsone_decode:decode(<<"+1">>)) end},
fun() -> ?assertMatch({error, {badarg, _}}, jsone_decode:decode(<<"+1">>))
end},

%% Numbers: Floats
{"float: decimal notation", fun() -> ?assertEqual({ok, 1.23, <<"">>}, jsone_decode:decode(<<"1.23">>)) end},
Expand Down
16 changes: 16 additions & 0 deletions test/jsone_encode_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,22 @@ encode_test_() ->
?assertMatch(<<"\"2015-06-25T14:57:25Z\"">>, Json)
end
end},
{"datetime: iso8601: local with daylight saving variable zone - summer time (2h offset)",
fun() ->
test_time_module:set_localtime({{2024, 9, 15}, {11, 00, 00}}),
test_time_module:mock_localtime_to_universaltime(fun(_) -> {{2024, 9, 15}, {9, 00, 00}} end),

{ok, Json} = jsone_encode:encode({{2015, 6, 25}, {14, 57, 25}}, [{datetime_format, {iso8601, local_dst}}]),
?assertMatch(<<"\"2015-06-25T14:57:25+02:00\"">>, Json)
end},
{"datetime: iso8601: local with daylight saving variable zone - winter time (1h offset)",
fun() ->
test_time_module:set_localtime({{2024, 12, 15}, {11, 00, 00}}),
test_time_module:mock_localtime_to_universaltime(fun(_) -> {{2024, 12, 15}, {10, 00, 00}} end),

{ok, Json} = jsone_encode:encode({{2015, 6, 25}, {14, 57, 25}}, [{datetime_format, {iso8601, local_dst}}]),
?assertMatch(<<"\"2015-06-25T14:57:25+01:00\"">>, Json)
end},
{"datetime: iso8601: timezone",
fun() ->
?assertEqual({ok, <<"\"2015-06-25T14:57:25Z\"">>},
Expand Down
19 changes: 19 additions & 0 deletions test/test_time_module.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-module(test_time_module).
-export([localtime/0, set_localtime/1, localtime_to_universaltime/1, mock_localtime_to_universaltime/1]).


set_localtime({{_, _, _}, {_, _, _}} = LocalTime) ->
erlang:put('__test_time_module__localtime__', LocalTime).


localtime() ->
erlang:get('__test_time_module__localtime__').


localtime_to_universaltime({{_, _, _}, {_, _, _}} = LocalTime) ->
LocalTimeToUniversalTimeFun = erlang:get('__test_time_module_localtime_to_universaltime__'),
LocalTimeToUniversalTimeFun(LocalTime).


mock_localtime_to_universaltime(Fun) when is_function(Fun) ->
erlang:put('__test_time_module_localtime_to_universaltime__', Fun).

0 comments on commit 8c97402

Please sign in to comment.