diff -r 5ae3e5665a0b -r cc93757aeca3 src/net/fortuna/ical4j/model/component/Observance.java --- a/src/net/fortuna/ical4j/model/component/Observance.java Thu Feb 12 18:02:00 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,334 +0,0 @@ -/** - * 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.component; - -import java.io.IOException; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import java.util.TreeMap; - -import net.fortuna.ical4j.model.Component; -import net.fortuna.ical4j.model.Date; -import net.fortuna.ical4j.model.DateList; -import net.fortuna.ical4j.model.DateTime; -import net.fortuna.ical4j.model.Property; -import net.fortuna.ical4j.model.PropertyList; -import net.fortuna.ical4j.model.ValidationException; -import net.fortuna.ical4j.model.parameter.Value; -import net.fortuna.ical4j.model.property.DtStart; -import net.fortuna.ical4j.model.property.RDate; -import net.fortuna.ical4j.model.property.RRule; -import net.fortuna.ical4j.model.property.TzOffsetFrom; -import net.fortuna.ical4j.model.property.TzOffsetTo; -import net.fortuna.ical4j.util.Dates; -import net.fortuna.ical4j.util.PropertyValidator; -import net.fortuna.ical4j.util.TimeZones; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -/** - * $Id$ [05-Apr-2004] - * - * Defines an iCalendar sub-component representing a timezone observance. Class made abstract such that only Standard - * and Daylight instances are valid. - * @author Ben Fortuna - */ -public abstract class Observance extends Component { - - /** - * - */ - private static final long serialVersionUID = 2523330383042085994L; - - /** - * one of 'standardc' or 'daylightc' MUST occur and each MAY occur more than once. - */ - public static final String STANDARD = "STANDARD"; - - /** - * Token for daylight observance. - */ - public static final String DAYLIGHT = "DAYLIGHT"; - - // TODO: clear cache when observance definition changes (??) - private long[] onsetsMillisec; - private DateTime[] onsetsDates; - private Map onsets = new TreeMap(); - private Date initialOnset = null; - - /** - * Used for parsing times in a UTC date-time representation. - */ - private static final String UTC_PATTERN = "yyyyMMdd'T'HHmmss"; - private static final DateFormat UTC_FORMAT = new SimpleDateFormat( - UTC_PATTERN); - - static { - UTC_FORMAT.setTimeZone(TimeZones.getUtcTimeZone()); - UTC_FORMAT.setLenient(false); - } - - /* If this is set we have rrules. If we get a date after this rebuild onsets */ - private Date onsetLimit; - - /** - * Constructs a timezone observance with the specified name and no properties. - * @param name the name of this observance component - */ - protected Observance(final String name) { - super(name); - } - - /** - * Constructor protected to enforce use of sub-classes from this library. - * @param name the name of the time type - * @param properties a list of properties - */ - protected Observance(final String name, final PropertyList properties) { - super(name, properties); - } - - /** - * {@inheritDoc} - */ - public final void validate(final boolean recurse) throws ValidationException { - - // From "4.8.3.3 Time Zone Offset From": - // Conformance: This property MUST be specified in a "VTIMEZONE" - // calendar component. - PropertyValidator.getInstance().assertOne(Property.TZOFFSETFROM, - getProperties()); - - // From "4.8.3.4 Time Zone Offset To": - // Conformance: This property MUST be specified in a "VTIMEZONE" - // calendar component. - PropertyValidator.getInstance().assertOne(Property.TZOFFSETTO, - getProperties()); - - /* - * ; the following are each REQUIRED, ; but MUST NOT occur more than once dtstart / tzoffsetto / tzoffsetfrom / - */ - PropertyValidator.getInstance().assertOne(Property.DTSTART, - getProperties()); - - /* - * ; the following are optional, ; and MAY occur more than once comment / rdate / rrule / tzname / x-prop - */ - - if (recurse) { - validateProperties(); - } - } - - /** - * Returns the latest applicable onset of this observance for the specified date. - * @param date the latest date that an observance onset may occur - * @return the latest applicable observance date or null if there is no applicable observance onset for the - * specified date - */ - public final Date getLatestOnset(final Date date) { - - if (initialOnset == null) { - try { - initialOnset = applyOffsetFrom(calculateOnset(((DtStart) getProperty(Property.DTSTART)).getDate())); - } catch (ParseException e) { - Log log = LogFactory.getLog(Observance.class); - log.error("Unexpected error calculating initial onset", e); - // XXX: is this correct? - return null; - } - } - - // observance not applicable if date is before the effective date of this observance.. - if (date.before(initialOnset)) { - return null; - } - - if ((onsetsMillisec != null) && (onsetLimit == null || date.before(onsetLimit))) { - return getCachedOnset(date); - } - - Date onset = initialOnset; - Date initialOnsetUTC; - // get first onset without adding TZFROM as this may lead to a day boundary - // change which would be incompatible with BYDAY RRULES - // we will have to add the offset to all cacheable onsets - try { - initialOnsetUTC = calculateOnset(((DtStart) getProperty(Property.DTSTART)).getDate()); - } catch (ParseException e) { - Log log = LogFactory.getLog(Observance.class); - log.error("Unexpected error calculating initial onset", e); - // XXX: is this correct? - return null; - } - // collect all onsets for the purposes of caching.. - final DateList cacheableOnsets = new DateList(); - cacheableOnsets.setUtc(true); - cacheableOnsets.add(initialOnset); - - // check rdates for latest applicable onset.. - final PropertyList rdates = getProperties(Property.RDATE); - for (final Iterator i = rdates.iterator(); i.hasNext();) { - final RDate rdate = (RDate) i.next(); - for (final Iterator j = rdate.getDates().iterator(); j.hasNext();) { - try { - final DateTime rdateOnset = applyOffsetFrom(calculateOnset((Date) j.next())); - if (!rdateOnset.after(date) && rdateOnset.after(onset)) { - onset = rdateOnset; - } - /* - * else if (rdateOnset.after(date) && rdateOnset.after(onset) && (nextOnset == null || - * rdateOnset.before(nextOnset))) { nextOnset = rdateOnset; } - */ - cacheableOnsets.add(rdateOnset); - } catch (ParseException e) { - Log log = LogFactory.getLog(Observance.class); - log.error("Unexpected error calculating onset", e); - } - } - } - - // check recurrence rules for latest applicable onset.. - final PropertyList rrules = getProperties(Property.RRULE); - for (final Iterator i = rrules.iterator(); i.hasNext();) { - final RRule rrule = (RRule) i.next(); - // include future onsets to determine onset period.. - final Calendar cal = Dates.getCalendarInstance(date); - cal.setTime(date); - cal.add(Calendar.YEAR, 10); - onsetLimit = Dates.getInstance(cal.getTime(), Value.DATE_TIME); - final DateList recurrenceDates = rrule.getRecur().getDates(initialOnsetUTC, - onsetLimit, Value.DATE_TIME); - for (final Iterator j = recurrenceDates.iterator(); j.hasNext();) { - final DateTime rruleOnset = applyOffsetFrom((DateTime) j.next()); - if (!rruleOnset.after(date) && rruleOnset.after(onset)) { - onset = rruleOnset; - } - /* - * else if (rruleOnset.after(date) && rruleOnset.after(onset) && (nextOnset == null || - * rruleOnset.before(nextOnset))) { nextOnset = rruleOnset; } - */ - cacheableOnsets.add(rruleOnset); - } - } - - // cache onsets.. - Collections.sort(cacheableOnsets); - DateTime cacheableOnset = null; - this.onsetsMillisec = new long[cacheableOnsets.size()]; - this.onsetsDates = new DateTime[onsetsMillisec.length]; - - for (int i = 0; i < onsetsMillisec.length; i++) { - cacheableOnset = (DateTime)cacheableOnsets.get(i); - onsetsMillisec[i] = cacheableOnset.getTime(); - onsetsDates[i] = cacheableOnset; - } - - return onset; - } - - /** - * Returns a cached onset for the specified date. - * @param date - * @return a cached onset date or null if no cached onset is applicable for the specified date - */ - private DateTime getCachedOnset(final Date date) { - int index = Arrays.binarySearch(onsetsMillisec, date.getTime()); - if (index >= 0) { - return onsetsDates[index]; - } else { - int insertionIndex = -index -1; - return onsetsDates[insertionIndex -1]; - } - } - - /** - * Returns the mandatory dtstart property. - * @return the DTSTART property or null if not specified - */ - public final DtStart getStartDate() { - return (DtStart) getProperty(Property.DTSTART); - } - - /** - * Returns the mandatory tzoffsetfrom property. - * @return the TZOFFSETFROM property or null if not specified - */ - public final TzOffsetFrom getOffsetFrom() { - return (TzOffsetFrom) getProperty(Property.TZOFFSETFROM); - } - - /** - * Returns the mandatory tzoffsetto property. - * @return the TZOFFSETTO property or null if not specified - */ - public final TzOffsetTo getOffsetTo() { - return (TzOffsetTo) getProperty(Property.TZOFFSETTO); - } - -// private Date calculateOnset(DateProperty dateProperty) { -// return calculateOnset(dateProperty.getValue()); -// } -// - private DateTime calculateOnset(Date date) throws ParseException { - return calculateOnset(date.toString()); - } - - private DateTime calculateOnset(String dateStr) throws ParseException { - - // Translate local onset into UTC time by parsing local time - // as GMT and adjusting by TZOFFSETFROM if required - long utcOnset; - - synchronized (UTC_FORMAT) { - utcOnset = UTC_FORMAT.parse(dateStr).getTime(); - } - - // return a UTC - DateTime onset = new DateTime(true); - onset.setTime(utcOnset); - return onset; - } - - private DateTime applyOffsetFrom(DateTime orig) { - DateTime withOffset = new DateTime(true); - withOffset.setTime(orig.getTime() - getOffsetFrom().getOffset().getOffset()); - return withOffset; - } -} \ No newline at end of file