parsing tzdata files appears to be functionally complete.
calling ezic:load("/path/to/tzdata_folder") returns a list of records without erroring-out.
The content may not be accurate; @todo create accuracy tests (idea: use system's zic/localtime/tzselect/etc.).
This commit is contained in:
14
Makefile
14
Makefile
@@ -1,8 +1,8 @@
|
|||||||
ERLC_WARNINGS = -W1
|
ERLC_WARNINGS = -W1
|
||||||
MNESIA_DIR = db
|
MNESIA_DIR = db
|
||||||
RUN_INIT =
|
RUN_INIT =
|
||||||
DEBUG =
|
DEBUG = +debug_info -Ddebug
|
||||||
OPTIONS =
|
TEST =
|
||||||
|
|
||||||
all : clean compile
|
all : clean compile
|
||||||
|
|
||||||
@@ -10,9 +10,9 @@ nowarn : ERLC_WARNINGS = -W0
|
|||||||
nowarn : clean compile
|
nowarn : clean compile
|
||||||
|
|
||||||
test : ERLC_WARNINGS = -W0
|
test : ERLC_WARNINGS = -W0
|
||||||
test : OPTIONS := $(OPTIONS) -DTEST
|
test : TEST := -DTEST
|
||||||
test : all
|
test : all
|
||||||
erlc $(ERLC_WARNINGS) $(OPTIONS) -o ./ebin ./tests/*.erl
|
erlc $(ERLC_WARNINGS) $(TEST) -o ./ebin ./tests/*.erl
|
||||||
erl -noshell -pa ebin -s test_master start -s erlang halt
|
erl -noshell -pa ebin -s test_master start -s erlang halt
|
||||||
|
|
||||||
compile :
|
compile :
|
||||||
@@ -26,7 +26,5 @@ clean :
|
|||||||
run :
|
run :
|
||||||
erl -pa ebin -mnesia dir $(MNESIA_DIR) $(RUN_INIT)
|
erl -pa ebin -mnesia dir $(MNESIA_DIR) $(RUN_INIT)
|
||||||
|
|
||||||
|
build : DEBUG =
|
||||||
debug : DEBUG = +debug_info
|
build : all
|
||||||
debug : RUN_INIT = -s debugger start
|
|
||||||
debug : all
|
|
||||||
@@ -8,5 +8,22 @@
|
|||||||
-record(link, {from, to}).
|
-record(link, {from, to}).
|
||||||
|
|
||||||
|
|
||||||
|
-record(leap, {datetime, corr, rs}).
|
||||||
|
|
||||||
|
|
||||||
|
-record(tztime, {time, flag}).
|
||||||
|
-record(tzon, {day, filter}).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
% 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, tzrule, from, to, gmtoff}).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-ifdef(debug).
|
||||||
|
-define(debug(F,X), io:format("{~p:~p} - " ++ F ++ "~n", [?MODULE, ?LINE] ++ X)).
|
||||||
|
-else.
|
||||||
|
-define(debug(F,X), void).
|
||||||
|
-endif.
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ parse_lines({ok, Line}, File, Records) ->
|
|||||||
|
|
||||||
|
|
||||||
parse_lines_2(Line, File, Records) ->
|
parse_lines_2(Line, File, Records) ->
|
||||||
|
?debug("Line: ~p", [Line]),
|
||||||
[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}),
|
||||||
@@ -68,10 +69,13 @@ prev_rec_type(List) when is_list(List) ->
|
|||||||
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),
|
||||||
CPos= string:chr(Line2, $#),
|
Line3= string:strip(Line2, both, $\t),
|
||||||
|
|
||||||
|
FinalLine=Line3,
|
||||||
|
CPos= string:chr(FinalLine, $#),
|
||||||
case CPos of
|
case CPos of
|
||||||
0 -> Line2;
|
0 -> FinalLine;
|
||||||
_ -> string:sub_string(Line2, 1, CPos-1)
|
_ -> string:sub_string(FinalLine, 1, CPos-1)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
@@ -84,6 +88,8 @@ build_record("Link", Data,_) ->
|
|||||||
ezic_record:link(Data);
|
ezic_record:link(Data);
|
||||||
build_record(GmtOff, Data, {"Zone", PrevName}) ->
|
build_record(GmtOff, Data, {"Zone", PrevName}) ->
|
||||||
ezic_record:zone([PrevName,GmtOff|Data]);
|
ezic_record:zone([PrevName,GmtOff|Data]);
|
||||||
|
build_record("Leap", Data,_) ->
|
||||||
|
ezic_record:leap(Data);
|
||||||
|
|
||||||
build_record(Type, Data, PT) ->
|
build_record(Type, Data, PT) ->
|
||||||
{error, {badLine, Type, Data, PT}}.
|
{error, {badLine, Type, Data, PT}}.
|
||||||
@@ -92,4 +98,4 @@ build_record(Type, Data, PT) ->
|
|||||||
% converts tzdata records to flattened records
|
% converts tzdata records to flattened records
|
||||||
% returns list of #flatzone{}
|
% returns list of #flatzone{}
|
||||||
flatten(Records) when is_list(Records) ->
|
flatten(Records) when is_list(Records) ->
|
||||||
void.
|
Records.
|
||||||
|
|||||||
@@ -1,30 +1,95 @@
|
|||||||
-module(ezic_record).
|
-module(ezic_record).
|
||||||
-include("include/ezic.hrl").
|
-include("include/ezic.hrl").
|
||||||
-export([rule/1,zone/1,link/1]).
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
|
||||||
rule([Name,From,To,Type,In,On,At,Save,Letters]) ->
|
-ifdef(TEST).
|
||||||
|
-compile([export_all]).
|
||||||
|
-endif.
|
||||||
|
|
||||||
|
|
||||||
|
-export([rule/1,zone/1,link/1,leap/1]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
rule([Name,FromS,ToS,Type,InS,OnS,AtS,SaveS,Letters]) ->
|
||||||
|
From= parse_from(FromS),
|
||||||
|
To= parse_to(ToS),
|
||||||
|
% @todo handle year types
|
||||||
|
In= month_to_num(InS),
|
||||||
|
On= parse_on(OnS),
|
||||||
|
At=parse_at(AtS),
|
||||||
|
Save=parse_save(SaveS),
|
||||||
|
|
||||||
{ok, #rule{
|
{ok, #rule{
|
||||||
name=Name, from=From, to=To, type=Type,
|
name=Name, from=From, to=To, type=Type,
|
||||||
in=In, on=On, at=At, save=Save, letters=Letters
|
in=In, on=On, at=At, save=Save, letters=Letters
|
||||||
}}.
|
}}.
|
||||||
|
|
||||||
zone([Name,GmtOff,Rules,Format | UntilTokens]) ->
|
|
||||||
|
zone([Name,GmtOffS,Rules,FormatS | UntilTokens]) ->
|
||||||
|
GmtOff=parse_time(GmtOffS),
|
||||||
Until = parse_until(UntilTokens),
|
Until = parse_until(UntilTokens),
|
||||||
|
Format= convert_format(FormatS),
|
||||||
|
|
||||||
{ok, #zone{
|
{ok, #zone{
|
||||||
name=Name, gmtoff=GmtOff, rules=Rules,
|
name=Name, gmtoff=GmtOff, rules=Rules,
|
||||||
format=Format, until=Until
|
format=Format, until=Until
|
||||||
}}.
|
}}.
|
||||||
|
|
||||||
|
|
||||||
link([From,To]) ->
|
link([From,To]) ->
|
||||||
{ok, #link{from=From, to=To}}.
|
{ok, #link{from=From, to=To}}.
|
||||||
|
|
||||||
|
|
||||||
|
leap([YearS, MonthS, DayS, TimeS, Corr, RS]) ->
|
||||||
|
Y=list_to_integer(YearS),
|
||||||
|
M=month_to_num(MonthS),
|
||||||
|
D=list_to_integer(DayS),
|
||||||
|
Time=parse_abs_time(TimeS),
|
||||||
|
{ok, #leap{datetime={{Y,M,D}, Time}}}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
% INTERNAL
|
% PRIVATE
|
||||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
|
||||||
|
parse_from(X) when X=:="maximum";X=:="max" ->
|
||||||
|
maximum;
|
||||||
|
parse_from(X) when X=:="minimum";X=:="min" ->
|
||||||
|
minimum;
|
||||||
|
parse_from(Year) ->
|
||||||
|
list_to_integer(Year).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
parse_to("only") ->
|
||||||
|
only;
|
||||||
|
parse_to(Other) ->
|
||||||
|
parse_from(Other).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
parse_on(X=[$l,$a,$s,$t|Day]) ->
|
||||||
|
case is_day(Day) of
|
||||||
|
true -> list_to_atom(X);
|
||||||
|
false -> erlang:error(badday,X)
|
||||||
|
end;
|
||||||
|
parse_on(X=[D,A,Y, Sign,$= | IntS]) ->
|
||||||
|
Int=list_to_integer(IntS),
|
||||||
|
Day=[D,A,Y],
|
||||||
|
{ok, FSign} =parse_on_sign(Sign),
|
||||||
|
|
||||||
|
case is_day(Day) of
|
||||||
|
true -> #tzon{day=list_to_atom(string:to_lower(Day)), filter={FSign, Int}};
|
||||||
|
false -> erlang:error(badday, X)
|
||||||
|
end;
|
||||||
|
parse_on(X) ->
|
||||||
|
list_to_integer(X).
|
||||||
|
|
||||||
|
|
||||||
parse_until([]) ->
|
parse_until([]) ->
|
||||||
current;
|
current;
|
||||||
parse_until([YS]) ->
|
parse_until([YS]) ->
|
||||||
@@ -37,32 +102,98 @@ parse_until([YS,MS]) ->
|
|||||||
parse_until([YS,MS,DS]) ->
|
parse_until([YS,MS,DS]) ->
|
||||||
Y= list_to_integer(YS),
|
Y= list_to_integer(YS),
|
||||||
M=month_to_num(MS),
|
M=month_to_num(MS),
|
||||||
D= list_to_integer(DS),
|
D= parse_on(DS),
|
||||||
{Y,M,D};
|
{Y,M,D};
|
||||||
% @todo broken: time flags must be accounted for
|
|
||||||
parse_until([YS,MS,DS,TS]) ->
|
parse_until([YS,MS,DS,TS]) ->
|
||||||
Y= list_to_integer(YS),
|
Y= list_to_integer(YS),
|
||||||
M=month_to_num(MS),
|
M=month_to_num(MS),
|
||||||
D= list_to_integer(DS),
|
D= parse_on(DS),
|
||||||
TimeTokens=string:tokens(TS, ":"),
|
{{Y,M,D},parse_time(TS)}.
|
||||||
{{Y,M,D},parse_time(TimeTokens)}.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
parse_at(X) ->
|
||||||
|
parse_time(X).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
parse_save("-") ->
|
||||||
|
parse_time("-");
|
||||||
|
parse_save(X) ->
|
||||||
|
XRev=lists:reverse(X),
|
||||||
|
parse_save_rev(XRev).
|
||||||
|
|
||||||
|
|
||||||
parse_time("-") ->
|
parse_time("-") ->
|
||||||
{0,0,0};
|
#tztime{time={0,0,0}};
|
||||||
parse_time([HS]) ->
|
parse_time(TS) ->
|
||||||
|
parse_time_rev(lists:reverse(TS)).
|
||||||
|
|
||||||
|
|
||||||
|
parse_abs_time(T) ->
|
||||||
|
Tokez= string:tokens(T, ":"),
|
||||||
|
parse_abs_time_2(Tokez).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
convert_format(Format) ->
|
||||||
|
convert_format(Format, string:str(Format, "%s")).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
% INTERNAL
|
||||||
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||||
|
|
||||||
|
|
||||||
|
parse_on_sign($>) -> {ok, geq};
|
||||||
|
parse_on_sign($<) -> {ok, leq}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
parse_time_rev([F|RTime]) when F =:= $s; F =:= $w; F =:= $u ->
|
||||||
|
Time=lists:reverse(RTime),
|
||||||
|
#tztime{time=parse_abs_time(Time), flag=list_to_atom([F])};
|
||||||
|
parse_time_rev(X=[F|_]) when F-48 < 0; F-48 > 9 ->
|
||||||
|
erlang:error(badtime, X);
|
||||||
|
parse_time_rev(RTime) ->
|
||||||
|
Time=lists:reverse(RTime),
|
||||||
|
#tztime{time=parse_abs_time(Time)}.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
parse_abs_time_2([HS]) ->
|
||||||
H= list_to_integer(HS),
|
H= list_to_integer(HS),
|
||||||
{H,0,0};
|
{H,0,0};
|
||||||
parse_time([HS,MS]) ->
|
parse_abs_time_2([HS,MS]) ->
|
||||||
H= list_to_integer(HS),
|
H= list_to_integer(HS),
|
||||||
M= list_to_integer(MS),
|
M= list_to_integer(MS),
|
||||||
{H,M,0};
|
{H,M,0};
|
||||||
parse_time([HS,MS,SS]) ->
|
parse_abs_time_2([HS,MS,SS]) ->
|
||||||
H= list_to_integer(HS),
|
H= list_to_integer(HS),
|
||||||
M= list_to_integer(MS),
|
M= list_to_integer(MS),
|
||||||
S= list_to_integer(SS),
|
S= list_to_integer(SS),
|
||||||
{H,M,S}.
|
{H,M,S};
|
||||||
|
parse_abs_time_2(X) ->
|
||||||
|
erlang:error(badtime, X).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
parse_save_rev(X=[F|_]) when F-48 < 0; F-48 > 9 ->
|
||||||
|
erlang:error(badsave, lists:reverse(X));
|
||||||
|
parse_save_rev(X) ->
|
||||||
|
parse_time(lists:reverse(X)).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
convert_format(Format, 0) ->
|
||||||
|
Format;
|
||||||
|
convert_format(Format, Pos) ->
|
||||||
|
First=string:substr(Format, 1, Pos-1),
|
||||||
|
Last=string:substr(Format, Pos+2),
|
||||||
|
string:concat(First, [$~, $s|Last]).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -79,4 +210,13 @@ month_to_num("Aug") -> 8;
|
|||||||
month_to_num("Sep") -> 9;
|
month_to_num("Sep") -> 9;
|
||||||
month_to_num("Oct") -> 10;
|
month_to_num("Oct") -> 10;
|
||||||
month_to_num("Nov") -> 11;
|
month_to_num("Nov") -> 11;
|
||||||
month_to_num("Dec") -> 12.
|
month_to_num("Dec") -> 12;
|
||||||
|
month_to_num(X) -> erlang:error(badMonth, X).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
is_day(X) when X=:="Mon";X=:="Tue";X=:="Wed";X=:="Thu";X=:="Fri";X=:="Sat";X=:="Sun" ->
|
||||||
|
true;
|
||||||
|
is_day(_) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
|||||||
87
tests/ezic_record_tests.erl
Normal file
87
tests/ezic_record_tests.erl
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
-module(ezic_record_tests).
|
||||||
|
-include("include/ezic.hrl").
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
|
||||||
|
parse_fromto_test_() ->
|
||||||
|
[
|
||||||
|
?_assertEqual(minimum, ezic_record:parse_from("minimum"))
|
||||||
|
, ?_assertEqual(minimum, ezic_record:parse_to("minimum"))
|
||||||
|
|
||||||
|
, ?_assertEqual(maximum, ezic_record:parse_from("maximum"))
|
||||||
|
, ?_assertEqual(maximum, ezic_record:parse_to("maximum"))
|
||||||
|
|
||||||
|
, ?_assertError(badarg, ezic_record:parse_from("only"))
|
||||||
|
, ?_assertEqual(only, ezic_record:parse_to("only"))
|
||||||
|
|
||||||
|
, ?_assertEqual(1978, ezic_record:parse_from("1978"))
|
||||||
|
, ?_assertEqual(1978, ezic_record:parse_to("1978"))
|
||||||
|
|
||||||
|
, ?_assertError(badarg, ezic_record:parse_from("asdf"))
|
||||||
|
, ?_assertError(badarg, ezic_record:parse_to("asdf"))
|
||||||
|
].
|
||||||
|
|
||||||
|
|
||||||
|
parse_time_test_() ->
|
||||||
|
[
|
||||||
|
?_assertEqual(#tztime{time={0,0,0}}, ezic_record:parse_time("-"))
|
||||||
|
, ?_assertEqual(#tztime{time={2,0,0}}, ezic_record:parse_time("2:00"))
|
||||||
|
, ?_assertEqual(#tztime{time={15,0,0}}, ezic_record:parse_time("15:00"))
|
||||||
|
, ?_assertEqual(#tztime{time={1,28,14}}, ezic_record:parse_time("1:28:14"))
|
||||||
|
, ?_assertEqual(#tztime{time={1,28,14}, flag=u}, ezic_record:parse_time("1:28:14u"))
|
||||||
|
, ?_assertEqual(#tztime{time={1,28,14}, flag=w}, ezic_record:parse_time("1:28:14w"))
|
||||||
|
, ?_assertEqual(#tztime{time={1,28,14}, flag=s}, ezic_record:parse_time("1:28:14s"))
|
||||||
|
|
||||||
|
, ?_assertError(badtime, ezic_record:parse_time("1:28:14wtf?"))
|
||||||
|
, ?_assertError(badtime, ezic_record:parse_time("1:28:14:00"))
|
||||||
|
].
|
||||||
|
|
||||||
|
|
||||||
|
parse_at_test_() ->
|
||||||
|
[
|
||||||
|
?_assertEqual(#tztime{time={0,0,0}}, ezic_record:parse_at("-"))
|
||||||
|
, ?_assertEqual(#tztime{time={2,0,0}}, ezic_record:parse_at("2:00"))
|
||||||
|
, ?_assertEqual(#tztime{time={15,0,0}}, ezic_record:parse_at("15:00"))
|
||||||
|
, ?_assertEqual(#tztime{time={1,28,14}}, ezic_record:parse_at("1:28:14"))
|
||||||
|
, ?_assertEqual(#tztime{time={1,28,14}, flag=u}, ezic_record:parse_at("1:28:14u"))
|
||||||
|
, ?_assertEqual(#tztime{time={1,28,14}, flag=w}, ezic_record:parse_at("1:28:14w"))
|
||||||
|
, ?_assertEqual(#tztime{time={1,28,14}, flag=s}, ezic_record:parse_at("1:28:14s"))
|
||||||
|
, ?_assertError(badtime, ezic_record:parse_at("1:28:14wtf?"))
|
||||||
|
].
|
||||||
|
|
||||||
|
|
||||||
|
parse_save_test_() ->
|
||||||
|
[
|
||||||
|
?_assertEqual(#tztime{time={0,0,0}}, ezic_record:parse_save("-"))
|
||||||
|
, ?_assertEqual(#tztime{time={2,0,0}}, ezic_record:parse_save("2:00"))
|
||||||
|
, ?_assertEqual(#tztime{time={15,0,0}}, ezic_record:parse_save("15:00"))
|
||||||
|
, ?_assertEqual(#tztime{time={1,28,14}}, ezic_record:parse_save("1:28:14"))
|
||||||
|
, ?_assertError(badsave, ezic_record:parse_save("1:28:14u"))
|
||||||
|
, ?_assertError(badsave, ezic_record:parse_save("1:28:14w"))
|
||||||
|
, ?_assertError(badsave, ezic_record:parse_save("1:28:14s"))
|
||||||
|
, ?_assertError(badsave, ezic_record:parse_save("1:28:14wtf?"))
|
||||||
|
].
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
parse_on_test_() ->
|
||||||
|
[
|
||||||
|
?_assertEqual(5, ezic_record:parse_on("5"))
|
||||||
|
|
||||||
|
, ?_assertEqual(lastSun, ezic_record:parse_on("lastSun"))
|
||||||
|
, ?_assertEqual(lastMon, ezic_record:parse_on("lastMon"))
|
||||||
|
, ?_assertError(badday, ezic_record:parse_on("lastFoo"))
|
||||||
|
|
||||||
|
, ?_assertEqual(#tzon{day=sun, filter={geq, 8}}, ezic_record:parse_on("Sun>=8"))
|
||||||
|
, ?_assertEqual(#tzon{day=sun, filter={leq, 25}}, ezic_record:parse_on("Sun<=25"))
|
||||||
|
, ?_assertError(badday, ezic_record:parse_on("Foo<=25"))
|
||||||
|
].
|
||||||
|
|
||||||
|
|
||||||
|
convert_format_test_() ->
|
||||||
|
[
|
||||||
|
?_assertEqual("POS", ezic_record:convert_format("POS"))
|
||||||
|
, ?_assertEqual("P~sS", ezic_record:convert_format("P%sS"))
|
||||||
|
].
|
||||||
@@ -2,4 +2,4 @@
|
|||||||
-export([start/0]).
|
-export([start/0]).
|
||||||
|
|
||||||
start() ->
|
start() ->
|
||||||
void.
|
ezic_record:test().
|
||||||
|
|||||||
Reference in New Issue
Block a user