Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 375 lines (320 sloc) 11.252 kB
ebdd677 @gardenia Renamed the dateformat module to erlydtl_dateformat
gardenia authored
1 -module(erlydtl_dateformat).
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
2 -export([format/1, format/2]).
3
4 -define(TAG_SUPPORTED(C),
5 C =:= $a orelse
6 C =:= $A orelse
7 C =:= $b orelse
8 C =:= $B orelse
3487fdd @evanmiller Support ISO-8601 date formatting
evanmiller authored
9 C =:= $c orelse
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
10 C =:= $d orelse
11 C =:= $D orelse
12 C =:= $f orelse
13 C =:= $F orelse
14 C =:= $g orelse
15 C =:= $G orelse
16 C =:= $h orelse
17 C =:= $H orelse
18 C =:= $i orelse
19 C =:= $I orelse
20 C =:= $j orelse
21 C =:= $l orelse
22 C =:= $L orelse
23 C =:= $m orelse
24 C =:= $M orelse
25 C =:= $n orelse
26 C =:= $N orelse
27 C =:= $O orelse
28 C =:= $P orelse
29 C =:= $r orelse
30 C =:= $s orelse
31 C =:= $S orelse
32 C =:= $t orelse
33 C =:= $T orelse
34 C =:= $U orelse
35 C =:= $w orelse
36 C =:= $W orelse
37 C =:= $y orelse
38 C =:= $Y orelse
39 C =:= $z orelse
40 C =:= $Z
41 ).
42
43 %
44 % Format the current date/time
45 %
c17a770 Compile to binaries by default
Evan Miller authored
46 format(FormatString) when is_binary(FormatString) ->
47 format(binary_to_list(FormatString));
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
48 format(FormatString) ->
49 {Date, Time} = erlang:localtime(),
50 replace_tags(Date, Time, FormatString).
51 %
52 % Format a tuple of the form {{Y,M,D},{H,M,S}}
53 % This is the format returned by erlang:localtime()
54 % and other standard date/time BIFs
55 %
c17a770 Compile to binaries by default
Evan Miller authored
56 format(DateTime, FormatString) when is_binary(FormatString) ->
57 format(DateTime, binary_to_list(FormatString));
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
58 format({{_,_,_} = Date,{_,_,_} = Time}, FormatString) ->
c17a770 Compile to binaries by default
Evan Miller authored
59 replace_tags(Date, Time, FormatString);
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
60 %
61 % Format a tuple of the form {Y,M,D}
62 %
63 format({_,_,_} = Date, FormatString) ->
64 replace_tags(Date, {0,0,0}, FormatString);
65 format(DateTime, FormatString) ->
66 io:format("Unrecognised date paramater : ~p~n", [DateTime]),
67 FormatString.
68
69 replace_tags(Date, Time, Input) ->
70 replace_tags(Date, Time, Input, [], noslash).
71 replace_tags(_Date, _Time, [], Out, _State) ->
72 lists:reverse(Out);
73 replace_tags(Date, Time, [C|Rest], Out, noslash) when ?TAG_SUPPORTED(C) ->
74 replace_tags(Date, Time, Rest,
75 lists:reverse(tag_to_value(C, Date, Time)) ++ Out, noslash);
76 replace_tags(Date, Time, [$\\|Rest], Out, noslash) ->
77 replace_tags(Date, Time, Rest, Out, slash);
78 replace_tags(Date, Time, [C|Rest], Out, slash) ->
79 replace_tags(Date, Time, Rest, [C|Out], noslash);
80 replace_tags(Date, Time, [C|Rest], Out, _State) ->
81 replace_tags(Date, Time, Rest, [C|Out], noslash).
82
83
84 %-----------------------------------------------------------
85 % Time formatting
86 %-----------------------------------------------------------
87
88 % 'a.m.' or 'p.m.'
89 tag_to_value($a, _, {H, _, _}) when H > 11 -> "p.m.";
90 tag_to_value($a, _, _) -> "a.m.";
91
92 % 'AM' or 'PM'
93 tag_to_value($A, _, {H, _, _}) when H > 11 -> "PM";
94 tag_to_value($A, _, _) -> "AM";
95
96 % Swatch Internet time
97 tag_to_value($B, _, _) ->
98 ""; % NotImplementedError
99
3487fdd @evanmiller Support ISO-8601 date formatting
evanmiller authored
100 % ISO 8601 Format.
101 tag_to_value($c, Date, Time) ->
102 tag_to_value($Y, Date, Time) ++
103 "-" ++ tag_to_value($m, Date, Time) ++
104 "-" ++ tag_to_value($d, Date, Time) ++
105 "T" ++ tag_to_value($H, Date, Time) ++
106 ":" ++ tag_to_value($i, Date, Time) ++
107 ":" ++ tag_to_value($s, Date, Time);
108
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
109 %
110 % Time, in 12-hour hours and minutes, with minutes
111 % left off if they're zero.
112 %
113 % Examples: '1', '1:30', '2:05', '2'
114 %
115 % Proprietary extension.
116 %
117 tag_to_value($f, Date, {H, 0, S}) ->
118 % If min is zero then return the hour only
119 tag_to_value($g, Date, {H, 0, S});
120 tag_to_value($f, Date, Time) ->
121 % Otherwise return hours and mins
122 tag_to_value($g, Date, Time)
123 ++ ":" ++ tag_to_value($i, Date, Time);
124
125 % Hour, 12-hour format without leading zeros; i.e. '1' to '12'
126 tag_to_value($g, _, {H,_,_}) ->
127 integer_to_list(hour_24to12(H));
128
129 % Hour, 24-hour format without leading zeros; i.e. '0' to '23'
130 tag_to_value($G, _, {H,_,_}) ->
131 integer_to_list(H);
132
133 % Hour, 12-hour format; i.e. '01' to '12'
134 tag_to_value($h, _, {H,_,_}) ->
2b29a5f Fixed date formatting for 'h', added tests.
Colin MacDonald authored
135 integer_to_list_zerofill(hour_24to12(H));
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
136
137 % Hour, 24-hour format; i.e. '00' to '23'
138 tag_to_value($H, _, {H,_,_}) ->
139 integer_to_list_zerofill(H);
140
141 % Minutes; i.e. '00' to '59'
142 tag_to_value($i, _, {_,M,_}) ->
143 integer_to_list_zerofill(M);
144
145 % Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off
146 % if they're zero and the strings 'midnight' and 'noon' if appropriate.
147 % Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.'
148 % Proprietary extension.
149 tag_to_value($P, _, {0, 0, _}) -> "midnight";
150 tag_to_value($P, _, {12, 0, _}) -> "noon";
151 tag_to_value($P, Date, Time) ->
152 tag_to_value($f, Date, Time)
153 ++ " " ++ tag_to_value($a, Date, Time);
154
155 % Seconds; i.e. '00' to '59'
156 tag_to_value($s, _, {_,_,S}) ->
157 integer_to_list_zerofill(S);
158
159 %-----------------------------------------------------------
160 % Date formatting
161 %-----------------------------------------------------------
162
163 % Month, textual, 3 letters, lowercase; e.g. 'jan'
164 tag_to_value($b, {_,M,_}, _) ->
165 string:sub_string(monthname(M), 1, 3);
166
167 % Day of the month, 2 digits with leading zeros; i.e. '01' to '31'
168 tag_to_value($d, {_, _, D}, _) ->
169 integer_to_list_zerofill(D);
170
171 % Day of the week, textual, 3 letters; e.g. 'Fri'
172 tag_to_value($D, Date, _) ->
173 Dow = calendar:day_of_the_week(Date),
174 ucfirst(string:sub_string(dayname(Dow), 1, 3));
175
176 % Month, textual, long; e.g. 'January'
177 tag_to_value($F, {_,M,_}, _) ->
178 ucfirst(monthname(M));
179
180 % '1' if Daylight Savings Time, '0' otherwise.
181 tag_to_value($I, _, _) ->
182 "TODO";
183
184 % Day of the month without leading zeros; i.e. '1' to '31'
185 tag_to_value($j, {_, _, D}, _) ->
186 integer_to_list(D);
187
188 % Day of the week, textual, long; e.g. 'Friday'
189 tag_to_value($l, Date, _) ->
190 ucfirst(dayname(calendar:day_of_the_week(Date)));
191
192 % Boolean for whether it is a leap year; i.e. True or False
193 tag_to_value($L, {Y,_,_}, _) ->
194 case calendar:is_leap_year(Y) of
195 true -> "True";
196 _ -> "False"
197 end;
198
199 % Month; i.e. '01' to '12'
200 tag_to_value($m, {_, M, _}, _) ->
201 integer_to_list_zerofill(M);
202
203 % Month, textual, 3 letters; e.g. 'Jan'
204 tag_to_value($M, {_,M,_}, _) ->
205 ucfirst(string:sub_string(monthname(M), 1, 3));
206
207 % Month without leading zeros; i.e. '1' to '12'
208 tag_to_value($n, {_, M, _}, _) ->
209 integer_to_list(M);
210
211 % Month abbreviation in Associated Press style. Proprietary extension.
212 tag_to_value($N, {_,M,_}, _) when M =:= 9 ->
213 % Special case - "Sept."
214 ucfirst(string:sub_string(monthname(M), 1, 4)) ++ ".";
215 tag_to_value($N, {_,M,_}, _) when M < 3 orelse M > 7 ->
216 % Jan, Feb, Aug, Oct, Nov, Dec are all
217 % abbreviated with a full-stop appended.
218 ucfirst(string:sub_string(monthname(M), 1, 3)) ++ ".";
219 tag_to_value($N, {_,M,_}, _) ->
220 % The rest are the fullname.
221 ucfirst(monthname(M));
222
223 % Difference to Greenwich time in hours; e.g. '+0200'
224 tag_to_value($O, Date, Time) ->
225 Diff = utc_diff(Date, Time),
38836b3 @evanmiller Fix time zone inconsistency. Removing tests of "r" dateformat tag bec…
evanmiller authored
226 Offset = if
227 Diff < 0 ->
228 io_lib:format("-~4..0w", [abs(Diff)]);
229 true ->
230 io_lib:format("+~4..0w", [Diff])
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
231 end,
232 lists:flatten(Offset);
233
234 % RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'
235 tag_to_value($r, Date, Time) ->
236 replace_tags(Date, Time, "D, j M Y H:i:s O");
237
238 % English ordinal suffix for the day of the month, 2 characters;
239 % i.e. 'st', 'nd', 'rd' or 'th'
240 tag_to_value($S, {_, _, D}, _) when
241 D rem 100 =:= 11 orelse
242 D rem 100 =:= 12 orelse
243 D rem 100 =:= 13 -> "th";
244 tag_to_value($S, {_, _, D}, _) when D rem 10 =:= 1 -> "st";
245 tag_to_value($S, {_, _, D}, _) when D rem 10 =:= 2 -> "nd";
246 tag_to_value($S, {_, _, D}, _) when D rem 10 =:= 3 -> "rd";
247 tag_to_value($S, _, _) -> "th";
248
249 % Number of days in the given month; i.e. '28' to '31'
250 tag_to_value($t, {Y,M,_}, _) ->
251 integer_to_list(calendar:last_day_of_the_month(Y,M));
252
253 % Time zone of this machine; e.g. 'EST' or 'MDT'
254 tag_to_value($T, _, _) ->
255 "TODO";
256
257 % Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)
258 tag_to_value($U, Date, Time) ->
259 EpochSecs = calendar:datetime_to_gregorian_seconds({Date, Time})
260 - calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}),
261 integer_to_list(EpochSecs);
262
263 % Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)
264 tag_to_value($w, Date, _) ->
265 % Note: calendar:day_of_the_week returns
266 % 1 | .. | 7. Monday = 1, Tuesday = 2, ..., Sunday = 7
267 integer_to_list(calendar:day_of_the_week(Date) rem 7);
268
269 % ISO-8601 week number of year, weeks starting on Monday
270 tag_to_value($W, {Y,M,D}, _) ->
271 integer_to_list(year_weeknum(Y,M,D));
272
273 % Year, 2 digits; e.g. '99'
274 tag_to_value($y, {Y, _, _}, _) ->
275 string:sub_string(integer_to_list(Y), 3);
276
277 % Year, 4 digits; e.g. '1999'
278 tag_to_value($Y, {Y, _, _}, _) ->
279 integer_to_list(Y);
280
281 % Day of the year; i.e. '0' to '365'
282 tag_to_value($z, {Y,M,D}, _) ->
283 integer_to_list(day_of_year(Y,M,D));
284
285 % Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for
286 % timezones west of UTC is always negative, and for those east of UTC is
287 % always positive.
288 tag_to_value($Z, _, _) ->
289 "TODO";
290
291 tag_to_value(C, Date, Time) ->
292 io:format("Unimplemented tag : ~p [Date : ~p] [Time : ~p]",
293 [C, Date, Time]),
294 "".
295
296 % Date helper functions
297 day_of_year(Y,M,D) ->
298 day_of_year(Y,M,D,0).
299 day_of_year(_Y,M,D,Count) when M =< 1 ->
300 D + Count;
301 day_of_year(Y,M,D,Count) when M =< 12 ->
302 day_of_year(Y, M - 1, D, Count + calendar:last_day_of_the_month(Y,M));
303 day_of_year(Y,_M,D,_Count) ->
304 day_of_year(Y, 12, D, 0).
305
306 hour_24to12(0) -> 12;
307 hour_24to12(H) when H < 13 -> H;
308 hour_24to12(H) when H < 24 -> H - 12;
309 hour_24to12(H) -> H.
310
311 year_weeknum(Y,M,D) ->
312 First = (calendar:day_of_the_week(Y, 1, 1) rem 7) - 1,
313 Wk = ((((calendar:date_to_gregorian_days(Y, M, D) -
314 calendar:date_to_gregorian_days(Y, 1, 1)) + First) div 7)
315 + (case First < 4 of true -> 1; _ -> 0 end)),
316 case Wk of
317 0 -> weeks_in_year(Y - 1);
318 _ -> case weeks_in_year(Y) of
319 WksInThisYear when Wk > WksInThisYear -> 1;
320 _ -> Wk
321 end
322 end.
323
324 weeks_in_year(Y) ->
325 D1 = calendar:day_of_the_week(Y, 1, 1),
326 D2 = calendar:day_of_the_week(Y, 12, 31),
327 if (D1 =:= 4 orelse D2 =:= 4) -> 53; true -> 52 end.
328
b96dd0b @evanmiller Zotonic: Basic boolean operators in "if" clause
evanmiller authored
329 utc_diff({Y, M, D}, Time) when Y < 1970->
330 utc_diff({1970, M, D}, Time);
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
331 utc_diff(Date, Time) ->
38836b3 @evanmiller Fix time zone inconsistency. Removing tests of "r" dateformat tag bec…
evanmiller authored
332 LTime = {Date, Time},
333 UTime = erlang:localtime_to_universaltime(LTime),
334 DiffSecs = calendar:datetime_to_gregorian_seconds(LTime) -
335 calendar:datetime_to_gregorian_seconds(UTime),
336 trunc((DiffSecs / 3600) * 100).
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
337
338 dayname(1) -> "monday";
339 dayname(2) -> "tuesday";
340 dayname(3) -> "wednesday";
341 dayname(4) -> "thursday";
342 dayname(5) -> "friday";
343 dayname(6) -> "saturday";
344 dayname(7) -> "sunday";
345 dayname(_) -> "???".
346
347 monthname(1) -> "january";
348 monthname(2) -> "february";
349 monthname(3) -> "march";
350 monthname(4) -> "april";
351 monthname(5) -> "may";
352 monthname(6) -> "june";
353 monthname(7) -> "july";
354 monthname(8) -> "august";
355 monthname(9) -> "september";
356 monthname(10) -> "october";
357 monthname(11) -> "november";
358 monthname(12) -> "december";
359 monthname(_) -> "???".
360
361 % Utility functions
1b8e85f @runejuhl is_float is a lot better than not is_integer...
runejuhl authored
362 integer_to_list_zerofill(N) when is_float(N) ->
788a0f6 @runejuhl Refactored and fixed a bug when seconds (float or integer) < 10.
runejuhl authored
363 integer_to_list_zerofill(erlang:round(N));
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
364 integer_to_list_zerofill(N) when N < 10 ->
365 lists:flatten(io_lib:format("~2..0B", [N]));
9946dfa @runejuhl Added handling floating point seconds in time tuples.
runejuhl authored
366 integer_to_list_zerofill(N) when is_integer(N) ->
788a0f6 @runejuhl Refactored and fixed a bug when seconds (float or integer) < 10.
runejuhl authored
367 integer_to_list(N).
48b9b5f @gardenia Added "now" tag and associated associated dateformat module.
gardenia authored
368
369 ucfirst([First | Rest]) when First >= $a, First =< $z ->
370 [First-($a-$A) | Rest];
371 ucfirst(Other) ->
372 Other.
373
374
Something went wrong with that request. Please try again.