lots of gutting. purposes, input, and output were unclear in many places. still broken.

This commit is contained in:
aj
2010-11-08 01:29:15 -08:00
parent 9ad8f3c826
commit d00937c3e9
13 changed files with 828 additions and 552 deletions

View File

@@ -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).

View File

@@ -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.

View File

@@ -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.

View File

@@ -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
View 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]).

View File

@@ -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]).

View File

@@ -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
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

View File

@@ -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).