fix: port the changes for date_to_unix_ts SQL fun from 4.4
This commit is contained in:
parent
1f38813cb9
commit
163d095dca
|
@ -1181,7 +1181,7 @@ format_date(TimeUnit, Offset, FormatString, TimeEpoch) ->
|
||||||
|
|
||||||
date_to_unix_ts(TimeUnit, FormatString, InputString) ->
|
date_to_unix_ts(TimeUnit, FormatString, InputString) ->
|
||||||
Unit = time_unit(TimeUnit),
|
Unit = time_unit(TimeUnit),
|
||||||
emqx_utils_calendar:parse(InputString, Unit, FormatString).
|
emqx_utils_calendar:formatted_datetime_to_system_time(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),
|
||||||
|
|
|
@ -1143,6 +1143,50 @@ timezone_to_offset_seconds_helper(FunctionName) ->
|
||||||
apply_func(FunctionName, [local]),
|
apply_func(FunctionName, [local]),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
|
t_date_to_unix_ts(_) ->
|
||||||
|
TestTab = [
|
||||||
|
{{"2024-03-01T10:30:38+08:00", second}, [
|
||||||
|
<<"second">>, <<"+08:00">>, <<"%Y-%m-%d %H-%M-%S">>, <<"2024-03-01 10:30:38">>
|
||||||
|
]},
|
||||||
|
{{"2024-03-01T10:30:38.333+08:00", second}, [
|
||||||
|
<<"second">>, <<"+08:00">>, <<"%Y-%m-%d %H-%M-%S.%3N">>, <<"2024-03-01 10:30:38.333">>
|
||||||
|
]},
|
||||||
|
{{"2024-03-01T10:30:38.333+08:00", millisecond}, [
|
||||||
|
<<"millisecond">>,
|
||||||
|
<<"+08:00">>,
|
||||||
|
<<"%Y-%m-%d %H-%M-%S.%3N">>,
|
||||||
|
<<"2024-03-01 10:30:38.333">>
|
||||||
|
]},
|
||||||
|
{{"2024-03-01T10:30:38.333+08:00", microsecond}, [
|
||||||
|
<<"microsecond">>,
|
||||||
|
<<"+08:00">>,
|
||||||
|
<<"%Y-%m-%d %H-%M-%S.%3N">>,
|
||||||
|
<<"2024-03-01 10:30:38.333">>
|
||||||
|
]},
|
||||||
|
{{"2024-03-01T10:30:38.333+08:00", nanosecond}, [
|
||||||
|
<<"nanosecond">>,
|
||||||
|
<<"+08:00">>,
|
||||||
|
<<"%Y-%m-%d %H-%M-%S.%3N">>,
|
||||||
|
<<"2024-03-01 10:30:38.333">>
|
||||||
|
]},
|
||||||
|
{{"2024-03-01T10:30:38.333444+08:00", microsecond}, [
|
||||||
|
<<"microsecond">>,
|
||||||
|
<<"+08:00">>,
|
||||||
|
<<"%Y-%m-%d %H-%M-%S.%6N">>,
|
||||||
|
<<"2024-03-01 10:30:38.333444">>
|
||||||
|
]}
|
||||||
|
],
|
||||||
|
lists:foreach(
|
||||||
|
fun({{DateTime3339, Unit}, DateToTsArgs}) ->
|
||||||
|
?assertEqual(
|
||||||
|
calendar:rfc3339_to_system_time(DateTime3339, [{unit, Unit}]),
|
||||||
|
apply_func(date_to_unix_ts, DateToTsArgs),
|
||||||
|
"Failed on test: " ++ DateTime3339 ++ "/" ++ atom_to_list(Unit)
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
TestTab
|
||||||
|
).
|
||||||
|
|
||||||
t_parse_date_errors(_) ->
|
t_parse_date_errors(_) ->
|
||||||
?assertError(
|
?assertError(
|
||||||
bad_formatter_or_date,
|
bad_formatter_or_date,
|
||||||
|
@ -1154,6 +1198,37 @@ t_parse_date_errors(_) ->
|
||||||
bad_formatter_or_date,
|
bad_formatter_or_date,
|
||||||
emqx_rule_funcs:date_to_unix_ts(second, <<"%y-%m-%d %H:%M:%S">>, <<"2022-05-26 10:40:12">>)
|
emqx_rule_funcs:date_to_unix_ts(second, <<"%y-%m-%d %H:%M:%S">>, <<"2022-05-26 10:40:12">>)
|
||||||
),
|
),
|
||||||
|
%% invalid formats
|
||||||
|
?assertThrow(
|
||||||
|
{missing_date_part, month},
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(
|
||||||
|
second, <<"%Y-%d %H:%M:%S">>, <<"2022-32 10:40:12">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
?assertThrow(
|
||||||
|
{missing_date_part, year},
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(
|
||||||
|
second, <<"%H:%M:%S">>, <<"10:40:12">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
?assertError(
|
||||||
|
_,
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(
|
||||||
|
second, <<"%Y-%m-%d %H:%M:%S">>, <<"2022-05-32 10:40:12">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
?assertError(
|
||||||
|
_,
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(
|
||||||
|
second, <<"%Y-%m-%d %H:%M:%S">>, <<"2023-02-29 10:40:12">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
?assertError(
|
||||||
|
_,
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(
|
||||||
|
second, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-02-30 10:40:12">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
|
||||||
%% Compatibility test
|
%% Compatibility test
|
||||||
%% UTC+0
|
%% UTC+0
|
||||||
|
@ -1173,25 +1248,42 @@ t_parse_date_errors(_) ->
|
||||||
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2022-05-26 10-40-12">>)
|
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2022-05-26 10-40-12">>)
|
||||||
),
|
),
|
||||||
|
|
||||||
%% UTC+0
|
%% leap year checks
|
||||||
UnixTsLeap0 = 1582986700,
|
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
UnixTsLeap0,
|
%% UTC+0
|
||||||
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2020-02-29 14:31:40">>)
|
1709217100,
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-02-29 14:31:40">>)
|
||||||
),
|
),
|
||||||
|
|
||||||
%% UTC+0
|
|
||||||
UnixTsLeap1 = 1709297071,
|
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
UnixTsLeap1,
|
%% UTC+0
|
||||||
|
1709297071,
|
||||||
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-03-01 12:44:31">>)
|
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-03-01 12:44:31">>)
|
||||||
),
|
),
|
||||||
|
|
||||||
%% UTC+0
|
|
||||||
UnixTsLeap2 = 1709535387,
|
|
||||||
?assertEqual(
|
?assertEqual(
|
||||||
UnixTsLeap2,
|
%% UTC+0
|
||||||
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-03-04 06:56:27">>)
|
4107588271,
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(second, <<"%Y-%m-%d %H:%M:%S">>, <<"2100-03-01 12:44:31">>)
|
||||||
|
),
|
||||||
|
?assertEqual(
|
||||||
|
%% UTC+8
|
||||||
|
1709188300,
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(
|
||||||
|
second, <<"+08:00">>, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-02-29 14:31:40">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
?assertEqual(
|
||||||
|
%% UTC+8
|
||||||
|
1709268271,
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(
|
||||||
|
second, <<"+08:00">>, <<"%Y-%m-%d %H:%M:%S">>, <<"2024-03-01 12:44:31">>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
?assertEqual(
|
||||||
|
%% UTC+8
|
||||||
|
4107559471,
|
||||||
|
emqx_rule_funcs:date_to_unix_ts(
|
||||||
|
second, <<"+08:00">>, <<"%Y-%m-%d %H:%M:%S">>, <<"2100-03-01 12:44:31">>
|
||||||
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
%% None zero zone shift with millisecond level precision
|
%% None zero zone shift with millisecond level precision
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
formatter/1,
|
formatter/1,
|
||||||
format/3,
|
format/3,
|
||||||
format/4,
|
format/4,
|
||||||
parse/3,
|
formatted_datetime_to_system_time/3,
|
||||||
offset_second/1
|
offset_second/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
@ -48,8 +48,9 @@
|
||||||
-define(DAYS_PER_YEAR, 365).
|
-define(DAYS_PER_YEAR, 365).
|
||||||
-define(DAYS_PER_LEAP_YEAR, 366).
|
-define(DAYS_PER_LEAP_YEAR, 366).
|
||||||
-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(DAYS_FROM_0_TO_10000, 2932897).
|
||||||
|
-define(SECONDS_FROM_0_TO_1970, ?DAYS_FROM_0_TO_1970 * ?SECONDS_PER_DAY).
|
||||||
|
-define(SECONDS_FROM_0_TO_10000, (?DAYS_FROM_0_TO_10000 * ?SECONDS_PER_DAY)).
|
||||||
%% the maximum value is the SECONDS_FROM_0_TO_10000 in the calendar.erl,
|
%% the maximum value is the SECONDS_FROM_0_TO_10000 in the calendar.erl,
|
||||||
%% here minus SECONDS_PER_DAY to tolerate timezone time offset,
|
%% here minus SECONDS_PER_DAY to tolerate timezone time offset,
|
||||||
%% so the maximum date can reach 9999-12-31 which is ample.
|
%% so the maximum date can reach 9999-12-31 which is ample.
|
||||||
|
@ -171,10 +172,10 @@ format(Time, Unit, Offset, FormatterBin) when is_binary(FormatterBin) ->
|
||||||
format(Time, Unit, Offset, Formatter) ->
|
format(Time, Unit, Offset, Formatter) ->
|
||||||
do_format(Time, time_unit(Unit), offset_second(Offset), Formatter).
|
do_format(Time, time_unit(Unit), offset_second(Offset), Formatter).
|
||||||
|
|
||||||
parse(DateStr, Unit, FormatterBin) when is_binary(FormatterBin) ->
|
formatted_datetime_to_system_time(DateStr, Unit, FormatterBin) when is_binary(FormatterBin) ->
|
||||||
parse(DateStr, Unit, formatter(FormatterBin));
|
formatted_datetime_to_system_time(DateStr, Unit, formatter(FormatterBin));
|
||||||
parse(DateStr, Unit, Formatter) ->
|
formatted_datetime_to_system_time(DateStr, Unit, Formatter) ->
|
||||||
do_parse(DateStr, Unit, Formatter).
|
do_formatted_datetime_to_system_time(DateStr, Unit, Formatter).
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% Time unit
|
%% Time unit
|
||||||
|
@ -467,56 +468,51 @@ padding(Data, _Len) ->
|
||||||
Data.
|
Data.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
%% internal: parse part
|
%% internal: formatted_datetime_to_system_time part
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
do_parse(DateStr, Unit, Formatter) ->
|
do_formatted_datetime_to_system_time(DateStr, Unit, Formatter) ->
|
||||||
DateInfo = do_parse_date_str(DateStr, Formatter, #{}),
|
DateInfo = do_parse_date_str(DateStr, Formatter, #{}),
|
||||||
{Precise, PrecisionUnit} = precision(DateInfo),
|
PrecisionUnit = precision(DateInfo),
|
||||||
Counter =
|
ToPrecisionUnit = fun(Time, FromUnit) ->
|
||||||
fun
|
erlang:convert_time_unit(Time, FromUnit, PrecisionUnit)
|
||||||
(year, V, Res) ->
|
end,
|
||||||
Res + dy(V) * ?SECONDS_PER_DAY * Precise - (?SECONDS_FROM_0_TO_1970 * Precise);
|
GetRequiredPart = fun(Key) ->
|
||||||
(month, V, Res) ->
|
case maps:get(Key, DateInfo, undefined) of
|
||||||
Dm = dym(maps:get(year, DateInfo, 0), V),
|
undefined -> throw({missing_date_part, Key});
|
||||||
Res + Dm * ?SECONDS_PER_DAY * Precise;
|
Value -> Value
|
||||||
(day, V, Res) ->
|
end
|
||||||
Res + (V * ?SECONDS_PER_DAY * Precise);
|
end,
|
||||||
(hour, V, Res) ->
|
GetOptionalPart = fun(Key) -> maps:get(Key, DateInfo, 0) end,
|
||||||
Res + (V * ?SECONDS_PER_HOUR * Precise);
|
Year = GetRequiredPart(year),
|
||||||
(minute, V, Res) ->
|
Month = GetRequiredPart(month),
|
||||||
Res + (V * ?SECONDS_PER_MINUTE * Precise);
|
Day = GetRequiredPart(day),
|
||||||
(second, V, Res) ->
|
Hour = GetRequiredPart(hour),
|
||||||
Res + V * Precise;
|
Min = GetRequiredPart(minute),
|
||||||
(millisecond, V, Res) ->
|
Sec = GetRequiredPart(second),
|
||||||
case PrecisionUnit of
|
DateTime = {{Year, Month, Day}, {Hour, Min, Sec}},
|
||||||
millisecond ->
|
TotalSecs = datetime_to_system_time(DateTime) - GetOptionalPart(parsed_offset),
|
||||||
Res + V;
|
check(TotalSecs, DateStr, Unit),
|
||||||
microsecond ->
|
TotalTime =
|
||||||
Res + (V * 1000);
|
ToPrecisionUnit(TotalSecs, second) +
|
||||||
nanosecond ->
|
ToPrecisionUnit(GetOptionalPart(millisecond), millisecond) +
|
||||||
Res + (V * 1000000)
|
ToPrecisionUnit(GetOptionalPart(microsecond), microsecond) +
|
||||||
end;
|
ToPrecisionUnit(GetOptionalPart(nanosecond), nanosecond),
|
||||||
(microsecond, V, Res) ->
|
erlang:convert_time_unit(TotalTime, PrecisionUnit, Unit).
|
||||||
case PrecisionUnit of
|
|
||||||
microsecond ->
|
|
||||||
Res + V;
|
|
||||||
nanosecond ->
|
|
||||||
Res + (V * 1000)
|
|
||||||
end;
|
|
||||||
(nanosecond, V, Res) ->
|
|
||||||
Res + V;
|
|
||||||
(parsed_offset, V, Res) ->
|
|
||||||
Res - V * Precise
|
|
||||||
end,
|
|
||||||
Count = maps:fold(Counter, 0, DateInfo) - (?SECONDS_PER_DAY * Precise),
|
|
||||||
erlang:convert_time_unit(Count, PrecisionUnit, Unit).
|
|
||||||
|
|
||||||
precision(#{nanosecond := _}) -> {1000_000_000, nanosecond};
|
check(Secs, _, _) when Secs >= -?SECONDS_FROM_0_TO_1970, Secs < ?SECONDS_FROM_0_TO_10000 ->
|
||||||
precision(#{microsecond := _}) -> {1000_000, microsecond};
|
ok;
|
||||||
precision(#{millisecond := _}) -> {1000, millisecond};
|
check(_Secs, DateStr, Unit) ->
|
||||||
precision(#{second := _}) -> {1, second};
|
throw({bad_format, #{date_string => DateStr, to_unit => Unit}}).
|
||||||
precision(_) -> {1, second}.
|
|
||||||
|
datetime_to_system_time(DateTime) ->
|
||||||
|
calendar:datetime_to_gregorian_seconds(DateTime) - ?SECONDS_FROM_0_TO_1970.
|
||||||
|
|
||||||
|
precision(#{nanosecond := _}) -> nanosecond;
|
||||||
|
precision(#{microsecond := _}) -> microsecond;
|
||||||
|
precision(#{millisecond := _}) -> millisecond;
|
||||||
|
precision(#{second := _}) -> second;
|
||||||
|
precision(_) -> second.
|
||||||
|
|
||||||
do_parse_date_str(<<>>, _, Result) ->
|
do_parse_date_str(<<>>, _, Result) ->
|
||||||
Result;
|
Result;
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Refactor the SQL function: `date_to_unix_ts()` by using `calendar:datetime_to_gregorian_seconds/1`.
|
||||||
|
This change also added validation for the input date format.
|
Loading…
Reference in New Issue