/* iCalTimeZone.m - this file is part of SOPE * * Copyright (C) 2006-2015 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This file is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #import #import #import #import #import #import "NSCalendarDate+NGCards.h" #import "NSString+NGCards.h" #import "iCalCalendar.h" #import "iCalTimeZonePeriod.h" #import "iCalTimeZone.h" static NSMutableDictionary *cache; static NSArray *knownTimeZones; @implementation iCalTimeZone + (void) initialize { cache = [[NSMutableDictionary alloc] init]; knownTimeZones = nil; } + (iCalTimeZone *) timeZoneForName: (NSString *) theName { iCalTimeZone *o; o = [cache objectForKey: theName]; if (!o) { NSFileManager *fm; NSEnumerator *e; NSArray *paths; NSString *path; BOOL b; paths = NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory, NSAllDomainsMask, YES); fm = [NSFileManager defaultManager]; if ([paths count] > 0) { e = [paths objectEnumerator]; while ((path = [e nextObject])) { path = [NSString stringWithFormat: @"%@/Libraries/Resources/NGCards/TimeZones", path]; if ([fm fileExistsAtPath: path isDirectory: &b] && b) { iCalCalendar *calendar; NSString *s; NSData *d; s = [NSString stringWithFormat: @"%@/%@.ics", path, theName]; d = [NSData dataWithContentsOfFile: s]; s = [[NSString alloc] initWithData: d encoding: NSUTF8StringEncoding]; AUTORELEASE(s); calendar = [iCalCalendar parseSingleFromSource: s]; o = [[calendar timezones] lastObject]; if (o) [cache setObject: o forKey: theName]; return o; } } } } return o; } /** * Fetch the names of the available timezones for which we have a * vTimeZone definition (.ics). * @return an array of timezones names. * @see [NSTimeZone knownTimeZoneNames] */ + (NSArray *) knownTimeZoneNames { NSFileManager *fm; NSEnumerator *e; NSDirectoryEnumerator *zones; NSArray *paths; NSMutableArray *timeZoneNames; NSString *path, *zone, *zonePath; NSRange ext; BOOL b; if (!knownTimeZones) { timeZoneNames = [NSMutableArray new]; paths = NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory, NSAllDomainsMask, YES); fm = [NSFileManager defaultManager]; if ([paths count] > 0) { e = [paths objectEnumerator]; while ((path = [e nextObject])) { path = [NSString stringWithFormat: @"%@/Libraries/Resources/NGCards/TimeZones", path]; if ([fm fileExistsAtPath: path isDirectory: &b] && b) { zones = [fm enumeratorAtPath: path]; while ((zone = [zones nextObject])) { zonePath = [NSString stringWithFormat: @"%@/%@", path, zone]; if ([fm fileExistsAtPath: zonePath isDirectory: &b] && !b) { ext = [zone rangeOfString: @".ics"]; if (ext.location != NSNotFound) { zone = [zone substringToIndex: ext.location]; if (![timeZoneNames containsObject: zone]) [timeZoneNames addObject: zone]; } } } } } } knownTimeZones = [NSArray arrayWithArray: timeZoneNames]; [knownTimeZones retain]; } return knownTimeZones; } - (Class) classForTag: (NSString *) classTag { Class tagClass; if ([classTag isEqualToString: @"STANDARD"] || [classTag isEqualToString: @"DAYLIGHT"]) tagClass = [iCalTimeZonePeriod class]; else if ([classTag isEqualToString: @"TZID"]) tagClass = [CardElement class]; else tagClass = [super classForTag: classTag]; return tagClass; } - (void) setTzId: (NSString *) tzId { [[self uniqueChildWithTag: @"tzid"] setSingleValue: tzId forKey: @""]; } - (NSString *) tzId { return [[self uniqueChildWithTag: @"tzid"] flattenedValuesForKey: @""]; } - (NSDictionary *) _occurrenceForPeriodNamed: (NSString *) pName forDate: (NSCalendarDate *) aDate { NSArray *periods; NSEnumerator *periodsList; iCalTimeZonePeriod *period; NSCalendarDate *occurrence; NSDictionary *periodDict; occurrence = nil; periodDict = nil; periods = [[self childrenWithTag: pName] sortedArrayUsingSelector: @selector (compare:)]; periodsList = [periods reverseObjectEnumerator]; while ((period = (iCalTimeZonePeriod *) [periodsList nextObject])) { if ([periods count] > 1 && ([[period startDate] yearOfCommonEra] > [aDate yearOfCommonEra])) continue; occurrence = [period occurrenceForDate: aDate]; if (occurrence && (!periodDict || [occurrence earlierDate: [periodDict objectForKey: @"occurrence"]] == [periodDict objectForKey: @"occurrence"])) { periodDict = [NSDictionary dictionaryWithObjectsAndKeys: period, @"period", occurrence, @"occurrence",nil]; occurrence = nil; } } return periodDict; } - (iCalTimeZonePeriod *) periodForDate: (NSCalendarDate *) date { //NSCalendarDate *daylightOccurence, *standardOccurence; NSDictionary *daylightOccurence, *standardOccurence; iCalTimeZonePeriod *period; /* FIXME, this could cause crashes when timezones are not properly specified, but let's say it won't happen often... */ daylightOccurence = [self _occurrenceForPeriodNamed: @"daylight" forDate: date]; standardOccurence = [self _occurrenceForPeriodNamed: @"standard" forDate: date]; if (!standardOccurence && !daylightOccurence) period = (iCalTimeZonePeriod *) [self uniqueChildWithTag: @"standard"]; else if (!standardOccurence) period = (iCalTimeZonePeriod *) [self uniqueChildWithTag: @"daylight"]; else if (!daylightOccurence) period = (iCalTimeZonePeriod *) [self uniqueChildWithTag: @"standard"]; else if ([date earlierDate: [daylightOccurence objectForKey: @"occurrence"]] == date) { if ([date earlierDate: [standardOccurence objectForKey: @"occurrence"]] == date && ([[standardOccurence objectForKey: @"occurrence"] earlierDate: [daylightOccurence objectForKey: @"occurrence"]] == [standardOccurence objectForKey: @"occurrence"])) period = (iCalTimeZonePeriod *) [daylightOccurence objectForKey: @"period"]; else period = (iCalTimeZonePeriod *) [standardOccurence objectForKey: @"period"]; } else { if ([[standardOccurence objectForKey: @"occurrence"] earlierDate: date] == [standardOccurence objectForKey: @"occurrence"] && ([[daylightOccurence objectForKey: @"occurrence"] earlierDate: [standardOccurence objectForKey: @"occurrence"]] == [daylightOccurence objectForKey: @"occurrence"] )) period = (iCalTimeZonePeriod *) [standardOccurence objectForKey: @"period"]; else period = (iCalTimeZonePeriod *) [daylightOccurence objectForKey: @"period"]; } return period; } /** * Adjust a date with respect to this vTimeZone. * @param theDate the date to adjust to the timezone. * @return a new GMT date adjusted with the offset of the timezone. */ - (NSCalendarDate *) computedDateForDate: (NSCalendarDate *) theDate { NSCalendarDate *tmpDate; NSTimeZone *utc; utc = [NSTimeZone timeZoneWithName: @"GMT"]; tmpDate = [theDate copy]; [tmpDate autorelease]; [tmpDate setTimeZone: utc]; return [tmpDate addYear: 0 month: 0 day: 0 hour: 0 minute: 0 second: [[self periodForDate: theDate] secondsOffsetFromGMT]]; } /** * Adjust a date with respect to this vTimeZone. * Example: Timezone is -0400, the date is 2012-05-23 13:00:00 +0000: * it returns 2012-05-23 09:00:00 +0000 * @param theDate the string representing a date. * @return a new GMT date adjusted with the offset of this timezone. */ - (NSCalendarDate *) computedDateForString: (NSString *) theDate { NSCalendarDate *tmpDate; NSTimeZone *utc; utc = [NSTimeZone timeZoneWithName: @"GMT"]; tmpDate = [theDate asCalendarDate]; [tmpDate setTimeZone: utc]; return [tmpDate addYear: 0 month: 0 day: 0 hour: 0 minute: 0 second: [[self periodForDate: tmpDate] secondsOffsetFromGMT]]; } /** * Adjust multiple dates with respect to this vTimeZone. * @param theDates an array of strings representing dates. * @param an array of NSCalendarDate objects. */ - (NSMutableArray *) computedDatesForStrings: (NSArray *) theDates { NSCalendarDate *date; NSMutableArray *dates; NSEnumerator *dateList; NSString *dateString; dates = [NSMutableArray array]; dateList = [theDates objectEnumerator]; while ((dateString = [dateList nextObject])) { date = [self computedDateForString: dateString]; [dates addObject: date]; } return dates; } - (NSString *) dateTimeStringForDate: (NSCalendarDate *) date { return [[self computedDateForDate: date] iCalFormattedDateTimeString]; } - (NSString *) dateStringForDate: (NSCalendarDate *) date { return [[self computedDateForDate: date] iCalFormattedDateString]; } - (NSCalendarDate *) dateForDateTimeString: (NSString *) string { NSCalendarDate *tmpDate; iCalTimeZonePeriod *period, *realPeriod; tmpDate = [string asCalendarDate]; period = [self periodForDate: tmpDate]; tmpDate = [tmpDate addYear: 0 month: 0 day: 0 hour: 0 minute: 0 second: -[period secondsOffsetFromGMT]]; #warning this is a dirty hack due to the fact that the date is first passed as UTC realPeriod = [self periodForDate: tmpDate]; if (realPeriod != period) tmpDate = [tmpDate addYear: 0 month: 0 day: 0 hour: 0 minute: 0 second: ([period secondsOffsetFromGMT] - [realPeriod secondsOffsetFromGMT])]; return tmpDate; } @end