diff -r 000000000000 -r fb9019fb1bf7 src/net/fortuna/ical4j/model/DateTime.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/net/fortuna/ical4j/model/DateTime.java Tue Feb 10 18:12:00 2015 +0100 @@ -0,0 +1,509 @@ +/** + * Copyright (c) 2012, Ben Fortuna + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * o Neither the name of Ben Fortuna nor the names of any other contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.fortuna.ical4j.model; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Map; +import java.util.WeakHashMap; + +import net.fortuna.ical4j.util.CompatibilityHints; +import net.fortuna.ical4j.util.Dates; +import net.fortuna.ical4j.util.TimeZones; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +/** + * $Id$ + * + * Created on 26/06/2005 + * + * Represents a time of day on a specific date. + * + *
+ * 4.3.5 Date-Time + * + * Value Name: DATE-TIME + * + * Purpose: This value type is used to identify values that specify a + * precise calendar date and time of day. + * + * Formal Definition: The value type is defined by the following + * notation: + * + * date-time = date "T" time ;As specified in the date and time + * ;value definitions + * + * Description: If the property permits, multiple "date-time" values are + * specified as a COMMA character (US-ASCII decimal 44) separated list + * of values. No additional content value encoding (i.e., BACKSLASH + * character encoding) is defined for this value type. + * + * The "DATE-TIME" data type is used to identify values that contain a + * precise calendar date and time of day. The format is based on the + * [ISO 8601] complete representation, basic format for a calendar date + * and time of day. The text format is a concatenation of the "date", + * followed by the LATIN CAPITAL LETTER T character (US-ASCII decimal + * 84) time designator, followed by the "time" format. + * + * The "DATE-TIME" data type expresses time values in three forms: + * + * The form of date and time with UTC offset MUST NOT be used. For + * example, the following is not valid for a date-time value: + * + * DTSTART:19980119T230000-0800 ;Invalid time format + * + * FORM #1: DATE WITH LOCAL TIME + * + * The date with local time form is simply a date-time value that does + * not contain the UTC designator nor does it reference a time zone. For + * example, the following represents Janurary 18, 1998, at 11 PM: + * + * DTSTART:19980118T230000 + * + * Date-time values of this type are said to be "floating" and are not + * bound to any time zone in particular. They are used to represent the + * same hour, minute, and second value regardless of which time zone is + * currently being observed. For example, an event can be defined that + * indicates that an individual will be busy from 11:00 AM to 1:00 PM + * every day, no matter which time zone the person is in. In these + * cases, a local time can be specified. The recipient of an iCalendar + * object with a property value consisting of a local time, without any + * relative time zone information, SHOULD interpret the value as being + * fixed to whatever time zone the ATTENDEE is in at any given moment. + * This means that two ATTENDEEs, in different time zones, receiving the + * same event definition as a floating time, may be participating in the + * event at different actual times. Floating time SHOULD only be used + * where that is the reasonable behavior. + * + * In most cases, a fixed time is desired. To properly communicate a + * fixed time in a property value, either UTC time or local time with + * time zone reference MUST be specified. + * + * The use of local time in a DATE-TIME value without the TZID property + * parameter is to be interpreted as floating time, regardless of the + * existence of "VTIMEZONE" calendar components in the iCalendar object. + * + * FORM #2: DATE WITH UTC TIME + * + * The date with UTC time, or absolute time, is identified by a LATIN + * CAPITAL LETTER Z suffix character (US-ASCII decimal 90), the UTC + * designator, appended to the time value. For example, the following + * represents January 19, 1998, at 0700 UTC: + * + * DTSTART:19980119T070000Z + * + * The TZID property parameter MUST NOT be applied to DATE-TIME + * properties whose time values are specified in UTC. + * + * FORM #3: DATE WITH LOCAL TIME AND TIME ZONE REFERENCE + * + * The date and local time with reference to time zone information is + * identified by the use the TZID property parameter to reference the + * appropriate time zone definition. TZID is discussed in detail in the + * section on Time Zone. For example, the following represents 2 AM in + * New York on Janurary 19, 1998: + * + * DTSTART;TZID=US-Eastern:19980119T020000 + * + * Example: The following represents July 14, 1997, at 1:30 PM in New + * York City in each of the three time formats, using the "DTSTART" + * property. + * + * DTSTART:19970714T133000 ;Local time + * DTSTART:19970714T173000Z ;UTC time + * DTSTART;TZID=US-Eastern:19970714T133000 ;Local time and time + * ; zone reference + * + * A time value MUST ONLY specify 60 seconds when specifying the + * periodic "leap second" in the time value. For example: + * + * COMPLETED:19970630T235960Z + *+ * + * @author Ben Fortuna + */ +public class DateTime extends Date { + + private static final long serialVersionUID = -6407231357919440387L; + + private static final String DEFAULT_PATTERN = "yyyyMMdd'T'HHmmss"; + + private static final String UTC_PATTERN = "yyyyMMdd'T'HHmmss'Z'"; + + private static final String RELAXED_PATTERN = "yyyyMMdd"; + + /** + * Used for parsing times in a UTC date-time representation. + */ + private static final DateFormatCache UTC_FORMAT; + static { + final DateFormat format = new SimpleDateFormat(UTC_PATTERN); + format.setTimeZone(TimeZones.getUtcTimeZone()); + format.setLenient(false); + + UTC_FORMAT = new DateFormatCache(format); + } + + /** + * Used for parsing times in a local date-time representation. + */ + private static final DateFormatCache DEFAULT_FORMAT; + static { + final DateFormat format = new SimpleDateFormat(DEFAULT_PATTERN); + format.setLenient(false); + DEFAULT_FORMAT = new DateFormatCache(format); + } + + private static final DateFormatCache LENIENT_DEFAULT_FORMAT; + static { + final DateFormat format = new SimpleDateFormat(DEFAULT_PATTERN); + LENIENT_DEFAULT_FORMAT = new DateFormatCache(format); + } + + private static final DateFormatCache RELAXED_FORMAT; + static { + final DateFormat format = new SimpleDateFormat(RELAXED_PATTERN); + format.setLenient(false); + RELAXED_FORMAT = new DateFormatCache(format); + } + + private Time time; + + private TimeZone timezone; + + /** + * Default constructor. + */ + public DateTime() { + super(Dates.PRECISION_SECOND, java.util.TimeZone.getDefault()); + this.time = new Time(getTime(), getFormat().getTimeZone()); + } + + /** + * @param utc + * indicates if the date is in UTC time + */ + public DateTime(final boolean utc) { + this(); + setUtc(utc); + } + + /** + * @param time + * a date-time value in milliseconds + */ + public DateTime(final long time) { + super(time, Dates.PRECISION_SECOND, java.util.TimeZone.getDefault()); + this.time = new Time(time, getFormat().getTimeZone()); + } + + /** + * @param date + * a date-time value + */ + public DateTime(final java.util.Date date) { + super(date.getTime(), Dates.PRECISION_SECOND, java.util.TimeZone.getDefault()); + this.time = new Time(date.getTime(), getFormat().getTimeZone()); + // copy timezone information if applicable.. + if (date instanceof DateTime) { + final DateTime dateTime = (DateTime) date; + if (dateTime.isUtc()) { + setUtc(true); + } else { + setTimeZone(dateTime.getTimeZone()); + } + } + } + + /** + * Constructs a new DateTime instance from parsing the specified string + * representation in the default (local) timezone. + * + * @param value + * a string representation of a date-time + * @throws ParseException + * where the specified string is not a valid date-time + */ + public DateTime(final String value) throws ParseException { + this(value, null); + /* + * long time = 0; try { synchronized (UTC_FORMAT) { time = + * UTC_FORMAT.parse(value).getTime(); } setUtc(true); } catch + * (ParseException pe) { synchronized (DEFAULT_FORMAT) { + * DEFAULT_FORMAT.setTimeZone(getFormat().getTimeZone()); time = + * DEFAULT_FORMAT.parse(value).getTime(); } this.time = new Time(time, + * getFormat().getTimeZone()); } setTime(time); + */ + } + + /** + * Creates a new date-time instance from the specified value in the given + * timezone. If a timezone is not specified, the default timezone (as + * returned by {@link java.util.TimeZone#getDefault()}) is used. + * + * @param value + * a string representation of a date-time + * @param timezone + * the timezone for the date-time instance + * @throws ParseException + * where the specified string is not a valid date-time + */ + public DateTime(final String value, final TimeZone timezone) + throws ParseException { + // setting the time to 0 since we are going to reset it anyway + super(0, Dates.PRECISION_SECOND, timezone != null ? timezone + : java.util.TimeZone.getDefault()); + this.time = new Time(getTime(), getFormat().getTimeZone()); + + try { + if (value.endsWith("Z")) { + setTime(value, (DateFormat) UTC_FORMAT.get(), null); + setUtc(true); + } else { + if (timezone != null) { + setTime(value, (DateFormat) DEFAULT_FORMAT.get(), timezone); + } else { + // Use lenient parsing for floating times. This is to + // overcome + // the problem of parsing VTimeZone dates that specify dates + // that the strict parser does not accept. + setTime(value, (DateFormat) LENIENT_DEFAULT_FORMAT.get(), + getFormat().getTimeZone()); + } + setTimeZone(timezone); + } + } catch (ParseException pe) { + if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) { + + setTime(value, (DateFormat) RELAXED_FORMAT.get(), timezone); + setTimeZone(timezone); + } else { + throw pe; + } + } + } + + /** + * @param value + * a string representation of a date-time + * @param pattern + * a pattern to apply when parsing the date-time value + * @param timezone + * the timezone for the date-time instance + * @throws ParseException + * where the specified string is not a valid date-time + */ + public DateTime(String value, String pattern, TimeZone timezone) + throws ParseException { + // setting the time to 0 since we are going to reset it anyway + super(0, Dates.PRECISION_SECOND, timezone != null ? timezone + : java.util.TimeZone.getDefault()); + this.time = new Time(getTime(), getFormat().getTimeZone()); + + final DateFormat format = CalendarDateFormatFactory + .getInstance(pattern); + setTime(value, format, timezone); + } + + /** + * @param value + * a string representation of a date-time + * @param pattern + * a pattern to apply when parsing the date-time value + * @param utc + * indicates whether the date-time is in UTC time + * @throws ParseException + * where the specified string is not a valid date-time + */ + public DateTime(String value, String pattern, boolean utc) + throws ParseException { + // setting the time to 0 since we are going to reset it anyway + this(0); + final DateFormat format = CalendarDateFormatFactory + .getInstance(pattern); + if (utc) { + setTime(value, format, + ((DateFormat) UTC_FORMAT.get()).getTimeZone()); + } else { + setTime(value, format, null); + } + setUtc(utc); + } + + /** + * Internal set of time by parsing value string. + * + * @param value + * @param format + * a {@code DateFormat}, protected by the use of a ThreadLocal. + * @param tz + * @throws ParseException + */ + private void setTime(final String value, final DateFormat format, + final java.util.TimeZone tz) throws ParseException { + + if (tz != null) { + format.setTimeZone(tz); + } + setTime(format.parse(value).getTime()); + } + + /** + * {@inheritDoc} + */ + public final void setTime(final long time) { + super.setTime(time); + // need to check for null time due to Android java.util.Date(long) + // constructor + // calling this method.. + if (this.time != null) { + this.time.setTime(time); + } + } + + /** + * @return Returns the utc. + */ + public final boolean isUtc() { + return time.isUtc(); + } + + /** + * Updates this date-time to display in UTC time if the argument is true. + * Otherwise, resets to the default timezone. + * + * @param utc + * The utc to set. + */ + public final void setUtc(final boolean utc) { + // reset the timezone associated with this instance.. + this.timezone = null; + if (utc) { + getFormat().setTimeZone(TimeZones.getUtcTimeZone()); + } else { + resetTimeZone(); + } + time = new Time(time, getFormat().getTimeZone(), utc); + } + + /** + * Sets the timezone associated with this date-time instance. If the + * specified timezone is null, it will reset to the default timezone. If the + * date-time instance is utc, it will turn into either a floating (no + * timezone) date-time, or a date-time with a timezone. + * + * @param timezone + * a timezone to apply to the instance + */ + public final void setTimeZone(final TimeZone timezone) { + this.timezone = timezone; + if (timezone != null) { + getFormat().setTimeZone(timezone); + } else { + resetTimeZone(); + } + time = new Time(time, getFormat().getTimeZone(), false); + } + + /** + * Reset the timezone to default. + */ + private void resetTimeZone() { + // use GMT timezone to avoid daylight savings rules affecting floating + // time values.. + getFormat().setTimeZone(TimeZone.getDefault()); + // getFormat().setTimeZone(TimeZone.getTimeZone(TimeZones.GMT_ID)); + } + + /** + * Returns the current timezone associated with this date-time value. + * + * @return a Java timezone + */ + public final TimeZone getTimeZone() { + return timezone; + } + + /** + * {@inheritDoc} + */ + public final String toString() { + final StringBuffer b = new StringBuffer(super.toString()); + b.append('T'); + b.append(time.toString()); + return b.toString(); + } + + /** + * {@inheritDoc} + */ + public boolean equals(final Object arg0) { + // TODO: what about compareTo, before, after, etc.? + + if (arg0 instanceof DateTime) { + return new EqualsBuilder().append(time, ((DateTime) arg0).time) + .isEquals(); + } + return super.equals(arg0); + } + + /** + * {@inheritDoc} + */ + public int hashCode() { + return super.hashCode(); + } + + private static class DateFormatCache { + + private final Map threadMap = new WeakHashMap(); + + private final DateFormat templateFormat; + + private DateFormatCache(DateFormat dateFormat) { + this.templateFormat = dateFormat; + } + + public DateFormat get() { + DateFormat dateFormat = (DateFormat) threadMap.get(Thread + .currentThread()); + if (dateFormat == null) { + dateFormat = (DateFormat) templateFormat.clone(); + threadMap.put(Thread.currentThread(), dateFormat); + } + return dateFormat; + } + } +}