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 }