1 /**
2  * Time formats for string formatting and parsing.
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <vladimir@thecybershadow.net>
12  */
13 
14 module ae.utils.time.common;
15 
16 import ae.utils.text;
17 
18 struct TimeFormats
19 {
20 static:
21 	const ATOM = `Y-m-d\TH:i:sP`;
22 	const COOKIE = `l, d-M-y H:i:s T`;
23 	const ISO8601 = `Y-m-d\TH:i:sO`;
24 	const RFC822 = `D, d M y H:i:s O`;
25 	const RFC850 = `l, d-M-y H:i:s T`;
26 	const RFC1036 = `D, d M y H:i:s O`;
27 	const RFC1123 = `D, d M Y H:i:s O`;
28 	const RFC2822 = `D, d M Y H:i:s O`;
29 	const RFC3339 = `Y-m-d\TH:i:sP`;
30 	const RSS = `D, d M Y H:i:s O`;
31 	const W3C = `Y-m-d\TH:i:sP`;
32 
33 	const HTML5DATE = `Y-m-d`;
34 
35 	/// Format produced by std.date.toString, e.g. "Tue Jun 07 13:23:19 GMT+0100 2011"
36 	const STD_DATE = `D M d H:i:s \G\M\TO Y`;
37 }
38 
39 /// We assume that no timezone will have a name longer than this.
40 /// If one does, it is truncated to this length.
41 enum MaxTimezoneNameLength = 256;
42 
43 /// Calculate the maximum amount of characters needed to store a time in this format.
44 /// Can be evaluated at compile-time.
45 size_t timeFormatSize(string fmt)
46 {
47 	import std.algorithm.iteration : map, reduce;
48 	import std.algorithm.comparison : max;
49 
50 	static size_t maxLength(in string[] names) { return reduce!max(map!`a.length`(WeekdayShortNames)); }
51 
52 	size_t size = 0;
53 	bool escaping = false;
54 	foreach (char c; fmt)
55 		if (escaping)
56 			size++, escaping = false;
57 		else
58 			switch (c)
59 			{
60 				case 'N':
61 				case 'w':
62 				case 'L':
63 				case 'I':
64 					size++;
65 					break;
66 				case 'd':
67 				case 'j':
68 				case 'S':
69 				case 'W':
70 				case 'm':
71 				case 'n':
72 				case 't':
73 				case 'y':
74 				case 'a':
75 				case 'A':
76 				case 'g':
77 				case 'G':
78 				case 'h':
79 				case 'H':
80 				case 'i':
81 				case 's':
82 					size += 2;
83 					break;
84 				case 'z':
85 				case 'E': // not standard
86 					size += 3;
87 					break;
88 				case 'Y':
89 					size += 4;
90 					break;
91 				case 'Z': // Timezone offset in seconds
92 				case 'O':
93 					size += 5;
94 					break;
95 				case 'u':
96 				case 'P':
97 					size += 6;
98 					break;
99 				case 'T':
100 					size += 32;
101 					break;
102 
103 				case 'D':
104 					size += maxLength(WeekdayShortNames);
105 					break;
106 				case 'l':
107 					size += maxLength(WeekdayLongNames);
108 					break;
109 				case 'F':
110 					size += maxLength(MonthLongNames);
111 					break;
112 				case 'M':
113 					size += maxLength(MonthShortNames);
114 					break;
115 
116 				case 'e': // Timezone name
117 					return MaxTimezoneNameLength;
118 
119 				// Full date/time
120 				case 'c':
121 					enum ISOExtLength = "-0004-01-05T00:00:02.052092+10:00".length;
122 					size += ISOExtLength;
123 					break;
124 				case 'r':
125 					size += timeFormatSize(TimeFormats.RFC2822);
126 					break;
127 				case 'U':
128 					size += DecimalSize!int;
129 					break;
130 
131 				// Escape next character
132 				case '\\':
133 					escaping = true;
134 					break;
135 
136 				// Other characters (whitespace, delimiters)
137 				default:
138 					size++;
139 			}
140 
141 	return size;
142 }
143 
144 static assert(timeFormatSize(TimeFormats.STD_DATE) == "Tue Jun 07 13:23:19 GMT+0100 2011".length);
145 
146 // ***************************************************************************
147 
148 const WeekdayShortNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
149 const WeekdayLongNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
150 const MonthShortNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
151 const MonthLongNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
152