From 5e43673c3419b54ff13b729c946e646adeb9518d Mon Sep 17 00:00:00 2001 From: Jesse Gumm Date: Sat, 5 Mar 2016 13:51:45 -0600 Subject: [PATCH 1/4] Add before and after as operators --- src/qdate.erl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/qdate.erl b/src/qdate.erl index 12d2db8..953db40 100644 --- a/src/qdate.erl +++ b/src/qdate.erl @@ -267,7 +267,7 @@ to_date(ToTZ, Disambiguate, RawDate) -> {ParsedDate,ExtractedTZ}; {ParsedDate,ParsedTZ} -> {ParsedDate,ParsedTZ} - end, + end, try raw_to_date(RawDate3) of D={{_,_,_},{_,_,_}} -> date_tz_to_tz(D, Disambiguate, FromTZ, ToTZ); @@ -403,10 +403,12 @@ compare(A, Op, B) -> '=/=' -> Comp =/= 0; '/=' -> Comp =/= 0; + 'before'-> Comp =:= -1; '<' -> Comp =:= -1; '<=' -> Comp =:= -1 orelse Comp =:= 0; '=<' -> Comp =:= -1 orelse Comp =:= 0; + 'after' -> Comp =:= 1; '>' -> Comp =:= 1; '>=' -> Comp =:= 1 orelse Comp =:= 0; '=>' -> Comp =:= 1 orelse Comp =:= 0 From 5d73286e925749f62a07dfa6970ac4cd24e77a7d Mon Sep 17 00:00:00 2001 From: Jesse Gumm Date: Sat, 5 Mar 2016 13:52:02 -0600 Subject: [PATCH 2/4] Allow a custom parser to return a unix timestamp --- src/qdate.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qdate.erl b/src/qdate.erl index 953db40..d43f637 100644 --- a/src/qdate.erl +++ b/src/qdate.erl @@ -768,6 +768,8 @@ try_parsers(_RawDate,[]) -> undefined; try_parsers(RawDate,[{ParserKey,Parser}|Parsers]) -> try Parser(RawDate) of + Timestamp when is_integer(Timestamp) -> + {Timestamp, undefined}; {{_,_,_},{_,_,_}} = DateTime -> {DateTime,undefined}; {DateTime={{_,_,_},{_,_,_}},Timezone} -> From 41b313d425883095fc8e41bf971452eefe87b2b7 Mon Sep 17 00:00:00 2001 From: Jesse Gumm Date: Sat, 5 Mar 2016 15:16:03 -0600 Subject: [PATCH 3/4] Add relative date/time parsing parser. --- src/qdate.erl | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/qdate.erl b/src/qdate.erl index d43f637..8f9c5ea 100644 --- a/src/qdate.erl +++ b/src/qdate.erl @@ -61,6 +61,8 @@ add_months/1, add_years/2, add_years/1, + add_unit/2, + add_unit/3, add_date/2 ]). @@ -94,6 +96,9 @@ clear_timezone/1 ]). +-export([ + parse_relative/1 +]). %% Exported for API compatibility with ec_date -export([ @@ -309,7 +314,8 @@ to_unixtime(Date) -> to_unixtime(_, Unixtime) when is_integer(Unixtime) -> Unixtime; -to_unixtime(_, {MegaSecs,Secs,_}) -> + +to_unixtime(_, {MegaSecs,Secs,_}) when is_integer(MegaSecs), is_integer(Secs) -> MegaSecs*1000000 + Secs; to_unixtime(Disamb, ToParse) -> %% We want to treat all unixtimes as GMT @@ -483,6 +489,39 @@ add_years(Years, Date) -> add_years(Years) -> add_years(Years, os:timestamp()). +add_unit(second, Value, Date) -> + add_unit(seconds, Value, Date); +add_unit(seconds, Value, Date) -> + add_seconds(Value, Date); +add_unit(minute, Value, Date) -> + add_unit(minutes, Value, Date); +add_unit(minutes, Value, Date) -> + add_minutes(Value, Date); +add_unit(hour, Value, Date) -> + add_unit(hours, Value, Date); +add_unit(hours, Value, Date) -> + add_hours(Value, Date); +add_unit(day, Value, Date) -> + add_unit(days, Value, Date); +add_unit(days, Value, Date) -> + add_days(Value, Date); +add_unit(week, Value, Date) -> + add_unit(weeks, Value, Date); +add_unit(weeks, Value, Date) -> + add_weeks(Value, Date); +add_unit(month, Value, Date) -> + add_unit(months, Value, Date); +add_unit(months, Value, Date) -> + add_months(Value, Date); +add_unit(year, Value, Date) -> + add_unit(years, Value, Date); +add_unit(years, Value, Date) -> + add_years(Value, Date). + +add_unit(Unit, Value) -> + add_unit(Unit, Value, os:timestamp()). + + add_date({{AddY, AddM, AddD}, {AddH, AddI, AddS}}, Date) -> {{Y, M, D}, {H, I, S}} = to_date(Date), Date1 = fix_maybe_improper_date({{Y+AddY, M+AddM, D+AddD}, {H, I, S}}), @@ -610,6 +649,58 @@ range_years(Interval, Start, Finish) -> range(years, Interval, Start, Finish). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%% Relative Date Parsing %%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +parse_relative({relative, Date, Relation}) when is_atom(Relation) -> + parse_relative({relative, Date, atom_to_list(Relation)}); +parse_relative({relative, Date, Relation}) -> + {OpStr, NumStr, UnitStr} = parse_actual_relation(Relation), + {Num, Unit} = normalize_relative_matches(OpStr, NumStr, UnitStr), + add_unit(Unit, Num, Date); +parse_relative(now) -> + unixtime(); +parse_relative("now") -> + unixtime(); +parse_relative(<<"now">>) -> + unixtime(); +parse_relative(Relation) -> + parse_relative({relative, unixtime(), Relation}). + + +%% I would do this function recursively, but the return order of arguments +%% inconsistent, so I just leave it like this. It's a little nasty to have the +%% nested case expressions, but I can deal with it. +parse_actual_relation(Relation) -> + PrefixRE = "^(\\-|\\+|in)\\s?(\\d+) (second|minute|hour|day|week|month|year)s?$", + SuffixRE = "^(\\d+) (second|minute|hour|day|week|month|year)s\\s?(ago|from now)?$", + case re:run(Relation, PrefixRE, [{capture, all_but_first, list}]) of + nomatch -> + case re:run(Relation, SuffixRE, [{capture, all_but_first, list}]) of + nomatch -> undefined; + {match, [NumStr, UnitStr, OpStr]} -> + {OpStr, NumStr, UnitStr} + end; + {match, [OpStr, NumStr, UnitStr]} -> + {OpStr, NumStr, UnitStr} + end. + +normalize_relative_matches(OpStr, NumStr, UnitStr) -> + Op = normalize_relative_op(OpStr), + Num = list_to_integer(Op ++ NumStr), + Unit = list_to_existing_atom(UnitStr), + {Num, Unit}. + +normalize_relative_op(Op) -> + case Op of + "+" -> "+"; + "-" -> "-"; + "ago" -> "-"; + "from now" -> "+"; + "in" -> "+" + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%% Timezone Stuff %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -637,6 +728,8 @@ extract_timezone(DateString) when is_list(DateString) -> end; extract_timezone(Date={{_,_,_},{_,_,_}}) -> {Date, ?DETERMINE_TZ}; +extract_timezone(Rel={relative, _, _}) -> + {Rel, "GMT"}; extract_timezone(Now={_,_,_}) -> {Now, "GMT"}; extract_timezone({MiscDate,TZ}) -> From 1ea2cadb1a868d1654192e7dd77ecba1f63a9a5a Mon Sep 17 00:00:00 2001 From: Jesse Gumm Date: Sat, 5 Mar 2016 16:26:58 -0600 Subject: [PATCH 4/4] Fix relative parser --- src/qdate.erl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/qdate.erl b/src/qdate.erl index 8f9c5ea..e7afb7b 100644 --- a/src/qdate.erl +++ b/src/qdate.erl @@ -655,8 +655,8 @@ range_years(Interval, Start, Finish) -> parse_relative({relative, Date, Relation}) when is_atom(Relation) -> parse_relative({relative, Date, atom_to_list(Relation)}); -parse_relative({relative, Date, Relation}) -> - {OpStr, NumStr, UnitStr} = parse_actual_relation(Relation), +parse_relative({relative, Date, Relation}) when is_list(Relation); is_binary(Relation) -> + {OpStr, NumStr, UnitStr} = parse_actual_relation(Relation), {Num, Unit} = normalize_relative_matches(OpStr, NumStr, UnitStr), add_unit(Unit, Num, Date); parse_relative(now) -> @@ -665,8 +665,10 @@ parse_relative("now") -> unixtime(); parse_relative(<<"now">>) -> unixtime(); -parse_relative(Relation) -> - parse_relative({relative, unixtime(), Relation}). +parse_relative(Relation) when is_list(Relation); is_binary(Relation) -> + parse_relative({relative, unixtime(), Relation}); +parse_relative(_) -> + undefined. %% I would do this function recursively, but the return order of arguments @@ -873,7 +875,8 @@ try_parsers(RawDate,[{ParserKey,Parser}|Parsers]) -> throw({invalid_parser_return_value,[{parser_key,ParserKey},{return,Other}]}) catch Error:Reason -> - throw({error_in_parser,[{error,{Error,Reason}},{parser_key,ParserKey}]}) + Stacktrace = erlang:get_stacktrace(), + throw({error_in_parser,[{error,{Error,Reason}},{parser_key,ParserKey}, {stacktrace, Stacktrace}]}) end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%