From 642ef7a04c41fac2cb7eecab7902933ce950649d Mon Sep 17 00:00:00 2001 From: Jesse Gumm Date: Mon, 25 Mar 2013 11:52:14 -0500 Subject: [PATCH] Add timezone server basics with some tests --- rebar.config | 2 +- src/qdate.app.src | 2 +- src/qdate.erl | 104 ++++++++++++++++++++++++++++++++++++------ src/qdate_app.erl | 16 +++++++ src/qdate_srv.erl | 114 ++++++++++++++++++++++++++++++++++++++++++++++ src/qdate_sup.erl | 29 ++++++++++++ 6 files changed, 252 insertions(+), 15 deletions(-) create mode 100644 src/qdate_app.erl create mode 100644 src/qdate_srv.erl create mode 100644 src/qdate_sup.erl diff --git a/rebar.config b/rebar.config index bfda3d3..b0fb93e 100644 --- a/rebar.config +++ b/rebar.config @@ -9,5 +9,5 @@ {deps, [ {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"}} ]}. diff --git a/src/qdate.app.src b/src/qdate.app.src index 270bbb8..7d67e17 100644 --- a/src/qdate.app.src +++ b/src/qdate.app.src @@ -7,6 +7,6 @@ kernel, stdlib ]}, - {mod, { qdate, []}}, + {mod, { qdate_app, []}}, {env, []} ]}. diff --git a/src/qdate.erl b/src/qdate.erl index 9fe68a2..aeba040 100644 --- a/src/qdate.erl +++ b/src/qdate.erl @@ -21,12 +21,14 @@ %% register_format/1 %% ]). %% -%% -export([ -%% set_timezone/1, -%% set_timezone/2, -%% get_timezone/0, -%% get_timezone/1 -%% ]). +-export([ + set_timezone/1, + set_timezone/2, + get_timezone/0, + get_timezone/1, + clear_timezone/0, + clear_timezone/1 +]). %% Exported for API compatibility with ec_date @@ -52,11 +54,14 @@ TZ -> TZ end). +-define(DETERMINE_TZ, determine_timezone()). + + to_string(Format) -> to_string(Format, now()). to_string(Format, Date) -> - to_string(Format, ?DEFAULT_TZ, Date). + to_string(Format, ?DETERMINE_TZ, Date). to_string(Format, ToTZ, Date) -> ec_date:format(Format,to_date(Date,ToTZ)). @@ -85,28 +90,51 @@ raw_to_date(Date = {{_,_,_},{_,_,_}}) -> Date. 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) -> {RawDate2,FromTZ} = extract_timezone(RawDate), Date = raw_to_date(RawDate2), 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) -> - {Unixtime, ?DEFAULT_TZ}; + {Unixtime, ?DETERMINE_TZ}; extract_timezone(DateString) when is_list(DateString) -> AllTimezones = localtime:list_timezones(), RevDate = lists:reverse(DateString), extract_timezone_helper(RevDate, AllTimezones); extract_timezone(Date={{_,_,_},{_,_,_}}) -> - {Date, ?DEFAULT_TZ}; + {Date, ?DETERMINE_TZ}; extract_timezone(Now={_,_,_}) -> - {Now, ?DEFAULT_TZ}; + {Now, ?DETERMINE_TZ}; extract_timezone({MiscDate,TZ}) -> {MiscDate,TZ}. extract_timezone_helper(RevDate, []) -> - {lists:reverse(RevDate), ?DEFAULT_TZ}; + {lists:reverse(RevDate), ?DETERMINE_TZ}; extract_timezone_helper(RevDate, [TZ | TZs]) -> RevTZ = lists:reverse(TZ), case lists:split(length(TZ),RevDate) of @@ -116,6 +144,12 @@ extract_timezone_helper(RevDate, [TZ | TZs]) -> extract_timezone_helper(RevDate, TZs) end. +determine_timezone() -> + case qdate_srv:get_timezone() of + undefined -> ?DEFAULT_TZ; + TZ -> TZ + end. + to_unixtime(Unixtime) when is_integer(Unixtime) -> Unixtime; to_unixtime({MegaSecs,Secs,_}) -> @@ -156,7 +190,36 @@ floor(N) when N < 0 -> %% TESTS -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,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")) ]. +%%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. diff --git a/src/qdate_app.erl b/src/qdate_app.erl new file mode 100644 index 0000000..fffad9a --- /dev/null +++ b/src/qdate_app.erl @@ -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. diff --git a/src/qdate_srv.erl b/src/qdate_srv.erl new file mode 100644 index 0000000..dcb54e2 --- /dev/null +++ b/src/qdate_srv.erl @@ -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. diff --git a/src/qdate_sup.erl b/src/qdate_sup.erl new file mode 100644 index 0000000..c9ddb3e --- /dev/null +++ b/src/qdate_sup.erl @@ -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]} }. +