diff --git a/.gitignore b/.gitignore index 497ba18..8d047d9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,4 @@ tmp/ commit_msg tzdata/ \#* - +db/ \ No newline at end of file diff --git a/Makefile b/Makefile index 6a23090..5a41b74 100644 --- a/Makefile +++ b/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 diff --git a/include/ezic.hrl b/include/ezic.hrl index 5da1d83..87405d5 100644 --- a/include/ezic.hrl +++ b/include/ezic.hrl @@ -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. diff --git a/src/ezic.erl b/src/ezic.erl index 4ec4f47..eecd4ed 100644 --- a/src/ezic.erl +++ b/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"]). diff --git a/src/ezic_compile.erl b/src/ezic_compile.erl index 7b5902d..ed720e8 100644 --- a/src/ezic_compile.erl +++ b/src/ezic_compile.erl @@ -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. diff --git a/src/ezic_date.erl b/src/ezic_date.erl index 60ef192..5c6bb57 100644 --- a/src/ezic_date.erl +++ b/src/ezic_date.erl @@ -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; diff --git a/src/ezic_db.erl b/src/ezic_db.erl new file mode 100644 index 0000000..61a31f1 --- /dev/null +++ b/src/ezic_db.erl @@ -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). + diff --git a/src/ezic_loader.erl b/src/ezic_loader.erl index de1c2d3..6488fb4 100644 --- a/src/ezic_loader.erl +++ b/src/ezic_loader.erl @@ -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). + diff --git a/src/ezic_rule.erl b/src/ezic_rule.erl index 0a03de9..3070758 100644 --- a/src/ezic_rule.erl +++ b/src/ezic_rule.erl @@ -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. diff --git a/src/ezic_zone.erl b/src/ezic_zone.erl new file mode 100644 index 0000000..e9e921e --- /dev/null +++ b/src/ezic_zone.erl @@ -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).