From d2ea6fef2eaca8b82b3389b5d993171b094ac0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Vall=C3=A9s?= Date: Fri, 11 Dec 2015 10:36:59 +0100 Subject: [PATCH 1/4] oc-calendar: Initialise NSCalendarDate with a SYSTEMTIME struct The method computes the date of a SYSTEMTIME structure, in which the day within the month is given by the Nth occurrence of a weekday (see [MS-OXOCAL] 2.2.1.39). --- OpenChange/NSDate+MAPIStore.h | 2 ++ OpenChange/NSDate+MAPIStore.m | 61 ++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/OpenChange/NSDate+MAPIStore.h b/OpenChange/NSDate+MAPIStore.h index 0e417d21f..d61df7b11 100644 --- a/OpenChange/NSDate+MAPIStore.h +++ b/OpenChange/NSDate+MAPIStore.h @@ -37,6 +37,8 @@ - (BOOL) isNever; /* occurs on 4500-12-31 */ ++ (NSCalendarDate *) dateFromSystemTime: (struct SYSTEMTIME) date + andRuleYear: (uint16_t) rYear; @end NSComparisonResult NSDateCompare (id date1, id date2, void *); diff --git a/OpenChange/NSDate+MAPIStore.m b/OpenChange/NSDate+MAPIStore.m index b96812adf..b8f09dcc6 100644 --- a/OpenChange/NSDate+MAPIStore.m +++ b/OpenChange/NSDate+MAPIStore.m @@ -24,6 +24,7 @@ #import #import +#import "MAPIStoreTypes.h" #import "NSDate+MAPIStore.h" #undef DEBUG @@ -48,7 +49,7 @@ _setupRefDate () refDate = [[NSCalendarDate alloc] initWithYear: 1601 month: 1 day: 1 hour: 0 minute: 0 second: 0 - timeZone: [NSTimeZone timeZoneWithName: @"UTC"]]; + timeZone: utcTZ]; } + (NSCalendarDate *) dateFromMinutesSince1601: (uint32_t) minutes @@ -128,6 +129,64 @@ _setupRefDate () return [calDate yearOfCommonEra] == 4500; } ++ (NSCalendarDate *) dateFromSystemTime: (struct SYSTEMTIME) date + andRuleYear: (uint16_t) rYear +{ + NSCalendarDate *result; + NSInteger daysToDate; + NSUInteger firstDayOfWeek, year; + + /* ([MS-OXOCAL] 2.2.1.41.1) When we're provided an absolute date (i.e., it + happens once), the SYSTEMTIME structure is enough to fill the date. + When we're parsing a SYSTEMTIME field from a time zone rule, however, a + relative date can be provided for the peroidicity of its periods. In this + scenario, the wYear field is empty and we have to use the wYear field in + the parent rule */ + if (date.wYear != 0) + year = date.wYear; + else + year = rYear; + + /* The wDay field indicates the occurrence of the wDayOfWeek within the month. + The 5th occurrence means the last one, even if it is the 4th. */ + if (date.wDay < 5) + { + result = [[NSCalendarDate alloc] initWithYear: year month: date.wMonth day: 1 + hour: date.wHour minute: date.wMinute second: date.wSecond + timeZone: utcTZ]; + [result autorelease]; + + firstDayOfWeek = [result dayOfWeek]; + + daysToDate = 7 * (date.wDay - 1) + date.wDayOfWeek - firstDayOfWeek; + if (date.wDayOfWeek < firstDayOfWeek) + daysToDate += 7; + + result = [result dateByAddingYears: 0 months: 0 days: daysToDate + hours: 0 minutes: 0 + seconds: 0]; + } + else + { + result = [[NSCalendarDate alloc] initWithYear: year month: date.wMonth + 1 day: 1 + hour: date.wHour minute: date.wMinute second: date.wSecond + timeZone: utcTZ]; + [result autorelease]; + + firstDayOfWeek = [result dayOfWeek]; + + daysToDate = date.wDayOfWeek - firstDayOfWeek; + if (date.wDayOfWeek >= firstDayOfWeek) + daysToDate -= 7; + + result = [result dateByAddingYears: 0 months: 0 days: daysToDate + hours: 0 minutes: 0 + seconds: 0]; + } + + return result; +} + @end NSComparisonResult From 332508e2dbb1e238d1b9d0a11ea9188dabb4f865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Vall=C3=A9s?= Date: Tue, 15 Dec 2015 10:40:19 +0100 Subject: [PATCH 2/4] oc-calendar: Use signed integer for time zone biases This change adapts the bias fields in the TimeZoneStruct and TZRule structures to the changes in openchange that allow this offsets to be negative (zentyal/openchange bba372faea29d942b9471e6bed90bf425dc4b231) --- OpenChange/iCalTimeZone+MAPIStore.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenChange/iCalTimeZone+MAPIStore.m b/OpenChange/iCalTimeZone+MAPIStore.m index f692272db..9a9e8fbb2 100644 --- a/OpenChange/iCalTimeZone+MAPIStore.m +++ b/OpenChange/iCalTimeZone+MAPIStore.m @@ -103,18 +103,18 @@ { iCalTimeZonePeriod *period; struct TimeZoneStruct tz; - int lBias, dlBias; + int32_t lBias, dlBias; memset (&tz, 0, sizeof (struct TimeZoneStruct)); period = [self _mostRecentPeriodWithName: @"STANDARD"]; lBias = -[period secondsOffsetFromGMT] / 60; - tz.lBias = (uint32_t) lBias; + tz.lBias = lBias; [period _fillTZDate: &tz.stStandardDate]; period = [self _mostRecentPeriodWithName: @"DAYLIGHT"]; if (!period) tz.stStandardDate.wMonth = 0; dlBias = -([period secondsOffsetFromGMT] / 60) - lBias; - tz.lDaylightBias = (uint32_t) (dlBias); + tz.lDaylightBias = dlBias; [period _fillTZDate: &tz.stDaylightDate]; tz.wStandardYear = tz.stStandardDate.wYear; tz.wDaylightYear = tz.stDaylightDate.wYear; @@ -153,13 +153,13 @@ period = [self _mostRecentPeriodWithName: @"STANDARD"]; rule.wYear = [[period startDate] yearOfCommonEra]; lBias = -[period secondsOffsetFromGMT] / 60; - rule.lBias = (uint32_t) lBias; + rule.lBias = lBias; [period _fillTZDate: &rule.stStandardDate]; period = [self _mostRecentPeriodWithName: @"DAYLIGHT"]; if (!period) rule.stStandardDate.wMonth = 0; dlBias = -([period secondsOffsetFromGMT] / 60) - lBias; - rule.lDaylightBias = (uint32_t) (dlBias); + rule.lDaylightBias = dlBias; [period _fillTZDate: &rule.stDaylightDate]; From 4ae5feb1318212a0d9a65e01f591f97890aac6a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Vall=C3=A9s?= Date: Fri, 11 Dec 2015 12:15:11 +0100 Subject: [PATCH 3/4] oc-calendar: Extract time zone from TimeZoneDefinition All-day and recurrent events have a binary property that describes the time zone they take place in. We were using the user's time zone in the webmail, but it may not be equal to the one in the client. This difference eventually leads to time shifts in events. --- OpenChange/MAPIStoreRecurrenceUtils.h | 3 +- OpenChange/MAPIStoreRecurrenceUtils.m | 8 +- OpenChange/iCalEvent+MAPIStore.m | 65 ++++----- OpenChange/iCalTimeZone+MAPIStore.h | 3 + OpenChange/iCalTimeZone+MAPIStore.m | 185 ++++++++++++++++++++++++++ 5 files changed, 228 insertions(+), 36 deletions(-) diff --git a/OpenChange/MAPIStoreRecurrenceUtils.h b/OpenChange/MAPIStoreRecurrenceUtils.h index df3700575..6912cb294 100644 --- a/OpenChange/MAPIStoreRecurrenceUtils.h +++ b/OpenChange/MAPIStoreRecurrenceUtils.h @@ -29,6 +29,7 @@ #import #import +#import @class NSTimeZone; @@ -46,7 +47,7 @@ fromRecurrencePattern: (struct RecurrencePattern *) rp withExceptions: (struct ExceptionInfo *) exInfos andExceptionCount: (uint16_t) exInfoCount - inTimeZone: (NSTimeZone *) tz; + inTimeZone: (iCalTimeZone *) tz; @end diff --git a/OpenChange/MAPIStoreRecurrenceUtils.m b/OpenChange/MAPIStoreRecurrenceUtils.m index 9e0bf29c3..e88fb5e4a 100644 --- a/OpenChange/MAPIStoreRecurrenceUtils.m +++ b/OpenChange/MAPIStoreRecurrenceUtils.m @@ -35,6 +35,7 @@ #import #import #import +#import #import "NSDate+MAPIStore.h" #import "MAPIStoreRecurrenceUtils.h" @@ -51,7 +52,7 @@ fromRecurrencePattern: (struct RecurrencePattern *) rp withExceptions: (struct ExceptionInfo *) exInfos andExceptionCount: (uint16_t) exInfoCount - inTimeZone: (NSTimeZone *) tz + inTimeZone: (iCalTimeZone *) tz { NSCalendarDate *startDate, *olEndDate, *untilDate, *exDate; @@ -63,7 +64,7 @@ iCalWeekOccurrence weekOccurrence; iCalWeekOccurrences dayMaskDays; NSUInteger count, max; - NSInteger bySetPos; + NSInteger bySetPos, tzOffset; unsigned char maskValue; [entity removeAllRecurrenceRules]; @@ -242,9 +243,10 @@ { /* The OriginalStartDate is in local time */ exDate = [NSDate dateFromMinutesSince1601: exInfos[count].OriginalStartDate]; + tzOffset = -[[tz periodForDate: exDate] secondsOffsetFromGMT]; exDate = [exDate dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 - seconds: - [tz secondsFromGMT]]; + seconds: tzOffset]; [exceptionDates removeObject: exDate]; } } diff --git a/OpenChange/iCalEvent+MAPIStore.m b/OpenChange/iCalEvent+MAPIStore.m index 90bf4e8bd..25877029b 100644 --- a/OpenChange/iCalEvent+MAPIStore.m +++ b/OpenChange/iCalEvent+MAPIStore.m @@ -36,6 +36,7 @@ #import #import #import +#import #import #import #import @@ -70,11 +71,12 @@ #include #import "iCalEvent+MAPIStore.h" +#import "iCalTimeZone+MAPIStore.h" @implementation iCalEvent (MAPIStoreProperties) - (void) _setupEventRecurrence: (NSData *) mapiRecurrenceData - inTimeZone: (NSTimeZone *) tz + inTimeZone: (iCalTimeZone *) tz inMemCtx: (TALLOC_CTX *) memCtx { struct Binary_r *blob; @@ -250,10 +252,8 @@ BOOL isAllDay; iCalDateTime *start, *end; iCalTimeZone *tz; - NSTimeZone *userTimeZone; - NSString *priority, *class = nil; + NSString *priority, *class = nil, *tzDescription = nil; NSUInteger responseStatus = 0; - NSInteger tzOffset; SOGoUser *ownerUser; id value; @@ -274,7 +274,31 @@ [self setAccessClass: @"PUBLIC"]; } - userTimeZone = [userContext timeZone]; + /* Time zone = PidLidAppointmentTimeZoneDefinitionRecur + or PidLidAppointmentTimeZoneDefinition[Start|End]Display */ + value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionStartDisplay)]; + if (!value) + { + value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionEndDisplay)]; + if (!value) + { + /* If PidLidtimeZoneStruct, TZID SHOULD come from PidLidTimeZoneDescription, + if PidLidAppointmentTimeZoneDefinition[Start|End]Display it MUST be derived from KeyName + (MS-OXCICAL] 2.1.3.1.1.19.1) */ + value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentTimeZoneDefinitionRecur)]; + tzDescription = [properties objectForKey: MAPIPropertyKey (PidLidTimeZoneDescription)]; + } + } + if (value) + { + tz = [[iCalTimeZone alloc] iCalTimeZoneFromDefinition: value + withDescription: tzDescription + inMemCtx: memCtx]; + } + else + /* The client is more likely to have the webmail's time zone than any other */ + tz = [iCalTimeZone timeZoneForName: [[userContext timeZone] name]]; + [(iCalCalendar *) parent addTimeZone: tz]; /* CREATED */ value = [properties objectForKey: MAPIPropertyKey (PidTagCreationTime)]; @@ -306,20 +330,13 @@ objectForKey: MAPIPropertyKey (PidLidAppointmentSubType)]; if (value) isAllDay = [value boolValue]; - if (!isAllDay) - { - tz = [iCalTimeZone timeZoneForName: [userTimeZone name]]; - [(iCalCalendar *) parent addTimeZone: tz]; - } - else - tz = nil; // recurrence-id value = [properties objectForKey: MAPIPropertyKey (PidLidExceptionReplaceTime)]; if (value) [self setRecurrenceId: value]; - + // start value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentStartWhole)]; if (!value) @@ -330,15 +347,7 @@ [start setTimeZone: tz]; if (isAllDay) { - /* when user TZ is positive (East) all-day events were not - shown properly in SOGo UI. This day delay fixes it */ - tzOffset = [userTimeZone secondsFromGMTForDate: value]; - if (tzOffset > 0) - { - value = [value dateByAddingYears: 0 months: 0 days: 1 - hours: 0 minutes: 0 - seconds: 0]; - } + /* All-day events are set in floating time ([MS-OXCICAL] 2.1.3.1.1.20.8) */ [start setDate: value]; [start setTimeZone: nil]; } @@ -356,15 +365,7 @@ [end setTimeZone: tz]; if (isAllDay) { - /* when user TZ is positive (East) all-day events were not - shown properly in SOGo UI. This day delay fixes it */ - tzOffset = [userTimeZone secondsFromGMTForDate: value]; - if (tzOffset > 0) - { - value = [value dateByAddingYears: 0 months: 0 days: 1 - hours: 0 minutes: 0 - seconds: 0]; - } + /* All-day events are set in floating time ([MS-OXCICAL] 2.1.3.1.1.20.8) */ [end setDate: value]; [end setTimeZone: nil]; } @@ -467,7 +468,7 @@ value = [properties objectForKey: MAPIPropertyKey (PidLidAppointmentRecur)]; if (value) - [self _setupEventRecurrence: value inTimeZone: userTimeZone inMemCtx: memCtx]; + [self _setupEventRecurrence: value inTimeZone: tz inMemCtx: memCtx]; /* alarm */ [self _setupEventAlarmFromProperties: properties]; diff --git a/OpenChange/iCalTimeZone+MAPIStore.h b/OpenChange/iCalTimeZone+MAPIStore.h index 76b0a00a2..01bc51b82 100644 --- a/OpenChange/iCalTimeZone+MAPIStore.h +++ b/OpenChange/iCalTimeZone+MAPIStore.h @@ -30,6 +30,9 @@ - (struct Binary_r *) asTimeZoneStructInMemCtx: (TALLOC_CTX *) memCtx; - (struct Binary_r *) asZoneTimeDefinitionWithFlags: (enum TZRuleFlag) flags inMemCtx: (TALLOC_CTX *) memCtx; +- (iCalTimeZone *) iCalTimeZoneFromDefinition: (NSData *) value + withDescription: (NSString *) description + inMemCtx: (TALLOC_CTX *) memCtx; @end diff --git a/OpenChange/iCalTimeZone+MAPIStore.m b/OpenChange/iCalTimeZone+MAPIStore.m index 9a9e8fbb2..bfdf64ed4 100644 --- a/OpenChange/iCalTimeZone+MAPIStore.m +++ b/OpenChange/iCalTimeZone+MAPIStore.m @@ -23,11 +23,15 @@ #import #import #import +#import #import +#import #import #import #import "NSString+MAPIStore.h" +#import "NSData+MAPIStore.h" +#import "NSDate+MAPIStore.h" #include #include @@ -166,5 +170,186 @@ return set_TimeZoneDefinition (memCtx, &definition); } +- (NSString *) _offsetStringFromOffset: (NSInteger) offset +{ + NSInteger offsetHours, offsetMins; + NSString *offsetSign; + + /* The offset format is, eg, "+0200" for 2 hours 0 minutes ahead */ + if (offset < 0) + offsetSign = @"-"; + else + offsetSign = @"+"; + offsetHours = abs (offset) / 60; + offsetMins = abs (offset) % 60; + + return [NSString stringWithFormat: @"%@%d%d%d%d", + offsetSign, offsetHours / 10, offsetHours % 10, + offsetMins / 10, offsetMins % 10]; + +} + +- (NSString *) _rRuleStringFromSystemTime: (struct SYSTEMTIME) date +{ + NSString *result, *byDay; + + /* The conversion tables between the SYSTEMTIME fields and the RRULE ones + can be found at [MS-OXCICAL] 2.1.3.2.1 */ + if (date.wDay == 5) + byDay = @"-1"; + else + byDay = [NSString stringWithFormat: @"%d", date.wDay]; + + switch (date.wDayOfWeek) + { + case iCalWeekDaySunday: + byDay = [byDay stringByAppendingString: @"SU"]; + break; + case iCalWeekDayMonday: + byDay = [byDay stringByAppendingString: @"MO"]; + break; + case iCalWeekDayTuesday: + byDay = [byDay stringByAppendingString: @"TU"]; + break; + case iCalWeekDayWednesday: + byDay = [byDay stringByAppendingString: @"WE"]; + break; + case iCalWeekDayThursday: + byDay = [byDay stringByAppendingString: @"TH"]; + break; + case iCalWeekDayFriday: + byDay = [byDay stringByAppendingString: @"FR"]; + break; + case iCalWeekDaySaturday: + byDay = [byDay stringByAppendingString: @"SA"]; + break; + } + + result = [NSString stringWithFormat: @"FREQ=YEARLY;BYDAY=%@;BYMONTH=%d", byDay, date.wMonth]; + + return result; +} + +- (iCalTimeZone *) iCalTimeZoneFromDefinition: (NSData *) value + withDescription: (NSString *) description + inMemCtx: (TALLOC_CTX *) memCtx +{ + BOOL daylightDefined = NO, ruleFound = NO; + iCalDateTime *daylightStart, *standardStart; + iCalRecurrenceRule *daylightRRule, *standardRRule; + iCalTimeZone *tz = nil; + iCalTimeZonePeriod *daylight, *standard; + NSCalendarDate *dlStartValue, *stStartValue; + NSString *strOffsetFrom, *strOffsetTo, *tzID; + char *keyName; + struct Binary_r *binValue; + struct SYSTEMTIME initDate; + struct TimeZoneDefinition *definition; + struct TZRule rule; + uint16_t count; + + binValue = [value asBinaryInMemCtx: memCtx]; + definition = get_TimeZoneDefinition (memCtx, binValue); + + if (!definition) + return nil; + + if (!definition->cRules) + goto end; + + for (count = 0; count < definition->cRules; count++) + { + /* ([MS-OXCICAL] 2.1.3.1.1.19) The TZRule with the + TZRULE_FLAG_EFFECTIVE_TZREG bit set in the TZRule flags field + is the one that MUST be exported */ + if (definition->TZRules[count].flags & TZRULE_FLAG_EFFECTIVE_TZREG) + { + rule = definition->TZRules[count]; + ruleFound = YES; + break; + } + } + + if (!ruleFound) + goto end; + + if (!description) + { + /* The cbHeader field contains the size, in bytes of the Reserved (2b), + cchKeyName (2b) keyName (variable Unicode string) and cRules (2b) + ([MS-OXOCAL] 2.2.1.41). The keyName field is a non-NULL-terminated + char array. */ + keyName = talloc_strndup (memCtx, definition->keyName, (definition->cbHeader - 6) / 2); + tzID = [NSString stringWithCString: keyName + encoding: [NSString defaultCStringEncoding]]; + talloc_free (keyName); + } + else + tzID = [NSString stringWithString: description]; + + tz = [iCalTimeZone groupWithTag: @"vtimezone"]; + [tz addChild: [CardElement simpleElementWithTag: @"tzid" + value: tzID]]; + + if (rule.stStandardDate.wMonth != 0) + daylightDefined = YES; + + /* STANDARD TIME ([MS-OXCICAL] 2.1.3.1.1.19.2) */ + standard = [iCalTimeZonePeriod groupWithTag: @"standard"]; + + /* TZOFFSETFROM = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lDaylightBias) */ + strOffsetFrom = [self _offsetStringFromOffset: -1 * (rule.lBias + rule.lDaylightBias)]; + [standard addChild: [CardElement simpleElementWithTag: @"tzoffsetfrom" + value: strOffsetFrom]]; + + /* TZOFFSETTO = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lStandardBias) */ + strOffsetTo = [self _offsetStringFromOffset: -1 * (rule.lBias + rule.lStandardBias)]; + [standard addChild: [CardElement simpleElementWithTag: @"tzoffsetto" + value: strOffsetTo]]; + + /* DTSTART & RRULE are derived from the stStandardDate and wYear properties */ + standardStart = [iCalDateTime elementWithTag: @"dtstart"]; + + initDate = rule.stStandardDate; + stStartValue = [NSCalendarDate dateFromSystemTime: initDate + andRuleYear: rule.wYear]; + + [standardStart setDateTime: stStartValue]; + [standard addChild: standardStart]; + + if (daylightDefined) + { + standardRRule = [[iCalRecurrenceRule alloc] initWithString: [self _rRuleStringFromSystemTime: initDate]]; + [standard addChild: standardRRule]; + + /* DAYLIGHT SAVING TIME ([MS-OXCICAL] 2.1.3.1.1.19.3) */ + daylight = [iCalTimeZonePeriod groupWithTag: @"daylight"]; + /* TZOFFSETFROM = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lStandardBias) */ + [daylight addChild: [CardElement simpleElementWithTag: @"tzoffsetfrom" + value: strOffsetTo]]; + /* TZOFFSETTO = -1 * (PidLidTimeZoneStruct.lBias + PidLidTimeZoneStruct.lDaylightBias) */ + [daylight addChild: [CardElement simpleElementWithTag: @"tzoffsetto" + value: strOffsetFrom]]; + + /* DTSTART & RRULE are derived from the stDaylightDate and wYear properties */ + daylightStart = [iCalDateTime elementWithTag: @"dtstart"]; + initDate = rule.stDaylightDate; + dlStartValue = [NSCalendarDate dateFromSystemTime: initDate + andRuleYear: rule.wYear]; + + [daylightStart setDateTime: dlStartValue]; + [daylight addChild: daylightStart]; + + daylightRRule = [[iCalRecurrenceRule alloc] initWithString: [self _rRuleStringFromSystemTime: initDate]]; + [daylight addChild: daylightRRule]; + [tz addChild: daylight]; + } + [tz addChild: standard]; + +end: + + talloc_free (definition); + return tz; +} @end From 376e717f459d1a74f90ba8da6471b9d80e30722d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Vall=C3=A9s?= Date: Wed, 16 Dec 2015 18:49:09 +0100 Subject: [PATCH 4/4] oc-calendar: Use the calendar's time zone if it is present If the event was created by the MAPI client, the client's time zone (if present) is assigned to the event's calendar in iCalEvent+MAPIStore. This way, we can use it to deliver the event's properties correctly. --- OpenChange/MAPIStoreAppointmentWrapper.h | 5 +- OpenChange/MAPIStoreAppointmentWrapper.m | 149 ++++++------------ OpenChange/MAPIStoreCalendarEmbeddedMessage.m | 1 - OpenChange/MAPIStoreCalendarMessage.m | 1 - OpenChange/MAPIStoreMailMessage.m | 1 - OpenChange/MAPIStoreRecurrenceUtils.h | 5 - OpenChange/MAPIStoreRecurrenceUtils.m | 6 +- OpenChange/iCalTimeZone+MAPIStore.h | 2 + OpenChange/iCalTimeZone+MAPIStore.m | 22 +++ 9 files changed, 73 insertions(+), 119 deletions(-) diff --git a/OpenChange/MAPIStoreAppointmentWrapper.h b/OpenChange/MAPIStoreAppointmentWrapper.h index aecf2200f..d114076fe 100644 --- a/OpenChange/MAPIStoreAppointmentWrapper.h +++ b/OpenChange/MAPIStoreAppointmentWrapper.h @@ -24,6 +24,7 @@ #define MAPISTORECALENDARWRAPPER_H #import +#import #import #import "MAPIStoreObjectProxy.h" @@ -42,7 +43,7 @@ iCalCalendar *calendar; iCalEvent *firstEvent; iCalEvent *event; - NSTimeZone *timeZone; + iCalTimeZone *timeZone; SOGoUser *user; NSString *senderEmail; NSData *globalObjectId; @@ -57,12 +58,10 @@ + (id) wrapperWithICalEvent: (iCalEvent *) newEvent andUser: (SOGoUser *) newUser andSenderEmail: (NSString *) newSenderEmail - inTimeZone: (NSTimeZone *) newTimeZone withConnectionInfo: (struct mapistore_connection_info *) newConnInfo; - (id) initWithICalEvent: (iCalEvent *) newEvent andUser: (SOGoUser *) newUser andSenderEmail: (NSString *) newSenderEmail - inTimeZone: (NSTimeZone *) newTimeZone withConnectionInfo: (struct mapistore_connection_info *) newConnInfo; /* getters */ diff --git a/OpenChange/MAPIStoreAppointmentWrapper.m b/OpenChange/MAPIStoreAppointmentWrapper.m index c8c6560fb..11a189500 100644 --- a/OpenChange/MAPIStoreAppointmentWrapper.m +++ b/OpenChange/MAPIStoreAppointmentWrapper.m @@ -37,7 +37,9 @@ #import #import #import +#import #import +#import #import #import @@ -80,7 +82,6 @@ static NSCharacterSet *hexCharacterSet = nil; + (id) wrapperWithICalEvent: (iCalEvent *) newEvent andUser: (SOGoUser *) newUser andSenderEmail: (NSString *) newSenderEmail - inTimeZone: (NSTimeZone *) newTimeZone withConnectionInfo: (struct mapistore_connection_info *) newConnInfo { MAPIStoreAppointmentWrapper *wrapper; @@ -88,7 +89,6 @@ static NSCharacterSet *hexCharacterSet = nil; wrapper = [[self alloc] initWithICalEvent: newEvent andUser: newUser andSenderEmail: newSenderEmail - inTimeZone: newTimeZone withConnectionInfo: newConnInfo]; [wrapper autorelease]; @@ -182,10 +182,10 @@ static NSCharacterSet *hexCharacterSet = nil; - (id) initWithICalEvent: (iCalEvent *) newEvent andUser: (SOGoUser *) newUser andSenderEmail: (NSString *) newSenderEmail - inTimeZone: (NSTimeZone *) newTimeZone withConnectionInfo: (struct mapistore_connection_info *) newConnInfo { NSArray *events; + iCalTimeZone *tz; if ((self = [self init])) { @@ -194,9 +194,20 @@ static NSCharacterSet *hexCharacterSet = nil; event = newEvent; events = [calendar events]; firstEvent = [events objectAtIndex: 0]; - ASSIGN (timeZone, newTimeZone); ASSIGN (user, newUser); ASSIGN (senderEmail, newSenderEmail); + /* If newEvent comes from the client, we set its time zone in + updateFromMAPIProperties. If it is not present, we use the + time zone of the user */ + tz = (iCalTimeZone *) [calendar firstChildWithTag: @"vtimezone"]; + if (!tz) + { + tz = [iCalTimeZone timeZoneForName: [[[user userDefaults] timeZone] name]]; + if (!tz) + [self logWithFormat: @"no time zone could be set"]; + } + ASSIGN (timeZone, tz); + [self _setupITIPContext]; } @@ -721,22 +732,15 @@ static NSCharacterSet *hexCharacterSet = nil; inMemCtx: (TALLOC_CTX *) memCtx { NSCalendarDate *dateValue; - NSInteger offset; // if ([event isRecurrent]) // dateValue = [event firstRecurrenceStartDate]; // else dateValue = [event startDate]; if ([event isAllDay]) - { - offset = -[timeZone secondsFromGMTForDate: dateValue]; - dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: offset]; - } - [dateValue setTimeZone: utcTZ]; + dateValue = [timeZone shiftedCalendarDateForDate: dateValue]; *data = [dateValue asFileTimeInMemCtx: memCtx]; - + return MAPISTORE_SUCCESS; } @@ -749,22 +753,14 @@ static NSCharacterSet *hexCharacterSet = nil; exceptions, where it is the normal start date for the day of the exception. */ NSCalendarDate *dateValue; - NSInteger offset; dateValue = [event recurrenceId]; if (!dateValue) dateValue = [event startDate]; - [dateValue setTimeZone: timeZone]; if ([event isAllDay]) - { - offset = -[timeZone secondsFromGMTForDate: dateValue]; - dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: offset]; - } - [dateValue setTimeZone: utcTZ]; + dateValue = [timeZone shiftedCalendarDateForDate: dateValue]; *data = [dateValue asFileTimeInMemCtx: memCtx]; - + return MAPISTORE_SUCCESS; } @@ -772,19 +768,12 @@ static NSCharacterSet *hexCharacterSet = nil; inMemCtx: (TALLOC_CTX *) memCtx { NSCalendarDate *dateValue; - NSInteger offset; dateValue = [firstEvent startDate]; if ([firstEvent isAllDay]) - { - offset = -[timeZone secondsFromGMTForDate: dateValue]; - dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: offset]; - } - [dateValue setTimeZone: utcTZ]; + dateValue = [timeZone shiftedCalendarDateForDate: dateValue]; *data = [dateValue asFileTimeInMemCtx: memCtx]; - + return MAPISTORE_SUCCESS; } @@ -804,8 +793,8 @@ static NSCharacterSet *hexCharacterSet = nil; month: [start monthOfYear] day: [start dayOfMonth] hour: 0 minute: 0 second: 0 - timeZone: timeZone]; - [dateValue setTimeZone: utcTZ]; + timeZone: utcTZ]; + dateValue = [timeZone shiftedCalendarDateForDate: dateValue]; *data = [dateValue asFileTimeInMemCtx: memCtx]; rc = MAPISTORE_SUCCESS; } @@ -829,7 +818,7 @@ static NSCharacterSet *hexCharacterSet = nil; dateValue = [event startDate]; offset = [event durationAsTimeInterval]; if ([event isAllDay]) - offset -= [timeZone secondsFromGMTForDate: dateValue]; + offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT]; dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: offset]; @@ -847,15 +836,14 @@ static NSCharacterSet *hexCharacterSet = nil; dateValue = [event recurrenceId]; if (!dateValue) dateValue = [event startDate]; - [dateValue setTimeZone: timeZone]; offset = [firstEvent durationAsTimeInterval]; if ([firstEvent isAllDay]) - offset -= [timeZone secondsFromGMTForDate: dateValue]; + offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT]; dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: offset]; *data = [dateValue asFileTimeInMemCtx: memCtx]; - + return MAPISTORE_SUCCESS; } @@ -871,7 +859,7 @@ static NSCharacterSet *hexCharacterSet = nil; dateValue = [firstEvent startDate]; offset = [firstEvent durationAsTimeInterval]; if ([event isAllDay]) - offset -= [timeZone secondsFromGMTForDate: dateValue]; + offset -= [[timeZone periodForDate: dateValue] secondsOffsetFromGMT]; dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: offset]; @@ -885,23 +873,14 @@ static NSCharacterSet *hexCharacterSet = nil; { enum mapistore_error rc; NSCalendarDate *dateValue; - NSInteger offset; iCalRecurrenceRule *rrule; if ([event isRecurrent]) { rrule = [[event recurrenceRules] objectAtIndex: 0]; dateValue = [rrule untilDate]; - if (dateValue) - { - if ([event isAllDay]) - offset = -[timeZone secondsFromGMTForDate: dateValue]; - else - offset = 0; - dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: offset]; - } + if (dateValue && [event isAllDay]) + dateValue = [timeZone shiftedCalendarDateForDate: dateValue]; else dateValue = [NSCalendarDate dateWithYear: 4500 month: 8 day: 31 hour: 23 minute: 59 second: 00 @@ -1338,21 +1317,14 @@ static NSCharacterSet *hexCharacterSet = nil; { enum mapistore_error rc; NSCalendarDate *dateValue; - NSInteger offset; dateValue = [event recurrenceId]; if (dateValue) { rc = MAPISTORE_SUCCESS; - + if ([event isAllDay]) - { - offset = -[timeZone secondsFromGMTForDate: dateValue]; - dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: offset]; - } - [dateValue setTimeZone: utcTZ]; + dateValue = [timeZone shiftedCalendarDateForDate: dateValue]; *data = [dateValue asFileTimeInMemCtx: memCtx]; } else @@ -1377,7 +1349,6 @@ static NSCharacterSet *hexCharacterSet = nil; iCalEventChanges *changes; NSArray *changedProperties; NSCalendarDate *dateValue; - NSInteger offset; changes = [iCalEventChanges changesFromEvent: event toEvent: exceptionEvent]; @@ -1385,28 +1356,17 @@ static NSCharacterSet *hexCharacterSet = nil; memset (extendedException, 0, sizeof (struct ExtendedException)); extendedException->ChangeHighlight.Size = sizeof (uint32_t); - dateValue = [exceptionEvent startDate]; - offset = [timeZone secondsFromGMTForDate: dateValue]; - dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: offset]; + dateValue = [timeZone computedDateForDate: [exceptionEvent startDate]]; exceptionInfo->StartDateTime = [dateValue asMinutesSince1601]; extendedException->ChangeHighlight.Value = BIT_CH_START; extendedException->StartDateTime = exceptionInfo->StartDateTime; - dateValue = [exceptionEvent endDate]; - offset = [timeZone secondsFromGMTForDate: dateValue]; - dateValue = [dateValue dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: offset]; + dateValue = [timeZone computedDateForDate: [exceptionEvent endDate]]; exceptionInfo->EndDateTime = [dateValue asMinutesSince1601]; extendedException->ChangeHighlight.Value |= BIT_CH_END; extendedException->EndDateTime = exceptionInfo->EndDateTime; - dateValue = [[exceptionEvent recurrenceId] - dateByAddingYears: 0 months: 0 days: 0 - hours: 0 minutes: 0 - seconds: offset]; + dateValue = [timeZone computedDateForDate: [exceptionEvent recurrenceId]]; exceptionInfo->OriginalStartDate = [dateValue asMinutesSince1601]; extendedException->OriginalStartDate = exceptionInfo->OriginalStartDate; @@ -1464,7 +1424,6 @@ static NSCharacterSet *hexCharacterSet = nil; arp = talloc_zero (NULL, struct AppointmentRecurrencePattern); [rule fillRecurrencePattern: &arp->RecurrencePattern withEvent: event - inTimeZone: timeZone inMemCtx: arp]; arp->ReaderVersion2 = 0x00003006; arp->WriterVersion2 = 0x00003008; /* 0x3008 for compatibility with @@ -1475,7 +1434,7 @@ static NSCharacterSet *hexCharacterSet = nil; fields are relative to midnight of those days ([MS-OXOCAL] 2.2.1.44.5), so no time zone adjustment is needed */ if (![event isAllDay]) - [firstStartDate setTimeZone: timeZone]; + firstStartDate = [timeZone computedDateForDate: firstStartDate]; startMinutes = ([firstStartDate hourOfDay] * 60 + [firstStartDate minuteOfHour]); arp->StartTimeOffset = startMinutes; @@ -1701,15 +1660,16 @@ ReservedBlockEE2Size: 00 00 00 00 fromDate: (NSCalendarDate *) instanceDate; { uint16_t year; + NSCalendarDate *dateValue; if (instanceDate) { - [instanceDate setTimeZone: timeZone]; - year = [instanceDate yearOfCommonEra]; + dateValue = [timeZone computedDateForDate: instanceDate]; + year = [dateValue yearOfCommonEra]; newGlobalId->YH = year >> 8; newGlobalId->YL = year & 0xff; - newGlobalId->Month = [instanceDate monthOfYear]; - newGlobalId->D = [instanceDate dayOfMonth]; + newGlobalId->Month = [dateValue monthOfYear]; + newGlobalId->D = [dateValue dayOfMonth]; } } @@ -1974,7 +1934,6 @@ ReservedBlockEE2Size: 00 00 00 00 if (alarm) { alarmDate = [alarm nextAlarmDate]; - [alarmDate setTimeZone: utcTZ]; *data = [alarmDate asFileTimeInMemCtx: memCtx]; } else @@ -2036,8 +1995,7 @@ ReservedBlockEE2Size: 00 00 00 00 enum mapistore_error rc; NSString *tzid; - tzid = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"] - value: 0 ofAttribute: @"tzid"]; + tzid = [timeZone tzId]; if ([tzid length] > 0) { *data = [tzid asUnicodeInMemCtx: memCtx]; @@ -2053,16 +2011,9 @@ ReservedBlockEE2Size: 00 00 00 00 inMemCtx: (TALLOC_CTX *) memCtx { enum mapistore_error rc; - iCalTimeZone *icalTZ; - icalTZ = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"] timeZone]; - if (icalTZ) - { - *data = [icalTZ asTimeZoneStructInMemCtx: memCtx]; - rc = MAPISTORE_SUCCESS; - } - else - rc = MAPISTORE_ERR_NOT_FOUND; + *data = [timeZone asTimeZoneStructInMemCtx: memCtx]; + rc = MAPISTORE_SUCCESS; return rc; } @@ -2071,24 +2022,16 @@ ReservedBlockEE2Size: 00 00 00 00 inMemCtx: (TALLOC_CTX *) memCtx { enum mapistore_error rc; - iCalTimeZone *icalTZ; /* [MS-OXOCAL] 3.1.5.5.1: This property is used in floating (all-day) events, specified in floating time, to convert the start date from UTC to the user's time zone */ - if ([event isAllDay]) - icalTZ = [iCalTimeZone timeZoneForName: [timeZone timeZoneName]]; - else if ([event isRecurrent]) - icalTZ = [(iCalDateTime *) [event firstChildWithTag: @"dtstart"] timeZone]; - else - icalTZ = nil; - - if (icalTZ) + if ([event isAllDay] | [event isRecurrent]) { /* [MS-OXOCAL] 2.2.1.42: This property can only have the E flag set in the TimeZoneDefinition struct */ - *data = [icalTZ asZoneTimeDefinitionWithFlags: TZRULE_FLAG_EFFECTIVE_TZREG - inMemCtx: memCtx]; + *data = [timeZone asZoneTimeDefinitionWithFlags: TZRULE_FLAG_EFFECTIVE_TZREG + inMemCtx: memCtx]; rc = MAPISTORE_SUCCESS; } else diff --git a/OpenChange/MAPIStoreCalendarEmbeddedMessage.m b/OpenChange/MAPIStoreCalendarEmbeddedMessage.m index ca3ba12d6..986b106d3 100644 --- a/OpenChange/MAPIStoreCalendarEmbeddedMessage.m +++ b/OpenChange/MAPIStoreCalendarEmbeddedMessage.m @@ -58,7 +58,6 @@ wrapperWithICalEvent: [newContainer event] andUser: [userContext sogoUser] andSenderEmail: nil - inTimeZone: [userContext timeZone] withConnectionInfo: [context connectionInfo]]; [self addProxy: appointmentWrapper]; } diff --git a/OpenChange/MAPIStoreCalendarMessage.m b/OpenChange/MAPIStoreCalendarMessage.m index 7f025e400..108471eae 100644 --- a/OpenChange/MAPIStoreCalendarMessage.m +++ b/OpenChange/MAPIStoreCalendarMessage.m @@ -197,7 +197,6 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK; = [MAPIStoreAppointmentWrapper wrapperWithICalEvent: masterEvent andUser: [userContext sogoUser] andSenderEmail: nil - inTimeZone: [userContext timeZone] withConnectionInfo: [context connectionInfo]]; [self addProxy: appointmentWrapper]; } diff --git a/OpenChange/MAPIStoreMailMessage.m b/OpenChange/MAPIStoreMailMessage.m index 235ca5698..f79511eb6 100644 --- a/OpenChange/MAPIStoreMailMessage.m +++ b/OpenChange/MAPIStoreMailMessage.m @@ -340,7 +340,6 @@ _compareBodyKeysByPriority (id entry1, id entry2, void *data) wrapperWithICalEvent: event andUser: [context activeUser] andSenderEmail: senderEmail - inTimeZone: [[self userContext] timeZone] withConnectionInfo: [context connectionInfo]]; [appointmentWrapper retain]; } diff --git a/OpenChange/MAPIStoreRecurrenceUtils.h b/OpenChange/MAPIStoreRecurrenceUtils.h index 6912cb294..dbb2f0c19 100644 --- a/OpenChange/MAPIStoreRecurrenceUtils.h +++ b/OpenChange/MAPIStoreRecurrenceUtils.h @@ -25,14 +25,10 @@ #include -#import - #import #import #import -@class NSTimeZone; - @class iCalEvent; @class iCalRepeatableEntityObject; @class iCalRecurrenceRule; @@ -55,7 +51,6 @@ - (void) fillRecurrencePattern: (struct RecurrencePattern *) rp withEvent: (iCalEvent *) event - inTimeZone: (NSTimeZone *) timeZone inMemCtx: (TALLOC_CTX *) memCtx; @end diff --git a/OpenChange/MAPIStoreRecurrenceUtils.m b/OpenChange/MAPIStoreRecurrenceUtils.m index e88fb5e4a..90052474a 100644 --- a/OpenChange/MAPIStoreRecurrenceUtils.m +++ b/OpenChange/MAPIStoreRecurrenceUtils.m @@ -24,7 +24,6 @@ #import #import #import -#import #import #import @@ -265,7 +264,6 @@ - (void) fillRecurrencePattern: (struct RecurrencePattern *) rp withEvent: (iCalEvent *) event - inTimeZone: (NSTimeZone *) timeZone inMemCtx: (TALLOC_CTX *) memCtx { iCalRecurrenceFrequency freq; @@ -281,10 +279,8 @@ NSMutableArray *deletedDates, *modifiedDates; startDate = [event firstRecurrenceStartDate]; - [startDate setTimeZone: timeZone]; endDate = [event lastPossibleRecurrenceStartDate]; - [endDate setTimeZone: timeZone]; - + rp->ReaderVersion = 0x3004; rp->WriterVersion = 0x3004; diff --git a/OpenChange/iCalTimeZone+MAPIStore.h b/OpenChange/iCalTimeZone+MAPIStore.h index 01bc51b82..25817392a 100644 --- a/OpenChange/iCalTimeZone+MAPIStore.h +++ b/OpenChange/iCalTimeZone+MAPIStore.h @@ -33,6 +33,8 @@ - (iCalTimeZone *) iCalTimeZoneFromDefinition: (NSData *) value withDescription: (NSString *) description inMemCtx: (TALLOC_CTX *) memCtx; +- (NSCalendarDate *) shiftedCalendarDateForDate: (NSCalendarDate *) date; + @end diff --git a/OpenChange/iCalTimeZone+MAPIStore.m b/OpenChange/iCalTimeZone+MAPIStore.m index bfdf64ed4..7cf380fdf 100644 --- a/OpenChange/iCalTimeZone+MAPIStore.m +++ b/OpenChange/iCalTimeZone+MAPIStore.m @@ -40,6 +40,7 @@ #include #import "iCalTimeZone+MAPIStore.h" +#import "MAPIStoreTypes.h" @interface iCalTimeZonePeriod (MAPIStorePropertiesPrivate) @@ -352,4 +353,25 @@ end: return tz; } +/** + * Adjust a date in this vTimeZone to its representation in UTC + * Example: Timezone is +0001, the date is 2015-12-15 00:00:00 +0000 + * it returns 2015-12-14 23:00:00 +0000 + * @param date the date to adjust to the timezone. + * @return a new GMT date adjusted with the offset of the timezone. + */ +- (NSCalendarDate *) shiftedCalendarDateForDate: (NSCalendarDate *) date +{ + NSCalendarDate *tmpDate; + + tmpDate = [date copy]; + [tmpDate autorelease]; + + [tmpDate setTimeZone: utcTZ]; + + return [tmpDate addYear: 0 month: 0 day: 0 + hour: 0 minute: 0 + second: -[[self periodForDate: tmpDate] secondsOffsetFromGMT]]; +} + @end