Tue, 10 Feb 2015 19:58:00 +0100
Upgrade the upgraded ical4j component to use org.apache.commons.lang3.
1 /**
2 * Copyright (c) 2012, Ben Fortuna
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * o Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * o Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * o Neither the name of Ben Fortuna nor the names of any other contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 package net.fortuna.ical4j.model;
34 import java.text.DateFormat;
35 import java.text.ParseException;
36 import java.text.SimpleDateFormat;
37 import java.util.Map;
38 import java.util.WeakHashMap;
40 import net.fortuna.ical4j.util.CompatibilityHints;
41 import net.fortuna.ical4j.util.Dates;
42 import net.fortuna.ical4j.util.TimeZones;
44 import org.apache.commons.lang3.builder.EqualsBuilder;
46 /**
47 * $Id$
48 *
49 * Created on 26/06/2005
50 *
51 * Represents a time of day on a specific date.
52 *
53 * <pre>
54 * 4.3.5 Date-Time
55 *
56 * Value Name: DATE-TIME
57 *
58 * Purpose: This value type is used to identify values that specify a
59 * precise calendar date and time of day.
60 *
61 * Formal Definition: The value type is defined by the following
62 * notation:
63 *
64 * date-time = date "T" time ;As specified in the date and time
65 * ;value definitions
66 *
67 * Description: If the property permits, multiple "date-time" values are
68 * specified as a COMMA character (US-ASCII decimal 44) separated list
69 * of values. No additional content value encoding (i.e., BACKSLASH
70 * character encoding) is defined for this value type.
71 *
72 * The "DATE-TIME" data type is used to identify values that contain a
73 * precise calendar date and time of day. The format is based on the
74 * [ISO 8601] complete representation, basic format for a calendar date
75 * and time of day. The text format is a concatenation of the "date",
76 * followed by the LATIN CAPITAL LETTER T character (US-ASCII decimal
77 * 84) time designator, followed by the "time" format.
78 *
79 * The "DATE-TIME" data type expresses time values in three forms:
80 *
81 * The form of date and time with UTC offset MUST NOT be used. For
82 * example, the following is not valid for a date-time value:
83 *
84 * DTSTART:19980119T230000-0800 ;Invalid time format
85 *
86 * FORM #1: DATE WITH LOCAL TIME
87 *
88 * The date with local time form is simply a date-time value that does
89 * not contain the UTC designator nor does it reference a time zone. For
90 * example, the following represents Janurary 18, 1998, at 11 PM:
91 *
92 * DTSTART:19980118T230000
93 *
94 * Date-time values of this type are said to be "floating" and are not
95 * bound to any time zone in particular. They are used to represent the
96 * same hour, minute, and second value regardless of which time zone is
97 * currently being observed. For example, an event can be defined that
98 * indicates that an individual will be busy from 11:00 AM to 1:00 PM
99 * every day, no matter which time zone the person is in. In these
100 * cases, a local time can be specified. The recipient of an iCalendar
101 * object with a property value consisting of a local time, without any
102 * relative time zone information, SHOULD interpret the value as being
103 * fixed to whatever time zone the ATTENDEE is in at any given moment.
104 * This means that two ATTENDEEs, in different time zones, receiving the
105 * same event definition as a floating time, may be participating in the
106 * event at different actual times. Floating time SHOULD only be used
107 * where that is the reasonable behavior.
108 *
109 * In most cases, a fixed time is desired. To properly communicate a
110 * fixed time in a property value, either UTC time or local time with
111 * time zone reference MUST be specified.
112 *
113 * The use of local time in a DATE-TIME value without the TZID property
114 * parameter is to be interpreted as floating time, regardless of the
115 * existence of "VTIMEZONE" calendar components in the iCalendar object.
116 *
117 * FORM #2: DATE WITH UTC TIME
118 *
119 * The date with UTC time, or absolute time, is identified by a LATIN
120 * CAPITAL LETTER Z suffix character (US-ASCII decimal 90), the UTC
121 * designator, appended to the time value. For example, the following
122 * represents January 19, 1998, at 0700 UTC:
123 *
124 * DTSTART:19980119T070000Z
125 *
126 * The TZID property parameter MUST NOT be applied to DATE-TIME
127 * properties whose time values are specified in UTC.
128 *
129 * FORM #3: DATE WITH LOCAL TIME AND TIME ZONE REFERENCE
130 *
131 * The date and local time with reference to time zone information is
132 * identified by the use the TZID property parameter to reference the
133 * appropriate time zone definition. TZID is discussed in detail in the
134 * section on Time Zone. For example, the following represents 2 AM in
135 * New York on Janurary 19, 1998:
136 *
137 * DTSTART;TZID=US-Eastern:19980119T020000
138 *
139 * Example: The following represents July 14, 1997, at 1:30 PM in New
140 * York City in each of the three time formats, using the "DTSTART"
141 * property.
142 *
143 * DTSTART:19970714T133000 ;Local time
144 * DTSTART:19970714T173000Z ;UTC time
145 * DTSTART;TZID=US-Eastern:19970714T133000 ;Local time and time
146 * ; zone reference
147 *
148 * A time value MUST ONLY specify 60 seconds when specifying the
149 * periodic "leap second" in the time value. For example:
150 *
151 * COMPLETED:19970630T235960Z
152 * </pre>
153 *
154 * @author Ben Fortuna
155 */
156 public class DateTime extends Date {
158 private static final long serialVersionUID = -6407231357919440387L;
160 private static final String DEFAULT_PATTERN = "yyyyMMdd'T'HHmmss";
162 private static final String UTC_PATTERN = "yyyyMMdd'T'HHmmss'Z'";
164 private static final String VCARD_PATTERN = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'";
166 private static final String RELAXED_PATTERN = "yyyyMMdd";
168 /**
169 * Used for parsing times in a UTC date-time representation.
170 */
171 private static final DateFormatCache UTC_FORMAT;
172 static {
173 final DateFormat format = new SimpleDateFormat(UTC_PATTERN);
174 format.setTimeZone(TimeZones.getUtcTimeZone());
175 format.setLenient(false);
177 UTC_FORMAT = new DateFormatCache(format);
178 }
180 /**
181 * Used for parsing times in a local date-time representation.
182 */
183 private static final DateFormatCache DEFAULT_FORMAT;
184 static {
185 final DateFormat format = new SimpleDateFormat(DEFAULT_PATTERN);
186 format.setLenient(false);
187 DEFAULT_FORMAT = new DateFormatCache(format);
188 }
190 private static final DateFormatCache LENIENT_DEFAULT_FORMAT;
191 static {
192 final DateFormat format = new SimpleDateFormat(DEFAULT_PATTERN);
193 LENIENT_DEFAULT_FORMAT = new DateFormatCache(format);
194 }
196 private static final DateFormatCache RELAXED_FORMAT;
197 static {
198 final DateFormat format = new SimpleDateFormat(RELAXED_PATTERN);
199 format.setLenient(true);
200 RELAXED_FORMAT = new DateFormatCache(format);
201 }
203 private static final DateFormatCache VCARD_FORMAT;
204 static {
205 final DateFormat format = new SimpleDateFormat(VCARD_PATTERN);
206 VCARD_FORMAT = new DateFormatCache(format);
207 }
209 private Time time;
211 private TimeZone timezone;
213 /**
214 * Default constructor.
215 */
216 public DateTime() {
217 super(Dates.PRECISION_SECOND, java.util.TimeZone.getDefault());
218 this.time = new Time(getTime(), getFormat().getTimeZone());
219 }
221 /**
222 * @param utc
223 * indicates if the date is in UTC time
224 */
225 public DateTime(final boolean utc) {
226 this();
227 setUtc(utc);
228 }
230 /**
231 * @param time
232 * a date-time value in milliseconds
233 */
234 public DateTime(final long time) {
235 super(time, Dates.PRECISION_SECOND, java.util.TimeZone.getDefault());
236 this.time = new Time(time, getFormat().getTimeZone());
237 }
239 /**
240 * @param date
241 * a date-time value
242 */
243 public DateTime(final java.util.Date date) {
244 super(date.getTime(), Dates.PRECISION_SECOND, java.util.TimeZone.getDefault());
245 this.time = new Time(date.getTime(), getFormat().getTimeZone());
246 // copy timezone information if applicable..
247 if (date instanceof DateTime) {
248 final DateTime dateTime = (DateTime) date;
249 if (dateTime.isUtc()) {
250 setUtc(true);
251 } else {
252 setTimeZone(dateTime.getTimeZone());
253 }
254 }
255 }
257 /**
258 * Constructs a new DateTime instance from parsing the specified string
259 * representation in the default (local) timezone.
260 *
261 * @param value
262 * a string representation of a date-time
263 * @throws ParseException
264 * where the specified string is not a valid date-time
265 */
266 public DateTime(final String value) throws ParseException {
267 this(value, null);
268 /*
269 * long time = 0; try { synchronized (UTC_FORMAT) { time =
270 * UTC_FORMAT.parse(value).getTime(); } setUtc(true); } catch
271 * (ParseException pe) { synchronized (DEFAULT_FORMAT) {
272 * DEFAULT_FORMAT.setTimeZone(getFormat().getTimeZone()); time =
273 * DEFAULT_FORMAT.parse(value).getTime(); } this.time = new Time(time,
274 * getFormat().getTimeZone()); } setTime(time);
275 */
276 }
278 /**
279 * Creates a new date-time instance from the specified value in the given
280 * timezone. If a timezone is not specified, the default timezone (as
281 * returned by {@link java.util.TimeZone#getDefault()}) is used.
282 *
283 * @param value
284 * a string representation of a date-time
285 * @param timezone
286 * the timezone for the date-time instance
287 * @throws ParseException
288 * where the specified string is not a valid date-time
289 */
290 public DateTime(final String value, final TimeZone timezone)
291 throws ParseException {
292 // setting the time to 0 since we are going to reset it anyway
293 super(0, Dates.PRECISION_SECOND, timezone != null ? timezone
294 : java.util.TimeZone.getDefault());
295 this.time = new Time(getTime(), getFormat().getTimeZone());
297 try {
298 if (value.endsWith("Z")) {
299 setTime(value, (DateFormat) UTC_FORMAT.get(), null);
300 setUtc(true);
301 } else {
302 if (timezone != null) {
303 setTime(value, (DateFormat) DEFAULT_FORMAT.get(), timezone);
304 } else {
305 // Use lenient parsing for floating times. This is to
306 // overcome
307 // the problem of parsing VTimeZone dates that specify dates
308 // that the strict parser does not accept.
309 setTime(value, (DateFormat) LENIENT_DEFAULT_FORMAT.get(),
310 getFormat().getTimeZone());
311 }
312 setTimeZone(timezone);
313 }
314 } catch (ParseException pe) {
315 if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_VCARD_COMPATIBILITY)) {
317 try {
318 setTime(value, (DateFormat) VCARD_FORMAT.get(), timezone);
319 setTimeZone(timezone);
320 } catch (ParseException pe2) {
321 if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) {
322 setTime(value, (DateFormat) RELAXED_FORMAT.get(), timezone);
323 setTimeZone(timezone);
324 }
325 }
326 } else if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) {
327 setTime(value, (DateFormat) RELAXED_FORMAT.get(), timezone);
328 setTimeZone(timezone);
329 } else {
330 throw pe;
331 }
332 }
333 }
335 /**
336 * @param value
337 * a string representation of a date-time
338 * @param pattern
339 * a pattern to apply when parsing the date-time value
340 * @param timezone
341 * the timezone for the date-time instance
342 * @throws ParseException
343 * where the specified string is not a valid date-time
344 */
345 public DateTime(String value, String pattern, TimeZone timezone)
346 throws ParseException {
347 // setting the time to 0 since we are going to reset it anyway
348 super(0, Dates.PRECISION_SECOND, timezone != null ? timezone
349 : java.util.TimeZone.getDefault());
350 this.time = new Time(getTime(), getFormat().getTimeZone());
352 final DateFormat format = CalendarDateFormatFactory
353 .getInstance(pattern);
354 setTime(value, format, timezone);
355 }
357 /**
358 * @param value
359 * a string representation of a date-time
360 * @param pattern
361 * a pattern to apply when parsing the date-time value
362 * @param utc
363 * indicates whether the date-time is in UTC time
364 * @throws ParseException
365 * where the specified string is not a valid date-time
366 */
367 public DateTime(String value, String pattern, boolean utc)
368 throws ParseException {
369 // setting the time to 0 since we are going to reset it anyway
370 this(0);
371 final DateFormat format = CalendarDateFormatFactory
372 .getInstance(pattern);
373 if (utc) {
374 setTime(value, format,
375 ((DateFormat) UTC_FORMAT.get()).getTimeZone());
376 } else {
377 setTime(value, format, null);
378 }
379 setUtc(utc);
380 }
382 /**
383 * Internal set of time by parsing value string.
384 *
385 * @param value
386 * @param format
387 * a {@code DateFormat}, protected by the use of a ThreadLocal.
388 * @param tz
389 * @throws ParseException
390 */
391 private void setTime(final String value, final DateFormat format,
392 final java.util.TimeZone tz) throws ParseException {
394 if (tz != null) {
395 format.setTimeZone(tz);
396 }
397 setTime(format.parse(value).getTime());
398 }
400 /**
401 * {@inheritDoc}
402 */
403 public final void setTime(final long time) {
404 super.setTime(time);
405 // need to check for null time due to Android java.util.Date(long)
406 // constructor
407 // calling this method..
408 if (this.time != null) {
409 this.time.setTime(time);
410 }
411 }
413 /**
414 * @return Returns the utc.
415 */
416 public final boolean isUtc() {
417 return time.isUtc();
418 }
420 /**
421 * Updates this date-time to display in UTC time if the argument is true.
422 * Otherwise, resets to the default timezone.
423 *
424 * @param utc
425 * The utc to set.
426 */
427 public final void setUtc(final boolean utc) {
428 // reset the timezone associated with this instance..
429 this.timezone = null;
430 if (utc) {
431 getFormat().setTimeZone(TimeZones.getUtcTimeZone());
432 } else {
433 resetTimeZone();
434 }
435 time = new Time(time, getFormat().getTimeZone(), utc);
436 }
438 /**
439 * Sets the timezone associated with this date-time instance. If the
440 * specified timezone is null, it will reset to the default timezone. If the
441 * date-time instance is utc, it will turn into either a floating (no
442 * timezone) date-time, or a date-time with a timezone.
443 *
444 * @param timezone
445 * a timezone to apply to the instance
446 */
447 public final void setTimeZone(final TimeZone timezone) {
448 this.timezone = timezone;
449 if (timezone != null) {
450 getFormat().setTimeZone(timezone);
451 } else {
452 resetTimeZone();
453 }
454 time = new Time(time, getFormat().getTimeZone(), false);
455 }
457 /**
458 * Reset the timezone to default.
459 */
460 private void resetTimeZone() {
461 // use GMT timezone to avoid daylight savings rules affecting floating
462 // time values..
463 getFormat().setTimeZone(TimeZone.getDefault());
464 // getFormat().setTimeZone(TimeZone.getTimeZone(TimeZones.GMT_ID));
465 }
467 /**
468 * Returns the current timezone associated with this date-time value.
469 *
470 * @return a Java timezone
471 */
472 public final TimeZone getTimeZone() {
473 return timezone;
474 }
476 /**
477 * {@inheritDoc}
478 */
479 public final String toString() {
480 final StringBuffer b = new StringBuffer(super.toString());
481 b.append('T');
482 b.append(time.toString());
483 return b.toString();
484 }
486 /**
487 * {@inheritDoc}
488 */
489 public boolean equals(final Object arg0) {
490 // TODO: what about compareTo, before, after, etc.?
492 if (arg0 instanceof DateTime) {
493 return new EqualsBuilder().append(time, ((DateTime) arg0).time)
494 .isEquals();
495 }
496 return super.equals(arg0);
497 }
499 /**
500 * {@inheritDoc}
501 */
502 public int hashCode() {
503 return super.hashCode();
504 }
506 private static class DateFormatCache {
508 private final Map threadMap = new WeakHashMap();
510 private final DateFormat templateFormat;
512 private DateFormatCache(DateFormat dateFormat) {
513 this.templateFormat = dateFormat;
514 }
516 public DateFormat get() {
517 DateFormat dateFormat = (DateFormat) threadMap.get(Thread
518 .currentThread());
519 if (dateFormat == null) {
520 dateFormat = (DateFormat) templateFormat.clone();
521 threadMap.put(Thread.currentThread(), dateFormat);
522 }
523 return dateFormat;
524 }
525 }
526 }