From 1520c631ef50a480923e2a9c3d00e315867f2208 Mon Sep 17 00:00:00 2001 From: aj Date: Wed, 10 Nov 2010 13:01:03 -0800 Subject: [PATCH] roughed-in the "nested" rule processing (flatten). hurdle on rule years->max. --- src/ezic.erl | 2 +- src/ezic_date.erl | 23 ++++++++-- src/ezic_flatten.erl | 104 ++++++++++++++++++++++++++++++++++--------- src/ezic_rule.erl | 4 +- 4 files changed, 105 insertions(+), 28 deletions(-) diff --git a/src/ezic.erl b/src/ezic.erl index a47e901..a2ac2cb 100644 --- a/src/ezic.erl +++ b/src/ezic.erl @@ -49,7 +49,7 @@ load(Folder) -> dev_start() -> - ezic:load(filename:join("priv","tzdata")), +% ezic:load(filename:join("priv","tzdata")), ezic_flatten:flatten(). diff --git a/src/ezic_date.erl b/src/ezic_date.erl index a66fb6d..3ee9720 100644 --- a/src/ezic_date.erl +++ b/src/ezic_date.erl @@ -10,6 +10,7 @@ % rule-specific , for_rule/2 , for_rule_utc/4 + , for_rule_all/4 % converters @@ -58,10 +59,14 @@ for_rule(#rule{in=M, on=#tzon{day=Day, filter=Filter}, at=At}, Y) -> {first_day_limited(Day, Filter, Y,M), At}. +for_rule_all(Rule, Offset, DSTOffset, Year) -> + DT= for_rule(Rule, Year), + all_times(DT, Offset, DSTOffset). + + % returns UTC datetime for rule, offset, and year for_rule_utc(Rule, Offset, DSTOffset, Year) -> - DT= for_rule(Rule, Year), - {_,_,UTCDatetime} = all_times(DT, Offset, DSTOffset), + {_,_,UTCDatetime} = for_rule_all(Rule, Offset, DSTOffset, Year), UTCDatetime. @@ -162,6 +167,14 @@ all_times({Date, #tztime{time=WallTime, flag=Flag}}, Offset, DSTOffset) +compare(_, current) -> + true; +compare(current, current) -> + true; +compare(current, _) -> + false; + + % returns true if DT1 =< DT2. False otherwise. can be used with lists:sort/2 % both times are assumed to be in the same zone/DST context compare(DT1={{Y1,M1,D1},{HH1,MM1,SS1}}, DT2={{Y2,M2,D2},{HH2,MM2,SS2}}) @@ -265,12 +278,14 @@ add_days_in_month(Days, Date={Y,M,D}) -> -m1s(D= {{Y,M,D},{HH,MM,SS}}) +m1s(Date= {{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). + calendar:gregorian_seconds_to_datetime(calendar:datetime_to_gregorian_seconds(Date) - 1); +m1s({WD, SD, UD}) -> + {m1s(WD), m1s(SD), m1s(UD)}. % @todo type checking m1s(W,S,U) when is_tuple(W), is_tuple(S), is_tuple(U) -> diff --git a/src/ezic_flatten.erl b/src/ezic_flatten.erl index e8fe94e..5628b35 100644 --- a/src/ezic_flatten.erl +++ b/src/ezic_flatten.erl @@ -4,7 +4,7 @@ -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,D), F#flatzone{wall_to=W, std_to=S, utc_to=U, dstoffset=D}). -define(MINFLAT, ?FLAT(minimum,minimum,minimum)). @@ -45,6 +45,7 @@ flatten_zones([Z1|_]= AllZones, Flats) -> % takes zones one-by-one, gathering relevant rules and creating flat periods of the same gmt offset (#flatzone) +% This is a recursive solution, eliminating Zones until they've been exhausted flatten_zone_set(Zones) -> flatten_zone_set(?MINFLAT, Zones, []). @@ -72,8 +73,52 @@ flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset} %% we gather all rules that _may_ apply (same year) Rules= ezic_db:rules(RuleName), - %% and add normalized (inaccurate) dates for sorting purposes + + %% tack the date onto the zone, so we can see if it ends before a rule does + ZoneWithDate= {ezic_zone:project_end_utc(Zone, DSTOffset), Zone}, + + + ?debugVal(FromTime), + ?debugVal(ZoneWithDate), + ?debugVal(Rules), + + RuleFlats= flatten_rule_set(FromTime, ZoneWithDate, Rules, []), + ?debugVal(RuleFlats), + + %% %% and add normalized (possibly inaccurate) dates for sorting purposes + %% %% note this may be empty + %% RulesWithDates= lists:foldl( + %% fun(R, Acc)-> + %% case ezic_rule:project_next(R, Offset, DSTOffset, UTCFrom) of + %% none -> Acc; + %% D -> [{D,R} | Acc] + %% end + %% end + %% ,[], Rules), + + %% [NextEventWithDate | RestWithDates]= lists:sort(RulesAndZoneWithDates), + + %% {_,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). + + not_done. + + + + + +flatten_rule_set(FlatStart=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset, offset=Offset} + , ZoneWithDate, Rules, Flats) -> + + %% and add normalized (possibly inaccurate) dates for sorting purposes %% note this may be empty + %% also note this MUST (I think) be done in the loop, since UTCFrom and DSTOffset + %% can potentially change which rule comes next (however unlikely) RulesWithDates= lists:foldl( fun(R, Acc)-> case ezic_rule:project_next(R, Offset, DSTOffset, UTCFrom) of @@ -82,33 +127,48 @@ flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset} end end ,[], Rules), - %% 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], + ?debugVal(RulesWithDates), - flatten_zone_set(NextFlat, RestZones, NewFlats). + [{EndingRuleDate, EndingRule} | _]= lists:sort(RulesWithDates), + {ZoneDate, Zone}= ZoneWithDate, + + ?debugVal(EndingRuleDate), + ?debugVal(ZoneDate), + + case ezic_date:compare(EndingRuleDate, ZoneDate) of + true -> + %% same zone, new rule + {EndFlat, NextFlat}= finish_and_start_flat(FlatStart, EndingRule, EndingRuleDate, Offset, DSTOffset), + NewFlats= [EndFlat | Flats], + flatten_rule_set(NextFlat, ZoneWithDate, Rules, NewFlats); + false -> + %% new zone is handled in calling function: flatten_zone_set + Flats + end. +finish_and_start_flat(FlatStub=#flatzone{utc_from=UTCFrom}, EndingRule=#rule{}, EndingRuleDate={{ERDY,_,_},_}, Offset, DSTOffset) -> + NewFlatStartDates={WD, SD, UD}= ezic_date:for_rule_all(EndingRule, Offset, DSTOffset, ERDY), + FlatEndDates={WDm, SDm, UDm}= ezic_date:m1s(NewFlatStartDates), -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}; + EndFlat= ?ENDFLAT(FlatStub, WDm, SDm, UDm, DSTOffset), + NewFlat1= ?FLAT(WD, SD, UD), + NewFlat2= NewFlat1#flatzone{offset=Offset}, + + FinalNewFlat= NewFlat2, + {EndFlat, FinalNewFlat}. + + + +%% 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. diff --git a/src/ezic_rule.erl b/src/ezic_rule.erl index 7ce8a29..f0db936 100644 --- a/src/ezic_rule.erl +++ b/src/ezic_rule.erl @@ -26,6 +26,8 @@ parse([Name,FromS,ToS,_Type,InS,OnS,AtS,SaveS,Letters]) -> % returns the projected date of the next "rule event" in UTC time, % after the given UTCDatetime, with the given the zone Offset and current DST offset. +project_next(Rule=#rule{from=RFrom, to=RTo}, Offset, DSTOffset, UTCAfter=minimum) -> + ezic_date:for_rule_utc(Rule, Offset, DSTOffset, RFrom); project_next(Rule=#rule{from=RFrom, to=RTo}, Offset, DSTOff, UTCAfter={{AY,_,_},_}) -> AllYears= years(Rule), GoodYears= years_after(AY, AllYears), @@ -43,7 +45,7 @@ years(Rule=#rule{from=From, to=only}) -> years(Rule=#rule{from=From, to=To}) -> try lists:seq(From, To) catch error:function_clause -> - erlang:error(bad_year_range) + erlang:error(bad_year_range, [From, To]) end. % returns the years from the list that are greater than OR equal to Y