001 /* 002 * Copyright 2006 The Apache Software Foundation. Licensed under the Apache License, Version 2.0 003 * (the "License"); you may not use this file except in compliance with the License. You may obtain 004 * a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 005 * law or agreed to in writing, software distributed under the License is distributed on an "AS IS" 006 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 007 * for the specific language governing permissions and limitations under the License. 008 */ 009 010 package org.apache.tapestry.util; 011 012 import java.text.ParseException; 013 import java.text.SimpleDateFormat; 014 import java.util.Date; 015 import java.util.Locale; 016 import java.util.Properties; 017 import java.util.TimeZone; 018 019 /** 020 * Converts dates to strings using the same format specifiers as strftime Note: This does not mimic 021 * strftime perfectly. Certain strftime commands, are not supported, and will convert as if they 022 * were literals. Certain complicated commands, like those dealing with the week of the year 023 * probably don't have exactly the same behavior as strftime. These limitations are due to use 024 * SimpleDateTime. If the conversion was done manually, all these limitations could be eliminated. 025 * The interface looks like a subset of DateFormat. Maybe someday someone will make this class 026 * extend DateFormat. 027 * 028 * <p> 029 * Added to tapestry in order to help with dojo/javascript date/time conversions. 030 * </p> 031 * 032 * @see "http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html" 033 * @author Bip Thelin 034 * @author Dan Sandberg 035 */ 036 public class Strftime 037 { 038 039 protected static Properties translate; 040 protected static Properties pTranslate; 041 protected SimpleDateFormat simpleDateFormat; 042 043 /** 044 * Initialize our pattern translation 045 */ 046 static { 047 translate = new Properties(); 048 translate.put("a", "EEE"); 049 translate.put("A", "EEEE"); 050 translate.put("b", "MMM"); 051 translate.put("B", "MMMM"); 052 translate.put("c", "EEE MMM d HH:mm:ss yyyy"); 053 054 // There's no way to specify the century in SimpleDateFormat. We don't want to hard-code 055 // 20 since this could be wrong for the pre-2000 files. 056 // translate.put("C", "20"); 057 translate.put("d", "dd"); 058 translate.put("D", "MM/dd/yy"); 059 translate.put("e", "dd"); // will show as '03' instead of ' 3' 060 translate.put("F", "yyyy-MM-dd"); 061 translate.put("g", "yy"); 062 translate.put("G", "yyyy"); 063 translate.put("H", "HH"); 064 translate.put("h", "MMM"); 065 translate.put("I", "hh"); 066 translate.put("j", "DDD"); 067 translate.put("k", "HH"); // will show as '07' instead of ' 7' 068 translate.put("l", "hh"); // will show as '07' instead of ' 7' 069 translate.put("m", "MM"); 070 translate.put("M", "mm"); 071 translate.put("n", "\n"); 072 translate.put("p", "a"); 073 translate.put("P", "a"); // will show as pm instead of PM 074 translate.put("r", "hh:mm:ss a"); 075 translate.put("R", "HH:mm"); 076 // There's no way to specify this with SimpleDateFormat 077 // translate.put("s","seconds since ecpoch"); 078 translate.put("S", "ss"); 079 translate.put("t", "\t"); 080 translate.put("T", "HH:mm:ss"); 081 // There's no way to specify this with SimpleDateFormat 082 // translate.put("u","day of week ( 1-7 )"); 083 084 // There's no way to specify this with SimpleDateFormat 085 // translate.put("U","week in year with first sunday as first day..."); 086 087 translate.put("V", "ww"); // I'm not sure this is always exactly the same 088 089 // There's no way to specify this with SimpleDateFormat 090 // translate.put("W","week in year with first monday as first day..."); 091 092 // There's no way to specify this with SimpleDateFormat 093 // translate.put("w","E"); 094 translate.put("X", "HH:mm:ss"); 095 translate.put("x", "MM/dd/yy"); 096 translate.put("y", "yy"); 097 translate.put("Y", "yyyy"); 098 translate.put("Z", "z"); 099 translate.put("z", "Z"); 100 translate.put("%", "%"); 101 102 pTranslate = new Properties(); 103 pTranslate.put("EEE", "%a"); 104 pTranslate.put("EEEE", "%A"); 105 pTranslate.put("MMM", "%b"); 106 pTranslate.put("MMMM", "%B"); 107 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 pTranslate.put("dd", "%d"); 113 pTranslate.put("MM/dd/yy", "%D"); 114 pTranslate.put("yyyy-MM-dd", "%F"); 115 pTranslate.put("yy", "%g"); 116 pTranslate.put("yyyy", "%G"); 117 pTranslate.put("HH", "%H"); 118 pTranslate.put("MMM", "%h"); 119 pTranslate.put("hh", "%I"); 120 pTranslate.put("DDD", "%j"); 121 pTranslate.put("MM", "%m"); 122 pTranslate.put("mm", "%M"); 123 pTranslate.put("\n", "%n"); 124 pTranslate.put("a", "%p"); // will show as pm instead of PM 125 pTranslate.put("hh:mm:ss a", "%r"); 126 pTranslate.put("HH:mm", "%R"); 127 // There's no way to specify this with SimpleDateFormat 128 // translate.put("s","seconds since ecpoch"); 129 pTranslate.put("ss", "%S"); 130 pTranslate.put("\t", "%t"); 131 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 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 pTranslate.put("HH:mm:ss", "%X"); 146 pTranslate.put("MM/dd/yy", "%x"); 147 pTranslate.put("yy", "%y"); 148 pTranslate.put("yyyy", "%Y"); 149 pTranslate.put("z", "%Z"); 150 pTranslate.put("Z", "%z"); 151 pTranslate.put("%", "%"); 152 } 153 154 /** 155 * Create an instance of this date formatting class. 156 * 157 * @see #Strftime( String, Locale ) 158 */ 159 public Strftime(String origFormat) 160 { 161 String convertedFormat = convertDateFormat(origFormat); 162 simpleDateFormat = new SimpleDateFormat(convertedFormat); 163 } 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 { 175 String convertedFormat = convertDateFormat(origFormat); 176 simpleDateFormat = new SimpleDateFormat(convertedFormat, locale); 177 } 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 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 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 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 simpleDateFormat.setTimeZone(timeZone); 223 } 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 if (pattern == null) return null; 234 235 StringBuffer buf = new StringBuffer(); 236 int start=-1; 237 238 for(int i = 0; i < pattern.length(); i++) { 239 char c = pattern.charAt(i); 240 241 // if in a definition 242 if (Character.isLetter(c)) { 243 if (start <= -1) start = i; 244 continue; 245 } else if (start >= 0) { 246 // we've hit the end of a definition 247 String conv = pattern.substring(start, i); 248 String match = pTranslate.getProperty(conv); 249 250 if (match == null) 251 buf.append(conv); // just append it, this shouldn't happen we hope 252 else 253 buf.append(match); 254 255 // reset 256 start=-1; 257 } 258 259 buf.append(c); 260 } 261 262 // grab last one, if any 263 if (start > -1) { 264 String conv = pattern.substring(start, pattern.length()); 265 String match = pTranslate.getProperty(conv); 266 if (match == null) buf.append(conv); 267 else buf.append(match); 268 } 269 270 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 boolean inside = false; 284 boolean mark = false; 285 boolean modifiedCommand = false; 286 287 StringBuffer buf = new StringBuffer(); 288 289 for(int i = 0; i < pattern.length(); i++) { 290 char c = pattern.charAt(i); 291 292 if (c == '%' && !mark) { 293 mark = true; 294 } else { 295 if (mark) { 296 if (modifiedCommand) { 297 // don't do anything--we just wanted to skip a char 298 modifiedCommand = false; 299 mark = false; 300 } else { 301 inside = translateCommand(buf, pattern, i, inside); 302 // It's a modifier code 303 if (c == 'O' || c == 'E') { 304 modifiedCommand = true; 305 } else { 306 mark = false; 307 } 308 } 309 } else { 310 if (!inside && c != ' ') { 311 // We start a literal, which we need to quote 312 buf.append("'"); 313 inside = true; 314 } 315 316 buf.append(c); 317 } 318 } 319 } 320 321 if (buf.length() > 0) { 322 char lastChar = buf.charAt(buf.length() - 1); 323 324 if (lastChar != '\'' && inside) { 325 buf.append('\''); 326 } 327 } 328 return buf.toString(); 329 } 330 331 private static String quote(String str, boolean insideQuotes) 332 { 333 String retVal = str; 334 if (!insideQuotes) { 335 retVal = '\'' + retVal + '\''; 336 } 337 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 char firstChar = pattern.charAt(index); 351 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 if (firstChar == 'O' || firstChar == 'E') { 357 if (index + 1 < pattern.length()) { 358 newInside = translateCommand(buf, pattern, index + 1, oldInside); 359 } else { 360 buf.append(quote("%" + firstChar, oldInside)); 361 } 362 } else { 363 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 if (command == null) { 367 buf.append(quote("%" + firstChar, oldInside)); 368 } else { 369 // If we were inside quotes, close the quotes 370 if (oldInside) { 371 buf.append('\''); 372 } 373 buf.append(command); 374 newInside = false; 375 } 376 } 377 return newInside; 378 } 379 }