Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
Strftime |
|
| 3.7;3.7 |
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 | 0 | translate = new Properties(); |
48 | 0 | translate.put("a", "EEE"); |
49 | 0 | translate.put("A", "EEEE"); |
50 | 0 | translate.put("b", "MMM"); |
51 | 0 | translate.put("B", "MMMM"); |
52 | 0 | 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 | 0 | translate.put("d", "dd"); |
58 | 0 | translate.put("D", "MM/dd/yy"); |
59 | 0 | translate.put("e", "dd"); // will show as '03' instead of ' 3' |
60 | 0 | translate.put("F", "yyyy-MM-dd"); |
61 | 0 | translate.put("g", "yy"); |
62 | 0 | translate.put("G", "yyyy"); |
63 | 0 | translate.put("H", "HH"); |
64 | 0 | translate.put("h", "MMM"); |
65 | 0 | translate.put("I", "hh"); |
66 | 0 | translate.put("j", "DDD"); |
67 | 0 | translate.put("k", "HH"); // will show as '07' instead of ' 7' |
68 | 0 | translate.put("l", "hh"); // will show as '07' instead of ' 7' |
69 | 0 | translate.put("m", "MM"); |
70 | 0 | translate.put("M", "mm"); |
71 | 0 | translate.put("n", "\n"); |
72 | 0 | translate.put("p", "a"); |
73 | 0 | translate.put("P", "a"); // will show as pm instead of PM |
74 | 0 | translate.put("r", "hh:mm:ss a"); |
75 | 0 | translate.put("R", "HH:mm"); |
76 | // There's no way to specify this with SimpleDateFormat | |
77 | // translate.put("s","seconds since ecpoch"); | |
78 | 0 | translate.put("S", "ss"); |
79 | 0 | translate.put("t", "\t"); |
80 | 0 | 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 | 0 | 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 | 0 | translate.put("X", "HH:mm:ss"); |
95 | 0 | translate.put("x", "MM/dd/yy"); |
96 | 0 | translate.put("y", "yy"); |
97 | 0 | translate.put("Y", "yyyy"); |
98 | 0 | translate.put("Z", "z"); |
99 | 0 | translate.put("z", "Z"); |
100 | 0 | translate.put("%", "%"); |
101 | ||
102 | 0 | pTranslate = new Properties(); |
103 | 0 | pTranslate.put("EEE", "%a"); |
104 | 0 | pTranslate.put("EEEE", "%A"); |
105 | 0 | pTranslate.put("MMM", "%b"); |
106 | 0 | pTranslate.put("MMMM", "%B"); |
107 | 0 | 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 | 0 | pTranslate.put("dd", "%d"); |
113 | 0 | pTranslate.put("MM/dd/yy", "%D"); |
114 | 0 | pTranslate.put("yyyy-MM-dd", "%F"); |
115 | 0 | pTranslate.put("yy", "%g"); |
116 | 0 | pTranslate.put("yyyy", "%G"); |
117 | 0 | pTranslate.put("HH", "%H"); |
118 | 0 | pTranslate.put("MMM", "%h"); |
119 | 0 | pTranslate.put("hh", "%I"); |
120 | 0 | pTranslate.put("DDD", "%j"); |
121 | 0 | pTranslate.put("MM", "%m"); |
122 | 0 | pTranslate.put("mm", "%M"); |
123 | 0 | pTranslate.put("\n", "%n"); |
124 | 0 | pTranslate.put("a", "%p"); // will show as pm instead of PM |
125 | 0 | pTranslate.put("hh:mm:ss a", "%r"); |
126 | 0 | pTranslate.put("HH:mm", "%R"); |
127 | // There's no way to specify this with SimpleDateFormat | |
128 | // translate.put("s","seconds since ecpoch"); | |
129 | 0 | pTranslate.put("ss", "%S"); |
130 | 0 | pTranslate.put("\t", "%t"); |
131 | 0 | 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 | 0 | 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 | 0 | pTranslate.put("HH:mm:ss", "%X"); |
146 | 0 | pTranslate.put("MM/dd/yy", "%x"); |
147 | 0 | pTranslate.put("yy", "%y"); |
148 | 0 | pTranslate.put("yyyy", "%Y"); |
149 | 0 | pTranslate.put("z", "%Z"); |
150 | 0 | pTranslate.put("Z", "%z"); |
151 | 0 | pTranslate.put("%", "%"); |
152 | 0 | } |
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 | 0 | if (pattern == null) return null; |
234 | ||
235 | 0 | StringBuffer buf = new StringBuffer(); |
236 | 0 | int start=-1; |
237 | ||
238 | 0 | for(int i = 0; i < pattern.length(); i++) { |
239 | 0 | char c = pattern.charAt(i); |
240 | ||
241 | // if in a definition | |
242 | 0 | if (Character.isLetter(c)) { |
243 | 0 | if (start <= -1) start = i; |
244 | continue; | |
245 | 0 | } else if (start >= 0) { |
246 | // we've hit the end of a definition | |
247 | 0 | String conv = pattern.substring(start, i); |
248 | 0 | String match = pTranslate.getProperty(conv); |
249 | ||
250 | 0 | if (match == null) |
251 | 0 | buf.append(conv); // just append it, this shouldn't happen we hope |
252 | else | |
253 | 0 | buf.append(match); |
254 | ||
255 | // reset | |
256 | 0 | start=-1; |
257 | } | |
258 | ||
259 | 0 | buf.append(c); |
260 | } | |
261 | ||
262 | // grab last one, if any | |
263 | 0 | if (start > -1) { |
264 | 0 | String conv = pattern.substring(start, pattern.length()); |
265 | 0 | String match = pTranslate.getProperty(conv); |
266 | 0 | if (match == null) buf.append(conv); |
267 | 0 | else buf.append(match); |
268 | } | |
269 | ||
270 | 0 | 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 | } |