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 df3700575..dbb2f0c19 100644 --- a/OpenChange/MAPIStoreRecurrenceUtils.h +++ b/OpenChange/MAPIStoreRecurrenceUtils.h @@ -25,12 +25,9 @@ #include -#import - #import #import - -@class NSTimeZone; +#import @class iCalEvent; @class iCalRepeatableEntityObject; @@ -46,7 +43,7 @@ fromRecurrencePattern: (struct RecurrencePattern *) rp withExceptions: (struct ExceptionInfo *) exInfos andExceptionCount: (uint16_t) exInfoCount - inTimeZone: (NSTimeZone *) tz; + inTimeZone: (iCalTimeZone *) tz; @end @@ -54,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 9e0bf29c3..90052474a 100644 --- a/OpenChange/MAPIStoreRecurrenceUtils.m +++ b/OpenChange/MAPIStoreRecurrenceUtils.m @@ -24,7 +24,6 @@ #import #import #import -#import #import #import @@ -35,6 +34,7 @@ #import #import #import +#import #import "NSDate+MAPIStore.h" #import "MAPIStoreRecurrenceUtils.h" @@ -51,7 +51,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 +63,7 @@ iCalWeekOccurrence weekOccurrence; iCalWeekOccurrences dayMaskDays; NSUInteger count, max; - NSInteger bySetPos; + NSInteger bySetPos, tzOffset; unsigned char maskValue; [entity removeAllRecurrenceRules]; @@ -242,9 +242,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]; } } @@ -263,7 +264,6 @@ - (void) fillRecurrencePattern: (struct RecurrencePattern *) rp withEvent: (iCalEvent *) event - inTimeZone: (NSTimeZone *) timeZone inMemCtx: (TALLOC_CTX *) memCtx { iCalRecurrenceFrequency freq; @@ -279,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/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 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..25817392a 100644 --- a/OpenChange/iCalTimeZone+MAPIStore.h +++ b/OpenChange/iCalTimeZone+MAPIStore.h @@ -30,6 +30,11 @@ - (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; +- (NSCalendarDate *) shiftedCalendarDateForDate: (NSCalendarDate *) date; + @end diff --git a/OpenChange/iCalTimeZone+MAPIStore.m b/OpenChange/iCalTimeZone+MAPIStore.m index f692272db..7cf380fdf 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 @@ -36,6 +40,7 @@ #include #import "iCalTimeZone+MAPIStore.h" +#import "MAPIStoreTypes.h" @interface iCalTimeZonePeriod (MAPIStorePropertiesPrivate) @@ -103,18 +108,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,18 +158,220 @@ 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]; 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; +} + +/** + * 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