/* NSTimeZone.m Time zone management. Copyright (C) 1997 Free Software Foundation, Inc. Author: Yoo C. Chung Date: June 1997 mySTEP: Felipe A. Rodriguez Date: April 2005 This file is part of the mySTEP Library and is provided under the terms of the GNU Library General Public License. The local time zone can be specified with the TZ environment variable, the file LOCAL_TIME_FILE or the fallback time zone (which is UTC) with precedence in that order. */ #import #import #import #import #import #import #import #import #import #import #import #import "NSPrivate.h" #include "tzfile.h" #define HOUR_SECS (60*60) #define DAY_SECS (HOUR_SECS*24) // System file that defines local time zone #define LOCAL_TIME_FILE @"localtime" #define POSIX_TZONES @"" // Temporary structure for holding struct ttinfo // time zone details { int offset; // Seconds east of UTC BOOL isdst; // Daylight savings time? char abbr_idx; // Index into time zone abbreviations string }; static NSTimeZone *__localTimeZone; // Local time zone static NSTimeZone *__defaultTimeZone; // App defined default time zone static NSLock *__zone_mutex; // Lock for creating time zones. // Dictionary for time zones. Each time // zone must have a unique name. static NSMutableDictionary *__zoneDictionary = nil; static NSMutableDictionary *__abbreviationDictionary = nil; static NSArray *__knownTimeZoneNames = nil; // Search for time zone files in these dirs // NOTE: this requires some symbolic links // since these names are relative to the virtual root so that -initWithContentsOfFile: can read them NSString *__zonedirs[] = { @"/etc/", // skipped for knownTimeZoneNames @"/usr/share/zoneinfo/", @"/usr/lib/zoneinfo/", @"/usr/local/share/zoneinfo/", @"/usr/local/lib/zoneinfo/", @"/etc/zoneinfo/", @"/usr/local/etc/zoneinfo/", }; static NSString *_getTimeZoneFile(NSString *name) { int i; NSFileManager *fm = [NSFileManager defaultManager]; if([name hasPrefix:@"/"]) return name; // absolute name if([name length] == 0) return @""; // empty name if([[name pathExtension] length] != 0) return @""; // contains a dot for(i = 0; i 350 || n_types > 20) { NSLog(@"Time Zone %@ has probably bad data %@", timeZoneName, data); return nil; } _details = [[NSMutableArray alloc] initWithCapacity:n_types]; for (i = 0; i < n_types; i++) // Read time zone details { time_t off; char isdst; unsigned char abbr_idx; if(offset+6 >= len) [NSException raise:NSGenericException format:@"range error scanning timezone details"]; off = decode(bytes+offset); // 4 bytes GMT offset in seconds isdst = ((char *) bytes)[offset+4]; // is DST flag abbr_idx = ((char *) bytes)[offset+5]; // abbreviation index if(abbr_idx < names_size) [(NSMutableArray *) _details addObject:[[[GSTimeZoneDetail alloc] initWithTimeZone: self withAbbrev: [NSString stringWithCString: &bytes[sizeof(struct tzhead) + (5*n_trans) + (6*n_types) + (unsigned char) abbr_idx]] withOffset:off withDST:(isdst > 0)] autorelease]]; else NSLog(@"invalid abbr_idx %d", abbr_idx); offset += 6; } [__zoneDictionary setObject:self forKey:timeZoneName]; // (replace any conflicting definition!) } return self; } - (void) dealloc { [_data release]; [_name release]; [_transitions release]; [_details release]; [super dealloc]; } - (NSString*) name { return _name; } - (NSData*) data { return _data; } - (NSArray*) _timeZoneDetailArray { return _details; } - (NSArray*) _determineTransitions { struct tzhead header; const char *bytes; unsigned int len; NSData *data = _openTimeZoneFile(_name); if (data && (bytes = [data bytes]) && (len = [data length]) > sizeof(struct tzhead) && memcpy(&header, bytes, sizeof(struct tzhead))) { unsigned int n_trans = decode(header.tzh_timecnt); char *trans; char *type_idxs; int i, offset = sizeof(struct tzhead); #if 0 fprintf(stderr, "ntrans=%d\n", n_trans); #endif if (bytes+offset+((4*n_trans)+n_trans) > bytes+len) [NSException raise:NSGenericException format:@"range error in timezone transitions"]; _transitions = [[NSMutableArray alloc] initWithCapacity: n_trans]; trans = objc_malloc(4 * n_trans); type_idxs = objc_malloc(n_trans); memcpy(trans, bytes+offset, (i = (4*n_trans))); // copy to adapt alignment (bytes+offset may be unaligned) memcpy(type_idxs, bytes+offset+i, (n_trans)); [data release]; for (i = 0; i < n_trans; i++) { GSTimeTransition *t=[[GSTimeTransition alloc] initWithTime: decode(trans+(i*4)) withIndex: type_idxs[i]]; [(NSMutableArray *) _transitions addObject:t]; [t release]; } objc_free(trans); objc_free(type_idxs); } return _transitions; } - (GSTimeZoneDetail*) _timeZoneDetailForDate:(NSDate*)date { unsigned index, count; int the_time = (int)[date timeIntervalSince1970]; if (!_transitions && ![self _determineTransitions]) return nil; count = [_transitions count]; if (count == 0 || the_time < [[_transitions objectAtIndex: 0] transTime]) { // Either DATE is before any unsigned detail_count; // transitions or there is no // transition. Return the first detail_count = [_details count]; // non-DST type, or the first index = 0; // one if they are all DST. while (index < detail_count && [[_details objectAtIndex: index] isDaylightSavingTime]) index++; if (index == detail_count) index = 0; } // Find the first transition else // after DATE, and then pick { // the type of the transition for (index = 1; index < count; index++) // before it. if (the_time < [[_transitions objectAtIndex: index] transTime]) break; index = [[_transitions objectAtIndex: index-1] detailIndex]; } return [_details objectAtIndex: index]; } @end /* GSConcreteTimeZone */ @interface GSConcreteAbsoluteTimeZone : NSTimeZone { NSString *_name; id _detail; int _offset; // Offset from UTC in seconds. } - (id) _initWithOffset:(int)anOffset; @end @implementation GSConcreteAbsoluteTimeZone - (id) _initWithOffset:(int)anOffset { if((self=[super init])) { _name = [[NSString stringWithFormat:@"GMT%+d", anOffset] retain]; _offset = anOffset; _detail = [[GSTimeZoneDetail alloc] initWithTimeZone:self withAbbrev:_name withOffset:_offset withDST:NO]; } return self; } - (void) dealloc { [_name release]; [_detail release]; [super dealloc]; } - (id) initWithCoder:(NSCoder*)aDecoder { if([aDecoder allowsKeyedCoding]) { NSLog(@"data=%@", [aDecoder decodeObjectForKey:@"NS.data"]); } else { [aDecoder decodeValueOfObjCType: @encode(id) at: &_name]; } return [self _initWithOffset:[_name intValue]]; } - (NSString*) name { return _name; } - (NSData*) data { return nil; } // we don't know... - (GSTimeZoneDetail*) _timeZoneDetailForDate:(NSDate*)date { return _detail; } @end /* GSConcreteAbsoluteTimeZone */ @implementation NSTimeZone + (void) initialize { if (!__zoneDictionary) { // should read zone directory paths from Info.plist __zoneDictionary = [[NSMutableDictionary alloc] init]; __zone_mutex = [NSLock new]; [self systemTimeZone]; } } + (NSTimeZone*) localTimeZone { return __localTimeZone; } + (NSTimeZone*) defaultTimeZone { return __defaultTimeZone ? __defaultTimeZone : [self systemTimeZone]; } + (NSTimeZone*) systemTimeZone { if (!__localTimeZone) { NSProcessInfo *pi = [NSProcessInfo processInfo]; id localZoneString = [[pi environment] objectForKey: @"TZ"]; if ([localZoneString length] > 0) __localTimeZone = [NSTimeZone timeZoneWithName: localZoneString]; if (__localTimeZone == nil) __localTimeZone = [NSTimeZone timeZoneWithName: LOCAL_TIME_FILE]; if (__localTimeZone == nil) { // Worst case alloc something sure to succeed NSLog(@"Using time zone with absolute offset 0."); __localTimeZone = [NSTimeZone timeZoneForSecondsFromGMT: 0]; } } if (__defaultTimeZone == nil) __defaultTimeZone = [__localTimeZone retain]; return __localTimeZone; } + (NSTimeZone*) timeZoneForSecondsFromGMT:(int)seconds { // We simply return the following because an existing time zone // with the given offset might not always have the same offset // (daylight savings time, change in standard time, etc.). return [[[GSConcreteAbsoluteTimeZone alloc] _initWithOffset:seconds] autorelease]; } + (NSTimeZone*) timeZoneWithAbbreviation:(NSString*)abbreviation { return [self timeZoneWithName:[[self abbreviationDictionary] objectForKey: abbreviation]]; } + (NSTimeZone*) timeZoneWithName:(NSString*)name { return [[[self alloc] initWithName:name] autorelease]; } + (NSTimeZone*) timeZoneWithName:(NSString*)name data:(NSData *) data { return [[[self alloc] initWithName:name data:data] autorelease]; } + (id) allocWithZone:(NSZone *) z { return NSAllocateObject(self == [NSTimeZone class]?[GSConcreteTimeZone class]:(Class) self, 0, z); } - (void) dealloc; { [super dealloc]; } - (id) initWithName:(NSString *)name { NSTimeZone *zone; NSData *data; #if 0 NSLog(@"NSTimeZone: __zone_mutex lock"); #endif [__zone_mutex lock]; if([name isEqual:LOCAL_TIME_FILE]) { // try to substitute real timezone name NSString *f=[[NSFileManager defaultManager] pathContentOfSymbolicLinkAtPath:@"/etc/localtime"]; // fprintf(stderr, "link to %s\n", f?[f cString]:""); if([f hasPrefix:@"/usr/share/zoneinfo/"]) name=[f substringFromIndex:20]; // fprintf(stderr, " name=%s\n", [name cString]); } if(!(zone = [__zoneDictionary objectForKey:name])) { if((data = _openTimeZoneFile(name))) // is just allocated & initialized - not autoreleased! zone = [self initWithName:name data:data]; [data release]; } [__zone_mutex unlock]; return zone; } - (id) initWithName:(NSString *)name data:(NSData *) data; { return SUBCLASS; } - (NSString*) name { return SUBCLASS; } - (NSData*) data { return SUBCLASS; } + (void) setDefaultTimeZone:(NSTimeZone*)aTimeZone { if (aTimeZone == nil) [NSException raise: NSInvalidArgumentException format: @"Can't set nil time zone."]; ASSIGN(__defaultTimeZone, aTimeZone); } + (void) resetSystemTimeZone { if (__defaultTimeZone == __localTimeZone) ASSIGN(__defaultTimeZone, nil); ASSIGN(__localTimeZone, nil); } + (NSDictionary*) abbreviationDictionary { if (__abbreviationDictionary == nil) // inefficient but rarely used { NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSMutableDictionary *d = [[NSMutableDictionary alloc] init]; NSArray *timeZoneNames = [NSTimeZone knownTimeZoneNames]; id e, name; int i; for (i = 0; i < 24; i++) { e = [[timeZoneNames objectAtIndex: i] objectEnumerator]; while ((name = [e nextObject])) { NSTimeZone *zone; if ((zone = [NSTimeZone timeZoneWithName: name])) { id de = [[zone _timeZoneDetailArray] objectEnumerator]; id detail; while ((detail = [de nextObject]) != nil) [d setObject:name forKey:[detail abbreviation]]; } } } if (__abbreviationDictionary == nil) // FIX ME use CAS primitive? __abbreviationDictionary = d; [pool release]; if (__abbreviationDictionary != d) [d release]; } return __abbreviationDictionary; } + (NSArray*) knownTimeZoneNames { if (__knownTimeZoneNames == nil) { NSFileManager *fm = [NSFileManager defaultManager]; NSString *zonedir = nil; int i; for(i = 1; i