1.1 --- a/src/net/fortuna/ical4j/model/component/Observance.java Thu Feb 12 18:02:00 2015 +0100 1.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 1.3 @@ -1,334 +0,0 @@ 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.component; 1.36 - 1.37 -import java.io.IOException; 1.38 -import java.text.DateFormat; 1.39 -import java.text.ParseException; 1.40 -import java.text.SimpleDateFormat; 1.41 -import java.util.Arrays; 1.42 -import java.util.Calendar; 1.43 -import java.util.Collections; 1.44 -import java.util.Iterator; 1.45 -import java.util.Map; 1.46 -import java.util.TreeMap; 1.47 - 1.48 -import net.fortuna.ical4j.model.Component; 1.49 -import net.fortuna.ical4j.model.Date; 1.50 -import net.fortuna.ical4j.model.DateList; 1.51 -import net.fortuna.ical4j.model.DateTime; 1.52 -import net.fortuna.ical4j.model.Property; 1.53 -import net.fortuna.ical4j.model.PropertyList; 1.54 -import net.fortuna.ical4j.model.ValidationException; 1.55 -import net.fortuna.ical4j.model.parameter.Value; 1.56 -import net.fortuna.ical4j.model.property.DtStart; 1.57 -import net.fortuna.ical4j.model.property.RDate; 1.58 -import net.fortuna.ical4j.model.property.RRule; 1.59 -import net.fortuna.ical4j.model.property.TzOffsetFrom; 1.60 -import net.fortuna.ical4j.model.property.TzOffsetTo; 1.61 -import net.fortuna.ical4j.util.Dates; 1.62 -import net.fortuna.ical4j.util.PropertyValidator; 1.63 -import net.fortuna.ical4j.util.TimeZones; 1.64 - 1.65 -import org.apache.commons.logging.Log; 1.66 -import org.apache.commons.logging.LogFactory; 1.67 - 1.68 -/** 1.69 - * $Id$ [05-Apr-2004] 1.70 - * 1.71 - * Defines an iCalendar sub-component representing a timezone observance. Class made abstract such that only Standard 1.72 - * and Daylight instances are valid. 1.73 - * @author Ben Fortuna 1.74 - */ 1.75 -public abstract class Observance extends Component { 1.76 - 1.77 - /** 1.78 - * 1.79 - */ 1.80 - private static final long serialVersionUID = 2523330383042085994L; 1.81 - 1.82 - /** 1.83 - * one of 'standardc' or 'daylightc' MUST occur and each MAY occur more than once. 1.84 - */ 1.85 - public static final String STANDARD = "STANDARD"; 1.86 - 1.87 - /** 1.88 - * Token for daylight observance. 1.89 - */ 1.90 - public static final String DAYLIGHT = "DAYLIGHT"; 1.91 - 1.92 - // TODO: clear cache when observance definition changes (??) 1.93 - private long[] onsetsMillisec; 1.94 - private DateTime[] onsetsDates; 1.95 - private Map onsets = new TreeMap(); 1.96 - private Date initialOnset = null; 1.97 - 1.98 - /** 1.99 - * Used for parsing times in a UTC date-time representation. 1.100 - */ 1.101 - private static final String UTC_PATTERN = "yyyyMMdd'T'HHmmss"; 1.102 - private static final DateFormat UTC_FORMAT = new SimpleDateFormat( 1.103 - UTC_PATTERN); 1.104 - 1.105 - static { 1.106 - UTC_FORMAT.setTimeZone(TimeZones.getUtcTimeZone()); 1.107 - UTC_FORMAT.setLenient(false); 1.108 - } 1.109 - 1.110 - /* If this is set we have rrules. If we get a date after this rebuild onsets */ 1.111 - private Date onsetLimit; 1.112 - 1.113 - /** 1.114 - * Constructs a timezone observance with the specified name and no properties. 1.115 - * @param name the name of this observance component 1.116 - */ 1.117 - protected Observance(final String name) { 1.118 - super(name); 1.119 - } 1.120 - 1.121 - /** 1.122 - * Constructor protected to enforce use of sub-classes from this library. 1.123 - * @param name the name of the time type 1.124 - * @param properties a list of properties 1.125 - */ 1.126 - protected Observance(final String name, final PropertyList properties) { 1.127 - super(name, properties); 1.128 - } 1.129 - 1.130 - /** 1.131 - * {@inheritDoc} 1.132 - */ 1.133 - public final void validate(final boolean recurse) throws ValidationException { 1.134 - 1.135 - // From "4.8.3.3 Time Zone Offset From": 1.136 - // Conformance: This property MUST be specified in a "VTIMEZONE" 1.137 - // calendar component. 1.138 - PropertyValidator.getInstance().assertOne(Property.TZOFFSETFROM, 1.139 - getProperties()); 1.140 - 1.141 - // From "4.8.3.4 Time Zone Offset To": 1.142 - // Conformance: This property MUST be specified in a "VTIMEZONE" 1.143 - // calendar component. 1.144 - PropertyValidator.getInstance().assertOne(Property.TZOFFSETTO, 1.145 - getProperties()); 1.146 - 1.147 - /* 1.148 - * ; the following are each REQUIRED, ; but MUST NOT occur more than once dtstart / tzoffsetto / tzoffsetfrom / 1.149 - */ 1.150 - PropertyValidator.getInstance().assertOne(Property.DTSTART, 1.151 - getProperties()); 1.152 - 1.153 - /* 1.154 - * ; the following are optional, ; and MAY occur more than once comment / rdate / rrule / tzname / x-prop 1.155 - */ 1.156 - 1.157 - if (recurse) { 1.158 - validateProperties(); 1.159 - } 1.160 - } 1.161 - 1.162 - /** 1.163 - * Returns the latest applicable onset of this observance for the specified date. 1.164 - * @param date the latest date that an observance onset may occur 1.165 - * @return the latest applicable observance date or null if there is no applicable observance onset for the 1.166 - * specified date 1.167 - */ 1.168 - public final Date getLatestOnset(final Date date) { 1.169 - 1.170 - if (initialOnset == null) { 1.171 - try { 1.172 - initialOnset = applyOffsetFrom(calculateOnset(((DtStart) getProperty(Property.DTSTART)).getDate())); 1.173 - } catch (ParseException e) { 1.174 - Log log = LogFactory.getLog(Observance.class); 1.175 - log.error("Unexpected error calculating initial onset", e); 1.176 - // XXX: is this correct? 1.177 - return null; 1.178 - } 1.179 - } 1.180 - 1.181 - // observance not applicable if date is before the effective date of this observance.. 1.182 - if (date.before(initialOnset)) { 1.183 - return null; 1.184 - } 1.185 - 1.186 - if ((onsetsMillisec != null) && (onsetLimit == null || date.before(onsetLimit))) { 1.187 - return getCachedOnset(date); 1.188 - } 1.189 - 1.190 - Date onset = initialOnset; 1.191 - Date initialOnsetUTC; 1.192 - // get first onset without adding TZFROM as this may lead to a day boundary 1.193 - // change which would be incompatible with BYDAY RRULES 1.194 - // we will have to add the offset to all cacheable onsets 1.195 - try { 1.196 - initialOnsetUTC = calculateOnset(((DtStart) getProperty(Property.DTSTART)).getDate()); 1.197 - } catch (ParseException e) { 1.198 - Log log = LogFactory.getLog(Observance.class); 1.199 - log.error("Unexpected error calculating initial onset", e); 1.200 - // XXX: is this correct? 1.201 - return null; 1.202 - } 1.203 - // collect all onsets for the purposes of caching.. 1.204 - final DateList cacheableOnsets = new DateList(); 1.205 - cacheableOnsets.setUtc(true); 1.206 - cacheableOnsets.add(initialOnset); 1.207 - 1.208 - // check rdates for latest applicable onset.. 1.209 - final PropertyList rdates = getProperties(Property.RDATE); 1.210 - for (final Iterator i = rdates.iterator(); i.hasNext();) { 1.211 - final RDate rdate = (RDate) i.next(); 1.212 - for (final Iterator j = rdate.getDates().iterator(); j.hasNext();) { 1.213 - try { 1.214 - final DateTime rdateOnset = applyOffsetFrom(calculateOnset((Date) j.next())); 1.215 - if (!rdateOnset.after(date) && rdateOnset.after(onset)) { 1.216 - onset = rdateOnset; 1.217 - } 1.218 - /* 1.219 - * else if (rdateOnset.after(date) && rdateOnset.after(onset) && (nextOnset == null || 1.220 - * rdateOnset.before(nextOnset))) { nextOnset = rdateOnset; } 1.221 - */ 1.222 - cacheableOnsets.add(rdateOnset); 1.223 - } catch (ParseException e) { 1.224 - Log log = LogFactory.getLog(Observance.class); 1.225 - log.error("Unexpected error calculating onset", e); 1.226 - } 1.227 - } 1.228 - } 1.229 - 1.230 - // check recurrence rules for latest applicable onset.. 1.231 - final PropertyList rrules = getProperties(Property.RRULE); 1.232 - for (final Iterator i = rrules.iterator(); i.hasNext();) { 1.233 - final RRule rrule = (RRule) i.next(); 1.234 - // include future onsets to determine onset period.. 1.235 - final Calendar cal = Dates.getCalendarInstance(date); 1.236 - cal.setTime(date); 1.237 - cal.add(Calendar.YEAR, 10); 1.238 - onsetLimit = Dates.getInstance(cal.getTime(), Value.DATE_TIME); 1.239 - final DateList recurrenceDates = rrule.getRecur().getDates(initialOnsetUTC, 1.240 - onsetLimit, Value.DATE_TIME); 1.241 - for (final Iterator j = recurrenceDates.iterator(); j.hasNext();) { 1.242 - final DateTime rruleOnset = applyOffsetFrom((DateTime) j.next()); 1.243 - if (!rruleOnset.after(date) && rruleOnset.after(onset)) { 1.244 - onset = rruleOnset; 1.245 - } 1.246 - /* 1.247 - * else if (rruleOnset.after(date) && rruleOnset.after(onset) && (nextOnset == null || 1.248 - * rruleOnset.before(nextOnset))) { nextOnset = rruleOnset; } 1.249 - */ 1.250 - cacheableOnsets.add(rruleOnset); 1.251 - } 1.252 - } 1.253 - 1.254 - // cache onsets.. 1.255 - Collections.sort(cacheableOnsets); 1.256 - DateTime cacheableOnset = null; 1.257 - this.onsetsMillisec = new long[cacheableOnsets.size()]; 1.258 - this.onsetsDates = new DateTime[onsetsMillisec.length]; 1.259 - 1.260 - for (int i = 0; i < onsetsMillisec.length; i++) { 1.261 - cacheableOnset = (DateTime)cacheableOnsets.get(i); 1.262 - onsetsMillisec[i] = cacheableOnset.getTime(); 1.263 - onsetsDates[i] = cacheableOnset; 1.264 - } 1.265 - 1.266 - return onset; 1.267 - } 1.268 - 1.269 - /** 1.270 - * Returns a cached onset for the specified date. 1.271 - * @param date 1.272 - * @return a cached onset date or null if no cached onset is applicable for the specified date 1.273 - */ 1.274 - private DateTime getCachedOnset(final Date date) { 1.275 - int index = Arrays.binarySearch(onsetsMillisec, date.getTime()); 1.276 - if (index >= 0) { 1.277 - return onsetsDates[index]; 1.278 - } else { 1.279 - int insertionIndex = -index -1; 1.280 - return onsetsDates[insertionIndex -1]; 1.281 - } 1.282 - } 1.283 - 1.284 - /** 1.285 - * Returns the mandatory dtstart property. 1.286 - * @return the DTSTART property or null if not specified 1.287 - */ 1.288 - public final DtStart getStartDate() { 1.289 - return (DtStart) getProperty(Property.DTSTART); 1.290 - } 1.291 - 1.292 - /** 1.293 - * Returns the mandatory tzoffsetfrom property. 1.294 - * @return the TZOFFSETFROM property or null if not specified 1.295 - */ 1.296 - public final TzOffsetFrom getOffsetFrom() { 1.297 - return (TzOffsetFrom) getProperty(Property.TZOFFSETFROM); 1.298 - } 1.299 - 1.300 - /** 1.301 - * Returns the mandatory tzoffsetto property. 1.302 - * @return the TZOFFSETTO property or null if not specified 1.303 - */ 1.304 - public final TzOffsetTo getOffsetTo() { 1.305 - return (TzOffsetTo) getProperty(Property.TZOFFSETTO); 1.306 - } 1.307 - 1.308 -// private Date calculateOnset(DateProperty dateProperty) { 1.309 -// return calculateOnset(dateProperty.getValue()); 1.310 -// } 1.311 -// 1.312 - private DateTime calculateOnset(Date date) throws ParseException { 1.313 - return calculateOnset(date.toString()); 1.314 - } 1.315 - 1.316 - private DateTime calculateOnset(String dateStr) throws ParseException { 1.317 - 1.318 - // Translate local onset into UTC time by parsing local time 1.319 - // as GMT and adjusting by TZOFFSETFROM if required 1.320 - long utcOnset; 1.321 - 1.322 - synchronized (UTC_FORMAT) { 1.323 - utcOnset = UTC_FORMAT.parse(dateStr).getTime(); 1.324 - } 1.325 - 1.326 - // return a UTC 1.327 - DateTime onset = new DateTime(true); 1.328 - onset.setTime(utcOnset); 1.329 - return onset; 1.330 - } 1.331 - 1.332 - private DateTime applyOffsetFrom(DateTime orig) { 1.333 - DateTime withOffset = new DateTime(true); 1.334 - withOffset.setTime(orig.getTime() - getOffsetFrom().getOffset().getOffset()); 1.335 - return withOffset; 1.336 - } 1.337 -} 1.338 \ No newline at end of file