Merge pull request #11446 from lafirest/refactor/calendar
refactor(calendar): refactor datetime-related code and remove redundant
This commit is contained in:
commit
5e448f5a02
|
@ -213,7 +213,7 @@ format(Node, #deactivated_alarm{
|
||||||
|
|
||||||
to_rfc3339(Timestamp) ->
|
to_rfc3339(Timestamp) ->
|
||||||
%% rfc3339 accuracy to millisecond
|
%% rfc3339 accuracy to millisecond
|
||||||
list_to_binary(calendar:system_time_to_rfc3339(Timestamp div 1000, [{unit, millisecond}])).
|
emqx_utils_calendar:epoch_to_rfc3339(Timestamp div 1000).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% gen_server callbacks
|
%% gen_server callbacks
|
||||||
|
|
|
@ -172,7 +172,7 @@ maybe_format_host({As, Who}) ->
|
||||||
{As, Who}.
|
{As, Who}.
|
||||||
|
|
||||||
to_rfc3339(Timestamp) ->
|
to_rfc3339(Timestamp) ->
|
||||||
list_to_binary(calendar:system_time_to_rfc3339(Timestamp, [{unit, second}])).
|
emqx_utils_calendar:epoch_to_rfc3339(Timestamp, second).
|
||||||
|
|
||||||
-spec create(emqx_types:banned() | map()) ->
|
-spec create(emqx_types:banned() | map()) ->
|
||||||
{ok, emqx_types:banned()} | {error, {already_exist, emqx_types:banned()}}.
|
{ok, emqx_types:banned()} | {error, {already_exist, emqx_types:banned()}}.
|
||||||
|
|
|
@ -1,156 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2017-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
||||||
%%
|
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
%% you may not use this file except in compliance with the License.
|
|
||||||
%% You may obtain a copy of the License at
|
|
||||||
%%
|
|
||||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
%%
|
|
||||||
%% Unless required by applicable law or agreed to in writing, software
|
|
||||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
%% See the License for the specific language governing permissions and
|
|
||||||
%% limitations under the License.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
-module(emqx_datetime).
|
|
||||||
|
|
||||||
-include_lib("typerefl/include/types.hrl").
|
|
||||||
|
|
||||||
%% API
|
|
||||||
-export([
|
|
||||||
to_epoch_millisecond/1,
|
|
||||||
to_epoch_second/1,
|
|
||||||
human_readable_duration_string/1
|
|
||||||
]).
|
|
||||||
-export([
|
|
||||||
epoch_to_rfc3339/1,
|
|
||||||
epoch_to_rfc3339/2
|
|
||||||
]).
|
|
||||||
|
|
||||||
-reflect_type([
|
|
||||||
epoch_millisecond/0,
|
|
||||||
epoch_second/0
|
|
||||||
]).
|
|
||||||
|
|
||||||
-type epoch_second() :: non_neg_integer().
|
|
||||||
-type epoch_millisecond() :: non_neg_integer().
|
|
||||||
-typerefl_from_string({epoch_second/0, ?MODULE, to_epoch_second}).
|
|
||||||
-typerefl_from_string({epoch_millisecond/0, ?MODULE, to_epoch_millisecond}).
|
|
||||||
|
|
||||||
%% the maximum value is the SECONDS_FROM_0_TO_10000 in the calendar.erl,
|
|
||||||
%% here minus SECONDS_PER_DAY to tolerate timezone time offset,
|
|
||||||
%% so the maximum date can reach 9999-12-31 which is ample.
|
|
||||||
-define(MAXIMUM_EPOCH, 253402214400).
|
|
||||||
-define(MAXIMUM_EPOCH_MILLI, 253402214400_000).
|
|
||||||
|
|
||||||
to_epoch_second(DateTime) ->
|
|
||||||
to_epoch(DateTime, second).
|
|
||||||
|
|
||||||
to_epoch_millisecond(DateTime) ->
|
|
||||||
to_epoch(DateTime, millisecond).
|
|
||||||
|
|
||||||
to_epoch(DateTime, Unit) ->
|
|
||||||
try
|
|
||||||
case string:to_integer(DateTime) of
|
|
||||||
{Epoch, []} -> validate_epoch(Epoch, Unit);
|
|
||||||
_ -> {ok, calendar:rfc3339_to_system_time(DateTime, [{unit, Unit}])}
|
|
||||||
end
|
|
||||||
catch
|
|
||||||
error:_ ->
|
|
||||||
{error, bad_rfc3339_timestamp}
|
|
||||||
end.
|
|
||||||
|
|
||||||
epoch_to_rfc3339(TimeStamp) ->
|
|
||||||
epoch_to_rfc3339(TimeStamp, millisecond).
|
|
||||||
|
|
||||||
epoch_to_rfc3339(TimeStamp, Unit) when is_integer(TimeStamp) ->
|
|
||||||
list_to_binary(calendar:system_time_to_rfc3339(TimeStamp, [{unit, Unit}])).
|
|
||||||
|
|
||||||
-spec human_readable_duration_string(integer()) -> string().
|
|
||||||
human_readable_duration_string(Milliseconds) ->
|
|
||||||
Seconds = Milliseconds div 1000,
|
|
||||||
{D, {H, M, S}} = calendar:seconds_to_daystime(Seconds),
|
|
||||||
L0 = [{D, " days"}, {H, " hours"}, {M, " minutes"}, {S, " seconds"}],
|
|
||||||
L1 = lists:dropwhile(fun({K, _}) -> K =:= 0 end, L0),
|
|
||||||
L2 = lists:map(fun({Time, Unit}) -> [integer_to_list(Time), Unit] end, L1),
|
|
||||||
lists:flatten(lists:join(", ", L2)).
|
|
||||||
|
|
||||||
validate_epoch(Epoch, _Unit) when Epoch < 0 ->
|
|
||||||
{error, bad_epoch};
|
|
||||||
validate_epoch(Epoch, second) when Epoch =< ?MAXIMUM_EPOCH ->
|
|
||||||
{ok, Epoch};
|
|
||||||
validate_epoch(Epoch, millisecond) when Epoch =< ?MAXIMUM_EPOCH_MILLI ->
|
|
||||||
{ok, Epoch};
|
|
||||||
validate_epoch(_Epoch, _Unit) ->
|
|
||||||
{error, bad_epoch}.
|
|
||||||
|
|
||||||
-ifdef(TEST).
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
|
||||||
-compile(nowarn_export_all).
|
|
||||||
-compile(export_all).
|
|
||||||
roots() -> [bar].
|
|
||||||
|
|
||||||
fields(bar) ->
|
|
||||||
[
|
|
||||||
{second, ?MODULE:epoch_second()},
|
|
||||||
{millisecond, ?MODULE:epoch_millisecond()}
|
|
||||||
].
|
|
||||||
|
|
||||||
-define(FORMAT(_Sec_, _Ms_),
|
|
||||||
lists:flatten(
|
|
||||||
io_lib:format("bar={second=~w,millisecond=~w}", [_Sec_, _Ms_])
|
|
||||||
)
|
|
||||||
).
|
|
||||||
|
|
||||||
epoch_ok_test() ->
|
|
||||||
BigStamp = 1 bsl 37,
|
|
||||||
Args = [
|
|
||||||
{0, 0, 0, 0},
|
|
||||||
{1, 1, 1, 1},
|
|
||||||
{BigStamp, BigStamp * 1000, BigStamp, BigStamp * 1000},
|
|
||||||
{"2022-01-01T08:00:00+08:00", "2022-01-01T08:00:00+08:00", 1640995200, 1640995200000}
|
|
||||||
],
|
|
||||||
lists:foreach(
|
|
||||||
fun({Sec, Ms, EpochSec, EpochMs}) ->
|
|
||||||
check_ok(?FORMAT(Sec, Ms), EpochSec, EpochMs)
|
|
||||||
end,
|
|
||||||
Args
|
|
||||||
),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
check_ok(Input, Sec, Ms) ->
|
|
||||||
{ok, Data} = hocon:binary(Input, #{}),
|
|
||||||
?assertMatch(
|
|
||||||
#{bar := #{second := Sec, millisecond := Ms}},
|
|
||||||
hocon_tconf:check_plain(?MODULE, Data, #{atom_key => true}, [bar])
|
|
||||||
),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
epoch_failed_test() ->
|
|
||||||
BigStamp = 1 bsl 38,
|
|
||||||
Args = [
|
|
||||||
{-1, -1},
|
|
||||||
{"1s", "1s"},
|
|
||||||
{BigStamp, 0},
|
|
||||||
{0, BigStamp * 1000},
|
|
||||||
{"2022-13-13T08:00:00+08:00", "2022-13-13T08:00:00+08:00"}
|
|
||||||
],
|
|
||||||
lists:foreach(
|
|
||||||
fun({Sec, Ms}) ->
|
|
||||||
check_failed(?FORMAT(Sec, Ms))
|
|
||||||
end,
|
|
||||||
Args
|
|
||||||
),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
check_failed(Input) ->
|
|
||||||
{ok, Data} = hocon:binary(Input, #{}),
|
|
||||||
?assertException(
|
|
||||||
throw,
|
|
||||||
_,
|
|
||||||
hocon_tconf:check_plain(?MODULE, Data, #{atom_key => true}, [bar])
|
|
||||||
),
|
|
||||||
ok.
|
|
||||||
|
|
||||||
-endif.
|
|
|
@ -28,7 +28,7 @@ format(
|
||||||
#{level := debug, meta := Meta = #{trace_tag := Tag}, msg := Msg},
|
#{level := debug, meta := Meta = #{trace_tag := Tag}, msg := Msg},
|
||||||
#{payload_encode := PEncode}
|
#{payload_encode := PEncode}
|
||||||
) ->
|
) ->
|
||||||
Time = calendar:system_time_to_rfc3339(erlang:system_time(microsecond), [{unit, microsecond}]),
|
Time = emqx_utils_calendar:now_to_rfc3339(microsecond),
|
||||||
ClientId = to_iolist(maps:get(clientid, Meta, "")),
|
ClientId = to_iolist(maps:get(clientid, Meta, "")),
|
||||||
Peername = maps:get(peername, Meta, ""),
|
Peername = maps:get(peername, Meta, ""),
|
||||||
MetaBin = format_meta(Meta, PEncode),
|
MetaBin = format_meta(Meta, PEncode),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{application, emqx_ft, [
|
{application, emqx_ft, [
|
||||||
{description, "EMQX file transfer over MQTT"},
|
{description, "EMQX file transfer over MQTT"},
|
||||||
{vsn, "0.1.4"},
|
{vsn, "0.1.5"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_ft_app, []}},
|
{mod, {emqx_ft_app, []}},
|
||||||
{applications, [
|
{applications, [
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
%% the resulting file is corrupted during transmission).
|
%% the resulting file is corrupted during transmission).
|
||||||
size => _Bytes :: non_neg_integer(),
|
size => _Bytes :: non_neg_integer(),
|
||||||
checksum => checksum(),
|
checksum => checksum(),
|
||||||
expire_at := emqx_datetime:epoch_second(),
|
expire_at := emqx_utils_calendar:epoch_second(),
|
||||||
%% TTL of individual segments
|
%% TTL of individual segments
|
||||||
%% Somewhat confusing that we won't know it on the nodes where the filemeta
|
%% Somewhat confusing that we won't know it on the nodes where the filemeta
|
||||||
%% is missing.
|
%% is missing.
|
||||||
|
|
|
@ -278,7 +278,7 @@ format_file_info(
|
||||||
end.
|
end.
|
||||||
|
|
||||||
format_timestamp(Timestamp) ->
|
format_timestamp(Timestamp) ->
|
||||||
iolist_to_binary(calendar:system_time_to_rfc3339(Timestamp, [{unit, second}])).
|
emqx_utils_calendar:epoch_to_rfc3339(Timestamp, second).
|
||||||
|
|
||||||
format_name(NameBin) when is_binary(NameBin) ->
|
format_name(NameBin) when is_binary(NameBin) ->
|
||||||
NameBin;
|
NameBin;
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
transfer := emqx_ft:transfer(),
|
transfer := emqx_ft:transfer(),
|
||||||
name := file:name(),
|
name := file:name(),
|
||||||
size := _Bytes :: non_neg_integer(),
|
size := _Bytes :: non_neg_integer(),
|
||||||
timestamp := emqx_datetime:epoch_second(),
|
timestamp := emqx_utils_calendar:epoch_second(),
|
||||||
uri => uri_string:uri_string(),
|
uri => uri_string:uri_string(),
|
||||||
meta => emqx_ft:filemeta()
|
meta => emqx_ft:filemeta()
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
transfer := transfer(),
|
transfer := transfer(),
|
||||||
name := file:name(),
|
name := file:name(),
|
||||||
uri := uri_string:uri_string(),
|
uri := uri_string:uri_string(),
|
||||||
timestamp := emqx_datetime:epoch_second(),
|
timestamp := emqx_utils_calendar:epoch_second(),
|
||||||
size := _Bytes :: non_neg_integer(),
|
size := _Bytes :: non_neg_integer(),
|
||||||
filemeta => filemeta()
|
filemeta => filemeta()
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -76,7 +76,7 @@
|
||||||
% TODO naming
|
% TODO naming
|
||||||
-type filefrag(T) :: #{
|
-type filefrag(T) :: #{
|
||||||
path := file:name(),
|
path := file:name(),
|
||||||
timestamp := emqx_datetime:epoch_second(),
|
timestamp := emqx_utils_calendar:epoch_second(),
|
||||||
size := _Bytes :: non_neg_integer(),
|
size := _Bytes :: non_neg_integer(),
|
||||||
fragment := T
|
fragment := T
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_gateway, [
|
{application, emqx_gateway, [
|
||||||
{description, "The Gateway management application"},
|
{description, "The Gateway management application"},
|
||||||
{vsn, "0.1.22"},
|
{vsn, "0.1.23"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_gateway_app, []}},
|
{mod, {emqx_gateway_app, []}},
|
||||||
{applications, [kernel, stdlib, emqx, emqx_authn, emqx_ctl]},
|
{applications, [kernel, stdlib, emqx, emqx_authn, emqx_ctl]},
|
||||||
|
|
|
@ -397,13 +397,13 @@ format_channel_info(WhichNode, {_, Infos, Stats} = R) ->
|
||||||
{ip_address, {peername, ConnInfo, fun peer_to_binary_addr/1}},
|
{ip_address, {peername, ConnInfo, fun peer_to_binary_addr/1}},
|
||||||
{port, {peername, ConnInfo, fun peer_to_port/1}},
|
{port, {peername, ConnInfo, fun peer_to_port/1}},
|
||||||
{is_bridge, ClientInfo, false},
|
{is_bridge, ClientInfo, false},
|
||||||
{connected_at, {connected_at, ConnInfo, fun emqx_gateway_utils:unix_ts_to_rfc3339/1}},
|
{connected_at, {connected_at, ConnInfo, fun emqx_utils_calendar:epoch_to_rfc3339/1}},
|
||||||
{disconnected_at, {disconnected_at, ConnInfo, fun emqx_gateway_utils:unix_ts_to_rfc3339/1}},
|
{disconnected_at, {disconnected_at, ConnInfo, fun emqx_utils_calendar:epoch_to_rfc3339/1}},
|
||||||
{connected, {conn_state, Infos, fun conn_state_to_connected/1}},
|
{connected, {conn_state, Infos, fun conn_state_to_connected/1}},
|
||||||
{keepalive, ClientInfo, 0},
|
{keepalive, ClientInfo, 0},
|
||||||
{clean_start, ConnInfo, true},
|
{clean_start, ConnInfo, true},
|
||||||
{expiry_interval, ConnInfo, 0},
|
{expiry_interval, ConnInfo, 0},
|
||||||
{created_at, {created_at, SessInfo, fun emqx_gateway_utils:unix_ts_to_rfc3339/1}},
|
{created_at, {created_at, SessInfo, fun emqx_utils_calendar:epoch_to_rfc3339/1}},
|
||||||
{subscriptions_cnt, Stats, 0},
|
{subscriptions_cnt, Stats, 0},
|
||||||
{subscriptions_max, Stats, infinity},
|
{subscriptions_max, Stats, infinity},
|
||||||
{inflight_cnt, Stats, 0},
|
{inflight_cnt, Stats, 0},
|
||||||
|
@ -640,28 +640,28 @@ params_client_searching_in_qs() ->
|
||||||
)},
|
)},
|
||||||
{gte_created_at,
|
{gte_created_at,
|
||||||
mk(
|
mk(
|
||||||
emqx_datetime:epoch_millisecond(),
|
emqx_utils_calendar:epoch_millisecond(),
|
||||||
M#{
|
M#{
|
||||||
desc => ?DESC(param_gte_created_at)
|
desc => ?DESC(param_gte_created_at)
|
||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
{lte_created_at,
|
{lte_created_at,
|
||||||
mk(
|
mk(
|
||||||
emqx_datetime:epoch_millisecond(),
|
emqx_utils_calendar:epoch_millisecond(),
|
||||||
M#{
|
M#{
|
||||||
desc => ?DESC(param_lte_created_at)
|
desc => ?DESC(param_lte_created_at)
|
||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
{gte_connected_at,
|
{gte_connected_at,
|
||||||
mk(
|
mk(
|
||||||
emqx_datetime:epoch_millisecond(),
|
emqx_utils_calendar:epoch_millisecond(),
|
||||||
M#{
|
M#{
|
||||||
desc => ?DESC(param_gte_connected_at)
|
desc => ?DESC(param_gte_connected_at)
|
||||||
}
|
}
|
||||||
)},
|
)},
|
||||||
{lte_connected_at,
|
{lte_connected_at,
|
||||||
mk(
|
mk(
|
||||||
emqx_datetime:epoch_millisecond(),
|
emqx_utils_calendar:epoch_millisecond(),
|
||||||
M#{
|
M#{
|
||||||
desc => ?DESC(param_lte_connected_at)
|
desc => ?DESC(param_lte_connected_at)
|
||||||
}
|
}
|
||||||
|
@ -888,12 +888,12 @@ common_client_props() ->
|
||||||
)},
|
)},
|
||||||
{connected_at,
|
{connected_at,
|
||||||
mk(
|
mk(
|
||||||
emqx_datetime:epoch_millisecond(),
|
emqx_utils_calendar:epoch_millisecond(),
|
||||||
#{desc => ?DESC(connected_at)}
|
#{desc => ?DESC(connected_at)}
|
||||||
)},
|
)},
|
||||||
{disconnected_at,
|
{disconnected_at,
|
||||||
mk(
|
mk(
|
||||||
emqx_datetime:epoch_millisecond(),
|
emqx_utils_calendar:epoch_millisecond(),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(disconnected_at)
|
desc => ?DESC(disconnected_at)
|
||||||
}
|
}
|
||||||
|
@ -931,7 +931,7 @@ common_client_props() ->
|
||||||
)},
|
)},
|
||||||
{created_at,
|
{created_at,
|
||||||
mk(
|
mk(
|
||||||
emqx_datetime:epoch_millisecond(),
|
emqx_utils_calendar:epoch_millisecond(),
|
||||||
#{desc => ?DESC(created_at)}
|
#{desc => ?DESC(created_at)}
|
||||||
)},
|
)},
|
||||||
{subscriptions_cnt,
|
{subscriptions_cnt,
|
||||||
|
|
|
@ -313,9 +313,9 @@ format_gateway(
|
||||||
[
|
[
|
||||||
Name,
|
Name,
|
||||||
Status,
|
Status,
|
||||||
emqx_gateway_utils:unix_ts_to_rfc3339(CreatedAt),
|
emqx_utils_calendar:epoch_to_rfc3339(CreatedAt),
|
||||||
StopOrStart,
|
StopOrStart,
|
||||||
emqx_gateway_utils:unix_ts_to_rfc3339(Timestamp),
|
emqx_utils_calendar:epoch_to_rfc3339(Timestamp),
|
||||||
Config
|
Config
|
||||||
]
|
]
|
||||||
).
|
).
|
||||||
|
|
|
@ -38,7 +38,6 @@
|
||||||
-export([
|
-export([
|
||||||
apply/2,
|
apply/2,
|
||||||
parse_listenon/1,
|
parse_listenon/1,
|
||||||
unix_ts_to_rfc3339/1,
|
|
||||||
unix_ts_to_rfc3339/2,
|
unix_ts_to_rfc3339/2,
|
||||||
listener_id/3,
|
listener_id/3,
|
||||||
parse_listener_id/1,
|
parse_listener_id/1,
|
||||||
|
@ -364,14 +363,10 @@ unix_ts_to_rfc3339(Key, Map) ->
|
||||||
Map;
|
Map;
|
||||||
Ts ->
|
Ts ->
|
||||||
Map#{
|
Map#{
|
||||||
Key =>
|
Key => emqx_utils_calendar:epoch_to_rfc3339(Ts)
|
||||||
emqx_rule_funcs:unix_ts_to_rfc3339(Ts, <<"millisecond">>)
|
|
||||||
}
|
}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
unix_ts_to_rfc3339(Ts) ->
|
|
||||||
emqx_rule_funcs:unix_ts_to_rfc3339(Ts, <<"millisecond">>).
|
|
||||||
|
|
||||||
-spec stringfy(term()) -> binary().
|
-spec stringfy(term()) -> binary().
|
||||||
stringfy(T) when is_list(T); is_binary(T) ->
|
stringfy(T) when is_list(T); is_binary(T) ->
|
||||||
iolist_to_binary(T);
|
iolist_to_binary(T);
|
||||||
|
|
|
@ -2312,9 +2312,7 @@ t_socket_passvice(_) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
t_clients_api(_) ->
|
t_clients_api(_) ->
|
||||||
TsNow = emqx_gateway_utils:unix_ts_to_rfc3339(
|
TsNow = emqx_utils_calendar:now_to_rfc3339(millisecond),
|
||||||
erlang:system_time(millisecond)
|
|
||||||
),
|
|
||||||
ClientId = <<"client_id_test1">>,
|
ClientId = <<"client_id_test1">>,
|
||||||
{ok, Socket} = gen_udp:open(0, [binary]),
|
{ok, Socket} = gen_udp:open(0, [binary]),
|
||||||
send_connect_msg(Socket, ClientId),
|
send_connect_msg(Socket, ClientId),
|
||||||
|
|
|
@ -244,7 +244,7 @@ broker_info() ->
|
||||||
Info#{node => node(), otp_release => otp_rel(), node_status => 'running'}.
|
Info#{node => node(), otp_release => otp_rel(), node_status => 'running'}.
|
||||||
|
|
||||||
convert_broker_info({uptime, Uptime}, M) ->
|
convert_broker_info({uptime, Uptime}, M) ->
|
||||||
M#{uptime => emqx_datetime:human_readable_duration_string(Uptime)};
|
M#{uptime => emqx_utils_calendar:human_readable_duration_string(Uptime)};
|
||||||
convert_broker_info({K, V}, M) ->
|
convert_broker_info({K, V}, M) ->
|
||||||
M#{K => iolist_to_binary(V)}.
|
M#{K => iolist_to_binary(V)}.
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,7 @@ fields(app) ->
|
||||||
)},
|
)},
|
||||||
{expired_at,
|
{expired_at,
|
||||||
hoconsc:mk(
|
hoconsc:mk(
|
||||||
hoconsc:union([infinity, emqx_datetime:epoch_second()]),
|
hoconsc:union([infinity, emqx_utils_calendar:epoch_second()]),
|
||||||
#{
|
#{
|
||||||
desc => "No longer valid datetime",
|
desc => "No longer valid datetime",
|
||||||
example => <<"2021-12-05T02:01:34.186Z">>,
|
example => <<"2021-12-05T02:01:34.186Z">>,
|
||||||
|
@ -137,7 +137,7 @@ fields(app) ->
|
||||||
)},
|
)},
|
||||||
{created_at,
|
{created_at,
|
||||||
hoconsc:mk(
|
hoconsc:mk(
|
||||||
emqx_datetime:epoch_second(),
|
emqx_utils_calendar:epoch_second(),
|
||||||
#{
|
#{
|
||||||
desc => "ApiKey create datetime",
|
desc => "ApiKey create datetime",
|
||||||
example => <<"2021-12-01T00:00:00.000Z">>
|
example => <<"2021-12-01T00:00:00.000Z">>
|
||||||
|
|
|
@ -147,13 +147,13 @@ fields(ban) ->
|
||||||
example => <<"Too many requests">>
|
example => <<"Too many requests">>
|
||||||
})},
|
})},
|
||||||
{at,
|
{at,
|
||||||
hoconsc:mk(emqx_datetime:epoch_second(), #{
|
hoconsc:mk(emqx_utils_calendar:epoch_second(), #{
|
||||||
desc => ?DESC(at),
|
desc => ?DESC(at),
|
||||||
required => false,
|
required => false,
|
||||||
example => <<"2021-10-25T21:48:47+08:00">>
|
example => <<"2021-10-25T21:48:47+08:00">>
|
||||||
})},
|
})},
|
||||||
{until,
|
{until,
|
||||||
hoconsc:mk(emqx_datetime:epoch_second(), #{
|
hoconsc:mk(emqx_utils_calendar:epoch_second(), #{
|
||||||
desc => ?DESC(until),
|
desc => ?DESC(until),
|
||||||
required => false,
|
required => false,
|
||||||
example => <<"2021-10-25T21:53:47+08:00">>
|
example => <<"2021-10-25T21:53:47+08:00">>
|
||||||
|
|
|
@ -161,7 +161,7 @@ schema("/clients") ->
|
||||||
desc => <<"Fuzzy search `username` as substring">>
|
desc => <<"Fuzzy search `username` as substring">>
|
||||||
})},
|
})},
|
||||||
{gte_created_at,
|
{gte_created_at,
|
||||||
hoconsc:mk(emqx_datetime:epoch_millisecond(), #{
|
hoconsc:mk(emqx_utils_calendar:epoch_millisecond(), #{
|
||||||
in => query,
|
in => query,
|
||||||
required => false,
|
required => false,
|
||||||
desc =>
|
desc =>
|
||||||
|
@ -169,7 +169,7 @@ schema("/clients") ->
|
||||||
" than or equal method, rfc3339 or timestamp(millisecond)">>
|
" than or equal method, rfc3339 or timestamp(millisecond)">>
|
||||||
})},
|
})},
|
||||||
{lte_created_at,
|
{lte_created_at,
|
||||||
hoconsc:mk(emqx_datetime:epoch_millisecond(), #{
|
hoconsc:mk(emqx_utils_calendar:epoch_millisecond(), #{
|
||||||
in => query,
|
in => query,
|
||||||
required => false,
|
required => false,
|
||||||
desc =>
|
desc =>
|
||||||
|
@ -177,7 +177,7 @@ schema("/clients") ->
|
||||||
" than or equal method, rfc3339 or timestamp(millisecond)">>
|
" than or equal method, rfc3339 or timestamp(millisecond)">>
|
||||||
})},
|
})},
|
||||||
{gte_connected_at,
|
{gte_connected_at,
|
||||||
hoconsc:mk(emqx_datetime:epoch_millisecond(), #{
|
hoconsc:mk(emqx_utils_calendar:epoch_millisecond(), #{
|
||||||
in => query,
|
in => query,
|
||||||
required => false,
|
required => false,
|
||||||
desc => <<
|
desc => <<
|
||||||
|
@ -186,7 +186,7 @@ schema("/clients") ->
|
||||||
>>
|
>>
|
||||||
})},
|
})},
|
||||||
{lte_connected_at,
|
{lte_connected_at,
|
||||||
hoconsc:mk(emqx_datetime:epoch_millisecond(), #{
|
hoconsc:mk(emqx_utils_calendar:epoch_millisecond(), #{
|
||||||
in => query,
|
in => query,
|
||||||
required => false,
|
required => false,
|
||||||
desc => <<
|
desc => <<
|
||||||
|
@ -399,16 +399,16 @@ fields(client) ->
|
||||||
{connected, hoconsc:mk(boolean(), #{desc => <<"Whether the client is connected">>})},
|
{connected, hoconsc:mk(boolean(), #{desc => <<"Whether the client is connected">>})},
|
||||||
{connected_at,
|
{connected_at,
|
||||||
hoconsc:mk(
|
hoconsc:mk(
|
||||||
emqx_datetime:epoch_millisecond(),
|
emqx_utils_calendar:epoch_millisecond(),
|
||||||
#{desc => <<"Client connection time, rfc3339 or timestamp(millisecond)">>}
|
#{desc => <<"Client connection time, rfc3339 or timestamp(millisecond)">>}
|
||||||
)},
|
)},
|
||||||
{created_at,
|
{created_at,
|
||||||
hoconsc:mk(
|
hoconsc:mk(
|
||||||
emqx_datetime:epoch_millisecond(),
|
emqx_utils_calendar:epoch_millisecond(),
|
||||||
#{desc => <<"Session creation time, rfc3339 or timestamp(millisecond)">>}
|
#{desc => <<"Session creation time, rfc3339 or timestamp(millisecond)">>}
|
||||||
)},
|
)},
|
||||||
{disconnected_at,
|
{disconnected_at,
|
||||||
hoconsc:mk(emqx_datetime:epoch_millisecond(), #{
|
hoconsc:mk(emqx_utils_calendar:epoch_millisecond(), #{
|
||||||
desc =>
|
desc =>
|
||||||
<<
|
<<
|
||||||
"Client offline time."
|
"Client offline time."
|
||||||
|
@ -950,7 +950,7 @@ result_format_time_fun(Key, NClientInfoMap) ->
|
||||||
case NClientInfoMap of
|
case NClientInfoMap of
|
||||||
#{Key := TimeStamp} ->
|
#{Key := TimeStamp} ->
|
||||||
NClientInfoMap#{
|
NClientInfoMap#{
|
||||||
Key => emqx_datetime:epoch_to_rfc3339(TimeStamp)
|
Key => emqx_utils_calendar:epoch_to_rfc3339(TimeStamp)
|
||||||
};
|
};
|
||||||
#{} ->
|
#{} ->
|
||||||
NClientInfoMap
|
NClientInfoMap
|
||||||
|
|
|
@ -281,7 +281,7 @@ fields(trace) ->
|
||||||
})},
|
})},
|
||||||
{start_at,
|
{start_at,
|
||||||
hoconsc:mk(
|
hoconsc:mk(
|
||||||
emqx_datetime:epoch_second(),
|
emqx_utils_calendar:epoch_second(),
|
||||||
#{
|
#{
|
||||||
description => ?DESC(time_format),
|
description => ?DESC(time_format),
|
||||||
required => false,
|
required => false,
|
||||||
|
@ -290,7 +290,7 @@ fields(trace) ->
|
||||||
)},
|
)},
|
||||||
{end_at,
|
{end_at,
|
||||||
hoconsc:mk(
|
hoconsc:mk(
|
||||||
emqx_datetime:epoch_second(),
|
emqx_utils_calendar:epoch_second(),
|
||||||
#{
|
#{
|
||||||
description => ?DESC(time_format),
|
description => ?DESC(time_format),
|
||||||
required => false,
|
required => false,
|
||||||
|
@ -410,8 +410,8 @@ trace(get, _Params) ->
|
||||||
Trace0#{
|
Trace0#{
|
||||||
log_size => LogSize,
|
log_size => LogSize,
|
||||||
Type => iolist_to_binary(Filter),
|
Type => iolist_to_binary(Filter),
|
||||||
start_at => list_to_binary(calendar:system_time_to_rfc3339(Start)),
|
start_at => emqx_utils_calendar:epoch_to_rfc3339(Start, second),
|
||||||
end_at => list_to_binary(calendar:system_time_to_rfc3339(End)),
|
end_at => emqx_utils_calendar:epoch_to_rfc3339(End, second),
|
||||||
status => status(Enable, Start, End, Now)
|
status => status(Enable, Start, End, Now)
|
||||||
}
|
}
|
||||||
end,
|
end,
|
||||||
|
@ -468,8 +468,8 @@ format_trace(Trace0) ->
|
||||||
Trace2#{
|
Trace2#{
|
||||||
log_size => LogSize,
|
log_size => LogSize,
|
||||||
Type => iolist_to_binary(Filter),
|
Type => iolist_to_binary(Filter),
|
||||||
start_at => list_to_binary(calendar:system_time_to_rfc3339(Start)),
|
start_at => emqx_utils_calendar:epoch_to_rfc3339(Start, second),
|
||||||
end_at => list_to_binary(calendar:system_time_to_rfc3339(End)),
|
end_at => emqx_utils_calendar:epoch_to_rfc3339(Start, second),
|
||||||
status => status(Enable, Start, End, Now)
|
status => status(Enable, Start, End, Now)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
|
|
@ -142,11 +142,11 @@ format(App = #{expired_at := ExpiredAt0, created_at := CreateAt}) ->
|
||||||
ExpiredAt =
|
ExpiredAt =
|
||||||
case ExpiredAt0 of
|
case ExpiredAt0 of
|
||||||
infinity -> <<"infinity">>;
|
infinity -> <<"infinity">>;
|
||||||
_ -> list_to_binary(calendar:system_time_to_rfc3339(ExpiredAt0))
|
_ -> emqx_utils_calendar:epoch_to_rfc3339(ExpiredAt0, second)
|
||||||
end,
|
end,
|
||||||
App#{
|
App#{
|
||||||
expired_at => ExpiredAt,
|
expired_at => ExpiredAt,
|
||||||
created_at => list_to_binary(calendar:system_time_to_rfc3339(CreateAt))
|
created_at => emqx_utils_calendar:epoch_to_rfc3339(CreateAt, second)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
list() ->
|
list() ->
|
||||||
|
|
|
@ -87,7 +87,7 @@ broker([]) ->
|
||||||
Funs = [sysdescr, version, datetime],
|
Funs = [sysdescr, version, datetime],
|
||||||
[emqx_ctl:print("~-10s: ~ts~n", [Fun, emqx_sys:Fun()]) || Fun <- Funs],
|
[emqx_ctl:print("~-10s: ~ts~n", [Fun, emqx_sys:Fun()]) || Fun <- Funs],
|
||||||
emqx_ctl:print("~-10s: ~ts~n", [
|
emqx_ctl:print("~-10s: ~ts~n", [
|
||||||
uptime, emqx_datetime:human_readable_duration_string(emqx_sys:uptime())
|
uptime, emqx_utils_calendar:human_readable_duration_string(emqx_sys:uptime())
|
||||||
]);
|
]);
|
||||||
broker(["stats"]) ->
|
broker(["stats"]) ->
|
||||||
[
|
[
|
||||||
|
|
|
@ -260,7 +260,7 @@ t_query_clients_with_time(_) ->
|
||||||
%% Do not uri_encode `=` to `%3D`
|
%% Do not uri_encode `=` to `%3D`
|
||||||
Rfc3339String = emqx_http_lib:uri_encode(
|
Rfc3339String = emqx_http_lib:uri_encode(
|
||||||
binary:bin_to_list(
|
binary:bin_to_list(
|
||||||
emqx_datetime:epoch_to_rfc3339(NowTimeStampInt)
|
emqx_utils_calendar:epoch_to_rfc3339(NowTimeStampInt)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
TimeStampString = emqx_http_lib:uri_encode(integer_to_list(NowTimeStampInt)),
|
TimeStampString = emqx_http_lib:uri_encode(integer_to_list(NowTimeStampInt)),
|
||||||
|
|
|
@ -208,8 +208,8 @@ format_delayed(
|
||||||
},
|
},
|
||||||
WithPayload
|
WithPayload
|
||||||
) ->
|
) ->
|
||||||
PublishTime = to_rfc3339(PublishTimeStamp div 1000),
|
PublishTime = emqx_utils_calendar:epoch_to_rfc3339(PublishTimeStamp),
|
||||||
ExpectTime = to_rfc3339(ExpectTimeStamp div 1000),
|
ExpectTime = emqx_utils_calendar:epoch_to_rfc3339(ExpectTimeStamp),
|
||||||
RemainingTime = ExpectTimeStamp - ?NOW,
|
RemainingTime = ExpectTimeStamp - ?NOW,
|
||||||
Result = #{
|
Result = #{
|
||||||
msgid => emqx_guid:to_hexstr(Id),
|
msgid => emqx_guid:to_hexstr(Id),
|
||||||
|
@ -230,9 +230,6 @@ format_delayed(
|
||||||
Result
|
Result
|
||||||
end.
|
end.
|
||||||
|
|
||||||
to_rfc3339(Timestamp) ->
|
|
||||||
list_to_binary(calendar:system_time_to_rfc3339(Timestamp, [{unit, second}])).
|
|
||||||
|
|
||||||
-spec get_delayed_message(binary()) -> with_id_return(map()).
|
-spec get_delayed_message(binary()) -> with_id_return(map()).
|
||||||
get_delayed_message(Id) ->
|
get_delayed_message(Id) ->
|
||||||
case ets:select(?TAB, ?QUERY_MS(Id)) of
|
case ets:select(?TAB, ?QUERY_MS(Id)) of
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_modules, [
|
{application, emqx_modules, [
|
||||||
{description, "EMQX Modules"},
|
{description, "EMQX Modules"},
|
||||||
{vsn, "5.0.19"},
|
{vsn, "5.0.20"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{applications, [kernel, stdlib, emqx, emqx_ctl]},
|
{applications, [kernel, stdlib, emqx, emqx_ctl]},
|
||||||
{mod, {emqx_modules_app, []}},
|
{mod, {emqx_modules_app, []}},
|
||||||
|
|
|
@ -295,7 +295,7 @@ terminate(_Reason, _State) ->
|
||||||
reset_topic({Topic, Data}, Speeds) ->
|
reset_topic({Topic, Data}, Speeds) ->
|
||||||
CRef = maps:get(counter_ref, Data),
|
CRef = maps:get(counter_ref, Data),
|
||||||
ok = reset_counter(CRef),
|
ok = reset_counter(CRef),
|
||||||
ResetTime = emqx_rule_funcs:now_rfc3339(),
|
ResetTime = emqx_utils_calendar:now_to_rfc3339(),
|
||||||
true = ets:insert(?TAB, {Topic, Data#{reset_time => ResetTime}}),
|
true = ets:insert(?TAB, {Topic, Data#{reset_time => ResetTime}}),
|
||||||
Fun =
|
Fun =
|
||||||
fun(Metric, CurrentSpeeds) ->
|
fun(Metric, CurrentSpeeds) ->
|
||||||
|
|
|
@ -183,7 +183,7 @@ fields(topic_metrics) ->
|
||||||
)},
|
)},
|
||||||
{create_time,
|
{create_time,
|
||||||
mk(
|
mk(
|
||||||
emqx_datetime:epoch_second(),
|
emqx_utils_calendar:epoch_second(),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(create_time),
|
desc => ?DESC(create_time),
|
||||||
required => true,
|
required => true,
|
||||||
|
@ -192,7 +192,7 @@ fields(topic_metrics) ->
|
||||||
)},
|
)},
|
||||||
{reset_time,
|
{reset_time,
|
||||||
mk(
|
mk(
|
||||||
emqx_datetime:epoch_second(),
|
emqx_utils_calendar:epoch_second(),
|
||||||
#{
|
#{
|
||||||
desc => ?DESC(reset_time),
|
desc => ?DESC(reset_time),
|
||||||
required => false,
|
required => false,
|
||||||
|
|
|
@ -211,11 +211,8 @@ format_message(#message{
|
||||||
msgid => emqx_guid:to_hexstr(ID),
|
msgid => emqx_guid:to_hexstr(ID),
|
||||||
qos => Qos,
|
qos => Qos,
|
||||||
topic => Topic,
|
topic => Topic,
|
||||||
publish_at => list_to_binary(
|
publish_at =>
|
||||||
calendar:system_time_to_rfc3339(
|
emqx_utils_calendar:epoch_to_rfc3339(Timestamp),
|
||||||
Timestamp, [{unit, millisecond}]
|
|
||||||
)
|
|
||||||
),
|
|
||||||
from_clientid => to_bin_string(From),
|
from_clientid => to_bin_string(From),
|
||||||
from_username => maps:get(username, Headers, <<>>)
|
from_username => maps:get(username, Headers, <<>>)
|
||||||
}.
|
}.
|
||||||
|
|
|
@ -1,270 +0,0 @@
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
%% Copyright (c) 2020-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
|
||||||
%%
|
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
%% you may not use this file except in compliance with the License.
|
|
||||||
%% You may obtain a copy of the License at
|
|
||||||
%%
|
|
||||||
%% http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
%%
|
|
||||||
%% Unless required by applicable law or agreed to in writing, software
|
|
||||||
%% distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
%% See the License for the specific language governing permissions and
|
|
||||||
%% limitations under the License.
|
|
||||||
%%--------------------------------------------------------------------
|
|
||||||
|
|
||||||
-module(emqx_rule_date).
|
|
||||||
|
|
||||||
-export([date/3, date/4, parse_date/4]).
|
|
||||||
|
|
||||||
-export([
|
|
||||||
is_int_char/1,
|
|
||||||
is_symbol_char/1,
|
|
||||||
is_m_char/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-record(result, {
|
|
||||||
%%year()
|
|
||||||
year = "1970" :: string(),
|
|
||||||
%%month()
|
|
||||||
month = "1" :: string(),
|
|
||||||
%%day()
|
|
||||||
day = "1" :: string(),
|
|
||||||
%%hour()
|
|
||||||
hour = "0" :: string(),
|
|
||||||
%%minute() %% epoch in millisecond precision
|
|
||||||
minute = "0" :: string(),
|
|
||||||
%%second() %% epoch in millisecond precision
|
|
||||||
second = "0" :: string(),
|
|
||||||
%%integer() %% zone maybe some value
|
|
||||||
zone = "+00:00" :: string()
|
|
||||||
}).
|
|
||||||
|
|
||||||
%% -type time_unit() :: 'microsecond'
|
|
||||||
%% | 'millisecond'
|
|
||||||
%% | 'nanosecond'
|
|
||||||
%% | 'second'.
|
|
||||||
%% -type offset() :: [byte()] | (Time :: integer()).
|
|
||||||
date(TimeUnit, Offset, FormatString) ->
|
|
||||||
date(TimeUnit, Offset, FormatString, erlang:system_time(TimeUnit)).
|
|
||||||
|
|
||||||
date(TimeUnit, Offset, FormatString, TimeEpoch) ->
|
|
||||||
[Head | Other] = string:split(FormatString, "%", all),
|
|
||||||
R = create_tag([{st, Head}], Other),
|
|
||||||
Res = lists:map(
|
|
||||||
fun(Expr) ->
|
|
||||||
eval_tag(rmap(make_time(TimeUnit, Offset, TimeEpoch)), Expr)
|
|
||||||
end,
|
|
||||||
R
|
|
||||||
),
|
|
||||||
lists:concat(Res).
|
|
||||||
|
|
||||||
parse_date(TimeUnit, Offset, FormatString, InputString) ->
|
|
||||||
[Head | Other] = string:split(FormatString, "%", all),
|
|
||||||
R = create_tag([{st, Head}], Other),
|
|
||||||
IsZ = fun(V) ->
|
|
||||||
case V of
|
|
||||||
{tag, $Z} -> true;
|
|
||||||
_ -> false
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
R1 = lists:filter(IsZ, R),
|
|
||||||
IfFun = fun(Con, A, B) ->
|
|
||||||
case Con of
|
|
||||||
[] -> A;
|
|
||||||
_ -> B
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
Res = parse_input(FormatString, InputString),
|
|
||||||
Str =
|
|
||||||
Res#result.year ++ "-" ++
|
|
||||||
Res#result.month ++ "-" ++
|
|
||||||
Res#result.day ++ "T" ++
|
|
||||||
Res#result.hour ++ ":" ++
|
|
||||||
Res#result.minute ++ ":" ++
|
|
||||||
Res#result.second ++
|
|
||||||
IfFun(R1, Offset, Res#result.zone),
|
|
||||||
calendar:rfc3339_to_system_time(Str, [{unit, TimeUnit}]).
|
|
||||||
|
|
||||||
mlist(R) ->
|
|
||||||
%% %H Shows hour in 24-hour format [15]
|
|
||||||
[
|
|
||||||
{$H, R#result.hour},
|
|
||||||
%% %M Displays minutes [00-59]
|
|
||||||
{$M, R#result.minute},
|
|
||||||
%% %S Displays seconds [00-59]
|
|
||||||
{$S, R#result.second},
|
|
||||||
%% %y Displays year YYYY [2021]
|
|
||||||
{$y, R#result.year},
|
|
||||||
%% %m Displays the number of the month [01-12]
|
|
||||||
{$m, R#result.month},
|
|
||||||
%% %d Displays the number of the month [01-12]
|
|
||||||
{$d, R#result.day},
|
|
||||||
%% %Z Displays Time zone
|
|
||||||
{$Z, R#result.zone}
|
|
||||||
].
|
|
||||||
|
|
||||||
rmap(Result) ->
|
|
||||||
maps:from_list(mlist(Result)).
|
|
||||||
|
|
||||||
support_char() -> "HMSymdZ".
|
|
||||||
|
|
||||||
create_tag(Head, []) ->
|
|
||||||
Head;
|
|
||||||
create_tag(Head, [Val1 | RVal]) ->
|
|
||||||
case Val1 of
|
|
||||||
[] ->
|
|
||||||
create_tag(Head ++ [{st, [$%]}], RVal);
|
|
||||||
[H | Other] ->
|
|
||||||
case lists:member(H, support_char()) of
|
|
||||||
true -> create_tag(Head ++ [{tag, H}, {st, Other}], RVal);
|
|
||||||
false -> create_tag(Head ++ [{st, [$% | Val1]}], RVal)
|
|
||||||
end
|
|
||||||
end.
|
|
||||||
|
|
||||||
eval_tag(_, {st, Str}) ->
|
|
||||||
Str;
|
|
||||||
eval_tag(Map, {tag, Char}) ->
|
|
||||||
maps:get(Char, Map, "undefined").
|
|
||||||
|
|
||||||
%% make_time(TimeUnit, Offset) ->
|
|
||||||
%% make_time(TimeUnit, Offset, erlang:system_time(TimeUnit)).
|
|
||||||
make_time(TimeUnit, Offset, TimeEpoch) ->
|
|
||||||
Res = calendar:system_time_to_rfc3339(
|
|
||||||
TimeEpoch,
|
|
||||||
[{unit, TimeUnit}, {offset, Offset}]
|
|
||||||
),
|
|
||||||
[
|
|
||||||
Y1,
|
|
||||||
Y2,
|
|
||||||
Y3,
|
|
||||||
Y4,
|
|
||||||
$-,
|
|
||||||
Mon1,
|
|
||||||
Mon2,
|
|
||||||
$-,
|
|
||||||
D1,
|
|
||||||
D2,
|
|
||||||
_T,
|
|
||||||
H1,
|
|
||||||
H2,
|
|
||||||
$:,
|
|
||||||
Min1,
|
|
||||||
Min2,
|
|
||||||
$:,
|
|
||||||
S1,
|
|
||||||
S2
|
|
||||||
| TimeStr
|
|
||||||
] = Res,
|
|
||||||
IsFractionChar = fun(C) -> C >= $0 andalso C =< $9 orelse C =:= $. end,
|
|
||||||
{FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr),
|
|
||||||
#result{
|
|
||||||
year = [Y1, Y2, Y3, Y4],
|
|
||||||
month = [Mon1, Mon2],
|
|
||||||
day = [D1, D2],
|
|
||||||
hour = [H1, H2],
|
|
||||||
minute = [Min1, Min2],
|
|
||||||
second = [S1, S2] ++ FractionStr,
|
|
||||||
zone = UtcOffset
|
|
||||||
}.
|
|
||||||
|
|
||||||
is_int_char(C) ->
|
|
||||||
C >= $0 andalso C =< $9.
|
|
||||||
is_symbol_char(C) ->
|
|
||||||
C =:= $- orelse C =:= $+.
|
|
||||||
is_m_char(C) ->
|
|
||||||
C =:= $:.
|
|
||||||
|
|
||||||
parse_char_with_fun(_, []) ->
|
|
||||||
error(null_input);
|
|
||||||
parse_char_with_fun(ValidFun, [C | Other]) ->
|
|
||||||
Res =
|
|
||||||
case erlang:is_function(ValidFun) of
|
|
||||||
true -> ValidFun(C);
|
|
||||||
false -> erlang:apply(emqx_rule_date, ValidFun, [C])
|
|
||||||
end,
|
|
||||||
case Res of
|
|
||||||
true -> {C, Other};
|
|
||||||
false -> error({unexpected, [C | Other]})
|
|
||||||
end.
|
|
||||||
parse_string([], Input) ->
|
|
||||||
{[], Input};
|
|
||||||
parse_string([C | Other], Input) ->
|
|
||||||
{C1, Input1} = parse_char_with_fun(fun(V) -> V =:= C end, Input),
|
|
||||||
{Res, Input2} = parse_string(Other, Input1),
|
|
||||||
{[C1 | Res], Input2}.
|
|
||||||
|
|
||||||
parse_times(0, _, Input) ->
|
|
||||||
{[], Input};
|
|
||||||
parse_times(Times, Fun, Input) ->
|
|
||||||
{C1, Input1} = parse_char_with_fun(Fun, Input),
|
|
||||||
{Res, Input2} = parse_times((Times - 1), Fun, Input1),
|
|
||||||
{[C1 | Res], Input2}.
|
|
||||||
|
|
||||||
parse_int_times(Times, Input) ->
|
|
||||||
parse_times(Times, is_int_char, Input).
|
|
||||||
|
|
||||||
parse_fraction(Input) ->
|
|
||||||
IsFractionChar = fun(C) -> C >= $0 andalso C =< $9 orelse C =:= $. end,
|
|
||||||
lists:splitwith(IsFractionChar, Input).
|
|
||||||
|
|
||||||
parse_second(Input) ->
|
|
||||||
{M, Input1} = parse_int_times(2, Input),
|
|
||||||
{M1, Input2} = parse_fraction(Input1),
|
|
||||||
{M ++ M1, Input2}.
|
|
||||||
|
|
||||||
parse_zone(Input) ->
|
|
||||||
{S, Input1} = parse_char_with_fun(is_symbol_char, Input),
|
|
||||||
{M, Input2} = parse_int_times(2, Input1),
|
|
||||||
{C, Input3} = parse_char_with_fun(is_m_char, Input2),
|
|
||||||
{V, Input4} = parse_int_times(2, Input3),
|
|
||||||
{[S | M ++ [C | V]], Input4}.
|
|
||||||
|
|
||||||
mlist1() ->
|
|
||||||
maps:from_list(
|
|
||||||
%% %H Shows hour in 24-hour format [15]
|
|
||||||
[
|
|
||||||
{$H, fun(Input) -> parse_int_times(2, Input) end},
|
|
||||||
%% %M Displays minutes [00-59]
|
|
||||||
{$M, fun(Input) -> parse_int_times(2, Input) end},
|
|
||||||
%% %S Displays seconds [00-59]
|
|
||||||
{$S, fun(Input) -> parse_second(Input) end},
|
|
||||||
%% %y Displays year YYYY [2021]
|
|
||||||
{$y, fun(Input) -> parse_int_times(4, Input) end},
|
|
||||||
%% %m Displays the number of the month [01-12]
|
|
||||||
{$m, fun(Input) -> parse_int_times(2, Input) end},
|
|
||||||
%% %d Displays the number of the month [01-12]
|
|
||||||
{$d, fun(Input) -> parse_int_times(2, Input) end},
|
|
||||||
%% %Z Displays Time zone
|
|
||||||
{$Z, fun(Input) -> parse_zone(Input) end}
|
|
||||||
]
|
|
||||||
).
|
|
||||||
|
|
||||||
update_result($H, Res, Str) -> Res#result{hour = Str};
|
|
||||||
update_result($M, Res, Str) -> Res#result{minute = Str};
|
|
||||||
update_result($S, Res, Str) -> Res#result{second = Str};
|
|
||||||
update_result($y, Res, Str) -> Res#result{year = Str};
|
|
||||||
update_result($m, Res, Str) -> Res#result{month = Str};
|
|
||||||
update_result($d, Res, Str) -> Res#result{day = Str};
|
|
||||||
update_result($Z, Res, Str) -> Res#result{zone = Str}.
|
|
||||||
|
|
||||||
parse_tag(Res, {st, St}, InputString) ->
|
|
||||||
{_A, B} = parse_string(St, InputString),
|
|
||||||
{Res, B};
|
|
||||||
parse_tag(Res, {tag, St}, InputString) ->
|
|
||||||
Fun = maps:get(St, mlist1()),
|
|
||||||
{A, B} = Fun(InputString),
|
|
||||||
NRes = update_result(St, Res, A),
|
|
||||||
{NRes, B}.
|
|
||||||
|
|
||||||
parse_tags(Res, [], _) ->
|
|
||||||
Res;
|
|
||||||
parse_tags(Res, [Tag | Others], InputString) ->
|
|
||||||
{NRes, B} = parse_tag(Res, Tag, InputString),
|
|
||||||
parse_tags(NRes, Others, B).
|
|
||||||
|
|
||||||
parse_input(FormatString, InputString) ->
|
|
||||||
[Head | Other] = string:split(FormatString, "%", all),
|
|
||||||
R = create_tag([{st, Head}], Other),
|
|
||||||
parse_tags(#result{}, R, InputString).
|
|
|
@ -2,7 +2,7 @@
|
||||||
{application, emqx_rule_engine, [
|
{application, emqx_rule_engine, [
|
||||||
{description, "EMQX Rule Engine"},
|
{description, "EMQX Rule Engine"},
|
||||||
% strict semver, bump manually!
|
% strict semver, bump manually!
|
||||||
{vsn, "5.0.22"},
|
{vsn, "5.0.23"},
|
||||||
{modules, []},
|
{modules, []},
|
||||||
{registered, [emqx_rule_engine_sup, emqx_rule_engine]},
|
{registered, [emqx_rule_engine_sup, emqx_rule_engine]},
|
||||||
{applications, [kernel, stdlib, rulesql, getopt, emqx_ctl, uuid]},
|
{applications, [kernel, stdlib, rulesql, getopt, emqx_ctl, uuid]},
|
||||||
|
|
|
@ -514,7 +514,7 @@ format_rule_engine_resp(Config) ->
|
||||||
maps:remove(rules, Config).
|
maps:remove(rules, Config).
|
||||||
|
|
||||||
format_datetime(Timestamp, Unit) ->
|
format_datetime(Timestamp, Unit) ->
|
||||||
list_to_binary(calendar:system_time_to_rfc3339(Timestamp, [{unit, Unit}])).
|
emqx_utils_calendar:epoch_to_rfc3339(Timestamp, Unit).
|
||||||
|
|
||||||
format_action(Actions) ->
|
format_action(Actions) ->
|
||||||
[do_format_action(Act) || Act <- Actions].
|
[do_format_action(Act) || Act <- Actions].
|
||||||
|
|
|
@ -74,8 +74,8 @@ pretty_print_rule(ID) ->
|
||||||
"Updated at:\n ~ts\n"
|
"Updated at:\n ~ts\n"
|
||||||
"Actions:\n ~s\n"
|
"Actions:\n ~s\n"
|
||||||
,[Id, Name, left_pad(Descr), Enable, left_pad(SQL),
|
,[Id, Name, left_pad(Descr), Enable, left_pad(SQL),
|
||||||
calendar:system_time_to_rfc3339(CreatedAt, [{unit, millisecond}]),
|
emqx_utils_calendar:epoch_to_rfc3339(CreatedAt, millisecond),
|
||||||
calendar:system_time_to_rfc3339(UpdatedAt, [{unit, millisecond}]),
|
emqx_utils_calendar:epoch_to_rfc3339(UpdatedAt, millisecond),
|
||||||
[left_pad(format_action(A)) || A <- Actions]
|
[left_pad(format_action(A)) || A <- Actions]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
|
@ -276,6 +276,8 @@
|
||||||
]}
|
]}
|
||||||
).
|
).
|
||||||
|
|
||||||
|
-import(emqx_utils_calendar, [time_unit/1, now_to_rfc3339/0, now_to_rfc3339/1, epoch_to_rfc3339/2]).
|
||||||
|
|
||||||
%% @doc "msgid()" Func
|
%% @doc "msgid()" Func
|
||||||
msgid() ->
|
msgid() ->
|
||||||
fun
|
fun
|
||||||
|
@ -1077,23 +1079,19 @@ kv_store_del(Key) ->
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
now_rfc3339() ->
|
now_rfc3339() ->
|
||||||
now_rfc3339(<<"second">>).
|
now_to_rfc3339().
|
||||||
|
|
||||||
now_rfc3339(Unit) ->
|
now_rfc3339(Unit) ->
|
||||||
unix_ts_to_rfc3339(now_timestamp(Unit), Unit).
|
now_to_rfc3339(time_unit(Unit)).
|
||||||
|
|
||||||
unix_ts_to_rfc3339(Epoch) ->
|
unix_ts_to_rfc3339(Epoch) ->
|
||||||
unix_ts_to_rfc3339(Epoch, <<"second">>).
|
epoch_to_rfc3339(Epoch, second).
|
||||||
|
|
||||||
unix_ts_to_rfc3339(Epoch, Unit) when is_integer(Epoch) ->
|
unix_ts_to_rfc3339(Epoch, Unit) when is_integer(Epoch) ->
|
||||||
emqx_utils_conv:bin(
|
epoch_to_rfc3339(Epoch, time_unit(Unit)).
|
||||||
calendar:system_time_to_rfc3339(
|
|
||||||
Epoch, [{unit, time_unit(Unit)}]
|
|
||||||
)
|
|
||||||
).
|
|
||||||
|
|
||||||
rfc3339_to_unix_ts(DateTime) ->
|
rfc3339_to_unix_ts(DateTime) ->
|
||||||
rfc3339_to_unix_ts(DateTime, <<"second">>).
|
rfc3339_to_unix_ts(DateTime, second).
|
||||||
|
|
||||||
rfc3339_to_unix_ts(DateTime, Unit) when is_binary(DateTime) ->
|
rfc3339_to_unix_ts(DateTime, Unit) when is_binary(DateTime) ->
|
||||||
calendar:rfc3339_to_system_time(
|
calendar:rfc3339_to_system_time(
|
||||||
|
@ -1107,15 +1105,6 @@ now_timestamp() ->
|
||||||
now_timestamp(Unit) ->
|
now_timestamp(Unit) ->
|
||||||
erlang:system_time(time_unit(Unit)).
|
erlang:system_time(time_unit(Unit)).
|
||||||
|
|
||||||
time_unit(<<"second">>) -> second;
|
|
||||||
time_unit(<<"millisecond">>) -> millisecond;
|
|
||||||
time_unit(<<"microsecond">>) -> microsecond;
|
|
||||||
time_unit(<<"nanosecond">>) -> nanosecond;
|
|
||||||
time_unit(second) -> second;
|
|
||||||
time_unit(millisecond) -> millisecond;
|
|
||||||
time_unit(microsecond) -> microsecond;
|
|
||||||
time_unit(nanosecond) -> nanosecond.
|
|
||||||
|
|
||||||
format_date(TimeUnit, Offset, FormatString) ->
|
format_date(TimeUnit, Offset, FormatString) ->
|
||||||
Unit = time_unit(TimeUnit),
|
Unit = time_unit(TimeUnit),
|
||||||
TimeEpoch = erlang:system_time(Unit),
|
TimeEpoch = erlang:system_time(Unit),
|
||||||
|
@ -1125,17 +1114,17 @@ format_date(TimeUnit, Offset, FormatString, TimeEpoch) ->
|
||||||
Unit = time_unit(TimeUnit),
|
Unit = time_unit(TimeUnit),
|
||||||
emqx_utils_conv:bin(
|
emqx_utils_conv:bin(
|
||||||
lists:concat(
|
lists:concat(
|
||||||
emqx_calendar:format(TimeEpoch, Unit, Offset, FormatString)
|
emqx_utils_calendar:format(TimeEpoch, Unit, Offset, FormatString)
|
||||||
)
|
)
|
||||||
).
|
).
|
||||||
|
|
||||||
date_to_unix_ts(TimeUnit, FormatString, InputString) ->
|
date_to_unix_ts(TimeUnit, FormatString, InputString) ->
|
||||||
Unit = time_unit(TimeUnit),
|
Unit = time_unit(TimeUnit),
|
||||||
emqx_calendar:parse(InputString, Unit, FormatString).
|
emqx_utils_calendar:parse(InputString, Unit, FormatString).
|
||||||
|
|
||||||
date_to_unix_ts(TimeUnit, Offset, FormatString, InputString) ->
|
date_to_unix_ts(TimeUnit, Offset, FormatString, InputString) ->
|
||||||
Unit = time_unit(TimeUnit),
|
Unit = time_unit(TimeUnit),
|
||||||
OffsetSecond = emqx_calendar:offset_second(Offset),
|
OffsetSecond = emqx_utils_calendar:offset_second(Offset),
|
||||||
OffsetDelta = erlang:convert_time_unit(OffsetSecond, second, Unit),
|
OffsetDelta = erlang:convert_time_unit(OffsetSecond, second, Unit),
|
||||||
date_to_unix_ts(Unit, FormatString, InputString) - OffsetDelta.
|
date_to_unix_ts(Unit, FormatString, InputString) - OffsetDelta.
|
||||||
|
|
||||||
|
@ -1143,7 +1132,7 @@ timezone_to_second(TimeZone) ->
|
||||||
timezone_to_offset_seconds(TimeZone).
|
timezone_to_offset_seconds(TimeZone).
|
||||||
|
|
||||||
timezone_to_offset_seconds(TimeZone) ->
|
timezone_to_offset_seconds(TimeZone) ->
|
||||||
emqx_calendar:offset_second(TimeZone).
|
emqx_utils_calendar:offset_second(TimeZone).
|
||||||
|
|
||||||
'$handle_undefined_function'(sprintf, [Format | Args]) ->
|
'$handle_undefined_function'(sprintf, [Format | Args]) ->
|
||||||
erlang:apply(fun sprintf_s/2, [Format, Args]);
|
erlang:apply(fun sprintf_s/2, [Format, Args]);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{application, emqx_utils, [
|
{application, emqx_utils, [
|
||||||
{description, "Miscellaneous utilities for EMQX apps"},
|
{description, "Miscellaneous utilities for EMQX apps"},
|
||||||
% strict semver, bump manually!
|
% strict semver, bump manually!
|
||||||
{vsn, "5.0.6"},
|
{vsn, "5.0.7"},
|
||||||
{modules, [
|
{modules, [
|
||||||
emqx_utils,
|
emqx_utils,
|
||||||
emqx_utils_api,
|
emqx_utils_api,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Copyright (c) 2019-2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
%% Copyright (c) 2023 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
%%
|
%%
|
||||||
%% Licensed under the Apache License, Version 2.0 (the "License");
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
%% you may not use this file except in compliance with the License.
|
%% you may not use this file except in compliance with the License.
|
||||||
|
@ -14,7 +14,32 @@
|
||||||
%% limitations under the License.
|
%% limitations under the License.
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
-module(emqx_calendar).
|
-module(emqx_utils_calendar).
|
||||||
|
|
||||||
|
-include_lib("typerefl/include/types.hrl").
|
||||||
|
|
||||||
|
-export([
|
||||||
|
formatter/1,
|
||||||
|
format/3,
|
||||||
|
format/4,
|
||||||
|
parse/3,
|
||||||
|
offset_second/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
%% API
|
||||||
|
-export([
|
||||||
|
to_epoch_millisecond/1,
|
||||||
|
to_epoch_second/1,
|
||||||
|
human_readable_duration_string/1
|
||||||
|
]).
|
||||||
|
-export([
|
||||||
|
epoch_to_rfc3339/1,
|
||||||
|
epoch_to_rfc3339/2,
|
||||||
|
now_to_rfc3339/0,
|
||||||
|
now_to_rfc3339/1
|
||||||
|
]).
|
||||||
|
|
||||||
|
-export([time_unit/1]).
|
||||||
|
|
||||||
-define(SECONDS_PER_MINUTE, 60).
|
-define(SECONDS_PER_MINUTE, 60).
|
||||||
-define(SECONDS_PER_HOUR, 3600).
|
-define(SECONDS_PER_HOUR, 3600).
|
||||||
|
@ -24,13 +49,11 @@
|
||||||
-define(DAYS_FROM_0_TO_1970, 719528).
|
-define(DAYS_FROM_0_TO_1970, 719528).
|
||||||
-define(SECONDS_FROM_0_TO_1970, (?DAYS_FROM_0_TO_1970 * ?SECONDS_PER_DAY)).
|
-define(SECONDS_FROM_0_TO_1970, (?DAYS_FROM_0_TO_1970 * ?SECONDS_PER_DAY)).
|
||||||
|
|
||||||
-export([
|
%% the maximum value is the SECONDS_FROM_0_TO_10000 in the calendar.erl,
|
||||||
formatter/1,
|
%% here minus SECONDS_PER_DAY to tolerate timezone time offset,
|
||||||
format/3,
|
%% so the maximum date can reach 9999-12-31 which is ample.
|
||||||
format/4,
|
-define(MAXIMUM_EPOCH, 253402214400).
|
||||||
parse/3,
|
-define(MAXIMUM_EPOCH_MILLI, 253402214400_000).
|
||||||
offset_second/1
|
|
||||||
]).
|
|
||||||
|
|
||||||
-define(DATE_PART, [
|
-define(DATE_PART, [
|
||||||
year,
|
year,
|
||||||
|
@ -50,6 +73,72 @@
|
||||||
timezone2
|
timezone2
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-reflect_type([
|
||||||
|
epoch_millisecond/0,
|
||||||
|
epoch_second/0
|
||||||
|
]).
|
||||||
|
|
||||||
|
-type epoch_second() :: non_neg_integer().
|
||||||
|
-type epoch_millisecond() :: non_neg_integer().
|
||||||
|
-typerefl_from_string({epoch_second/0, ?MODULE, to_epoch_second}).
|
||||||
|
-typerefl_from_string({epoch_millisecond/0, ?MODULE, to_epoch_millisecond}).
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Epoch <-> RFC 3339
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
to_epoch_second(DateTime) ->
|
||||||
|
to_epoch(DateTime, second).
|
||||||
|
|
||||||
|
to_epoch_millisecond(DateTime) ->
|
||||||
|
to_epoch(DateTime, millisecond).
|
||||||
|
|
||||||
|
to_epoch(DateTime, Unit) ->
|
||||||
|
try
|
||||||
|
case string:to_integer(DateTime) of
|
||||||
|
{Epoch, []} -> validate_epoch(Epoch, Unit);
|
||||||
|
_ -> {ok, calendar:rfc3339_to_system_time(DateTime, [{unit, Unit}])}
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
error:_ ->
|
||||||
|
{error, bad_rfc3339_timestamp}
|
||||||
|
end.
|
||||||
|
|
||||||
|
epoch_to_rfc3339(Timestamp) ->
|
||||||
|
epoch_to_rfc3339(Timestamp, millisecond).
|
||||||
|
|
||||||
|
epoch_to_rfc3339(Timestamp, Unit) when is_integer(Timestamp) ->
|
||||||
|
list_to_binary(calendar:system_time_to_rfc3339(Timestamp, [{unit, Unit}])).
|
||||||
|
|
||||||
|
now_to_rfc3339() ->
|
||||||
|
now_to_rfc3339(second).
|
||||||
|
|
||||||
|
now_to_rfc3339(Unit) ->
|
||||||
|
epoch_to_rfc3339(erlang:system_time(Unit), Unit).
|
||||||
|
|
||||||
|
-spec human_readable_duration_string(integer()) -> string().
|
||||||
|
human_readable_duration_string(Milliseconds) ->
|
||||||
|
Seconds = Milliseconds div 1000,
|
||||||
|
{D, {H, M, S}} = calendar:seconds_to_daystime(Seconds),
|
||||||
|
L0 = [{D, " days"}, {H, " hours"}, {M, " minutes"}, {S, " seconds"}],
|
||||||
|
L1 = lists:dropwhile(fun({K, _}) -> K =:= 0 end, L0),
|
||||||
|
L2 = lists:map(fun({Time, Unit}) -> [integer_to_list(Time), Unit] end, L1),
|
||||||
|
lists:flatten(lists:join(", ", L2)).
|
||||||
|
|
||||||
|
validate_epoch(Epoch, _Unit) when Epoch < 0 ->
|
||||||
|
{error, bad_epoch};
|
||||||
|
validate_epoch(Epoch, second) when Epoch =< ?MAXIMUM_EPOCH ->
|
||||||
|
{ok, Epoch};
|
||||||
|
validate_epoch(Epoch, millisecond) when Epoch =< ?MAXIMUM_EPOCH_MILLI ->
|
||||||
|
{ok, Epoch};
|
||||||
|
validate_epoch(_Epoch, _Unit) ->
|
||||||
|
{error, bad_epoch}.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Timestamp <-> any format date string
|
||||||
|
%% Timestamp treat as a superset for epoch, it can be any positive integer
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
formatter(FormatterStr) when is_list(FormatterStr) ->
|
formatter(FormatterStr) when is_list(FormatterStr) ->
|
||||||
formatter(list_to_binary(FormatterStr));
|
formatter(list_to_binary(FormatterStr));
|
||||||
formatter(FormatterBin) when is_binary(FormatterBin) ->
|
formatter(FormatterBin) when is_binary(FormatterBin) ->
|
||||||
|
@ -70,8 +159,10 @@ parse(DateStr, Unit, FormatterBin) when is_binary(FormatterBin) ->
|
||||||
parse(DateStr, Unit, formatter(FormatterBin));
|
parse(DateStr, Unit, formatter(FormatterBin));
|
||||||
parse(DateStr, Unit, Formatter) ->
|
parse(DateStr, Unit, Formatter) ->
|
||||||
do_parse(DateStr, Unit, Formatter).
|
do_parse(DateStr, Unit, Formatter).
|
||||||
%% -------------------------------------------------------------------------------------------------
|
|
||||||
%% internal
|
%%--------------------------------------------------------------------
|
||||||
|
%% Time unit
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
time_unit(second) -> second;
|
time_unit(second) -> second;
|
||||||
time_unit(millisecond) -> millisecond;
|
time_unit(millisecond) -> millisecond;
|
||||||
|
@ -84,10 +175,12 @@ time_unit("nanosecond") -> nanosecond;
|
||||||
time_unit(<<"second">>) -> second;
|
time_unit(<<"second">>) -> second;
|
||||||
time_unit(<<"millisecond">>) -> millisecond;
|
time_unit(<<"millisecond">>) -> millisecond;
|
||||||
time_unit(<<"microsecond">>) -> microsecond;
|
time_unit(<<"microsecond">>) -> microsecond;
|
||||||
time_unit(<<"nanosecond">>) -> nanosecond.
|
time_unit(<<"nanosecond">>) -> nanosecond;
|
||||||
|
time_unit(Any) -> error({invalid_time_unit, Any}).
|
||||||
|
|
||||||
%% -------------------------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% internal: format part
|
%% internal: format part
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
do_formatter(<<>>, Formatter) ->
|
do_formatter(<<>>, Formatter) ->
|
||||||
lists:reverse(Formatter);
|
lists:reverse(Formatter);
|
||||||
|
@ -357,9 +450,9 @@ padding(Data, Len) when Len > 0 andalso erlang:length(Data) < Len ->
|
||||||
padding(Data, _Len) ->
|
padding(Data, _Len) ->
|
||||||
Data.
|
Data.
|
||||||
|
|
||||||
%% -------------------------------------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% internal
|
%% internal: parse part
|
||||||
%% parse part
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
do_parse(DateStr, Unit, Formatter) ->
|
do_parse(DateStr, Unit, Formatter) ->
|
||||||
DateInfo = do_parse_date_str(DateStr, Formatter, #{}),
|
DateInfo = do_parse_date_str(DateStr, Formatter, #{}),
|
||||||
|
@ -476,3 +569,77 @@ str_to_int_or_error(Str, Error) ->
|
||||||
_ ->
|
_ ->
|
||||||
error(Error)
|
error(Error)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Unit Test
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-compile(nowarn_export_all).
|
||||||
|
-compile(export_all).
|
||||||
|
roots() -> [bar].
|
||||||
|
|
||||||
|
fields(bar) ->
|
||||||
|
[
|
||||||
|
{second, ?MODULE:epoch_second()},
|
||||||
|
{millisecond, ?MODULE:epoch_millisecond()}
|
||||||
|
].
|
||||||
|
|
||||||
|
-define(FORMAT(_Sec_, _Ms_),
|
||||||
|
lists:flatten(
|
||||||
|
io_lib:format("bar={second=~w,millisecond=~w}", [_Sec_, _Ms_])
|
||||||
|
)
|
||||||
|
).
|
||||||
|
|
||||||
|
epoch_ok_test() ->
|
||||||
|
BigStamp = 1 bsl 37,
|
||||||
|
Args = [
|
||||||
|
{0, 0, 0, 0},
|
||||||
|
{1, 1, 1, 1},
|
||||||
|
{BigStamp, BigStamp * 1000, BigStamp, BigStamp * 1000},
|
||||||
|
{"2022-01-01T08:00:00+08:00", "2022-01-01T08:00:00+08:00", 1640995200, 1640995200000}
|
||||||
|
],
|
||||||
|
lists:foreach(
|
||||||
|
fun({Sec, Ms, EpochSec, EpochMs}) ->
|
||||||
|
check_ok(?FORMAT(Sec, Ms), EpochSec, EpochMs)
|
||||||
|
end,
|
||||||
|
Args
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
check_ok(Input, Sec, Ms) ->
|
||||||
|
{ok, Data} = hocon:binary(Input, #{}),
|
||||||
|
?assertMatch(
|
||||||
|
#{bar := #{second := Sec, millisecond := Ms}},
|
||||||
|
hocon_tconf:check_plain(?MODULE, Data, #{atom_key => true}, [bar])
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
epoch_failed_test() ->
|
||||||
|
BigStamp = 1 bsl 38,
|
||||||
|
Args = [
|
||||||
|
{-1, -1},
|
||||||
|
{"1s", "1s"},
|
||||||
|
{BigStamp, 0},
|
||||||
|
{0, BigStamp * 1000},
|
||||||
|
{"2022-13-13T08:00:00+08:00", "2022-13-13T08:00:00+08:00"}
|
||||||
|
],
|
||||||
|
lists:foreach(
|
||||||
|
fun({Sec, Ms}) ->
|
||||||
|
check_failed(?FORMAT(Sec, Ms))
|
||||||
|
end,
|
||||||
|
Args
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
check_failed(Input) ->
|
||||||
|
{ok, Data} = hocon:binary(Input, #{}),
|
||||||
|
?assertException(
|
||||||
|
throw,
|
||||||
|
_,
|
||||||
|
hocon_tconf:check_plain(?MODULE, Data, #{atom_key => true}, [bar])
|
||||||
|
),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
-endif.
|
|
@ -0,0 +1 @@
|
||||||
|
Refactored datetime-related modules and functions to simplify the code.
|
Loading…
Reference in New Issue