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:
46
src/ezic.erl
46
src/ezic.erl
@@ -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.
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user