rianjs / ical.net

ical.NET - an open source iCal library for .NET
MIT License
776 stars 230 forks source link

Concrete RecurrencePattern for all day event is not parsed correctly (with code example) #503

Open anichiporovich-luware opened 3 years ago

anichiporovich-luware commented 3 years ago

Overview

I have a code example here.

Application creates CalendarEvent with IsAllDay flag, Start and End date. And then adds RecurrenceRules as following: "RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=MO;BYSETPOS=1" (RRULE).

Expects that this RRULE should create an event that happens each Monday. But it happens each day. So when I use CalendarEvent.GetOccurrences it gives me always this event event for days when it should not happen.

Code Examples

In application I start from Start day and tries to use CalendarEvent.GetOccurrences on each next day. Provide some code below or on GitHub here.

Demo.cs

    public class Demo
    {
        private const string LocalTimeZone = "Europe/Minsk";
        private readonly CalendarEvent calendarEvent;

        public Demo()
        {
            // start day is Wednesday 
            var startDate = DateTimeOffset.Parse("2020-08-05T21:00:00.0000000+00:00");
            var localStartDate = ToCal(startDate).ToTimeZone(LocalTimeZone);
            var endDate = DateTimeOffset.Parse("2020-11-06T20:59:59.0000000+00:00");
            var localEndDate = ToCal(endDate).ToTimeZone(LocalTimeZone);

            calendarEvent = new CalendarEvent
            {
                Status = "Holiday",
                Start = localStartDate,
                End = localEndDate,
                IsAllDay = true,
            };

            // RRULE for Monthly each Monday
            calendarEvent.RecurrenceRules.Add(
                new RecurrencePattern("RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=MO;BYSETPOS=1"));
        }

        public string GetStatus(DateTimeOffset now)
        {
            var localNow = ToCal(now).ToTimeZone(LocalTimeZone);

            var found = calendarEvent.GetOccurrences(localNow, localNow);

            return ExtractStatus(found.FirstOrDefault());
        }

        private IDateTime ToCal(DateTimeOffset o) =>
            new CalDateTime(o.UtcDateTime);

        private string ExtractStatus(Occurrence x) =>
            ((CalendarEvent) x?.Source)?.Status;
    }

Program.cs

var now = DateTimeOffset.Parse("2020-08-07T11:31:38.1222194+00:00");
var demo = new Demo();
var count = 0;
var maxCount = 100;
while (count < maxCount)
{
    System.Console.WriteLine($"{now.Date.ToShortDateString()} - {demo.GetStatus(now)}");
    now = now.AddDays(1);
    count++;
}
System.Console.WriteLine("Press [Enter] to exit.");
System.Console.ReadLine();

How i tested RRULE online (not sure if it is correct approach)

I tried to find online parsers for RRULE (because it might be that my rule is wrong). And found this one http://worthfreeman.com/projects/online-icalendar-recurrence-event-parser/ And if I specify the following

DTSTART:20200805T000000
DTEND:20201106T115900
RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=MO;BYSETPOS=1

It specifies correct days when event happens. Only Mondays.

Many thanks for any help or comments.

rootGst commented 3 years ago

@anichiporovich-luware I am not sure if I got you right. Between 2020-08-05 (Wednesday) and 2020-11-06 (Friday) you want every Monday an all-day event called "Holiday". Is this right? You have defined an event, that lasts for 3 month (2020-08-05 to 2020-11-06) and will get repeated every Monday forever since the RRule is not terminated. You could define your event like this:

/// monday
var startDate = Datetime.Parse("2020-08-10");

calendarEvent = new CalendarEvent {
    Status = "Holiday",
    Start = startDate,
    IsAllDay = true,
};

// RRULE for Monthly each Monday
calendarEvent.RecurrenceRules.Add(
    new RecurrencePattern("RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=MO;BYSETPOS=1;UNTIL=2020-11-06"));

Consider following points:

anichiporovich-luware commented 2 years ago

@rootGst thanks a lot for your response. I see that the example that I sent has incorrect usage of RRULE and understanding of what startDate should be set to. So I changed it to the following

var startDate = DateTimeOffset.Parse("2020-08-10");
var localStartDate = ToCal(startDate).ToTimeZone(LocalTimeZone);
calendarEvent = new CalendarEvent
{
    Status = "Holiday",
    Start = localStartDate,
    IsAllDay = true,
};
calendarEvent.RecurrenceRules.Add(
      new RecurrencePattern("RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=MO;BYSETPOS=1;UNTIL=2020-11-06"));

But my original question was a bit messed up. It contained two questions, about event that exists after End Date and it was solved with UNTIL that your suggested to use. And another part is that I would expect to have event on Monday 2020-08-10 (2020 August 10), and then next Monday 2020-08-17. And I did not manage to achieve it in this code sample (see below)

To test it in Demo.cs there is method GetStatus(DateTime that simply return event name If I have an issue here e.g. incorrect usage of iCal.Net API, please let me know.

Example below (or you can take a look at my code on GitHube here.

public string GetStatus(DateTimeOffset now)
{
    var localNow = ToCal(now).ToTimeZone(LocalTimeZone);
    var found = calendarEvent.GetOccurrences(localNow, localNow);
    return ExtractStatus(found.FirstOrDefault());
}

private IDateTime ToCal(DateTimeOffset o) =>
    new CalDateTime(o.UtcDateTime);

private string ExtractStatus(Occurrence x) =>
    ((CalendarEvent) x?.Source)?.Status;

At the end in Program.cs I have a code that executes GetStatus each day starting from 2020-08-09. So what I would expect to get is the name of event like 'Holiday' for only two dates, but after I applied changes e.g. the same code that you suggested above, I do not have events

output looks like this

8/10/2020 - <None>
8/11/2020 - <None>
8/12/2020 - <None>
8/13/2020 - <None>
8/14/2020 - <None>
8/15/2020 - <None>
8/16/2020 - <None>
8/17/2020 - <None>
8/18/2020 - <None>
8/19/2020 - <None>
8/20/2020 - <None>
8/21/2020 - <None>
8/22/2020 - <None>
8/23/2020 - <None>

NOTE That when I do almost the same with slight difference - specify end date

var startDate = DateTimeOffset.Parse("2020-08-10");
var localStartDate = ToCal(startDate).ToTimeZone(LocalTimeZone);
var endDate = DateTimeOffset.Parse("2020-11-06");
var localEndDate = ToCal(endDate).ToTimeZone(LocalTimeZone);

calendarEvent = new CalendarEvent
{
    Status = "Holiday",
    Start = localStartDate,
    End = localEndDate,
    IsAllDay = true,
};

calendarEvent.RecurrenceRules.Add(
                new RecurrencePattern("RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=MO;BYSETPOS=1;UNTIL=2020-11-06"));

Then the output is the following

8/9/2020 - <None>
8/10/2020 - Holiday
8/11/2020 - Holiday
8/12/2020 - Holiday
8/13/2020 - Holiday
8/14/2020 - Holiday
8/15/2020 - Holiday
8/16/2020 - Holiday
8/17/2020 - Holiday
8/18/2020 - Holiday
8/19/2020 - Holiday
8/20/2020 - Holiday
8/21/2020 - Holiday
8/22/2020 - Holiday
8/23/2020 - Holiday

But what I want is to have "Holiday" only for 2020-08-10 and 2020-08-17. Could you please help me to understand how can I achieve it.

Thanks a lot for your help in advance.

Best regards, Alex.