36
41
eventually have to change the implementation to prevent the year
39
The local time zone can be specified with the user defaults
40
database, the GNUSTEP_TZ environment variable, the file LOCAL_TIME_FILE,
41
the TZ environment variable, or the fallback time zone (which is UTC),
44
The local time zone can be specified with:
45
1) the user defaults database
46
2) the GNUSTEP_TZ environment variable
47
3) the file LOCAL_TIME_FILE in _time_zone_path()
48
4) the TZ environment variable
49
5) TZDEFAULT defined in tzfile.h on platforms which have it
50
6) tzset() & tznam[] for platforms which have it
51
7) Windows registry, for Win32 systems
52
8) or the fallback time zone (which is UTC)
42
53
with the ones listed first having precedence.
44
55
Any time zone must be a file name in ZONES_DIR.
57
Files & File System Heirarchy info:
58
===================================
60
Default place for the NSTimeZone directory is _time_zone_path():
61
{$(GNUSTEP_SYSTEM_ROOT)Libary/Libraries/Resources/TIME_ZONE_DIR}
63
LOCAL_TIME_FILE is a text file with the name of the time zone file.
65
ZONES_DIR is a sub-directory under TIME_ZONE_DIR
67
(dir) ../System/Library/Libraries/Resources/..
69
(file) localtime {text; time zone eg Australia/Perth}
72
Note that full zone info is required, especially the various "GMT"
73
files which are created especially for OPENSTEP compatibility.
74
Zone info comes from the Olson time database.
46
76
FIXME?: use leap seconds? */
49
#include <base/preface.h>
79
#include "GNUstepBase/preface.h"
80
#include "GNUstepBase/GSLock.h"
50
81
#include <limits.h>
52
83
#include <stdlib.h>
53
84
#include <string.h>
54
#include <Foundation/NSArray.h>
55
#include <Foundation/NSCoder.h>
56
#include <Foundation/NSData.h>
57
#include <Foundation/NSDate.h>
58
#include <Foundation/NSDictionary.h>
59
#include <Foundation/NSException.h>
60
#include <Foundation/NSLock.h>
61
#include <Foundation/NSObject.h>
62
#include <Foundation/NSProcessInfo.h>
63
#include <Foundation/NSString.h>
64
#include <Foundation/NSUserDefaults.h>
65
#include <Foundation/NSUtilities.h>
66
#include <Foundation/NSZone.h>
67
#include <Foundation/NSBundle.h>
68
#include <Foundation/NSMapTable.h>
69
#include <Foundation/NSThread.h>
70
#include <Foundation/NSNotification.h>
71
#include <Foundation/NSPortCoder.h>
72
#include <Foundation/NSTimeZone.h>
73
#include <Foundation/NSDebug.h>
86
#include "Foundation/NSArray.h"
87
#include "Foundation/NSCoder.h"
88
#include "Foundation/NSData.h"
89
#include "Foundation/NSDate.h"
90
#include "Foundation/NSDictionary.h"
91
#include "Foundation/NSException.h"
92
#include "Foundation/NSFileManager.h"
93
#include "Foundation/NSLock.h"
94
#include "Foundation/NSObject.h"
95
#include "Foundation/NSProcessInfo.h"
96
#include "Foundation/NSString.h"
97
#include "Foundation/NSUserDefaults.h"
98
#include "Foundation/NSUtilities.h"
99
#include "Foundation/NSZone.h"
100
#include "Foundation/NSBundle.h"
101
#include "Foundation/NSMapTable.h"
102
#include "Foundation/NSThread.h"
103
#include "Foundation/NSNotification.h"
104
#include "Foundation/NSPortCoder.h"
105
#include "Foundation/NSTimeZone.h"
106
#include "Foundation/NSByteOrder.h"
107
#include "Foundation/NSDebug.h"
108
#include "GNUstepBase/GSCategories.h"
109
#include "GSConfig.h"
115
#include "nstzfile.h"
79
118
/* Key for local time zone in user defaults. */
80
119
#define LOCALDBKEY @"Local Time Zone"
82
/* Directory that contains the time zone data. */
121
/* Directory that contains the time zone data.
122
Expected in Resources directory for library bundle. */
83
123
#define TIME_ZONE_DIR @"NSTimeZones"
85
/* Location of time zone abbreviation dictionary. It is a text file
125
/* Name of time zone abbreviation (plist) dictionary. */
126
#define ABBREV_DICT @"abbreviations"
128
/* Name of time zone abbreviation map. It is a text file
86
129
with each line comprised of the abbreviation, a whitespace, and the
87
130
name. Neither the abbreviation nor the name can contain
88
131
whitespace, and each line must not be longer than 80 characters. */
89
#define ABBREV_DICT @"abbreviations"
132
#define ABBREV_MAP @"abbreviations"
91
134
/* File holding regions grouped by latitude. It is a text file with
92
135
each line comprised of the latitude region, whitespace, and the
345
322
return self; // placeholders never get released.
348
- (id) objectAtIndex: (unsigned)index
350
[NSException raise: NSInternalInconsistencyException
351
format: @"attempt to use uninitialised time zone"];
357
327
return; // placeholders never get deallocated.
360
- (id) initWithName: (NSString*)name
330
- (id) initWithName: (NSString*)name data: (NSData*)data
362
332
NSTimeZone *zone;
333
unsigned length = [name length];
365
* Quick return if it's the local timezone.
367
if ([name isEqual: @"NSLocalTimeZone"])
369
return [self initWithName: name data: nil];
337
NSLog(@"Disallowed null time zone name");
340
if (length == 15 && [name isEqual: @"NSLocalTimeZone"])
342
zone = RETAIN(localTimeZone);
373
348
* Return a cached time zone if possible.
349
* NB. if data of cached zone does not match new data ... don't use cache
375
351
if (zone_mutex != nil)
377
353
[zone_mutex lock];
379
355
zone = [zoneDictionary objectForKey: name];
356
if (data != nil && [data isEqual: [zone data]] == NO)
380
360
IF_NO_GC(RETAIN(zone));
381
361
if (zone_mutex != nil)
383
363
[zone_mutex unlock];
391
* Now return an absolute time zone or load one from file.
393
if ([name hasPrefix: @"NSAbsoluteTimeZone:"] == YES)
395
int i = [[name substringFromIndex: 19] intValue];
397
zone = [[NSConcreteAbsoluteTimeZone alloc] initWithOffset: i];
405
length = [name length];
408
NSLog(@"Disallowed null time zone name");
413
const char *str = [name lossyCString];
415
/* Make sure that only time zone files are accessed.
416
FIXME: Make this more robust. */
417
if ((str)[0] == '/' || strchr(str, '.') != NULL)
419
NSLog(@"Disallowed time zone name `%@'.", name);
424
fileName = [NSTimeZoneClass getTimeZoneFile: name];
427
NSLog(@"Unknown time zone name `%@'.", name);
430
data = [NSData dataWithContentsOfFile: fileName];
431
zone = [self initWithName: name data: data];
436
- (id) initWithName: (NSString*)name data: (NSData*)data
440
if ([name isEqual: @"NSLocalTimeZone"])
442
zone = RETAIN(localTimeZone);
444
else if ([name hasPrefix: @"NSAbsoluteTimeZone:"] == YES)
446
int i = [[name substringFromIndex: 19] intValue];
448
zone = [[NSConcreteAbsoluteTimeZone alloc] initWithOffset: i];
452
static NSString *fileException = @"fileException";
453
static NSString *errMess = @"File read error in NSTimeZone.";
458
const void *bytes = [data bytes];
459
unsigned length = [data length];
463
int i, n_trans, n_types, names_size;
468
struct tzhead *header;
469
struct ttinfo *types; // Temporary array for details
471
if (length < sizeof(struct tzhead))
473
[NSException raise: fileException format: errMess];
475
header = (struct tzhead *)(bytes + pos);
476
pos += sizeof(struct tzhead);
478
n_trans = decode(header->tzh_timecnt);
479
n_types = decode(header->tzh_typecnt);
480
names_size = decode(header->tzh_charcnt);
482
/* Read in transitions. */
483
trans = (char*)(bytes + pos);
485
type_idxs = (char*)(bytes + pos);
489
[NSException raise: fileException format: errMess];
491
transArray = [NSMutableArray arrayWithCapacity: n_trans];
492
for (i = 0; i < n_trans; i++)
495
addObject: [[NSInternalTimeTransition alloc]
496
initWithTime: decode(trans+(i*4))
497
withIndex: type_idxs[i]]];
500
/* Read in time zone details. */
501
types = (struct ttinfo*)(bytes + pos);
502
pos += n_types*sizeof(struct ttinfo);
505
[NSException raise: fileException format: errMess];
508
/* Read in time zone abbreviation strings. */
509
zone_abbrevs = (char*)(bytes + pos);
513
[NSException raise: fileException format: errMess];
515
abbrevsArray = NSZoneMalloc(NSDefaultMallocZone(),
516
sizeof(id)*names_size);
517
memset(abbrevsArray, '\0', sizeof(id)*names_size);
518
for (i = 0; i < n_types; i++)
520
struct ttinfo *inf = types + i;
521
int loc = inf->abbr_idx;
523
if (abbrevsArray[loc] == nil)
526
= [NSString stringWithCString: zone_abbrevs+loc];
530
zone = [NSConcreteTimeZone alloc];
532
/* Create time zone details. */
533
detailsArray = [NSMutableArray arrayWithCapacity: n_types];
534
for (i = 0; i < n_types; i++)
536
NSConcreteTimeZoneDetail *detail;
537
struct ttinfo *inf = types + i;
538
int off = decode(inf->offset);
539
BOOL dst = (inf->isdst > 0) ? YES : NO;
540
int idx = inf->abbr_idx;
541
id abr = abbrevsArray[idx];
543
detail = [[NSConcreteTimeZoneDetail alloc]
544
initWithTimeZone: zone
548
[detailsArray addObject: detail];
551
NSZoneFree(NSDefaultMallocZone(), abbrevsArray);
553
zone = [(id)zone initWithName: name
554
withTransitions: transArray
555
withDetails: detailsArray];
556
if (zone_mutex != nil)
560
[zoneDictionary setObject: zone forKey: (NSString*)[zone name]];
561
if (zone_mutex != nil)
569
if ([localException name] != fileException)
570
[localException raise];
571
NSLog(@"Unable to obtain time zone `%@'.", name);
371
if (length == 8 && [name hasPrefix: @"GMT"] == YES
372
&& ((c = [name characterAtIndex: 3]) == '+' || c == '-'))
374
c = [name characterAtIndex: 4];
375
if (c >= '0' && c <= '9')
378
c = [name characterAtIndex: 5];
379
if (c >= '0' && c <= '9')
381
i = i * 10 + (c - '0');
382
c = [name characterAtIndex: 6];
383
if (c >= '0' && c <= '9')
385
i = i * 6 + (c - '0');
386
c = [name characterAtIndex: 7];
387
if (c >= '0' && c <= '9')
389
i = i * 10 + (c - '0');
390
zone = [[GSAbsTimeZone alloc] initWithOffset: i*60];
397
if (zone == nil && length > 19
398
&& [name hasPrefix: @"NSAbsoluteTimeZone:"] == YES)
400
i = [[name substringFromIndex: 19] intValue];
402
zone = [[GSAbsTimeZone alloc] initWithOffset: i];
410
const char *str = [name UTF8String];
412
/* Make sure that only time zone files are accessed.
413
FIXME: Make this more robust. */
414
if ((str)[0] == '/' || strchr(str, '.') != NULL)
416
NSLog(@"Disallowed time zone name `%@'.", name);
420
fileName = [NSTimeZoneClass getTimeZoneFile: name];
421
if (fileName == nil || ![[NSFileManager defaultManager] fileExistsAtPath:fileName])
424
zone = [[GSWindowsTimeZone alloc] initWithName:name data:0];
430
NSLog(@"Unknown time zone name `%@'.", name);
434
data = [NSData dataWithContentsOfFile: fileName];
438
zone = [[GSWindowsTimeZone alloc] initWithName: name data: data];
441
zone = [[GSTimeZone alloc] initWithName: name data: data];
760
@implementation GSAbsTimeZoneDetail
762
- (NSString*) abbreviation
767
- (NSString*) abbreviationForDate: (NSDate*)aDate
778
- (id) initWithTimeZone: (GSAbsTimeZone*)aZone
780
zone = RETAIN(aZone);
784
- (BOOL) isDaylightSavingTimeZone
789
- (BOOL) isDaylightSavingTimeZoneForDate: (NSDate*)aDate
799
- (NSString*) timeZoneAbbreviation
804
- (NSArray*) timeZoneDetailArray
806
return [zone timeZoneDetailArray];
809
- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date
814
- (int) timeZoneSecondsFromGMT
819
- (int) timeZoneSecondsFromGMTForDate: (NSDate*)aDate
829
* The local time zone is obtained from, in order of preference:<br/ >
830
* 1) the user defaults database: NSGlobalDomain "Local Time Zone"<br/ >
831
* 2) the GNUSTEP_TZ environment variable<br/ >
832
* 3) the file "localtime" in System/Library/Libraries/Resources/NSTimeZone<br/ >
833
* 4) the TZ environment variable<br/ >
834
* 5) The system zone settings (typically in /etc/localtime)<br/ >
835
* 6) tzset and tznam on platforms which have it<br/ >
836
* 7) Windows registry, on Win32 systems<br/ >
837
* 8) or the fallback time zone (which is UTC)<br/ >
839
* <p>If the GNUstep time zone datafiles become too out of date, one
840
* can download an updated database from <uref
841
* url="ftp://elsie.nci.nih.gov/pub/">ftp://elsie.nci.nih.gov/pub/</uref>
842
* and compile it as specified in the README file in the
843
* NSTimeZones directory.
845
* <p>Time zone names in NSDates should be GMT, MET etc. not
846
* Europe/Berlin, America/Washington etc.
848
* <p>The problem with this is that various time zones may use the
849
* same abbreviation (e.g. Australia/Brisbane and
850
* America/New_York both use EST), and some time zones
851
* may have different rules for daylight saving time even if the
852
* abbreviation and offsets from UTC are the same.
854
* <p>The problems with depending on the OS for providing time zone
855
* info are that time zone names may vary
856
* wildly between OSes (this could be a big problem when
857
* archiving is used between different systems).
859
* <p>Win32: Time zone names read from the registry are different
860
* from other GNUstep installations. Be careful when moving data
861
* between platforms in this case.
855
864
@implementation NSTimeZone
867
* Returns a dictionary containing time zone abbreviations and their
868
* corresponding time zone names. More than one time zone may be associated
869
* with a single abbreviation. In this case, the dictionary contains only
870
* one (usually the most common) time zone name for the abbreviation.
857
872
+ (NSDictionary*) abbreviationDictionary
859
return fake_abbrev_dict;
875
if (abbreviationDictionary != nil)
876
return abbreviationDictionary;
878
path = _time_zone_path (ABBREV_DICT, @"plist");
882
raise: NSInternalInconsistencyException
883
format: @"Failed to open time zone abbreviation dictionary."];
885
abbreviationDictionary =
886
[[NSString stringWithContentsOfFile: path] propertyList];
887
abbreviationDictionary =
888
[abbreviationDictionary makeImmutableCopyOnFail: NO];
889
return RETAIN(abbreviationDictionary);
893
* Returns a dictionary that maps abbreviations to the array
894
* containing all the time zone names that use the abbreviation.
862
896
+ (NSDictionary*) abbreviationMap
898
FILE *file; // For the file containing the abbreviation dictionary
899
char abbrev[80], name[80];
864
902
/* Instead of creating the abbreviation dictionary when the class is
865
903
initialized, we create it when we first need it, since the
866
904
dictionary can be potentially very large, considering that it's
867
905
almost never used. */
869
static NSMutableDictionary *abbreviationDictionary = nil;
870
FILE *file; // For the file containing the abbreviation dictionary
871
char abbrev[80], name[80];
874
if (abbreviationDictionary != nil)
875
return abbreviationDictionary;
906
if (abbreviationMap != nil)
907
return abbreviationMap;
877
909
/* Read dictionary from file. */
878
abbreviationDictionary = [[NSMutableDictionary alloc] init];
879
fileName = [NSTimeZone getAbbreviationFile];
910
abbreviationMap = [[NSMutableDictionary alloc] init];
911
path = _time_zone_path (ABBREV_MAP, nil);
880
912
#if defined(__WIN32__)
881
file = fopen([fileName fileSystemRepresentation], "rb");
913
file = fopen([path fileSystemRepresentation], "rb");
883
file = fopen([fileName fileSystemRepresentation], "r");
915
file = fopen([path fileSystemRepresentation], "r");
885
917
if (file == NULL)
887
919
raise: NSInternalInconsistencyException
888
format: @"Failed to open time zone abbreviation dictionary."];
920
format: @"Failed to open time zone abbreviation map."];
889
921
while (fscanf(file, "%79s %79s", abbrev, name) == 2)
891
923
id a, the_name, the_abbrev;
893
925
the_name = [NSString stringWithCString: name];
894
926
the_abbrev = [NSString stringWithCString: abbrev];
895
a = [abbreviationDictionary objectForKey: the_abbrev];
927
a = [abbreviationMap objectForKey: the_abbrev];
898
a = [[NSMutableArray alloc] init];
899
[abbreviationDictionary setObject: a forKey: the_abbrev];
930
a = AUTORELEASE([NSMutableArray new]);
931
[abbreviationMap setObject: a forKey: the_abbrev];
901
933
[a addObject: the_name];
905
return abbreviationDictionary;
937
/* Special case: Add the system time zone if it doesn't exist in the map */
940
id the_abbrev = [systemTimeZone abbreviation];
941
array = [abbreviationMap objectForKey: the_abbrev];
944
array = AUTORELEASE([NSMutableArray new]);
945
[abbreviationMap setObject: array forKey: the_abbrev];
947
if ([array containsObject: [systemTimeZone timeZoneName]] == NO)
948
[array addObject: [systemTimeZone timeZoneName]];
951
return abbreviationMap;
955
* Returns an array of all known time zone names.
957
+ (NSArray*) knownTimeZoneNames
959
static NSArray *namesArray = nil;
962
NSArray *regionsArray, *array;
964
/* We create the array only when we need it to reduce overhead. */
965
if (namesArray != nil)
968
temp_array = [NSMutableArray array];
969
regionsArray = [self timeZoneArray];
971
for (i = 0; i < [regionsArray count]; i++)
973
array = [regionsArray objectAtIndex:i];
974
[temp_array addObjectsFromArray: array];
977
namesArray = [[NSArray alloc] initWithArray: temp_array];
908
981
+ (id) allocWithZone: (NSZone*)z
1054
1159
systemTimeZone = RETAIN([NSTimeZoneClass timeZoneForSecondsFromGMT: 0]);
1162
* Try to get timezone from user defaults database
1056
1164
localZoneString = [[NSUserDefaults standardUserDefaults]
1057
1165
stringForKey: LOCALDBKEY];
1168
* Try to get timezone from GNUSTEP_TZ environment variable.
1058
1170
if (localZoneString == nil)
1061
* Try to get timezone from GNUSTEP_TZ environment variable.
1063
1172
localZoneString = [[[NSProcessInfo processInfo]
1064
1173
environment] objectForKey: @"GNUSTEP_TZ"];
1176
* Try to get timezone from LOCAL_TIME_FILE.
1066
1178
if (localZoneString == nil)
1069
* Try to get timezone from LOCAL_TIME_FILE.
1073
f = [NSBundle pathForGNUstepResource: LOCAL_TIME_FILE
1075
inDirectory: TIME_ZONE_DIR];
1180
NSString *f = _time_zone_path(LOCAL_TIME_FILE, nil);
1078
1183
localZoneString = [NSString stringWithContentsOfFile: f];
1079
1184
localZoneString = [localZoneString stringByTrimmingSpaces];
1188
* Try to get timezone from standard unix environment variable.
1082
1190
if (localZoneString == nil)
1085
* Try to get timezone from standard unix environment variable.
1087
1192
localZoneString = [[[NSProcessInfo processInfo]
1088
1193
environment] objectForKey: @"TZ"];
1195
if (localZoneString == nil)
1197
/* Get the zone name from the localtime file, assuming the file
1198
is a symlink to the time zone. Getting the actual data (which
1199
is easier) doesn't help, since we won't know the name itself. */
1200
#if defined(HAVE_TZHEAD) && defined(TZDEFAULT)
1201
tzdir = RETAIN([NSString stringWithCString: TZDIR]);
1202
localZoneString = [NSString stringWithCString: TZDEFAULT];
1203
localZoneString = [localZoneString stringByResolvingSymlinksInPath];
1205
NSFileManager *dflt = [NSFileManager defaultManager];
1206
if ([dflt fileExistsAtPath: SYSTEM_TIME_FILE])
1208
localZoneString = SYSTEM_TIME_FILE;
1209
localZoneString = [localZoneString stringByResolvingSymlinksInPath];
1210
/* Guess what tzdir is */
1211
tzdir = [localZoneString stringByDeletingLastPathComponent];
1212
while ([tzdir length] > 2
1213
&& [dflt fileExistsAtPath: [tzdir stringByAppendingPathComponent: @"GMT"]] == NO)
1214
tzdir = [tzdir stringByDeletingLastPathComponent];
1215
if ([tzdir length] > 2)
1219
localZoneString = tzdir = nil;
1223
if (localZoneString != nil && [localZoneString hasPrefix: tzdir])
1225
/* This must be the time zone name */
1226
localZoneString = [[localZoneString mutableCopy] autorelease];
1227
[(NSMutableString *)localZoneString deletePrefix: tzdir];
1228
if ([localZoneString hasPrefix: @"/"])
1230
[(NSMutableString *)localZoneString deletePrefix: @"/"];
1234
localZoneString = nil;
1238
* Try to get timezone from tzset and tzname
1240
if (localZoneString == nil)
1243
if (tzname[0] != NULL && *tzname[0] != '\0')
1244
localZoneString = [NSString stringWithCString: tzname[0]];
1250
* Try to get timezone from windows registry.
1255
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, \
1256
"SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", 0, KEY_READ, ®key))
1261
if (ERROR_SUCCESS==RegQueryValueEx(regkey, "StandardName", 0, &type, buf, &bufsize))
1263
bufsize=strlen(buf);
1264
while (bufsize && isspace(buf[bufsize-1]))
1268
localZoneString = [NSString stringWithCString:buf length:bufsize];
1270
RegCloseKey(regkey);
1090
1275
if (localZoneString != nil)
1277
NSDebugLLog (@"NSTimeZone", @"Using zone %@", localZoneString);
1092
1278
zone = [defaultPlaceholderTimeZone initWithName: localZoneString];
1349
1656
@implementation NSTimeZone (Private)
1352
* When the system becomes multithreaded, we set a flag to say so
1659
* Returns the path to the named zone info file.
1354
+ (void) _becomeThreaded: (NSNotification*)notification
1356
if (zone_mutex == nil)
1358
zone_mutex = [NSRecursiveLock new];
1360
[[NSNotificationCenter defaultCenter]
1361
removeObserver: self
1362
name: NSWillBecomeMultiThreadedNotification
1366
+ (NSString*) getAbbreviationFile
1368
return [NSBundle pathForGNUstepResource: ABBREV_DICT
1370
inDirectory: TIME_ZONE_DIR];
1373
+ (NSString*) getRegionsFile
1375
return [NSBundle pathForGNUstepResource: REGIONS_FILE
1377
inDirectory: TIME_ZONE_DIR];
1380
1661
+ (NSString*) getTimeZoneFile: (NSString *)name
1382
NSString *dir = [NSString stringWithFormat: @"%@/%@", TIME_ZONE_DIR, ZONES_DIR];
1383
NSString *path = [NSBundle pathForGNUstepResource: name
1663
NSString *dir = nil;
1665
/* Use the system zone info if possible, otherwise, use our installed
1667
if (tzdir && [[NSFileManager defaultManager] fileExistsAtPath:
1668
[tzdir stringByAppendingPathComponent: name]] == NO)
1671
dir= _time_zone_path (ZONES_DIR, nil);
1672
return [dir stringByAppendingPathComponent: name];
1679
/* Timezone information data as stored in the registry */
1680
typedef struct TZI_format {
1684
SYSTEMTIME StandardDate;
1685
SYSTEMTIME DaylightDate;
1688
static inline unsigned int
1689
lastDayOfGregorianMonth(int month, int year)
1694
if ((((year % 4) == 0) && ((year % 100) != 0))
1695
|| ((year % 400) == 0))
1707
/* IMPORT from NSCalendar date */
1709
GSBreakTime(NSTimeInterval when, int *year, int *month, int *day,
1710
int *hour, int *minute, int *second, int *mil);
1711
int dayOfCommonEra(NSTimeInterval when);
1713
@implementation GSWindowsTimeZone
1715
- (NSString*) abbreviationForDate: (NSDate*)aDate
1717
if ([self isDaylightSavingTimeForDate:aDate])
1718
return daylightZoneNameAbbr;
1719
return timeZoneNameAbbr;
1729
RELEASE(timeZoneName);
1730
RELEASE(daylightZoneName);
1731
RELEASE(timeZoneNameAbbr);
1732
RELEASE(daylightZoneNameAbbr);
1736
- (id) initWithName: (NSString*)name data: (NSData*)data
1739
BOOL isNT = NO,regFound=NO;
1741
/* Open the key in the local machine hive where the time zone data is stored. */
1742
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones", 0, KEY_READ, ®DirKey))
1749
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones", 0, KEY_READ, ®DirKey))
1757
/* Iterate over all subKeys in the registry to find the right one.
1758
Unfortunately name is a localized value. The keys in the registry are
1759
unlocalized names. */
1760
CHAR achKey[255]; // buffer for subkey name
1761
DWORD cbName; // size of name string
1762
CHAR achClass[MAX_PATH] = ""; // buffer for class name
1763
DWORD cchClassName = MAX_PATH; // size of class string
1764
DWORD cSubKeys=0; // number of subkeys
1765
DWORD cbMaxSubKey; // longest subkey size
1766
DWORD cchMaxClass; // longest class string
1767
DWORD cValues; // number of values for key
1768
DWORD cchMaxValue; // longest value name
1769
DWORD cbMaxValueData; // longest value data
1770
DWORD cbSecurityDescriptor; // size of security descriptor
1771
FILETIME ftLastWriteTime; // last write time
1776
/* Get the class name and the value count. */
1777
retCode = RegQueryInfoKey(
1778
regDirKey, // key handle
1779
achClass, // buffer for class name
1780
&cchClassName, // size of class string
1782
&cSubKeys, // number of subkeys
1783
&cbMaxSubKey, // longest subkey size
1784
&cchMaxClass, // longest class string
1785
&cValues, // number of values for this key
1786
&cchMaxValue, // longest value name
1787
&cbMaxValueData, // longest value data
1788
&cbSecurityDescriptor, // security descriptor
1789
&ftLastWriteTime); // last write time
1791
if (cSubKeys && (retCode == ERROR_SUCCESS))
1793
const char *cName = [name cString];
1795
for (i=0; i<cSubKeys && !tzFound; i++)
1799
retCode = RegEnumKeyEx(regDirKey, i, achKey, &cbName, NULL, NULL, NULL, &ftLastWriteTime);
1800
if (retCode == ERROR_SUCCESS)
1802
char keyBuffer[16384];
1806
sprintf(keyBuffer,"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\%s",achKey);
1808
sprintf(keyBuffer,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\%s",achKey);
1810
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyBuffer, 0, KEY_READ, ®Key))
1813
char standardName[256];
1814
char daylightName[256];
1818
/* check standardname */
1819
standardName[0]='\0';
1820
bufsize=sizeof(buf);
1821
if (ERROR_SUCCESS==RegQueryValueEx(regKey, "Std", 0, &type, buf, &bufsize))
1823
strcpy(standardName,buf);
1824
if (strcmp(standardName,cName) == 0)
1828
/* check daylightname */
1829
daylightName[0]='\0';
1830
bufsize=sizeof(buf);
1831
if (ERROR_SUCCESS==RegQueryValueEx(regKey, "Dlt", 0, &type, buf, &bufsize))
1833
strcpy(daylightName,buf);
1834
if (strcmp(daylightName,cName) == 0)
1840
/* Read in the time zone data */
1841
bufsize=sizeof(buf);
1842
if (ERROR_SUCCESS==RegQueryValueEx(regKey, "TZI", 0, &type, buf, &bufsize))
1844
TZI *tzi = (void*)buf;
1846
StandardBias = tzi->StandardBias;
1847
DaylightBias = tzi->DaylightBias;
1848
StandardDate = tzi->StandardDate;
1849
DaylightDate = tzi->DaylightDate;
1852
/* Set the standard name for the time zone. */
1853
if (strlen(standardName))
1856
[timeZoneName release];
1857
timeZoneName = [[NSString stringWithCString:standardName] retain];
1859
/* Abbr generated here is IMHO a bit suspicous but I kept it */
1860
for (a=0,b=0;standardName[a];a++)
1862
if (isupper(standardName[a]))
1863
standardName[b++]=standardName[a];
1866
[timeZoneNameAbbr release];
1867
timeZoneNameAbbr = [[NSString stringWithCString:standardName] retain];
1870
/* Set the daylight savings name for the time zone. */
1871
if (strlen(daylightName))
1874
[daylightZoneName release];
1875
daylightZoneName = [[NSString stringWithCString:daylightName] retain];
1877
/* Abbr generated here is IMHO a bit suspicous but I kept it */
1878
for (a=0,b=0;daylightName[a];a++)
1880
if (isupper(daylightName[a]))
1881
daylightName[b++]=daylightName[a];
1884
[daylightZoneNameAbbr release];
1885
daylightZoneNameAbbr = [[NSString stringWithCString:daylightName] retain];
1888
RegCloseKey(regKey);
1893
RegCloseKey(regDirKey);
1899
- (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate
1901
int year, month, day, hour, minute, second, mil;
1903
int daylightdate, count, maxdate;
1904
NSTimeInterval when;
1906
if (DaylightDate.wMonth == 0)
1909
when = [aDate timeIntervalSinceReferenceDate] - Bias*60;
1911
GSBreakTime(when, &year, &month, &day, &hour, &minute, &second, &mil);
1913
// Before April or after October is Std
1914
if (month < DaylightDate.wMonth || month > StandardDate.wMonth)
1916
// After April and before October is DST
1917
if (month > DaylightDate.wMonth && month < StandardDate.wMonth)
1919
dow = dayOfCommonEra(when);
1925
if (month == DaylightDate.wMonth /* April */)
1927
daylightdate = day - dow + DaylightDate.wDayOfWeek;
1928
maxdate = lastDayOfGregorianMonth(DaylightDate.wMonth, year)-7;
1929
while (daylightdate > 7)
1931
if (daylightdate < 1)
1933
count=DaylightDate.wDay;
1934
while (count>1 && daylightdate < maxdate)
1939
if (day > daylightdate)
1941
if (day < daylightdate)
1943
if (hour > DaylightDate.wHour)
1945
if (hour < DaylightDate.wHour)
1947
if (minute > DaylightDate.wMinute)
1949
if (minute < DaylightDate.wMinute)
1951
if (second > DaylightDate.wSecond)
1953
if (second < DaylightDate.wSecond)
1955
if (mil >= DaylightDate.wMilliseconds)
1959
if (month == StandardDate.wMonth /* October */)
1961
daylightdate = day - dow + StandardDate.wDayOfWeek;
1962
maxdate = lastDayOfGregorianMonth(StandardDate.wMonth, year)-7;
1963
while (daylightdate > 7)
1965
if (daylightdate < 1)
1967
count=StandardDate.wDay;
1968
while (count>1 && daylightdate < maxdate)
1973
if (day > daylightdate)
1975
if (day < daylightdate)
1977
if (hour > StandardDate.wHour)
1979
if (hour < StandardDate.wHour)
1981
if (minute > StandardDate.wMinute)
1983
if (minute < StandardDate.wMinute)
1985
if (second > StandardDate.wSecond)
1987
if (second < StandardDate.wSecond)
1989
if (mil >= StandardDate.wMilliseconds)
1993
return NO; // Never reached
1998
return timeZoneName;
2001
- (int) secondsFromGMTForDate: (NSDate*)aDate
2003
if ([self isDaylightSavingTimeForDate:aDate])
2004
return -Bias*60 - DaylightBias*60;
2005
return -Bias*60 - StandardBias*60;
2008
- (NSArray*) timeZoneDetailArray
2010
return [NSArray arrayWithObjects:
2011
[[[GSTimeZoneDetail alloc] initWithTimeZone:self
2012
withAbbrev:timeZoneNameAbbr
2013
withOffset:-Bias*60 - StandardBias*60
2014
withDST:NO] autorelease],
2015
[[[GSTimeZoneDetail alloc] initWithTimeZone:self
2016
withAbbrev:daylightZoneNameAbbr
2017
withOffset:-Bias*60 - DaylightBias*60
2018
withDST:YES] autorelease], 0];
2021
- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)aDate
2023
GSTimeZoneDetail *detail;
2025
BOOL isDST = [self isDaylightSavingTimeForDate:aDate];
2030
offset = -Bias*60 - DaylightBias*60;
2031
abbr = daylightZoneNameAbbr;
2035
offset = -Bias*60 - StandardBias*60;
2036
abbr = timeZoneNameAbbr;
2038
detail = [GSTimeZoneDetail alloc];
2039
detail = [detail initWithTimeZone: self
2046
- (NSString*) timeZoneName
2048
return timeZoneName;
2054
@implementation GSTimeZone
2057
* Perform a binary search of a transitions table to locate the index
2058
* of the transition to use for a particular time interval since 1970.<br />
2059
* We locate the index of the highest transition before the date, or zero
2060
* if there is no transition before it.
2063
chop(NSTimeInterval since, GSTimeZone *zone)
2065
gss32 when = (gss32)since;
2066
gss32 *trans = zone->trans;
2067
unsigned hi = zone->n_trans;
2071
if (hi == 0 || trans[0] > when)
2073
unsigned n_types = zone->n_types;
2076
* If the first transition is greater than our date,
2077
* we locate the first non-DST transition and use that offset,
2078
* or just use the first transition.
2080
for (i = 0; i < n_types; i++)
2082
if (zone->types[i].isdst == 0)
2084
return &zone->types[i];
2087
return &zone->types[0];
2091
for (i = hi/2; hi != lo; i = (hi + lo)/2)
2093
if (when < trans[i])
2097
else if (when > trans[i])
2107
* If we went off the top of the table or the closest transition
2108
* was later than our date, we step back to find the last
2109
* transition before our date.
2111
if (i > 0 && (i == zone->n_trans || trans[i] > when))
2115
return &zone->types[zone->idxs[i]];
2119
static NSTimeZoneDetail*
2120
newDetailInZoneForType(GSTimeZone *zone, TypeInfo *type)
2122
GSTimeZoneDetail *detail;
2124
detail = [GSTimeZoneDetail alloc];
2125
detail = [detail initWithTimeZone: zone
2126
withAbbrev: type->abbreviation
2127
withOffset: type->offset
2128
withDST: type->isdst];
2132
- (NSString*) abbreviationForDate: (NSDate*)aDate
2134
TypeInfo *type = chop([aDate timeIntervalSince1970], self);
2136
return type->abbreviation;
2141
return timeZoneData;
2146
RELEASE(timeZoneName);
2147
RELEASE(timeZoneData);
2152
for (i = 0; i < n_types; i++)
2154
RELEASE(types[i].abbreviation);
2156
NSZoneFree(NSDefaultMallocZone(), types);
2161
- (id) initWithName: (NSString*)name data: (NSData*)data
2163
static NSString *fileException = @"GSTimeZoneFileException";
2165
timeZoneName = [name copy];
2166
timeZoneData = [data copy];
2169
const void *bytes = [timeZoneData bytes];
2170
unsigned length = [timeZoneData length];
2173
unsigned i, charcnt;
2174
unsigned char *abbr;
2175
struct tzhead *header;
2177
if (length < sizeof(struct tzhead))
2179
[NSException raise: fileException
2180
format: @"File is too small"];
2182
header = (struct tzhead *)(bytes + pos);
2183
pos += sizeof(struct tzhead);
2185
if (memcmp(header->tzh_magic, TZ_MAGIC, strlen(TZ_MAGIC)) != 0)
2187
[NSException raise: fileException
2188
format: @"TZ_MAGIC is incorrect"];
2191
n_trans = GSSwapBigI32ToHost(*(gss32*)header->tzh_timecnt);
2192
n_types = GSSwapBigI32ToHost(*(gss32*)header->tzh_typecnt);
2193
charcnt = GSSwapBigI32ToHost(*(gss32*)header->tzh_charcnt);
2196
i += sizeof(gss32)*n_trans;
2199
[NSException raise: fileException
2200
format: @"Transitions list is truncated"];
2205
[NSException raise: fileException
2206
format: @"Transition indexes are truncated"];
2208
i += sizeof(struct ttinfo)*n_types;
2211
[NSException raise: fileException
2212
format: @"Types list is truncated"];
2214
if (i + charcnt > length)
2216
[NSException raise: fileException
2217
format: @"Abbreviations list is truncated"];
2221
* Now calculate size we need to store the information
2222
* for efficient access ... not the same saze as the data
2225
i = n_trans * (sizeof(gss32)+1) + n_types * sizeof(TypeInfo);
2226
buf = NSZoneMalloc(NSDefaultMallocZone(), i);
2227
types = (TypeInfo*)buf;
2228
buf += (n_types * sizeof(TypeInfo));
2229
trans = (gss32*)buf;
2230
buf += (n_trans * sizeof(gss32));
2231
idxs = (unsigned char*)buf;
2233
/* Read in transitions. */
2234
for (i = 0; i < n_trans; i++)
2236
trans[i] = GSSwapBigI32ToHost(*(gss32*)(bytes + pos));
2237
pos += sizeof(gss32);
2239
for (i = 0; i < n_trans; i++)
2241
idxs[i] = *(unsigned char*)(bytes + pos);
2244
for (i = 0; i < n_types; i++)
2246
struct ttinfo *ptr = (struct ttinfo*)(bytes + pos);
2248
types[i].isdst = (ptr->isdst != 0 ? YES : NO);
2249
types[i].abbr_idx = ptr->abbr_idx;
2250
types[i].offset = decode(ptr->offset);
2251
pos += sizeof(struct ttinfo);
2253
abbr = (char*)(bytes + pos);
2255
id abbrevs[charcnt];
2257
memset(abbrevs, '\0', sizeof(id)*charcnt);
2258
for (i = 0; i < n_types; i++)
2260
int loc = types[i].abbr_idx;
2262
if (abbrevs[loc] == nil)
2264
abbrevs[loc] = [[NSString alloc] initWithCString: abbr + loc];
2268
RETAIN(abbrevs[loc]);
2270
types[i].abbreviation = abbrevs[loc];
2274
if (zone_mutex != nil)
2278
[zoneDictionary setObject: self forKey: timeZoneName];
2279
if (zone_mutex != nil)
2281
[zone_mutex unlock];
2287
NSLog(@"Unable to obtain time zone `%@'... %@", name, localException);
2288
if ([localException name] != fileException)
2290
[localException raise];
2297
- (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate
2299
TypeInfo *type = chop([aDate timeIntervalSince1970], self);
2306
return timeZoneName;
2309
- (int) secondsFromGMTForDate: (NSDate*)aDate
2311
TypeInfo *type = chop([aDate timeIntervalSince1970], self);
2313
return type->offset;
2316
- (NSArray*) timeZoneDetailArray
2318
NSTimeZoneDetail *details[n_types];
2322
for (i = 0; i < n_types; i++)
2324
details[i] = newDetailInZoneForType(self, &types[i]);
2326
array = [NSArray arrayWithObjects: details count: n_types];
2327
for (i = 0; i < n_types; i++)
2329
RELEASE(details[i]);
2334
- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)aDate
2337
NSTimeZoneDetail *detail;
2339
type = chop([aDate timeIntervalSince1970], self);
2340
detail = newDetailInZoneForType(self, type);
2341
return AUTORELEASE(detail);
2344
- (NSString*) timeZoneName
2346
return timeZoneName;