happy path tested! current times correctly reported for LA, NY, Tokyo, and Jamaica!
Japan doesn't observer DST. I didn't know that.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,4 +5,4 @@ tmp/
|
|||||||
commit_msg
|
commit_msg
|
||||||
tzdata/
|
tzdata/
|
||||||
\#*
|
\#*
|
||||||
|
db/
|
||||||
4
Makefile
4
Makefile
@@ -1,6 +1,6 @@
|
|||||||
ERLC_WARNINGS := -W1
|
ERLC_WARNINGS := -W1
|
||||||
MNESIA_DIR := db
|
MNESIA_DIR := db
|
||||||
RUN_INIT :=
|
RUN_INIT := -run ezic_db init
|
||||||
DEBUG := +debug_info -Ddebug
|
DEBUG := +debug_info -Ddebug
|
||||||
TEST :=
|
TEST :=
|
||||||
TZSET := northamerica
|
TZSET := northamerica
|
||||||
@@ -32,7 +32,7 @@ build : DEBUG =
|
|||||||
build : all
|
build : all
|
||||||
|
|
||||||
|
|
||||||
devstart : RUN_INIT = -s ezic dev_start
|
devstart : RUN_INIT := $(RUN_INIT) -s ezic dev_start
|
||||||
devstart : all run
|
devstart : all run
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,10 @@
|
|||||||
|
|
||||||
-ifdef(debug).
|
-ifdef(debug).
|
||||||
-define(debug(F,X), io:format("{~p:~p} - " ++ F ++ "~n", [?MODULE, ?LINE] ++ X)).
|
-define(debug(F,X), io:format("{~p:~p} - " ++ F ++ "~n", [?MODULE, ?LINE] ++ X)).
|
||||||
|
-define(debug(F), io:format("{~p:~p} - ~p~n", [?MODULE, ?LINE, F])).
|
||||||
-else.
|
-else.
|
||||||
-define(debug(F,X), void).
|
-define(debug(F,X), void).
|
||||||
|
-define(debug(F), void).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
48
src/ezic.erl
48
src/ezic.erl
@@ -1,11 +1,37 @@
|
|||||||
-module(ezic).
|
-module(ezic).
|
||||||
|
-include("include/ezic.hrl").
|
||||||
|
|
||||||
-export([load/1, time2time/3]).
|
-export([localtime/1, time2time/3]).
|
||||||
-export([dev_start/0]).
|
-export([load/1, dev_start/0, test/0]).
|
||||||
|
|
||||||
|
|
||||||
load(Folder) ->
|
-define(gs2dt(X), calendar:gregorian_seconds_to_datetime(X)).
|
||||||
ezic_loader:load(Folder).
|
-define(dt2gs(X), calendar:datetime_to_gregorian_seconds(X)).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
localtime(TzName) ->
|
||||||
|
Now= erlang:universaltime(),
|
||||||
|
|
||||||
|
Zones= ezic_db:zones(TzName),
|
||||||
|
% ?debug("All Zones: ~p", [Zones]),
|
||||||
|
CZone= ezic_zone:current(Now, Zones),
|
||||||
|
% ?debug("Current Zone: ~p", [CZone]),
|
||||||
|
|
||||||
|
RuleName= CZone#zone.rule,
|
||||||
|
Rules= ezic_db:rules(RuleName),
|
||||||
|
% ?debug("All Rules: ~p", [Rules]),
|
||||||
|
CRule= ezic_rule:current(Now, Rules),
|
||||||
|
% ?debug("Current Rule: ~p", [CRule]),
|
||||||
|
|
||||||
|
|
||||||
|
OffsetSec= ezic_zone:offset_sec(CZone),
|
||||||
|
DSTSec= ezic_rule:dst_sec(CRule),
|
||||||
|
%
|
||||||
|
|
||||||
|
SecDiff= OffsetSec + DSTSec,
|
||||||
|
?gs2dt(?dt2gs(Now) + SecDiff).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -15,5 +41,19 @@ time2time(_DateTime, _FromTimeZone, _ToTimeZone) ->
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
load(Folder) ->
|
||||||
|
ezic_loader:load(Folder).
|
||||||
|
|
||||||
|
|
||||||
dev_start() ->
|
dev_start() ->
|
||||||
ezic:load(filename:join("priv","tzdata")).
|
ezic:load(filename:join("priv","tzdata")).
|
||||||
|
|
||||||
|
|
||||||
|
test() ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(TZ)->
|
||||||
|
io:format("~s: ~p~n", [TZ, localtime(TZ)])
|
||||||
|
end,
|
||||||
|
["Asia/Tokyo", "America/New_York", "America/Los_Angeles", "America/Jamaica"]).
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ flatten_one(Zone=#zone{until=UTo}, NextZone=#zone{until=UFrom}, Rules) ->
|
|||||||
flatten_each(Zone, Rule, From) ->
|
flatten_each(Zone, Rule, From) ->
|
||||||
% ?debug("flatten_each: ~n\tZone: ~p~n\tRule: ~p~n\tFrom: ~p", [Zone, Rule, From]),
|
% ?debug("flatten_each: ~n\tZone: ~p~n\tRule: ~p~n\tFrom: ~p", [Zone, Rule, From]),
|
||||||
% erlang:error(dbg),
|
% erlang:error(dbg),
|
||||||
years
|
|
||||||
not_done.
|
not_done.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
-export([for/2]).
|
-export([for/2]).
|
||||||
-export([month_to_num/1, day_to_num/1]).
|
-export([month_to_num/1, day_to_num/1]).
|
||||||
-export([compare_times/2, compare_datetimes/2, normalize/1, overlap/2]).
|
-export([compare_times/2, compare_datetimes/2, normalize/1, overlap/2, date_between/2]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -153,6 +153,9 @@ normalize({Y,M,{last, D}}) ->
|
|||||||
{Date, #tztime{}};
|
{Date, #tztime{}};
|
||||||
|
|
||||||
|
|
||||||
|
normalize({Date={Y,M,D},Time={H,_,_}}) when is_integer(H) ->
|
||||||
|
{Date, #tztime{time=Time}};
|
||||||
|
|
||||||
normalize({Date={Y,M,D}, Tz=#tztime{}}) when is_integer(D) ->
|
normalize({Date={Y,M,D}, Tz=#tztime{}}) when is_integer(D) ->
|
||||||
{Date, Tz};
|
{Date, Tz};
|
||||||
normalize({{Y,M,TzOn=#tzon{day=Day, filter=Filter}}, Tz=#tztime{}}) ->
|
normalize({{Y,M,TzOn=#tzon{day=Day, filter=Filter}}, Tz=#tztime{}}) ->
|
||||||
@@ -181,6 +184,13 @@ dates_overlap_normal({D1s,D1e}, {D2s,D2e}) ->
|
|||||||
orelse date_between_normal(D1e, {D2s,D2e}).
|
orelse date_between_normal(D1e, {D2s,D2e}).
|
||||||
|
|
||||||
|
|
||||||
|
date_between(D1, {D2s, D2e}) ->
|
||||||
|
D1N= normalize(D1),
|
||||||
|
D2sN= normalize(D2s),
|
||||||
|
D2eN= normalize(D2e),
|
||||||
|
date_between_normal(D1N, {D2sN, D2eN}).
|
||||||
|
|
||||||
|
|
||||||
date_between_normal(minimum, {minimum,_}) -> true;
|
date_between_normal(minimum, {minimum,_}) -> true;
|
||||||
date_between_normal(minimum, {_,_}) -> false;
|
date_between_normal(minimum, {_,_}) -> false;
|
||||||
|
|
||||||
|
|||||||
82
src/ezic_db.erl
Normal file
82
src/ezic_db.erl
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
-module(ezic_db).
|
||||||
|
-include("include/ezic.hrl").
|
||||||
|
-include_lib("stdlib/include/qlc.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
-export([zones/1, rules/1]).
|
||||||
|
-export([wipe/0, init/0, insert_all/1, getall/1]).
|
||||||
|
|
||||||
|
|
||||||
|
zones(Name) ->
|
||||||
|
F = fun()->
|
||||||
|
Q = qlc:q([Z || Z=#zone{name=N} <- mnesia:table(zone), N=:=Name]),
|
||||||
|
qlc:e(Q)
|
||||||
|
end,
|
||||||
|
{atomic, Zones}= mnesia:transaction(F),
|
||||||
|
Zones.
|
||||||
|
|
||||||
|
rules(Name) ->
|
||||||
|
F = fun()->
|
||||||
|
Q = qlc:q([R || R=#rule{name=N} <- mnesia:table(rule), N=:=Name]),
|
||||||
|
qlc:e(Q)
|
||||||
|
end,
|
||||||
|
{atomic, Rules}= mnesia:transaction(F),
|
||||||
|
Rules.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-define(create(Record),
|
||||||
|
{atomic, ok} = mnesia:create_table(Record, [{type, bag}, {disc_copies, [node()]},{attributes, record_info(fields, Record)}])).
|
||||||
|
|
||||||
|
|
||||||
|
init() ->
|
||||||
|
create_tabs(mnesia:create_schema([node()])),
|
||||||
|
mnesia:wait_for_tables([rule, zone, link, leap], 3000).
|
||||||
|
|
||||||
|
wipe() ->
|
||||||
|
mnesia:stop(),
|
||||||
|
mnesia:delete_schema([node()]).
|
||||||
|
|
||||||
|
|
||||||
|
create_tabs(ok) ->
|
||||||
|
mnesia:start(),
|
||||||
|
|
||||||
|
?create(rule),
|
||||||
|
?create(zone),
|
||||||
|
?create(link),
|
||||||
|
?create(leap),
|
||||||
|
|
||||||
|
%% {atomic, ok}= mnesia:create_table(rule, [{disc_copies, [node()]},{attributes, record_info(fields, rule)}]),
|
||||||
|
%% {atomic, ok}= mnesia:create_table(zone, [{disc_copies, [node()]},{attributes, record_info(fields, zone)}]),
|
||||||
|
%% {atomic, ok}= mnesia:create_table(leap, [{disc_copies, [node()]},{attributes, record_info(fields, leap)}]),
|
||||||
|
%% {atomic, ok}= mnesia:create_table(link, [{disc_copies, [node()]},{attributes, record_info(fields, link)}]);
|
||||||
|
|
||||||
|
ok;
|
||||||
|
create_tabs(E={error, {_, {already_exists,_}}}) ->
|
||||||
|
mnesia:start(),
|
||||||
|
ok;
|
||||||
|
create_tabs(E) ->
|
||||||
|
?debug(E).
|
||||||
|
|
||||||
|
|
||||||
|
insert_all(Records) ->
|
||||||
|
mnesia:transaction(
|
||||||
|
fun() ->
|
||||||
|
lists:foreach(
|
||||||
|
fun(R)-> mnesia:write(R) end,
|
||||||
|
Records)
|
||||||
|
end).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
getall(Tab) when is_atom(Tab) ->
|
||||||
|
F = fun() ->
|
||||||
|
Q = qlc:q([R || R<- mnesia:table(Tab)]),
|
||||||
|
qlc:e(Q)
|
||||||
|
end,
|
||||||
|
mnesia:transaction(F).
|
||||||
|
|
||||||
@@ -14,8 +14,17 @@ load(File) ->
|
|||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
{ok, Zones, Rules, Leaps, Links} = ezic_compile:separate(Records),
|
{ok, Zones, Rules, Leaps, Links} = ezic_compile:separate(Records),
|
||||||
ezic_compile:flatten(Zones, Rules).
|
|
||||||
% @todo store in mnesia
|
% ezic_compile:flatten(Zones, Rules).
|
||||||
|
|
||||||
|
ezic_db:wipe(),
|
||||||
|
ezic_db:init(),
|
||||||
|
|
||||||
|
ezic_db:insert_all(Zones),
|
||||||
|
ezic_db:insert_all(Rules),
|
||||||
|
ezic_db:insert_all(Leaps),
|
||||||
|
ezic_db:insert_all(Links).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,19 +2,61 @@
|
|||||||
-include("include/ezic.hrl").
|
-include("include/ezic.hrl").
|
||||||
|
|
||||||
|
|
||||||
-export([]).
|
-export([current/2, relevant/2, sort/2]).
|
||||||
|
-export([from_time/1, dst_sec/1]).
|
||||||
|
|
||||||
|
|
||||||
% returns a list of all {datetime,offset} sets that must be
|
|
||||||
% projected for this Rule.
|
current(_, []) ->
|
||||||
project(Rule=#rule{from=F, to=only}) ->
|
none;
|
||||||
not_done;
|
current(Now, Rules) ->
|
||||||
project(Rule=#rule{from=F, to=T}) ->
|
RelevantRules= lists:filter(fun(R)-> relevant(Now, R) end, Rules),
|
||||||
Dates= lists:map(fun(Y)-> ezic_date:for(Rule, Y) end, lists:seq(F, T)).
|
% ?debug("RelevantRules: ~p", [RelevantRules]),
|
||||||
|
SRules= lists:sort(fun sort/2, RelevantRules),
|
||||||
|
CRule= choose_rule(SRules),
|
||||||
|
CRule.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
sort(R1, R2) ->
|
||||||
|
T1= from_time(R1),
|
||||||
|
T2= from_time(R2),
|
||||||
|
ezic_date:compare_datetimes(T1, T2).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
relevant(Now, Rule=#rule{from=F, to=T}) ->
|
||||||
|
{{Y,_,_},_} = Now,
|
||||||
|
case ezic_date:date_between(Y, {F,T}) of
|
||||||
|
false -> false;
|
||||||
|
true -> relevant2(Now, Rule)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
relevant2(Now, Rule=#rule{in=Month, on=Day, at=Time}) ->
|
||||||
|
{{Y,_,_},_} = Now,
|
||||||
|
RTime= {{Y, Month, Day}, Time},
|
||||||
|
ezic_date:compare_datetimes(RTime, Now).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from_time(#rule{from=F, in=M, on=O, at=A}) ->
|
||||||
|
{{F,M,O},A}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
choose_rule([]) ->
|
||||||
|
none;
|
||||||
|
choose_rule([H|_]) ->
|
||||||
|
H.
|
||||||
|
|
||||||
|
|
||||||
|
dst_sec(none) ->
|
||||||
|
0;
|
||||||
|
dst_sec(Rule) ->
|
||||||
|
calendar:time_to_seconds((Rule#rule.save)#tztime.time).
|
||||||
|
|
||||||
|
|
||||||
% returns all the years inbetween
|
|
||||||
years(_, Rule=#rule{from=F, to=only}) ->
|
|
||||||
[F];
|
|
||||||
years(Max, Rule=#rule{from=F, to=maximum}) ->
|
|
||||||
fudge.
|
|
||||||
|
|||||||
29
src/ezic_zone.erl
Normal file
29
src/ezic_zone.erl
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
-module(ezic_zone).
|
||||||
|
-include("include/ezic.hrl").
|
||||||
|
|
||||||
|
-export([current/2, sort/2, revsort/2, offset_sec/1]).
|
||||||
|
|
||||||
|
|
||||||
|
current(_, []) ->
|
||||||
|
erlang:error(no_current);
|
||||||
|
current(Now, Zones) ->
|
||||||
|
SortedZones= lists:sort(fun revsort/2, Zones),
|
||||||
|
% ?debug("SortedZones: ~p", [SortedZones]),
|
||||||
|
[CZone|_]= lists:dropwhile(fun(SZ)-> older_zone(SZ, Now) end, SortedZones),
|
||||||
|
CZone.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
sort(#zone{until=U1}, #zone{until=U2}) ->
|
||||||
|
ezic_date:compare_datetimes(U2, U1).
|
||||||
|
|
||||||
|
revsort(Z1=#zone{}, Z2=#zone{}) ->
|
||||||
|
not sort(Z1, Z2).
|
||||||
|
|
||||||
|
older_zone(Zone=#zone{until=Until}, Now) ->
|
||||||
|
ezic_date:compare_datetimes(Until, Now).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
offset_sec(Zone) ->
|
||||||
|
calendar:time_to_seconds((Zone#zone.gmtoff)#tztime.time).
|
||||||
Reference in New Issue
Block a user