2011-05-14 15:20:12 +02:00
|
|
|
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
|
|
|
|
%%
|
|
|
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
%% purpose with or without fee is hereby granted, provided that the above
|
|
|
|
%% copyright notice and this permission notice appear in all copies.
|
|
|
|
%%
|
|
|
|
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
|
|
-module(cowboy_clock).
|
|
|
|
-behaviour(gen_server).
|
|
|
|
|
|
|
|
-export([start_link/0, stop/0, rfc1123/0]). %% API.
|
|
|
|
-export([init/1, handle_call/3, handle_cast/2,
|
|
|
|
handle_info/2, terminate/2, code_change/3]). %% gen_server.
|
|
|
|
|
|
|
|
%% @todo Use calendar types whenever they get exported.
|
|
|
|
-type year() :: non_neg_integer().
|
|
|
|
-type month() :: 1..12.
|
|
|
|
-type day() :: 1..31.
|
|
|
|
-type hour() :: 0..23.
|
|
|
|
-type minute() :: 0..59.
|
|
|
|
-type second() :: 0..59.
|
|
|
|
-type daynum() :: 1..7.
|
|
|
|
|
|
|
|
-type date() :: {year(), month(), day()}.
|
|
|
|
-type time() :: {hour(), minute(), second()}.
|
|
|
|
|
|
|
|
-type datetime() :: {date(), time()}.
|
|
|
|
|
|
|
|
-record(state, {
|
|
|
|
universaltime = undefined :: undefined | datetime(),
|
|
|
|
rfc1123 = <<>> :: binary(),
|
|
|
|
tref = undefined :: undefined | timer:tref()
|
|
|
|
}).
|
|
|
|
|
|
|
|
-define(SERVER, ?MODULE).
|
|
|
|
-define(TABLE, ?MODULE).
|
|
|
|
|
|
|
|
-include_lib("eunit/include/eunit.hrl").
|
|
|
|
|
|
|
|
%% API.
|
|
|
|
|
2011-05-25 23:02:40 +02:00
|
|
|
-spec start_link() -> {ok, pid()}.
|
2011-05-14 15:20:12 +02:00
|
|
|
start_link() ->
|
|
|
|
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
|
|
|
|
|
|
|
|
-spec stop() -> stopped.
|
|
|
|
stop() ->
|
|
|
|
gen_server:call(?SERVER, stop).
|
|
|
|
|
|
|
|
-spec rfc1123() -> binary().
|
|
|
|
rfc1123() ->
|
|
|
|
ets:lookup_element(?TABLE, rfc1123, 2).
|
|
|
|
|
|
|
|
%% gen_server.
|
|
|
|
|
2011-06-27 23:36:17 +02:00
|
|
|
-spec init([]) -> {ok, #state{}}.
|
2011-05-14 15:20:12 +02:00
|
|
|
init([]) ->
|
|
|
|
?TABLE = ets:new(?TABLE, [set, protected,
|
|
|
|
named_table, {read_concurrency, true}]),
|
|
|
|
T = erlang:universaltime(),
|
2011-06-07 17:26:53 +02:00
|
|
|
B = update_rfc1123(<<>>, undefined, T),
|
2011-05-14 18:50:43 +02:00
|
|
|
{ok, TRef} = timer:send_interval(1000, update),
|
2011-05-14 15:20:12 +02:00
|
|
|
ets:insert(?TABLE, {rfc1123, B}),
|
|
|
|
{ok, #state{universaltime=T, rfc1123=B, tref=TRef}}.
|
|
|
|
|
2011-06-27 23:36:17 +02:00
|
|
|
-spec handle_call(_, _, State)
|
|
|
|
-> {reply, ignored, State} | {stop, normal, stopped, State}.
|
2011-05-14 15:20:12 +02:00
|
|
|
handle_call(stop, _From, State=#state{tref=TRef}) ->
|
|
|
|
{ok, cancel} = timer:cancel(TRef),
|
|
|
|
{stop, normal, stopped, State};
|
|
|
|
handle_call(_Request, _From, State) ->
|
|
|
|
{reply, ignored, State}.
|
|
|
|
|
2011-06-27 23:36:17 +02:00
|
|
|
-spec handle_cast(_, State) -> {noreply, State}.
|
2011-05-14 15:20:12 +02:00
|
|
|
handle_cast(_Msg, State) ->
|
|
|
|
{noreply, State}.
|
|
|
|
|
2011-06-27 23:36:17 +02:00
|
|
|
-spec handle_info(_, State) -> {noreply, State}.
|
2011-05-14 15:20:12 +02:00
|
|
|
handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef}) ->
|
|
|
|
T = erlang:universaltime(),
|
2011-06-07 17:26:53 +02:00
|
|
|
B2 = update_rfc1123(B1, Prev, T),
|
2011-05-14 15:20:12 +02:00
|
|
|
ets:insert(?TABLE, {rfc1123, B2}),
|
|
|
|
{noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}};
|
|
|
|
handle_info(_Info, State) ->
|
|
|
|
{noreply, State}.
|
|
|
|
|
2011-06-27 23:36:17 +02:00
|
|
|
-spec terminate(_, _) -> ok.
|
2011-05-14 15:20:12 +02:00
|
|
|
terminate(_Reason, _State) ->
|
|
|
|
ok.
|
|
|
|
|
2011-06-27 23:36:17 +02:00
|
|
|
-spec code_change(_, State, _) -> {ok, State}.
|
2011-05-14 15:20:12 +02:00
|
|
|
code_change(_OldVsn, State, _Extra) ->
|
|
|
|
{ok, State}.
|
|
|
|
|
|
|
|
%% Internal.
|
|
|
|
|
2011-06-07 17:26:53 +02:00
|
|
|
-spec update_rfc1123(binary(), undefined | datetime(), datetime()) -> binary().
|
|
|
|
update_rfc1123(Bin, Now, Now) ->
|
2011-05-14 15:20:12 +02:00
|
|
|
Bin;
|
2011-06-07 17:26:53 +02:00
|
|
|
update_rfc1123(<< Keep:23/binary, _/bits >>,
|
|
|
|
{Date, {H, M, _}}, {Date, {H, M, S}}) ->
|
2011-05-14 15:20:12 +02:00
|
|
|
<< Keep/binary, (pad_int(S))/binary, " GMT" >>;
|
2011-06-07 17:26:53 +02:00
|
|
|
update_rfc1123(<< Keep:20/binary, _/bits >>,
|
|
|
|
{Date, {H, _, _}}, {Date, {H, M, S}}) ->
|
2011-05-14 15:20:12 +02:00
|
|
|
<< Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>;
|
2011-06-07 17:26:53 +02:00
|
|
|
update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) ->
|
2011-05-14 15:20:12 +02:00
|
|
|
<< Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary,
|
|
|
|
$:, (pad_int(S))/binary, " GMT" >>;
|
2011-06-07 17:26:53 +02:00
|
|
|
update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>,
|
|
|
|
{{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
|
2011-05-14 15:20:12 +02:00
|
|
|
Wday = calendar:day_of_the_week(Date),
|
|
|
|
<< (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary,
|
|
|
|
(pad_int(H))/binary, $:, (pad_int(M))/binary,
|
|
|
|
$:, (pad_int(S))/binary, " GMT" >>;
|
2011-06-07 17:26:53 +02:00
|
|
|
update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>,
|
|
|
|
{{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
|
2011-05-14 15:20:12 +02:00
|
|
|
Wday = calendar:day_of_the_week(Date),
|
|
|
|
<< (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
|
|
|
|
(month(Mo))/binary, Keep/binary,
|
|
|
|
(pad_int(H))/binary, $:, (pad_int(M))/binary,
|
|
|
|
$:, (pad_int(S))/binary, " GMT" >>;
|
2011-06-07 17:26:53 +02:00
|
|
|
update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) ->
|
2011-05-14 15:20:12 +02:00
|
|
|
Wday = calendar:day_of_the_week(Date),
|
|
|
|
<< (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
|
|
|
|
(month(Mo))/binary, " ", (list_to_binary(integer_to_list(Y)))/binary,
|
|
|
|
" ", (pad_int(H))/binary, $:, (pad_int(M))/binary,
|
|
|
|
$:, (pad_int(S))/binary, " GMT" >>.
|
|
|
|
|
|
|
|
%% Following suggestion by MononcQc on #erlounge.
|
|
|
|
-spec pad_int(0..59) -> binary().
|
|
|
|
pad_int(X) when X < 10 ->
|
|
|
|
<< $0, ($0 + X) >>;
|
|
|
|
pad_int(X) ->
|
|
|
|
list_to_binary(integer_to_list(X)).
|
|
|
|
|
2011-05-25 23:02:40 +02:00
|
|
|
-spec weekday(daynum()) -> <<_:24>>.
|
2011-05-14 15:20:12 +02:00
|
|
|
weekday(1) -> <<"Mon">>;
|
|
|
|
weekday(2) -> <<"Tue">>;
|
|
|
|
weekday(3) -> <<"Wed">>;
|
|
|
|
weekday(4) -> <<"Thu">>;
|
|
|
|
weekday(5) -> <<"Fri">>;
|
|
|
|
weekday(6) -> <<"Sat">>;
|
|
|
|
weekday(7) -> <<"Sun">>.
|
|
|
|
|
2011-05-25 23:02:40 +02:00
|
|
|
-spec month(month()) -> <<_:24>>.
|
2011-05-14 15:20:12 +02:00
|
|
|
month( 1) -> <<"Jan">>;
|
|
|
|
month( 2) -> <<"Feb">>;
|
|
|
|
month( 3) -> <<"Mar">>;
|
|
|
|
month( 4) -> <<"Apr">>;
|
|
|
|
month( 5) -> <<"May">>;
|
|
|
|
month( 6) -> <<"Jun">>;
|
|
|
|
month( 7) -> <<"Jul">>;
|
|
|
|
month( 8) -> <<"Aug">>;
|
|
|
|
month( 9) -> <<"Sep">>;
|
|
|
|
month(10) -> <<"Oct">>;
|
|
|
|
month(11) -> <<"Nov">>;
|
|
|
|
month(12) -> <<"Dec">>.
|
|
|
|
|
|
|
|
%% Tests.
|
|
|
|
|
|
|
|
-ifdef(TEST).
|
|
|
|
|
|
|
|
update_rfc1123_test_() ->
|
|
|
|
Tests = [
|
|
|
|
{<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined,
|
|
|
|
{{2011, 5, 14}, {14, 25, 33}}, <<>>},
|
|
|
|
{<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
|
|
|
|
{{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
|
|
|
|
{<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
|
|
|
|
{{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
|
|
|
|
{<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}},
|
|
|
|
{{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>},
|
|
|
|
{<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}},
|
|
|
|
{{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>},
|
|
|
|
{<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}},
|
|
|
|
{{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>},
|
|
|
|
{<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
|
|
|
|
{{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>},
|
|
|
|
{<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
|
|
|
|
{{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>}
|
|
|
|
],
|
2011-06-07 17:26:53 +02:00
|
|
|
[{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests].
|
2011-05-14 15:20:12 +02:00
|
|
|
|
|
|
|
pad_int_test_() ->
|
|
|
|
Tests = [
|
|
|
|
{ 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>},
|
|
|
|
{ 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>},
|
|
|
|
{ 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>},
|
|
|
|
{12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>},
|
|
|
|
{16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>},
|
|
|
|
{20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>},
|
|
|
|
{24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>},
|
|
|
|
{28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>},
|
|
|
|
{32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>},
|
|
|
|
{36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>},
|
|
|
|
{40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>},
|
|
|
|
{44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>},
|
|
|
|
{48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>},
|
|
|
|
{52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>},
|
|
|
|
{56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>}
|
|
|
|
],
|
|
|
|
[{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests].
|
|
|
|
|
|
|
|
-endif.
|