cowboy_clock.erl 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. %% @doc Date and time related functions.
  15. %%
  16. %% While a gen_server process runs in the background to update
  17. %% the cache of formatted dates every second, all API calls are
  18. %% local and directly read from the ETS cache table, providing
  19. %% fast time and date computations.
  20. -module(cowboy_clock).
  21. -behaviour(gen_server).
  22. -export([start_link/0, stop/0, rfc1123/0, rfc2109/1]). %% API.
  23. -export([init/1, handle_call/3, handle_cast/2,
  24. handle_info/2, terminate/2, code_change/3]). %% gen_server.
  25. -record(state, {
  26. universaltime = undefined :: undefined | calendar:datetime(),
  27. rfc1123 = <<>> :: binary(),
  28. tref = undefined :: undefined | timer:tref()
  29. }).
  30. -define(SERVER, ?MODULE).
  31. -define(TABLE, ?MODULE).
  32. -include_lib("eunit/include/eunit.hrl").
  33. %% API.
  34. %% @private
  35. -spec start_link() -> {ok, pid()}.
  36. start_link() ->
  37. gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
  38. %% @private
  39. -spec stop() -> stopped.
  40. stop() ->
  41. gen_server:call(?SERVER, stop).
  42. %% @doc Return the current date and time formatted according to RFC-1123.
  43. %%
  44. %% This format is used in the <em>'Date'</em> header sent with HTTP responses.
  45. -spec rfc1123() -> binary().
  46. rfc1123() ->
  47. ets:lookup_element(?TABLE, rfc1123, 2).
  48. %% @doc Return the current date and time formatted according to RFC-2109.
  49. %%
  50. %% This format is used in the <em>'Set-Cookie'</em> header sent with
  51. %% HTTP responses.
  52. -spec rfc2109(calendar:datetime()) -> binary().
  53. rfc2109(LocalTime) ->
  54. {{YYYY,MM,DD},{Hour,Min,Sec}} =
  55. case calendar:local_time_to_universal_time_dst(LocalTime) of
  56. [Gmt] -> Gmt;
  57. [_,Gmt] -> Gmt;
  58. [] ->
  59. %% The localtime generated by cowboy_cookies may fall within
  60. %% the hour that is skipped by daylight savings time. If this
  61. %% is such a localtime, increment the localtime with one hour
  62. %% and try again, if this succeeds, subtracting the max_age
  63. %% from the resulting universaltime and converting to a local
  64. %% time will yield the original localtime.
  65. {Date, {Hour1, Min1, Sec1}} = LocalTime,
  66. LocalTime2 = {Date, {Hour1 + 1, Min1, Sec1}},
  67. case calendar:local_time_to_universal_time_dst(LocalTime2) of
  68. [Gmt] -> Gmt;
  69. [_,Gmt] -> Gmt
  70. end
  71. end,
  72. Wday = calendar:day_of_the_week({YYYY,MM,DD}),
  73. DayBin = pad_int(DD),
  74. YearBin = list_to_binary(integer_to_list(YYYY)),
  75. HourBin = pad_int(Hour),
  76. MinBin = pad_int(Min),
  77. SecBin = pad_int(Sec),
  78. WeekDay = weekday(Wday),
  79. Month = month(MM),
  80. <<WeekDay/binary, ", ",
  81. DayBin/binary, " ", Month/binary, " ",
  82. YearBin/binary, " ",
  83. HourBin/binary, ":",
  84. MinBin/binary, ":",
  85. SecBin/binary, " GMT">>.
  86. %% gen_server.
  87. %% @private
  88. -spec init([]) -> {ok, #state{}}.
  89. init([]) ->
  90. ?TABLE = ets:new(?TABLE, [set, protected,
  91. named_table, {read_concurrency, true}]),
  92. T = erlang:universaltime(),
  93. B = update_rfc1123(<<>>, undefined, T),
  94. {ok, TRef} = timer:send_interval(1000, update),
  95. ets:insert(?TABLE, {rfc1123, B}),
  96. {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}.
  97. %% @private
  98. -spec handle_call(_, _, State)
  99. -> {reply, ignored, State} | {stop, normal, stopped, State}.
  100. handle_call(stop, _From, State=#state{tref=TRef}) ->
  101. {ok, cancel} = timer:cancel(TRef),
  102. {stop, normal, stopped, State};
  103. handle_call(_Request, _From, State) ->
  104. {reply, ignored, State}.
  105. %% @private
  106. -spec handle_cast(_, State) -> {noreply, State}.
  107. handle_cast(_Msg, State) ->
  108. {noreply, State}.
  109. %% @private
  110. -spec handle_info(_, State) -> {noreply, State}.
  111. handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef}) ->
  112. T = erlang:universaltime(),
  113. B2 = update_rfc1123(B1, Prev, T),
  114. ets:insert(?TABLE, {rfc1123, B2}),
  115. {noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}};
  116. handle_info(_Info, State) ->
  117. {noreply, State}.
  118. %% @private
  119. -spec terminate(_, _) -> ok.
  120. terminate(_Reason, _State) ->
  121. ok.
  122. %% @private
  123. -spec code_change(_, State, _) -> {ok, State}.
  124. code_change(_OldVsn, State, _Extra) ->
  125. {ok, State}.
  126. %% Internal.
  127. -spec update_rfc1123(binary(), undefined | calendar:datetime(),
  128. calendar:datetime()) -> binary().
  129. update_rfc1123(Bin, Now, Now) ->
  130. Bin;
  131. update_rfc1123(<< Keep:23/binary, _/bits >>,
  132. {Date, {H, M, _}}, {Date, {H, M, S}}) ->
  133. << Keep/binary, (pad_int(S))/binary, " GMT" >>;
  134. update_rfc1123(<< Keep:20/binary, _/bits >>,
  135. {Date, {H, _, _}}, {Date, {H, M, S}}) ->
  136. << Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>;
  137. update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) ->
  138. << Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary,
  139. $:, (pad_int(S))/binary, " GMT" >>;
  140. update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>,
  141. {{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
  142. Wday = calendar:day_of_the_week(Date),
  143. << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary,
  144. (pad_int(H))/binary, $:, (pad_int(M))/binary,
  145. $:, (pad_int(S))/binary, " GMT" >>;
  146. update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>,
  147. {{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
  148. Wday = calendar:day_of_the_week(Date),
  149. << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
  150. (month(Mo))/binary, Keep/binary,
  151. (pad_int(H))/binary, $:, (pad_int(M))/binary,
  152. $:, (pad_int(S))/binary, " GMT" >>;
  153. update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) ->
  154. Wday = calendar:day_of_the_week(Date),
  155. << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
  156. (month(Mo))/binary, " ", (list_to_binary(integer_to_list(Y)))/binary,
  157. " ", (pad_int(H))/binary, $:, (pad_int(M))/binary,
  158. $:, (pad_int(S))/binary, " GMT" >>.
  159. %% Following suggestion by MononcQc on #erlounge.
  160. -spec pad_int(0..59) -> binary().
  161. pad_int(X) when X < 10 ->
  162. << $0, ($0 + X) >>;
  163. pad_int(X) ->
  164. list_to_binary(integer_to_list(X)).
  165. -spec weekday(1..7) -> <<_:24>>.
  166. weekday(1) -> <<"Mon">>;
  167. weekday(2) -> <<"Tue">>;
  168. weekday(3) -> <<"Wed">>;
  169. weekday(4) -> <<"Thu">>;
  170. weekday(5) -> <<"Fri">>;
  171. weekday(6) -> <<"Sat">>;
  172. weekday(7) -> <<"Sun">>.
  173. -spec month(1..12) -> <<_:24>>.
  174. month( 1) -> <<"Jan">>;
  175. month( 2) -> <<"Feb">>;
  176. month( 3) -> <<"Mar">>;
  177. month( 4) -> <<"Apr">>;
  178. month( 5) -> <<"May">>;
  179. month( 6) -> <<"Jun">>;
  180. month( 7) -> <<"Jul">>;
  181. month( 8) -> <<"Aug">>;
  182. month( 9) -> <<"Sep">>;
  183. month(10) -> <<"Oct">>;
  184. month(11) -> <<"Nov">>;
  185. month(12) -> <<"Dec">>.
  186. %% Tests.
  187. -ifdef(TEST).
  188. update_rfc1123_test_() ->
  189. Tests = [
  190. {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined,
  191. {{2011, 5, 14}, {14, 25, 33}}, <<>>},
  192. {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
  193. {{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
  194. {<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
  195. {{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
  196. {<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}},
  197. {{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>},
  198. {<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}},
  199. {{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>},
  200. {<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}},
  201. {{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>},
  202. {<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
  203. {{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>},
  204. {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
  205. {{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>}
  206. ],
  207. [{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests].
  208. pad_int_test_() ->
  209. Tests = [
  210. { 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>},
  211. { 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>},
  212. { 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>},
  213. {12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>},
  214. {16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>},
  215. {20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>},
  216. {24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>},
  217. {28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>},
  218. {32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>},
  219. {36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>},
  220. {40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>},
  221. {44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>},
  222. {48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>},
  223. {52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>},
  224. {56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>}
  225. ],
  226. [{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests].
  227. -endif.