Add deterministic_parsing env_var
This commit is contained in:
parent
5a790ab4ee
commit
b602984931
2 changed files with 128 additions and 6 deletions
|
@ -144,6 +144,90 @@ be attempted before engaging the `ec_date` parser.
|
||||||
+ `deregister_format(Key)` - Deregister the formatting string from the
|
+ `deregister_format(Key)` - Deregister the formatting string from the
|
||||||
`qdate` server.
|
`qdate` server.
|
||||||
|
|
||||||
|
## About backwards compatibility with `ec_date` and deterministic parsing
|
||||||
|
|
||||||
|
`ec_date` and `dh_date` both have a quirk that bothers me with respect to the
|
||||||
|
parsing of dates that causes some date parsing to be *non-deterministic*. That
|
||||||
|
is, if parsing an incomplete date or time (ie, a text string that is missing a
|
||||||
|
time or a date), `ec_date` will automatically insert the current values of
|
||||||
|
those.
|
||||||
|
|
||||||
|
For example, if the following lines are run a few seconds apart:
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
1> ec_date:parse("2012-02-04").
|
||||||
|
{{2012,2,4},{0,1,10}}
|
||||||
|
2> ec_date:parse("2012-02-04").
|
||||||
|
{{2012,2,4},{0,1,12}}
|
||||||
|
3> ec_date:parse("2012-02-04").
|
||||||
|
{{2012,2,4},{0,1,13}}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, even though the inputs are the same each time, the resulting
|
||||||
|
parsed dates have the current time inferred. The same behavior can be observed
|
||||||
|
if parsing a time without a date:
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
4> ec_date:parse("7pm").
|
||||||
|
{{2013,4,30},{19,0,0}}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, even though the time did not specify a date, the resulting
|
||||||
|
parsed datetime has the date inferred from the current date. Admittedly,
|
||||||
|
inferring the date bothers me less than inferring the time, but in the name of
|
||||||
|
consistency, there should be options for enabling or disabling both.
|
||||||
|
|
||||||
|
### The Solution For Both
|
||||||
|
|
||||||
|
To solve this issue for users that are bothered by this, while preserving
|
||||||
|
backwards compatibility for folks who prefer this, we're going to introduce a
|
||||||
|
`qdate` application environment variable called `deterministic_parsing`.
|
||||||
|
|
||||||
|
The value of `deterministic_parsing` can be a tuple of the following format:
|
||||||
|
|
||||||
|
`{DatePref, TimePref}`
|
||||||
|
|
||||||
|
Where `DatePref` and `TimePref` are either of the following atoms:
|
||||||
|
|
||||||
|
+ `now` - Automatically fill in the missing date or time components with the
|
||||||
|
current time (the is the behavior described above)
|
||||||
|
+ `zero` - Fill in the missing date or time components with zeroed out
|
||||||
|
values. This means that if a date is missing, it'll be set to the unix
|
||||||
|
epoch (`{1970,1,1}`) and if a time is missing, it'll be set to midnight:
|
||||||
|
`{0,0,0}`.
|
||||||
|
|
||||||
|
So, the acceptable combinations can be the following:
|
||||||
|
|
||||||
|
+ `{zero, zero}` - Any missing components will be replaced with zero-values.
|
||||||
|
**(This is the qdate default behavior)**
|
||||||
|
+ `{now, zero}` - If a date is missing, insert the current date, but if a
|
||||||
|
time is missing, set it to midnight.
|
||||||
|
+ `{zero, now}` - If a date is missing, set it to the unix epoch, and if a
|
||||||
|
time is missing, set it to the current time of day.
|
||||||
|
+ `{now, now}` - If either date or time are missing, set it to the current
|
||||||
|
date or current time.
|
||||||
|
|
||||||
|
**Note:** If this application value is not set, the default behavior for
|
||||||
|
`qdate` is to avoid non-determinism and use `{zero, zero}`.
|
||||||
|
|
||||||
|
To set this value, you can either set the value manually in code with:
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
application:set_env(qdate, deterministic_parsing, {now, zero}).
|
||||||
|
```
|
||||||
|
|
||||||
|
or (and this is the preferred method) use a config file and load it with
|
||||||
|
|
||||||
|
`erl -config path/to/file.config`
|
||||||
|
|
||||||
|
Sample config file specifying this application variable:
|
||||||
|
|
||||||
|
```erlang
|
||||||
|
[{qdate, [
|
||||||
|
{deterministic_parsing, {now, zero}}
|
||||||
|
]}].
|
||||||
|
```
|
||||||
|
|
||||||
## Demonstration
|
## Demonstration
|
||||||
|
|
||||||
### Basic Conversion and Formatting
|
### Basic Conversion and Formatting
|
||||||
|
|
|
@ -182,9 +182,17 @@ to_date(ToTZ, RawDate) ->
|
||||||
{ParsedDate,ParsedTZ} ->
|
{ParsedDate,ParsedTZ} ->
|
||||||
{ParsedDate,ParsedTZ}
|
{ParsedDate,ParsedTZ}
|
||||||
end,
|
end,
|
||||||
Date = raw_to_date(RawDate3),
|
try raw_to_date(RawDate3) of
|
||||||
|
D={{_,_,_},{_,_,_}} ->
|
||||||
|
date_tz_to_tz(D, FromTZ, ToTZ)
|
||||||
|
catch
|
||||||
|
_:_ ->
|
||||||
|
case raw_to_date(RawDate) of
|
||||||
|
D2={{_,_,_},{_,_,_}} ->
|
||||||
|
date_tz_to_tz(D2, ?DETERMINE_TZ, ToTZ)
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
date_tz_to_tz(Date, FromTZ, ToTZ).
|
|
||||||
|
|
||||||
extract_timezone(Unixtime) when is_integer(Unixtime) ->
|
extract_timezone(Unixtime) when is_integer(Unixtime) ->
|
||||||
{Unixtime, "GMT"};
|
{Unixtime, "GMT"};
|
||||||
|
@ -223,14 +231,16 @@ minutes_from_gmt_relative_timezone("-", HourStr, MinStr) ->
|
||||||
|
|
||||||
extract_timezone_helper(RevDate, []) ->
|
extract_timezone_helper(RevDate, []) ->
|
||||||
{lists:reverse(RevDate), ?DETERMINE_TZ};
|
{lists:reverse(RevDate), ?DETERMINE_TZ};
|
||||||
extract_timezone_helper(RevDate, [TZ | TZs]) ->
|
extract_timezone_helper(RevDate, [TZ | TZs]) when length(RevDate) >= length(TZ) ->
|
||||||
RevTZ = lists:reverse(TZ),
|
RevTZ = lists:reverse(TZ),
|
||||||
case lists:split(length(TZ),RevDate) of
|
case lists:split(length(TZ),RevDate) of
|
||||||
{RevTZ," " ++ Remainder} ->
|
{RevTZ," " ++ Remainder} ->
|
||||||
{lists:reverse(Remainder), TZ};
|
{lists:reverse(Remainder), TZ};
|
||||||
_ ->
|
_ ->
|
||||||
extract_timezone_helper(RevDate, TZs)
|
extract_timezone_helper(RevDate, TZs)
|
||||||
end.
|
end;
|
||||||
|
extract_timezone_helper(RevDate, [_TZ | TZs]) ->
|
||||||
|
extract_timezone_helper(RevDate, TZs).
|
||||||
|
|
||||||
determine_timezone() ->
|
determine_timezone() ->
|
||||||
case qdate_srv:get_timezone() of
|
case qdate_srv:get_timezone() of
|
||||||
|
@ -243,12 +253,24 @@ determine_timezone() ->
|
||||||
raw_to_date(Unixtime) when is_integer(Unixtime) ->
|
raw_to_date(Unixtime) when is_integer(Unixtime) ->
|
||||||
unixtime_to_date(Unixtime);
|
unixtime_to_date(Unixtime);
|
||||||
raw_to_date(DateString) when is_list(DateString) ->
|
raw_to_date(DateString) when is_list(DateString) ->
|
||||||
ec_date:parse(DateString);
|
ec_date:parse(DateString, get_deterministic_datetime());
|
||||||
raw_to_date(Now = {_,_,_}) ->
|
raw_to_date(Now = {_,_,_}) ->
|
||||||
calendar:now_to_datetime(Now);
|
calendar:now_to_datetime(Now);
|
||||||
raw_to_date(Date = {{_,_,_},{_,_,_}}) ->
|
raw_to_date(Date = {{_,_,_},{_,_,_}}) ->
|
||||||
Date.
|
Date.
|
||||||
|
|
||||||
|
get_deterministic_datetime() ->
|
||||||
|
DateZero = {1970,1,1},
|
||||||
|
TimeZero = {0,0,0},
|
||||||
|
case application:get_env(qdate, deterministic_parsing) of
|
||||||
|
{ok, {zero, zero}} -> {DateZero, TimeZero};
|
||||||
|
{ok, {zero, now}} -> {DateZero, time()};
|
||||||
|
{ok, {now, zero}} -> {date(), TimeZero};
|
||||||
|
{ok, {now, now}} -> {date(), time()};
|
||||||
|
undefined -> {DateZero, TimeZero};
|
||||||
|
{ok, Val} -> throw({invalid_env_var, {qdate, deterministic_parsing, Val}})
|
||||||
|
end.
|
||||||
|
|
||||||
%% If FromTZ is an integer, then it's an integer that represents the number of minutes
|
%% If FromTZ is an integer, then it's an integer that represents the number of minutes
|
||||||
%% relative to GMT. So we convert the date to GMT based on that number, then we can
|
%% relative to GMT. So we convert the date to GMT based on that number, then we can
|
||||||
%% do the other timezone conversion.
|
%% do the other timezone conversion.
|
||||||
|
@ -389,11 +411,27 @@ tz_test_() ->
|
||||||
simple_test(SetupData),
|
simple_test(SetupData),
|
||||||
tz_tests(SetupData),
|
tz_tests(SetupData),
|
||||||
test_process_die(SetupData),
|
test_process_die(SetupData),
|
||||||
parser_format_test(SetupData)
|
parser_format_test(SetupData),
|
||||||
|
test_deterministic_parser(SetupData)
|
||||||
]}
|
]}
|
||||||
end
|
end
|
||||||
}.
|
}.
|
||||||
|
|
||||||
|
test_deterministic_parser(_) ->
|
||||||
|
{inorder, [
|
||||||
|
?_assertEqual(ok, application:set_env(qdate, deterministic_parsing, {now, now})),
|
||||||
|
?_assertEqual({date(), {7,0,0}}, qdate:to_date("7am")),
|
||||||
|
?_assertEqual({{2012,5,10}, time()}, qdate:to_date("2012-5-10")),
|
||||||
|
|
||||||
|
?_assertEqual(ok, application:set_env(qdate, deterministic_parsing, {zero, zero})),
|
||||||
|
?_assertEqual({{1970,1,1}, {7,0,0}}, qdate:to_date("7am")),
|
||||||
|
?_assertEqual({{2012,5,10}, {0,0,0}}, qdate:to_date("2012-5-10")),
|
||||||
|
|
||||||
|
?_assertEqual(ok, application:unset_env(qdate, deterministic_parsing)),
|
||||||
|
?_assertEqual({{1970,1,1}, {7,0,0}}, qdate:to_date("7am")),
|
||||||
|
?_assertEqual({{2012,5,10}, {0,0,0}}, qdate:to_date("2012-5-10"))
|
||||||
|
]}.
|
||||||
|
|
||||||
tz_tests(_) ->
|
tz_tests(_) ->
|
||||||
{inorder,[
|
{inorder,[
|
||||||
?_assertEqual(ok,set_timezone(?SELF_TZ)),
|
?_assertEqual(ok,set_timezone(?SELF_TZ)),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue