lots of gutting. purposes, input, and output were unclear in many places. still broken.
This commit is contained in:
40
src/ezic.erl
40
src/ezic.erl
@@ -4,22 +4,16 @@
|
||||
-export([
|
||||
localtime/1
|
||||
, utc_to_local/2
|
||||
% , local_to_utc/2
|
||||
% , zone_convert/3
|
||||
, next_timechange/1
|
||||
, next_timechange/2
|
||||
, next_timechange_after/2
|
||||
]).
|
||||
|
||||
-export([load/1, dev_start/0, test/0]).
|
||||
|
||||
|
||||
-define(gs2dt(X), calendar:gregorian_seconds_to_datetime(X)).
|
||||
-define(dt2gs(X), calendar:datetime_to_gregorian_seconds(X)).
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% API
|
||||
% PUBLIC API
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
@@ -27,30 +21,17 @@
|
||||
localtime(TzName) ->
|
||||
utc_to_local(erlang:universaltime(), TzName).
|
||||
|
||||
|
||||
utc_to_local(UTCDatetime, TzName) ->
|
||||
{ok, Zone, Rule}= current_as_of_utc(UTCDatetime, TzName),
|
||||
SecDiff= time_offset(Zone, Rule),
|
||||
date_add(UTCDatetime, SecDiff).
|
||||
ezic_date:add_seconds(UTCDatetime, SecDiff).
|
||||
|
||||
|
||||
%% local_to_utc(Datetime, TzName) ->
|
||||
%% {ok, Zone, Rule}= current_as_of_local(Datetime, TzName),
|
||||
%% SecDiff= time_offset(Zone, Rule),
|
||||
%% date_subtract(Datetime, SecDiff).
|
||||
|
||||
|
||||
%% zone_convert(Datetime, FromTimeZone, ToTimeZone) ->
|
||||
%% UTC= local_to_utc(Datetime, FromTimeZone),
|
||||
%% utc_to_local(UTC, ToTimeZone).
|
||||
|
||||
|
||||
|
||||
next_timechange(TzName) ->
|
||||
next_timechange(erlang:universaltime(), TzName).
|
||||
next_timechange_after(erlang:universaltime(), TzName).
|
||||
|
||||
next_timechange(UTCDatetime, TzName) ->
|
||||
UTCDatetime, TzName,
|
||||
next_timechange_after(_UTCDatetime, _TzName) ->
|
||||
not_done.
|
||||
|
||||
|
||||
@@ -64,8 +45,7 @@ next_timechange(UTCDatetime, TzName) ->
|
||||
|
||||
|
||||
load(Folder) ->
|
||||
ezic_loader:load(Folder),
|
||||
ezic_flatten:flatten().
|
||||
ezic_loader:load(Folder).
|
||||
|
||||
|
||||
dev_start() ->
|
||||
@@ -103,11 +83,3 @@ time_offset(Zone, Rule) ->
|
||||
DSTSec= ezic_rule:dst_sec(Rule),
|
||||
OffsetSec + DSTSec.
|
||||
|
||||
|
||||
|
||||
date_add(Datetime, SecDiff) ->
|
||||
?gs2dt(?dt2gs(Datetime) + SecDiff).
|
||||
|
||||
%% date_subtract(Datetime, SecDiff) ->
|
||||
%% ?gs2dt(?dt2gs(Datetime) - SecDiff).
|
||||
|
||||
|
||||
@@ -1,86 +1,52 @@
|
||||
-module(ezic_date).
|
||||
-include("include/ezic.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
|
||||
|
||||
-ifdef(TEST).
|
||||
-compile([export_all]).
|
||||
-endif.
|
||||
-export([
|
||||
% rule-specific
|
||||
for_rule/2
|
||||
, for_rule_zone/3
|
||||
|
||||
|
||||
% converters
|
||||
, month_to_num/1
|
||||
, day_to_num/1
|
||||
|
||||
|
||||
% date math
|
||||
, add_seconds/2
|
||||
, all_times/3
|
||||
]).
|
||||
|
||||
|
||||
|
||||
|
||||
-export([for/2]).
|
||||
-export([month_to_num/1, day_to_num/1]).
|
||||
-export([compare_times/2, compare_datetimes/2, normalize/1, overlap/2, date_between/2]).
|
||||
|
||||
-export([add_seconds/2, all_times/3]).
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% PUBLIC API
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
% returns the date tuple for a given rule and year
|
||||
% same for all timezones.
|
||||
% e.g. -> {Y,M,D}
|
||||
for(#rule{in=M, on={last, D}}, Y) ->
|
||||
% returns {Y,M,D} for a rule and Year
|
||||
for_rule(#rule{in=M, on={last, D}}, Y) ->
|
||||
last_day_of(D, Y,M);
|
||||
for(#rule{in=M, on=#tzon{day=Day, filter=Filter}}, Y) ->
|
||||
for_rule(#rule{in=M, on=#tzon{day=Day, filter=Filter}}, Y) ->
|
||||
first_day_limited(Day, Filter, Y,M).
|
||||
|
||||
|
||||
% returns the date on which the last Day (sun,mon,tue,etc.) occurs in a given month/year
|
||||
% same for all timezones
|
||||
last_day_of(Day, Y,M) ->
|
||||
LastDay= calendar:last_day_of_the_month(Y,M),
|
||||
previous_day(Day, {Y,M,LastDay}).
|
||||
|
||||
|
||||
% returns a date tuple {Y,M,D} representing the first available date, given the filter
|
||||
% same for all timezones
|
||||
first_day_limited(Day, {geq, N}, Y,M) ->
|
||||
next_day(Day, {Y,M,N});
|
||||
first_day_limited(Day, {leq, N}, Y,M) ->
|
||||
previous_day(Day, {Y,M,N}).
|
||||
% for_rule(#rule{}, Zone, Year) -> {WallTime, STDTime, UTCTime}
|
||||
% returns the date set for a given rule and year
|
||||
% same for all timezones.
|
||||
for_rule_zone(#rule{in=M, on={last, D}, at=_Time, save=_DSTOffset}, _Zone=#zone{gmtoff=_Offset}, Y) ->
|
||||
_LDO= last_day_of(D, Y,M),
|
||||
|
||||
not_done;
|
||||
for_rule_zone(#rule{in=M, on=#tzon{day=Day, filter=Filter}}, _Zone, Y) ->
|
||||
first_day_limited(Day, Filter, Y,M),
|
||||
|
||||
|
||||
% Returns the soonest date on which Day (sun/mon/tue/etc.) occurs BEFORE the given date.
|
||||
% same for all timezones
|
||||
previous_day(Day, Date={Y,M,D}) ->
|
||||
Daynum= day_to_num(Day),
|
||||
LeqDoW= calendar:day_of_the_week(Date),
|
||||
case Daynum=:=LeqDoW of
|
||||
true -> Date;
|
||||
_ ->
|
||||
DayDiff= case LeqDoW-Daynum < 0 of
|
||||
true -> LeqDoW-Daynum+7;
|
||||
_ -> LeqDoW-Daynum
|
||||
end,
|
||||
case D-DayDiff < 0 of
|
||||
true -> erlang:error(no_previous_day, [Day, Date]);
|
||||
false -> {Y,M,D-DayDiff}
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
% Returns the soonest date on which Day (sun/mon/tue/etc.) occurs AFTER the given date.
|
||||
% same for all timezones
|
||||
next_day(Day, Date={Y,M,D}) ->
|
||||
Daynum= day_to_num(Day),
|
||||
LeqDoW= calendar:day_of_the_week(Date),
|
||||
case Daynum=:=LeqDoW of
|
||||
true -> Date;
|
||||
_ ->
|
||||
DayDiff= case Daynum-LeqDoW < 0 of
|
||||
true -> Daynum-LeqDoW+7;
|
||||
_ -> Daynum-LeqDoW
|
||||
end,
|
||||
case D+DayDiff > calendar:last_day_of_the_month(Y,M) of
|
||||
true -> erlang:error(no_next_day, [Day, Date]);
|
||||
false -> {Y,M,D+DayDiff}
|
||||
end
|
||||
end.
|
||||
not_done.
|
||||
|
||||
|
||||
|
||||
@@ -107,127 +73,18 @@ day_to_num("Thu") -> 4;
|
||||
day_to_num("Fri") -> 5;
|
||||
day_to_num("Sat") -> 6;
|
||||
day_to_num("Sun") -> 7;
|
||||
day_to_num(_) -> -1.
|
||||
day_to_num(X) -> erlang:error(badday, X).
|
||||
|
||||
|
||||
|
||||
compare_datetimes(DT1, DT2) ->
|
||||
NDT1= normalize(DT1),
|
||||
NDT2= normalize(DT2),
|
||||
compare_datetimes_normal(NDT1, NDT2).
|
||||
|
||||
|
||||
compare_datetimes_normal(minimum, _) -> true;
|
||||
compare_datetimes_normal(_, maximum) -> true;
|
||||
|
||||
|
||||
compare_datetimes_normal(current, current) -> true;
|
||||
compare_datetimes_normal(current, _) -> false;
|
||||
compare_datetimes_normal(_,current) -> true;
|
||||
|
||||
% @bug this is not true if the times are from different time zones
|
||||
% @todo check types for D1 and D2
|
||||
compare_datetimes_normal({D1, _}, {D2, _}) when D1 < D2 ->
|
||||
true;
|
||||
compare_datetimes_normal({D1, _}, {D2, _}) when D1 > D2 ->
|
||||
false;
|
||||
compare_datetimes_normal({_, T1}, {_, T2}) ->
|
||||
compare_times(T1, T2).
|
||||
|
||||
|
||||
compare_times(TZ1=#tztime{}, TZ2=#tztime{}) ->
|
||||
{NT1, NT2} = time_equalize(TZ1, TZ2),
|
||||
NT1 =< NT2;
|
||||
compare_times(Tz1, Tz2) ->
|
||||
erlang:error(wtf_not_done, [Tz1, Tz2]).
|
||||
|
||||
|
||||
|
||||
time_equalize(#tztime{time=T1, flag=F1}, #tztime{time=T2, flag=F2}) when F1=:=F2 ->
|
||||
{T1, T2};
|
||||
time_equalize(T1, T2) ->
|
||||
%@todo this may be difficult. gotta do it!
|
||||
erlang:error(wtf_not_done, T1, T2).
|
||||
|
||||
|
||||
|
||||
normalize(current) -> current;
|
||||
normalize(only) -> only;
|
||||
normalize(maximum) -> maximum;
|
||||
normalize(minimum) -> minimum;
|
||||
|
||||
|
||||
normalize(Y) when is_integer(Y) ->
|
||||
{{Y,1,1}, #tztime{}};
|
||||
normalize(Date={_,_,D}) when is_integer(D) ->
|
||||
{Date, #tztime{}};
|
||||
normalize({Y,M,{last, D}}) ->
|
||||
Date=last_day_of(D, Y,M),
|
||||
{Date, #tztime{}};
|
||||
|
||||
|
||||
normalize({Date={_,_,_},Time={H,_,_}}) when is_integer(H) ->
|
||||
{Date, #tztime{time=Time}};
|
||||
|
||||
normalize({Date={_,_,D}, Tz=#tztime{}}) when is_integer(D) ->
|
||||
{Date, Tz};
|
||||
normalize({{Y,M,#tzon{day=Day, filter=Filter}}, Tz=#tztime{}}) ->
|
||||
Date= first_day_limited(Day, Filter, Y,M),
|
||||
{Date, Tz};
|
||||
normalize({{Y,M,{last, D}}, Tz=#tztime{}}) ->
|
||||
Date=last_day_of(D, Y,M),
|
||||
{Date, Tz};
|
||||
normalize(X) ->
|
||||
?debug("normalize: ~p", [X]),
|
||||
erlang:error(wtf_not_done, X).
|
||||
|
||||
|
||||
|
||||
|
||||
overlap({D1s,D1e}, {D2s,D2e}) ->
|
||||
D1sN= normalize(D1s),
|
||||
D1eN= normalize(D1e),
|
||||
D2sN= normalize(D2s),
|
||||
D2eN= normalize(D2e),
|
||||
dates_overlap_normal({D1sN,D1eN}, {D2sN,D2eN}).
|
||||
|
||||
|
||||
dates_overlap_normal({D1s,D1e}, {D2s,D2e}) ->
|
||||
date_between_normal(D1s, {D2s,D2e})
|
||||
orelse date_between_normal(D1e, {D2s,D2e}).
|
||||
|
||||
|
||||
date_between(D1, {D2s, D2e}) ->
|
||||
D1N= normalize(D1),
|
||||
D2sN= normalize(D2s),
|
||||
D2eN= normalize(D2e),
|
||||
date_between_normal(D1N, {D2sN, D2eN}).
|
||||
|
||||
|
||||
date_between_normal(minimum, {minimum,_}) -> true;
|
||||
date_between_normal(minimum, {_,_}) -> false;
|
||||
|
||||
date_between_normal(maximum, {_, maximum}) -> true;
|
||||
date_between_normal(maximum, {_,_}) -> false;
|
||||
|
||||
date_between_normal(only, _) -> false;
|
||||
date_between_normal(current, {_, current}) -> true;
|
||||
date_between_normal(current, {_, End}) when End =/= current -> false;
|
||||
date_between_normal(D1, {D2, only}) ->
|
||||
{{Y1,_,_},_}=D1,
|
||||
{{Y2,_,_},_}=D2,
|
||||
Y1 =:= Y2;
|
||||
date_between_normal(D1, {D2s, D2e}) ->
|
||||
compare_datetimes_normal(D2s, D1)
|
||||
andalso compare_datetimes_normal(D1, D2e).
|
||||
|
||||
|
||||
|
||||
|
||||
add_seconds(Datetime, Seconds) ->
|
||||
try
|
||||
calendar:gregorian_seconds_to_datetime(
|
||||
calendar:date_to_gregorian_seconds(Datetime) + Seconds
|
||||
).
|
||||
calendar:datetime_to_gregorian_seconds(Datetime) + Seconds
|
||||
)
|
||||
catch
|
||||
error:Reason -> erlang:error(baddate, Datetime)
|
||||
end.
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +94,7 @@ add_seconds(Datetime, Seconds) ->
|
||||
% where each is a datetime tuple: {{Y,M,D}{HH,MM,SS}}
|
||||
|
||||
% universal time given
|
||||
all_times(Datetime=#tztime{time=UTCTime, flag=Flag}, Offset, DSTOffset)
|
||||
all_times(#tztime{time=UTCTime, flag=Flag}, Offset, DSTOffset)
|
||||
when Flag=:=u; Flag=:=g; Flag=:=z ->
|
||||
OSec= calendar:time_to_seconds(Offset),
|
||||
DSTSec= calendar:time_to_seconds(DSTOffset),
|
||||
@@ -249,7 +106,7 @@ all_times(Datetime=#tztime{time=UTCTime, flag=Flag}, Offset, DSTOffset)
|
||||
|
||||
|
||||
% standard time given
|
||||
all_times(Datetime=#tztime{time=STDTime, flag=s}, Offset, DSTOffset) ->
|
||||
all_times(#tztime{time=STDTime, flag=s}, Offset, DSTOffset) ->
|
||||
OSec= calendar:time_to_seconds(Offset),
|
||||
DSTSec= calendar:time_to_seconds(DSTOffset),
|
||||
|
||||
@@ -260,7 +117,7 @@ all_times(Datetime=#tztime{time=STDTime, flag=s}, Offset, DSTOffset) ->
|
||||
|
||||
|
||||
% wall time given
|
||||
all_times(Datetime=#tztime{time=WallTime, flag=Flag}, Offset, DSTOffset)
|
||||
all_times(#tztime{time=WallTime, flag=Flag}, Offset, DSTOffset)
|
||||
when Flag=:=w, Flag=:=undefined ->
|
||||
OSec= calendar:time_to_seconds(Offset),
|
||||
DSTSec= calendar:time_to_seconds(DSTOffset),
|
||||
@@ -271,3 +128,93 @@ all_times(Datetime=#tztime{time=WallTime, flag=Flag}, Offset, DSTOffset)
|
||||
{WallTime, STDTime, UTCTime}.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% PRIVATE
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
|
||||
% last_day_of(Day, Y,M) -> {Y,M,D}
|
||||
% returns the date on which the last Day (sun,mon,tue,etc.) occurs in a given month/year
|
||||
% same for all timezones
|
||||
last_day_of(Day, Y,M) ->
|
||||
LastDay= calendar:last_day_of_the_month(Y,M),
|
||||
previous_day(Day, {Y,M,LastDay}).
|
||||
|
||||
|
||||
% returns a date tuple {Y,M,D} representing the first available date, given the filter
|
||||
% same for all timezones
|
||||
first_day_limited(Day, {geq, N}, Y,M) ->
|
||||
next_day(Day, {Y,M,N});
|
||||
first_day_limited(Day, {leq, N}, Y,M) ->
|
||||
previous_day(Day, {Y,M,N}).
|
||||
|
||||
|
||||
|
||||
% Returns the soonest date on which Day (sun/mon/tue/etc.) occurs BEFORE the given date.
|
||||
% same for all timezones
|
||||
previous_day(Day, Date) ->
|
||||
Daynum= day_to_num(Day),
|
||||
LeqDoW= calendar:day_of_the_week(Date),
|
||||
case Daynum=:=LeqDoW of
|
||||
true -> Date;
|
||||
_ ->
|
||||
DayDiff= day_diff(LeqDoW, Daynum),
|
||||
add_days_in_month(-DayDiff, Date)
|
||||
end.
|
||||
|
||||
|
||||
% Returns the soonest date on which Day (sun/mon/tue/etc.) occurs AFTER the given date.
|
||||
% same for all timezones
|
||||
next_day(Day, Date) ->
|
||||
Daynum= day_to_num(Day),
|
||||
LeqDoW= calendar:day_of_the_week(Date),
|
||||
case Daynum=:=LeqDoW of
|
||||
true -> Date;
|
||||
_ ->
|
||||
DayDiff= day_diff(Daynum, LeqDoW),
|
||||
add_days_in_month(DayDiff, Date)
|
||||
end.
|
||||
|
||||
|
||||
% returns absolute number of days until the next day-of-the-week,
|
||||
% given some current day-of-the-week.
|
||||
day_diff(From, To) ->
|
||||
case From - To < 0 of
|
||||
true -> From - To + 7;
|
||||
_ -> From - To
|
||||
end.
|
||||
|
||||
|
||||
% adds/subtracts days within a month
|
||||
% errors-out if arguments require change of month
|
||||
add_days_in_month(Days, Date={Y,M,D}) ->
|
||||
case D+Days < 1 of
|
||||
true -> erlang:error(no_previous_day, {Days, Date});
|
||||
_ ->
|
||||
case D+Days > calendar:last_day_of_the_month(Y,M) of
|
||||
true -> erlang:error(no_next_day, {Days, Date});
|
||||
_ -> {Y,M,D+Days}
|
||||
end
|
||||
end.
|
||||
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% INTERNAL
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
|
||||
normalize(_Datetime) ->
|
||||
not_done.
|
||||
|
||||
compare_normal(_DT1, _DT2) ->
|
||||
not_done.
|
||||
|
||||
|
||||
@@ -74,11 +74,7 @@ flatten_zones([Z1|_]= AllZones, Flats) ->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
% takes zones one-by-one, gathering relevant rules and create flat periods of the same gmt offset (#flatzone)
|
||||
% takes zones one-by-one, gathering relevant rules and creating flat periods of the same gmt offset (#flatzone)
|
||||
flatten_zone_set(Zones) ->
|
||||
flatten_zone_set(?MINFLAT, Zones, []).
|
||||
|
||||
@@ -91,20 +87,20 @@ flatten_zone_set(Zones) ->
|
||||
% assumes a new zone every time it is called
|
||||
flatten_zone_set(_, [], Flats) ->
|
||||
Flats;
|
||||
flatten_zone_set(FromTimeStub, [Z1=#zone{rule=RuleName, until=UntilTime, gmtoff=Offset} | RestZones] = Zones, Flats) ->
|
||||
flatten_zone_set(FromTimeStub, [Z1=#zone{rule=RuleName, until=UntilTime, gmtoff=Offset} | _RestZones] = _Zones, _Flats) ->
|
||||
|
||||
%% we have a flatzone with start times, we populate the base offset
|
||||
FromTime= FromTimeStub#flatzone{offset=Offset},
|
||||
|
||||
|
||||
|
||||
%% we gather all rules that _may_ apply (same year)
|
||||
Rules= ezic_db:rules(RuleName),
|
||||
RelevantRules= ezic_rule:filter(FromTime, UntilTime, Rules),
|
||||
SortedRules= lists:sort(fun ezic_rule:sort_ascending/2, RelevantRules),
|
||||
|
||||
|
||||
%% Then we plot points, bouncing from rule to rule, ending when the zone ends
|
||||
{ok, NextFromTimeStub, NewFlats}= flatten_zone(FromTime, Z1, SortedRules),
|
||||
{ok, _NextFromTimeStub, _NewFlats}= flatten_zone(FromTime, Z1, SortedRules),
|
||||
|
||||
|
||||
not_done.
|
||||
|
||||
@@ -10,10 +10,10 @@ load(File) ->
|
||||
true -> {ok, parse_dir(File)};
|
||||
false -> case filelib:is_regular(File) of
|
||||
true -> {ok, parse_file(File)};
|
||||
false -> {error, {badFile, File}}
|
||||
false -> erlang:error(badFile, File)
|
||||
end
|
||||
end,
|
||||
{ok, Zones, Rules, Leaps, Links} = ezic_compile:separate(Records),
|
||||
{ok, Zones, Rules, Leaps, Links} = ezic_record:separate(Records),
|
||||
|
||||
ezic_db:wipe(),
|
||||
ezic_db:init(),
|
||||
@@ -27,12 +27,11 @@ load(File) ->
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% PRIVATE
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% INTERNAL
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
|
||||
% returns a list of tzdata records for all files in folder
|
||||
@@ -56,21 +55,18 @@ parse_lines(eof, _, Records) ->
|
||||
parse_lines({ok, Line}, File, Records) ->
|
||||
StrLine= clean_line(Line),
|
||||
NewRecords= case length(StrLine) > 0 of
|
||||
false -> Records;
|
||||
true -> [parse_to_record(StrLine, File, Records) | Records]
|
||||
true -> [parse_to_record(StrLine, File, Records) | Records];
|
||||
false -> Records
|
||||
end,
|
||||
parse_lines(file:read_line(File), File, NewRecords).
|
||||
|
||||
|
||||
|
||||
% the Line has data, so we parse it, build a record, and return it
|
||||
parse_to_record(Line, File, Records) ->
|
||||
parse_to_record(Line, _, Records) ->
|
||||
[Type | Data] = string:tokens(Line, " \t"),
|
||||
{ok, PrevType, PrevName} = prev_rec_type(Records),
|
||||
{ok, Record} = build_record(Type, Data, {PrevType, PrevName}),
|
||||
|
||||
% parse_lines(file:read_line(File), File, [Record|Records]).
|
||||
|
||||
Record.
|
||||
|
||||
|
||||
@@ -88,10 +84,12 @@ prev_rec_type(List) when is_list(List) ->
|
||||
|
||||
|
||||
clean_line(Line) ->
|
||||
%% remove spaces, newlines, and tabs
|
||||
Line1= string:strip(Line),
|
||||
Line2= string:strip(Line1, both, $\n),
|
||||
Line3= string:strip(Line2, both, $\t),
|
||||
|
||||
%% remove comments and return
|
||||
FinalLine=Line3,
|
||||
CPos= string:chr(FinalLine, $#),
|
||||
case CPos of
|
||||
@@ -102,18 +100,18 @@ clean_line(Line) ->
|
||||
|
||||
|
||||
build_record("Rule", Data,_) ->
|
||||
ezic_record:rule(Data);
|
||||
ezic_rule:parse(Data);
|
||||
build_record("Zone", Data,_) ->
|
||||
ezic_record:zone(Data);
|
||||
ezic_zone:parse(Data);
|
||||
build_record("Link", Data,_) ->
|
||||
ezic_record:link(Data);
|
||||
build_record(GmtOff, Data, {"Zone", PrevName}) ->
|
||||
ezic_record:zone([PrevName,GmtOff|Data]);
|
||||
build_record("Leap", Data,_) ->
|
||||
ezic_record:leap(Data);
|
||||
build_record(GmtOff, Data, {"Zone", PrevName}) ->
|
||||
ezic_zone:parse([PrevName,GmtOff|Data]);
|
||||
|
||||
build_record(Type, Data, PT) ->
|
||||
{error, {badLine, Type, Data, PT}}.
|
||||
erlang:error(badLine, {Type, Data, PT}).
|
||||
|
||||
|
||||
|
||||
|
||||
162
src/ezic_parse.erl
Normal file
162
src/ezic_parse.erl
Normal file
@@ -0,0 +1,162 @@
|
||||
-module(ezic_parse).
|
||||
-include("include/ezic.hrl").
|
||||
|
||||
|
||||
-export([
|
||||
year/1
|
||||
, day_pattern/1
|
||||
, time/1
|
||||
, time_val/1
|
||||
, until/1
|
||||
, tz_abbr/1
|
||||
]).
|
||||
|
||||
|
||||
|
||||
% year(string) to year(int|atom)
|
||||
year("minimum") -> minimum;
|
||||
year("min") -> minimum;
|
||||
year("maximum") -> maximum;
|
||||
year("max") -> maximum;
|
||||
year("only") -> only;
|
||||
year(X) ->
|
||||
list_to_integer(X).
|
||||
|
||||
|
||||
|
||||
% X=last(Mon,Tue,Wed,...,Sun) -> {last, (Mon,Tue,Web,...,Sun)}
|
||||
day_pattern(X=[$l,$a,$s,$t|Day]) ->
|
||||
case ezic_date:day_to_num(Day) > 0 of
|
||||
true -> {last, Day};
|
||||
false -> erlang:error(badday,X)
|
||||
end;
|
||||
% X=(Mon,Tue,Wed,...,Sun)[<>]=int().
|
||||
day_pattern(X=[D,A,Y, Sign,$= | IntS]) ->
|
||||
Int=list_to_integer(IntS),
|
||||
Day=[D,A,Y],
|
||||
{ok, FSign}= sign(Sign),
|
||||
|
||||
case ezic_date:day_to_num(Day) > 0 of
|
||||
true -> #tzon{day=Day, filter={FSign, Int}};
|
||||
false -> erlang:error(badday, X)
|
||||
end;
|
||||
% X=int()
|
||||
day_pattern(X) ->
|
||||
list_to_integer(X).
|
||||
|
||||
|
||||
|
||||
|
||||
time_val(Str) ->
|
||||
#tztime{time=Time}= time(Str),
|
||||
Time.
|
||||
|
||||
time("-") ->
|
||||
#tztime{time={0,0,0}};
|
||||
time(TS) ->
|
||||
{Flag, RTime} = time_flag(TS),
|
||||
Time= time_abs(RTime),
|
||||
#tztime{time=Time, flag=Flag}.
|
||||
|
||||
|
||||
|
||||
until([]) ->
|
||||
current;
|
||||
until([YS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
{Y,1,1};
|
||||
until([YS,MS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
M= ezic_date:month_to_num(MS),
|
||||
{Y,M,1};
|
||||
until([YS,MS,DS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
M= ezic_date:month_to_num(MS),
|
||||
D= day_pattern(DS),
|
||||
{Y,M,D};
|
||||
until([YS,MS,DS,TS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
M= ezic_date:month_to_num(MS),
|
||||
D= day_pattern(DS),
|
||||
{{Y,M,D},ezic_parse:time(TS)}.
|
||||
|
||||
|
||||
|
||||
|
||||
tz_abbr(Format) ->
|
||||
tz_abbr(Format, string:str(Format, "%s")).
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% PRIVATE
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
|
||||
time_flag(TS) ->
|
||||
{F, RTime}= time_flag_rev(lists:reverse(TS)),
|
||||
{F, lists:reverse(RTime)}.
|
||||
|
||||
|
||||
% absolute time string (no relativity modifiers)
|
||||
% e.g. 08:01:03
|
||||
time_abs(Time) ->
|
||||
TTok= string:tokens(Time, ":"),
|
||||
time_tokens(TTok).
|
||||
|
||||
|
||||
time_tokens([HS]) ->
|
||||
H= list_to_integer(HS),
|
||||
{H,0,0};
|
||||
time_tokens([HS,MS]) ->
|
||||
H= list_to_integer(HS),
|
||||
M= list_to_integer(MS),
|
||||
{H,M,0};
|
||||
time_tokens([HS,MS,SS]) ->
|
||||
H= list_to_integer(HS),
|
||||
M= list_to_integer(MS),
|
||||
S= list_to_integer(SS),
|
||||
{H,M,S};
|
||||
time_tokens(X) ->
|
||||
erlang:error(badtime, X).
|
||||
|
||||
|
||||
|
||||
|
||||
sign($>) -> {ok, geq};
|
||||
sign($<) -> {ok, leq}.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% INTERNAL
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
time_flag_rev([F|RTime]) when F =:= $s; F =:= $w; F =:= $u ->
|
||||
{list_to_atom([F]), RTime};
|
||||
time_flag_rev([F|RTime]) when F =:= $g; F =:= $z ->
|
||||
{u, RTime};
|
||||
time_flag_rev(X=[F|_]) when F-48 < 0; F-48 > 9 ->
|
||||
erlang:error(badtime, lists:reverse(X));
|
||||
time_flag_rev(RTime) ->
|
||||
{undefined, RTime}.
|
||||
|
||||
|
||||
|
||||
tz_abbr(Format, 0) ->
|
||||
Format;
|
||||
tz_abbr(Format, Pos) ->
|
||||
First=string:substr(Format, 1, Pos-1),
|
||||
Last=string:substr(Format, Pos+2),
|
||||
string:concat(First, [$~, $s|Last]).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,46 +8,24 @@
|
||||
-endif.
|
||||
|
||||
|
||||
-export([rule/1,zone/1,link/1,leap/1, separate/1]).
|
||||
-export([
|
||||
link/1
|
||||
, leap/1
|
||||
, separate/1
|
||||
]).
|
||||
|
||||
|
||||
|
||||
rule([Name,FromS,ToS,Type,InS,OnS,AtS,SaveS,Letters]) ->
|
||||
From= parse_from(FromS),
|
||||
To= parse_to(ToS),
|
||||
% @todo handle year types
|
||||
In= ezic_date:month_to_num(InS),
|
||||
On= parse_on(OnS),
|
||||
At=parse_at(AtS),
|
||||
Save=parse_save(SaveS),
|
||||
|
||||
{ok, #rule{
|
||||
name=Name, from=From, to=To, type=Type,
|
||||
in=In, on=On, at=At, save=Save, letters=Letters
|
||||
}}.
|
||||
|
||||
|
||||
zone([Name,GmtOffS,Rule,FormatS | UntilTokens]) ->
|
||||
GmtOff=parse_time(GmtOffS),
|
||||
Until = parse_until(UntilTokens),
|
||||
Format= convert_format(FormatS),
|
||||
|
||||
{ok, #zone{
|
||||
name=Name, gmtoff=GmtOff, rule=Rule,
|
||||
format=Format, until=Until
|
||||
}}.
|
||||
|
||||
|
||||
link([From,To]) ->
|
||||
{ok, #link{from=From, to=To}}.
|
||||
|
||||
|
||||
% @todo figure out what must be done with leap seconds
|
||||
leap([YearS, MonthS, DayS, TimeS, Corr, RS]) ->
|
||||
Y=list_to_integer(YearS),
|
||||
M=ezic_date:month_to_num(MonthS),
|
||||
D=list_to_integer(DayS),
|
||||
Time=parse_abs_time(TimeS),
|
||||
Y= list_to_integer(YearS),
|
||||
M= ezic_date:month_to_num(MonthS),
|
||||
D= list_to_integer(DayS),
|
||||
Time= ezic_parse:time_val(TimeS),
|
||||
{ok, #leap{datetime={{Y,M,D}, Time}, corr=Corr, rs=RS}}.
|
||||
|
||||
|
||||
@@ -61,153 +39,3 @@ separate(Records) ->
|
||||
{ok, Zones, Rules, Leaps, Links}.
|
||||
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% PRIVATE
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
parse_from(X) when X=:="maximum";X=:="max" ->
|
||||
maximum;
|
||||
parse_from(X) when X=:="minimum";X=:="min" ->
|
||||
minimum;
|
||||
parse_from(Year) ->
|
||||
list_to_integer(Year).
|
||||
|
||||
|
||||
|
||||
parse_to("only") ->
|
||||
only;
|
||||
parse_to(Other) ->
|
||||
parse_from(Other).
|
||||
|
||||
|
||||
|
||||
parse_on(X=[$l,$a,$s,$t|Day]) ->
|
||||
case ezic_date:day_to_num(Day) > 0 of
|
||||
true -> {last, Day};
|
||||
false -> erlang:error(badday,X)
|
||||
end;
|
||||
parse_on(X=[D,A,Y, Sign,$= | IntS]) ->
|
||||
Int=list_to_integer(IntS),
|
||||
Day=[D,A,Y],
|
||||
{ok, FSign} =parse_on_sign(Sign),
|
||||
|
||||
case ezic_date:day_to_num(Day) > 0 of
|
||||
true -> #tzon{day=Day, filter={FSign, Int}};
|
||||
false -> erlang:error(badday, X)
|
||||
end;
|
||||
parse_on(X) ->
|
||||
list_to_integer(X).
|
||||
|
||||
|
||||
parse_until([]) ->
|
||||
current;
|
||||
parse_until([YS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
{Y,1,1};
|
||||
parse_until([YS,MS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
M= ezic_date:month_to_num(MS),
|
||||
{Y,M,1};
|
||||
parse_until([YS,MS,DS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
M= ezic_date:month_to_num(MS),
|
||||
D= parse_on(DS),
|
||||
{Y,M,D};
|
||||
parse_until([YS,MS,DS,TS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
M= ezic_date:month_to_num(MS),
|
||||
D= parse_on(DS),
|
||||
{{Y,M,D},parse_time(TS)}.
|
||||
|
||||
|
||||
|
||||
parse_at(X) ->
|
||||
parse_time(X).
|
||||
|
||||
|
||||
|
||||
parse_save("-") ->
|
||||
parse_time("-");
|
||||
parse_save(X) ->
|
||||
XRev=lists:reverse(X),
|
||||
parse_save_rev(XRev).
|
||||
|
||||
|
||||
|
||||
parse_time("-") ->
|
||||
#tztime{time={0,0,0}};
|
||||
parse_time(TS) ->
|
||||
parse_time_rev(lists:reverse(TS)).
|
||||
|
||||
|
||||
parse_abs_time(T) ->
|
||||
Tokez= string:tokens(T, ":"),
|
||||
parse_abs_time_2(Tokez).
|
||||
|
||||
|
||||
|
||||
convert_format(Format) ->
|
||||
convert_format(Format, string:str(Format, "%s")).
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% INTERNAL
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
parse_on_sign($>) -> {ok, geq};
|
||||
parse_on_sign($<) -> {ok, leq}.
|
||||
|
||||
|
||||
|
||||
|
||||
parse_time_rev([F|RTime]) when F =:= $s; F =:= $w; F =:= $u ->
|
||||
Time=lists:reverse(RTime),
|
||||
#tztime{time=parse_abs_time(Time), flag=list_to_atom([F])};
|
||||
parse_time_rev(X=[F|_]) when F-48 < 0; F-48 > 9 ->
|
||||
erlang:error(badtime, X);
|
||||
parse_time_rev(RTime) ->
|
||||
Time=lists:reverse(RTime),
|
||||
#tztime{time=parse_abs_time(Time)}.
|
||||
|
||||
|
||||
|
||||
|
||||
parse_abs_time_2([HS]) ->
|
||||
H= list_to_integer(HS),
|
||||
{H,0,0};
|
||||
parse_abs_time_2([HS,MS]) ->
|
||||
H= list_to_integer(HS),
|
||||
M= list_to_integer(MS),
|
||||
{H,M,0};
|
||||
parse_abs_time_2([HS,MS,SS]) ->
|
||||
H= list_to_integer(HS),
|
||||
M= list_to_integer(MS),
|
||||
S= list_to_integer(SS),
|
||||
{H,M,S};
|
||||
parse_abs_time_2(X) ->
|
||||
erlang:error(badtime, X).
|
||||
|
||||
|
||||
|
||||
parse_save_rev(X=[F|_]) when F-48 < 0; F-48 > 9 ->
|
||||
erlang:error(badsave, lists:reverse(X));
|
||||
parse_save_rev(X) ->
|
||||
parse_time(lists:reverse(X)).
|
||||
|
||||
|
||||
|
||||
convert_format(Format, 0) ->
|
||||
Format;
|
||||
convert_format(Format, Pos) ->
|
||||
First=string:substr(Format, 1, Pos-1),
|
||||
Last=string:substr(Format, Pos+2),
|
||||
string:concat(First, [$~, $s|Last]).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,22 +2,40 @@
|
||||
-include("include/ezic.hrl").
|
||||
|
||||
|
||||
-export([parse/1]).
|
||||
-export([next_event/4]).
|
||||
|
||||
|
||||
|
||||
parse([Name,FromS,ToS,_Type,InS,OnS,AtS,SaveS,Letters]) ->
|
||||
From= ezic_parse:year(FromS),
|
||||
To= ezic_parse:year(ToS),
|
||||
|
||||
% @todo handle year types
|
||||
|
||||
In= ezic_date:month_to_num(InS),
|
||||
On= ezic_parse:day_pattern(OnS),
|
||||
At= ezic_parse:time(AtS),
|
||||
|
||||
Save= ezic_parse:time_val(SaveS),
|
||||
|
||||
Rule= #rule{name=Name, from=From, to=To, type=not_done,
|
||||
in=In, on=On, at=At, save=Save, letters=Letters},
|
||||
{ok, Rule}.
|
||||
|
||||
|
||||
|
||||
% determines the next zone or dst change event
|
||||
% returns the latest possible times the previous period is in effect (`Until -1sec` in most cases)
|
||||
% Rules are sorted
|
||||
next_event(BaseFlat, ZoneEnd, Offset, []) ->
|
||||
next_event(_BaseFlat, ZoneEnd, Offset, []) ->
|
||||
{end_until, make_times(ZoneEnd, Offset)};
|
||||
|
||||
% BaseFlat has offset defined
|
||||
% @todo ensure offset defined on BaseFlat
|
||||
% Rules are sorted in ascending order (oldest first)
|
||||
next_event(BaseFlat=#flatzone{utc_from=#tztime{time={{Year,_,_},_}}},
|
||||
ZoneEnd, ZoneOffset, [Rule|RestRules]) ->
|
||||
next_event(_BaseFlat=#flatzone{utc_from=#tztime{time={{_Year,_,_},_}}},
|
||||
_ZoneEnd, _ZoneOffset, [_Rule|_RestRules]) ->
|
||||
|
||||
not_done.
|
||||
|
||||
@@ -38,3 +56,11 @@ make_times(Until, Offset, DSTOffset) ->
|
||||
|
||||
|
||||
|
||||
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
% INTERNAL
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
-export([
|
||||
current/1
|
||||
parse/1
|
||||
, current/1
|
||||
, current_as_of_utc/2
|
||||
, split_by_name/2
|
||||
, sort_ascending/2
|
||||
@@ -14,6 +15,26 @@
|
||||
|
||||
|
||||
|
||||
parse([Name,GmtOffS,Rule,FormatS | UntilTokens]) ->
|
||||
% ?debugVal(GmtOffS),
|
||||
GmtOff= ezic_parse:time_val(GmtOffS),
|
||||
Until = ezic_parse:until(UntilTokens),
|
||||
Format= ezic_parse:tz_abbr(FormatS),
|
||||
|
||||
{ok, #zone{
|
||||
name=Name, gmtoff=GmtOff, rule=Rule,
|
||||
format=Format, until=Until
|
||||
}}.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
current(TzName) ->
|
||||
current_as_of_utc(erlang:universaltime(), TzName).
|
||||
|
||||
@@ -21,10 +42,6 @@ current_as_of_utc(UTCDatetime, TzName) ->
|
||||
Zones= ezic_db:zones(TzName),
|
||||
get_zone_utc(UTCDatetime, Zones).
|
||||
|
||||
%%current_as_of_local(_Datetime, _TzName) ->
|
||||
%% not_done.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -58,7 +75,7 @@ split_by_name(#zone{name=N}, Zones) ->
|
||||
|
||||
|
||||
sort_descending(#zone{until=U1}, #zone{until=U2}) ->
|
||||
ezic_date:compare_datetimes(U2, U1).
|
||||
ezic_date:compare(U2, U1).
|
||||
|
||||
sort_ascending(Z1=#zone{}, Z2=#zone{}) ->
|
||||
not sort_descending(Z1, Z2).
|
||||
@@ -67,9 +84,16 @@ sort_ascending(Z1=#zone{}, Z2=#zone{}) ->
|
||||
|
||||
% @bug doesn't normalize times. Times are self-relative (to standard time), and Now may come from any timezone unless we're careful about that.
|
||||
%% older_zone(#zone{until=Until}, UTCDatetime) ->
|
||||
%% ezic_date:compare_datetimes(Until, UTCDatetime).
|
||||
%% ezic_date:compare(Until, UTCDatetime).
|
||||
|
||||
|
||||
|
||||
offset_sec(Zone) ->
|
||||
calendar:time_to_seconds((Zone#zone.gmtoff)#tztime.time).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user