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
|
||||
MNESIA_DIR = db
|
||||
RUN_INIT =
|
||||
DEBUG =
|
||||
OPTIONS =
|
||||
DEBUG = +debug_info -Ddebug
|
||||
TEST =
|
||||
|
||||
all : clean compile
|
||||
|
||||
@@ -10,9 +10,9 @@ nowarn : ERLC_WARNINGS = -W0
|
||||
nowarn : clean compile
|
||||
|
||||
test : ERLC_WARNINGS = -W0
|
||||
test : OPTIONS := $(OPTIONS) -DTEST
|
||||
test : TEST := -DTEST
|
||||
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
|
||||
|
||||
compile :
|
||||
@@ -26,7 +26,5 @@ clean :
|
||||
run :
|
||||
erl -pa ebin -mnesia dir $(MNESIA_DIR) $(RUN_INIT)
|
||||
|
||||
|
||||
debug : DEBUG = +debug_info
|
||||
debug : RUN_INIT = -s debugger start
|
||||
debug : all
|
||||
build : DEBUG =
|
||||
build : all
|
||||
@@ -8,5 +8,22 @@
|
||||
-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.
|
||||
-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) ->
|
||||
?debug("Line: ~p", [Line]),
|
||||
[Type | Data] = string:tokens(Line, " \t"),
|
||||
{ok, PrevType, PrevName} = prev_rec_type(Records),
|
||||
{ok, Record} = build_record(Type, Data, {PrevType, PrevName}),
|
||||
@@ -68,10 +69,13 @@ prev_rec_type(List) when is_list(List) ->
|
||||
clean_line(Line) ->
|
||||
Line1= string:strip(Line),
|
||||
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
|
||||
0 -> Line2;
|
||||
_ -> string:sub_string(Line2, 1, CPos-1)
|
||||
0 -> FinalLine;
|
||||
_ -> string:sub_string(FinalLine, 1, CPos-1)
|
||||
end.
|
||||
|
||||
|
||||
@@ -84,6 +88,8 @@ build_record("Link", Data,_) ->
|
||||
ezic_record:link(Data);
|
||||
build_record(GmtOff, Data, {"Zone", PrevName}) ->
|
||||
ezic_record:zone([PrevName,GmtOff|Data]);
|
||||
build_record("Leap", Data,_) ->
|
||||
ezic_record:leap(Data);
|
||||
|
||||
build_record(Type, Data, PT) ->
|
||||
{error, {badLine, Type, Data, PT}}.
|
||||
@@ -92,4 +98,4 @@ build_record(Type, Data, PT) ->
|
||||
% converts tzdata records to flattened records
|
||||
% returns list of #flatzone{}
|
||||
flatten(Records) when is_list(Records) ->
|
||||
void.
|
||||
Records.
|
||||
|
||||
@@ -1,30 +1,95 @@
|
||||
-module(ezic_record).
|
||||
-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{
|
||||
name=Name, from=From, to=To, type=Type,
|
||||
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),
|
||||
Format= convert_format(FormatS),
|
||||
|
||||
{ok, #zone{
|
||||
name=Name, gmtoff=GmtOff, rules=Rules,
|
||||
format=Format, until=Until
|
||||
}}.
|
||||
|
||||
|
||||
link([From,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([]) ->
|
||||
current;
|
||||
parse_until([YS]) ->
|
||||
@@ -37,32 +102,98 @@ parse_until([YS,MS]) ->
|
||||
parse_until([YS,MS,DS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
M=month_to_num(MS),
|
||||
D= list_to_integer(DS),
|
||||
D= parse_on(DS),
|
||||
{Y,M,D};
|
||||
% @todo broken: time flags must be accounted for
|
||||
parse_until([YS,MS,DS,TS]) ->
|
||||
Y= list_to_integer(YS),
|
||||
M=month_to_num(MS),
|
||||
D= list_to_integer(DS),
|
||||
TimeTokens=string:tokens(TS, ":"),
|
||||
{{Y,M,D},parse_time(TimeTokens)}.
|
||||
D= parse_on(DS),
|
||||
{{Y,M,D},parse_time(TS)}.
|
||||
|
||||
|
||||
|
||||
parse_at(X) ->
|
||||
parse_time(X).
|
||||
|
||||
|
||||
|
||||
parse_save("-") ->
|
||||
parse_time("-");
|
||||
parse_save(X) ->
|
||||
XRev=lists:reverse(X),
|
||||
parse_save_rev(XRev).
|
||||
|
||||
|
||||
parse_time("-") ->
|
||||
{0,0,0};
|
||||
parse_time([HS]) ->
|
||||
#tztime{time={0,0,0}};
|
||||
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,0,0};
|
||||
parse_time([HS,MS]) ->
|
||||
parse_abs_time_2([HS,MS]) ->
|
||||
H= list_to_integer(HS),
|
||||
M= list_to_integer(MS),
|
||||
{H,M,0};
|
||||
parse_time([HS,MS,SS]) ->
|
||||
parse_abs_time_2([HS,MS,SS]) ->
|
||||
H= list_to_integer(HS),
|
||||
M= list_to_integer(MS),
|
||||
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("Oct") -> 10;
|
||||
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]).
|
||||
|
||||
start() ->
|
||||
void.
|
||||
ezic_record:test().
|
||||
|
||||
Reference in New Issue
Block a user