src/net/fortuna/ical4j/model/DateTime.java

changeset 0
fb9019fb1bf7
child 3
73bdfa70b04e
equal deleted inserted replaced
-1:000000000000 0:77392570fe49
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;
33
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;
39
40 import net.fortuna.ical4j.util.CompatibilityHints;
41 import net.fortuna.ical4j.util.Dates;
42 import net.fortuna.ical4j.util.TimeZones;
43
44 import org.apache.commons.lang.builder.EqualsBuilder;
45 import org.apache.commons.lang.builder.HashCodeBuilder;
46
47 /**
48 * $Id$
49 *
50 * Created on 26/06/2005
51 *
52 * Represents a time of day on a specific date.
53 *
54 * <pre>
55 * 4.3.5 Date-Time
56 *
57 * Value Name: DATE-TIME
58 *
59 * Purpose: This value type is used to identify values that specify a
60 * precise calendar date and time of day.
61 *
62 * Formal Definition: The value type is defined by the following
63 * notation:
64 *
65 * date-time = date "T" time ;As specified in the date and time
66 * ;value definitions
67 *
68 * Description: If the property permits, multiple "date-time" values are
69 * specified as a COMMA character (US-ASCII decimal 44) separated list
70 * of values. No additional content value encoding (i.e., BACKSLASH
71 * character encoding) is defined for this value type.
72 *
73 * The "DATE-TIME" data type is used to identify values that contain a
74 * precise calendar date and time of day. The format is based on the
75 * [ISO 8601] complete representation, basic format for a calendar date
76 * and time of day. The text format is a concatenation of the "date",
77 * followed by the LATIN CAPITAL LETTER T character (US-ASCII decimal
78 * 84) time designator, followed by the "time" format.
79 *
80 * The "DATE-TIME" data type expresses time values in three forms:
81 *
82 * The form of date and time with UTC offset MUST NOT be used. For
83 * example, the following is not valid for a date-time value:
84 *
85 * DTSTART:19980119T230000-0800 ;Invalid time format
86 *
87 * FORM #1: DATE WITH LOCAL TIME
88 *
89 * The date with local time form is simply a date-time value that does
90 * not contain the UTC designator nor does it reference a time zone. For
91 * example, the following represents Janurary 18, 1998, at 11 PM:
92 *
93 * DTSTART:19980118T230000
94 *
95 * Date-time values of this type are said to be "floating" and are not
96 * bound to any time zone in particular. They are used to represent the
97 * same hour, minute, and second value regardless of which time zone is
98 * currently being observed. For example, an event can be defined that
99 * indicates that an individual will be busy from 11:00 AM to 1:00 PM
100 * every day, no matter which time zone the person is in. In these
101 * cases, a local time can be specified. The recipient of an iCalendar
102 * object with a property value consisting of a local time, without any
103 * relative time zone information, SHOULD interpret the value as being
104 * fixed to whatever time zone the ATTENDEE is in at any given moment.
105 * This means that two ATTENDEEs, in different time zones, receiving the
106 * same event definition as a floating time, may be participating in the
107 * event at different actual times. Floating time SHOULD only be used
108 * where that is the reasonable behavior.
109 *
110 * In most cases, a fixed time is desired. To properly communicate a
111 * fixed time in a property value, either UTC time or local time with
112 * time zone reference MUST be specified.
113 *
114 * The use of local time in a DATE-TIME value without the TZID property
115 * parameter is to be interpreted as floating time, regardless of the
116 * existence of "VTIMEZONE" calendar components in the iCalendar object.
117 *
118 * FORM #2: DATE WITH UTC TIME
119 *
120 * The date with UTC time, or absolute time, is identified by a LATIN
121 * CAPITAL LETTER Z suffix character (US-ASCII decimal 90), the UTC
122 * designator, appended to the time value. For example, the following
123 * represents January 19, 1998, at 0700 UTC:
124 *
125 * DTSTART:19980119T070000Z
126 *
127 * The TZID property parameter MUST NOT be applied to DATE-TIME
128 * properties whose time values are specified in UTC.
129 *
130 * FORM #3: DATE WITH LOCAL TIME AND TIME ZONE REFERENCE
131 *
132 * The date and local time with reference to time zone information is
133 * identified by the use the TZID property parameter to reference the
134 * appropriate time zone definition. TZID is discussed in detail in the
135 * section on Time Zone. For example, the following represents 2 AM in
136 * New York on Janurary 19, 1998:
137 *
138 * DTSTART;TZID=US-Eastern:19980119T020000
139 *
140 * Example: The following represents July 14, 1997, at 1:30 PM in New
141 * York City in each of the three time formats, using the "DTSTART"
142 * property.
143 *
144 * DTSTART:19970714T133000 ;Local time
145 * DTSTART:19970714T173000Z ;UTC time
146 * DTSTART;TZID=US-Eastern:19970714T133000 ;Local time and time
147 * ; zone reference
148 *
149 * A time value MUST ONLY specify 60 seconds when specifying the
150 * periodic "leap second" in the time value. For example:
151 *
152 * COMPLETED:19970630T235960Z
153 * </pre>
154 *
155 * @author Ben Fortuna
156 */
157 public class DateTime extends Date {
158
159 private static final long serialVersionUID = -6407231357919440387L;
160
161 private static final String DEFAULT_PATTERN = "yyyyMMdd'T'HHmmss";
162
163 private static final String UTC_PATTERN = "yyyyMMdd'T'HHmmss'Z'";
164
165 private static final String RELAXED_PATTERN = "yyyyMMdd";
166
167 /**
168 * Used for parsing times in a UTC date-time representation.
169 */
170 private static final DateFormatCache UTC_FORMAT;
171 static {
172 final DateFormat format = new SimpleDateFormat(UTC_PATTERN);
173 format.setTimeZone(TimeZones.getUtcTimeZone());
174 format.setLenient(false);
175
176 UTC_FORMAT = new DateFormatCache(format);
177 }
178
179 /**
180 * Used for parsing times in a local date-time representation.
181 */
182 private static final DateFormatCache DEFAULT_FORMAT;
183 static {
184 final DateFormat format = new SimpleDateFormat(DEFAULT_PATTERN);
185 format.setLenient(false);
186 DEFAULT_FORMAT = new DateFormatCache(format);
187 }
188
189 private static final DateFormatCache LENIENT_DEFAULT_FORMAT;
190 static {
191 final DateFormat format = new SimpleDateFormat(DEFAULT_PATTERN);
192 LENIENT_DEFAULT_FORMAT = new DateFormatCache(format);
193 }
194
195 private static final DateFormatCache RELAXED_FORMAT;
196 static {
197 final DateFormat format = new SimpleDateFormat(RELAXED_PATTERN);
198 format.setLenient(false);
199 RELAXED_FORMAT = new DateFormatCache(format);
200 }
201
202 private Time time;
203
204 private TimeZone timezone;
205
206 /**
207 * Default constructor.
208 */
209 public DateTime() {
210 super(Dates.PRECISION_SECOND, java.util.TimeZone.getDefault());
211 this.time = new Time(getTime(), getFormat().getTimeZone());
212 }
213
214 /**
215 * @param utc
216 * indicates if the date is in UTC time
217 */
218 public DateTime(final boolean utc) {
219 this();
220 setUtc(utc);
221 }
222
223 /**
224 * @param time
225 * a date-time value in milliseconds
226 */
227 public DateTime(final long time) {
228 super(time, Dates.PRECISION_SECOND, java.util.TimeZone.getDefault());
229 this.time = new Time(time, getFormat().getTimeZone());
230 }
231
232 /**
233 * @param date
234 * a date-time value
235 */
236 public DateTime(final java.util.Date date) {
237 super(date.getTime(), Dates.PRECISION_SECOND, java.util.TimeZone.getDefault());
238 this.time = new Time(date.getTime(), getFormat().getTimeZone());
239 // copy timezone information if applicable..
240 if (date instanceof DateTime) {
241 final DateTime dateTime = (DateTime) date;
242 if (dateTime.isUtc()) {
243 setUtc(true);
244 } else {
245 setTimeZone(dateTime.getTimeZone());
246 }
247 }
248 }
249
250 /**
251 * Constructs a new DateTime instance from parsing the specified string
252 * representation in the default (local) timezone.
253 *
254 * @param value
255 * a string representation of a date-time
256 * @throws ParseException
257 * where the specified string is not a valid date-time
258 */
259 public DateTime(final String value) throws ParseException {
260 this(value, null);
261 /*
262 * long time = 0; try { synchronized (UTC_FORMAT) { time =
263 * UTC_FORMAT.parse(value).getTime(); } setUtc(true); } catch
264 * (ParseException pe) { synchronized (DEFAULT_FORMAT) {
265 * DEFAULT_FORMAT.setTimeZone(getFormat().getTimeZone()); time =
266 * DEFAULT_FORMAT.parse(value).getTime(); } this.time = new Time(time,
267 * getFormat().getTimeZone()); } setTime(time);
268 */
269 }
270
271 /**
272 * Creates a new date-time instance from the specified value in the given
273 * timezone. If a timezone is not specified, the default timezone (as
274 * returned by {@link java.util.TimeZone#getDefault()}) is used.
275 *
276 * @param value
277 * a string representation of a date-time
278 * @param timezone
279 * the timezone for the date-time instance
280 * @throws ParseException
281 * where the specified string is not a valid date-time
282 */
283 public DateTime(final String value, final TimeZone timezone)
284 throws ParseException {
285 // setting the time to 0 since we are going to reset it anyway
286 super(0, Dates.PRECISION_SECOND, timezone != null ? timezone
287 : java.util.TimeZone.getDefault());
288 this.time = new Time(getTime(), getFormat().getTimeZone());
289
290 try {
291 if (value.endsWith("Z")) {
292 setTime(value, (DateFormat) UTC_FORMAT.get(), null);
293 setUtc(true);
294 } else {
295 if (timezone != null) {
296 setTime(value, (DateFormat) DEFAULT_FORMAT.get(), timezone);
297 } else {
298 // Use lenient parsing for floating times. This is to
299 // overcome
300 // the problem of parsing VTimeZone dates that specify dates
301 // that the strict parser does not accept.
302 setTime(value, (DateFormat) LENIENT_DEFAULT_FORMAT.get(),
303 getFormat().getTimeZone());
304 }
305 setTimeZone(timezone);
306 }
307 } catch (ParseException pe) {
308 if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) {
309
310 setTime(value, (DateFormat) RELAXED_FORMAT.get(), timezone);
311 setTimeZone(timezone);
312 } else {
313 throw pe;
314 }
315 }
316 }
317
318 /**
319 * @param value
320 * a string representation of a date-time
321 * @param pattern
322 * a pattern to apply when parsing the date-time value
323 * @param timezone
324 * the timezone for the date-time instance
325 * @throws ParseException
326 * where the specified string is not a valid date-time
327 */
328 public DateTime(String value, String pattern, TimeZone timezone)
329 throws ParseException {
330 // setting the time to 0 since we are going to reset it anyway
331 super(0, Dates.PRECISION_SECOND, timezone != null ? timezone
332 : java.util.TimeZone.getDefault());
333 this.time = new Time(getTime(), getFormat().getTimeZone());
334
335 final DateFormat format = CalendarDateFormatFactory
336 .getInstance(pattern);
337 setTime(value, format, timezone);
338 }
339
340 /**
341 * @param value
342 * a string representation of a date-time
343 * @param pattern
344 * a pattern to apply when parsing the date-time value
345 * @param utc
346 * indicates whether the date-time is in UTC time
347 * @throws ParseException
348 * where the specified string is not a valid date-time
349 */
350 public DateTime(String value, String pattern, boolean utc)
351 throws ParseException {
352 // setting the time to 0 since we are going to reset it anyway
353 this(0);
354 final DateFormat format = CalendarDateFormatFactory
355 .getInstance(pattern);
356 if (utc) {
357 setTime(value, format,
358 ((DateFormat) UTC_FORMAT.get()).getTimeZone());
359 } else {
360 setTime(value, format, null);
361 }
362 setUtc(utc);
363 }
364
365 /**
366 * Internal set of time by parsing value string.
367 *
368 * @param value
369 * @param format
370 * a {@code DateFormat}, protected by the use of a ThreadLocal.
371 * @param tz
372 * @throws ParseException
373 */
374 private void setTime(final String value, final DateFormat format,
375 final java.util.TimeZone tz) throws ParseException {
376
377 if (tz != null) {
378 format.setTimeZone(tz);
379 }
380 setTime(format.parse(value).getTime());
381 }
382
383 /**
384 * {@inheritDoc}
385 */
386 public final void setTime(final long time) {
387 super.setTime(time);
388 // need to check for null time due to Android java.util.Date(long)
389 // constructor
390 // calling this method..
391 if (this.time != null) {
392 this.time.setTime(time);
393 }
394 }
395
396 /**
397 * @return Returns the utc.
398 */
399 public final boolean isUtc() {
400 return time.isUtc();
401 }
402
403 /**
404 * Updates this date-time to display in UTC time if the argument is true.
405 * Otherwise, resets to the default timezone.
406 *
407 * @param utc
408 * The utc to set.
409 */
410 public final void setUtc(final boolean utc) {
411 // reset the timezone associated with this instance..
412 this.timezone = null;
413 if (utc) {
414 getFormat().setTimeZone(TimeZones.getUtcTimeZone());
415 } else {
416 resetTimeZone();
417 }
418 time = new Time(time, getFormat().getTimeZone(), utc);
419 }
420
421 /**
422 * Sets the timezone associated with this date-time instance. If the
423 * specified timezone is null, it will reset to the default timezone. If the
424 * date-time instance is utc, it will turn into either a floating (no
425 * timezone) date-time, or a date-time with a timezone.
426 *
427 * @param timezone
428 * a timezone to apply to the instance
429 */
430 public final void setTimeZone(final TimeZone timezone) {
431 this.timezone = timezone;
432 if (timezone != null) {
433 getFormat().setTimeZone(timezone);
434 } else {
435 resetTimeZone();
436 }
437 time = new Time(time, getFormat().getTimeZone(), false);
438 }
439
440 /**
441 * Reset the timezone to default.
442 */
443 private void resetTimeZone() {
444 // use GMT timezone to avoid daylight savings rules affecting floating
445 // time values..
446 getFormat().setTimeZone(TimeZone.getDefault());
447 // getFormat().setTimeZone(TimeZone.getTimeZone(TimeZones.GMT_ID));
448 }
449
450 /**
451 * Returns the current timezone associated with this date-time value.
452 *
453 * @return a Java timezone
454 */
455 public final TimeZone getTimeZone() {
456 return timezone;
457 }
458
459 /**
460 * {@inheritDoc}
461 */
462 public final String toString() {
463 final StringBuffer b = new StringBuffer(super.toString());
464 b.append('T');
465 b.append(time.toString());
466 return b.toString();
467 }
468
469 /**
470 * {@inheritDoc}
471 */
472 public boolean equals(final Object arg0) {
473 // TODO: what about compareTo, before, after, etc.?
474
475 if (arg0 instanceof DateTime) {
476 return new EqualsBuilder().append(time, ((DateTime) arg0).time)
477 .isEquals();
478 }
479 return super.equals(arg0);
480 }
481
482 /**
483 * {@inheritDoc}
484 */
485 public int hashCode() {
486 return super.hashCode();
487 }
488
489 private static class DateFormatCache {
490
491 private final Map threadMap = new WeakHashMap();
492
493 private final DateFormat templateFormat;
494
495 private DateFormatCache(DateFormat dateFormat) {
496 this.templateFormat = dateFormat;
497 }
498
499 public DateFormat get() {
500 DateFormat dateFormat = (DateFormat) threadMap.get(Thread
501 .currentThread());
502 if (dateFormat == null) {
503 dateFormat = (DateFormat) templateFormat.clone();
504 threadMap.put(Thread.currentThread(), dateFormat);
505 }
506 return dateFormat;
507 }
508 }
509 }

mercurial