001    package biweekly.component;
002    
003    import java.util.Date;
004    import java.util.List;
005    
006    import biweekly.property.Comment;
007    import biweekly.property.DateStart;
008    import biweekly.property.ExceptionDates;
009    import biweekly.property.RecurrenceDates;
010    import biweekly.property.RecurrenceRule;
011    import biweekly.property.TimezoneName;
012    import biweekly.property.TimezoneOffsetFrom;
013    import biweekly.property.TimezoneOffsetTo;
014    import biweekly.util.DateTimeComponents;
015    import biweekly.util.Recurrence;
016    
017    /*
018     Copyright (c) 2013, Michael Angstadt
019     All rights reserved.
020    
021     Redistribution and use in source and binary forms, with or without
022     modification, are permitted provided that the following conditions are met: 
023    
024     1. Redistributions of source code must retain the above copyright notice, this
025     list of conditions and the following disclaimer. 
026     2. Redistributions in binary form must reproduce the above copyright notice,
027     this list of conditions and the following disclaimer in the documentation
028     and/or other materials provided with the distribution. 
029    
030     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
031     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
032     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
033     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
034     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
036     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
037     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
038     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
039     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
040     */
041    
042    /**
043     * Parent class for the "daylight" and "standard" timezone observances.
044     * @author Michael Angstadt
045     * @see DaylightSavingsTime
046     * @see StandardTime
047     * @rfc 5545 p.62-71
048     */
049    public abstract class Observance extends ICalComponent {
050            /**
051             * Gets the date that the timezone observance starts.
052             * @return the start date or null if not set
053             * @rfc 5545 p.97-8
054             */
055            public DateStart getDateStart() {
056                    return getProperty(DateStart.class);
057            }
058    
059            /**
060             * Sets the date that the timezone observance starts.
061             * @param dateStart the start date or null to remove
062             * @rfc 5545 p.97-8
063             */
064            public void setDateStart(DateStart dateStart) {
065                    if (dateStart != null) {
066                            dateStart.setLocalTime(true);
067                    }
068                    setProperty(DateStart.class, dateStart);
069            }
070    
071            /**
072             * Sets the date that the timezone observance starts.
073             * @param components the raw components of the start date or null to remove
074             * @return the property that was created
075             * @rfc 5545 p.97-8
076             */
077            public DateStart setDateStart(DateTimeComponents components) {
078                    DateStart prop = (components == null) ? null : new DateStart(components);
079                    setDateStart(prop);
080                    return prop;
081            }
082    
083            /**
084             * Gets the UTC offset that the timezone observance transitions to.
085             * @return the UTC offset or null if not set
086             * @rfc 5545 p.105-6
087             */
088            public TimezoneOffsetTo getTimezoneOffsetTo() {
089                    return getProperty(TimezoneOffsetTo.class);
090            }
091    
092            /**
093             * Sets the UTC offset that the timezone observance transitions to.
094             * @param timezoneOffsetTo the UTC offset or null to remove
095             * @rfc 5545 p.105-6
096             */
097            public void setTimezoneOffsetTo(TimezoneOffsetTo timezoneOffsetTo) {
098                    setProperty(TimezoneOffsetTo.class, timezoneOffsetTo);
099            }
100    
101            /**
102             * Sets the UTC offset that the timezone observance transitions to.
103             * @param hour the hour offset (e.g. "-5")
104             * @param minute the minute offset (e.g. "0")
105             * @return the property that was created
106             * @rfc 5545 p.105-6
107             */
108            public TimezoneOffsetTo setTimezoneOffsetTo(Integer hour, Integer minute) {
109                    TimezoneOffsetTo prop = new TimezoneOffsetTo(hour, minute);
110                    setTimezoneOffsetTo(prop);
111                    return prop;
112            }
113    
114            /**
115             * Gets the UTC offset that the timezone observance transitions from.
116             * @return the UTC offset or null if not set
117             * @rfc 5545 p.104-5
118             */
119            public TimezoneOffsetFrom getTimezoneOffsetFrom() {
120                    return getProperty(TimezoneOffsetFrom.class);
121            }
122    
123            /**
124             * Sets the UTC offset that the timezone observance transitions from.
125             * @param timezoneOffsetFrom the UTC offset or null to remove
126             * @rfc 5545 p.104-5
127             */
128            public void setTimezoneOffsetFrom(TimezoneOffsetFrom timezoneOffsetFrom) {
129                    setProperty(TimezoneOffsetFrom.class, timezoneOffsetFrom);
130            }
131    
132            /**
133             * Sets the UTC offset that the timezone observance transitions from.
134             * @param hour the hour offset (e.g. "-5")
135             * @param minute the minute offset (e.g. "0")
136             * @return the property that was created
137             * @rfc 5545 p.104-5
138             */
139            public TimezoneOffsetFrom setTimezoneOffsetFrom(Integer hour, Integer minute) {
140                    TimezoneOffsetFrom prop = new TimezoneOffsetFrom(hour, minute);
141                    setTimezoneOffsetFrom(prop);
142                    return prop;
143            }
144    
145            /**
146             * Gets how often the timezone observance repeats.
147             * @return the recurrence rule or null if not set
148             * @rfc 5545 p.122-32
149             */
150            public RecurrenceRule getRecurrenceRule() {
151                    return getProperty(RecurrenceRule.class);
152            }
153    
154            /**
155             * Sets how often the timezone observance repeats.
156             * @param recur the recurrence rule or null to remove
157             * @return the property that was created
158             * @rfc 5545 p.122-32
159             */
160            public RecurrenceRule setRecurrenceRule(Recurrence recur) {
161                    RecurrenceRule prop = (recur == null) ? null : new RecurrenceRule(recur);
162                    setRecurrenceRule(prop);
163                    return prop;
164            }
165    
166            /**
167             * Sets how often the timezone observance repeats.
168             * @param recurrenceRule the recurrence rule or null to remove
169             * @rfc 5545 p.122-32
170             */
171            public void setRecurrenceRule(RecurrenceRule recurrenceRule) {
172                    setProperty(RecurrenceRule.class, recurrenceRule);
173            }
174    
175            /**
176             * Gets the comments attached to the timezone observance.
177             * @return the comments
178             * @rfc 5545 p.83-4
179             */
180            public List<Comment> getComments() {
181                    return getProperties(Comment.class);
182            }
183    
184            /**
185             * Adds a comment to the timezone observance.
186             * @param comment the comment to add
187             * @rfc 5545 p.83-4
188             */
189            public void addComment(Comment comment) {
190                    addProperty(comment);
191            }
192    
193            /**
194             * Adds a comment to the timezone observance.
195             * @param comment the comment to add
196             * @return the property that was created
197             * @rfc 5545 p.83-4
198             */
199            public Comment addComment(String comment) {
200                    Comment prop = new Comment(comment);
201                    addComment(prop);
202                    return prop;
203            }
204    
205            /**
206             * Gets the list of dates/periods that help define the recurrence rule of
207             * this timezone observance (if one is defined).
208             * @return the recurrence dates
209             * @rfc 5545 p.120-2
210             */
211            public List<RecurrenceDates> getRecurrenceDates() {
212                    return getProperties(RecurrenceDates.class);
213            }
214    
215            /**
216             * Adds a list of dates/periods that help define the recurrence rule of this
217             * timezone observance (if one is defined).
218             * @param recurrenceDates the recurrence dates
219             * @rfc 5545 p.120-2
220             */
221            public void addRecurrenceDates(RecurrenceDates recurrenceDates) {
222                    addProperty(recurrenceDates);
223            }
224    
225            /**
226             * Gets the traditional, non-standard names for the timezone observance.
227             * @return the timezone observance names
228             * @rfc 5545 p.103-4
229             */
230            public List<TimezoneName> getTimezoneNames() {
231                    return getProperties(TimezoneName.class);
232            }
233    
234            /**
235             * Adds a traditional, non-standard name for the timezone observance.
236             * @param timezoneName the timezone observance name
237             * @rfc 5545 p.103-4
238             */
239            public void addTimezoneName(TimezoneName timezoneName) {
240                    addProperty(timezoneName);
241            }
242    
243            /**
244             * Adds a traditional, non-standard name for the timezone observance.
245             * @param timezoneName the timezone observance name (e.g. "EST")
246             * @return the property that was created
247             * @rfc 5545 p.103-4
248             */
249            public TimezoneName addTimezoneName(String timezoneName) {
250                    TimezoneName prop = new TimezoneName(timezoneName);
251                    addTimezoneName(prop);
252                    return prop;
253            }
254    
255            /**
256             * Gets the list of exceptions to the timezone observance.
257             * @return the list of exceptions
258             * @rfc 5545 p.118-20
259             */
260            public List<ExceptionDates> getExceptionDates() {
261                    return getProperties(ExceptionDates.class);
262            }
263    
264            /**
265             * Adds a list of exceptions to the timezone observance. Note that this
266             * property can contain multiple dates.
267             * @param exceptionDates the list of exceptions
268             * @rfc 5545 p.118-20
269             */
270            public void addExceptionDates(ExceptionDates exceptionDates) {
271                    addProperty(exceptionDates);
272            }
273    
274            @SuppressWarnings("unchecked")
275            @Override
276            protected void validate(List<ICalComponent> components, List<String> warnings) {
277                    checkRequiredCardinality(warnings, DateStart.class, TimezoneOffsetTo.class, TimezoneOffsetFrom.class);
278    
279                    //RFC 5545 p. 167
280                    DateStart dateStart = getDateStart();
281                    RecurrenceRule rrule = getRecurrenceRule();
282                    if (dateStart != null && rrule != null) {
283                            Date start = dateStart.getValue();
284                            Recurrence recur = rrule.getValue();
285                            if (start != null && recur != null) {
286                                    if (!dateStart.hasTime() && (!recur.getByHour().isEmpty() || !recur.getByMinute().isEmpty() || !recur.getBySecond().isEmpty())) {
287                                            warnings.add("The BYHOUR, BYMINUTE, and BYSECOND rule parts cannot be specified in the " + RecurrenceRule.class.getSimpleName() + " property when the " + DateStart.class.getSimpleName() + " property contains a date value (as opposed to a date-time value).");
288                                    }
289                            }
290                    }
291    
292                    //RFC 5545 p. 167
293                    if (getProperties(RecurrenceRule.class).size() > 1) {
294                            warnings.add("There should be only one instance of the " + RecurrenceRule.class.getSimpleName() + " property.");
295                    }
296            }
297    }