alerted to buginess in README. added notes & zic reference. removed local_to_utc conversion for now.

Time comparisons are patently wrong. They do not consider s/w/u/g/z time modifiers, so the comparisons can be off by hours. At least 24 hours within any time or zone change, ezic will give unreliable results.
I think a solution can involve "flattening" the data, starting from the earliest time possible for each zone/rule, going forward.
There exist ambiguous local times for zones and rules. For example, on Nov 7th, in Los Angeles, the times from 1:00:00am to 1:59:59am were repeated, once for PDT and once for PST. Given a local time in that zone between those times, we cannot determine what UTC time is unambiguously. Similar for Zone transitions where fall-back occurs.
This commit is contained in:
aj
2010-11-07 12:52:51 -08:00
parent 4d95017b7f
commit 329f699e51
7 changed files with 609 additions and 51 deletions

View File

@@ -4,8 +4,8 @@
-export([
localtime/1
, utc_to_local/2
, utc_from_local/2
, zone_convert/3
% , local_to_utc/2
% , zone_convert/3
, next_timechange/1
, next_timechange/2
]).
@@ -28,28 +28,28 @@ localtime(TzName) ->
utc_to_local(erlang:universaltime(), TzName).
utc_to_local(Datetime, TzName) ->
{ok, Zone, Rule}= current(Datetime, TzName),
SecDiff= offset(Zone, Rule),
date_add(Datetime, SecDiff).
utc_to_local(UTCDatetime, TzName) ->
{ok, Zone, Rule}= current_as_of_utc(UTCDatetime, TzName),
SecDiff= time_offset(Zone, Rule),
date_add(UTCDatetime, SecDiff).
utc_from_local(Datetime, TzName) ->
{ok, Zone, Rule}= current(Datetime, TzName),
SecDiff= offset(Zone, Rule),
date_subtract(Datetime, 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= utc_from_local(Datetime, FromTimeZone),
utc_to_local(UTC, ToTimeZone).
%% zone_convert(Datetime, FromTimeZone, ToTimeZone) ->
%% UTC= local_to_utc(Datetime, FromTimeZone),
%% utc_to_local(UTC, ToTimeZone).
next_timechange(TzName) ->
next_timechange(localtime(TzName), TzName).
next_timechange(erlang:universaltime(), TzName).
next_timechange(Datetime, TzName) ->
next_timechange(UtcDatetime, TzName) ->
not_done.
@@ -84,23 +84,19 @@ test() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
current(Datetime, TzName) ->
Zones= ezic_db:zones(TzName),
% ?debug("All Zones: ~p", [Zones]),
CZone= ezic_zone:current(Datetime, Zones),
% ?debug("Current Zone: ~p", [CZone]),
current_as_of_utc(UTCDatetime, TzName) ->
CZone= ezic_zone:current_as_of_utc(UTCDatetime, TzName),
RuleName= CZone#zone.rule,
Rules= ezic_db:rules(RuleName),
% ?debug("All Rules: ~p", [Rules]),
CRule= ezic_rule:current(Datetime, Rules),
% ?debug("Current Rule: ~p", [CRule]),
CRule= ezic_rule:current_as_of_utc(UTCDatetime, Rules),
{ok, CZone, CRule}.
%%current_as_of_local(Datetime, TzName) ->
%% not_done.
offset(Zone, Rule) ->
time_offset(Zone, Rule) ->
OffsetSec= ezic_zone:offset_sec(Zone),
DSTSec= ezic_rule:dst_sec(Rule),
OffsetSec + DSTSec.

View File

@@ -16,7 +16,8 @@
% returns the concrete date tuple for a given rule and year, irrespective of zone
% 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) ->
last_day_of(D, Y,M);
@@ -24,13 +25,15 @@ for(#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) ->
@@ -38,7 +41,8 @@ first_day_limited(Day, {leq, N}, 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),
@@ -56,6 +60,8 @@ previous_day(Day, Date={Y,M,D}) ->
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),
@@ -115,6 +121,8 @@ 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 ->
@@ -144,6 +152,7 @@ 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) ->

View File

@@ -2,7 +2,7 @@
-include("include/ezic.hrl").
-export([current/2, relevant/2, sort/2]).
-export([current/2, current_set/2, sort/2]).
-export([from_time/1, dst_sec/1]).
@@ -10,9 +10,9 @@
current(_, []) ->
none;
current(Now, Rules) ->
RelevantRules= lists:filter(fun(R)-> relevant(Now, R) end, Rules),
% ?debug("RelevantRules: ~p", [RelevantRules]),
SRules= lists:sort(fun sort/2, RelevantRules),
Current_SetRules= lists:filter(fun(R)-> current_set(Now, R) end, Rules),
% ?debug("Current_SetRules: ~p", [Current_SetRules]),
SRules= lists:sort(fun sort/2, Current_SetRules),
CRule= choose_rule(SRules),
CRule.
@@ -27,15 +27,15 @@ sort(R1, R2) ->
relevant(Now, Rule=#rule{from=F, to=T}) ->
current_set(Now, Rule=#rule{from=F, to=T}) ->
{{Y,_,_},_} = Now,
case ezic_date:date_between(Y, {F,T}) of
false -> false;
true -> relevant2(Now, Rule)
true -> current_set2(Now, Rule)
end.
relevant2(Now, #rule{in=Month, on=Day, at=Time}) ->
current_set2(Now, #rule{in=Month, on=Day, at=Time}) ->
{{Y,_,_},_} = Now,
RTime= {{Y, Month, Day}, Time},
ezic_date:compare_datetimes(RTime, Now).

View File

@@ -1,16 +1,45 @@
-module(ezic_zone).
-include("include/ezic.hrl").
-export([current/2, sort/2, revsort/2, offset_sec/1]).
-export([
current/1
, current_as_of_utc/2
, sort/2
, revsort/2
, offset_sec/1
]).
current(_, []) ->
current(TzName) ->
current_as_of_utc(erlang:universaltime(), TzName).
current_as_of_utc(UTCDatetime, TzName) ->
Zones= ezic_db:zones(TzName),
get_zone_utc(UTCDatetime, Zones).
%%current_as_of_local(_Datetime, _TzName) ->
%% not_done.
get_zone_utc(_, []) ->
erlang:error(no_current);
current(Now, Zones) ->
get_zone_utc(UTCDatetime, Zones) ->
SortedZones= lists:sort(fun revsort/2, Zones),
% ?debug("SortedZones: ~p", [SortedZones]),
[CZone|_]= lists:dropwhile(fun(SZ)-> older_zone(SZ, Now) end, SortedZones),
CZone.
%% [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.
@@ -20,8 +49,11 @@ sort(#zone{until=U1}, #zone{until=U2}) ->
revsort(Z1=#zone{}, Z2=#zone{}) ->
not sort(Z1, Z2).
older_zone(#zone{until=Until}, Now) ->
ezic_date:compare_datetimes(Until, Now).
% @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).