From bd8b49d2c95a187b218bd7c4b8689a85faa3dda2 Mon Sep 17 00:00:00 2001 From: Hivert Quentin Date: Wed, 12 Jul 2023 15:00:24 +0200 Subject: [PATCH] fix(calendar): Repeated events didn't use the correct timezone in some cases --- SOPE/NGCards/iCalDateTime.m | 67 +++++----- SOPE/NGCards/iCalRepeatableEntityObject.m | 94 +++++++------- .../Appointments/SOGoAppointmentFolder.m | 118 ++++++++---------- UI/Scheduler/UIxCalListingActions.m | 3 +- 4 files changed, 140 insertions(+), 142 deletions(-) diff --git a/SOPE/NGCards/iCalDateTime.m b/SOPE/NGCards/iCalDateTime.m index 8525d033d..0826c9ffc 100644 --- a/SOPE/NGCards/iCalDateTime.m +++ b/SOPE/NGCards/iCalDateTime.m @@ -85,13 +85,12 @@ tzId = [self value: 0 ofAttribute: @"tzid"]; if ([tzId length]) - { - calendar - = (iCalCalendar *) [self searchParentOfClass: [iCalCalendar class]]; + { + calendar = (iCalCalendar *) [self searchParentOfClass: [iCalCalendar class]]; timeZone = [calendar timeZoneWithId: tzId]; //if (!timeZone) //[self logWithFormat: @"timezone '%@' not found in calendar", tzId]; - } + } return timeZone; } @@ -169,37 +168,37 @@ count = [subValues count]; dates = [NSMutableArray arrayWithCapacity: count]; for (i = 0; i < count; i++) - { - date = [subValues objectAtIndex: i]; - iTZ = [self timeZone]; + { + date = [subValues objectAtIndex: i]; + iTZ = [self timeZone]; - if (iTZ) - dateTime = [iTZ dateForDateTimeString: date]; - else - { - initialDate = [date asCalendarDate]; - if (initialDate) - dateTime = initialDate; - /* - { - if ([date hasSuffix: @"Z"] || [date hasSuffix: @"z"]) - dateTime = initialDate; - else - { - // same TODO as above - tz = [NSTimeZone defaultTimeZone]; - dateTime = [initialDate addYear: 0 month: 0 day: 0 - hour: 0 minute: 0 - second: -[tz secondsFromGMTForDate: initialDate]]; - } - } - */ - else - dateTime = nil; - } - if (dateTime) - [dates addObject: dateTime]; - } + if (iTZ) + dateTime = [iTZ dateForDateTimeString: date]; + else + { + initialDate = [date asCalendarDate]; + if (initialDate) + dateTime = initialDate; + /* + { + if ([date hasSuffix: @"Z"] || [date hasSuffix: @"z"]) + dateTime = initialDate; + else + { + // same TODO as above + tz = [NSTimeZone defaultTimeZone]; + dateTime = [initialDate addYear: 0 month: 0 day: 0 + hour: 0 minute: 0 + second: -[tz secondsFromGMTForDate: initialDate]]; + } + } + */ + else + dateTime = nil; + } + if (dateTime) + [dates addObject: dateTime]; + } return dates; } diff --git a/SOPE/NGCards/iCalRepeatableEntityObject.m b/SOPE/NGCards/iCalRepeatableEntityObject.m index acfa5c14a..febd5ad6f 100644 --- a/SOPE/NGCards/iCalRepeatableEntityObject.m +++ b/SOPE/NGCards/iCalRepeatableEntityObject.m @@ -122,37 +122,44 @@ NSEnumerator *dateList; NSCalendarDate *rDate; NSString *dateString; + NSTimeZone *rdateTimezone; int offset; unsigned i; if (theTimeZone) - { - dates = [NSMutableArray array]; - dateList = [[self childrenWithTag: @"rdate"] objectEnumerator]; + { + dates = [NSMutableArray array]; + dateList = [[self childrenWithTag: @"rdate"] objectEnumerator]; - while ((dateString = [dateList nextObject])) - { - rDates = [(iCalDateTime*) dateString dateTimes]; - for (i = 0; i < [rDates count]; i++) + while ((dateString = [dateList nextObject])) + { + + rDates = [(iCalDateTime*) dateString dateTimes]; + for (i = 0; i < [rDates count]; i++) { rDate = [rDates objectAtIndex: i]; - // Example: timezone is -0400, date is 2012-05-24 (00:00:00 +0000), - // and changes to 2012-05-24 04:00:00 +0000 - if ([theTimeZone isKindOfClass: [iCalTimeZone class]]) - { - rDate = [(iCalTimeZone *) theTimeZone computedDateForDate: rDate]; - } - else - { - offset = [(NSTimeZone *) theTimeZone secondsFromGMTForDate: rDate]; - rDate = (NSCalendarDate *) [rDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 - seconds:-offset]; - } + + if ((rdateTimezone =[rDate timeZone])) + { + //The property rdate can have the timezone, https://www.kanzaki.com/docs/ical/rdate.html + //In that case, dont force the + } + // Example: timezone is -0400, date is 2012-05-24 (00:00:00 +0000), + // and changes to 2012-05-24 04:00:00 +0000 + else if ([theTimeZone isKindOfClass: [iCalTimeZone class]]) + { + rDate = [(iCalTimeZone *) theTimeZone computedDateForDate: rDate]; + } + else + { + offset = [(NSTimeZone *) theTimeZone secondsFromGMTForDate: rDate]; + rDate = (NSCalendarDate *) [rDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-offset]; + } [(NSMutableArray *) dates addObject: rDate]; - } - } - } + } + } + } else dates = [self recurrenceDates]; @@ -351,33 +358,32 @@ unsigned i; if (theTimeZone) - { - dates = [NSMutableArray array]; - dateList = [[self childrenWithTag: @"exdate"] objectEnumerator]; + { + dates = [NSMutableArray array]; + dateList = [[self childrenWithTag: @"exdate"] objectEnumerator]; - while ((dateString = [dateList nextObject])) - { - exDates = [(iCalDateTime*) dateString dateTimes]; - for (i = 0; i < [exDates count]; i++) + while ((dateString = [dateList nextObject])) + { + exDates = [(iCalDateTime*) dateString dateTimes]; + for (i = 0; i < [exDates count]; i++) { exDate = [exDates objectAtIndex: i]; - // Example: timezone is -0400, date is 2012-05-24 (00:00:00 +0000), - // and changes to 2012-05-24 04:00:00 +0000 - if ([theTimeZone isKindOfClass: [iCalTimeZone class]]) - { - exDate = [(iCalTimeZone *) theTimeZone computedDateForDate: exDate]; - } - else - { - offset = [(NSTimeZone *) theTimeZone secondsFromGMTForDate: exDate]; - exDate = (NSCalendarDate *) [exDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 - seconds:-offset]; - } + // Example: timezone is -0400, date is 2012-05-24 (00:00:00 +0000), + // and changes to 2012-05-24 04:00:00 +0000 + if ([theTimeZone isKindOfClass: [iCalTimeZone class]]) + { + exDate = [(iCalTimeZone *) theTimeZone computedDateForDate: exDate]; + } + else + { + offset = [(NSTimeZone *) theTimeZone secondsFromGMTForDate: exDate]; + exDate = (NSCalendarDate *) [exDate dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-offset]; + } [(NSMutableArray *) dates addObject: exDate]; - } - } - } + } + } + } else dates = [self exceptionDates]; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 034ec5f22..0370d2c97 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -1282,11 +1282,10 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir cycleinfo = [content propertyList]; if (!cycleinfo) - { - [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", - theRecord]; - return; - } + { + [self errorWithFormat:@"cyclic record doesn't have cycleinfo -> %@", theRecord]; + return; + } rules = [cycleinfo objectForKey: @"rules"]; exRules = [cycleinfo objectForKey: @"exRules"]; rDates = [cycleinfo objectForKey: @"rDates"]; @@ -1322,54 +1321,48 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir eventTimeZone = [dtstart timeZone]; if (eventTimeZone) - { - // Adjust the range to check with respect to the event timezone (extracted from the start date) - checkStartDate = [eventTimeZone computedDateForDate: [theRange startDate]]; - checkEndDate = [eventTimeZone computedDateForDate: [theRange endDate]]; - recurrenceRange = [NGCalendarDateRange calendarDateRangeWithStartDate: checkStartDate - endDate: checkEndDate]; - - } + { + // Adjust the range to check with respect to the event timezone (extracted from the start date) + checkStartDate = [eventTimeZone computedDateForDate: [theRange startDate]]; + checkEndDate = [eventTimeZone computedDateForDate: [theRange endDate]]; + recurrenceRange = [NGCalendarDateRange calendarDateRangeWithStartDate: checkStartDate endDate: checkEndDate]; + } else + { + recurrenceRange = theRange; + if ([[theRecord objectForKey: @"c_isallday"] boolValue]) { - recurrenceRange = theRange; - if ([[theRecord objectForKey: @"c_isallday"] boolValue]) - { - // The event lasts all-day and has no timezone (floating); we convert the range of the first event - // to the user's timezone - allDayTimeZone = timeZone; - offset = [allDayTimeZone secondsFromGMTForDate: [firstRange startDate]]; - firstStartDate = [[firstRange startDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 - seconds:-offset]; - firstEndDate = [[firstRange endDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 - seconds:-offset]; - [firstStartDate setTimeZone: allDayTimeZone]; - [firstEndDate setTimeZone: allDayTimeZone]; - firstRange = [NGCalendarDateRange calendarDateRangeWithStartDate: firstStartDate - endDate: firstEndDate]; - } + // The event lasts all-day and has no timezone (floating); we convert the range of the first event + // to the user's timezone + allDayTimeZone = timeZone; + offset = [allDayTimeZone secondsFromGMTForDate: [firstRange startDate]]; + firstStartDate = [[firstRange startDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-offset]; + firstEndDate = [[firstRange endDate] dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds:-offset]; + [firstStartDate setTimeZone: allDayTimeZone]; + [firstEndDate setTimeZone: allDayTimeZone]; + firstRange = [NGCalendarDateRange calendarDateRangeWithStartDate: firstStartDate endDate: firstEndDate]; } + } #warning this code is ugly: we should not mix objects with different types as \ it reduces readability tz = eventTimeZone ? eventTimeZone : allDayTimeZone; if (tz) - { - // Adjust the recurrence and exception dates - exDates = [component exceptionDatesWithTimeZone: tz]; - rDates = [component recurrenceDatesWithTimeZone: tz]; - - // Adjust the recurrence rules "until" dates - rules = [component recurrenceRulesWithTimeZone: tz]; - exRules = [component exceptionRulesWithTimeZone: tz]; - } + { + // Adjust the recurrence and exception dates + exDates = [component exceptionDatesWithTimeZone: tz]; + rDates = [component recurrenceDatesWithTimeZone: tz]; + + // Adjust the recurrence rules "until" dates + rules = [component recurrenceRulesWithTimeZone: tz]; + exRules = [component exceptionRulesWithTimeZone: tz]; + } rules = [rules uniqueObjects]; // Calculate the occurrences for the given range records = [NSMutableArray array]; - ranges = - [NSMutableArray arrayWithArray: + ranges = [NSMutableArray arrayWithArray: [iCalRecurrenceCalculator recurrenceRangesWithinCalendarDateRange: recurrenceRange firstInstanceCalendarDateRange: firstRange recurrenceRules: rules @@ -1380,33 +1373,32 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir // Add the master occurrence when dealing with RDATES. // However, the master event must not be flagged with X-MOZ-FAKED-MASTER. if ([component hasRecurrenceDates] && - ![[[component uniqueChildWithTag: @"x-moz-faked-master"] - flattenedValuesForKey: @""] isEqualToString: @"1"] && - [recurrenceRange doesIntersectWithDateRange: firstRange]) - { - [ranges insertObject: firstRange atIndex: 0]; - } + ![[[component uniqueChildWithTag: @"x-moz-faked-master"]flattenedValuesForKey: @""] isEqualToString: @"1"] && + [recurrenceRange doesIntersectWithDateRange: firstRange]) + { + [ranges insertObject: firstRange atIndex: 0]; + } max = [ranges count]; for (count = 0; count < max; count++) + { + oneRange = [ranges objectAtIndex: count]; + fixedRow = [self fixupCycleRecord: row + cycleRange: oneRange + firstInstanceCalendarDateRange: firstRange + withEventTimeZone: eventTimeZone]; + + // We now adjust the c_nextalarm based on each occurences. For each of them, we use the master event + // alarm information since exceptions to recurrence rules might have their own, while that is not the + // case for standard occurences. + if ([component hasAlarms]) { - oneRange = [ranges objectAtIndex: count]; - fixedRow = [self fixupCycleRecord: row - cycleRange: oneRange - firstInstanceCalendarDateRange: firstRange - withEventTimeZone: eventTimeZone]; - - // We now adjust the c_nextalarm based on each occurences. For each of them, we use the master event - // alarm information since exceptions to recurrence rules might have their own, while that is not the - // case for standard occurences. - if ([component hasAlarms]) - { - [self _computeAlarmForRow: fixedRow - master: component]; - } - - [records addObject: fixedRow]; + [self _computeAlarmForRow: fixedRow + master: component]; } + + [records addObject: fixedRow]; + } [self _appendCycleExceptionsFromRow: row firstInstanceCalendarDateRange: firstRange @@ -1414,7 +1406,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir withTimeZone: allDayTimeZone withCalendar: calendar toArray: records]; - + [theRecords addObjectsFromArray: records]; } // if ([components count]) ... } diff --git a/UI/Scheduler/UIxCalListingActions.m b/UI/Scheduler/UIxCalListingActions.m index 2aac0c1b8..ff6240a82 100644 --- a/UI/Scheduler/UIxCalListingActions.m +++ b/UI/Scheduler/UIxCalListingActions.m @@ -892,7 +892,8 @@ static inline NSString* _userStateInEvent (NSArray *event) NSMutableArray *fields, *dayEvents, *newEvent, *allDayEvents; NSMutableDictionary *allEvents, *monthData, *monthEvents, *dayData; NSString *sort, *ascending, *day, *weekDay, *month, *userState; - unsigned int interval, count, max; + unsigned int count, max; + long long interval; SEL sortSelector; [self _setupContext];