/** Time zone management. -*- Mode: ObjC -*- Copyright (C) 1997-2002 Free Software Foundation, Inc. Written by: Yoo C. Chung Date: June 1997 Rewrite large chunks by: Richard Frith-Macdonald Date: September 2002 This file is part of the GNUstep Base Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. NSTimeZone class reference $Date$ $Revision$ */ /* Use the system time zones if available. In other cases, use an implementation independent of the system, since POSIX functions for time zones are woefully inadequate for implementing NSTimeZone. Time zone names can be different from system to system, but usually the user has already set up his timezone independant of GNUstep, so we should respect that information. We do not use a dictionary for storing time zones, since such a dictionary would be VERY large (~500K). And we would have to use a complicated object determining whether we're using daylight savings time and such for every entry in the dictionary. (Though we will eventually have to change the implementation to prevent the year 2038 problem.) The local time zone can be specified with: 1) the user defaults database 2) the GNUSTEP_TZ environment variable 3) the file LOCAL_TIME_FILE in _time_zone_path() 4) the TZ environment variable 5) TZDEFAULT defined in tzfile.h on platforms which have it 6) tzset() & tznam[] for platforms which have it 7) Windows registry, for Win32 systems 8) or the fallback time zone (which is UTC) with the ones listed first having precedence. Any time zone must be a file name in ZONES_DIR. Files & File System Heirarchy info: =================================== Default place for the NSTimeZone directory is _time_zone_path(): {$(GNUSTEP_SYSTEM_ROOT)Libary/Libraries/Resources/TIME_ZONE_DIR} LOCAL_TIME_FILE is a text file with the name of the time zone file. ZONES_DIR is a sub-directory under TIME_ZONE_DIR (dir) ../System/Library/Libraries/Resources/.. (dir) NSTimeZone (file) localtime {text; time zone eg Australia/Perth} (dir) zones Note that full zone info is required, especially the various "GMT" files which are created especially for OPENSTEP compatibility. Zone info comes from the Olson time database. FIXME?: use leap seconds? */ #include "config.h" #include "GNUstepBase/preface.h" #include "GNUstepBase/GSLock.h" #include #include #include #include #include #include "Foundation/NSArray.h" #include "Foundation/NSCoder.h" #include "Foundation/NSData.h" #include "Foundation/NSDate.h" #include "Foundation/NSDictionary.h" #include "Foundation/NSException.h" #include "Foundation/NSFileManager.h" #include "Foundation/NSLock.h" #include "Foundation/NSObject.h" #include "Foundation/NSProcessInfo.h" #include "Foundation/NSString.h" #include "Foundation/NSUserDefaults.h" #include "Foundation/NSUtilities.h" #include "Foundation/NSZone.h" #include "Foundation/NSBundle.h" #include "Foundation/NSMapTable.h" #include "Foundation/NSThread.h" #include "Foundation/NSNotification.h" #include "Foundation/NSPortCoder.h" #include "Foundation/NSTimeZone.h" #include "Foundation/NSByteOrder.h" #include "Foundation/NSDebug.h" #include "GNUstepBase/GSCategories.h" #include "GSConfig.h" #ifdef HAVE_TZHEAD #include #else #define NOID #include "nstzfile.h" #endif /* Key for local time zone in user defaults. */ #define LOCALDBKEY @"Local Time Zone" /* Directory that contains the time zone data. Expected in Resources directory for library bundle. */ #define TIME_ZONE_DIR @"NSTimeZones" /* Name of time zone abbreviation (plist) dictionary. */ #define ABBREV_DICT @"abbreviations" /* Name of time zone abbreviation map. It is a text file with each line comprised of the abbreviation, a whitespace, and the name. Neither the abbreviation nor the name can contain whitespace, and each line must not be longer than 80 characters. */ #define ABBREV_MAP @"abbreviations" /* File holding regions grouped by latitude. It is a text file with each line comprised of the latitude region, whitespace, and the name. Neither the abbreviation nor the name can contain whitespace, and each line must not be longer than 80 characters. */ #define REGIONS_FILE @"regions" /* Name of the file that contains the name of the local time zone. */ #define LOCAL_TIME_FILE @"localtime" /* Directory that contains the actual time zones. */ #define ZONES_DIR @"zones/" /* Many systems have this file */ #define SYSTEM_TIME_FILE @"/etc/localtime" /* Possible location of system time zone files */ static NSString *tzdir = nil; @class GSAbsTimeZone; @class GSTimeZoneDetail; @class GSAbsTimeZoneDetail; @class GSPlaceholderTimeZone; /* * Information for abstract placeholder class. */ static GSPlaceholderTimeZone *defaultPlaceholderTimeZone; static NSMapTable *placeholderMap; /* * Temporary structure for holding time zone details. * This is the format in the data object. */ struct ttinfo { char offset[4]; // Seconds east of UTC unsigned char isdst; // Daylight savings time? unsigned char abbr_idx; // Index into time zone abbreviations string } __attribute__((packed)); /* * And this is the structure used in the time zone instances. */ typedef struct { gss32 offset; BOOL isdst; unsigned char abbr_idx; char pad[2]; NSString *abbreviation; } TypeInfo; @interface GSTimeZone : NSTimeZone { @public NSString *timeZoneName; NSData *timeZoneData; unsigned int n_trans; unsigned int n_types; gss32 *trans; TypeInfo *types; unsigned char *idxs; } @end #ifdef WIN32 @interface GSWindowsTimeZone : NSTimeZone { @public NSString *timeZoneName; NSString *daylightZoneName; NSString *timeZoneNameAbbr; NSString *daylightZoneNameAbbr; LONG Bias; LONG StandardBias; LONG DaylightBias; SYSTEMTIME StandardDate; SYSTEMTIME DaylightDate; } @end #endif static NSTimeZone *defaultTimeZone = nil; static NSTimeZone *localTimeZone = nil; static NSTimeZone *systemTimeZone = nil; /* Dictionary for time zones. Each time zone must have a unique name. */ static NSMutableDictionary *zoneDictionary; /* one-to-one abbreviation to time zone name dictionary. */ static NSMutableDictionary *abbreviationDictionary = nil; /* one-to-many abbreviation to time zone name dictionary. */ static NSMutableDictionary *abbreviationMap = nil; /* Lock for creating time zones. */ static NSRecursiveLock *zone_mutex = nil; static Class NSTimeZoneClass; static Class GSPlaceholderTimeZoneClass; /* Decode the four bytes at PTR as a signed integer in network byte order. Based on code included in the GNU C Library 2.0.3. */ static inline int decode (const void *ptr) { #if defined(WORDS_BIGENDIAN) && SIZEOF_INT == 4 #if NEED_WORD_ALIGNMENT int value; memcpy(&value, ptr, sizeof(int)); return value; #else return *(const int *) ptr; #endif #else /* defined(WORDS_BIGENDIAN) && SIZEOF_INT == 4 */ const unsigned char *p = ptr; int result = *p & (1 << (CHAR_BIT - 1)) ? ~0 : 0; result = (result << 8) | *p++; result = (result << 8) | *p++; result = (result << 8) | *p++; result = (result << 8) | *p++; return result; #endif /* defined(WORDS_BIGENDIAN) && SIZEOF_INT == 4 */ } /* Return path to a TimeZone directory file */ static NSString *_time_zone_path(NSString *subpath, NSString *type) { NSBundle *gbundle; if (type == nil) type = @""; gbundle = [NSBundle bundleForLibrary: @"gnustep-base"]; return [gbundle pathForResource: subpath ofType: type inDirectory: TIME_ZONE_DIR]; } @interface GSPlaceholderTimeZone : NSTimeZone @end @interface GSAbsTimeZone : NSTimeZone { @public NSString *name; id detail; int offset; // Offset from UTC in seconds. } - (id) initWithOffset: (int)anOffset; @end @interface NSLocalTimeZone : NSTimeZone @end @interface GSTimeZoneDetail : NSTimeZoneDetail { NSTimeZone *timeZone; // Time zone which created this object. NSString *abbrev; // Abbreviation for time zone detail. int offset; // Offset from UTC in seconds. BOOL is_dst; // Is it daylight savings time? } - (id) initWithTimeZone: (NSTimeZone*)aZone withAbbrev: (NSString*)anAbbrev withOffset: (int)anOffset withDST: (BOOL)isDST; @end @interface GSAbsTimeZoneDetail : NSTimeZoneDetail { GSAbsTimeZone *zone; // Time zone which created this object. } - (id) initWithTimeZone: (GSAbsTimeZone*)aZone; @end /* Private methods for obtaining resource file names. */ @interface NSTimeZone (Private) + (NSString*) getTimeZoneFile: (NSString*)name; @end @implementation GSPlaceholderTimeZone - (id) autorelease { NSWarnLog(@"-autorelease sent to uninitialised time zone"); return self; // placeholders never get released. } - (void) dealloc { return; // placeholders never get deallocated. } - (id) initWithName: (NSString*)name data: (NSData*)data { NSTimeZone *zone; unsigned length = [name length]; if (length == 0) { NSLog(@"Disallowed null time zone name"); return nil; } if (length == 15 && [name isEqual: @"NSLocalTimeZone"]) { zone = RETAIN(localTimeZone); DESTROY(self); return zone; } /* * Return a cached time zone if possible. * NB. if data of cached zone does not match new data ... don't use cache */ if (zone_mutex != nil) { [zone_mutex lock]; } zone = [zoneDictionary objectForKey: name]; if (data != nil && [data isEqual: [zone data]] == NO) { zone = nil; } IF_NO_GC(RETAIN(zone)); if (zone_mutex != nil) { [zone_mutex unlock]; } if (zone == nil) { unichar c; unsigned i; if (length == 8 && [name hasPrefix: @"GMT"] == YES && ((c = [name characterAtIndex: 3]) == '+' || c == '-')) { c = [name characterAtIndex: 4]; if (c >= '0' && c <= '9') { i = c - '0'; c = [name characterAtIndex: 5]; if (c >= '0' && c <= '9') { i = i * 10 + (c - '0'); c = [name characterAtIndex: 6]; if (c >= '0' && c <= '9') { i = i * 6 + (c - '0'); c = [name characterAtIndex: 7]; if (c >= '0' && c <= '9') { i = i * 10 + (c - '0'); zone = [[GSAbsTimeZone alloc] initWithOffset: i*60]; } } } } } if (zone == nil && length > 19 && [name hasPrefix: @"NSAbsoluteTimeZone:"] == YES) { i = [[name substringFromIndex: 19] intValue]; zone = [[GSAbsTimeZone alloc] initWithOffset: i]; } if (zone == nil) { if (data == nil) { NSString *fileName; const char *str = [name UTF8String]; /* Make sure that only time zone files are accessed. FIXME: Make this more robust. */ if ((str)[0] == '/' || strchr(str, '.') != NULL) { NSLog(@"Disallowed time zone name `%@'.", name); return nil; } fileName = [NSTimeZoneClass getTimeZoneFile: name]; if (fileName == nil || ![[NSFileManager defaultManager] fileExistsAtPath:fileName]) #ifdef WIN32 { zone = [[GSWindowsTimeZone alloc] initWithName:name data:0]; RELEASE(self); return zone; } #else { NSLog(@"Unknown time zone name `%@'.", name); return nil; } #endif data = [NSData dataWithContentsOfFile: fileName]; } #ifdef WIN32 if (!data) zone = [[GSWindowsTimeZone alloc] initWithName: name data: data]; else #endif zone = [[GSTimeZone alloc] initWithName: name data: data]; } } RELEASE(self); return zone; } - (void) release { return; // placeholders never get released. } - (id) retain { return self; // placeholders never get retained. } @end @implementation NSLocalTimeZone - (NSString*) abbreviation { return [[NSTimeZoneClass defaultTimeZone] abbreviation]; } - (NSString*) abbreviationForDate: (NSDate*)aDate { return [[NSTimeZoneClass defaultTimeZone] abbreviationForDate: aDate]; } - (id) autorelease { return self; } - (NSData*) data { return [[NSTimeZoneClass defaultTimeZone] data]; } - (void) encodeWithCoder: (NSCoder*)aCoder { [aCoder encodeObject: @"NSLocalTimeZone"]; } - (id) init { return self; } - (BOOL) isDaylightSavingTime { return [[NSTimeZoneClass defaultTimeZone] isDaylightSavingTime]; } - (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate { return [[NSTimeZoneClass defaultTimeZone] isDaylightSavingTimeForDate: aDate]; } - (NSString*) name { return [[NSTimeZoneClass defaultTimeZone] name]; } - (void) release { } - (id) retain { return self; } - (int) secondsFromGMT { return [[NSTimeZoneClass defaultTimeZone] secondsFromGMT]; } - (int) secondsFromGMTForDate: (NSDate*)aDate { return [[NSTimeZoneClass defaultTimeZone] secondsFromGMTForDate: aDate]; } - (NSArray*) timeZoneDetailArray { return [[NSTimeZoneClass defaultTimeZone] timeZoneDetailArray]; } - (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date { return [[NSTimeZoneClass defaultTimeZone] timeZoneDetailForDate: date]; } - (NSString*) timeZoneName { return [[NSTimeZoneClass defaultTimeZone] timeZoneName]; } @end @implementation GSAbsTimeZone static int uninitialisedOffset = 100000; static NSMapTable *absolutes = 0; + (void) initialize { if (self == [GSAbsTimeZone class]) { absolutes = NSCreateMapTable(NSIntMapKeyCallBacks, NSNonOwnedPointerMapValueCallBacks, 0); } } - (NSString*) abbreviationForDate: (NSDate*)aDate { return name; } - (void) dealloc { if (offset != uninitialisedOffset) { if (zone_mutex != nil) [zone_mutex lock]; NSMapRemove(absolutes, (void*)(gsaddr)offset); if (zone_mutex != nil) [zone_mutex unlock]; } RELEASE(name); RELEASE(detail); [super dealloc]; } - (void) encodeWithCoder: (NSCoder*)aCoder { [aCoder encodeObject: name]; } - (id) initWithOffset: (int)anOffset { GSAbsTimeZone *z; int extra; int sign = anOffset >= 0 ? 1 : -1; /* * Set the uninitialised offset so that dealloc before full * initialisation won't remove the timezeone for offset 0 from cache. */ offset = uninitialisedOffset; /* * Round the offset to the nearest minute, (for MacOS-X compatibility) * and ensure it is no more than 18 hours. */ anOffset *= sign; extra = anOffset % 60; if (extra < 30) { anOffset -= extra; } else { anOffset += 60 - extra; } if (anOffset > 64800) { RELEASE(self); return nil; } anOffset *= sign; if (zone_mutex != nil) { [zone_mutex lock]; } z = (GSAbsTimeZone*)NSMapGet(absolutes, (void*)(gsaddr)anOffset); if (z != nil) { IF_NO_GC(RETAIN(z)); RELEASE(self); } else { if (anOffset % 60 == 0) { char s = (anOffset >= 0) ? '+' : '-'; int i = (anOffset >= 0) ? anOffset / 60 : -anOffset / 60; int h = i / 60; int m = i % 60; char buf[9]; sprintf(buf, "GMT%c%02d%02d", s, h, m); name = [[NSString alloc] initWithCString: buf]; } else { /* * Should never happen now we round to the minute * for MacOS-X compatibnility. */ name = [[NSString alloc] initWithFormat: @"NSAbsoluteTimeZone:%d", anOffset]; } detail = [[GSAbsTimeZoneDetail alloc] initWithTimeZone: self]; offset = anOffset; z = self; NSMapInsert(absolutes, (void*)(gsaddr)anOffset, (void*)z); [zoneDictionary setObject: self forKey: (NSString*)name]; } if (zone_mutex != nil) { [zone_mutex unlock]; } return z; } - (BOOL) isDaylightSavingTimeZoneForDate: (NSDate*)aDate { return NO; } - (NSString*) name { return name; } - (int) secondsFromGMTForDate: (NSDate*)aDate { return offset; } - (NSArray*) timeZoneDetailArray { return [NSArray arrayWithObject: detail]; } - (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date { return detail; } - (NSString*) timeZoneName { return name; } @end @implementation GSTimeZoneDetail - (void) dealloc { RELEASE(timeZone); [super dealloc]; } - (id) initWithCoder: (NSCoder*)aDecoder { [aDecoder decodeValueOfObjCType: @encode(id) at: &abbrev]; [aDecoder decodeValueOfObjCType: @encode(int) at: &offset]; [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &is_dst]; return self; } - (id) initWithTimeZone: (NSTimeZone*)aZone withAbbrev: (NSString*)anAbbrev withOffset: (int)anOffset withDST: (BOOL)isDST { timeZone = RETAIN(aZone); abbrev = anAbbrev; // NB. Depend on this being retained in aZone offset = anOffset; is_dst = isDST; return self; } - (BOOL) isDaylightSavingTimeZone { return is_dst; } - (NSString*) name { return [timeZone name]; } - (NSString*) timeZoneAbbreviation { return abbrev; } - (NSArray*) timeZoneDetailArray { return [timeZone timeZoneDetailArray]; } - (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date { return [timeZone timeZoneDetailForDate: date]; } - (int) timeZoneSecondsFromGMT { return offset; } - (int) timeZoneSecondsFromGMTForDate: (NSDate*)aDate { return offset; } @end @implementation GSAbsTimeZoneDetail - (NSString*) abbreviation { return zone->name; } - (NSString*) abbreviationForDate: (NSDate*)aDate { return zone->name; } - (void) dealloc { RELEASE(zone); [super dealloc]; } - (id) initWithTimeZone: (GSAbsTimeZone*)aZone { zone = RETAIN(aZone); return self; } - (BOOL) isDaylightSavingTimeZone { return NO; } - (BOOL) isDaylightSavingTimeZoneForDate: (NSDate*)aDate { return NO; } - (NSString*) name { return zone->name; } - (NSString*) timeZoneAbbreviation { return zone->name; } - (NSArray*) timeZoneDetailArray { return [zone timeZoneDetailArray]; } - (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date { return self; } - (int) timeZoneSecondsFromGMT { return zone->offset; } - (int) timeZoneSecondsFromGMTForDate: (NSDate*)aDate { return zone->offset; } @end /** *

* The local time zone is obtained from, in order of preference:
* 1) the user defaults database: NSGlobalDomain "Local Time Zone"
* 2) the GNUSTEP_TZ environment variable
* 3) the file "localtime" in System/Library/Libraries/Resources/NSTimeZone
* 4) the TZ environment variable
* 5) The system zone settings (typically in /etc/localtime)
* 6) tzset and tznam on platforms which have it
* 7) Windows registry, on Win32 systems
* 8) or the fallback time zone (which is UTC)
*

*

If the GNUstep time zone datafiles become too out of date, one * can download an updated database from ftp://elsie.nci.nih.gov/pub/ * and compile it as specified in the README file in the * NSTimeZones directory. *

*

Time zone names in NSDates should be GMT, MET etc. not * Europe/Berlin, America/Washington etc. *

*

The problem with this is that various time zones may use the * same abbreviation (e.g. Australia/Brisbane and * America/New_York both use EST), and some time zones * may have different rules for daylight saving time even if the * abbreviation and offsets from UTC are the same. *

*

The problems with depending on the OS for providing time zone * info are that time zone names may vary * wildly between OSes (this could be a big problem when * archiving is used between different systems). *

*

Win32: Time zone names read from the registry are different * from other GNUstep installations. Be careful when moving data * between platforms in this case. *

*/ @implementation NSTimeZone /** * Returns a dictionary containing time zone abbreviations and their * corresponding time zone names. More than one time zone may be associated * with a single abbreviation. In this case, the dictionary contains only * one (usually the most common) time zone name for the abbreviation. */ + (NSDictionary*) abbreviationDictionary { NSString *path; if (abbreviationDictionary != nil) return abbreviationDictionary; path = _time_zone_path (ABBREV_DICT, @"plist"); if (path == NULL) { [NSException raise: NSInternalInconsistencyException format: @"Failed to open time zone abbreviation dictionary."]; } abbreviationDictionary = [[NSString stringWithContentsOfFile: path] propertyList]; abbreviationDictionary = [abbreviationDictionary makeImmutableCopyOnFail: NO]; return RETAIN(abbreviationDictionary); } /** * Returns a dictionary that maps abbreviations to the array * containing all the time zone names that use the abbreviation. */ + (NSDictionary*) abbreviationMap { FILE *file; // For the file containing the abbreviation dictionary char abbrev[80], name[80]; NSString *path; /* Instead of creating the abbreviation dictionary when the class is initialized, we create it when we first need it, since the dictionary can be potentially very large, considering that it's almost never used. */ if (abbreviationMap != nil) return abbreviationMap; /* Read dictionary from file. */ abbreviationMap = [[NSMutableDictionary alloc] init]; path = _time_zone_path (ABBREV_MAP, nil); #if defined(__WIN32__) file = fopen([path fileSystemRepresentation], "rb"); #else file = fopen([path fileSystemRepresentation], "r"); #endif if (file == NULL) [NSException raise: NSInternalInconsistencyException format: @"Failed to open time zone abbreviation map."]; while (fscanf(file, "%79s %79s", abbrev, name) == 2) { id a, the_name, the_abbrev; the_name = [NSString stringWithCString: name]; the_abbrev = [NSString stringWithCString: abbrev]; a = [abbreviationMap objectForKey: the_abbrev]; if (a == nil) { a = AUTORELEASE([NSMutableArray new]); [abbreviationMap setObject: a forKey: the_abbrev]; } [a addObject: the_name]; } fclose(file); /* Special case: Add the system time zone if it doesn't exist in the map */ { id array; id the_abbrev = [systemTimeZone abbreviation]; array = [abbreviationMap objectForKey: the_abbrev]; if (array == nil) { array = AUTORELEASE([NSMutableArray new]); [abbreviationMap setObject: array forKey: the_abbrev]; } if ([array containsObject: [systemTimeZone timeZoneName]] == NO) [array addObject: [systemTimeZone timeZoneName]]; } return abbreviationMap; } /** * Returns an array of all known time zone names. */ + (NSArray*) knownTimeZoneNames { static NSArray *namesArray = nil; int i; id temp_array; NSArray *regionsArray, *array; /* We create the array only when we need it to reduce overhead. */ if (namesArray != nil) return namesArray; temp_array = [NSMutableArray array]; regionsArray = [self timeZoneArray]; for (i = 0; i < [regionsArray count]; i++) { array = [regionsArray objectAtIndex:i]; [temp_array addObjectsFromArray: array]; } namesArray = [[NSArray alloc] initWithArray: temp_array]; return namesArray; } + (id) allocWithZone: (NSZone*)z { if (self == NSTimeZoneClass) { /* * We return a placeholder object that can * be converted to a real object when its initialisation method * is called. */ if (z == NSDefaultMallocZone() || z == 0) { /* * As a special case, we can return a placeholder for a time zone * in the default malloc zone extremely efficiently. */ return defaultPlaceholderTimeZone; } else { id obj; /* * For anything other than the default zone, we need to * locate the correct placeholder in the (lock protected) * table of placeholders. */ if (zone_mutex != nil) { [zone_mutex lock]; } obj = (id)NSMapGet(placeholderMap, (void*)z); if (obj == nil) { /* * There is no placeholder object for this zone, so we * create a new one and use that. */ obj = (id)NSAllocateObject(GSPlaceholderTimeZoneClass, 0, z); NSMapInsert(placeholderMap, (void*)z, (void*)obj); } if (zone_mutex != nil) { [zone_mutex unlock]; } return obj; } } else { return NSAllocateObject(self, 0, z); } } /** * Return the default time zone for this process. */ + (NSTimeZone*) defaultTimeZone { NSTimeZone *zone; if (zone_mutex != nil) { [zone_mutex lock]; } if (defaultTimeZone == nil) { zone = [self systemTimeZone]; } else { if (zone_mutex != nil) { zone = AUTORELEASE(RETAIN(defaultTimeZone)); } else { zone = defaultTimeZone; } } if (zone_mutex != nil) { [zone_mutex unlock]; } return zone; } + (void) initialize { if (self == [NSTimeZone class]) { NSTimeZoneClass = self; GSPlaceholderTimeZoneClass = [GSPlaceholderTimeZone class]; zoneDictionary = [[NSMutableDictionary alloc] init]; /* * Set up infrastructure for placeholder timezones. */ defaultPlaceholderTimeZone = (GSPlaceholderTimeZone*) NSAllocateObject(GSPlaceholderTimeZoneClass, 0, NSDefaultMallocZone()); placeholderMap = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks, NSNonRetainedObjectMapValueCallBacks, 0); localTimeZone = [[NSLocalTimeZone alloc] init]; zone_mutex = [GSLazyRecursiveLock new]; } } /** * Return a proxy to the default time zone for this process. */ + (NSTimeZone*) localTimeZone { return localTimeZone; } /** * Destroy the system time zone so that it will be recreated * next time it is used. */ + (void) resetSystemTimeZone { if (zone_mutex != nil) { [zone_mutex lock]; } DESTROY(systemTimeZone); if (zone_mutex != nil) { [zone_mutex unlock]; } } /** * Set the default time zone to be used for this process. */ + (void) setDefaultTimeZone: (NSTimeZone*)aTimeZone { if (aTimeZone != defaultTimeZone) { /* * We can't make the localTimeZone the default since that would * cause recursion ... */ if (aTimeZone == localTimeZone) { aTimeZone = [self systemTimeZone]; } if (zone_mutex != nil) { [zone_mutex lock]; } ASSIGN(defaultTimeZone, aTimeZone); if (zone_mutex != nil) { [zone_mutex unlock]; } } } /** * Returns the current system time zone for the process. */ + (NSTimeZone*) systemTimeZone { NSTimeZone *zone = nil; if (zone_mutex != nil) { [zone_mutex lock]; } if (systemTimeZone == nil) { NSString *localZoneString = nil; /* * setup default value in case something goes wrong. */ systemTimeZone = RETAIN([NSTimeZoneClass timeZoneForSecondsFromGMT: 0]); /* * Try to get timezone from user defaults database */ localZoneString = [[NSUserDefaults standardUserDefaults] stringForKey: LOCALDBKEY]; /* * Try to get timezone from GNUSTEP_TZ environment variable. */ if (localZoneString == nil) { localZoneString = [[[NSProcessInfo processInfo] environment] objectForKey: @"GNUSTEP_TZ"]; } /* * Try to get timezone from LOCAL_TIME_FILE. */ if (localZoneString == nil) { NSString *f = _time_zone_path(LOCAL_TIME_FILE, nil); if (f != nil) { localZoneString = [NSString stringWithContentsOfFile: f]; localZoneString = [localZoneString stringByTrimmingSpaces]; } } /* * Try to get timezone from standard unix environment variable. */ if (localZoneString == nil) { localZoneString = [[[NSProcessInfo processInfo] environment] objectForKey: @"TZ"]; } if (localZoneString == nil) { /* Get the zone name from the localtime file, assuming the file is a symlink to the time zone. Getting the actual data (which is easier) doesn't help, since we won't know the name itself. */ #if defined(HAVE_TZHEAD) && defined(TZDEFAULT) tzdir = RETAIN([NSString stringWithCString: TZDIR]); localZoneString = [NSString stringWithCString: TZDEFAULT]; localZoneString = [localZoneString stringByResolvingSymlinksInPath]; #else NSFileManager *dflt = [NSFileManager defaultManager]; if ([dflt fileExistsAtPath: SYSTEM_TIME_FILE]) { localZoneString = SYSTEM_TIME_FILE; localZoneString = [localZoneString stringByResolvingSymlinksInPath]; /* Guess what tzdir is */ tzdir = [localZoneString stringByDeletingLastPathComponent]; while ([tzdir length] > 2 && [dflt fileExistsAtPath: [tzdir stringByAppendingPathComponent: @"GMT"]] == NO) tzdir = [tzdir stringByDeletingLastPathComponent]; if ([tzdir length] > 2) RETAIN(tzdir); else { localZoneString = tzdir = nil; } } #endif if (localZoneString != nil && [localZoneString hasPrefix: tzdir]) { /* This must be the time zone name */ localZoneString = [[localZoneString mutableCopy] autorelease]; [(NSMutableString *)localZoneString deletePrefix: tzdir]; if ([localZoneString hasPrefix: @"/"]) { [(NSMutableString *)localZoneString deletePrefix: @"/"]; } } else localZoneString = nil; } #if HAVE_TZSET /* * Try to get timezone from tzset and tzname */ if (localZoneString == nil) { tzset(); if (tzname[0] != NULL && *tzname[0] != '\0') localZoneString = [NSString stringWithCString: tzname[0]]; } #endif #ifdef WIN32 /* * Try to get timezone from windows registry. */ { HKEY regkey; if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, \ "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", 0, KEY_READ, ®key)) { char buf[255]; DWORD bufsize=255; DWORD type; if (ERROR_SUCCESS==RegQueryValueEx(regkey, "StandardName", 0, &type, buf, &bufsize)) { bufsize=strlen(buf); while (bufsize && isspace(buf[bufsize-1])) { bufsize--; } localZoneString = [NSString stringWithCString:buf length:bufsize]; } RegCloseKey(regkey); } } #endif if (localZoneString != nil) { NSDebugLLog (@"NSTimeZone", @"Using zone %@", localZoneString); zone = [defaultPlaceholderTimeZone initWithName: localZoneString]; } else { NSLog(@"No local time zone specified."); } /* * If local time zone fails to allocate, then allocate something * that is sure to succeed (unless we run out of memory, of * course). */ if (zone == nil) { NSLog(@"Using time zone with absolute offset 0."); zone = systemTimeZone; } ASSIGN(systemTimeZone, zone); } if (zone_mutex != nil) { zone = AUTORELEASE(RETAIN(systemTimeZone)); [zone_mutex unlock]; } else { zone = systemTimeZone; } return zone; } /** * Returns an array of all the known regions.
* There are 24 elements, of course, one for each time zone. * Each element contains an array of NSStrings which are * the region names. */ + (NSArray*) timeZoneArray { static NSArray *regionsArray = nil; int index, i; char name[80]; FILE *file; id temp_array[24]; NSString *fileName; /* We create the array only when we need it to reduce overhead. */ if (regionsArray != nil) return regionsArray; for (i = 0; i < 24; i++) temp_array[i] = [NSMutableArray array]; fileName = _time_zone_path (REGIONS_FILE, nil); #if defined(__WIN32__) file = fopen([fileName fileSystemRepresentation], "rb"); #else file = fopen([fileName fileSystemRepresentation], "r"); #endif if (file == NULL) [NSException raise: NSInternalInconsistencyException format: @"Failed to open time zone regions array file."]; while (fscanf(file, "%d %s", &index, name) == 2) [temp_array[index] addObject: [NSString stringWithCString: name]]; fclose(file); regionsArray = [[NSArray alloc] initWithObjects: temp_array count: 24]; return regionsArray; } /** * Return a timezone for the specified offset from GMT.
* The timezone returned does not use daylight savings time. * The actual timezone returned has an offset rounded to the nearest * minute.
* Time zones with an offset of more than +/- 18 hours are disallowed, * and nil is returned. */ + (NSTimeZone*) timeZoneForSecondsFromGMT: (int)seconds { NSTimeZone *zone; zone = [[GSAbsTimeZone alloc] initWithOffset: seconds]; return AUTORELEASE(zone); } /** * Returns a timezone for the specified abbrevition. The same abbreviations * are used in different regions so this isn't particularly useful.
* Calls NSTimeZone-abbreviation dictionary an so uses a lot of memory. */ + (NSTimeZone*) timeZoneWithAbbreviation: (NSString*)abbreviation { NSTimeZone *zone; zone = [self timeZoneWithName: [[self abbreviationDictionary] objectForKey: abbreviation] data: nil]; return zone; } /** * Returns a timezone for the specified name. */ + (NSTimeZone*) timeZoneWithName: (NSString*)aTimeZoneName { NSTimeZone *zone; zone = [defaultPlaceholderTimeZone initWithName: aTimeZoneName data: nil]; return AUTORELEASE(zone); } /** * Returns a timezone for aTimeZoneName, created from the supplied * time zone data. Data must be in TZ format as per the Olson database. */ + (NSTimeZone*) timeZoneWithName: (NSString*)name data: (NSData*)data { NSTimeZone *zone; zone = [defaultPlaceholderTimeZone initWithName: name data: data]; return AUTORELEASE(zone); } /** * Returns the abbreviation for this timezone now. * Invokes -abbreviationForDate: */ - (NSString*) abbreviation { return [self abbreviationForDate: [NSDate date]]; } /** * Returns the abbreviation for this timezone at aDate. This may differ * depending on whether daylight savings time is in effect or not. */ - (NSString*) abbreviationForDate: (NSDate*)aDate { NSTimeZoneDetail *detail; NSString *abbr; detail = [self timeZoneDetailForDate: aDate]; abbr = [detail timeZoneAbbreviation]; return abbr; } /** * Returns the Class for this object */ - (Class) classForCoder { return NSTimeZoneClass; } - (id) copyWithZone: (NSZone*)z { return RETAIN(self); } /** * Returns the data with which the receiver was initialised. */ - (NSData*) data { return nil; } /** * Returns the name of this object. */ - (NSString*) description { return [self name]; } - (void) encodeWithCoder: (NSCoder*)aCoder { [aCoder encodeObject: [self name]]; } - (id) init { return [self initWithName: @"NSLocalTimeZone" data: nil]; } - (id) initWithCoder: (NSCoder*)aDecoder { NSString *name; name = [aDecoder decodeObject]; self = [self initWithName: name data: nil]; return self; } /** * Initialise a timezone with the supplied name. May return a cached * timezone object rather than the newly created one. */ - (id) initWithName: (NSString*)name { return [self initWithName: name data: nil]; } /** * Initialises a time zone object using the supplied data object.
* This method is intended for internal use by the NSTimeZone * class cluster. * Don't use it ... use -initWithName: instead. */ - (id) initWithName: (NSString*)name data: (NSData*)data { [self notImplemented: _cmd]; return nil; } /** * Returns a boolean indicating whether daylight savings time is in * effect now. Invokes -isDaylightSavingTimeForDate: */ - (BOOL) isDaylightSavingTime { return [self isDaylightSavingTimeForDate: [NSDate date]]; } /** * Returns a boolean indicating whether daylight savings time is in * effect for this time zone at aDate. */ - (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate { NSTimeZoneDetail *detail; BOOL isDST; detail = [self timeZoneDetailForDate: aDate]; isDST = [detail isDaylightSavingTimeZone]; return isDST; } - (BOOL) isEqual: (id)other { if (other == self) return YES; if ([other isKindOfClass: NSTimeZoneClass] == NO) return NO; return [self isEqualToTimeZone: other]; } /** * Returns TRUE if the time zones have the same name. */ - (BOOL) isEqualToTimeZone: (NSTimeZone*)aTimeZone { if (aTimeZone == self) return YES; if ([[self name] isEqual: [aTimeZone name]] == NO) return NO; if (([self data] == nil && [aTimeZone data] == nil) || [[self name] isEqual: [aTimeZone name]] == YES) return YES; return NO; } /** * Returns the name of the timezone */ - (NSString*) name { return [self subclassResponsibility: _cmd]; } - (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder { if ([aCoder isByref] == NO) { return self; } return [super replacementObjectForPortCoder: aCoder]; } /** * Returns the number of seconds by which the receiver differs * from Greenwich Mean Time at the current date and time.
* Invokes -secondsFromGMTForDate: */ - (int) secondsFromGMT { return [self secondsFromGMTForDate: [NSDate date]]; } /** * Returns the number of seconds by which the receiver differs * from Greenwich Mean Time at the date aDate.
* If the time zone uses dayl;ight savings time, the returned value * will vary at different times of year. */ - (int) secondsFromGMTForDate: (NSDate*)aDate { NSTimeZoneDetail *detail; int offset; detail = [self timeZoneDetailForDate: aDate]; offset = [detail timeZoneSecondsFromGMT]; return offset; } /** * DEPRECATED: see NSTimeZoneDetail */ - (NSArray*) timeZoneDetailArray { return [self subclassResponsibility: _cmd]; } /** * DEPRECATED: see NSTimeZoneDetail */ - (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date { return [self subclassResponsibility: _cmd]; } /** * Returns the name of this timezone. */ - (NSString*) timeZoneName { return [self name]; } @end /** * This class serves no useful purpose in GNUstep, and is provided * solely for backward compatibility with the OpenStep spec. It is * missing entirely from MacOS-X. */ @implementation NSTimeZoneDetail - (NSString*) description { return [NSString stringWithFormat: @"%@(%@, %s%d)", [self name], [self timeZoneAbbreviation], ([self isDaylightSavingTimeZone]? "IS_DST, ": ""), [self timeZoneSecondsFromGMT]]; } /** * DEPRECATED: Class is no longer used. */ - (BOOL) isDaylightSavingTimeZone { [self subclassResponsibility: _cmd]; return NO; } /** * DEPRECATED: Class is no longer used. */ - (NSString*) timeZoneAbbreviation { return [self subclassResponsibility: _cmd]; } /** * DEPRECATED: Class is no longer used. */ - (int) timeZoneSecondsFromGMT { [self subclassResponsibility: _cmd]; return 0; } @end @implementation NSTimeZone (Private) /** * Returns the path to the named zone info file. */ + (NSString*) getTimeZoneFile: (NSString *)name { NSString *dir = nil; /* Use the system zone info if possible, otherwise, use our installed info. */ if (tzdir && [[NSFileManager defaultManager] fileExistsAtPath: [tzdir stringByAppendingPathComponent: name]] == NO) dir = nil; if (dir == nil) dir= _time_zone_path (ZONES_DIR, nil); return [dir stringByAppendingPathComponent: name]; } @end #ifdef WIN32 /* Timezone information data as stored in the registry */ typedef struct TZI_format { LONG Bias; LONG StandardBias; LONG DaylightBias; SYSTEMTIME StandardDate; SYSTEMTIME DaylightDate; } TZI; static inline unsigned int lastDayOfGregorianMonth(int month, int year) { switch (month) { case 2: if ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0)) return 29; else return 28; case 4: case 6: case 9: case 11: return 30; default: return 31; } } /* IMPORT from NSCalendar date */ void GSBreakTime(NSTimeInterval when, int *year, int *month, int *day, int *hour, int *minute, int *second, int *mil); int dayOfCommonEra(NSTimeInterval when); @implementation GSWindowsTimeZone - (NSString*) abbreviationForDate: (NSDate*)aDate { if ([self isDaylightSavingTimeForDate:aDate]) return daylightZoneNameAbbr; return timeZoneNameAbbr; } - (NSData*) data { return 0; } - (void) dealloc { RELEASE(timeZoneName); RELEASE(daylightZoneName); RELEASE(timeZoneNameAbbr); RELEASE(daylightZoneNameAbbr); [super dealloc]; } - (id) initWithName: (NSString*)name data: (NSData*)data { HKEY regDirKey; BOOL isNT = NO,regFound=NO; /* Open the key in the local machine hive where the time zone data is stored. */ if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones", 0, KEY_READ, ®DirKey)) { isNT=YES; regFound=YES; } else { if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones", 0, KEY_READ, ®DirKey)) { regFound=YES; } } if (regFound) { /* Iterate over all subKeys in the registry to find the right one. Unfortunately name is a localized value. The keys in the registry are unlocalized names. */ CHAR achKey[255]; // buffer for subkey name DWORD cbName; // size of name string CHAR achClass[MAX_PATH] = ""; // buffer for class name DWORD cchClassName = MAX_PATH; // size of class string DWORD cSubKeys=0; // number of subkeys DWORD cbMaxSubKey; // longest subkey size DWORD cchMaxClass; // longest class string DWORD cValues; // number of values for key DWORD cchMaxValue; // longest value name DWORD cbMaxValueData; // longest value data DWORD cbSecurityDescriptor; // size of security descriptor FILETIME ftLastWriteTime; // last write time DWORD i, retCode; BOOL tzFound = NO; /* Get the class name and the value count. */ retCode = RegQueryInfoKey( regDirKey, // key handle achClass, // buffer for class name &cchClassName, // size of class string NULL, // reserved &cSubKeys, // number of subkeys &cbMaxSubKey, // longest subkey size &cchMaxClass, // longest class string &cValues, // number of values for this key &cchMaxValue, // longest value name &cbMaxValueData, // longest value data &cbSecurityDescriptor, // security descriptor &ftLastWriteTime); // last write time if (cSubKeys && (retCode == ERROR_SUCCESS)) { const char *cName = [name cString]; for (i=0; iBias; StandardBias = tzi->StandardBias; DaylightBias = tzi->DaylightBias; StandardDate = tzi->StandardDate; DaylightDate = tzi->DaylightDate; } /* Set the standard name for the time zone. */ if (strlen(standardName)) { int a, b; [timeZoneName release]; timeZoneName = [[NSString stringWithCString:standardName] retain]; /* Abbr generated here is IMHO a bit suspicous but I kept it */ for (a=0,b=0;standardName[a];a++) { if (isupper(standardName[a])) standardName[b++]=standardName[a]; } standardName[b]=0; [timeZoneNameAbbr release]; timeZoneNameAbbr = [[NSString stringWithCString:standardName] retain]; } /* Set the daylight savings name for the time zone. */ if (strlen(daylightName)) { int a,b; [daylightZoneName release]; daylightZoneName = [[NSString stringWithCString:daylightName] retain]; /* Abbr generated here is IMHO a bit suspicous but I kept it */ for (a=0,b=0;daylightName[a];a++) { if (isupper(daylightName[a])) daylightName[b++]=daylightName[a]; } daylightName[b]=0; [daylightZoneNameAbbr release]; daylightZoneNameAbbr = [[NSString stringWithCString:daylightName] retain]; } } RegCloseKey(regKey); } } } } RegCloseKey(regDirKey); } return self; } - (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate { int year, month, day, hour, minute, second, mil; int dow; int daylightdate, count, maxdate; NSTimeInterval when; if (DaylightDate.wMonth == 0) return NO; when = [aDate timeIntervalSinceReferenceDate] - Bias*60; GSBreakTime(when, &year, &month, &day, &hour, &minute, &second, &mil); // Before April or after October is Std if (month < DaylightDate.wMonth || month > StandardDate.wMonth) return NO; // After April and before October is DST if (month > DaylightDate.wMonth && month < StandardDate.wMonth) return YES; dow = dayOfCommonEra(when); dow = dow % 7; if (dow < 0) dow += 7; if (month == DaylightDate.wMonth /* April */) { daylightdate = day - dow + DaylightDate.wDayOfWeek; maxdate = lastDayOfGregorianMonth(DaylightDate.wMonth, year)-7; while (daylightdate > 7) daylightdate-=7; if (daylightdate < 1) daylightdate += 7; count=DaylightDate.wDay; while (count>1 && daylightdate < maxdate) { daylightdate+=7; count--; } if (day > daylightdate) return YES; if (day < daylightdate) return NO; if (hour > DaylightDate.wHour) return YES; if (hour < DaylightDate.wHour) return NO; if (minute > DaylightDate.wMinute) return YES; if (minute < DaylightDate.wMinute) return NO; if (second > DaylightDate.wSecond) return YES; if (second < DaylightDate.wSecond) return NO; if (mil >= DaylightDate.wMilliseconds) return YES; return NO; } if (month == StandardDate.wMonth /* October */) { daylightdate = day - dow + StandardDate.wDayOfWeek; maxdate = lastDayOfGregorianMonth(StandardDate.wMonth, year)-7; while (daylightdate > 7) daylightdate-=7; if (daylightdate < 1) daylightdate += 7; count=StandardDate.wDay; while (count>1 && daylightdate < maxdate) { daylightdate+=7; count--; } if (day > daylightdate) return NO; if (day < daylightdate) return YES; if (hour > StandardDate.wHour) return NO; if (hour < StandardDate.wHour) return YES; if (minute > StandardDate.wMinute) return NO; if (minute < StandardDate.wMinute) return YES; if (second > StandardDate.wSecond) return NO; if (second < StandardDate.wSecond) return YES; if (mil >= StandardDate.wMilliseconds) return NO; return YES; } return NO; // Never reached } - (NSString*) name { return timeZoneName; } - (int) secondsFromGMTForDate: (NSDate*)aDate { if ([self isDaylightSavingTimeForDate:aDate]) return -Bias*60 - DaylightBias*60; return -Bias*60 - StandardBias*60; } - (NSArray*) timeZoneDetailArray { return [NSArray arrayWithObjects: [[[GSTimeZoneDetail alloc] initWithTimeZone:self withAbbrev:timeZoneNameAbbr withOffset:-Bias*60 - StandardBias*60 withDST:NO] autorelease], [[[GSTimeZoneDetail alloc] initWithTimeZone:self withAbbrev:daylightZoneNameAbbr withOffset:-Bias*60 - DaylightBias*60 withDST:YES] autorelease], 0]; } - (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)aDate { GSTimeZoneDetail *detail; int offset; BOOL isDST = [self isDaylightSavingTimeForDate:aDate]; NSString *abbr; if (isDST) { offset = -Bias*60 - DaylightBias*60; abbr = daylightZoneNameAbbr; } else { offset = -Bias*60 - StandardBias*60; abbr = timeZoneNameAbbr; } detail = [GSTimeZoneDetail alloc]; detail = [detail initWithTimeZone: self withAbbrev: abbr withOffset: offset withDST: isDST]; return detail; } - (NSString*) timeZoneName { return timeZoneName; } @end #endif // WIN32 @implementation GSTimeZone /** * Perform a binary search of a transitions table to locate the index * of the transition to use for a particular time interval since 1970.
* We locate the index of the highest transition before the date, or zero * if there is no transition before it. */ static TypeInfo* chop(NSTimeInterval since, GSTimeZone *zone) { gss32 when = (gss32)since; gss32 *trans = zone->trans; unsigned hi = zone->n_trans; unsigned lo = 0; unsigned int i; if (hi == 0 || trans[0] > when) { unsigned n_types = zone->n_types; /* * If the first transition is greater than our date, * we locate the first non-DST transition and use that offset, * or just use the first transition. */ for (i = 0; i < n_types; i++) { if (zone->types[i].isdst == 0) { return &zone->types[i]; } } return &zone->types[0]; } else { for (i = hi/2; hi != lo; i = (hi + lo)/2) { if (when < trans[i]) { hi = i; } else if (when > trans[i]) { lo = ++i; } else { break; } } /* * If we went off the top of the table or the closest transition * was later than our date, we step back to find the last * transition before our date. */ if (i > 0 && (i == zone->n_trans || trans[i] > when)) { i--; } return &zone->types[zone->idxs[i]]; } } static NSTimeZoneDetail* newDetailInZoneForType(GSTimeZone *zone, TypeInfo *type) { GSTimeZoneDetail *detail; detail = [GSTimeZoneDetail alloc]; detail = [detail initWithTimeZone: zone withAbbrev: type->abbreviation withOffset: type->offset withDST: type->isdst]; return detail; } - (NSString*) abbreviationForDate: (NSDate*)aDate { TypeInfo *type = chop([aDate timeIntervalSince1970], self); return type->abbreviation; } - (NSData*) data { return timeZoneData; } - (void) dealloc { RELEASE(timeZoneName); RELEASE(timeZoneData); if (types != 0) { unsigned i; for (i = 0; i < n_types; i++) { RELEASE(types[i].abbreviation); } NSZoneFree(NSDefaultMallocZone(), types); } [super dealloc]; } - (id) initWithName: (NSString*)name data: (NSData*)data { static NSString *fileException = @"GSTimeZoneFileException"; timeZoneName = [name copy]; timeZoneData = [data copy]; NS_DURING { const void *bytes = [timeZoneData bytes]; unsigned length = [timeZoneData length]; void *buf; unsigned pos = 0; unsigned i, charcnt; unsigned char *abbr; struct tzhead *header; if (length < sizeof(struct tzhead)) { [NSException raise: fileException format: @"File is too small"]; } header = (struct tzhead *)(bytes + pos); pos += sizeof(struct tzhead); #ifdef TZ_MAGIC if (memcmp(header->tzh_magic, TZ_MAGIC, strlen(TZ_MAGIC)) != 0) { [NSException raise: fileException format: @"TZ_MAGIC is incorrect"]; } #endif n_trans = GSSwapBigI32ToHost(*(gss32*)header->tzh_timecnt); n_types = GSSwapBigI32ToHost(*(gss32*)header->tzh_typecnt); charcnt = GSSwapBigI32ToHost(*(gss32*)header->tzh_charcnt); i = pos; i += sizeof(gss32)*n_trans; if (i > length) { [NSException raise: fileException format: @"Transitions list is truncated"]; } i += n_trans; if (i > length) { [NSException raise: fileException format: @"Transition indexes are truncated"]; } i += sizeof(struct ttinfo)*n_types; if (i > length) { [NSException raise: fileException format: @"Types list is truncated"]; } if (i + charcnt > length) { [NSException raise: fileException format: @"Abbreviations list is truncated"]; } /* * Now calculate size we need to store the information * for efficient access ... not the same saze as the data * we received. */ i = n_trans * (sizeof(gss32)+1) + n_types * sizeof(TypeInfo); buf = NSZoneMalloc(NSDefaultMallocZone(), i); types = (TypeInfo*)buf; buf += (n_types * sizeof(TypeInfo)); trans = (gss32*)buf; buf += (n_trans * sizeof(gss32)); idxs = (unsigned char*)buf; /* Read in transitions. */ for (i = 0; i < n_trans; i++) { trans[i] = GSSwapBigI32ToHost(*(gss32*)(bytes + pos)); pos += sizeof(gss32); } for (i = 0; i < n_trans; i++) { idxs[i] = *(unsigned char*)(bytes + pos); pos++; } for (i = 0; i < n_types; i++) { struct ttinfo *ptr = (struct ttinfo*)(bytes + pos); types[i].isdst = (ptr->isdst != 0 ? YES : NO); types[i].abbr_idx = ptr->abbr_idx; types[i].offset = decode(ptr->offset); pos += sizeof(struct ttinfo); } abbr = (char*)(bytes + pos); { id abbrevs[charcnt]; memset(abbrevs, '\0', sizeof(id)*charcnt); for (i = 0; i < n_types; i++) { int loc = types[i].abbr_idx; if (abbrevs[loc] == nil) { abbrevs[loc] = [[NSString alloc] initWithCString: abbr + loc]; } else { RETAIN(abbrevs[loc]); } types[i].abbreviation = abbrevs[loc]; } } if (zone_mutex != nil) { [zone_mutex lock]; } [zoneDictionary setObject: self forKey: timeZoneName]; if (zone_mutex != nil) { [zone_mutex unlock]; } } NS_HANDLER { DESTROY(self); NSLog(@"Unable to obtain time zone `%@'... %@", name, localException); if ([localException name] != fileException) { [localException raise]; } } NS_ENDHANDLER return self; } - (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate { TypeInfo *type = chop([aDate timeIntervalSince1970], self); return type->isdst; } - (NSString*) name { return timeZoneName; } - (int) secondsFromGMTForDate: (NSDate*)aDate { TypeInfo *type = chop([aDate timeIntervalSince1970], self); return type->offset; } - (NSArray*) timeZoneDetailArray { NSTimeZoneDetail *details[n_types]; unsigned i; NSArray *array; for (i = 0; i < n_types; i++) { details[i] = newDetailInZoneForType(self, &types[i]); } array = [NSArray arrayWithObjects: details count: n_types]; for (i = 0; i < n_types; i++) { RELEASE(details[i]); } return array; } - (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)aDate { TypeInfo *type; NSTimeZoneDetail *detail; type = chop([aDate timeIntervalSince1970], self); detail = newDetailInZoneForType(self, type); return AUTORELEASE(detail); } - (NSString*) timeZoneName { return timeZoneName; } @end