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

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

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 10 Feb 2015 19:58:00 +0100
changeset 4
45d57ecba757
permissions
-rw-r--r--

Upgrade the upgraded ical4j component to use org.apache.commons.lang3.

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

mercurial