Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
Strftime |
|
| 3.2;3.2 |
1 | /* |
|
2 | * Copyright 2006 The Apache Software Foundation. Licensed under the Apache License, Version 2.0 |
|
3 | * (the "License"); you may not use this file except in compliance with the License. You may obtain |
|
4 | * a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable |
|
5 | * law or agreed to in writing, software distributed under the License is distributed on an "AS IS" |
|
6 | * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License |
|
7 | * for the specific language governing permissions and limitations under the License. |
|
8 | */ |
|
9 | ||
10 | package org.apache.tapestry.util; |
|
11 | ||
12 | import java.text.ParseException; |
|
13 | import java.text.SimpleDateFormat; |
|
14 | import java.util.Date; |
|
15 | import java.util.Locale; |
|
16 | import java.util.Properties; |
|
17 | import java.util.TimeZone; |
|
18 | ||
19 | /** |
|
20 | * Converts dates to strings using the same format specifiers as strftime Note: This does not mimic |
|
21 | * strftime perfectly. Certain strftime commands, are not supported, and will convert as if they |
|
22 | * were literals. Certain complicated commands, like those dealing with the week of the year |
|
23 | * probably don't have exactly the same behavior as strftime. These limitations are due to use |
|
24 | * SimpleDateTime. If the conversion was done manually, all these limitations could be eliminated. |
|
25 | * The interface looks like a subset of DateFormat. Maybe someday someone will make this class |
|
26 | * extend DateFormat. |
|
27 | * |
|
28 | * <p> |
|
29 | * Added to tapestry in order to help with dojo/javascript date/time conversions. |
|
30 | * </p> |
|
31 | * |
|
32 | * @see "http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html" |
|
33 | * @author Bip Thelin |
|
34 | * @author Dan Sandberg |
|
35 | */ |
|
36 | public class Strftime |
|
37 | { |
|
38 | ||
39 | protected static Properties translate; |
|
40 | protected static Properties pTranslate; |
|
41 | protected SimpleDateFormat simpleDateFormat; |
|
42 | ||
43 | /** |
|
44 | * Initialize our pattern translation |
|
45 | */ |
|
46 | static { |
|
47 | 1 | translate = new Properties(); |
48 | 1 | translate.put("a", "EEE"); |
49 | 1 | translate.put("A", "EEEE"); |
50 | 1 | translate.put("b", "MMM"); |
51 | 1 | translate.put("B", "MMMM"); |
52 | 1 | translate.put("c", "EEE MMM d HH:mm:ss yyyy"); |
53 | ||
54 | // There's no way to specify the century in SimpleDateFormat. We don't want to hard-code |
|
55 | // 20 since this could be wrong for the pre-2000 files. |
|
56 | // translate.put("C", "20"); |
|
57 | 1 | translate.put("d", "dd"); |
58 | 1 | translate.put("D", "MM/dd/yy"); |
59 | 1 | translate.put("e", "dd"); // will show as '03' instead of ' 3' |
60 | 1 | translate.put("F", "yyyy-MM-dd"); |
61 | 1 | translate.put("g", "yy"); |
62 | 1 | translate.put("G", "yyyy"); |
63 | 1 | translate.put("H", "HH"); |
64 | 1 | translate.put("h", "MMM"); |
65 | 1 | translate.put("I", "hh"); |
66 | 1 | translate.put("j", "DDD"); |
67 | 1 | translate.put("k", "HH"); // will show as '07' instead of ' 7' |
68 | 1 | translate.put("l", "hh"); // will show as '07' instead of ' 7' |
69 | 1 | translate.put("m", "MM"); |
70 | 1 | translate.put("M", "mm"); |
71 | 1 | translate.put("n", "\n"); |
72 | 1 | translate.put("p", "a"); |
73 | 1 | translate.put("P", "a"); // will show as pm instead of PM |
74 | 1 | translate.put("r", "hh:mm:ss a"); |
75 | 1 | translate.put("R", "HH:mm"); |
76 | // There's no way to specify this with SimpleDateFormat |
|
77 | // translate.put("s","seconds since ecpoch"); |
|
78 | 1 | translate.put("S", "ss"); |
79 | 1 | translate.put("t", "\t"); |
80 | 1 | translate.put("T", "HH:mm:ss"); |
81 | // There's no way to specify this with SimpleDateFormat |
|
82 | // translate.put("u","day of week ( 1-7 )"); |
|
83 | ||
84 | // There's no way to specify this with SimpleDateFormat |
|
85 | // translate.put("U","week in year with first sunday as first day..."); |
|
86 | ||
87 | 1 | translate.put("V", "ww"); // I'm not sure this is always exactly the same |
88 | ||
89 | // There's no way to specify this with SimpleDateFormat |
|
90 | // translate.put("W","week in year with first monday as first day..."); |
|
91 | ||
92 | // There's no way to specify this with SimpleDateFormat |
|
93 | // translate.put("w","E"); |
|
94 | 1 | translate.put("X", "HH:mm:ss"); |
95 | 1 | translate.put("x", "MM/dd/yy"); |
96 | 1 | translate.put("y", "yy"); |
97 | 1 | translate.put("Y", "yyyy"); |
98 | 1 | translate.put("Z", "z"); |
99 | 1 | translate.put("z", "Z"); |
100 | 1 | translate.put("%", "%"); |
101 | ||
102 | 1 | pTranslate = new Properties(); |
103 | 1 | pTranslate.put("EEE", "%a"); |
104 | 1 | pTranslate.put("EEEE", "%A"); |
105 | 1 | pTranslate.put("MMM", "%b"); |
106 | 1 | pTranslate.put("MMMM", "%B"); |
107 | 1 | pTranslate.put("EEE MMM d HH:mm:ss yyyy", "%c"); |
108 | ||
109 | // There's no way to specify the century in SimpleDateFormat. We don't want to hard-code |
|
110 | // 20 since this could be wrong for the pre-2000 files. |
|
111 | // translate.put("C", "20"); |
|
112 | 1 | pTranslate.put("dd", "%d"); |
113 | 1 | pTranslate.put("MM/dd/yy", "%D"); |
114 | 1 | pTranslate.put("yyyy-MM-dd", "%F"); |
115 | 1 | pTranslate.put("yy", "%g"); |
116 | 1 | pTranslate.put("yyyy", "%G"); |
117 | 1 | pTranslate.put("HH", "%H"); |
118 | 1 | pTranslate.put("MMM", "%h"); |
119 | 1 | pTranslate.put("hh", "%I"); |
120 | 1 | pTranslate.put("DDD", "%j"); |
121 | 1 | pTranslate.put("MM", "%m"); |
122 | 1 | pTranslate.put("mm", "%M"); |
123 | 1 | pTranslate.put("\n", "%n"); |
124 | 1 | pTranslate.put("a", "%p"); // will show as pm instead of PM |
125 | 1 | pTranslate.put("hh:mm:ss a", "%r"); |
126 | 1 | pTranslate.put("HH:mm", "%R"); |
127 | // There's no way to specify this with SimpleDateFormat |
|
128 | // translate.put("s","seconds since ecpoch"); |
|
129 | 1 | pTranslate.put("ss", "%S"); |
130 | 1 | pTranslate.put("\t", "%t"); |
131 | 1 | pTranslate.put("HH:mm:ss", "%T"); |
132 | // There's no way to specify this with SimpleDateFormat |
|
133 | // translate.put("u","day of week ( 1-7 )"); |
|
134 | ||
135 | // There's no way to specify this with SimpleDateFormat |
|
136 | // translate.put("U","week in year with first sunday as first day..."); |
|
137 | ||
138 | 1 | pTranslate.put("ww", "%V"); // I'm not sure this is always exactly the same |
139 | ||
140 | // There's no way to specify this with SimpleDateFormat |
|
141 | // translate.put("W","week in year with first monday as first day..."); |
|
142 | ||
143 | // There's no way to specify this with SimpleDateFormat |
|
144 | // translate.put("w","E"); |
|
145 | 1 | pTranslate.put("HH:mm:ss", "%X"); |
146 | 1 | pTranslate.put("MM/dd/yy", "%x"); |
147 | 1 | pTranslate.put("yy", "%y"); |
148 | 1 | pTranslate.put("yyyy", "%Y"); |
149 | 1 | pTranslate.put("z", "%Z"); |
150 | 1 | pTranslate.put("Z", "%z"); |
151 | 1 | pTranslate.put("%", "%"); |
152 | 1 | } |
153 | ||
154 | /** |
|
155 | * Create an instance of this date formatting class. |
|
156 | * |
|
157 | * @see #Strftime( String, Locale ) |
|
158 | */ |
|
159 | public Strftime(String origFormat) |
|
160 | 0 | { |
161 | 0 | String convertedFormat = convertDateFormat(origFormat); |
162 | 0 | simpleDateFormat = new SimpleDateFormat(convertedFormat); |
163 | 0 | } |
164 | ||
165 | /** |
|
166 | * Create an instance of this date formatting class. |
|
167 | * |
|
168 | * @param origFormat |
|
169 | * the strftime-style formatting string |
|
170 | * @param locale |
|
171 | * the locale to use for locale-specific conversions |
|
172 | */ |
|
173 | public Strftime(String origFormat, Locale locale) |
|
174 | 0 | { |
175 | 0 | String convertedFormat = convertDateFormat(origFormat); |
176 | 0 | simpleDateFormat = new SimpleDateFormat(convertedFormat, locale); |
177 | 0 | } |
178 | ||
179 | /** |
|
180 | * Format the date according to the strftime-style string given in the constructor. |
|
181 | * |
|
182 | * @param date |
|
183 | * the date to format |
|
184 | * @return the formatted date |
|
185 | */ |
|
186 | public String format(Date date) |
|
187 | { |
|
188 | 0 | return simpleDateFormat.format(date); |
189 | } |
|
190 | ||
191 | /** |
|
192 | * Parses the input. |
|
193 | * |
|
194 | * @see java.text.SimpleDateFormat#parse(String) |
|
195 | * @param input The string to parse. |
|
196 | * @return A parsed {@link Date}. |
|
197 | * @throws ParseException On input error. |
|
198 | */ |
|
199 | public Date parse(String input) |
|
200 | throws ParseException |
|
201 | { |
|
202 | 0 | return simpleDateFormat.parse(input); |
203 | } |
|
204 | ||
205 | /** |
|
206 | * Get the timezone used for formatting conversions. |
|
207 | * |
|
208 | * @return the timezone |
|
209 | */ |
|
210 | public TimeZone getTimeZone() |
|
211 | { |
|
212 | 0 | return simpleDateFormat.getTimeZone(); |
213 | } |
|
214 | ||
215 | /** |
|
216 | * Change the timezone used to format dates. |
|
217 | * |
|
218 | * @see SimpleDateFormat#setTimeZone(TimeZone) |
|
219 | */ |
|
220 | public void setTimeZone(TimeZone timeZone) |
|
221 | { |
|
222 | 0 | simpleDateFormat.setTimeZone(timeZone); |
223 | 0 | } |
224 | ||
225 | /** |
|
226 | * Does the exact opposite of {{@link #convertDateFormat(String)} by converting |
|
227 | * the incoming java date format string into a POSIX compliant format string. |
|
228 | * @param pattern The java date format style format |
|
229 | * @return The converted format into something usable by POSIX strftime style parser/formatters. |
|
230 | */ |
|
231 | public static String convertToPosixFormat(String pattern) |
|
232 | { |
|
233 | 2 | if (pattern == null) return null; |
234 | ||
235 | 2 | StringBuffer buf = new StringBuffer(); |
236 | 2 | int start=-1; |
237 | ||
238 | 25 | for(int i = 0; i < pattern.length(); i++) { |
239 | 23 | char c = pattern.charAt(i); |
240 | ||
241 | // if in a definition |
|
242 | 23 | if (Character.isLetter(c)) { |
243 | 17 | if (start <= -1) start = i; |
244 | continue; |
|
245 | 6 | } else if (start >= 0) { |
246 | // we've hit the end of a definition |
|
247 | 6 | String conv = pattern.substring(start, i); |
248 | 6 | String match = pTranslate.getProperty(conv); |
249 | ||
250 | 6 | if (match == null) |
251 | 1 | buf.append(conv); // just append it, this shouldn't happen we hope |
252 | else |
|
253 | 5 | buf.append(match); |
254 | ||
255 | // reset |
|
256 | 6 | start=-1; |
257 | } |
|
258 | ||
259 | 6 | buf.append(c); |
260 | } |
|
261 | ||
262 | // grab last one, if any |
|
263 | 2 | if (start > -1) { |
264 | 2 | String conv = pattern.substring(start, pattern.length()); |
265 | 2 | String match = pTranslate.getProperty(conv); |
266 | 2 | if (match == null) buf.append(conv); |
267 | 2 | else buf.append(match); |
268 | } |
|
269 | ||
270 | 2 | return buf.toString(); |
271 | } |
|
272 | ||
273 | /** |
|
274 | * Search the provided pattern and get the C standard Date/Time formatting rules and convert |
|
275 | * them to the Java equivalent. |
|
276 | * |
|
277 | * @param pattern |
|
278 | * The pattern to search |
|
279 | * @return The modified pattern |
|
280 | */ |
|
281 | public static String convertDateFormat(String pattern) |
|
282 | { |
|
283 | 0 | boolean inside = false; |
284 | 0 | boolean mark = false; |
285 | 0 | boolean modifiedCommand = false; |
286 | ||
287 | 0 | StringBuffer buf = new StringBuffer(); |
288 | ||
289 | 0 | for(int i = 0; i < pattern.length(); i++) { |
290 | 0 | char c = pattern.charAt(i); |
291 | ||
292 | 0 | if (c == '%' && !mark) { |
293 | 0 | mark = true; |
294 | } else { |
|
295 | 0 | if (mark) { |
296 | 0 | if (modifiedCommand) { |
297 | // don't do anything--we just wanted to skip a char |
|
298 | 0 | modifiedCommand = false; |
299 | 0 | mark = false; |
300 | } else { |
|
301 | 0 | inside = translateCommand(buf, pattern, i, inside); |
302 | // It's a modifier code |
|
303 | 0 | if (c == 'O' || c == 'E') { |
304 | 0 | modifiedCommand = true; |
305 | } else { |
|
306 | 0 | mark = false; |
307 | } |
|
308 | } |
|
309 | } else { |
|
310 | 0 | if (!inside && c != ' ') { |
311 | // We start a literal, which we need to quote |
|
312 | 0 | buf.append("'"); |
313 | 0 | inside = true; |
314 | } |
|
315 | ||
316 | 0 | buf.append(c); |
317 | } |
|
318 | } |
|
319 | } |
|
320 | ||
321 | 0 | if (buf.length() > 0) { |
322 | 0 | char lastChar = buf.charAt(buf.length() - 1); |
323 | ||
324 | 0 | if (lastChar != '\'' && inside) { |
325 | 0 | buf.append('\''); |
326 | } |
|
327 | } |
|
328 | 0 | return buf.toString(); |
329 | } |
|
330 | ||
331 | private static String quote(String str, boolean insideQuotes) |
|
332 | { |
|
333 | 0 | String retVal = str; |
334 | 0 | if (!insideQuotes) { |
335 | 0 | retVal = '\'' + retVal + '\''; |
336 | } |
|
337 | 0 | return retVal; |
338 | } |
|
339 | ||
340 | /** |
|
341 | * try to get the Java Date/Time formating associated with the C standard provided. |
|
342 | * |
|
343 | * @param c |
|
344 | * The C equivalent to translate |
|
345 | * @return The Java formatting rule to use |
|
346 | */ |
|
347 | private static boolean translateCommand(StringBuffer buf, String pattern, int index, |
|
348 | boolean oldInside) |
|
349 | { |
|
350 | 0 | char firstChar = pattern.charAt(index); |
351 | 0 | boolean newInside = oldInside; |
352 | ||
353 | // O and E are modifiers, they mean to present an alternative representation of the next |
|
354 | // char |
|
355 | // we just handle the next char as if the O or E wasn't there |
|
356 | 0 | if (firstChar == 'O' || firstChar == 'E') { |
357 | 0 | if (index + 1 < pattern.length()) { |
358 | 0 | newInside = translateCommand(buf, pattern, index + 1, oldInside); |
359 | } else { |
|
360 | 0 | buf.append(quote("%" + firstChar, oldInside)); |
361 | } |
|
362 | } else { |
|
363 | 0 | String command = translate.getProperty(String.valueOf(firstChar)); |
364 | ||
365 | // If we don't find a format, treat it as a literal--That's what apache does |
|
366 | 0 | if (command == null) { |
367 | 0 | buf.append(quote("%" + firstChar, oldInside)); |
368 | } else { |
|
369 | // If we were inside quotes, close the quotes |
|
370 | 0 | if (oldInside) { |
371 | 0 | buf.append('\''); |
372 | } |
|
373 | 0 | buf.append(command); |
374 | 0 | newInside = false; |
375 | } |
|
376 | } |
|
377 | 0 | return newInside; |
378 | } |
|
379 | } |