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