src/net/fortuna/ical4j/model/component/Observance.java

Tue, 10 Feb 2015 19:38:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 10 Feb 2015 19:38:00 +0100
changeset 3
73bdfa70b04e
permissions
-rw-r--r--

Upgrade embedded ical4j from ancient whatever to upstream version 1.0.6.

michael@0 1 /**
michael@0 2 * Copyright (c) 2012, Ben Fortuna
michael@0 3 * All rights reserved.
michael@0 4 *
michael@0 5 * Redistribution and use in source and binary forms, with or without
michael@0 6 * modification, are permitted provided that the following conditions
michael@0 7 * are met:
michael@0 8 *
michael@0 9 * o Redistributions of source code must retain the above copyright
michael@0 10 * notice, this list of conditions and the following disclaimer.
michael@0 11 *
michael@0 12 * o Redistributions in binary form must reproduce the above copyright
michael@0 13 * notice, this list of conditions and the following disclaimer in the
michael@0 14 * documentation and/or other materials provided with the distribution.
michael@0 15 *
michael@0 16 * o Neither the name of Ben Fortuna nor the names of any other contributors
michael@0 17 * may be used to endorse or promote products derived from this software
michael@0 18 * without specific prior written permission.
michael@0 19 *
michael@0 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
michael@0 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
michael@0 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
michael@0 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
michael@0 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
michael@0 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
michael@0 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
michael@0 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
michael@0 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
michael@0 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
michael@0 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
michael@0 31 */
michael@0 32 package net.fortuna.ical4j.model.component;
michael@0 33
michael@0 34 import java.io.IOException;
michael@0 35 import java.text.DateFormat;
michael@0 36 import java.text.ParseException;
michael@0 37 import java.text.SimpleDateFormat;
michael@0 38 import java.util.Arrays;
michael@0 39 import java.util.Calendar;
michael@0 40 import java.util.Collections;
michael@0 41 import java.util.Iterator;
michael@0 42 import java.util.Map;
michael@0 43 import java.util.TreeMap;
michael@0 44
michael@0 45 import net.fortuna.ical4j.model.Component;
michael@0 46 import net.fortuna.ical4j.model.Date;
michael@0 47 import net.fortuna.ical4j.model.DateList;
michael@0 48 import net.fortuna.ical4j.model.DateTime;
michael@0 49 import net.fortuna.ical4j.model.Property;
michael@0 50 import net.fortuna.ical4j.model.PropertyList;
michael@0 51 import net.fortuna.ical4j.model.ValidationException;
michael@0 52 import net.fortuna.ical4j.model.parameter.Value;
michael@0 53 import net.fortuna.ical4j.model.property.DtStart;
michael@0 54 import net.fortuna.ical4j.model.property.RDate;
michael@0 55 import net.fortuna.ical4j.model.property.RRule;
michael@0 56 import net.fortuna.ical4j.model.property.TzOffsetFrom;
michael@0 57 import net.fortuna.ical4j.model.property.TzOffsetTo;
michael@0 58 import net.fortuna.ical4j.util.Dates;
michael@0 59 import net.fortuna.ical4j.util.PropertyValidator;
michael@0 60 import net.fortuna.ical4j.util.TimeZones;
michael@0 61
michael@0 62 import org.apache.commons.logging.Log;
michael@0 63 import org.apache.commons.logging.LogFactory;
michael@0 64
michael@0 65 /**
michael@0 66 * $Id$ [05-Apr-2004]
michael@0 67 *
michael@0 68 * Defines an iCalendar sub-component representing a timezone observance. Class made abstract such that only Standard
michael@0 69 * and Daylight instances are valid.
michael@0 70 * @author Ben Fortuna
michael@0 71 */
michael@0 72 public abstract class Observance extends Component {
michael@0 73
michael@0 74 /**
michael@0 75 *
michael@0 76 */
michael@0 77 private static final long serialVersionUID = 2523330383042085994L;
michael@0 78
michael@0 79 /**
michael@0 80 * one of 'standardc' or 'daylightc' MUST occur and each MAY occur more than once.
michael@0 81 */
michael@0 82 public static final String STANDARD = "STANDARD";
michael@0 83
michael@0 84 /**
michael@0 85 * Token for daylight observance.
michael@0 86 */
michael@0 87 public static final String DAYLIGHT = "DAYLIGHT";
michael@0 88
michael@0 89 // TODO: clear cache when observance definition changes (??)
michael@0 90 private long[] onsetsMillisec;
michael@0 91 private DateTime[] onsetsDates;
michael@0 92 private Map onsets = new TreeMap();
michael@0 93 private Date initialOnset = null;
michael@0 94
michael@0 95 /**
michael@0 96 * Used for parsing times in a UTC date-time representation.
michael@0 97 */
michael@0 98 private static final String UTC_PATTERN = "yyyyMMdd'T'HHmmss";
michael@0 99 private static final DateFormat UTC_FORMAT = new SimpleDateFormat(
michael@0 100 UTC_PATTERN);
michael@0 101
michael@0 102 static {
michael@0 103 UTC_FORMAT.setTimeZone(TimeZones.getUtcTimeZone());
michael@0 104 UTC_FORMAT.setLenient(false);
michael@0 105 }
michael@0 106
michael@0 107 /* If this is set we have rrules. If we get a date after this rebuild onsets */
michael@0 108 private Date onsetLimit;
michael@0 109
michael@0 110 /**
michael@0 111 * Constructs a timezone observance with the specified name and no properties.
michael@0 112 * @param name the name of this observance component
michael@0 113 */
michael@0 114 protected Observance(final String name) {
michael@0 115 super(name);
michael@0 116 }
michael@0 117
michael@0 118 /**
michael@0 119 * Constructor protected to enforce use of sub-classes from this library.
michael@0 120 * @param name the name of the time type
michael@0 121 * @param properties a list of properties
michael@0 122 */
michael@0 123 protected Observance(final String name, final PropertyList properties) {
michael@0 124 super(name, properties);
michael@0 125 }
michael@0 126
michael@0 127 /**
michael@0 128 * {@inheritDoc}
michael@0 129 */
michael@0 130 public final void validate(final boolean recurse) throws ValidationException {
michael@0 131
michael@0 132 // From "4.8.3.3 Time Zone Offset From":
michael@0 133 // Conformance: This property MUST be specified in a "VTIMEZONE"
michael@0 134 // calendar component.
michael@0 135 PropertyValidator.getInstance().assertOne(Property.TZOFFSETFROM,
michael@0 136 getProperties());
michael@0 137
michael@0 138 // From "4.8.3.4 Time Zone Offset To":
michael@0 139 // Conformance: This property MUST be specified in a "VTIMEZONE"
michael@0 140 // calendar component.
michael@0 141 PropertyValidator.getInstance().assertOne(Property.TZOFFSETTO,
michael@0 142 getProperties());
michael@0 143
michael@0 144 /*
michael@0 145 * ; the following are each REQUIRED, ; but MUST NOT occur more than once dtstart / tzoffsetto / tzoffsetfrom /
michael@0 146 */
michael@0 147 PropertyValidator.getInstance().assertOne(Property.DTSTART,
michael@0 148 getProperties());
michael@0 149
michael@0 150 /*
michael@0 151 * ; the following are optional, ; and MAY occur more than once comment / rdate / rrule / tzname / x-prop
michael@0 152 */
michael@0 153
michael@0 154 if (recurse) {
michael@0 155 validateProperties();
michael@0 156 }
michael@0 157 }
michael@0 158
michael@0 159 /**
michael@0 160 * Returns the latest applicable onset of this observance for the specified date.
michael@0 161 * @param date the latest date that an observance onset may occur
michael@0 162 * @return the latest applicable observance date or null if there is no applicable observance onset for the
michael@0 163 * specified date
michael@0 164 */
michael@0 165 public final Date getLatestOnset(final Date date) {
michael@0 166
michael@0 167 if (initialOnset == null) {
michael@0 168 try {
michael@0 169 initialOnset = applyOffsetFrom(calculateOnset(((DtStart) getProperty(Property.DTSTART)).getDate()));
michael@0 170 } catch (ParseException e) {
michael@0 171 Log log = LogFactory.getLog(Observance.class);
michael@0 172 log.error("Unexpected error calculating initial onset", e);
michael@0 173 // XXX: is this correct?
michael@0 174 return null;
michael@0 175 }
michael@0 176 }
michael@0 177
michael@0 178 // observance not applicable if date is before the effective date of this observance..
michael@0 179 if (date.before(initialOnset)) {
michael@0 180 return null;
michael@0 181 }
michael@0 182
michael@0 183 if ((onsetsMillisec != null) && (onsetLimit == null || date.before(onsetLimit))) {
michael@0 184 return getCachedOnset(date);
michael@0 185 }
michael@0 186
michael@0 187 Date onset = initialOnset;
michael@0 188 Date initialOnsetUTC;
michael@0 189 // get first onset without adding TZFROM as this may lead to a day boundary
michael@0 190 // change which would be incompatible with BYDAY RRULES
michael@0 191 // we will have to add the offset to all cacheable onsets
michael@0 192 try {
michael@0 193 initialOnsetUTC = calculateOnset(((DtStart) getProperty(Property.DTSTART)).getDate());
michael@0 194 } catch (ParseException e) {
michael@0 195 Log log = LogFactory.getLog(Observance.class);
michael@0 196 log.error("Unexpected error calculating initial onset", e);
michael@0 197 // XXX: is this correct?
michael@0 198 return null;
michael@0 199 }
michael@0 200 // collect all onsets for the purposes of caching..
michael@0 201 final DateList cacheableOnsets = new DateList();
michael@0 202 cacheableOnsets.setUtc(true);
michael@0 203 cacheableOnsets.add(initialOnset);
michael@0 204
michael@0 205 // check rdates for latest applicable onset..
michael@0 206 final PropertyList rdates = getProperties(Property.RDATE);
michael@0 207 for (final Iterator i = rdates.iterator(); i.hasNext();) {
michael@0 208 final RDate rdate = (RDate) i.next();
michael@0 209 for (final Iterator j = rdate.getDates().iterator(); j.hasNext();) {
michael@0 210 try {
michael@0 211 final DateTime rdateOnset = applyOffsetFrom(calculateOnset((Date) j.next()));
michael@0 212 if (!rdateOnset.after(date) && rdateOnset.after(onset)) {
michael@0 213 onset = rdateOnset;
michael@0 214 }
michael@0 215 /*
michael@0 216 * else if (rdateOnset.after(date) && rdateOnset.after(onset) && (nextOnset == null ||
michael@0 217 * rdateOnset.before(nextOnset))) { nextOnset = rdateOnset; }
michael@0 218 */
michael@0 219 cacheableOnsets.add(rdateOnset);
michael@0 220 } catch (ParseException e) {
michael@0 221 Log log = LogFactory.getLog(Observance.class);
michael@0 222 log.error("Unexpected error calculating onset", e);
michael@0 223 }
michael@0 224 }
michael@0 225 }
michael@0 226
michael@0 227 // check recurrence rules for latest applicable onset..
michael@0 228 final PropertyList rrules = getProperties(Property.RRULE);
michael@0 229 for (final Iterator i = rrules.iterator(); i.hasNext();) {
michael@0 230 final RRule rrule = (RRule) i.next();
michael@0 231 // include future onsets to determine onset period..
michael@0 232 final Calendar cal = Dates.getCalendarInstance(date);
michael@0 233 cal.setTime(date);
michael@0 234 cal.add(Calendar.YEAR, 10);
michael@0 235 onsetLimit = Dates.getInstance(cal.getTime(), Value.DATE_TIME);
michael@0 236 final DateList recurrenceDates = rrule.getRecur().getDates(initialOnsetUTC,
michael@0 237 onsetLimit, Value.DATE_TIME);
michael@0 238 for (final Iterator j = recurrenceDates.iterator(); j.hasNext();) {
michael@0 239 final DateTime rruleOnset = applyOffsetFrom((DateTime) j.next());
michael@0 240 if (!rruleOnset.after(date) && rruleOnset.after(onset)) {
michael@0 241 onset = rruleOnset;
michael@0 242 }
michael@0 243 /*
michael@0 244 * else if (rruleOnset.after(date) && rruleOnset.after(onset) && (nextOnset == null ||
michael@0 245 * rruleOnset.before(nextOnset))) { nextOnset = rruleOnset; }
michael@0 246 */
michael@0 247 cacheableOnsets.add(rruleOnset);
michael@0 248 }
michael@0 249 }
michael@0 250
michael@0 251 // cache onsets..
michael@0 252 Collections.sort(cacheableOnsets);
michael@0 253 DateTime cacheableOnset = null;
michael@0 254 this.onsetsMillisec = new long[cacheableOnsets.size()];
michael@0 255 this.onsetsDates = new DateTime[onsetsMillisec.length];
michael@0 256
michael@0 257 for (int i = 0; i < onsetsMillisec.length; i++) {
michael@0 258 cacheableOnset = (DateTime)cacheableOnsets.get(i);
michael@0 259 onsetsMillisec[i] = cacheableOnset.getTime();
michael@0 260 onsetsDates[i] = cacheableOnset;
michael@0 261 }
michael@0 262
michael@0 263 return onset;
michael@0 264 }
michael@0 265
michael@0 266 /**
michael@0 267 * Returns a cached onset for the specified date.
michael@0 268 * @param date
michael@0 269 * @return a cached onset date or null if no cached onset is applicable for the specified date
michael@0 270 */
michael@0 271 private DateTime getCachedOnset(final Date date) {
michael@0 272 int index = Arrays.binarySearch(onsetsMillisec, date.getTime());
michael@0 273 if (index >= 0) {
michael@0 274 return onsetsDates[index];
michael@0 275 } else {
michael@0 276 int insertionIndex = -index -1;
michael@0 277 return onsetsDates[insertionIndex -1];
michael@0 278 }
michael@0 279 }
michael@0 280
michael@0 281 /**
michael@0 282 * Returns the mandatory dtstart property.
michael@0 283 * @return the DTSTART property or null if not specified
michael@0 284 */
michael@0 285 public final DtStart getStartDate() {
michael@0 286 return (DtStart) getProperty(Property.DTSTART);
michael@0 287 }
michael@0 288
michael@0 289 /**
michael@0 290 * Returns the mandatory tzoffsetfrom property.
michael@0 291 * @return the TZOFFSETFROM property or null if not specified
michael@0 292 */
michael@0 293 public final TzOffsetFrom getOffsetFrom() {
michael@0 294 return (TzOffsetFrom) getProperty(Property.TZOFFSETFROM);
michael@0 295 }
michael@0 296
michael@0 297 /**
michael@0 298 * Returns the mandatory tzoffsetto property.
michael@0 299 * @return the TZOFFSETTO property or null if not specified
michael@0 300 */
michael@0 301 public final TzOffsetTo getOffsetTo() {
michael@0 302 return (TzOffsetTo) getProperty(Property.TZOFFSETTO);
michael@0 303 }
michael@0 304
michael@0 305 // private Date calculateOnset(DateProperty dateProperty) {
michael@0 306 // return calculateOnset(dateProperty.getValue());
michael@0 307 // }
michael@0 308 //
michael@0 309 private DateTime calculateOnset(Date date) throws ParseException {
michael@0 310 return calculateOnset(date.toString());
michael@0 311 }
michael@0 312
michael@0 313 private DateTime calculateOnset(String dateStr) throws ParseException {
michael@0 314
michael@0 315 // Translate local onset into UTC time by parsing local time
michael@0 316 // as GMT and adjusting by TZOFFSETFROM if required
michael@0 317 long utcOnset;
michael@0 318
michael@0 319 synchronized (UTC_FORMAT) {
michael@0 320 utcOnset = UTC_FORMAT.parse(dateStr).getTime();
michael@0 321 }
michael@0 322
michael@0 323 // return a UTC
michael@0 324 DateTime onset = new DateTime(true);
michael@0 325 onset.setTime(utcOnset);
michael@0 326 return onset;
michael@0 327 }
michael@0 328
michael@0 329 private DateTime applyOffsetFrom(DateTime orig) {
michael@0 330 DateTime withOffset = new DateTime(true);
michael@0 331 withOffset.setTime(orig.getTime() - getOffsetFrom().getOffset().getOffset());
michael@0 332 return withOffset;
michael@0 333 }
michael@0 334 }

mercurial