Add timezone server basics with some tests

This commit is contained in:
Jesse Gumm 2013-03-25 11:52:14 -05:00
parent 1391f38558
commit 642ef7a04c
6 changed files with 252 additions and 15 deletions

View file

@ -9,5 +9,5 @@
{deps, [ {deps, [
{erlware_commons, ".*", {git, "git://github.com/erlware/erlware_commons.git", "HEAD"}}, {erlware_commons, ".*", {git, "git://github.com/erlware/erlware_commons.git", "HEAD"}},
{erlang_localtime, ".*", {git, "git://github.com/dmitryme/erlang_localtime.git", "HEAD"}} {erlang_localtime, ".*", {git, "git://github.com/choptastic/erlang_localtime.git", "HEAD"}}
]}. ]}.

View file

@ -7,6 +7,6 @@
kernel, kernel,
stdlib stdlib
]}, ]},
{mod, { qdate, []}}, {mod, { qdate_app, []}},
{env, []} {env, []}
]}. ]}.

View file

@ -21,12 +21,14 @@
%% register_format/1 %% register_format/1
%% ]). %% ]).
%% %%
%% -export([ -export([
%% set_timezone/1, set_timezone/1,
%% set_timezone/2, set_timezone/2,
%% get_timezone/0, get_timezone/0,
%% get_timezone/1 get_timezone/1,
%% ]). clear_timezone/0,
clear_timezone/1
]).
%% Exported for API compatibility with ec_date %% Exported for API compatibility with ec_date
@ -52,11 +54,14 @@
TZ -> TZ TZ -> TZ
end). end).
-define(DETERMINE_TZ, determine_timezone()).
to_string(Format) -> to_string(Format) ->
to_string(Format, now()). to_string(Format, now()).
to_string(Format, Date) -> to_string(Format, Date) ->
to_string(Format, ?DEFAULT_TZ, Date). to_string(Format, ?DETERMINE_TZ, Date).
to_string(Format, ToTZ, Date) -> to_string(Format, ToTZ, Date) ->
ec_date:format(Format,to_date(Date,ToTZ)). ec_date:format(Format,to_date(Date,ToTZ)).
@ -85,28 +90,51 @@ raw_to_date(Date = {{_,_,_},{_,_,_}}) ->
Date. Date.
to_date(RawDate) -> to_date(RawDate) ->
to_date(RawDate, ?DEFAULT_TZ). to_date(RawDate, ?DETERMINE_TZ).
to_date(RawDate, ToTZKey) when is_atom(ToTZKey) orelse is_tuple(ToTZKey) ->
case get_timezone(ToTZKey) of
undefined -> throw({timezone_key_not_found,ToTZKey});
ToTZ -> to_date(RawDate, ToTZ)
end;
to_date(RawDate, ToTZ) -> to_date(RawDate, ToTZ) ->
{RawDate2,FromTZ} = extract_timezone(RawDate), {RawDate2,FromTZ} = extract_timezone(RawDate),
Date = raw_to_date(RawDate2), Date = raw_to_date(RawDate2),
localtime:local_to_local(Date,FromTZ,ToTZ). localtime:local_to_local(Date,FromTZ,ToTZ).
set_timezone(TZ) ->
qdate_srv:set_timezone(TZ).
set_timezone(Key,TZ) ->
qdate_srv:set_timezone(Key, TZ).
get_timezone() ->
qdate_srv:get_timezone().
get_timezone(Key) ->
qdate_srv:get_timezone(Key).
clear_timezone() ->
qdate_srv:clear_timezone().
clear_timezone(Key) ->
qdate_srv:clear_timezone(Key).
extract_timezone(Unixtime) when is_integer(Unixtime) -> extract_timezone(Unixtime) when is_integer(Unixtime) ->
{Unixtime, ?DEFAULT_TZ}; {Unixtime, ?DETERMINE_TZ};
extract_timezone(DateString) when is_list(DateString) -> extract_timezone(DateString) when is_list(DateString) ->
AllTimezones = localtime:list_timezones(), AllTimezones = localtime:list_timezones(),
RevDate = lists:reverse(DateString), RevDate = lists:reverse(DateString),
extract_timezone_helper(RevDate, AllTimezones); extract_timezone_helper(RevDate, AllTimezones);
extract_timezone(Date={{_,_,_},{_,_,_}}) -> extract_timezone(Date={{_,_,_},{_,_,_}}) ->
{Date, ?DEFAULT_TZ}; {Date, ?DETERMINE_TZ};
extract_timezone(Now={_,_,_}) -> extract_timezone(Now={_,_,_}) ->
{Now, ?DEFAULT_TZ}; {Now, ?DETERMINE_TZ};
extract_timezone({MiscDate,TZ}) -> extract_timezone({MiscDate,TZ}) ->
{MiscDate,TZ}. {MiscDate,TZ}.
extract_timezone_helper(RevDate, []) -> extract_timezone_helper(RevDate, []) ->
{lists:reverse(RevDate), ?DEFAULT_TZ}; {lists:reverse(RevDate), ?DETERMINE_TZ};
extract_timezone_helper(RevDate, [TZ | TZs]) -> extract_timezone_helper(RevDate, [TZ | TZs]) ->
RevTZ = lists:reverse(TZ), RevTZ = lists:reverse(TZ),
case lists:split(length(TZ),RevDate) of case lists:split(length(TZ),RevDate) of
@ -116,6 +144,12 @@ extract_timezone_helper(RevDate, [TZ | TZs]) ->
extract_timezone_helper(RevDate, TZs) extract_timezone_helper(RevDate, TZs)
end. end.
determine_timezone() ->
case qdate_srv:get_timezone() of
undefined -> ?DEFAULT_TZ;
TZ -> TZ
end.
to_unixtime(Unixtime) when is_integer(Unixtime) -> to_unixtime(Unixtime) when is_integer(Unixtime) ->
Unixtime; Unixtime;
to_unixtime({MegaSecs,Secs,_}) -> to_unixtime({MegaSecs,Secs,_}) ->
@ -156,7 +190,36 @@ floor(N) when N < 0 ->
%% TESTS %% TESTS
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
all_test_() -> %% emulates as if a forum-type website has a Site tz, and a user-specified tz
-define(SITE_TZ,"PST").
-define(USER_TZ,"CST").
-define(SELF_TZ,"EST"). %% Self will be the pid of the current running process
-define(SITE_KEY,test_site_key).
-define(USER_KEY,test_user_key).
tz_test_() ->
{
setup,
fun start_test/0,
fun stop_test/1,
fun(SetupData) ->
{spawn,[
simple_test(),
tz_tests(SetupData)
]}
end
}.
tz_tests(_) ->
[
?_assertEqual(?SELF_TZ,begin set_timezone(?SELF_TZ),get_timezone() end),
?_assertEqual(?USER_TZ,get_timezone(?USER_KEY)),
?_assertEqual(?SITE_TZ,get_timezone(?SITE_KEY))
].
simple_test() ->
[ [
?_assertEqual(0,to_unixtime({0,0,0})), ?_assertEqual(0,to_unixtime({0,0,0})),
?_assertEqual({0,0,0},to_now(0)), ?_assertEqual({0,0,0},to_now(0)),
@ -171,3 +234,18 @@ all_test_() ->
?_assertEqual({{2013,1,1},{0,15,15}},to_date("December 31, 2012 6:15:15pm CST","GMT")) ?_assertEqual({{2013,1,1},{0,15,15}},to_date("December 31, 2012 6:15:15pm CST","GMT"))
]. ].
%%tz_char_tests(_) ->
%% qdate:set_timezone(?SELF_TZ),
start_test() ->
application:start(qdate),
qdate:set_timezone(?SITE_KEY,?SITE_TZ),
qdate:set_timezone(?USER_KEY,?USER_TZ).
stop_test(_) ->
ok.

16
src/qdate_app.erl Normal file
View file

@ -0,0 +1,16 @@
-module(qdate_app).
-behaviour(application).
%% Application callbacks
-export([start/2, stop/1]).
%% ===================================================================
%% Application callbacks
%% ===================================================================
start(_StartType, _StartArgs) ->
qdate_sup:start_link().
stop(_State) ->
ok.

114
src/qdate_srv.erl Normal file
View file

@ -0,0 +1,114 @@
-module(qdate_srv).
-behaviour(gen_server).
-define(SRV, ?MODULE).
-export([
start_link/0,
init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
code_change/3,
terminate/2
]).
-export([
set_timezone/1,
set_timezone/2,
get_timezone/0,
get_timezone/1,
clear_timezone/0,
clear_timezone/1
%% register_parser/1,
%% register_parser/2,
%%
%% deregister_parsers/0,
%% deregister_parsers/1,
%%
%% register_format/1,
%% register_format/2,
%%
%% deregister_format/0,
%% deregister_format/1
]).
%% PUBLIC API FUNCTIONS
start_link() ->
gen_server:start_link({local, ?SRV}, ?MODULE, [], []).
set_timezone(TZ) ->
set_timezone(self(),TZ).
set_timezone(Key,TZ) ->
gen_server:call(?SRV,{set_timezone,Key,TZ}).
get_timezone() ->
get_timezone(self()).
get_timezone(Key) ->
gen_server:call(?SRV,{get_timezone,Key}).
clear_timezone() ->
clear_timezone(self()).
clear_timezone(Key) ->
gen_server:call(?SRV, {clear_timezone, Key}).
%% SERVER FUNCTIONS
-record(state, {tz, parsers, formats}).
init(_) ->
State = #state{tz=dict:new(),parsers=dict:new(),formats=dict:new()},
{ok, State}.
handle_cast(_,State) ->
{noreply, State}.
handle_info({'DOWN', MonitorRef, process, Pid, _Reason}, State) ->
erlang:demonitor(MonitorRef),
NewTZ = dict:erase(Pid, State#state.tz),
NewParsers = dict:erase(Pid, State#state.parsers),
NewFormats = dict:erase(Pid, State#state.formats),
NewState = State#state{tz=NewTZ, parsers=NewParsers, formats=NewFormats},
{noreply, NewState };
handle_info(_, State) ->
{noreply, State}.
handle_call({set_timezone,Key,TZ}, _From, State) ->
monitor_if_pid(Key),
NewTZ = dict:store(Key, TZ, State#state.tz),
NewState = State#state{tz=NewTZ},
{reply, ok, NewState};
handle_call({clear_timezone,Key},_From, State) ->
NewTZ = dict:erase(Key, State#state.tz),
NewState = State#state{tz=NewTZ},
{reply, ok, NewState};
handle_call({get_timezone,Key},_From, State) ->
Reply = case dict:find(Key, State#state.tz) of
error -> undefined;
{ok, Value} -> Value
end,
{reply, Reply, State}.
%% handle_call({register_parser,Key,Parser}) ->
%% handle_call({deregister_parsers,Key}) ->
%% handle_call({register_format,Key,Format}) ->
%% handle_call({deregister_formats,Key}) ->
terminate(_Reason, _State) ->
ok.
code_change(_OldVersion, State, _Extra) ->
{ok, State}.
%% PRIVATE TOOLS
monitor_if_pid(Key) when is_pid(Key) ->
erlang:monitor(process,Key);
monitor_if_pid(_) ->
do_nothing.

29
src/qdate_sup.erl Normal file
View file

@ -0,0 +1,29 @@
-module(qdate_sup).
-behaviour(supervisor).
%% API
-export([start_link/0]).
%% Supervisor callbacks
-export([init/1]).
%% Helper macro for declaring children of supervisor
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
%% ===================================================================
%% API functions
%% ===================================================================
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
%% ===================================================================
%% Supervisor callbacks
%% ===================================================================
init([]) ->
Server = ?CHILD(qdate_srv, worker),
{ok, { {one_for_one, 5, 10}, [Server]} }.