1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/net/fortuna/ical4j/model/DateTime.java Tue Feb 10 18:12:00 2015 +0100 1.3 @@ -0,0 +1,509 @@ 1.4 +/** 1.5 + * Copyright (c) 2012, Ben Fortuna 1.6 + * All rights reserved. 1.7 + * 1.8 + * Redistribution and use in source and binary forms, with or without 1.9 + * modification, are permitted provided that the following conditions 1.10 + * are met: 1.11 + * 1.12 + * o Redistributions of source code must retain the above copyright 1.13 + * notice, this list of conditions and the following disclaimer. 1.14 + * 1.15 + * o Redistributions in binary form must reproduce the above copyright 1.16 + * notice, this list of conditions and the following disclaimer in the 1.17 + * documentation and/or other materials provided with the distribution. 1.18 + * 1.19 + * o Neither the name of Ben Fortuna nor the names of any other contributors 1.20 + * may be used to endorse or promote products derived from this software 1.21 + * without specific prior written permission. 1.22 + * 1.23 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 1.24 + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 1.25 + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 1.26 + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 1.27 + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 1.28 + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 1.29 + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 1.30 + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 1.31 + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 1.32 + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 1.33 + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 1.34 + */ 1.35 +package net.fortuna.ical4j.model; 1.36 + 1.37 +import java.text.DateFormat; 1.38 +import java.text.ParseException; 1.39 +import java.text.SimpleDateFormat; 1.40 +import java.util.Map; 1.41 +import java.util.WeakHashMap; 1.42 + 1.43 +import net.fortuna.ical4j.util.CompatibilityHints; 1.44 +import net.fortuna.ical4j.util.Dates; 1.45 +import net.fortuna.ical4j.util.TimeZones; 1.46 + 1.47 +import org.apache.commons.lang.builder.EqualsBuilder; 1.48 +import org.apache.commons.lang.builder.HashCodeBuilder; 1.49 + 1.50 +/** 1.51 + * $Id$ 1.52 + * 1.53 + * Created on 26/06/2005 1.54 + * 1.55 + * Represents a time of day on a specific date. 1.56 + * 1.57 + * <pre> 1.58 + * 4.3.5 Date-Time 1.59 + * 1.60 + * Value Name: DATE-TIME 1.61 + * 1.62 + * Purpose: This value type is used to identify values that specify a 1.63 + * precise calendar date and time of day. 1.64 + * 1.65 + * Formal Definition: The value type is defined by the following 1.66 + * notation: 1.67 + * 1.68 + * date-time = date "T" time ;As specified in the date and time 1.69 + * ;value definitions 1.70 + * 1.71 + * Description: If the property permits, multiple "date-time" values are 1.72 + * specified as a COMMA character (US-ASCII decimal 44) separated list 1.73 + * of values. No additional content value encoding (i.e., BACKSLASH 1.74 + * character encoding) is defined for this value type. 1.75 + * 1.76 + * The "DATE-TIME" data type is used to identify values that contain a 1.77 + * precise calendar date and time of day. The format is based on the 1.78 + * [ISO 8601] complete representation, basic format for a calendar date 1.79 + * and time of day. The text format is a concatenation of the "date", 1.80 + * followed by the LATIN CAPITAL LETTER T character (US-ASCII decimal 1.81 + * 84) time designator, followed by the "time" format. 1.82 + * 1.83 + * The "DATE-TIME" data type expresses time values in three forms: 1.84 + * 1.85 + * The form of date and time with UTC offset MUST NOT be used. For 1.86 + * example, the following is not valid for a date-time value: 1.87 + * 1.88 + * DTSTART:19980119T230000-0800 ;Invalid time format 1.89 + * 1.90 + * FORM #1: DATE WITH LOCAL TIME 1.91 + * 1.92 + * The date with local time form is simply a date-time value that does 1.93 + * not contain the UTC designator nor does it reference a time zone. For 1.94 + * example, the following represents Janurary 18, 1998, at 11 PM: 1.95 + * 1.96 + * DTSTART:19980118T230000 1.97 + * 1.98 + * Date-time values of this type are said to be "floating" and are not 1.99 + * bound to any time zone in particular. They are used to represent the 1.100 + * same hour, minute, and second value regardless of which time zone is 1.101 + * currently being observed. For example, an event can be defined that 1.102 + * indicates that an individual will be busy from 11:00 AM to 1:00 PM 1.103 + * every day, no matter which time zone the person is in. In these 1.104 + * cases, a local time can be specified. The recipient of an iCalendar 1.105 + * object with a property value consisting of a local time, without any 1.106 + * relative time zone information, SHOULD interpret the value as being 1.107 + * fixed to whatever time zone the ATTENDEE is in at any given moment. 1.108 + * This means that two ATTENDEEs, in different time zones, receiving the 1.109 + * same event definition as a floating time, may be participating in the 1.110 + * event at different actual times. Floating time SHOULD only be used 1.111 + * where that is the reasonable behavior. 1.112 + * 1.113 + * In most cases, a fixed time is desired. To properly communicate a 1.114 + * fixed time in a property value, either UTC time or local time with 1.115 + * time zone reference MUST be specified. 1.116 + * 1.117 + * The use of local time in a DATE-TIME value without the TZID property 1.118 + * parameter is to be interpreted as floating time, regardless of the 1.119 + * existence of "VTIMEZONE" calendar components in the iCalendar object. 1.120 + * 1.121 + * FORM #2: DATE WITH UTC TIME 1.122 + * 1.123 + * The date with UTC time, or absolute time, is identified by a LATIN 1.124 + * CAPITAL LETTER Z suffix character (US-ASCII decimal 90), the UTC 1.125 + * designator, appended to the time value. For example, the following 1.126 + * represents January 19, 1998, at 0700 UTC: 1.127 + * 1.128 + * DTSTART:19980119T070000Z 1.129 + * 1.130 + * The TZID property parameter MUST NOT be applied to DATE-TIME 1.131 + * properties whose time values are specified in UTC. 1.132 + * 1.133 + * FORM #3: DATE WITH LOCAL TIME AND TIME ZONE REFERENCE 1.134 + * 1.135 + * The date and local time with reference to time zone information is 1.136 + * identified by the use the TZID property parameter to reference the 1.137 + * appropriate time zone definition. TZID is discussed in detail in the 1.138 + * section on Time Zone. For example, the following represents 2 AM in 1.139 + * New York on Janurary 19, 1998: 1.140 + * 1.141 + * DTSTART;TZID=US-Eastern:19980119T020000 1.142 + * 1.143 + * Example: The following represents July 14, 1997, at 1:30 PM in New 1.144 + * York City in each of the three time formats, using the "DTSTART" 1.145 + * property. 1.146 + * 1.147 + * DTSTART:19970714T133000 ;Local time 1.148 + * DTSTART:19970714T173000Z ;UTC time 1.149 + * DTSTART;TZID=US-Eastern:19970714T133000 ;Local time and time 1.150 + * ; zone reference 1.151 + * 1.152 + * A time value MUST ONLY specify 60 seconds when specifying the 1.153 + * periodic "leap second" in the time value. For example: 1.154 + * 1.155 + * COMPLETED:19970630T235960Z 1.156 + * </pre> 1.157 + * 1.158 + * @author Ben Fortuna 1.159 + */ 1.160 +public class DateTime extends Date { 1.161 + 1.162 + private static final long serialVersionUID = -6407231357919440387L; 1.163 + 1.164 + private static final String DEFAULT_PATTERN = "yyyyMMdd'T'HHmmss"; 1.165 + 1.166 + private static final String UTC_PATTERN = "yyyyMMdd'T'HHmmss'Z'"; 1.167 + 1.168 + private static final String RELAXED_PATTERN = "yyyyMMdd"; 1.169 + 1.170 + /** 1.171 + * Used for parsing times in a UTC date-time representation. 1.172 + */ 1.173 + private static final DateFormatCache UTC_FORMAT; 1.174 + static { 1.175 + final DateFormat format = new SimpleDateFormat(UTC_PATTERN); 1.176 + format.setTimeZone(TimeZones.getUtcTimeZone()); 1.177 + format.setLenient(false); 1.178 + 1.179 + UTC_FORMAT = new DateFormatCache(format); 1.180 + } 1.181 + 1.182 + /** 1.183 + * Used for parsing times in a local date-time representation. 1.184 + */ 1.185 + private static final DateFormatCache DEFAULT_FORMAT; 1.186 + static { 1.187 + final DateFormat format = new SimpleDateFormat(DEFAULT_PATTERN); 1.188 + format.setLenient(false); 1.189 + DEFAULT_FORMAT = new DateFormatCache(format); 1.190 + } 1.191 + 1.192 + private static final DateFormatCache LENIENT_DEFAULT_FORMAT; 1.193 + static { 1.194 + final DateFormat format = new SimpleDateFormat(DEFAULT_PATTERN); 1.195 + LENIENT_DEFAULT_FORMAT = new DateFormatCache(format); 1.196 + } 1.197 + 1.198 + private static final DateFormatCache RELAXED_FORMAT; 1.199 + static { 1.200 + final DateFormat format = new SimpleDateFormat(RELAXED_PATTERN); 1.201 + format.setLenient(false); 1.202 + RELAXED_FORMAT = new DateFormatCache(format); 1.203 + } 1.204 + 1.205 + private Time time; 1.206 + 1.207 + private TimeZone timezone; 1.208 + 1.209 + /** 1.210 + * Default constructor. 1.211 + */ 1.212 + public DateTime() { 1.213 + super(Dates.PRECISION_SECOND, java.util.TimeZone.getDefault()); 1.214 + this.time = new Time(getTime(), getFormat().getTimeZone()); 1.215 + } 1.216 + 1.217 + /** 1.218 + * @param utc 1.219 + * indicates if the date is in UTC time 1.220 + */ 1.221 + public DateTime(final boolean utc) { 1.222 + this(); 1.223 + setUtc(utc); 1.224 + } 1.225 + 1.226 + /** 1.227 + * @param time 1.228 + * a date-time value in milliseconds 1.229 + */ 1.230 + public DateTime(final long time) { 1.231 + super(time, Dates.PRECISION_SECOND, java.util.TimeZone.getDefault()); 1.232 + this.time = new Time(time, getFormat().getTimeZone()); 1.233 + } 1.234 + 1.235 + /** 1.236 + * @param date 1.237 + * a date-time value 1.238 + */ 1.239 + public DateTime(final java.util.Date date) { 1.240 + super(date.getTime(), Dates.PRECISION_SECOND, java.util.TimeZone.getDefault()); 1.241 + this.time = new Time(date.getTime(), getFormat().getTimeZone()); 1.242 + // copy timezone information if applicable.. 1.243 + if (date instanceof DateTime) { 1.244 + final DateTime dateTime = (DateTime) date; 1.245 + if (dateTime.isUtc()) { 1.246 + setUtc(true); 1.247 + } else { 1.248 + setTimeZone(dateTime.getTimeZone()); 1.249 + } 1.250 + } 1.251 + } 1.252 + 1.253 + /** 1.254 + * Constructs a new DateTime instance from parsing the specified string 1.255 + * representation in the default (local) timezone. 1.256 + * 1.257 + * @param value 1.258 + * a string representation of a date-time 1.259 + * @throws ParseException 1.260 + * where the specified string is not a valid date-time 1.261 + */ 1.262 + public DateTime(final String value) throws ParseException { 1.263 + this(value, null); 1.264 + /* 1.265 + * long time = 0; try { synchronized (UTC_FORMAT) { time = 1.266 + * UTC_FORMAT.parse(value).getTime(); } setUtc(true); } catch 1.267 + * (ParseException pe) { synchronized (DEFAULT_FORMAT) { 1.268 + * DEFAULT_FORMAT.setTimeZone(getFormat().getTimeZone()); time = 1.269 + * DEFAULT_FORMAT.parse(value).getTime(); } this.time = new Time(time, 1.270 + * getFormat().getTimeZone()); } setTime(time); 1.271 + */ 1.272 + } 1.273 + 1.274 + /** 1.275 + * Creates a new date-time instance from the specified value in the given 1.276 + * timezone. If a timezone is not specified, the default timezone (as 1.277 + * returned by {@link java.util.TimeZone#getDefault()}) is used. 1.278 + * 1.279 + * @param value 1.280 + * a string representation of a date-time 1.281 + * @param timezone 1.282 + * the timezone for the date-time instance 1.283 + * @throws ParseException 1.284 + * where the specified string is not a valid date-time 1.285 + */ 1.286 + public DateTime(final String value, final TimeZone timezone) 1.287 + throws ParseException { 1.288 + // setting the time to 0 since we are going to reset it anyway 1.289 + super(0, Dates.PRECISION_SECOND, timezone != null ? timezone 1.290 + : java.util.TimeZone.getDefault()); 1.291 + this.time = new Time(getTime(), getFormat().getTimeZone()); 1.292 + 1.293 + try { 1.294 + if (value.endsWith("Z")) { 1.295 + setTime(value, (DateFormat) UTC_FORMAT.get(), null); 1.296 + setUtc(true); 1.297 + } else { 1.298 + if (timezone != null) { 1.299 + setTime(value, (DateFormat) DEFAULT_FORMAT.get(), timezone); 1.300 + } else { 1.301 + // Use lenient parsing for floating times. This is to 1.302 + // overcome 1.303 + // the problem of parsing VTimeZone dates that specify dates 1.304 + // that the strict parser does not accept. 1.305 + setTime(value, (DateFormat) LENIENT_DEFAULT_FORMAT.get(), 1.306 + getFormat().getTimeZone()); 1.307 + } 1.308 + setTimeZone(timezone); 1.309 + } 1.310 + } catch (ParseException pe) { 1.311 + if (CompatibilityHints.isHintEnabled(CompatibilityHints.KEY_RELAXED_PARSING)) { 1.312 + 1.313 + setTime(value, (DateFormat) RELAXED_FORMAT.get(), timezone); 1.314 + setTimeZone(timezone); 1.315 + } else { 1.316 + throw pe; 1.317 + } 1.318 + } 1.319 + } 1.320 + 1.321 + /** 1.322 + * @param value 1.323 + * a string representation of a date-time 1.324 + * @param pattern 1.325 + * a pattern to apply when parsing the date-time value 1.326 + * @param timezone 1.327 + * the timezone for the date-time instance 1.328 + * @throws ParseException 1.329 + * where the specified string is not a valid date-time 1.330 + */ 1.331 + public DateTime(String value, String pattern, TimeZone timezone) 1.332 + throws ParseException { 1.333 + // setting the time to 0 since we are going to reset it anyway 1.334 + super(0, Dates.PRECISION_SECOND, timezone != null ? timezone 1.335 + : java.util.TimeZone.getDefault()); 1.336 + this.time = new Time(getTime(), getFormat().getTimeZone()); 1.337 + 1.338 + final DateFormat format = CalendarDateFormatFactory 1.339 + .getInstance(pattern); 1.340 + setTime(value, format, timezone); 1.341 + } 1.342 + 1.343 + /** 1.344 + * @param value 1.345 + * a string representation of a date-time 1.346 + * @param pattern 1.347 + * a pattern to apply when parsing the date-time value 1.348 + * @param utc 1.349 + * indicates whether the date-time is in UTC time 1.350 + * @throws ParseException 1.351 + * where the specified string is not a valid date-time 1.352 + */ 1.353 + public DateTime(String value, String pattern, boolean utc) 1.354 + throws ParseException { 1.355 + // setting the time to 0 since we are going to reset it anyway 1.356 + this(0); 1.357 + final DateFormat format = CalendarDateFormatFactory 1.358 + .getInstance(pattern); 1.359 + if (utc) { 1.360 + setTime(value, format, 1.361 + ((DateFormat) UTC_FORMAT.get()).getTimeZone()); 1.362 + } else { 1.363 + setTime(value, format, null); 1.364 + } 1.365 + setUtc(utc); 1.366 + } 1.367 + 1.368 + /** 1.369 + * Internal set of time by parsing value string. 1.370 + * 1.371 + * @param value 1.372 + * @param format 1.373 + * a {@code DateFormat}, protected by the use of a ThreadLocal. 1.374 + * @param tz 1.375 + * @throws ParseException 1.376 + */ 1.377 + private void setTime(final String value, final DateFormat format, 1.378 + final java.util.TimeZone tz) throws ParseException { 1.379 + 1.380 + if (tz != null) { 1.381 + format.setTimeZone(tz); 1.382 + } 1.383 + setTime(format.parse(value).getTime()); 1.384 + } 1.385 + 1.386 + /** 1.387 + * {@inheritDoc} 1.388 + */ 1.389 + public final void setTime(final long time) { 1.390 + super.setTime(time); 1.391 + // need to check for null time due to Android java.util.Date(long) 1.392 + // constructor 1.393 + // calling this method.. 1.394 + if (this.time != null) { 1.395 + this.time.setTime(time); 1.396 + } 1.397 + } 1.398 + 1.399 + /** 1.400 + * @return Returns the utc. 1.401 + */ 1.402 + public final boolean isUtc() { 1.403 + return time.isUtc(); 1.404 + } 1.405 + 1.406 + /** 1.407 + * Updates this date-time to display in UTC time if the argument is true. 1.408 + * Otherwise, resets to the default timezone. 1.409 + * 1.410 + * @param utc 1.411 + * The utc to set. 1.412 + */ 1.413 + public final void setUtc(final boolean utc) { 1.414 + // reset the timezone associated with this instance.. 1.415 + this.timezone = null; 1.416 + if (utc) { 1.417 + getFormat().setTimeZone(TimeZones.getUtcTimeZone()); 1.418 + } else { 1.419 + resetTimeZone(); 1.420 + } 1.421 + time = new Time(time, getFormat().getTimeZone(), utc); 1.422 + } 1.423 + 1.424 + /** 1.425 + * Sets the timezone associated with this date-time instance. If the 1.426 + * specified timezone is null, it will reset to the default timezone. If the 1.427 + * date-time instance is utc, it will turn into either a floating (no 1.428 + * timezone) date-time, or a date-time with a timezone. 1.429 + * 1.430 + * @param timezone 1.431 + * a timezone to apply to the instance 1.432 + */ 1.433 + public final void setTimeZone(final TimeZone timezone) { 1.434 + this.timezone = timezone; 1.435 + if (timezone != null) { 1.436 + getFormat().setTimeZone(timezone); 1.437 + } else { 1.438 + resetTimeZone(); 1.439 + } 1.440 + time = new Time(time, getFormat().getTimeZone(), false); 1.441 + } 1.442 + 1.443 + /** 1.444 + * Reset the timezone to default. 1.445 + */ 1.446 + private void resetTimeZone() { 1.447 + // use GMT timezone to avoid daylight savings rules affecting floating 1.448 + // time values.. 1.449 + getFormat().setTimeZone(TimeZone.getDefault()); 1.450 + // getFormat().setTimeZone(TimeZone.getTimeZone(TimeZones.GMT_ID)); 1.451 + } 1.452 + 1.453 + /** 1.454 + * Returns the current timezone associated with this date-time value. 1.455 + * 1.456 + * @return a Java timezone 1.457 + */ 1.458 + public final TimeZone getTimeZone() { 1.459 + return timezone; 1.460 + } 1.461 + 1.462 + /** 1.463 + * {@inheritDoc} 1.464 + */ 1.465 + public final String toString() { 1.466 + final StringBuffer b = new StringBuffer(super.toString()); 1.467 + b.append('T'); 1.468 + b.append(time.toString()); 1.469 + return b.toString(); 1.470 + } 1.471 + 1.472 + /** 1.473 + * {@inheritDoc} 1.474 + */ 1.475 + public boolean equals(final Object arg0) { 1.476 + // TODO: what about compareTo, before, after, etc.? 1.477 + 1.478 + if (arg0 instanceof DateTime) { 1.479 + return new EqualsBuilder().append(time, ((DateTime) arg0).time) 1.480 + .isEquals(); 1.481 + } 1.482 + return super.equals(arg0); 1.483 + } 1.484 + 1.485 + /** 1.486 + * {@inheritDoc} 1.487 + */ 1.488 + public int hashCode() { 1.489 + return super.hashCode(); 1.490 + } 1.491 + 1.492 + private static class DateFormatCache { 1.493 + 1.494 + private final Map threadMap = new WeakHashMap(); 1.495 + 1.496 + private final DateFormat templateFormat; 1.497 + 1.498 + private DateFormatCache(DateFormat dateFormat) { 1.499 + this.templateFormat = dateFormat; 1.500 + } 1.501 + 1.502 + public DateFormat get() { 1.503 + DateFormat dateFormat = (DateFormat) threadMap.get(Thread 1.504 + .currentThread()); 1.505 + if (dateFormat == null) { 1.506 + dateFormat = (DateFormat) templateFormat.clone(); 1.507 + threadMap.put(Thread.currentThread(), dateFormat); 1.508 + } 1.509 + return dateFormat; 1.510 + } 1.511 + } 1.512 +}