Add timezone server basics with some tests
This commit is contained in:
parent
1391f38558
commit
642ef7a04c
6 changed files with 252 additions and 15 deletions
|
@ -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"}}
|
||||||
]}.
|
]}.
|
||||||
|
|
|
@ -7,6 +7,6 @@
|
||||||
kernel,
|
kernel,
|
||||||
stdlib
|
stdlib
|
||||||
]},
|
]},
|
||||||
{mod, { qdate, []}},
|
{mod, { qdate_app, []}},
|
||||||
{env, []}
|
{env, []}
|
||||||
]}.
|
]}.
|
||||||
|
|
104
src/qdate.erl
104
src/qdate.erl
|
@ -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
16
src/qdate_app.erl
Normal 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
114
src/qdate_srv.erl
Normal 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
29
src/qdate_sup.erl
Normal 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]} }.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue