flattening algorithm needs to be reworked.
also, datetime methods aren't consistent in their naming and return values.
This commit is contained in:
@@ -49,7 +49,8 @@ load(Folder) ->
|
|||||||
|
|
||||||
|
|
||||||
dev_start() ->
|
dev_start() ->
|
||||||
ezic:load(filename:join("priv","tzdata")).
|
ezic:load(filename:join("priv","tzdata")),
|
||||||
|
ezic_flatten:flatten().
|
||||||
|
|
||||||
|
|
||||||
test() ->
|
test() ->
|
||||||
|
|||||||
@@ -5,8 +5,10 @@
|
|||||||
|
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
|
normalize/1
|
||||||
|
|
||||||
% rule-specific
|
% rule-specific
|
||||||
for_rule/2
|
, for_rule/2
|
||||||
, for_rule_utc/4
|
, for_rule_utc/4
|
||||||
|
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@
|
|||||||
, add_seconds/2
|
, add_seconds/2
|
||||||
, add_offset/2
|
, add_offset/2
|
||||||
, all_times/3
|
, all_times/3
|
||||||
|
, m1s/1
|
||||||
|
|
||||||
% comparisons
|
% comparisons
|
||||||
, compare/2
|
, compare/2
|
||||||
@@ -32,6 +35,20 @@
|
|||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
|
||||||
|
% converts any sane date part into a complete datetime
|
||||||
|
% USED EXCLUSIVELY FOR COMPARISONS. The resulting "datetime" values may not
|
||||||
|
% represent true dates or times.
|
||||||
|
normalize(A) when is_atom(A) ->
|
||||||
|
A;
|
||||||
|
normalize(Y) when is_integer(Y) ->
|
||||||
|
{{Y,1,1}, #tztime{}};
|
||||||
|
normalize(Date={Y,M,D})
|
||||||
|
when is_integer(Y), is_integer(M), is_integer(D) ->
|
||||||
|
{Date, #tztime{}};
|
||||||
|
normalize(R={{Y,M,D}, #tztime{}}) ->
|
||||||
|
R.
|
||||||
|
|
||||||
|
|
||||||
% returns RELATIVE {Y,M,D} for a rule and Year
|
% returns RELATIVE {Y,M,D} for a rule and Year
|
||||||
for_rule(#rule{in=M, on=D, at=At}, Y) when is_integer(D) ->
|
for_rule(#rule{in=M, on=D, at=At}, Y) when is_integer(D) ->
|
||||||
{{Y,M,D}, At};
|
{{Y,M,D}, At};
|
||||||
@@ -97,6 +114,11 @@ add_offset(Datetime, Offset) ->
|
|||||||
% returns {WallTime, StdTime, UtcTime}
|
% returns {WallTime, StdTime, UtcTime}
|
||||||
% where each is a datetime tuple: {{Y,M,D}{HH,MM,SS}}
|
% where each is a datetime tuple: {{Y,M,D}{HH,MM,SS}}
|
||||||
|
|
||||||
|
|
||||||
|
% @todo ensure X is in list: max, maximum, min, minimum, current, ...?
|
||||||
|
all_times(X,_,_) when is_atom(X) ->
|
||||||
|
{X,X,X};
|
||||||
|
|
||||||
% universal time given
|
% universal time given
|
||||||
all_times({Date, #tztime{time=UTCTime, flag=Flag}}, Offset, DSTOffset)
|
all_times({Date, #tztime{time=UTCTime, flag=Flag}}, Offset, DSTOffset)
|
||||||
when Flag=:=u; Flag=:=g; Flag=:=z ->
|
when Flag=:=u; Flag=:=g; Flag=:=z ->
|
||||||
@@ -153,6 +175,8 @@ when is_integer(Y1), is_integer(Y2)
|
|||||||
|
|
||||||
DT1 =< DT2.
|
DT1 =< DT2.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
% returns true if DT1 =:= DT2. False otherwise
|
% returns true if DT1 =:= DT2. False otherwise
|
||||||
% both times are assumed to be in the same zone/DST context
|
% both times are assumed to be in the same zone/DST context
|
||||||
equal(DT1={{Y1,M1,D1},{HH1,MM1,SS1}}, DT2={{Y2,M2,D2},{HH2,MM2,SS2}})
|
equal(DT1={{Y1,M1,D1},{HH1,MM1,SS1}}, DT2={{Y2,M2,D2},{HH2,MM2,SS2}})
|
||||||
@@ -241,6 +265,19 @@ add_days_in_month(Days, Date={Y,M,D}) ->
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
m1s(D= {{Y,M,D},{HH,MM,SS}})
|
||||||
|
when is_integer(Y), is_integer(M), is_integer(D)
|
||||||
|
, is_integer(HH), is_integer(MM), is_integer(SS) ->
|
||||||
|
|
||||||
|
calendar:gregorian_seconds_to_datetime(calendar:datetime_to_gregorian_seconds(D) - 1).
|
||||||
|
|
||||||
|
|
||||||
|
% @todo type checking
|
||||||
|
m1s(W,S,U) when is_tuple(W), is_tuple(S), is_tuple(U) ->
|
||||||
|
{m1s(W), m1s(S), m1s(U)}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
% INTERNAL
|
% INTERNAL
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
-module(ezic_flatten).
|
-module(ezic_flatten).
|
||||||
-include("include/ezic.hrl").
|
-include("include/ezic.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
|
||||||
-define(FLAT(W,S,U), #flatzone{wall_from=W, std_from=S, utc_from=U}).
|
-define(FLAT(W,S,U), #flatzone{wall_from=W, std_from=S, utc_from=U}).
|
||||||
-define(ENDFLAT(F,W,S,U), F#flatzone{wall_to=W, std_to=S, utc_to=U}).
|
-define(ENDFLAT(F,W,S,U), F#flatzone{wall_to=W, std_to=S, utc_to=U}).
|
||||||
@@ -19,7 +21,6 @@ flatten() ->
|
|||||||
not_done.
|
not_done.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
% PRIVATE
|
% PRIVATE
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
@@ -29,43 +30,11 @@ flatten() ->
|
|||||||
flatten_zones(Zones) ->
|
flatten_zones(Zones) ->
|
||||||
flatten_zones(Zones, []).
|
flatten_zones(Zones, []).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
make_next_flat(#flatzone{wall_to=W, std_to=S, utc_to=U, offset=O}) ->
|
|
||||||
make_next_flat({W,S,U}, O);
|
|
||||||
make_next_flat(EndTimes) ->
|
|
||||||
make_next_flat(EndTimes, undefined).
|
|
||||||
|
|
||||||
|
|
||||||
make_next_flat({W,S,U}, O) ->
|
|
||||||
FW= ezic_date:add_seconds(W,1),
|
|
||||||
FS= ezic_date:add_seconds(S,1),
|
|
||||||
FU= ezic_date:add_seconds(U,1),
|
|
||||||
Flat= ?FLAT(FW, FS, FU),
|
|
||||||
Flat#flatzone{offset=O}.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end_flat(Flat=#flatzone{}, {W,S,U}) ->
|
|
||||||
?ENDFLAT(Flat,W,S,U).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
% INTERNAL
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
flatten_zones([], Flats) ->
|
flatten_zones([], Flats) ->
|
||||||
Flats;
|
Flats;
|
||||||
flatten_zones([Z1|_]= AllZones, Flats) ->
|
flatten_zones([Z1|_]= AllZones, Flats) ->
|
||||||
{CurrentZones, RestZones}= ezic_zone:split_by_name(Z1, AllZones),
|
{CurrentZones, RestZones}= ezic_zone:split_by_name(Z1, AllZones),
|
||||||
|
NewFlats= lists:merge(Flats, flatten_zone_set(CurrentZones)),
|
||||||
SortedZones= lists:sort(fun ezic_zone:sort_ascending/2, CurrentZones),
|
|
||||||
NewFlats= lists:merge(Flats, flatten_zone_set(SortedZones)),
|
|
||||||
|
|
||||||
flatten_zones(RestZones, NewFlats).
|
flatten_zones(RestZones, NewFlats).
|
||||||
|
|
||||||
|
|
||||||
@@ -88,7 +57,12 @@ flatten_zone_set(Zones) ->
|
|||||||
% assumes a new zone every time it is called
|
% assumes a new zone every time it is called
|
||||||
flatten_zone_set(_, [], Flats) ->
|
flatten_zone_set(_, [], Flats) ->
|
||||||
Flats;
|
Flats;
|
||||||
flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}, [Z1=#zone{rule=RuleName, until=UntilTime, gmtoff=Offset} | _RestZones], _Flats) ->
|
flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}
|
||||||
|
, Zones %[Z1=#zone{rule=RuleName, until=UntilTime, gmtoff=Offset} | _RestZones],
|
||||||
|
, Flats) ->
|
||||||
|
|
||||||
|
{Zone, RestZones}= ezic_zone:next(Zones, UTCFrom, DSTOffset),
|
||||||
|
#zone{rule=RuleName, until=UntilTime, gmtoff=Offset}=Zone,
|
||||||
|
|
||||||
%% we have a flatzone with start times, we populate the base offset
|
%% we have a flatzone with start times, we populate the base offset
|
||||||
FromTime= FromTimeStub#flatzone{offset=Offset},
|
FromTime= FromTimeStub#flatzone{offset=Offset},
|
||||||
@@ -97,7 +71,10 @@ flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset},
|
|||||||
|
|
||||||
%% we gather all rules that _may_ apply (same year)
|
%% we gather all rules that _may_ apply (same year)
|
||||||
Rules= ezic_db:rules(RuleName),
|
Rules= ezic_db:rules(RuleName),
|
||||||
RuleDates= lists:foldl(
|
|
||||||
|
%% and add normalized (inaccurate) dates for sorting purposes
|
||||||
|
%% note this may be empty
|
||||||
|
RulesWithDates= lists:foldl(
|
||||||
fun(R, Acc)->
|
fun(R, Acc)->
|
||||||
case ezic_rule:project_next(R, Offset, DSTOffset, UTCFrom) of
|
case ezic_rule:project_next(R, Offset, DSTOffset, UTCFrom) of
|
||||||
none -> Acc;
|
none -> Acc;
|
||||||
@@ -105,11 +82,33 @@ flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset},
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
,[], Rules),
|
,[], Rules),
|
||||||
AllDates= [{ezic_zone:project_end(Z1, DSTOffset), Z1} | RuleDates],
|
%% tack on the zone w/ date, in case it ends first
|
||||||
|
ZoneWithDate= {ezic_zone:project_end_utc(Zone, DSTOffset), Zone},
|
||||||
|
|
||||||
|
[NextEventWithDate | RestWithDates]= lists:sort(RulesAndZoneWithDates),
|
||||||
|
?debugVal(NextEventWithDate),
|
||||||
|
?debugVal(RestWithDates),
|
||||||
|
|
||||||
|
{_,NextEvent}= NextEventWithDate,
|
||||||
|
{EndFlat, NextFlat}= finish_and_start_flat(FromTime, NextEvent, Offset, DSTOffset),
|
||||||
|
NewFlats= [EndFlat | Flats],
|
||||||
|
|
||||||
|
RestZones= [Z || {_,Z} <- RestWithDates],
|
||||||
|
|
||||||
|
flatten_zone_set(NextFlat, RestZones, NewFlats).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
finish_and_start_flat(Flat=#flatzone{}, Zone=#zone{}, _Offset, DSTOffset) ->
|
||||||
|
NextStart= {WT, ST, UT}= ezic_zone:project_end(Zone, DSTOffset),
|
||||||
|
{MWT, MST, MUT}= ezic_date:m1s(NextStart),
|
||||||
|
EndFlat= ?ENDFLAT(Flat, MWT, MST, MUT),
|
||||||
|
NextFlat= ?FLAT(WT, ST, UT),
|
||||||
|
{EndFlat, NextFlat};
|
||||||
|
|
||||||
|
finish_and_start_flat(Flat=#flatzone{utc_from=UTCFrom}, Rule=#rule{}, Offset, DSTOffset) ->
|
||||||
not_done.
|
not_done.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
, current/1
|
, current/1
|
||||||
, current_as_of_utc/2
|
, current_as_of_utc/2
|
||||||
, split_by_name/2
|
, split_by_name/2
|
||||||
, sort_ascending/2
|
|
||||||
, sort_descending/2
|
|
||||||
, offset_sec/1
|
, offset_sec/1
|
||||||
|
|
||||||
, project_end/2
|
, project_end/2
|
||||||
|
, project_end_utc/2
|
||||||
|
, next/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
|
||||||
@@ -50,16 +50,6 @@ current_as_of_utc(UTCDatetime, TzName) ->
|
|||||||
get_zone_utc(_, []) ->
|
get_zone_utc(_, []) ->
|
||||||
erlang:error(no_current);
|
erlang:error(no_current);
|
||||||
get_zone_utc(_UTCDatetime, Zones) ->
|
get_zone_utc(_UTCDatetime, Zones) ->
|
||||||
_SortedZones= lists:sort(fun sort_descending/2, Zones),
|
|
||||||
%% [CZone|_]= lists:dropwhile(fun(SZ)-> older_zone_utc(SZ, UTCDatetime) end, SortedZones),
|
|
||||||
%% CZone.
|
|
||||||
|
|
||||||
|
|
||||||
%% begin with GMT offset.
|
|
||||||
%% foreach rule (oldest first)
|
|
||||||
%% see whether until=(standard|wall|utc) time
|
|
||||||
%%
|
|
||||||
|
|
||||||
not_done.
|
not_done.
|
||||||
|
|
||||||
|
|
||||||
@@ -76,14 +66,6 @@ split_by_name(#zone{name=N}, Zones) ->
|
|||||||
, Zones).
|
, Zones).
|
||||||
|
|
||||||
|
|
||||||
sort_descending(#zone{until=U1}, #zone{until=U2}) ->
|
|
||||||
ezic_date:compare(U2, U1).
|
|
||||||
|
|
||||||
sort_ascending(Z1=#zone{}, Z2=#zone{}) ->
|
|
||||||
not sort_descending(Z1, Z2).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
% @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.
|
% @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) ->
|
%% older_zone(#zone{until=Until}, UTCDatetime) ->
|
||||||
%% ezic_date:compare(Until, UTCDatetime).
|
%% ezic_date:compare(Until, UTCDatetime).
|
||||||
@@ -98,9 +80,28 @@ offset_sec(Zone) ->
|
|||||||
|
|
||||||
% returns the UTC datetime projected for zone end (given current DST Offset)
|
% returns the UTC datetime projected for zone end (given current DST Offset)
|
||||||
project_end(#zone{until=Until, gmtoff=Offset}, DSTOffset) ->
|
project_end(#zone{until=Until, gmtoff=Offset}, DSTOffset) ->
|
||||||
{_,_,UTCDatetime}= ezic_date:all_times(Until, Offset, DSTOffset),
|
ezic_date:all_times(Until, Offset, DSTOffset).
|
||||||
|
|
||||||
|
% returns just the UTC datetime projected for zone end
|
||||||
|
project_end_utc(Zone=#zone{}, DSTOffset) ->
|
||||||
|
{_,_,UTCDatetime}= project_end(Zone, DSTOffset),
|
||||||
UTCDatetime.
|
UTCDatetime.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% returns {Zone, Rest} where Zone is the next zone after UTCFrom,
|
||||||
|
% subject to the DST offset.
|
||||||
|
% Note that dst differences *can* change which zone comes next,
|
||||||
|
% though it's very unlikely (and does not exist in the current tz database files).
|
||||||
|
% this method covers that event, anyhow.
|
||||||
|
next(ZoneList, UTCFrom, DSTOff) ->
|
||||||
|
DatedList= lists:map(
|
||||||
|
fun(Z=#zone{until=Until, gmtoff=Offset})->
|
||||||
|
NU= ezic_date:normalize(Until),
|
||||||
|
{_,_,UTCDt}= ezic_date:all_times(NU, Offset, DSTOff),
|
||||||
|
{UTCDt, Z}
|
||||||
|
end
|
||||||
|
, ZoneList),
|
||||||
|
[{_,Zone} | DRest]= lists:sort(DatedList),
|
||||||
|
{Zone, [R || {_,R}<- DRest]}.
|
||||||
|
|||||||
@@ -15,20 +15,20 @@ split_by_name_test_() ->
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
project_end_test_() ->
|
project_end_utc_test_() ->
|
||||||
[
|
[
|
||||||
?_assertEqual({{2010,11,10},{16,20,0}},
|
?_assertEqual({{2010,11,10},{16,20,0}},
|
||||||
ezic_zone:project_end(
|
ezic_zone:project_end_utc(
|
||||||
#zone{until={{2010,11,10},#tztime{time={10,20,0}, flag=w}}, gmtoff={-7,0,0}}
|
#zone{until={{2010,11,10},#tztime{time={10,20,0}, flag=w}}, gmtoff={-7,0,0}}
|
||||||
, {1,0,0}))
|
, {1,0,0}))
|
||||||
|
|
||||||
, ?_assertEqual({{2010,11,10},{16,20,0}},
|
, ?_assertEqual({{2010,11,10},{16,20,0}},
|
||||||
ezic_zone:project_end(
|
ezic_zone:project_end_utc(
|
||||||
#zone{until={{2010,11,10},#tztime{time={9,20,0}, flag=s}}, gmtoff={-7,0,0}}
|
#zone{until={{2010,11,10},#tztime{time={9,20,0}, flag=s}}, gmtoff={-7,0,0}}
|
||||||
, {1,0,0}))
|
, {1,0,0}))
|
||||||
|
|
||||||
, ?_assertEqual({{2010,11,10},{16,20,0}},
|
, ?_assertEqual({{2010,11,10},{16,20,0}},
|
||||||
ezic_zone:project_end(
|
ezic_zone:project_end_utc(
|
||||||
#zone{until={{2010,11,10},#tztime{time={16,20,0}, flag=u}}, gmtoff={-7,0,0}}
|
#zone{until={{2010,11,10},#tztime{time={16,20,0}, flag=u}}, gmtoff={-7,0,0}}
|
||||||
, {1,0,0}))
|
, {1,0,0}))
|
||||||
].
|
].
|
||||||
|
|||||||
Reference in New Issue
Block a user