lots of work towards flattening the records. stalled at ezic_rule:next_event/4
This commit is contained in:
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
|
|
||||||
% a flattened zone record, giving specific information for a specific time range.
|
% a flattened zone record, giving specific information for a specific time range.
|
||||||
-record(flatzone, {tzname, tzrule, from, to, gmtoff}).
|
-record(flatzone, {tzname, wall_from, wall_to, std_from, std_to, utc_from, utc_to, offset}).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
122
notes/flattening.svg
Normal file
122
notes/flattening.svg
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="744.09448819"
|
||||||
|
height="1052.3622047"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.47pre4 r22446"
|
||||||
|
sodipodi:docname="flattening.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<inkscape:perspective
|
||||||
|
sodipodi:type="inkscape:persp3d"
|
||||||
|
inkscape:vp_x="0 : 526.18109 : 1"
|
||||||
|
inkscape:vp_y="0 : 1000 : 0"
|
||||||
|
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||||
|
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||||
|
id="perspective10" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="311.19013"
|
||||||
|
inkscape:cy="621.31928"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1280"
|
||||||
|
inkscape:window-height="750"
|
||||||
|
inkscape:window-x="-1"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ff0000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||||
|
d="m 83.571429,374.50504 607.142861,0"
|
||||||
|
id="path2818" />
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono"
|
||||||
|
x="34.285713"
|
||||||
|
y="288.07645"
|
||||||
|
id="text3604"
|
||||||
|
sodipodi:linespacing="100%"><tspan
|
||||||
|
sodipodi:role="line"
|
||||||
|
id="tspan3606"
|
||||||
|
x="34.285713"
|
||||||
|
y="288.07645">DST point changes</tspan></text>
|
||||||
|
<text
|
||||||
|
sodipodi:linespacing="100%"
|
||||||
|
id="text3608"
|
||||||
|
y="468.07645"
|
||||||
|
x="34.285713"
|
||||||
|
style="font-size:16px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:100%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:DejaVu Sans Mono;-inkscape-font-specification:DejaVu Sans Mono"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
y="468.07645"
|
||||||
|
x="34.285713"
|
||||||
|
id="tspan3610"
|
||||||
|
sodipodi:role="line">Zone point changes</tspan></text>
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 84.285714,408.07647 217.142856,0"
|
||||||
|
id="path3612" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 262.85714,432.36218 462.71429,0"
|
||||||
|
id="path3614"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 137,340.36218 102,0"
|
||||||
|
id="path3668" />
|
||||||
|
<path
|
||||||
|
id="path3670"
|
||||||
|
d="m 217,348.36218 102,0"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
id="path3674"
|
||||||
|
d="m 337,340.36218 102,0"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 417,348.36218 102,0"
|
||||||
|
id="path3676" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 537,340.36218 102,0"
|
||||||
|
id="path3678" />
|
||||||
|
<path
|
||||||
|
id="path3680"
|
||||||
|
d="m 617,348.36218 102,0"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.6 KiB |
10
src/ezic.erl
10
src/ezic.erl
@@ -49,7 +49,8 @@ utc_to_local(UTCDatetime, TzName) ->
|
|||||||
next_timechange(TzName) ->
|
next_timechange(TzName) ->
|
||||||
next_timechange(erlang:universaltime(), TzName).
|
next_timechange(erlang:universaltime(), TzName).
|
||||||
|
|
||||||
next_timechange(UtcDatetime, TzName) ->
|
next_timechange(UTCDatetime, TzName) ->
|
||||||
|
UTCDatetime, TzName,
|
||||||
not_done.
|
not_done.
|
||||||
|
|
||||||
|
|
||||||
@@ -63,7 +64,8 @@ next_timechange(UtcDatetime, TzName) ->
|
|||||||
|
|
||||||
|
|
||||||
load(Folder) ->
|
load(Folder) ->
|
||||||
ezic_loader:load(Folder).
|
ezic_loader:load(Folder),
|
||||||
|
ezic_flatten:flatten().
|
||||||
|
|
||||||
|
|
||||||
dev_start() ->
|
dev_start() ->
|
||||||
@@ -106,6 +108,6 @@ time_offset(Zone, Rule) ->
|
|||||||
date_add(Datetime, SecDiff) ->
|
date_add(Datetime, SecDiff) ->
|
||||||
?gs2dt(?dt2gs(Datetime) + SecDiff).
|
?gs2dt(?dt2gs(Datetime) + SecDiff).
|
||||||
|
|
||||||
date_subtract(Datetime, SecDiff) ->
|
%% date_subtract(Datetime, SecDiff) ->
|
||||||
?gs2dt(?dt2gs(Datetime) - SecDiff).
|
%% ?gs2dt(?dt2gs(Datetime) - SecDiff).
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,14 @@
|
|||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-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, date_between/2]).
|
-export([compare_times/2, compare_datetimes/2, normalize/1, overlap/2, date_between/2]).
|
||||||
|
|
||||||
|
-export([add_seconds/2, all_times/3]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -218,3 +222,52 @@ date_between_normal(D1, {D2s, D2e}) ->
|
|||||||
andalso compare_datetimes_normal(D1, D2e).
|
andalso compare_datetimes_normal(D1, D2e).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
add_seconds(Datetime, Seconds) ->
|
||||||
|
calendar:gregorian_seconds_to_datetime(
|
||||||
|
calendar:date_to_gregorian_seconds(Datetime) + Seconds
|
||||||
|
).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% returns {WallTime, StdTime, UtcTime}
|
||||||
|
% where each is a datetime tuple: {{Y,M,D}{HH,MM,SS}}
|
||||||
|
|
||||||
|
% universal time given
|
||||||
|
all_times(Datetime=#tztime{time=UTCTime, flag=Flag}, Offset, DSTOffset)
|
||||||
|
when Flag=:=u; Flag=:=g; Flag=:=z ->
|
||||||
|
OSec= calendar:time_to_seconds(Offset),
|
||||||
|
DSTSec= calendar:time_to_seconds(DSTOffset),
|
||||||
|
|
||||||
|
STDTime= add_seconds(UTCTime, OSec),
|
||||||
|
WallTime= add_seconds(STDTime, DSTSec),
|
||||||
|
|
||||||
|
{WallTime, STDTime, UTCTime};
|
||||||
|
|
||||||
|
|
||||||
|
% standard time given
|
||||||
|
all_times(Datetime=#tztime{time=STDTime, flag=s}, Offset, DSTOffset) ->
|
||||||
|
OSec= calendar:time_to_seconds(Offset),
|
||||||
|
DSTSec= calendar:time_to_seconds(DSTOffset),
|
||||||
|
|
||||||
|
UTCTime= add_seconds(STDTime, -1*OSec),
|
||||||
|
WallTime= add_seconds(STDTime, DSTSec),
|
||||||
|
|
||||||
|
{WallTime, STDTime, UTCTime};
|
||||||
|
|
||||||
|
|
||||||
|
% wall time given
|
||||||
|
all_times(Datetime=#tztime{time=WallTime, flag=Flag}, Offset, DSTOffset)
|
||||||
|
when Flag=:=w, Flag=:=undefined ->
|
||||||
|
OSec= calendar:time_to_seconds(Offset),
|
||||||
|
DSTSec= calendar:time_to_seconds(DSTOffset),
|
||||||
|
|
||||||
|
STDTime= add_seconds(WallTime, -1*DSTSec),
|
||||||
|
UTCTime= add_seconds(STDTime, -1*OSec),
|
||||||
|
|
||||||
|
{WallTime, STDTime, UTCTime}.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
112
src/ezic_db.erl
112
src/ezic_db.erl
@@ -1,13 +1,28 @@
|
|||||||
|
|
||||||
-module(ezic_db).
|
-module(ezic_db).
|
||||||
-include("include/ezic.hrl").
|
-include("include/ezic.hrl").
|
||||||
-include_lib("stdlib/include/qlc.hrl").
|
-include_lib("stdlib/include/qlc.hrl").
|
||||||
|
|
||||||
|
|
||||||
-export([zones/1, rules/1]).
|
-export([zones/1, rules/1]).
|
||||||
-export([wipe/0, init/0, insert_all/1, getall/1]).
|
-export([wipe/0, init/0, insert_all/1, get_all/1]).
|
||||||
|
|
||||||
|
|
||||||
|
-define(create(Record),
|
||||||
|
{atomic, ok} = mnesia:create_table(Record,
|
||||||
|
[{type, bag}
|
||||||
|
, {disc_copies, [node()]}
|
||||||
|
, {attributes, record_info(fields, Record)}
|
||||||
|
])).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
% READ - db reading methods
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% retrieve all zones by name
|
||||||
zones(Name) ->
|
zones(Name) ->
|
||||||
F = fun()->
|
F = fun()->
|
||||||
Q = qlc:q([Z || Z=#zone{name=N} <- mnesia:table(zone), N=:=Name]),
|
Q = qlc:q([Z || Z=#zone{name=N} <- mnesia:table(zone), N=:=Name]),
|
||||||
@@ -16,6 +31,7 @@ zones(Name) ->
|
|||||||
{atomic, Zones}= mnesia:transaction(F),
|
{atomic, Zones}= mnesia:transaction(F),
|
||||||
Zones.
|
Zones.
|
||||||
|
|
||||||
|
% retrieve all rules by name
|
||||||
rules(Name) ->
|
rules(Name) ->
|
||||||
F = fun()->
|
F = fun()->
|
||||||
Q = qlc:q([R || R=#rule{name=N} <- mnesia:table(rule), N=:=Name]),
|
Q = qlc:q([R || R=#rule{name=N} <- mnesia:table(rule), N=:=Name]),
|
||||||
@@ -24,46 +40,22 @@ rules(Name) ->
|
|||||||
{atomic, Rules}= mnesia:transaction(F),
|
{atomic, Rules}= mnesia:transaction(F),
|
||||||
Rules.
|
Rules.
|
||||||
|
|
||||||
|
% get all records from table
|
||||||
|
get_all(Tab) when is_atom(Tab) ->
|
||||||
|
F = fun() ->
|
||||||
|
Q = qlc:q([R || R<- mnesia:table(Tab)]),
|
||||||
|
qlc:e(Q)
|
||||||
|
end,
|
||||||
|
{atomic, Ret}= mnesia:transaction(F),
|
||||||
|
Ret.
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
% WRITE - insertion/edit methods
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-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({error, {_, {already_exists,_}}}) ->
|
|
||||||
mnesia:start(),
|
|
||||||
ok;
|
|
||||||
create_tabs(E) ->
|
|
||||||
?debug(E).
|
|
||||||
|
|
||||||
|
|
||||||
insert_all(Records) ->
|
insert_all(Records) ->
|
||||||
mnesia:transaction(
|
mnesia:transaction(
|
||||||
fun() ->
|
fun() ->
|
||||||
@@ -74,10 +66,40 @@ insert_all(Records) ->
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
getall(Tab) when is_atom(Tab) ->
|
|
||||||
F = fun() ->
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
Q = qlc:q([R || R<- mnesia:table(Tab)]),
|
% ADMIN - initialization and administration methods
|
||||||
qlc:e(Q)
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
end,
|
|
||||||
mnesia:transaction(F).
|
|
||||||
|
% initialize the db
|
||||||
|
init() ->
|
||||||
|
create_tabs(mnesia:create_schema([node()])),
|
||||||
|
mnesia:wait_for_tables([rule, zone, link, leap], 3000).
|
||||||
|
|
||||||
|
|
||||||
|
% WARNING: deletes all db files
|
||||||
|
wipe() ->
|
||||||
|
mnesia:stop(),
|
||||||
|
mnesia:delete_schema([node()]).
|
||||||
|
|
||||||
|
|
||||||
|
create_tabs(ok) ->
|
||||||
|
mnesia:start(),
|
||||||
|
|
||||||
|
?create(rule),
|
||||||
|
?create(zone),
|
||||||
|
?create(link),
|
||||||
|
?create(leap),
|
||||||
|
?create(flatzone),
|
||||||
|
|
||||||
|
ok;
|
||||||
|
create_tabs({error, {_, {already_exists,_}}}) ->
|
||||||
|
mnesia:start(),
|
||||||
|
ok;
|
||||||
|
create_tabs(E) ->
|
||||||
|
?debug(E).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
145
src/ezic_flatten.erl
Normal file
145
src/ezic_flatten.erl
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
-module(ezic_flatten).
|
||||||
|
-include("include/ezic.hrl").
|
||||||
|
|
||||||
|
-define(FLAT(W,S,U), #flatzone{wall_from=W, std_from=S, utc_from=U}).
|
||||||
|
-define(ENDFLAT(F,W,S,U), F#flatzone{wall_to=W, std_to=S, utc_to=U}).
|
||||||
|
-define(MINFLAT, ?FLAT(minimum,minimum,minimum)).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-export([flatten/0]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
flatten() ->
|
||||||
|
AllZones= ezic_db:get_all(zone),
|
||||||
|
flatten_zones(AllZones),
|
||||||
|
not_done.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
% PRIVATE
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
make_next_flat(#flatzone{wall_to=W, std_to=S, utc_to=U, offset=O}) ->
|
||||||
|
make_next_flat({W,S,U}, O);
|
||||||
|
make_next_flat(EndTimes) ->
|
||||||
|
make_next_flat(EndTimes, undefined).
|
||||||
|
|
||||||
|
|
||||||
|
make_next_flat({W,S,U}, O) ->
|
||||||
|
FW= ezic_date:add_seconds(W,1),
|
||||||
|
FS= ezic_date:add_seconds(S,1),
|
||||||
|
FU= ezic_date:add_seconds(U,1),
|
||||||
|
Flat= ?FLAT(FW, FS, FU),
|
||||||
|
Flat#flatzone{offset=O}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
end_flat(Flat=#flatzone{}, {W,S,U}) ->
|
||||||
|
?ENDFLAT(Flat,W,S,U).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
% INTERNAL
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% recursively processes sets of similar zones until they're all done, passing zone sets to flatten_zone_set/1
|
||||||
|
flatten_zones(Zones) ->
|
||||||
|
flatten_zones(Zones, []).
|
||||||
|
|
||||||
|
flatten_zones([], Flats) ->
|
||||||
|
Flats;
|
||||||
|
flatten_zones([Z1|_]= AllZones, Flats) ->
|
||||||
|
{CurrentZones, RestZones}= ezic_zone:split_by_name(Z1, AllZones),
|
||||||
|
|
||||||
|
SortedZones= lists:sort(fun ezic_zone:sort_ascending/2, CurrentZones),
|
||||||
|
NewFlats= lists:merge(Flats, flatten_zone_set(SortedZones)),
|
||||||
|
|
||||||
|
flatten_zones(RestZones, NewFlats).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% takes zones one-by-one, gathering relevant rules and create flat periods of the same gmt offset (#flatzone)
|
||||||
|
flatten_zone_set(Zones) ->
|
||||||
|
flatten_zone_set(?MINFLAT, Zones, []).
|
||||||
|
|
||||||
|
|
||||||
|
% flatten_zone_set(FromTime, Zones, Flats) -> [#flatzone{}]
|
||||||
|
% FromTime= #flatzone{*_from =/= undefined}
|
||||||
|
% Zones= [#zone{}]
|
||||||
|
% Flats= [#flatzone{}]
|
||||||
|
%
|
||||||
|
% assumes a new zone every time it is called
|
||||||
|
flatten_zone_set(_, [], Flats) ->
|
||||||
|
Flats;
|
||||||
|
flatten_zone_set(FromTimeStub, [Z1=#zone{rule=RuleName, until=UntilTime, gmtoff=Offset} | RestZones] = Zones, Flats) ->
|
||||||
|
|
||||||
|
%% we have a flatzone with start times, we populate the base offset
|
||||||
|
FromTime= FromTimeStub#flatzone{offset=Offset},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%% we gather all rules that _may_ apply (same year)
|
||||||
|
Rules= ezic_db:rules(RuleName),
|
||||||
|
RelevantRules= ezic_rule:filter(FromTime, UntilTime, Rules),
|
||||||
|
SortedRules= lists:sort(fun ezic_rule:sort_ascending/2, RelevantRules),
|
||||||
|
|
||||||
|
%% Then we plot points, bouncing from rule to rule, ending when the zone ends
|
||||||
|
{ok, NextFromTimeStub, NewFlats}= flatten_zone(FromTime, Z1, SortedRules),
|
||||||
|
|
||||||
|
|
||||||
|
not_done.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% flattens a single zone into multiple flatzones.
|
||||||
|
% FromTime has its *_from fields and offset field populated. (it should anyway)
|
||||||
|
% @todo invalid input checking
|
||||||
|
% Rules are sorted
|
||||||
|
flatten_zone(BaseFlat, Zone, Rules) ->
|
||||||
|
flatten_zone(BaseFlat, Zone, Rules, []).
|
||||||
|
|
||||||
|
flatten_zone(BaseFlat, Zone=#zone{until=Until, gmtoff=Offset}, Rules, Flats) ->
|
||||||
|
case ezic_rule:next_event(BaseFlat, Until, Offset, Rules) of
|
||||||
|
% is this enough information to compare all times corectly?
|
||||||
|
{end_until, EndTimes} ->
|
||||||
|
NewFlats= [end_flat(BaseFlat, EndTimes) | Flats],
|
||||||
|
NewFrom= make_next_flat(EndTimes),
|
||||||
|
{ok, NewFrom, NewFlats}; % base case
|
||||||
|
|
||||||
|
{end_rule, EndTimes, NextOffset} ->
|
||||||
|
NewFlats= [end_flat(BaseFlat, EndTimes) | Flats],
|
||||||
|
NewFrom= make_next_flat(EndTimes, NextOffset),
|
||||||
|
flatten_zone(NewFrom, Zone, Rules, NewFlats) % tail recursion
|
||||||
|
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -15,8 +15,6 @@ load(File) ->
|
|||||||
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).
|
|
||||||
|
|
||||||
ezic_db:wipe(),
|
ezic_db:wipe(),
|
||||||
ezic_db:init(),
|
ezic_db:init(),
|
||||||
|
|
||||||
@@ -30,6 +28,7 @@ load(File) ->
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
% INTERNAL
|
% INTERNAL
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
@@ -38,7 +37,6 @@ load(File) ->
|
|||||||
|
|
||||||
% returns a list of tzdata records for all files in folder
|
% returns a list of tzdata records for all files in folder
|
||||||
% note: assumes every file in the folder is a tzdata file
|
% note: assumes every file in the folder is a tzdata file
|
||||||
% this method does not recurse.
|
|
||||||
parse_dir(Folder) ->
|
parse_dir(Folder) ->
|
||||||
{ok, Files} = file:list_dir(Folder),
|
{ok, Files} = file:list_dir(Folder),
|
||||||
lists:flatten([ parse_file(filename:join(Folder,File)) || File <- Files ]).
|
lists:flatten([ parse_file(filename:join(Folder,File)) || File <- Files ]).
|
||||||
@@ -52,24 +50,33 @@ parse_file(File) ->
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
parse_lines(eof, _, Records) ->
|
parse_lines(eof, _, Records) ->
|
||||||
Records;
|
Records;
|
||||||
parse_lines({ok, Line}, File, Records) ->
|
parse_lines({ok, Line}, File, Records) ->
|
||||||
StrLine= clean_line(Line),
|
StrLine= clean_line(Line),
|
||||||
case length(StrLine) > 0 of
|
NewRecords= case length(StrLine) > 0 of
|
||||||
false -> parse_lines(file:read_line(File), File, Records);
|
false -> Records;
|
||||||
true -> parse_lines_2(StrLine, File, Records)
|
true -> [parse_to_record(StrLine, File, Records) | Records]
|
||||||
end.
|
end,
|
||||||
|
parse_lines(file:read_line(File), File, NewRecords).
|
||||||
|
|
||||||
|
|
||||||
parse_lines_2(Line, File, Records) ->
|
|
||||||
%?debug("Line: ~p", [Line]),
|
% the Line has data, so we parse it, build a record, and return it
|
||||||
|
parse_to_record(Line, File, Records) ->
|
||||||
[Type | Data] = string:tokens(Line, " \t"),
|
[Type | Data] = string:tokens(Line, " \t"),
|
||||||
{ok, PrevType, PrevName} = prev_rec_type(Records),
|
{ok, PrevType, PrevName} = prev_rec_type(Records),
|
||||||
{ok, Record} = build_record(Type, Data, {PrevType, PrevName}),
|
{ok, Record} = build_record(Type, Data, {PrevType, PrevName}),
|
||||||
parse_lines(file:read_line(File), File, [Record|Records]).
|
|
||||||
|
% parse_lines(file:read_line(File), File, [Record|Records]).
|
||||||
|
|
||||||
|
Record.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
% retrieves the previous record type, in case Zone continuations occur (multiline)
|
||||||
prev_rec_type([]) ->
|
prev_rec_type([]) ->
|
||||||
{ok, null, null};
|
{ok, null, null};
|
||||||
prev_rec_type([#zone{name=Name}|_]) ->
|
prev_rec_type([#zone{name=Name}|_]) ->
|
||||||
@@ -77,6 +84,9 @@ prev_rec_type([#zone{name=Name}|_]) ->
|
|||||||
prev_rec_type(List) when is_list(List) ->
|
prev_rec_type(List) when is_list(List) ->
|
||||||
{ok, void, void}.
|
{ok, void, void}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
clean_line(Line) ->
|
clean_line(Line) ->
|
||||||
Line1= string:strip(Line),
|
Line1= string:strip(Line),
|
||||||
Line2= string:strip(Line1, both, $\n),
|
Line2= string:strip(Line1, both, $\n),
|
||||||
@@ -105,3 +115,6 @@ build_record("Leap", Data,_) ->
|
|||||||
build_record(Type, Data, PT) ->
|
build_record(Type, Data, PT) ->
|
||||||
{error, {badLine, Type, Data, PT}}.
|
{error, {badLine, Type, Data, PT}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ parse_save(X) ->
|
|||||||
parse_save_rev(XRev).
|
parse_save_rev(XRev).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
parse_time("-") ->
|
parse_time("-") ->
|
||||||
#tztime{time={0,0,0}};
|
#tztime{time={0,0,0}};
|
||||||
parse_time(TS) ->
|
parse_time(TS) ->
|
||||||
|
|||||||
@@ -2,62 +2,39 @@
|
|||||||
-include("include/ezic.hrl").
|
-include("include/ezic.hrl").
|
||||||
|
|
||||||
|
|
||||||
-export([current/2, current_set/2, sort/2]).
|
-export([next_event/4]).
|
||||||
-export([from_time/1, dst_sec/1]).
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
current(_, []) ->
|
|
||||||
none;
|
|
||||||
current(Now, Rules) ->
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
% newest first
|
% determines the next zone or dst change event
|
||||||
sort(R1, R2) ->
|
% returns the latest possible times the previous period is in effect (`Until -1sec` in most cases)
|
||||||
T1= from_time(R1),
|
% Rules are sorted
|
||||||
T2= from_time(R2),
|
next_event(BaseFlat, ZoneEnd, Offset, []) ->
|
||||||
ezic_date:compare_datetimes(T2, T1).
|
{end_until, make_times(ZoneEnd, Offset)};
|
||||||
|
|
||||||
|
% BaseFlat has offset defined
|
||||||
|
% @todo ensure offset defined on BaseFlat
|
||||||
|
% Rules are sorted in ascending order (oldest first)
|
||||||
|
next_event(BaseFlat=#flatzone{utc_from=#tztime{time={{Year,_,_},_}}},
|
||||||
|
ZoneEnd, ZoneOffset, [Rule|RestRules]) ->
|
||||||
|
|
||||||
|
not_done.
|
||||||
current_set(Now, Rule=#rule{from=F, to=T}) ->
|
|
||||||
{{Y,_,_},_} = Now,
|
|
||||||
case ezic_date:date_between(Y, {F,T}) of
|
|
||||||
false -> false;
|
|
||||||
true -> current_set2(Now, Rule)
|
|
||||||
end.
|
|
||||||
|
|
||||||
|
|
||||||
current_set2(Now, #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;
|
% PRIVATE
|
||||||
choose_rule([H|_]) ->
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
H.
|
|
||||||
|
% create endtimes for a #flatzone, with no dst offset
|
||||||
|
make_times(Until, Offset) ->
|
||||||
|
make_times(Until, Offset, 0).
|
||||||
|
|
||||||
|
make_times(Until, Offset, DSTOffset) ->
|
||||||
|
ezic_date:all_times(Until, Offset, DSTOffset).
|
||||||
|
|
||||||
|
|
||||||
dst_sec(none) ->
|
|
||||||
0;
|
|
||||||
dst_sec(Rule) ->
|
|
||||||
calendar:time_to_seconds((Rule#rule.save)#tztime.time).
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
-module(ezic_zone).
|
-module(ezic_zone).
|
||||||
-include("include/ezic.hrl").
|
-include("include/ezic.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-export([
|
-export([
|
||||||
current/1
|
current/1
|
||||||
, current_as_of_utc/2
|
, current_as_of_utc/2
|
||||||
, sort/2
|
, split_by_name/2
|
||||||
, revsort/2
|
, sort_ascending/2
|
||||||
|
, sort_descending/2
|
||||||
, offset_sec/1
|
, offset_sec/1
|
||||||
]).
|
]).
|
||||||
|
|
||||||
@@ -28,8 +30,8 @@ current_as_of_utc(UTCDatetime, TzName) ->
|
|||||||
|
|
||||||
get_zone_utc(_, []) ->
|
get_zone_utc(_, []) ->
|
||||||
erlang:error(no_current);
|
erlang:error(no_current);
|
||||||
get_zone_utc(UTCDatetime, Zones) ->
|
get_zone_utc(_UTCDatetime, Zones) ->
|
||||||
SortedZones= lists:sort(fun revsort/2, Zones),
|
_SortedZones= lists:sort(fun sort_descending/2, Zones),
|
||||||
%% [CZone|_]= lists:dropwhile(fun(SZ)-> older_zone_utc(SZ, UTCDatetime) end, SortedZones),
|
%% [CZone|_]= lists:dropwhile(fun(SZ)-> older_zone_utc(SZ, UTCDatetime) end, SortedZones),
|
||||||
%% CZone.
|
%% CZone.
|
||||||
|
|
||||||
@@ -43,17 +45,29 @@ get_zone_utc(UTCDatetime, Zones) ->
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
sort(#zone{until=U1}, #zone{until=U2}) ->
|
|
||||||
|
% returns {[SimilarZone], [DifferentZone]}
|
||||||
|
% where Similar Zones are those with the same name as Zone
|
||||||
|
% and DifferentZones are all the rest
|
||||||
|
split_by_name(#zone{name=N}, Zones) ->
|
||||||
|
lists:partition(
|
||||||
|
fun(#zone{name=NI}) ->
|
||||||
|
N =:= NI
|
||||||
|
end
|
||||||
|
, Zones).
|
||||||
|
|
||||||
|
|
||||||
|
sort_descending(#zone{until=U1}, #zone{until=U2}) ->
|
||||||
ezic_date:compare_datetimes(U2, U1).
|
ezic_date:compare_datetimes(U2, U1).
|
||||||
|
|
||||||
revsort(Z1=#zone{}, Z2=#zone{}) ->
|
sort_ascending(Z1=#zone{}, Z2=#zone{}) ->
|
||||||
not sort(Z1, Z2).
|
not sort_descending(Z1, Z2).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
% @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.
|
% @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) ->
|
%% older_zone(#zone{until=Until}, UTCDatetime) ->
|
||||||
ezic_date:compare_datetimes(Until, UTCDatetime).
|
%% ezic_date:compare_datetimes(Until, UTCDatetime).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
14
test/ezic_zone_tests.erl
Normal file
14
test/ezic_zone_tests.erl
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
-module(ezic_zone_tests).
|
||||||
|
-include("include/ezic.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
split_by_name_test_() ->
|
||||||
|
AZones= [A=#zone{name=a}, #zone{name=a}, #zone{name=a}],
|
||||||
|
BZones= [B=#zone{name=b}, #zone{name=b}, #zone{name=b}],
|
||||||
|
AllZones= lists:merge(AZones, BZones),
|
||||||
|
|
||||||
|
[
|
||||||
|
?_assertEqual({AZones, BZones}, ezic_zone:split_by_name(A, AllZones))
|
||||||
|
, ?_assertEqual({BZones, AZones}, ezic_zone:split_by_name(B, AllZones))
|
||||||
|
, ?_assertEqual({[], AllZones}, ezic_zone:split_by_name(#zone{name=c}, AllZones))
|
||||||
|
].
|
||||||
@@ -4,4 +4,5 @@
|
|||||||
|
|
||||||
all() ->
|
all() ->
|
||||||
eunit:test(ezic_record),
|
eunit:test(ezic_record),
|
||||||
eunit:test(ezic_date).
|
eunit:test(ezic_date),
|
||||||
|
eunit:test(ezic_zone).
|
||||||
|
|||||||
Reference in New Issue
Block a user