there is a subtle bug in wall-time calculations. Asia/Irkutsk times are incorrect.

This commit is contained in:
aj
2010-11-12 01:22:50 -08:00
parent 7985f9e74f
commit 3e77287db6
4 changed files with 63 additions and 42 deletions

View File

@@ -37,9 +37,10 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% converts any sane date part into a complete datetime %% converts any sane date part into a complete datetime
% USED EXCLUSIVELY FOR COMPARISONS. The resulting "datetime" values may not %% USED EXCLUSIVELY FOR COMPARISONS. The resulting "datetime" values may not
% represent true dates or times. %% represent true dates or times.
%% @todo ensure atom is valid
normalize(A) when is_atom(A) -> normalize(A) when is_atom(A) ->
A; A;
normalize(Y) when is_integer(Y) -> normalize(Y) when is_integer(Y) ->
@@ -179,6 +180,14 @@ compare(current, _) ->
false; false;
compare(_, X) when X=:=max; X=:=maximum ->
true;
compare(X, X) when X=:=max; X=:=maximum ->
true;
compare(X, _) when X=:=max; X=:=maximum ->
false;
% returns true if DT1 =< DT2. False otherwise. can be used with lists:sort/2 % 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 % 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}}) compare(DT1={{Y1,M1,D1},{HH1,MM1,SS1}}, DT2={{Y2,M2,D2},{HH2,MM2,SS2}})

View File

@@ -60,18 +60,19 @@ 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; erlang:error(debug_quit),
Flats;
flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset} flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}
, Zones %[Z1=#zone{rule=RuleName, until=UntilTime, gmtoff=Offset} | _RestZones], , Zones %[Z1=#zone{rule=RuleName, until=UntilTime, gmtoff=Offset} | _RestZones],
, _Flats) -> , Flats) ->
[Zone | _RestZones] = ezic_zone:next(Zones, UTCFrom, DSTOffset), [Zone | RestZones] = ezic_zone:next(Zones, UTCFrom, DSTOffset),
#zone{rule=RuleName, until=_UntilTime, gmtoff=Offset}=Zone, #zone{rule=RuleName, until=_UntilTime, gmtoff=Offset}=Zone,
%% we have a flatzone with start times; must populate the base gmt offset %% we have a flatzone with start times; must populate the base gmt offset
FromTime= FromTimeStub#flatzone{offset=Offset}, FromTime= FromTimeStub#flatzone{offset=Offset, tzname=Zone#zone.name},
%% if this is the first run, DST offset is {0,0,0} %% if this is the first run, DST offset is {0,0,0}
%% if this is a recursion, DST offset is the previous zone's DST offset %% if this is a recursion, DST offset is the previous zone's last DST offset
%% we gather all rules that _may_ apply (same year) %% we gather all rules that _may_ apply (same year)
@@ -79,38 +80,27 @@ flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}
%% tack the date onto the zone, so we can see if the zone ends before a rule does %% tack the date onto the zone, so we can see if the zone ends before a rule does
ZoneWithDate= {ezic_zone:project_end_utc(Zone, DSTOffset), Zone}, TempZoneEndUTC= ezic_zone:project_end_utc(Zone, DSTOffset),
ZoneWithDate= {TempZoneEndUTC, Zone},
?debugVal(FromTime), ?debugVal(FromTime),
?debugVal(ZoneWithDate), ?debugVal(ZoneWithDate),
?debugVal(Rules), ?debugVal(Rules),
RuleFlats= flatten_rule_set(FromTime, ZoneWithDate, Rules, []), {RuleFlats, LastFlat, EndingDST}= flatten_rule_set(FromTime, ZoneWithDate, Rules, []),
?debugVal(RuleFlats), ?debugVal(RuleFlats),
?debugVal(LastFlat),
%% %% and add normalized (possibly inaccurate) dates for sorting purposes ?debugVal(EndingDST),
%% %% 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], %% rules have been exhausted, and zone is ending.
{FinalFlat, NextFlat}= finish_and_start_flat(LastFlat, Zone, EndingDST),
?debugVal(FinalFlat),
FinalFlats= lists:merge([[FinalFlat], RuleFlats, Flats]),
?debugVal(FinalFlats),
%% flatten_zone_set(NextFlat, RestZones, NewFlats).
not_done. flatten_zone_set(NextFlat, RestZones, FinalFlats).
@@ -135,7 +125,12 @@ flatten_rule_set(FlatStart=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset, offs
?debugVal(RulesWithDates), ?debugVal(RulesWithDates),
[{EndingRuleDate, EndingRule} | _]= lists:sort(RulesWithDates), {EndingRuleDate, EndingRule}=
case length(RulesWithDates) > 0 of
false -> {maximum, none};
true -> hd(lists:sort(RulesWithDates))
end,
{ZoneDate, _Zone}= ZoneWithDate, {ZoneDate, _Zone}= ZoneWithDate,
?debugVal(EndingRuleDate), ?debugVal(EndingRuleDate),
@@ -149,32 +144,40 @@ flatten_rule_set(FlatStart=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset, offs
flatten_rule_set(NextFlat, ZoneWithDate, Rules, NewFlats); flatten_rule_set(NextFlat, ZoneWithDate, Rules, NewFlats);
false -> false ->
%% new zone is handled in the caller: flatten_zone_set %% new zone is handled in the caller: flatten_zone_set
Flats {Flats, FlatStart, DSTOffset}
end. end.
finish_and_start_flat(FlatStub=#flatzone{utc_from=_UTCFrom}, EndingRule=#rule{}, _EndingRuleDate={{ERDY,_,_},_}, Offset, DSTOffset) -> finish_and_start_flat(FlatStub=#flatzone{utc_from=_UTCFrom}, NewRule=#rule{save=NewDSTSave}, _EndingRuleDate={{ERDY,_,_},_}, Offset, DSTOffset) ->
%% @todo for_rule_all was already called in a loop earlier. use those values instead. %% @todo for_rule_all was already called in a loop earlier. use those values instead.
NewFlatStartDates={WD, SD, UD}= ezic_date:for_rule_all(EndingRule, Offset, DSTOffset, ERDY), NewFlatStartDates={WD, SD, UD}= ezic_date:for_rule_all(NewRule, Offset, DSTOffset, ERDY),
{WDm, SDm, UDm}= ezic_date:m1s(NewFlatStartDates), {WDm, SDm, UDm}= ezic_date:m1s(NewFlatStartDates),
EndFlat= ?ENDFLAT(FlatStub, WDm, SDm, UDm, DSTOffset), EndFlat= ?ENDFLAT(FlatStub, WDm, SDm, UDm, DSTOffset),
NewFlat1= ?FLAT(WD, SD, UD), NewFlat1= ?FLAT(WD, SD, UD),
NewFlat2= NewFlat1#flatzone{offset=Offset}, NewFlat2= NewFlat1#flatzone{offset=Offset, dstoffset=NewDSTSave, tzname=EndFlat#flatzone.tzname},
FinalNewFlat= NewFlat2, FinalNewFlat= NewFlat2,
?debugVal(EndFlat),
?debugVal(FinalNewFlat),
{EndFlat, FinalNewFlat}. {EndFlat, FinalNewFlat}.
finish_and_start_flat(FlatStub=#flatzone{}, Zone=#zone{}, EndingDST) ->
EndDatesP1={WD,SD,UD}= ezic_zone:project_end(Zone, EndingDST),
{WDm, SDm, UDm}= ezic_date:m1s(EndDatesP1),
EndFlat= ?ENDFLAT(FlatStub, WDm, SDm, UDm, EndingDST),
NextFlat1= ?FLAT(WD,SD,UD),
NextFlat2= NextFlat1#flatzone{dstoffset=EndingDST},
RetNextFlat= NextFlat2,
%% finish_and_start_flat(Flat=#flatzone{}, Zone=#zone{}, _Offset, DSTOffset) -> ?debugVal(EndFlat),
%% NextStart= {WT, ST, UT}= ezic_zone:project_end(Zone, DSTOffset), ?debugVal(RetNextFlat),
%% {MWT, MST, MUT}= ezic_date:m1s(NextStart),
%% EndFlat= ?ENDFLAT(Flat, MWT, MST, MUT),
%% NextFlat= ?FLAT(WT, ST, UT),
%% {EndFlat, NextFlat};
{EndFlat, RetNextFlat}.

View File

@@ -42,6 +42,8 @@ project_next(Rule=#rule{}, Offset, DSTOff, UTCAfter={{AY,_,_},_}) ->
% returns the sorted list of years a rule existed for. % returns the sorted list of years a rule existed for.
years(#rule{from=From, to=only}) -> years(#rule{from=From, to=only}) ->
[From]; [From];
years(#rule{from=From, to=X}) when X=:=max; X=:=maximum->
[From,9999]; % let's hope this isn't used past year 9999
years(#rule{from=From, to=To}) -> years(#rule{from=From, to=To}) ->
try lists:seq(From, To) try lists:seq(From, To)
catch error:function_clause -> catch error:function_clause ->

View File

@@ -58,3 +58,10 @@ add_seconds_test_() ->
]. ].
compare_test_() ->
[
?_assert(ezic_date:compare({2011,12,12}, current))
, ?_assertNot(ezic_date:compare(current, {2099,12,12})) % is this the right behavior?
].