Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test 'Evaluate1' broken - should fail but succeeds #602

Closed
minichma opened this issue Oct 15, 2024 · 12 comments · Fixed by #616
Closed

Test 'Evaluate1' broken - should fail but succeeds #602

minichma opened this issue Oct 15, 2024 · 12 comments · Fixed by #616
Labels

Comments

@minichma
Copy link
Collaborator

minichma commented Oct 15, 2024

The test 'Evaluate1' is broken, as it tests for occurrences' properties but doesn't produce any. This is a improved version of the test. The relevant difference is the start date passed to GetOccurrences is fixed and Assert.AreEqual(5, occurrences.Count) is added. The fixed version fails on current main branch.

However, I'm not sure, whether the case covered in that test is allowed at all but I assume it isn't. I.e. the MINUTELY freq requires a time component, which doesn't exist in the given case, because evt.Start is date-only. The comment says 'Start at midnight, UTC time', but without a time component, there is no real start date. Maybe it wasn't intended to have a date-only start, so then the problem would be caused by this, which is a problem closely related to #564.

        /// <summary>
        /// Ensures that the StartTime and EndTime of periods have
        /// HasTime set to true if the beginning time had HasTime set
        /// to false.
        /// </summary>
        [Test, Category("Recurrence")]
        public void Evaluate1()
        {
            Calendar cal = new Calendar();
            CalendarEvent evt = cal.Create<CalendarEvent>();
            evt.Summary = "Event summary";

            // Start at midnight, UTC time
            evt.Start = new CalDateTime(DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc));

            evt.RecurrenceRules.Add(new RecurrencePattern("FREQ=MINUTELY;INTERVAL=10;COUNT=5"));
            var occurrences = evt.GetOccurrences(CalDateTime.Today.AddDays(-1), CalDateTime.Today.AddDays(1));

            Assert.AreEqual(5, occurrences.Count);
            Assert.IsTrue(occurrences.All(x => x.Period.StartTime.HasTime), "All recurrences of this event should have a time set.");
            Assert.IsTrue(occurrences.Skip(1).All(x => x.Period.StartTime.Value.TimeOfDay != TimeSpan.Zero));
        }
@axunonb
Copy link
Collaborator

axunonb commented Oct 15, 2024

Hey, very good! Thanks for reviewing.

Maybe it wasn't intended to have a date-only start

This what I assume, too

HasTime = value.Second != 0 || value.Minute != 0 || value.Hour != 0;

Agree, it is prone to errors using "magic numbers" (0). The first idea, to use a nullable TimeSpan as an instance variable is not enough. IDateTime (and thus the HasDate and HasTime members) and its class implementations is literally present everywhere. This could become a tough job.

@axunonb axunonb added the bug label Oct 15, 2024
@minichma
Copy link
Collaborator Author

Well, according to the doc-comment the HasTime being false is intended. The problem for 0 recurrences being returned is that the start time passed to GetOccurrences() is after the last occurence. After fixing that 1 recurrence is returned but the test still fails, because the returned single occurrence does not have HasTime set, as required by the test. I don't know though what the expected outcome would be here as I don't think this whole test case is conformant to RFC 5545.

@minichma
Copy link
Collaborator Author

The first idea, to use a nullable TimeSpan as an instance variable is not enough. IDateTime (and thus the HasDate and HasTime members) and its class implementations is literally present everywhere. This could become a tough job.

Another problem with today's implementation of Period is that it doesn't retain the information whether it has been specified in the form date-time "/" date-time or date-time "/" dur-value as pointed out in #570.

I feel that all this should be cleaned up in a v5, where we don't need to deal with backwards-compatibility. Otherwise this would become overly challenging.

@axunonb
Copy link
Collaborator

axunonb commented Oct 16, 2024

don't think this whole test case is conformant to RFC 5545

iCalaendar Validator considers the ics string from the test method as valid :)
Maybe ask them why...

BEGIN:VCALENDAR
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:20241016T083451Z
DTSTART;VALUE=DATE:20241016
RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5
SEQUENCE:0
SUMMARY:Event summary
UID:785daa26-985e-462b-afd1-93d5a0e2e42d
END:VEVENT
END:VCALENDAR

@minichma
Copy link
Collaborator Author

minichma commented Oct 16, 2024

Hm, but which occurrences would be expected?

libical doesn't handle this case cleanly either. They return 5 recurrences, everyone date-only 20241016.

@axunonb
Copy link
Collaborator

axunonb commented Oct 16, 2024

I have asked them for their opinion:

Hi,
I'm one of the maintainers of https://github.com/ical-org/ical.net
The calendar below is valid for your validator. So far so good.
However, we're just discussing internally, whether it's really RFC compliant, and if so, which occurrences were to be expected.
We'd be happy if you gave us a helping hand.
Thanks a lot.

BEGIN:VCALENDAR
PRODID:-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:20241016T083451Z
DTSTART;VALUE=DATE:20241016
RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5
SEQUENCE:0
SUMMARY:Event summary
UID:785daa26-985e-462b-afd1-93d5a0e2e42d
END:VEVENT
END:VCALENDAR

@axunonb
Copy link
Collaborator

axunonb commented Oct 16, 2024

What client apps do with it:

  • Outlook 365: Import does just nothing (calendar not added, no error message)
  • Outlook 365 Web: Imports and shows the calendar without entries
  • https://icscalendar.com/preview/: Hangs with no response

This might be an indication for no occurrences recognized or it's because of no duration.

Co-Pilot says:

The provided VEVENT in the VCALENDAR file specifies a recurrence rule (RRULE) with the following properties:
• FREQ=MINUTELY: The event recurs every minute.
• INTERVAL=10: The event recurs every 10 minutes.
• COUNT=5: The event occurs 5 times in total.
Given these properties, the event will occur 5 times, starting from the DTSTART date and time, with each occurrence 10 minutes apart.

The 5 occurrences will be:

  1. October 16, 2024, 00:00:00
  2. October 16, 2024, 00:10:00
  3. October 16, 2024, 00:20:00
  4. October 16, 2024, 00:30:00
  5. October 16, 2024, 00:40:00

@minichma
Copy link
Collaborator Author

Tried to find the relevant spec in the RFC but couldn't. I don't think this case is explicitly specified. A related topic is defined as follows:

The BYSECOND rule part specifies a COMMA-separated list of seconds
within a minute. Valid values are 0 to 60. The BYMINUTE rule
part specifies a COMMA-separated list of minutes within an hour.
Valid values are 0 to 59. The BYHOUR rule part specifies a COMMA-
separated list of hours of the day. Valid values are 0 to 23.
The BYSECOND, BYMINUTE and BYHOUR rule parts MUST NOT be specified
when the associated "DTSTART" property has a DATE value type.
These rule parts MUST be ignored in RECUR value that violate the
above requirement (e.g., generated by applications that pre-date
this revision of iCalendar).

So specifying BYMINUTE would be forbidden but rules exist for how to deal with the case. One could argue that FREQ=MINUTELY equals FREQ=DAILY;BYMINUTE=0,1,2,..59. According to the above spec this would be resolved to FREQ=DAILY. But then there would still be the question of how to deal with the INTERVAL and COUNT parts. I don't see any reasonably way of resolving this. Its certainly safe to assume that its forbidden. I assume it's undefined by the RFC and I don't think we need to continue investing much time into this.

We should either

  • Rewrite the test to pin the current behavior (don't think this is advisable)
  • Rewrite the test to operate on a legal (or at least defined) rule.
  • Remove the test.

I suggest to rewrite with a defined (maybe still forbidden) rule. I'd suggest something like FREQ=DAILY;BYMINUTE=10;COUNT=5, which would (should) resolve to (based on DTSTRART October 16, 2024)

  • October 16, 2024
  • October 17, 2024
  • October 18, 2024
  • October 19, 2024
  • October 20, 2024

@axunonb
Copy link
Collaborator

axunonb commented Oct 16, 2024

Its certainly safe to assume that its forbidden.

Reading the rule, and seeing what apps are doing, this sounds reasonable.

What you're saying is: There is date, but no time, so it resolves to daily.
This makes more sense than to interpret "there is a date and a minutely interval, but minutely needs a time, so we set the start time to be midnight".

Agree with your proposal, and write some comments regarding the dilemma and our decision.
Well done!

@minichma
Copy link
Collaborator Author

minichma commented Oct 17, 2024

Just to be clear, I'm suggesting to change the test from something that's presumably forbidden and undefined (FREQ=MINUTELY) to something that's forbidden and defined (FREQ=DAILY;BYMINUTE=1,2,3). I did not comment on how ical.net should deal with the undefined case. (In fact I think the whole rrule should be rejected (ignored?). But that's a different story.)

I also sent this as a question to the IETF Calsify mailing list.

I'm working on an issue on the Ical.Net project (#602). The question arose, how to deal with the following scenario according to RFC 5545:

DTSTART;VALUE=DATE:20241016
RRULE:FREQ=MINUTELY;INTERVAL=10;COUNT=5
I.e. the DTSTART is date-only but the FREQ is MINUTELY.

My questions are:

  • Is this defined in RFC 5545 (or any related RFC) at all?
  • If yes: Where/how?
  • If no: What would be the recommended way to deal with such undefined rules? I assume rejecting the rule altogether would be the best option, right?

A related case (FREQ=DAILY;BYMINUTE=1,2,3) is explicitly forbidden but the behavior is defined (ignore BYMINUTE). I can't find a specification for the case above though.

@axunonb
Copy link
Collaborator

axunonb commented Oct 17, 2024

Yes, understood.

@minichma
Copy link
Collaborator Author

Thats what I received from Neil Jenkins on the calsify mailing list:

I don't think this is defined in the spec. With JSCalendar we removed the ambiguity of separate DATE vs DATE-TIME types, so this would be the same as a minutely recurrence starting at midnight on the given date.

My updated suggestion would now be the following:

  • Leave the test case as it was (just add a check to verify we get the expected recurrences)
  • Improve the lib to deal with the case as if it was a DTSTART with time set to midnight.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants