/// <summary> /// This method is used to return all recurring instances between the two specified date/times based on /// the current settings. /// </summary> /// <param name="fromDate">The minimum date/time on or after which instances should occur. This will /// include an instance if it starts before the date/time but overlaps it when its duration is added to /// its start time.</param> /// <param name="toDate">The maximum date/time on or before which instances should occur. This will /// include an instance if it starts on or before the specified date/time regardless of its duration.</param> /// <param name="inLocalTime">If true, the date/time parameters are assumed to be in local time and the /// returned date/times are expressed in local time. If false, the date/time parameters are assumed to /// be in the time zone of the object and the returned date/times are expressed in the time zone of the /// object as specified by the <see cref="TimeZoneId"/> property. If no time zone ID has been specified /// or it cannot be found, local time is used.</param> /// <returns>Returns a <see cref="DateTimeInstanceCollection"/> containing <see cref="DateTimeInstance" /> /// objects that represent all instances found between the two specified date/times. Instances may have /// a different duration if created from an <c>RDATE</c> property.</returns> /// <exception cref="ArgumentException">This is thrown if a start date has not been specified (it equals /// <c>DateTime.MinValue</c>) or the duration is negative.</exception> /// <seealso cref="AllInstances"/> /// <seealso cref="OccursOn"/> public DateTimeInstanceCollection InstancesBetween(DateTime fromDate, DateTime toDate, bool inLocalTime) { DateTimeCollection recurDates; Period p; DateTime endDate, tempDate1 = DateTime.MaxValue, tempDate2; int idx, count; string timeZoneID = this.TimeZoneId; PeriodCollection periods = new PeriodCollection(); DateTime startDate = this.StartDateTime.TimeZoneDateTime; Duration dur = this.InstanceDuration; if(startDate == DateTime.MinValue) throw new ArgumentException(LR.GetString("ExNoComponentStartDate")); if(dur.Ticks < 0) throw new ArgumentException(LR.GetString("ExRONegativeDuration")); // Convert fromDate and toDate to time zone time if inLocalTime is true. Recurrences are always // resolved in the time of the object. if(inLocalTime && timeZoneID != null) { fromDate = VCalendar.LocalTimeToTimeZoneTime(fromDate, timeZoneID).StartDateTime; toDate = VCalendar.LocalTimeToTimeZoneTime(toDate, timeZoneID).StartDateTime; } // There might be instances that overlap the requested range so we'll adjust the From date/time by // the duration to catch them. if(dur.Ticks > 1) fromDate = fromDate.Add(new TimeSpan(0 - dur.Ticks + 1)); // As per the spec, the start date/time is always included in the set but only if it (or it's // duration) is within the requested range. However, if it is recurring and the custom Exclude Start // property is set to true, it is not added. p = new Period(startDate, dur); if(((p.StartDateTime >= fromDate && p.StartDateTime <= toDate) || (p.EndDateTime >= fromDate && p.EndDateTime <= toDate)) && (!this.IsRecurring || !excludeStart)) { periods.Add(p); } // If it isn't recurring or starts after the requested end date, just return the collection as it is if(this.IsRecurring && startDate <= toDate) { // Expand each recurrence rule foreach(RRuleProperty rr in this.RecurrenceRules) { // If used, RecurUntil is stored in Universal Time which is converted to local time. If a // time zone ID is specified, convert it to that time zone temporarily to make sure things // are calculated correctly. if(rr.Recurrence.MaximumOccurrences == 0 && timeZoneID != null) { tempDate1 = rr.Recurrence.RecurUntil; rr.Recurrence.RecurUntil = VCalendar.LocalTimeToTimeZoneTime(tempDate1, timeZoneID).StartDateTime; } rr.Recurrence.StartDateTime = startDate; recurDates = rr.Recurrence.InstancesBetween(fromDate, toDate); if(rr.Recurrence.MaximumOccurrences == 0 && timeZoneID != null) rr.Recurrence.RecurUntil = tempDate1; foreach(DateTime dt in recurDates) periods.Add(new Period(dt, dur)); } // Add on recurrence dates within the range foreach(RDateProperty rd in this.RecurDates) if(rd.ValueLocation != ValLocValue.Period) { // If it's not a period, use the component's duration. If it's only a date, assume it's // the whole day. The spec is not clear on this so I'm making a best guess. if(rd.ValueLocation == ValLocValue.DateTime) endDate = rd.TimeZoneDateTime.Add(dur.TimeSpan); else endDate = rd.TimeZoneDateTime.Add(new TimeSpan(TimeSpan.TicksPerDay)); if((rd.TimeZoneDateTime >= fromDate && rd.TimeZoneDateTime <= toDate) || (endDate >= fromDate && endDate <= toDate)) { periods.Add(new Period(rd.TimeZoneDateTime, endDate)); } } else { // As with Recurrence.RecurUntil, the period values are in Universal Time so convert them // to the time zone time for proper comparison. tempDate1 = rd.PeriodValue.StartDateTime; tempDate2 = rd.PeriodValue.EndDateTime; if(timeZoneID != null) tempDate1 = VCalendar.LocalTimeToTimeZoneTime(tempDate1, timeZoneID).StartDateTime; if(timeZoneID != null) tempDate2 = VCalendar.LocalTimeToTimeZoneTime(tempDate2, timeZoneID).StartDateTime; if((tempDate1 >= fromDate && tempDate1 <= toDate) || (tempDate2 >= fromDate && tempDate2 <= toDate)) { periods.Add(new Period(tempDate1, tempDate2)); } } // Expand exception rules and filter out those instances count = periods.Count; foreach(RRuleProperty er in this.ExceptionRules) { // Same as above if(er.Recurrence.MaximumOccurrences == 0 && timeZoneID != null) { tempDate1 = er.Recurrence.RecurUntil; er.Recurrence.RecurUntil = VCalendar.LocalTimeToTimeZoneTime(tempDate1, timeZoneID).StartDateTime; } er.Recurrence.StartDateTime = startDate; recurDates = er.Recurrence.InstancesBetween(fromDate, toDate); if(er.Recurrence.MaximumOccurrences == 0 && timeZoneID != null) er.Recurrence.RecurUntil = tempDate1; foreach(DateTime dt in recurDates) for(idx = 0; idx < count; idx++) if(periods[idx].StartDateTime == dt) { periods.RemoveAt(idx); idx--; count--; } } // Filter out any exception dates foreach(ExDateProperty ed in this.ExceptionDates) { DateTime dt = ed.TimeZoneDateTime; // If it's only a date, assume it's the whole day and remove all instances on that day // regardless of the time. The spec is not clear on this so I'm making a best guess. if(ed.ValueLocation == ValLocValue.DateTime) { if(dt >= fromDate && dt <= toDate) for(idx = 0; idx < count; idx++) if(periods[idx].StartDateTime == dt) { periods.RemoveAt(idx); idx--; count--; } } else if(dt >= fromDate.Date && dt <= toDate.Date) for(idx = 0; idx < count; idx++) if(periods[idx].StartDateTime.Date == dt) { periods.RemoveAt(idx); idx--; count--; } } // Sort the periods and remove duplicates periods.Sort(true); for(idx = 1; idx < count; idx++) if(periods[idx] == periods[idx - 1]) { periods.RemoveAt(idx); idx--; count--; } } // Now convert the periods to DateTimeInstances that include the necessary time zone information DateTimeInstanceCollection dtic = new DateTimeInstanceCollection(); DateTimeInstance dti, dtiEnd; // Always in local time if there is no time zone ID if(timeZoneID == null) inLocalTime = true; foreach(Period pd in periods) { if(inLocalTime) { dti = VCalendar.TimeZoneTimeToLocalTime(pd.StartDateTime, timeZoneID); dtiEnd = VCalendar.TimeZoneTimeToLocalTime(pd.EndDateTime, timeZoneID); } else { dti = VCalendar.TimeZoneTimeInfo(pd.StartDateTime, timeZoneID); dtiEnd = VCalendar.TimeZoneTimeInfo(pd.EndDateTime, timeZoneID); } dti.Duration = pd.Duration; dti.EndDateTime = dtiEnd.EndDateTime; dti.EndIsDaylightSavingTime = dtiEnd.EndIsDaylightSavingTime; dti.EndTimeZoneName = dtiEnd.EndTimeZoneName; // If it already contains the entry and it is in DST, bump it forward an hour to account for the // time adjustment. This will happen on hourly, minutely, and secondly recurrence patterns. By // moving duplicates forward an hour, we retain the expected number of occurrences. if(!dtic.Contains(dti)) dtic.Add(dti); else if(dti.StartIsDaylightSavingTime) { dti.StartDateTime = dti.StartDateTime.AddHours(1); dti.EndDateTime = dti.EndDateTime.AddHours(1); dtic.Add(dti); } } return dtic; // And finally, we are done }
/// <summary> /// This method is used to return all recurring instances between the two specified date/times based on /// the current settings. /// </summary> /// <param name="fromDate">The minimum date/time on or after which instances should occur. This will /// include an instance if it starts before the date/time but overlaps it when its duration is added to /// its start time.</param> /// <param name="toDate">The maximum date/time on or before which instances should occur. This will /// include an instance if it starts on or before the specified date/time regardless of its duration.</param> /// <param name="inLocalTime">If true, the date/time parameters are assumed to be in local time and the /// returned date/times are expressed in local time. If false, the date/time parameters are assumed to /// be in the time zone of the object and the returned date/times are expressed in the time zone of the /// object as specified by the <see cref="TimeZoneId"/> property. If no time zone ID has been specified /// or it cannot be found, local time is used.</param> /// <returns>Returns a <see cref="DateTimeInstanceCollection"/> containing <see cref="DateTimeInstance" /> /// objects that represent all instances found between the two specified date/times. Instances may have /// a different duration if created from an <c>RDATE</c> property.</returns> /// <exception cref="ArgumentException">This is thrown if a start date has not been specified (it equals /// <c>DateTime.MinValue</c>) or the duration is negative.</exception> /// <seealso cref="AllInstances"/> /// <seealso cref="OccursOn"/> public DateTimeInstanceCollection InstancesBetween(DateTime fromDate, DateTime toDate, bool inLocalTime) { DateTimeCollection recurDates; Period p; DateTime endDate, tempDate1 = DateTime.MaxValue, tempDate2; int idx, count; string timeZoneID = this.TimeZoneId; PeriodCollection periods = new PeriodCollection(); DateTime startDate = this.StartDateTime.TimeZoneDateTime; Duration dur = this.InstanceDuration; if (startDate == DateTime.MinValue) { throw new ArgumentException(LR.GetString("ExNoComponentStartDate")); } if (dur.Ticks < 0) { throw new ArgumentException(LR.GetString("ExRONegativeDuration")); } // Convert fromDate and toDate to time zone time if inLocalTime is true. Recurrences are always // resolved in the time of the object. if (inLocalTime && timeZoneID != null) { fromDate = VCalendar.LocalTimeToTimeZoneTime(fromDate, timeZoneID).StartDateTime; toDate = VCalendar.LocalTimeToTimeZoneTime(toDate, timeZoneID).StartDateTime; } // There might be instances that overlap the requested range so we'll adjust the From date/time by // the duration to catch them. if (dur.Ticks > 1) { fromDate = fromDate.Add(new TimeSpan(0 - dur.Ticks + 1)); } // As per the spec, the start date/time is always included in the set but only if it (or it's // duration) is within the requested range. However, if it is recurring and the custom Exclude Start // property is set to true, it is not added. p = new Period(startDate, dur); if (((p.StartDateTime >= fromDate && p.StartDateTime <= toDate) || (p.EndDateTime >= fromDate && p.EndDateTime <= toDate)) && (!this.IsRecurring || !this.ExcludeStartDateTime)) { periods.Add(p); } // If it isn't recurring or starts after the requested end date, just return the collection as it is if (this.IsRecurring && startDate <= toDate) { // Expand each recurrence rule foreach (RRuleProperty rr in this.RecurrenceRules) { // If used, RecurUntil is stored in Universal Time which is converted to local time. If a // time zone ID is specified, convert it to that time zone temporarily to make sure things // are calculated correctly. if (rr.Recurrence.MaximumOccurrences == 0 && timeZoneID != null) { tempDate1 = rr.Recurrence.RecurUntil; rr.Recurrence.RecurUntil = VCalendar.LocalTimeToTimeZoneTime(tempDate1, timeZoneID).StartDateTime; } rr.Recurrence.StartDateTime = startDate; recurDates = rr.Recurrence.InstancesBetween(fromDate, toDate); if (rr.Recurrence.MaximumOccurrences == 0 && timeZoneID != null) { rr.Recurrence.RecurUntil = tempDate1; } foreach (DateTime dt in recurDates) { periods.Add(new Period(dt, dur)); } } // Add on recurrence dates within the range foreach (RDateProperty rd in this.RecurDates) { if (rd.ValueLocation != ValLocValue.Period) { // If it's not a period, use the component's duration. If it's only a date, assume it's // the whole day. The spec is not clear on this so I'm making a best guess. if (rd.ValueLocation == ValLocValue.DateTime) { endDate = rd.TimeZoneDateTime.Add(dur.TimeSpan); } else { endDate = rd.TimeZoneDateTime.Add(new TimeSpan(TimeSpan.TicksPerDay)); } if ((rd.TimeZoneDateTime >= fromDate && rd.TimeZoneDateTime <= toDate) || (endDate >= fromDate && endDate <= toDate)) { periods.Add(new Period(rd.TimeZoneDateTime, endDate)); } } else { // As with Recurrence.RecurUntil, the period values are in Universal Time so convert them // to the time zone time for proper comparison. tempDate1 = rd.PeriodValue.StartDateTime; tempDate2 = rd.PeriodValue.EndDateTime; if (timeZoneID != null) { tempDate1 = VCalendar.LocalTimeToTimeZoneTime(tempDate1, timeZoneID).StartDateTime; } if (timeZoneID != null) { tempDate2 = VCalendar.LocalTimeToTimeZoneTime(tempDate2, timeZoneID).StartDateTime; } if ((tempDate1 >= fromDate && tempDate1 <= toDate) || (tempDate2 >= fromDate && tempDate2 <= toDate)) { periods.Add(new Period(tempDate1, tempDate2)); } } } // Expand exception rules and filter out those instances count = periods.Count; foreach (RRuleProperty er in this.ExceptionRules) { // Same as above if (er.Recurrence.MaximumOccurrences == 0 && timeZoneID != null) { tempDate1 = er.Recurrence.RecurUntil; er.Recurrence.RecurUntil = VCalendar.LocalTimeToTimeZoneTime(tempDate1, timeZoneID).StartDateTime; } er.Recurrence.StartDateTime = startDate; recurDates = er.Recurrence.InstancesBetween(fromDate, toDate); if (er.Recurrence.MaximumOccurrences == 0 && timeZoneID != null) { er.Recurrence.RecurUntil = tempDate1; } foreach (DateTime dt in recurDates) { for (idx = 0; idx < count; idx++) { if (periods[idx].StartDateTime == dt) { periods.RemoveAt(idx); idx--; count--; } } } } // Filter out any exception dates foreach (ExDateProperty ed in this.ExceptionDates) { DateTime dt = ed.TimeZoneDateTime; // If it's only a date, assume it's the whole day and remove all instances on that day // regardless of the time. The spec is not clear on this so I'm making a best guess. if (ed.ValueLocation == ValLocValue.DateTime) { if (dt >= fromDate && dt <= toDate) { for (idx = 0; idx < count; idx++) { if (periods[idx].StartDateTime == dt) { periods.RemoveAt(idx); idx--; count--; } } } } else if (dt >= fromDate.Date && dt <= toDate.Date) { for (idx = 0; idx < count; idx++) { if (periods[idx].StartDateTime.Date == dt) { periods.RemoveAt(idx); idx--; count--; } } } } // Sort the periods and remove duplicates periods.Sort(true); for (idx = 1; idx < count; idx++) { if (periods[idx] == periods[idx - 1]) { periods.RemoveAt(idx); idx--; count--; } } } // Now convert the periods to DateTimeInstances that include the necessary time zone information DateTimeInstanceCollection dtic = new DateTimeInstanceCollection(); DateTimeInstance dti, dtiEnd; // Always in local time if there is no time zone ID if (timeZoneID == null) { inLocalTime = true; } foreach (Period pd in periods) { if (inLocalTime) { dti = VCalendar.TimeZoneTimeToLocalTime(pd.StartDateTime, timeZoneID); dtiEnd = VCalendar.TimeZoneTimeToLocalTime(pd.EndDateTime, timeZoneID); } else { dti = VCalendar.TimeZoneTimeInfo(pd.StartDateTime, timeZoneID); dtiEnd = VCalendar.TimeZoneTimeInfo(pd.EndDateTime, timeZoneID); } dti.Duration = pd.Duration; dti.EndDateTime = dtiEnd.EndDateTime; dti.EndIsDaylightSavingTime = dtiEnd.EndIsDaylightSavingTime; dti.EndTimeZoneName = dtiEnd.EndTimeZoneName; // If it already contains the entry and it is in DST, bump it forward an hour to account for the // time adjustment. This will happen on hourly, minutely, and secondly recurrence patterns. By // moving duplicates forward an hour, we retain the expected number of occurrences. if (!dtic.Contains(dti)) { dtic.Add(dti); } else if (dti.StartIsDaylightSavingTime) { dti.StartDateTime = dti.StartDateTime.AddHours(1); dti.EndDateTime = dti.EndDateTime.AddHours(1); dtic.Add(dti); } } return(dtic); // And finally, we are done }