ical-org / ical.net

ical.NET - an open source iCal library for .NET
MIT License
784 stars 231 forks source link

Exdate not taken correctly into account when getting the FreeBusyStatus? #590

Open GillesDV opened 1 month ago

GillesDV commented 1 month ago

Hello,

We currently have a problem and I'm wondering if we parse our Calendar string wrong somewhere or if there's a bug in the code.

Background: In this scenario we have a Calendar that starts at 1st of August. Fills the whole day, and repeats all day. Basically always occupied. The 29th of August is an exception, then we're free. I did a GetFreeBusyStatus check on that day, but rather than Free, I get Busy.

This happens on ical.net nuget 4.2.0

Reproducable with the following bit of code:

void Main() {
    var timestamp = new DateTimeOffset(2024, 8, 29, 11, 00, 00, 0, TimeSpan.FromHours(2));
    string timeZoneId = "Europe/Paris";

    // All-day, repeating every day, starting from 1st of August. Except 29th of August
    var scheduleEverythingExcept29th = new Schedule2($@"BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART;TZID=Europe/Paris:20240801T000000
DTEND;TZID=Europe/Paris:20240802T000000
RRULE:FREQ=DAILY
EXDATE:20240828T220000Z
END:VEVENT
END:VCALENDAR");

    var calStatus = scheduleEverythingExcept29th.GetStatus(timestamp, timeZoneId);
    Console.WriteLine("calStatus: " + calStatus);
    // calStatus should be Free. 
        // Since we check in the middle of the 29th and the 29th is an exception of the daily-repetition of always occupied / busy
}
public sealed class Schedule2
{
    private readonly Calendar m_calendar;

    public Schedule2(string iCalendar)
    {

        Calendar calendar;

        using (var stringReader = new StringReader(iCalendar))
        {
            calendar = Calendar.Load(stringReader);

            if (calendar == null)
                throw new ArgumentException($"Unable to build a calendar out of the specified {nameof(iCalendar)} instance.");
        }

        m_calendar = calendar;
    }

    public FreeBusyStatus GetStatus(DateTimeOffset timestamp, string timeZoneId)
    {
        DateTime timestampLocal = DateTime.SpecifyKind(timestamp.DateTime, DateTimeKind.Local);

        SetCalendarTimeZone(timeZoneId);

        //  Computes the schedule of the day.
        var fromBeginningOfDay = new CalDateTime(timestampLocal.Date, timeZoneId);
        var toEndOfDay = new CalDateTime(timestampLocal.Date.AddDays(1d).Subtract(TimeSpan.FromTicks(1)), timeZoneId);

        FreeBusy freeBusy = m_calendar.GetFreeBusy(fromBeginningOfDay, toEndOfDay);

        //  Tests the schedule at the specified timestamp in the local time zone.
        var calTimestamp = new CalDateTime(timestampLocal, timeZoneId);
        FreeBusyStatus status = freeBusy.GetFreeBusyStatus(calTimestamp);

        return status;
    }

    private void SetCalendarTimeZone(string timeZoneId)
    {
        if (m_calendar.TimeZones.Count <= 0)
        {
            m_calendar.AddTimeZone(new VTimeZone(timeZoneId));
        }
        else
        {
            string timeZone = m_calendar.TimeZones.Where(z => z.TzId == timeZoneId).Select(z => z.TzId).FirstOrDefault();
            if (string.IsNullOrEmpty(timeZone))
                m_calendar.AddTimeZone(new VTimeZone(timeZoneId));
        }
    }
}

For the exDate formatting, I have tried both

But neither of them seem to give me the Free Status. Is there an error in the way we format our Calendar? Or perhaps in ical.net?