Issue #7: Some Zones no longer observing DST are broen. The matchspec generation function for universal time was wrong, whereas it was fine for wall and local times. Fixing that line, and considering the `current` flatzone time, was enough to get TZs like Tokyo and Jamaica working correctly in tested cases.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
-module(ezic_flatten).
|
||||
-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}).
|
||||
@@ -41,12 +40,8 @@ ms(Date, Name) ->
|
||||
R= ['$_'],
|
||||
|
||||
{D, #tztime{time=T, flag=F}}=Date,
|
||||
|
||||
%SDate= {D,T},
|
||||
DComp= {{{D},{T}}},
|
||||
|
||||
DComp= {{ {D},{T} }},
|
||||
G= ms_guards(F, DComp),
|
||||
|
||||
MS= [{M,G,R}],
|
||||
|
||||
|
||||
@@ -77,12 +72,9 @@ flatten_all_zones(AllZones, AllRules) ->
|
||||
flatten_all_zones([], _AllRules, FlatZones) ->
|
||||
FlatZones;
|
||||
flatten_all_zones([Z1|_]= AllZones, AllRules, FlatZones) ->
|
||||
%% io:format("Flattening zones: ~s~n", [Z1#zone.name]),
|
||||
io:format("Flattening zones: ~s~n", [Z1#zone.name]),
|
||||
{CurrentZones, RestZones}= ezic_zone:split_by_name(Z1, AllZones),
|
||||
|
||||
Flats= flatten_zone_set(CurrentZones, AllRules),
|
||||
%ezic_db:insert_all(Flats),
|
||||
|
||||
flatten_all_zones(RestZones, AllRules, Flats ++ FlatZones).
|
||||
|
||||
|
||||
@@ -128,7 +120,7 @@ flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}
|
||||
[Zone | RestZones] = ezic_zone:next(Zones, UTCFrom, DSTOffset),
|
||||
#zone{rule=RuleName}= Zone,
|
||||
|
||||
?debugVal(Zone),
|
||||
%% ?debugVal(Zone),
|
||||
|
||||
%% we have a flatzone with start times;
|
||||
%% must populate the base gmt offset and name for the current zone
|
||||
@@ -137,17 +129,16 @@ flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}
|
||||
|
||||
%% if this is the first run, DST offset is {0,0,0} if this is a
|
||||
%% recursion, DST offset is the previous zone's last DST offset
|
||||
%% @todo see if dst rules carry over zone changes IRL. I'll assume
|
||||
%% they don't.
|
||||
|
||||
%% @todo see if dst rules carry over zone changes IRL. This
|
||||
%% assumes they don't.
|
||||
|
||||
%% gather all rules that _may_ apply
|
||||
|
||||
%% we gather all rules that _may_ apply
|
||||
%Rules= ezic_db:rules(RuleName),
|
||||
Rules= [R || R <- AllRules, R#rule.name =:= RuleName ],
|
||||
|
||||
?debugVal(FromTime),
|
||||
?debugVal(Rules),
|
||||
|
||||
%% ?debugVal(FromTime),
|
||||
%% ?debugVal(Rules),
|
||||
|
||||
%% apply all rules in order, creating flatzones, until this zone
|
||||
%% ends, then regain control. rules have been exhausted, and zone
|
||||
@@ -156,21 +147,21 @@ flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}
|
||||
{RuleFlats, NextFlat, EndingRule}= flatten_rule_set(FromTime, Zone, Rules, CurrentRule, []),
|
||||
FinalFlats= lists:append([RuleFlats, Flats]),
|
||||
|
||||
?debugVal(FinalFlats),
|
||||
?debugVal(NextFlat),
|
||||
%% ?debugVal(FinalFlats),
|
||||
%% ?debugVal(NextFlat),
|
||||
|
||||
%% return flats if we've exceeded our years, or recurse if we can keep going
|
||||
NFUTCFrom = NextFlat#flatzone.utc_from,
|
||||
try maxyear_reached(NFUTCFrom) of
|
||||
true ->
|
||||
?debugMsg("maxyear reached from flatten_zone_set"),
|
||||
%% ?debugMsg("maxyear reached from flatten_zone_set"),
|
||||
FinalFlats;
|
||||
false ->
|
||||
flatten_zone_set(NextFlat, RestZones, AllRules, FinalFlats, EndingRule)
|
||||
catch
|
||||
exit:Reason ->
|
||||
?debugMsg("bad year for nextflat:"),
|
||||
?debugVal(NextFlat),
|
||||
%% ?debugMsg("bad year for nextflat:"),
|
||||
%% ?debugVal(NextFlat),
|
||||
erlang:error(Reason)
|
||||
end.
|
||||
|
||||
@@ -181,10 +172,9 @@ flatten_zone_set(FromTimeStub=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}
|
||||
flatten_rule_set(FlatStart=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset, offset=Offset}
|
||||
, Zone, Rules, CurrentRule, Flats) ->
|
||||
|
||||
|
||||
?debugVal(Offset),
|
||||
?debugVal(DSTOffset),
|
||||
?debugVal(UTCFrom),
|
||||
%% ?debugVal(Offset),
|
||||
%% ?debugVal(DSTOffset),
|
||||
%% ?debugVal(UTCFrom),
|
||||
|
||||
ValidRules= lists:delete(CurrentRule, Rules),
|
||||
RulesWithDates= lists:foldl(
|
||||
@@ -206,15 +196,15 @@ flatten_rule_set(FlatStart=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset, offs
|
||||
ZoneDate= ezic_zone:project_end_utc(Zone, DSTOffset),
|
||||
|
||||
|
||||
?debugVal(UTCEndingRuleDate),
|
||||
?debugVal(EndingRuleYear),
|
||||
?debugVal(EndingRule),
|
||||
?debugVal(ZoneDate),
|
||||
%% ?debugVal(UTCEndingRuleDate),
|
||||
%% ?debugVal(EndingRuleYear),
|
||||
%% ?debugVal(EndingRule),
|
||||
%% ?debugVal(ZoneDate),
|
||||
|
||||
|
||||
try maxyear_reached(UTCEndingRuleDate) andalso maxyear_reached(ZoneDate) of
|
||||
true ->
|
||||
?debugMsg("maxyear reached from flatten_rule_set"),
|
||||
%% ?debugMsg("maxyear reached from flatten_rule_set"),
|
||||
{EndFlat, NextFlat}= finish_and_start_flat(max_year, FlatStart, DSTOffset),
|
||||
NewFlats= [EndFlat | Flats],
|
||||
{NewFlats, NextFlat, none};
|
||||
@@ -263,8 +253,8 @@ finish_and_start_flat(FlatStub=#flatzone{offset=Offset, dstoffset=OldDSTOffset}
|
||||
|
||||
FinalNewFlat= NewFlat2,
|
||||
|
||||
?debugVal(EndFlat),
|
||||
?debugVal(FinalNewFlat),
|
||||
%% ?debugVal(EndFlat),
|
||||
%% ?debugVal(FinalNewFlat),
|
||||
|
||||
{EndFlat, FinalNewFlat};
|
||||
|
||||
@@ -280,7 +270,7 @@ finish_and_start_flat(FlatStub=#flatzone{}, Zone=#zone{}, EndingDST) ->
|
||||
|
||||
RetNextFlat= #flatzone{dstoffset=EndingDST, utc_from=UD},
|
||||
|
||||
?debugVal(EndFlat),
|
||||
%% ?debugVal(EndFlat),
|
||||
% ?debugVal(RetNextFlat),
|
||||
|
||||
{EndFlat, RetNextFlat};
|
||||
@@ -295,23 +285,21 @@ finish_and_start_flat(max_year, FlatStub, DSTOffset) ->
|
||||
|
||||
%% both timezone and rule are ending at the same time
|
||||
finish_flatzone_both(FlatStub=#flatzone{}, EndingZone=#zone{}, ChangingRule=#rule{save=NewDST}, EndingDST) ->
|
||||
?debugVal(FlatStub),
|
||||
%% ?debugVal(FlatStub),
|
||||
|
||||
EndDatesP1={_,_,UD}= ezic_zone:project_end(EndingZone, EndingDST),
|
||||
{WDm, SDm, UDm}= ezic_date:m1s(EndDatesP1),
|
||||
EndFlat= ?ENDFLAT(FlatStub, WDm, SDm, UDm, EndingDST),
|
||||
|
||||
?debugVal(EndFlat),
|
||||
?debugVal(ChangingRule),
|
||||
%% ?debugVal(EndFlat),
|
||||
%% ?debugVal(ChangingRule),
|
||||
|
||||
RetNextFlat= #flatzone{dstoffset=NewDST, utc_from=UD},
|
||||
{EndFlat, RetNextFlat}.
|
||||
|
||||
|
||||
populate_flatzone(
|
||||
FZ=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}
|
||||
populate_flatzone(FZ=#flatzone{utc_from=UTCFrom, dstoffset=DSTOffset}
|
||||
, #zone{name=Name, gmtoff=Offset}) ->
|
||||
|
||||
UTCFromTZ= ezic_date:normalize(UTCFrom, u),
|
||||
{WT, ST, _}= ezic_date:all_times(UTCFromTZ, Offset, DSTOffset),
|
||||
FZ#flatzone{offset=Offset, tzname=Name, wall_from=WT, std_from=ST}.
|
||||
@@ -332,7 +320,7 @@ maxyear_reached(Val) ->
|
||||
|
||||
|
||||
ms_guards(X, D) when X=:=u;X=:=g;X=:=z ->
|
||||
ms_guards2(D, '$6', '$5');
|
||||
ms_guards2(D, '$5', '$6');
|
||||
ms_guards(s, D) ->
|
||||
ms_guards2(D, '$3', '$4');
|
||||
ms_guards(X, D) when X=:=w;X=:=undefined ->
|
||||
@@ -342,5 +330,5 @@ ms_guards(X, D) when X=:=w;X=:=undefined ->
|
||||
ms_guards2(D, From, To) ->
|
||||
[
|
||||
{'=<', From, D}
|
||||
, {'=<', D, To}
|
||||
, {'or', {'=<', D, To}, {'=:=', current, To}}
|
||||
].
|
||||
|
||||
@@ -30,13 +30,6 @@ parse([Name,GmtOffS,Rule,FormatS | UntilTokens]) ->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
current(TzName) ->
|
||||
current_as_of_utc(erlang:universaltime(), TzName).
|
||||
|
||||
@@ -101,9 +94,9 @@ project_end_utc(Zone=#zone{}, DSTOffset) ->
|
||||
%% though it's very unlikely (and does not exist in the current tz database files).
|
||||
%% this method covers that event, anyhow. see unit tests for examples.
|
||||
next(ZoneList, UTCFrom, DSTOff) ->
|
||||
?debugMsg("next:"),
|
||||
?debugVal(ZoneList),
|
||||
?debugVal(UTCFrom),
|
||||
%% ?debugMsg("next:"),
|
||||
%% ?debugVal(ZoneList),
|
||||
%% ?debugVal(UTCFrom),
|
||||
|
||||
DatedList= lists:map(
|
||||
fun(Z=#zone{until=Until, gmtoff=Offset})->
|
||||
@@ -113,7 +106,5 @@ next(ZoneList, UTCFrom, DSTOff) ->
|
||||
end
|
||||
, ZoneList),
|
||||
FilteredList= lists:filter(fun({IDt,_})-> ezic_date:compare(UTCFrom, IDt) end, DatedList),
|
||||
|
||||
%x ?debugVal(FilteredList),
|
||||
SortedList= lists:sort(fun({X,_},{Y,_})->ezic_date:compare(X,Y)end, FilteredList),
|
||||
[Z || {_,Z}<- SortedList].
|
||||
|
||||
@@ -2,12 +2,44 @@
|
||||
-include("include/ezic.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
smoke_test_() ->
|
||||
[
|
||||
%% utc_to_local - normal tests
|
||||
?_assertEqual({{2012,12,17},{13,20,0}}, ezic:utc_to_local({{2012,12,17},{4,20,0}}, "Asia/Tokyo"))
|
||||
, ?_assertEqual({{2012,12,16},{23,20,0}}, ezic:utc_to_local({{2012,12,17},{4,20,0}}, "America/Jamaica"))
|
||||
, ?_assertEqual({{2012,12,16},{20,20,0}}, ezic:utc_to_local({{2012,12,17},{4,20,0}}, "America/Los_Angeles"))
|
||||
|
||||
%% local_to_utc - normal tests
|
||||
, ?_assertEqual({{2012,12,17},{4,20,0}}, ezic:local_to_utc({{2012,12,17},{13,20,0}}, "Asia/Tokyo"))
|
||||
, ?_assertEqual({{2012,12,17},{4,20,0}}, ezic:local_to_utc({{2012,12,16},{23,20,0}}, "America/Jamaica"))
|
||||
, ?_assertEqual({{2012,12,17},{4,20,0}}, ezic:local_to_utc({{2012,12,16},{20,20,0}}, "America/Los_Angeles"))
|
||||
].
|
||||
|
||||
|
||||
local_to_utc_errors_test_() ->
|
||||
[
|
||||
?_assertMatch({error, {ambiguous_zone, _}}, ezic:local_to_utc({{2010,11,7},{1,0,0}}, "America/Los_Angeles"))
|
||||
, ?_assertEqual({error, no_zone}, ezic:local_to_utc({{2010,3,14},{2,30,0}}, "America/Los_Angeles"))
|
||||
].
|
||||
|
||||
utc_to_local_smoke_test_() ->
|
||||
[?_assertEqual({{2012,12,17},{13,20,0}}, ezic:utc_to_local({{2012,12,17},{4,20,0}}, "Asia/Tokyo"))
|
||||
|
||||
%% Tests overlap logic around timezones that have odd DST rules
|
||||
local_to_utc_funkyDST_test_() ->
|
||||
[
|
||||
%% gap
|
||||
?_assertMatch({{1948,5,1},{16,0,0}}, ezic:local_to_utc({{1948,5,2},{1,00,00}}, "Asia/Tokyo"))
|
||||
, ?_assertMatch({error, no_zone}, ezic:local_to_utc({{1948,5,2},{2,00,00}}, "Asia/Tokyo"))
|
||||
, ?_assertMatch({{1948,5,1},{17,0,0}}, ezic:local_to_utc({{1948,5,2},{3,00,00}}, "Asia/Tokyo"))
|
||||
|
||||
%% overlap
|
||||
, ?_assertMatch({{1948,9,10},{14,30,0}}, ezic:local_to_utc({{1948,9,11},{0,30,00}}, "Asia/Tokyo"))
|
||||
, ?_assertMatch({error, {ambiguous_zone, _}}, ezic:local_to_utc({{1948,9,11},{1,00,00}}, "Asia/Tokyo"))
|
||||
, ?_assertMatch({{1948,9,10},{17,0,0}}, ezic:local_to_utc({{1948,9,11},{2,00,00}}, "Asia/Tokyo"))
|
||||
|
||||
%% DST end
|
||||
, ?_assertMatch({error, no_zone}, ezic:local_to_utc({{1951,5,6},{2,0,0}}, "Asia/Tokyo")) %% final DST gap
|
||||
, ?_assertMatch({{1951,5,5},{17,0,0}}, ezic:local_to_utc({{1951,5,6},{3,0,0}}, "Asia/Tokyo")) %% DST in effect for the last time
|
||||
, ?_assertMatch({error, {ambiguous_zone, _}}, ezic:local_to_utc({{1951,9,8},{1,0,0}}, "Asia/Tokyo")) %% DST overlap
|
||||
, ?_assertMatch({{1951,9,7},{17,0,0}}, ezic:local_to_utc({{1951,9,8},{2,0,0}}, "Asia/Tokyo")) %% DST off for the last time
|
||||
, ?_assertMatch({{1952,5,3},{17,0,0}}, ezic:local_to_utc({{1952,5,4},{2,0,0}}, "Asia/Tokyo")) %% ensure DST not in effect for 1952
|
||||
].
|
||||
|
||||
Reference in New Issue
Block a user