001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/framework/util/DateUtil.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree. 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 http://www.geographie.uni-bonn.de/deegree/ 007 and 008 lat/lon GmbH 009 http://lat-lon.de/ 010 011 Additional copyright notes: 012 Code has partly been taken from JBoss. 013 JBoss, Home of Professional Open Source. 014 Copyright 2008, Red Hat Middleware LLC, and individual contributors 015 as indicated by the @author tags. See the copyright.txt file in the 016 distribution for a full listing of individual contributors. 017 018 This library is free software; you can redistribute it and/or modify it under 019 the terms of the GNU Lesser General Public License as published by the Free 020 Software Foundation; either version 2.1 of the License, or (at your option) 021 any later version. 022 This library is distributed in the hope that it will be useful, but WITHOUT 023 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 024 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 025 details. 026 You should have received a copy of the GNU Lesser General Public License 027 along with this library; if not, write to the Free Software Foundation, Inc., 028 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 029 030 Contact information: 031 032 lat/lon GmbH 033 Aennchenstr. 19, 53177 Bonn 034 Germany 035 036 Department of Geography, University of Bonn 037 Prof. Dr. Klaus Greve 038 Postfach 1147, 53001 Bonn 039 Germany 040 041 e-mail: info@deegree.org 042 ----------------------------------------------------------------------------*/ 043 package org.deegree.framework.util; 044 045 import java.text.DateFormat; 046 import java.text.ParseException; 047 import java.text.SimpleDateFormat; 048 import java.util.Calendar; 049 import java.util.Locale; 050 import java.util.TimeZone; 051 import java.util.regex.Matcher; 052 import java.util.regex.Pattern; 053 054 /** 055 * Utilities for working with dates. 056 * <p> 057 * Many of the methods that convert dates to and from strings utilize the <a 058 * href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601:2004</a> standard string format 059 * <code>yyyy-MM-ddTHH:mm:ss.SSSZ</code>, where <blockquote> 060 * 061 * <pre> 062 * Symbol Meaning Presentation Example 063 * ------ ------- ------------ ------- 064 * y year (Number) 1996 065 * M month in year (Number) 07 066 * d day in month (Number) 10 067 * h hour in am/pm (1˜12) (Number) 12 068 * H hour in day (0˜23) (Number) 0 069 * m minute in hour (Number) 30 070 * s second in minute (Number) 55 071 * S millisecond (Number) 978 072 * Z time zone (Number) -0600 073 * </pre> 074 * 075 * </blockquote> 076 * </p> 077 * <p> 078 * This class is written to be thread safe. As {@link SimpleDateFormat} is not threadsafe, no shared instances are used. 079 * </p> 080 * 081 * @author Randall Hauch 082 */ 083 public class DateUtil { 084 085 /** 086 * 087 */ 088 public static final String ISO_8601_2004_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; 089 090 /** 091 * 092 */ 093 public static final String ISO_8601_2004_FORMAT_UTC = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; 094 095 /** 096 * 097 */ 098 public static final String JDBC_TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"; 099 100 //private final static TimeZone GMT = TimeZone.getTimeZone( "GMT" ); 101 102 /** 103 * Parse the date contained in the supplied string and return a UTC Calendar object. The date must follow one of the 104 * standard ISO 8601 formats, of the form <code><i>datepart</i>T<i>timepart</i></code>, where 105 * <code><i>datepart</i></code> is one of the following forms: 106 * <p> 107 * <dl> 108 * <dt>YYYYMMDD</dt> 109 * <dd>The 4-digit year, the 2-digit month (00-12), and the 2-digit day of the month (00-31). The month and day are 110 * optional, but the month is required if the day is given.</dd> 111 * <dt>YYYY-MM-DD</dt> 112 * <dd>The 4-digit year, the 2-digit month (00-12), and the 2-digit day of the month (00-31). The month and day are 113 * optional, but the month is required if the day is given.</dd> 114 * <dt>YYYY-Www-D</dt> 115 * <dd>The 4-digit year followed by 'W', the 2-digit week number (00-53), and the day of the week (1-7). The day of 116 * week number is optional.</dd> 117 * <dt>YYYYWwwD</dt> 118 * <dd>The 4-digit year followed by 'W', the 2-digit week number (00-53), and the day of the week (1-7). The day of 119 * week number is optional.</dd> 120 * <dt>YYYY-DDD</dt> 121 * <dd>The 4-digit year followed by the 3-digit day of the year (000-365)</dd> 122 * <dt>YYYYDDD</dt> 123 * <dd>The 4-digit year followed by the 3-digit day of the year (000-365)</dd> 124 * </dl> 125 * </p> 126 * <p> 127 * The <code><i>timepart</i></code> consists of one of the following forms that contain the 2-digit hour (00-24), 128 * the 2-digit minutes (00-59), the 2-digit seconds (00-59), and the 1-to-3 digit milliseconds. The minutes, seconds 129 * and milliseconds are optional, but any component is required if it is followed by another component (e.g., 130 * minutes are required if the seconds are given). 131 * <dl> 132 * <dt>hh:mm:ss.SSS</dt> 133 * <dt>hhmmssSSS</dt> 134 * </dl> 135 * </p> 136 * <p> 137 * followed by one of the following time zone definitions: 138 * <dt>Z</dt> 139 * <dd>The uppercase or lowercase 'Z' to denote UTC time</dd> 140 * <dt>±hh:mm</dt> 141 * <dd>The 2-digit hour and the 2-digit minute offset from UTC</dd> 142 * <dt>±hhmm</dt> 143 * <dd>The 2-digit hour and the 2-digit minute offset from UTC</dd> 144 * <dt>±hh</dt> 145 * <dd>The 2-digit hour offset from UTC</dd> 146 * <dt>hh:mm</dt> 147 * <dd>The 2-digit hour and the 2-digit minute offset from UTC</dd> 148 * <dt>hhmm</dt> 149 * <dd>The 2-digit hour and the 2-digit minute offset from UTC</dd> 150 * <dt>hh</dt> 151 * <dd>The 2-digit hour offset from UTC</dd> 152 * </dl> 153 * </p> 154 * 155 * @param dateString 156 * the string containing the date to be parsed 157 * @return the parsed date as a {@link Calendar} object. The return value is always in UTC time zone. Conversion 158 * occurs when necessary. 159 * @throws ParseException 160 * if there is a problem parsing the string 161 */ 162 public static Calendar parseISO8601Date( final String dateString ) 163 throws ParseException { 164 // Example: 2008-02-16T12:30:45.123-0600 165 // Example: 2008-W06-6 166 // Example: 2008-053 167 // 168 // Group Optional Field Description 169 // ----- -------- --------- ------------------------------------------ 170 // 1 no 2008 4 digit year as a number 171 // 2 yes 02-16 or W06-6 or 053 172 // 3 yes W06-6 173 // 4 yes 06 2 digit week number (00-59) 174 // 5 yes 6 1 digit day of week as a number (1-7) 175 // 6 yes 02-16 176 // 7 yes 02 2 digit month as a number (00-19) 177 // 8 yes -16 178 // 9 yes 16 2 digit day of month as a number (00-39) 179 // 10 yes 02 2 digit month as a number (00-19) 180 // 11 yes 16 2 digit day of month as a number (00-39) 181 // 12 yes 234 3 digit day of year as a number (000-399) 182 // 13 yes T12:30:45.123-0600 183 // 14 yes 12 2 digit hour as a number (00-29) 184 // 15 yes 30 2 digit minute as a number (00-59) 185 // 16 yes :45.123 186 // 17 yes 45 2 digit second as a number (00-59) 187 // 18 yes .123 188 // 19 yes 123 1, 2 or 3 digit milliseconds as a number (000-999) 189 // 20 yes -0600 190 // 21 yes Z The letter 'Z' if in UTC 191 // 22 yes -06 1 or 2 digit time zone hour offset as a signed number 192 // 23 yes + the plus or minus in the time zone offset 193 // 24 yes 00 1 or 2 digit time zone hour offset as an unsigned number (00-29) 194 // 25 yes 00 1 or 2 digit time zone minute offset as a number (00-59) 195 final String regex = "^(\\d{4})-?(([wW]([012345]\\d)-?([1234567])?)|(([01]\\d)(-([0123]\\d))?)|([01]\\d)([0123]\\d)|([0123]\\d\\d))?(T([012]\\d):?([012345]\\d)?(:?([012345]\\d)(.(\\d{1,3}))?)?)?((Z)|(([+-])(\\d{2})):?(\\d{2})?)?$"; 196 final Pattern pattern = Pattern.compile( regex ); 197 final Matcher matcher = pattern.matcher( dateString ); 198 if ( !matcher.matches() ) { 199 throw new ParseException( "error while parsing iso8601 date: " + dateString, 0 ); 200 } 201 String year = matcher.group( 1 ); 202 String week = matcher.group( 4 ); 203 String dayOfWeek = matcher.group( 5 ); 204 String month = matcher.group( 7 ); 205 if ( month == null ) 206 month = matcher.group( 10 ); 207 String dayOfMonth = matcher.group( 9 ); 208 if ( dayOfMonth == null ) 209 dayOfMonth = matcher.group( 11 ); 210 String dayOfYear = matcher.group( 12 ); 211 String hourOfDay = matcher.group( 14 ); 212 String minutesOfHour = matcher.group( 15 ); 213 String seconds = matcher.group( 17 ); 214 String milliseconds = matcher.group( 19 ); 215 String timeZoneSign = matcher.group( 23 ); 216 String timeZoneHour = matcher.group( 24 ); 217 String timeZoneMinutes = matcher.group( 25 ); 218 219 boolean localeTime = true; 220 if ( timeZoneHour != null ) { 221 localeTime = false; 222 } 223 if ( matcher.group( 21 ) != null ) { 224 localeTime = false; 225 timeZoneHour = "00"; 226 timeZoneMinutes = "00"; 227 } 228 229 // Create the calendar object and start setting the fields ... 230 Calendar calendar; 231 if ( localeTime ) { 232 calendar = Calendar.getInstance(); 233 } else { 234 calendar = Calendar.getInstance( TimeZone.getTimeZone( "GMT" ) ); 235 } 236 calendar.clear(); 237 238 // And start setting the fields. Note that Integer.parseInt should never fail, since we're checking for null and 239 // the 240 // regular expression should only have digits in these strings! 241 if ( year != null ) 242 calendar.set( Calendar.YEAR, Integer.parseInt( year ) ); 243 if ( month != null ) { 244 calendar.set( Calendar.MONTH, Integer.parseInt( month ) - 1 ); // month is zero-based! 245 if ( dayOfMonth != null ) 246 calendar.set( Calendar.DAY_OF_MONTH, Integer.parseInt( dayOfMonth ) ); 247 } else if ( week != null ) { 248 calendar.set( Calendar.WEEK_OF_YEAR, Integer.parseInt( week ) ); 249 if ( dayOfWeek != null ) 250 calendar.set( Calendar.DAY_OF_WEEK, Integer.parseInt( dayOfWeek ) ); 251 } else if ( dayOfYear != null ) { 252 calendar.set( Calendar.DAY_OF_YEAR, Integer.parseInt( dayOfYear ) ); 253 } 254 if ( hourOfDay != null ) 255 calendar.set( Calendar.HOUR_OF_DAY, Integer.parseInt( hourOfDay ) ); 256 if ( minutesOfHour != null ) 257 calendar.set( Calendar.MINUTE, Integer.parseInt( minutesOfHour ) ); 258 if ( seconds != null ) 259 calendar.set( Calendar.SECOND, Integer.parseInt( seconds ) ); 260 if ( milliseconds != null ) 261 calendar.set( Calendar.MILLISECOND, Integer.parseInt( milliseconds ) ); 262 if ( timeZoneHour != null ) { 263 int zoneOffsetInMillis = Integer.parseInt( timeZoneHour ) * 60 * 60 * 1000; 264 if ( "-".equals( timeZoneSign ) ) 265 zoneOffsetInMillis *= -1; 266 if ( timeZoneMinutes != null ) { 267 int minuteOffsetInMillis = Integer.parseInt( timeZoneMinutes ) * 60 * 1000; 268 if ( zoneOffsetInMillis < 0 ) { 269 zoneOffsetInMillis -= minuteOffsetInMillis; 270 } else { 271 zoneOffsetInMillis += minuteOffsetInMillis; 272 } 273 } 274 calendar.set( Calendar.ZONE_OFFSET, zoneOffsetInMillis ); 275 } 276 // convert to utc time 277 Calendar result = Calendar.getInstance( TimeZone.getTimeZone( "GMT" ) ); 278 result.setTime( calendar.getTime() ); 279 return result; 280 } 281 282 /** 283 * Obtain an ISO 8601:2004 string representation of the date given the supplied milliseconds since the epoch. 284 * 285 * @param date 286 * the date in calendar form 287 * @return the string in the {@link #ISO_8601_2004_FORMAT standard format} 288 */ 289 public static String formatISO8601Date( final Calendar date ) { 290 if ( date.getTimeZone().getRawOffset() == 0 ) { 291 return new SimpleDateFormat( ISO_8601_2004_FORMAT_UTC ).format( date.getTime() ); 292 } 293 return formatISO8601Date( date.getTime() ); 294 } 295 296 /** 297 * Obtain an ISO 8601:2004 string representation of the supplied date. 298 * 299 * @param date 300 * the date 301 * @return the string in the {@link #ISO_8601_2004_FORMAT standard format} 302 */ 303 public static String formatISO8601Date( final java.util.Date date ) { 304 if ( date == null ) { 305 return ""; 306 } 307 return new SimpleDateFormat( ISO_8601_2004_FORMAT ).format( date ); 308 } 309 310 /** 311 * Obtain an JDBC timestamp string representation of the supplied date. 312 * 313 * @param date 314 * the date 315 * @return the string in the JDBC timestamp format 316 */ 317 public static String formatJDBCTimeStamp( final java.util.Date date ) { 318 return new SimpleDateFormat( JDBC_TIMESTAMP_FORMAT ).format( date ); 319 } 320 321 /** 322 * Return a string representation of the supplied date with the current default locale. 323 * 324 * @param date 325 * @return the string in locale format 326 */ 327 public static String formatLocaleDate( final java.util.Date date ) { 328 return formatLocaleDate( date, Locale.getDefault() ); 329 } 330 331 /** 332 * Return a string representation of the supplied date with the supplied locale. 333 * 334 * @param date 335 * @param locale 336 * @return the string in locale format 337 */ 338 public static String formatLocaleDate( final java.util.Date date, Locale locale ) { 339 return DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.LONG, locale ).format( date ); 340 } 341 342 private DateUtil() { 343 // Prevent instantiation 344 } 345 346 }