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:
aj
2010-11-03 15:06:51 -07:00
parent 3c965fe7b6
commit 188f31d543
6 changed files with 276 additions and 28 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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,33 +102,99 @@ 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.

View 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"))
].

View File

@@ -2,4 +2,4 @@
-export([start/0]).
start() ->
void.
ezic_record:test().