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
|
||||
tzdata/
|
||||
\#*
|
||||
|
||||
db/
|
||||
4
Makefile
4
Makefile
@@ -1,6 +1,6 @@
|
||||
ERLC_WARNINGS := -W1
|
||||
MNESIA_DIR := db
|
||||
RUN_INIT :=
|
||||
RUN_INIT := -run ezic_db init
|
||||
DEBUG := +debug_info -Ddebug
|
||||
TEST :=
|
||||
TZSET := northamerica
|
||||
@@ -32,7 +32,7 @@ build : DEBUG =
|
||||
build : all
|
||||
|
||||
|
||||
devstart : RUN_INIT = -s ezic dev_start
|
||||
devstart : RUN_INIT := $(RUN_INIT) -s ezic dev_start
|
||||
devstart : all run
|
||||
|
||||
|
||||
|
||||
@@ -24,8 +24,10 @@
|
||||
|
||||
-ifdef(debug).
|
||||
-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.
|
||||
-define(debug(F,X), void).
|
||||
-define(debug(F), void).
|
||||
-endif.
|
||||
|
||||
|
||||
|
||||
48
src/ezic.erl
48
src/ezic.erl
@@ -1,11 +1,37 @@
|
||||
-module(ezic).
|
||||
-include("include/ezic.hrl").
|
||||
|
||||
-export([load/1, time2time/3]).
|
||||
-export([dev_start/0]).
|
||||
-export([localtime/1, time2time/3]).
|
||||
-export([load/1, dev_start/0, test/0]).
|
||||
|
||||
|
||||
load(Folder) ->
|
||||
ezic_loader:load(Folder).
|
||||
-define(gs2dt(X), calendar:gregorian_seconds_to_datetime(X)).
|
||||
-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() ->
|
||||
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) ->
|
||||
% ?debug("flatten_each: ~n\tZone: ~p~n\tRule: ~p~n\tFrom: ~p", [Zone, Rule, From]),
|
||||
% erlang:error(dbg),
|
||||
years
|
||||
not_done.
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
-export([for/2]).
|
||||
-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{}};
|
||||
|
||||
|
||||
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) ->
|
||||
{Date, Tz};
|
||||
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}).
|
||||
|
||||
|
||||
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, {_,_}) -> 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,
|
||||
{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").
|
||||
|
||||
|
||||
-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.
|
||||
project(Rule=#rule{from=F, to=only}) ->
|
||||
not_done;
|
||||
project(Rule=#rule{from=F, to=T}) ->
|
||||
Dates= lists:map(fun(Y)-> ezic_date:for(Rule, Y) end, lists:seq(F, T)).
|
||||
|
||||
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),
|
||||
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