1/** Time zone management. -*- Mode: ObjC -*-
2   Copyright (C) 1997-20 11Free Software Foundation, Inc.
3
4   Written by: Yoo C. Chung <wacko@laplace.snu.ac.kr>
5   Date: June 1997
6
7   Rewrite large chunks by: Richard Frith-Macdonald <rfm@gnu.org>
8   Date: September 2002
9
10     This file is part of the GNUstep Base Library.
11
12   This library is free software; you can redistribute it and/or
13   modify it under the terms of the GNU Lesser General Public License
14   as published by the Free Software Foundation; either
15   version 2 of the License, or (at your option) any later version.
16
17   This library is distributed in the hope that it will be useful, but
18   WITHOUT ANY WARRANTY; without even the implied warranty of
19   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20   Lesser General Public License for more details.
21
22   You should have received a copy of the GNU Lesser General Public
23   License along with this library; if not, write to the Free Software
24   Foundation, Inc., 51 Franklin Street, Fifth Floor,
25   Boston, MA 02110 USA.
26
27   <title>NSTimeZone class reference</title>
28   $Date$ $Revision$
29 */
30
31/* Use the system time zones if available. In other cases, use an
32   implementation independent of the system, since POSIX functions for
33   time zones are woefully inadequate for implementing NSTimeZone.
34   Time zone names can be different from system to system, but usually
35   the user has already set up his timezone independant of GNUstep, so we
36   should respect that information.
37
38   We do not use a dictionary for storing time zones, since such a
39   dictionary would be VERY large (~500K).  And we would have to use a
40   complicated object determining whether we're using daylight savings
41   time and such for every entry in the dictionary.  (Though we will
42   eventually have to change the implementation to prevent the year
43   2038 problem.)
44
45   The local time zone can be specified (with the ones listed first
46   having precedence) by:
47
48    1) the user defaults database
49    2) the GNUSTEP_TZ environment variable
50    3) the file LOCAL_TIME_FILE in _time_zone_path()
51
52   Failing that, the time zone may be guessed from system dependent sources
53   such as:
54
55    the windows registry
56    the /etc/timezone file
57    the /etc/sysconfig/clock file
58    TZDEFAULT defined in tzfile.h
59    the TZ environment variable
60    tzset() & tznam[]/daylight
61
62    If all else faile, the fallback time zone (which is GMT/UTC)
63
64   Any time zone must be a file name in ZONES_DIR.
65
66   Files & File System Heirarchy info:
67   ===================================
68
69   Default place for the NSTimeZone directory is _time_zone_path():
70     {$(GNUSTEP_SYSTEM_LIBRARY)/Libraries/gnustep-base/Versions/???/Resources/TIME_ZONE_DIR)
71
72   LOCAL_TIME_FILE is a text file with the name of the time zone file.
73
74   ZONES_DIR is a sub-directory under TIME_ZONE_DIR
75
76   (dir) ../System/Library/Libraries/gnustep-base/Versions/???/Resources/..
77   (dir)     NSTimeZone
78   (file)      localtime {text; time zone eg Australia/Perth}
79   (dir)       zones
80
81   Note that full zone info is required, especially the various "GMT"
82   files which are created especially for OPENSTEP compatibility.
83   Zone info comes from the Olson time database.
84
85   FIXME?: use leap seconds? */
86
87#import "common.h"
88#define	EXPOSE_NSTimeZone_IVARS	1
89#import "GNUstepBase/GSLock.h"
90#include <stdio.h>
91#include <time.h>
92#import "Foundation/NSArray.h"
93#import "Foundation/NSCoder.h"
94#import "Foundation/NSData.h"
95#import "Foundation/NSDate.h"
96#import "Foundation/NSDictionary.h"
97#import "Foundation/NSException.h"
98#import "Foundation/NSFileManager.h"
99#import "Foundation/NSLock.h"
100#import "Foundation/NSProcessInfo.h"
101#import "Foundation/NSUserDefaults.h"
102#import "Foundation/NSMapTable.h"
103#import "Foundation/NSThread.h"
104#import "Foundation/NSNotification.h"
105#import "Foundation/NSPortCoder.h"
106#import "Foundation/NSTimeZone.h"
107#import "Foundation/NSByteOrder.h"
108#import "Foundation/NSLocale.h"
109#import "GNUstepBase/NSString+GNUstepBase.h"
110#import "GSPrivate.h"
111#import "GSPThread.h"
112
113#ifdef HAVE_TZHEAD
114#include <tzfile.h>
115#else
116#include "nstzfile.h"
117#endif
118
119#if defined(HAVE_UNICODE_UCAL_H)
120#define id id_ucal
121#include <unicode/ucal.h>
122#undef id
123#endif
124
125NSString * const NSSystemTimeZoneDidChangeNotification
126  = @"NSSystemTimeZoneDidChangeNotification";
127
128/* Key for local time zone in user defaults. */
129#define LOCALDBKEY @"Local Time Zone"
130
131/* Directory that contains the time zone data.
132   Expected in Resources directory for library bundle. */
133#define TIME_ZONE_DIR @"NSTimeZones"
134
135/* Name of time zone abbreviation (plist) dictionary.  */
136#define ABBREV_DICT @"abbreviations"
137
138/* Name of time zone abbreviation map.  It is a text file
139   with each line comprised of the abbreviation, a whitespace, and the
140   name.  Neither the abbreviation nor the name can contain
141   whitespace, and each line must not be longer than 80 characters. */
142#define ABBREV_MAP @"abbreviations"
143
144/* File holding regions grouped by latitude.  It is a text file with
145   each line comprised of the latitude region, whitespace, and the
146   name.  Neither the abbreviation nor the name can contain
147   whitespace, and each line must not be longer than 80 characters. */
148#define REGIONS_FILE @"regions"
149
150/* Name of the file that contains the name of the local time zone. */
151#define LOCAL_TIME_FILE @"localtime"
152
153/* Directory that contains the actual time zones. */
154#define ZONES_DIR @"zones"
155
156/* Many systems have this file */
157#define SYSTEM_TIME_FILE @"/etc/localtime"
158
159/* If TZDIR told us where the zoneinfo files are, don't append anything else */
160#ifdef TZDIR
161#define POSIX_TZONES     @""
162#else
163#define POSIX_TZONES     @"posix/"
164#endif
165
166#define BUFFER_SIZE 512
167#define WEEK_MILLISECONDS (7.0*24.0*60.0*60.0*1000.0)
168
169#if GS_USE_ICU == 1
170static inline int
171_NSToICUTZDisplayStyle(NSTimeZoneNameStyle style)
172{
173  switch (style)
174    {
175      case NSTimeZoneNameStyleStandard:
176        return UCAL_STANDARD;
177      case NSTimeZoneNameStyleShortStandard:
178        return UCAL_SHORT_STANDARD;
179      case NSTimeZoneNameStyleDaylightSaving:
180        return UCAL_DST;
181      case NSTimeZoneNameStyleShortDaylightSaving:
182        return UCAL_SHORT_DST;
183      default:
184        return -1;
185    }
186}
187
188static inline UCalendar *
189ICUCalendarSetup (NSTimeZone *tz, NSLocale *locale)
190{
191  NSString *tzStr;
192  int32_t tzLen;
193  const char *cLocale;
194  UChar tzName[BUFFER_SIZE];
195  UCalendar *cal;
196  UErrorCode err = U_ZERO_ERROR;
197
198  tzStr = [tz name];
199  if ((tzLen = [tzStr length]) > BUFFER_SIZE)
200    tzLen = BUFFER_SIZE;
201  [tzStr getCharacters: tzName range: NSMakeRange(0, tzLen)];
202  cLocale = [[locale localeIdentifier] UTF8String];
203
204  cal = ucal_open(tzName, tzLen, cLocale, UCAL_TRADITIONAL, &err);
205  if (U_FAILURE(err))
206    return NULL;
207
208  return cal;
209}
210#endif
211
212/* Possible location of system time zone files */
213static NSString *tzdir = nil;
214
215@class GSAbsTimeZone;
216@class GSTimeZoneDetail;
217@class GSAbsTimeZoneDetail;
218
219@class GSPlaceholderTimeZone;
220
221/*
222 * Information for abstract placeholder class.
223 */
224static GSPlaceholderTimeZone	*defaultPlaceholderTimeZone;
225static NSMapTable		*placeholderMap;
226static GSAbsTimeZone            *commonAbsolutes[145] = { 0 };
227
228/*
229 * Temporary structure for holding time zone details.
230 * This is the format in the data object.
231 */
232struct ttinfo
233{
234  char offset[4];         // Seconds east of UTC
235  unsigned char isdst;    // Daylight savings time?
236  unsigned char abbr_idx; // Index into time zone abbreviations string
237} __attribute__((packed));
238
239/*
240 * And this is the structure used in the time zone instances.
241 */
242typedef struct {
243  int32_t	offset;
244  BOOL		isdst;
245  unsigned char	abbr_idx;
246  char		pad[2];
247  NSString	*abbreviation;
248} TypeInfo;
249
250@interface	GSTimeZone : NSTimeZone
251{
252@public
253  NSString	*timeZoneName;
254  NSArray	*abbreviations;
255  NSData	*timeZoneData;
256  unsigned int	n_trans;
257  unsigned int	n_types;
258  int32_t	*trans;
259  TypeInfo	*types;
260  unsigned char	*idxs;
261}
262@end
263
264#if	defined(_WIN32)
265@interface	GSWindowsTimeZone : NSTimeZone
266{
267@public
268  NSString	*timeZoneName;
269  NSString	*daylightZoneName;
270  NSString	*timeZoneNameAbbr;
271  NSString	*daylightZoneNameAbbr;
272  LONG		Bias;
273  LONG		StandardBias;
274  LONG		DaylightBias;
275  SYSTEMTIME	StandardDate;
276  SYSTEMTIME	DaylightDate;
277}
278@end
279#endif
280
281static NSTimeZone	*defaultTimeZone = nil;
282static NSTimeZone	*localTimeZone = nil;
283static NSTimeZone	*systemTimeZone = nil;
284
285/* Dictionary for time zones.  Each time zone must have a unique
286   name. */
287static NSMutableDictionary *zoneDictionary;
288
289/* one-to-one abbreviation to time zone name dictionary. */
290static NSMutableDictionary *abbreviationDictionary = nil;
291/* one-to-many abbreviation to time zone name dictionary. */
292static NSMutableDictionary *abbreviationMap = nil;
293
294/* Lock for creating time zones. */
295static pthread_mutex_t zone_mutex;
296
297static Class	NSTimeZoneClass;
298static Class	GSPlaceholderTimeZoneClass;
299
300
301/* Return path to a TimeZone directory file */
302static NSString *_time_zone_path(NSString *subpath, NSString *type)
303{
304  NSBundle *gbundle;
305  if (type == nil)
306    type = @"";
307  gbundle = [NSBundle bundleForClass: [NSObject class]];
308  return [gbundle pathForResource: subpath
309		           ofType: type
310		      inDirectory: TIME_ZONE_DIR];
311}
312
313@interface GSPlaceholderTimeZone : NSTimeZone
314@end
315
316@interface GSAbsTimeZone : NSTimeZone
317{
318@public
319  NSString	*name;
320  id		detail;
321  int		offset; // Offset from UTC in seconds.
322}
323
324- (id) initWithOffset: (NSInteger)anOffset name: (NSString*)aName;
325@end
326
327@interface NSLocalTimeZone : NSTimeZone
328@end
329
330@interface GSTimeZoneDetail : NSTimeZoneDetail
331{
332  NSTimeZone	*timeZone; // Time zone which created this object.
333  NSString	*abbrev; // Abbreviation for time zone detail.
334  int		offset; // Offset from UTC in seconds.
335  BOOL		is_dst; // Is it daylight savings time?
336}
337
338- (id) initWithTimeZone: (NSTimeZone*)aZone
339	     withAbbrev: (NSString*)anAbbrev
340	     withOffset: (NSInteger)anOffset
341		withDST: (BOOL)isDST;
342@end
343
344@interface GSAbsTimeZoneDetail : NSTimeZoneDetail
345{
346  GSAbsTimeZone	*zone; // Time zone which created this object.
347}
348
349- (id) initWithTimeZone: (GSAbsTimeZone*)aZone;
350@end
351
352/* Private methods for obtaining resource file names. */
353@interface NSTimeZone (Private)
354+ (NSString*) _getTimeZoneFile: (NSString*)name;
355+ (void) _notified: (NSNotification*)n;
356@end
357
358
359@implementation GSPlaceholderTimeZone
360
361- (id) autorelease
362{
363  NSWarnLog(@"-autorelease sent to uninitialised time zone");
364  return self;		// placeholders never get released.
365}
366
367- (void) dealloc
368{
369  GSNOSUPERDEALLOC;	// placeholders never get deallocated.
370}
371
372- (id) initWithName: (NSString*)name data: (NSData*)data
373{
374  NSTimeZone	*zone;
375  unsigned	length = [name length];
376
377  if (length == 0)
378    {
379      NSLog(@"Disallowed null time zone name");
380      return nil;
381    }
382  if (length == 15 && [name isEqual: @"NSLocalTimeZone"])
383    {
384      zone = RETAIN(localTimeZone);
385      DESTROY(self);
386      return (GSPlaceholderTimeZone*)zone;
387    }
388
389  /*
390   * Return a cached time zone if possible.
391   * NB. if data of cached zone does not match new data ... don't use cache
392   */
393  pthread_mutex_lock(&zone_mutex);
394  zone = [zoneDictionary objectForKey: name];
395  if (data != nil && [data isEqual: [zone data]] == NO)
396    {
397      zone = nil;
398    }
399  IF_NO_GC(RETAIN(zone));
400  pthread_mutex_unlock(&zone_mutex);
401
402  if (zone == nil)
403    {
404      unichar	c;
405      int	i;
406
407      if ((length == 3
408	&& ([name isEqualToString: @"GMT"] == YES
409          || [name isEqualToString: @"UTC"] == YES
410          || [name isEqualToString: @"UCT"] == YES))
411	|| (length == 4
412	  && ([name isEqualToString: @"GMT0"] == YES
413	    || [name isEqualToString: @"Zulu"] == YES))
414	|| (length == 9 && [name isEqualToString: @"Universal"] == YES))
415	{
416	  // Synonyms for GMT
417	  zone = [[GSAbsTimeZone alloc] initWithOffset: 0 name: name];
418	}
419      else if (length == 5 && [name hasPrefix: @"GMT"] == YES
420	&& ((c = [name characterAtIndex: 3]) == '+' || c == '-')
421	&& ((c = [name characterAtIndex: 4]) >= '0' && c <= '9'))
422	{
423	  // GMT-9 to GMT+9
424	  i = (c - '0') * 60 * 60;
425	  if ([name characterAtIndex: 3] == '-')
426	    {
427	      i = -i;
428	    }
429	  zone = [[GSAbsTimeZone alloc] initWithOffset: i name: name];
430	}
431      else if (length == 6 && [name hasPrefix: @"GMT"] == YES
432	&& ((c = [name characterAtIndex: 3]) == '+' || c == '-')
433	&& ((c = [name characterAtIndex: 4]) == '0' || c == '1')
434	&& ((c = [name characterAtIndex: 5]) >= '0' && c <= '4'))
435	{
436	  // GMT-14 to GMT-10 and GMT+10 to GMT+14
437	  i = (c - '0') * 60 * 60;
438	  if ([name characterAtIndex: 4] == '1')
439	    {
440	      i += 60 * 60 * 10;
441	    }
442	  if ([name characterAtIndex: 3] == '-')
443	    {
444	      i = -i;
445	    }
446	  zone = [[GSAbsTimeZone alloc] initWithOffset: i name: name];
447	}
448      else if (length == 8 && [name hasPrefix: @"GMT"] == YES
449	&& ((c = [name characterAtIndex: 3]) == '+' || c == '-'))
450	{
451	  // GMT+NNNN and GMT-NNNN
452	  c = [name characterAtIndex: 4];
453	  if (c >= '0' && c <= '9')
454	    {
455	      i = c - '0';
456	      c = [name characterAtIndex: 5];
457	      if (c >= '0' && c <= '9')
458		{
459		  i = i * 10 + (c - '0');
460		  c = [name characterAtIndex: 6];
461		  if (c >= '0' && c <= '9')
462		    {
463		      i = i * 6 + (c - '0');
464		      c = [name characterAtIndex: 7];
465		      if (c >= '0' && c <= '9')
466			{
467			  i = i * 10 + (c - '0');
468			  i = i * 60;
469			  if ([name characterAtIndex: 3] == '-')
470			    {
471			      i = -i;
472			    }
473			  zone = [[GSAbsTimeZone alloc] initWithOffset: i
474								  name: nil];
475			}
476		    }
477		}
478	    }
479	}
480
481      if (zone == nil && length > 19
482	&& [name hasPrefix: @"NSAbsoluteTimeZone: "] == YES)
483	{
484	  i = [[name substringFromIndex: 19] intValue];
485
486	  zone = [[GSAbsTimeZone alloc] initWithOffset: i name: nil];
487	}
488
489      if (zone == nil)
490	{
491	  if (data == nil)
492	    {
493	      NSString	*fileName;
494	      BOOL	isDir;
495
496	      fileName = [NSTimeZoneClass _getTimeZoneFile: name];
497	      if (fileName == nil
498		|| ![[NSFileManager defaultManager] fileExistsAtPath: fileName
499		isDirectory: &isDir] || YES == isDir)
500		{
501		  data = nil;
502		}
503	      else
504		{
505	          data = [NSData dataWithContentsOfFile: fileName];
506		}
507	      if (nil == data)
508#if	defined(_WIN32)
509                {
510                  zone = [[GSWindowsTimeZone alloc] initWithName: name data: 0];
511                  DESTROY(self);
512                  return zone;
513                }
514#else
515		{
516		  return nil;
517		}
518#endif
519	    }
520#if	defined(_WIN32)
521	  if (!data)
522	    zone = [[GSWindowsTimeZone alloc] initWithName: name data: data];
523	  else
524#endif
525	  zone = [[GSTimeZone alloc] initWithName: name data: data];
526	}
527    }
528  DESTROY(self);
529  return (GSPlaceholderTimeZone*)zone;
530}
531
532- (oneway void) release
533{
534  return;		// placeholders never get released.
535}
536
537- (id) retain
538{
539  return self;		// placeholders never get retained.
540}
541@end
542
543
544
545@implementation	NSLocalTimeZone
546
547- (NSString*) abbreviation
548{
549  return [[NSTimeZoneClass defaultTimeZone] abbreviation];
550}
551
552- (NSString*) abbreviationForDate: (NSDate*)aDate
553{
554  return [[NSTimeZoneClass defaultTimeZone] abbreviationForDate: aDate];
555}
556
557- (id) autorelease
558{
559  return self;
560}
561
562- (id) copy
563{
564  return self;
565}
566
567- (id) copyWithZone: (NSZone*)z
568{
569  return self;
570}
571
572- (NSData*) data
573{
574  return [[NSTimeZoneClass defaultTimeZone] data];
575}
576
577- (void) encodeWithCoder: (NSCoder*)aCoder
578{
579  [aCoder encodeObject: @"NSLocalTimeZone"];
580}
581
582- (id) init
583{
584  return self;
585}
586
587- (BOOL) isDaylightSavingTime
588{
589  return [[NSTimeZoneClass defaultTimeZone] isDaylightSavingTime];
590}
591
592- (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate
593{
594  return [[NSTimeZoneClass defaultTimeZone] isDaylightSavingTimeForDate: aDate];
595}
596
597- (NSString*) name
598{
599  return [[NSTimeZoneClass defaultTimeZone] name];
600}
601
602- (oneway void) release
603{
604}
605
606- (id) retain
607{
608  return self;
609}
610
611- (NSInteger) secondsFromGMT
612{
613  return [[NSTimeZoneClass defaultTimeZone] secondsFromGMT];
614}
615
616- (NSInteger) secondsFromGMTForDate: (NSDate*)aDate
617{
618  return [[NSTimeZoneClass defaultTimeZone] secondsFromGMTForDate: aDate];
619}
620
621- (NSArray*) timeZoneDetailArray
622{
623  return [[NSTimeZoneClass defaultTimeZone] timeZoneDetailArray];
624}
625
626- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date
627{
628  return [[NSTimeZoneClass defaultTimeZone] timeZoneDetailForDate: date];
629}
630
631- (NSString*) timeZoneName
632{
633  return [[NSTimeZoneClass defaultTimeZone] timeZoneName];
634}
635
636@end
637
638
639@implementation GSAbsTimeZone
640
641static int		uninitialisedOffset = 100000;
642static NSMapTable	*absolutes = 0;
643
644+ (void) initialize
645{
646  if (self == [GSAbsTimeZone class])
647    {
648      absolutes = NSCreateMapTable(NSIntegerMapKeyCallBacks,
649	NSNonOwnedPointerMapValueCallBacks, 0);
650      [[NSObject leakAt: (id*)&absolutes] release];
651    }
652}
653
654- (NSString*) abbreviationForDate: (NSDate*)aDate
655{
656  return name;
657}
658
659- (void) dealloc
660{
661  if (offset != uninitialisedOffset)
662    {
663      pthread_mutex_lock(&zone_mutex);
664      NSMapRemove(absolutes, (void*)(uintptr_t)offset);
665      pthread_mutex_unlock(&zone_mutex);
666    }
667  RELEASE(name);
668  RELEASE(detail);
669  [super dealloc];
670}
671
672- (void) encodeWithCoder: (NSCoder*)aCoder
673{
674  [aCoder encodeObject: name];
675}
676
677- (id) initWithOffset: (NSInteger)anOffset name: (NSString*)aName
678{
679  GSAbsTimeZone	*z;
680  int		extra;
681  int		sign = anOffset >= 0 ? 1 : -1;
682
683  /*
684   * Set the uninitialised offset so that dealloc before full
685   * initialisation won't remove the timezeone for offset 0 from cache.
686   */
687  offset = uninitialisedOffset;
688
689  /*
690   * Round the offset to the nearest minute, (for MacOS-X compatibility)
691   * and ensure it is no more than 18 hours.
692   */
693  anOffset *= sign;
694  extra = anOffset % 60;
695  if (extra < 30)
696    {
697      anOffset -= extra;
698    }
699  else
700    {
701      anOffset += 60 - extra;
702    }
703  if (anOffset > 64800)
704    {
705      DESTROY(self);
706      return nil;
707    }
708  anOffset *= sign;
709
710  if (anOffset % 900 == 0)
711    {
712      z = commonAbsolutes[anOffset/900 + 72];
713      if (z != nil)
714        {
715          IF_NO_GC(RETAIN(z));
716          DESTROY(self);
717          return z;
718        }
719    }
720
721  pthread_mutex_lock(&zone_mutex);
722  z = (GSAbsTimeZone*)NSMapGet(absolutes, (void*)(uintptr_t)anOffset);
723  if (z != nil)
724    {
725      IF_NO_GC(RETAIN(z));
726      DESTROY(self);
727    }
728  else
729    {
730      if (aName == nil)
731	{
732	  if (anOffset % 60 == 0)
733	    {
734	      char	s = (anOffset >= 0) ? '+' : '-';
735	      unsigned	i = (anOffset >= 0) ? anOffset / 60 : -anOffset / 60;
736	      unsigned	h = (i / 60) % 24;
737	      unsigned	m = i % 60;
738	      char	buf[9];
739
740	      snprintf(buf, sizeof(buf), "GMT%c%02u%02u", s, h, m);
741	      name = [[NSString alloc] initWithUTF8String: buf];
742	    }
743	  else
744	    {
745	      /*
746	       * Should never happen now we round to the minute
747	       * for MacOS-X compatibnility.
748	       */
749	      name = [[NSString alloc]
750		initWithFormat: @"NSAbsoluteTimeZone:%"PRIdPTR, anOffset];
751	    }
752	}
753      else
754	{
755	  name = [aName copy];
756	}
757      detail = [[GSAbsTimeZoneDetail alloc] initWithTimeZone: self];
758      offset = anOffset;
759      z = self;
760      NSMapInsert(absolutes, (void*)(uintptr_t)anOffset, (void*)z);
761      [zoneDictionary setObject: self forKey: (NSString*)name];
762    }
763  if (anOffset % 900 == 0)
764    {
765      int       index = anOffset/900 + 72;
766
767      if (nil == commonAbsolutes[index])
768        {
769          commonAbsolutes[index] = RETAIN(self);
770        }
771    }
772  pthread_mutex_unlock(&zone_mutex);
773  return z;
774}
775
776- (BOOL) isDaylightSavingTimeZoneForDate: (NSDate*)aDate
777{
778  return NO;
779}
780
781- (NSString*) name
782{
783  return name;
784}
785
786- (NSInteger) secondsFromGMTForDate: (NSDate*)aDate
787{
788  return offset;
789}
790
791- (NSArray*) timeZoneDetailArray
792{
793  return [NSArray arrayWithObject: detail];
794}
795
796- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date
797{
798  return detail;
799}
800
801- (NSString*) timeZoneName
802{
803  return name;
804}
805@end
806
807
808@implementation GSTimeZoneDetail
809
810- (void) dealloc
811{
812  RELEASE(timeZone);
813  [super dealloc];
814}
815
816- (id) initWithCoder: (NSCoder*)aDecoder
817{
818  [aDecoder decodeValueOfObjCType: @encode(id) at: &abbrev];
819  [aDecoder decodeValueOfObjCType: @encode(int) at: &offset];
820  [aDecoder decodeValueOfObjCType: @encode(BOOL) at: &is_dst];
821  return self;
822}
823
824- (id) initWithTimeZone: (NSTimeZone*)aZone
825	     withAbbrev: (NSString*)anAbbrev
826	     withOffset: (NSInteger)anOffset
827		withDST: (BOOL)isDST
828{
829  timeZone = RETAIN(aZone);
830  abbrev = anAbbrev;		// NB. Depend on this being retained in aZone
831  offset = anOffset;
832  is_dst = isDST;
833  return self;
834}
835
836- (BOOL) isDaylightSavingTimeZone
837{
838  return is_dst;
839}
840
841- (NSString*) name
842{
843  return [timeZone name];
844}
845
846- (NSString*) timeZoneAbbreviation
847{
848  return abbrev;
849}
850
851- (NSArray*) timeZoneDetailArray
852{
853  return [timeZone timeZoneDetailArray];
854}
855
856- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date
857{
858  return [timeZone timeZoneDetailForDate: date];
859}
860
861- (NSInteger) timeZoneSecondsFromGMT
862{
863  return offset;
864}
865
866- (NSInteger) timeZoneSecondsFromGMTForDate: (NSDate*)aDate
867{
868  return offset;
869}
870
871@end
872
873
874@implementation GSAbsTimeZoneDetail
875
876- (NSString*) abbreviation
877{
878  return zone->name;
879}
880
881- (NSString*) abbreviationForDate: (NSDate*)aDate
882{
883  return zone->name;
884}
885
886- (void) dealloc
887{
888  RELEASE(zone);
889  [super dealloc];
890}
891
892- (id) initWithTimeZone: (GSAbsTimeZone*)aZone
893{
894  zone = RETAIN(aZone);
895  return self;
896}
897
898- (BOOL) isDaylightSavingTimeZone
899{
900  return NO;
901}
902
903- (BOOL) isDaylightSavingTimeZoneForDate: (NSDate*)aDate
904{
905  return NO;
906}
907
908- (NSString*) name
909{
910  return zone->name;
911}
912
913- (NSString*) timeZoneAbbreviation
914{
915  return zone->name;
916}
917
918- (NSArray*) timeZoneDetailArray
919{
920  return [zone timeZoneDetailArray];
921}
922
923- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date
924{
925  return self;
926}
927
928- (NSInteger) timeZoneSecondsFromGMT
929{
930  return zone->offset;
931}
932
933- (NSInteger) timeZoneSecondsFromGMTForDate: (NSDate*)aDate
934{
935  return zone->offset;
936}
937
938@end
939
940
941/**
942 * <p>
943 * The local time zone is obtained from, in order of preference:<br/ >
944 *  1) the user defaults database: NSGlobalDomain "Local Time Zone"<br/ >
945 *  2) the GNUSTEP_TZ environment variable<br/ >
946 *  3) the file "localtime" in System/Library/Libraries/Resources/NSTimeZone<br/ >
947 *  4) the TZ environment variable<br/ >
948 *  5) The system zone settings (typically in /etc/localtime)<br/ >
949 *  6) tzset and tznam on platforms which have it<br/ >
950 *  7) Windows registry, on Win32 systems<br/ >
951 *  8) or the fallback time zone (which is UTC)<br/ >
952 * </p>
953 * <p>If the GNUstep time zone datafiles become too out of date, one
954 * can download an updated database from <uref
955 * url="ftp://elsie.nci.nih.gov/pub/">ftp://elsie.nci.nih.gov/pub/</uref>
956 * and compile it as specified in the README file in the
957 * NSTimeZones directory.
958 * </p>
959 * <p>Time zone names in NSDates should be GMT, MET etc. not
960 * Europe/Berlin, America/Washington etc.
961 * </p>
962 * <p>The problem with this is that various time zones may use the
963 * same abbreviation (e.g. Australia/Brisbane and
964 * America/New_York both use EST), and some time zones
965 * may have different rules for daylight saving time even if the
966 * abbreviation and offsets from UTC are the same.
967 * </p>
968 * <p>The problems with depending on the OS for providing time zone
969 * info are that time zone names may vary
970 * wildly between OSes (this could be a big problem when
971 * archiving is used between different systems).
972 * </p>
973 * <p>Win32:  Time zone names read from the registry are different
974 * from other GNUstep installations. Be careful when moving data
975 * between platforms in this case.
976 * </p>
977 */
978@implementation NSTimeZone
979
980/**
981 * Returns a dictionary containing time zone abbreviations and their
982 * corresponding time zone names. More than one time zone may be associated
983 * with a single abbreviation. In this case, the dictionary contains only
984 * one (usually the most common) time zone name for the abbreviation.
985 */
986+ (NSDictionary*) abbreviationDictionary
987{
988  if (abbreviationDictionary != nil)
989    {
990      return abbreviationDictionary;
991    }
992  pthread_mutex_lock(&zone_mutex);
993  if (abbreviationDictionary == nil)
994    {
995      NSAutoreleasePool	*pool = [NSAutoreleasePool new];
996      NSString		*path;
997
998      path = _time_zone_path (ABBREV_DICT, @"plist");
999      if (path != nil)
1000	{
1001	  /*
1002	   * Fast mechanism ... load prebuilt data from file so we don't
1003	   * need to load in all time zones.
1004	   */
1005	  abbreviationDictionary
1006	    = RETAIN([[NSString stringWithContentsOfFile: path] propertyList]);
1007	}
1008      if (abbreviationDictionary == nil)
1009	{
1010	  NSMutableDictionary	*md;
1011	  NSString		*name;
1012	  NSEnumerator		*names;
1013
1014	  /*
1015	   * Slow fallback ... load all time zones and generate
1016	   * abbreviation dictionary from them.
1017	   */
1018	  md = [[NSMutableDictionary alloc] init];
1019	  names = [[NSTimeZone knownTimeZoneNames] objectEnumerator];
1020	  while ((name = [names nextObject]) != nil)
1021	    {
1022	      NSTimeZone *zone;
1023
1024	      if ((zone = [NSTimeZone timeZoneWithName: name]))
1025		{
1026		  NSEnumerator		*details;
1027		  NSTimeZoneDetail	*detail;
1028
1029		  details = [[zone timeZoneDetailArray] objectEnumerator];
1030		  while ((detail = [details nextObject]) != nil)
1031		    {
1032		      [md setObject: name
1033			     forKey: [detail timeZoneAbbreviation]];
1034		    }
1035		}
1036	    }
1037          if ([md makeImmutable] == YES)
1038            {
1039              abbreviationDictionary = md;
1040            }
1041          else
1042            {
1043              abbreviationDictionary = [md copy];
1044              RELEASE(md);
1045            }
1046	}
1047      [pool drain];
1048    }
1049  pthread_mutex_unlock(&zone_mutex);
1050  return abbreviationDictionary;
1051}
1052
1053/**
1054 * Returns a dictionary that maps abbreviations to the array
1055 * containing all the time zone names that use the abbreviation.
1056 */
1057+ (NSDictionary*) abbreviationMap
1058{
1059  /* Instead of creating the abbreviation dictionary when the class is
1060     initialized, we create it when we first need it, since the
1061     dictionary can be potentially very large, considering that it's
1062     almost never used. */
1063  if (abbreviationMap != nil)
1064    {
1065      return abbreviationMap;
1066    }
1067  pthread_mutex_lock(&zone_mutex);
1068  if (abbreviationMap == nil)
1069    {
1070      NSAutoreleasePool		*pool = [NSAutoreleasePool new];
1071      NSMutableDictionary	*md;
1072      NSMutableArray		*ma;
1073      NSString			*the_name;
1074      NSString			*the_abbrev;
1075      FILE			*file;
1076      char			abbrev[80];
1077      char			name[80];
1078      NSString			*path;
1079
1080      /*
1081       * Read dictionary from file... fast mechanism because we don't have
1082       * to create all timezoneas and parse all their data files.
1083       */
1084      md = [NSMutableDictionary dictionaryWithCapacity: 100];
1085      path = _time_zone_path (ABBREV_MAP, nil);
1086      if (path != nil)
1087	{
1088#if	defined(_WIN32)
1089	  unichar	mode[3];
1090
1091	  mode[0] = 'r';
1092	  mode[1] = 'b';
1093	  mode[2] = '\0';
1094
1095	  file = _wfopen((const unichar*)[path fileSystemRepresentation], mode);
1096#else
1097	  file = fopen([path fileSystemRepresentation], "r");
1098#endif
1099	  if (file == NULL)
1100	    {
1101              pthread_mutex_unlock(&zone_mutex);
1102	      [NSException
1103		raise: NSInternalInconsistencyException
1104		format: @"Failed to open time zone abbreviation map."];
1105	    }
1106	  while (fscanf(file, "%79s %79s", abbrev, name) == 2)
1107	    {
1108	      the_name = [[NSString alloc] initWithUTF8String: name];
1109	      the_abbrev = [[NSString alloc] initWithUTF8String: abbrev];
1110	      ma = [md objectForKey: the_abbrev];
1111	      if (ma == nil)
1112		{
1113		  ma = [[NSMutableArray alloc] initWithCapacity: 1];
1114		  [md setObject: ma forKey: the_abbrev];
1115		  RELEASE(ma);
1116		}
1117	      RELEASE(the_abbrev);
1118	      if ([ma containsObject: the_name] == NO)
1119		{
1120		  [ma addObject: the_name];
1121		}
1122	      RELEASE(the_name);
1123	    }
1124	  fclose(file);
1125	}
1126      else
1127	{
1128	  NSString		*name;
1129	  NSEnumerator		*names;
1130
1131	  /*
1132	   * Slow fallback mechanism ... go through all time names
1133	   * so we load all the time zone data and generate the info
1134	   * we need from it.
1135	   */
1136	  names = [[NSTimeZone knownTimeZoneNames] objectEnumerator];
1137	  while ((name = [names nextObject]) != nil)
1138	    {
1139	      NSTimeZone *zone;
1140
1141	      if ((zone = [NSTimeZone timeZoneWithName: name]) != nil)
1142		{
1143		  NSEnumerator		*details;
1144		  NSTimeZoneDetail	*detail;
1145
1146		  details = [[zone timeZoneDetailArray] objectEnumerator];
1147		  while ((detail = [details nextObject]) != nil)
1148		    {
1149		      the_abbrev = [detail timeZoneAbbreviation];
1150		      ma = [md objectForKey: the_abbrev];
1151		      if (ma == nil)
1152			{
1153			  ma = [[NSMutableArray alloc] initWithCapacity: 1];
1154			  [md setObject: ma forKey: the_abbrev];
1155			  RELEASE(ma);
1156			}
1157		      if ([ma containsObject: name] == NO)
1158		        {
1159		          [ma addObject: name];
1160			}
1161		    }
1162		}
1163	    }
1164	}
1165
1166      /* Special case: Add the system time zone if
1167       * it doesn't exist in the map */
1168      the_abbrev = [systemTimeZone abbreviation];
1169      ma = [md objectForKey: the_abbrev];
1170      if (ma == nil)
1171	{
1172	  ma = [NSMutableArray new];
1173	  [md setObject: ma forKey: the_abbrev];
1174	  RELEASE(ma);
1175	}
1176      the_name = [systemTimeZone timeZoneName];
1177      if ([ma containsObject: the_name] == NO)
1178	{
1179	  [ma addObject: the_name];
1180	}
1181
1182      if ([md makeImmutable] == YES)
1183        {
1184          abbreviationMap = RETAIN(md);
1185        }
1186      else
1187        {
1188          abbreviationMap = [md copy];
1189        }
1190      [pool drain];
1191    }
1192  pthread_mutex_unlock(&zone_mutex);
1193
1194  return abbreviationMap;
1195}
1196
1197/**
1198 * Returns an array of all known time zone names.
1199 */
1200+ (NSArray*) knownTimeZoneNames
1201{
1202  static NSArray *namesArray = nil;
1203
1204  /* We create the array only when we need it to reduce overhead. */
1205  if (namesArray != nil)
1206    {
1207      return namesArray;
1208    }
1209
1210  pthread_mutex_lock(&zone_mutex);
1211  if (namesArray == nil)
1212    {
1213      unsigned		i;
1214      NSMutableArray	*ma;
1215      NSArray		*regionsArray;
1216
1217      ma = [NSMutableArray new];
1218      regionsArray = [self timeZoneArray];
1219
1220      for (i = 0; i < [regionsArray count]; i++)
1221	{
1222	  NSArray *names = [regionsArray objectAtIndex: i];
1223
1224	  [ma addObjectsFromArray: names];
1225	}
1226      if ([ma makeImmutable] == YES)
1227        {
1228          namesArray = ma;
1229        }
1230      else
1231        {
1232          namesArray = [ma copy];
1233          RELEASE(ma);
1234        }
1235    }
1236  pthread_mutex_unlock(&zone_mutex);
1237  return namesArray;
1238}
1239
1240+ (id) allocWithZone: (NSZone*)z
1241{
1242  if (self == NSTimeZoneClass)
1243    {
1244      /*
1245       * We return a placeholder object that can
1246       * be converted to a real object when its initialisation method
1247       * is called.
1248       */
1249      if (z == NSDefaultMallocZone() || z == 0)
1250	{
1251	  /*
1252	   * As a special case, we can return a placeholder for a time zone
1253	   * in the default zone extremely efficiently.
1254	   */
1255	  return defaultPlaceholderTimeZone;
1256	}
1257      else
1258	{
1259	  id	obj;
1260
1261	  /*
1262	   * For anything other than the default zone, we need to
1263	   * locate the correct placeholder in the (lock protected)
1264	   * table of placeholders.
1265	   */
1266          pthread_mutex_lock(&zone_mutex);
1267	  obj = (id)NSMapGet(placeholderMap, (void*)z);
1268	  if (obj == nil)
1269	    {
1270	      /*
1271	       * There is no placeholder object for this zone, so we
1272	       * create a new one and use that.
1273	       */
1274	      obj = (id)NSAllocateObject(GSPlaceholderTimeZoneClass, 0, z);
1275	      NSMapInsert(placeholderMap, (void*)z, (void*)obj);
1276	    }
1277          pthread_mutex_unlock(&zone_mutex);
1278	  return obj;
1279	}
1280    }
1281  else
1282    {
1283      return NSAllocateObject(self, 0, z);
1284    }
1285}
1286
1287/**
1288 * Return the default time zone for this process.
1289 */
1290+ (NSTimeZone*) defaultTimeZone
1291{
1292  NSTimeZone	*zone;
1293
1294  pthread_mutex_lock(&zone_mutex);
1295  if (defaultTimeZone == nil)
1296    {
1297      zone = [self systemTimeZone];
1298    }
1299  else
1300    {
1301      zone = AUTORELEASE(RETAIN(defaultTimeZone));
1302    }
1303  pthread_mutex_unlock(&zone_mutex);
1304  return zone;
1305}
1306
1307+ (void) initialize
1308{
1309  if (self == [NSTimeZone class])
1310    {
1311      NSTimeZoneClass = self;
1312      GS_INIT_RECURSIVE_MUTEX(zone_mutex);
1313      GSPlaceholderTimeZoneClass = [GSPlaceholderTimeZone class];
1314      zoneDictionary = [[NSMutableDictionary alloc] init];
1315      [[NSObject leakAt: &zoneDictionary] release];
1316
1317      /*
1318       * Set up infrastructure for placeholder timezones.
1319       */
1320      defaultPlaceholderTimeZone = (GSPlaceholderTimeZone*)
1321	NSAllocateObject(GSPlaceholderTimeZoneClass, 0, NSDefaultMallocZone());
1322      [[NSObject leakAt: &defaultPlaceholderTimeZone] release];
1323      placeholderMap = NSCreateMapTable(NSNonOwnedPointerMapKeyCallBacks,
1324	NSNonRetainedObjectMapValueCallBacks, 0);
1325      [[NSObject leakAt: (id*)&placeholderMap] release];
1326
1327      localTimeZone = [[NSLocalTimeZone alloc] init];
1328      [[NSObject leakAt: (id*)&localTimeZone] release];
1329
1330      [[NSObject leakAt: (id*)&defaultTimeZone] release];
1331      [[NSObject leakAt: (id*)&systemTimeZone] release];
1332      [[NSObject leakAt: (id*)&abbreviationDictionary] release];
1333      [[NSObject leakAt: (id*)&abbreviationMap] release];
1334      [[NSObject leakAt: (id*)&absolutes] release];
1335
1336      [[NSNotificationCenter defaultCenter] addObserver: self
1337        selector: @selector(_notified:)
1338        name: NSUserDefaultsDidChangeNotification
1339        object: nil];
1340    }
1341}
1342
1343/**
1344 * Return a proxy to the default time zone for this process.
1345 */
1346+ (NSTimeZone*) localTimeZone
1347{
1348  return localTimeZone;
1349}
1350
1351/**
1352 * Destroy the system time zone so that it will be recreated
1353 * next time it is used.
1354 */
1355+ (void) resetSystemTimeZone
1356{
1357  pthread_mutex_lock(&zone_mutex);
1358  DESTROY(systemTimeZone);
1359  pthread_mutex_unlock(&zone_mutex);
1360  [[NSNotificationCenter defaultCenter]
1361    postNotificationName: NSSystemTimeZoneDidChangeNotification
1362                  object: nil];
1363}
1364
1365/**
1366 * Set the default time zone to be used for this process.
1367 */
1368+ (void) setDefaultTimeZone: (NSTimeZone*)aTimeZone
1369{
1370  if (aTimeZone != defaultTimeZone)
1371    {
1372      /*
1373       * We can't make the localTimeZone the default since that would
1374       * cause recursion ...
1375       */
1376      if (aTimeZone == localTimeZone)
1377	{
1378	  aTimeZone = [self systemTimeZone];
1379	}
1380      pthread_mutex_lock(&zone_mutex);
1381      ASSIGN(defaultTimeZone, aTimeZone);
1382      pthread_mutex_unlock(&zone_mutex);
1383    }
1384}
1385
1386/**
1387 * Returns the current system time zone for the process.
1388 */
1389+ (NSTimeZone*) systemTimeZone
1390{
1391  NSTimeZone	*zone = nil;
1392
1393  pthread_mutex_lock(&zone_mutex);
1394  if (systemTimeZone == nil)
1395    {
1396      NSFileManager *dflt = [NSFileManager defaultManager];
1397      NSString	*localZoneString = nil;
1398      NSString	*localZoneSource = nil;
1399
1400      /*
1401       * setup default value in case something goes wrong.
1402       */
1403      systemTimeZone = RETAIN([NSTimeZoneClass timeZoneForSecondsFromGMT: 0]);
1404
1405      /*
1406       * Try to get timezone from user defaults database
1407       */
1408      localZoneSource = [NSString stringWithFormat:
1409	@"NSUserDefaults: '%@'", LOCALDBKEY];
1410      localZoneString = [[NSUserDefaults standardUserDefaults]
1411	stringForKey: LOCALDBKEY];
1412
1413      /*
1414       * Try to get timezone from GNUSTEP_TZ environment variable.
1415       */
1416      if (localZoneString == nil)
1417	{
1418          localZoneSource = _(@"environment variable: 'GNUSTEP_TZ'");
1419	  localZoneString = [[[NSProcessInfo processInfo]
1420	    environment] objectForKey: @"GNUSTEP_TZ"];
1421	}
1422
1423      /*
1424       * Try to get timezone from LOCAL_TIME_FILE.
1425       */
1426      if (localZoneString == nil)
1427	{
1428	  NSString	*f = _time_zone_path(LOCAL_TIME_FILE, nil);
1429
1430          localZoneSource = [NSString stringWithFormat: @"file: '%@'", f];
1431	  if (f != nil)
1432	    {
1433	      localZoneString = [NSString stringWithContentsOfFile: f];
1434	      localZoneString = [localZoneString stringByTrimmingSpaces];
1435	    }
1436	}
1437
1438#if	defined(_WIN32)
1439      /*
1440       * Try to get timezone from windows system call.
1441       */
1442      {
1443      	TIME_ZONE_INFORMATION tz;
1444      	DWORD DST = GetTimeZoneInformation(&tz);
1445
1446        localZoneSource = @"function: 'GetTimeZoneInformation()'";
1447      	if (DST == TIME_ZONE_ID_DAYLIGHT)
1448	  {
1449	    localZoneString = [NSString stringWithCharacters: tz.DaylightName
1450	      length: wcslen(tz.DaylightName)];
1451	  }
1452      	else
1453	  {
1454	    localZoneString = [NSString stringWithCharacters: tz.StandardName
1455	      length: wcslen(tz.StandardName)];
1456	  }
1457      }
1458#endif
1459
1460      if (localZoneString == nil)
1461	{
1462	  if (YES == [dflt isReadableFileAtPath: @"/etc/timezone"])
1463	    {
1464	      NSString	*s;
1465
1466	      s = [NSString stringWithContentsOfFile: @"/etc/timezone"];
1467	      s = [s stringByTrimmingSpaces];
1468	      if (0 != [s length])
1469		{
1470		  localZoneSource = _(@"/etc/timezone file");
1471		  localZoneString = s;
1472		}
1473	    }
1474	}
1475
1476      if (localZoneString == nil)
1477	{
1478	  if (YES == [dflt isReadableFileAtPath: @"/etc/sysconfig/clock"])
1479	    {
1480	      NSString		*s;
1481	      NSEnumerator	*e;
1482
1483	      s = [NSString stringWithContentsOfFile:
1484		@"/etc/sysconfig/clock"];
1485	      e = [[s componentsSeparatedByString: @"\n"] objectEnumerator];
1486	      while (nil != (s = [e nextObject]))
1487		{
1488		  s = [s stringByTrimmingSpaces];
1489                  // OpenSuse uses the non-standard key TIMEZONE
1490		  if ([s hasPrefix: @"ZONE"] || [s hasPrefix: @"TIMEZONE"])
1491		    {
1492                      if ([s hasPrefix: @"ZONE"])
1493                        s = [s substringFromIndex: 4];
1494                      else
1495                        s = [s substringFromIndex: 8];
1496
1497		      s = [s stringByTrimmingSpaces];
1498		      if ([s hasPrefix: @"="])
1499			{
1500			  s = [s substringFromIndex: 1];
1501			  s = [s stringByTrimmingSpaces];
1502			  if ([s hasPrefix: @"\""])
1503			    {
1504			      s = [s substringFromIndex: 1];
1505			    }
1506			  if ([s hasSuffix: @"\""])
1507			    {
1508			      s = [s substringToIndex: [s length] - 1];
1509			    }
1510			  s = [s stringByTrimmingSpaces];
1511			  if ([s length] > 0)
1512			    {
1513			      localZoneSource = _(@"/etc/sysconfig/clock file");
1514			      localZoneString = s;
1515			    }
1516			}
1517		    }
1518		}
1519	    }
1520	}
1521
1522      if (localZoneString == nil)
1523        {
1524          /* Get the zone name from the localtime file, assuming the file
1525	     is a symlink to the time zone. Getting the actual data (which
1526	     is easier) doesn't help, since we won't know the name itself.  */
1527#if defined(HAVE_TZHEAD) && defined(TZDEFAULT)
1528	  tzdir = RETAIN([NSString stringWithUTF8String: TZDIR]);
1529	  localZoneString = [NSString stringWithUTF8String: TZDEFAULT];
1530          localZoneSource = [NSString stringWithFormat:
1531	    @"file (TZDEFAULT): '%@'", localZoneString];
1532	  localZoneString = [localZoneString stringByResolvingSymlinksInPath];
1533#else
1534          if ([dflt fileExistsAtPath: SYSTEM_TIME_FILE])
1535	    {
1536	      localZoneString = SYSTEM_TIME_FILE;
1537	      localZoneSource = [NSString stringWithFormat:
1538		@"file (SYSTEM_TIME_FILE): '%@'", localZoneString];
1539	      localZoneString
1540		= [localZoneString stringByResolvingSymlinksInPath];
1541	      /* Guess what tzdir is */
1542	      tzdir = [localZoneString stringByDeletingLastPathComponent];
1543	      while ([tzdir length] > 2
1544		&& [dflt fileExistsAtPath:
1545		[tzdir stringByAppendingPathComponent: @"GMT"]] == NO)
1546		{
1547		  tzdir = [tzdir stringByDeletingLastPathComponent];
1548		}
1549	      if ([tzdir length] <= 2)
1550	        {
1551		  localZoneString = tzdir = nil;
1552		}
1553	      else
1554		{
1555		  [tzdir retain];
1556		}
1557	    }
1558#endif
1559	  if (localZoneString != nil && [localZoneString hasPrefix: tzdir])
1560	    {
1561	      /* This must be the time zone name */
1562	      localZoneString = AUTORELEASE([localZoneString mutableCopy]);
1563	      [(NSMutableString*)localZoneString deleteCharactersInRange:
1564		NSMakeRange(0, [tzdir length])];
1565	      while ([localZoneString hasPrefix: @"/"])
1566	        {
1567		  [(NSMutableString*)localZoneString deleteCharactersInRange:
1568		    NSMakeRange(0, 1)];
1569	        }
1570	    }
1571	  else
1572	    {
1573	      localZoneString = nil;
1574	    }
1575        }
1576
1577      /* Try to get timezone from standard unix environment variable.
1578       * This is often an ambiguous abbreviation :-(
1579       */
1580      if (localZoneString == nil)
1581	{
1582          localZoneSource = _(@"environment variable: 'TZ'");
1583	  localZoneString = [[[NSProcessInfo processInfo]
1584	    environment] objectForKey: @"TZ"];
1585	}
1586
1587
1588#if HAVE_TZSET && !defined(__FreeBSD__) && !defined(__OpenBSD__)
1589      /*
1590       * Try to get timezone from tzset and tzname/daylight.
1591       * If daylight is non-zero, then tzname[0] is only the name
1592       * the the zone for part of the year, so we can't use it as
1593       * the definitive zone.
1594       *
1595       * FreeBSD doesn't implement TZSet fully, so we can't use it there.
1596       * Apparently, OpenBSD neither.
1597       */
1598      if (localZoneString == nil)
1599	{
1600          localZoneSource = @"function: 'tzset()/tzname'";
1601	  tzset();
1602	  if (NULL != tzname[0] && '\0' != *tzname[0] && 0 == daylight)
1603	    localZoneString = [NSString stringWithUTF8String: tzname[0]];
1604	}
1605#endif
1606
1607      if (localZoneString != nil)
1608	{
1609	  NSDebugLLog (@"NSTimeZone", @"Using zone %@", localZoneString);
1610	  zone = [defaultPlaceholderTimeZone initWithName: localZoneString];
1611	  if (zone == nil)
1612	    {
1613	      NSArray	*possibleZoneNames;
1614
1615	      /*
1616		It is not guaranteed on some systems (e.g., Ubuntu) that
1617		SYSTEM_TIME_FILE is a symlink. This file is more probably
1618		a copy of a zoneinfo file. The above time zone detecting
1619		approach can lead to the situation when we can only know
1620		about the time zone abbreviation (localZoneString) and
1621		(for some time zone abbreviations) the corresponding list
1622		of possible time zone names (e.g. SAMT is valid for
1623		Pacific/Samoa, Pacific/Pago_Pago, Pacific/Apia,
1624		Asia/Samarkand, Europe/Samara, US/Samoa).
1625		In such a case the time zone can be selected
1626		from the list by comparing the content of SYSTEM_TIME_FILE
1627		and the content of zoneinfo files corresponding to the items
1628		of that list.
1629	       */
1630	      possibleZoneNames = [[self abbreviationMap]
1631		objectForKey: localZoneString];
1632	      if (possibleZoneNames != nil)
1633		{
1634		  NSEnumerator	*en = [possibleZoneNames objectEnumerator];
1635		  NSString	*zoneName;
1636		  NSFileManager *dflt = [NSFileManager defaultManager];
1637
1638		  while ((zoneName = [en nextObject]) != nil)
1639		    {
1640		      NSString	*fileName = [self _getTimeZoneFile: zoneName];
1641
1642		      if (fileName != nil
1643			&& [dflt contentsEqualAtPath: fileName
1644					     andPath: SYSTEM_TIME_FILE])
1645			{
1646			  zone = [[self timeZoneWithName: zoneName] retain];
1647
1648			  if (zone != nil)
1649			    {
1650			      GSPrintf(stderr,
1651@"\nIt seems that your operating system does not have a valid timezone name\n"
1652@"configured and is using an abbreviation instead.  By comparing timezone\n"
1653@"file data it is has been possible to find the actual timezone used, but\n"
1654@"doing that is a slow process.\n"
1655@"\nYou can avoid slowness of this time zone detecting approach\n"
1656@"by setting the environment variable TZ='%@'\n"
1657@"Or You can override the timezone name by setting the '%@'\n"
1658@"NSUserDefault via the 'defaults' command line utility, a Preferences\n"
1659@"application, or some other utility.\n"
1660@"eg \"defaults write NSGlobalDomain '%@' '%@'\"\n\n",
1661zoneName, LOCALDBKEY, LOCALDBKEY, zoneName);
1662			      break;
1663			    }
1664			}
1665		    }
1666		}
1667	    }
1668	  if (zone == nil)
1669	    {
1670	      if (zone == nil)
1671		{
1672		  GSPrintf(stderr,
1673@"\nUnable to create time zone for name: '%@'\n"
1674@"(source '%@').\n", localZoneString, localZoneSource);
1675		}
1676	      if ([localZoneSource hasPrefix: @"file"]
1677	        || [localZoneSource hasPrefix: @"function"])
1678		{
1679                  GSPrintf(stderr,
1680@"\nIt seems that your operating system does not have a valid timezone name\n"
1681@"configured (it could be that some other software has set a, possibly\n"
1682@"ambiguous, timezone abbreviation rather than a name) ... please correct\n"
1683@"that or override by setting a timezone name (such as 'Europe/London'\n"
1684@"or 'America/Chicago').\n");
1685		}
1686	      GSPrintf(stderr,
1687@"\nYou can override the timezone name by setting the '%@'\n"
1688@"NSUserDefault via the 'defaults' command line utility, a Preferences\n"
1689@"application, or some other utility.\n"
1690@"eg \"defaults write NSGlobalDomain '%@' 'Africa/Nairobi'\"\n"
1691@"See '%@'\n"
1692@"for the standard timezones such as 'GB-Eire' or 'America/Chicago'.\n",
1693LOCALDBKEY, LOCALDBKEY, _time_zone_path (ZONES_DIR, nil));
1694	      zone = [[self timeZoneWithAbbreviation: localZoneString] retain];
1695	      if (zone != nil)
1696		{
1697		  NSInteger	s;
1698		  char		sign = '+';
1699
1700		  s = [zone secondsFromGMT];
1701		  if (s < 0)
1702		    {
1703		      sign = '-';
1704		      s = -s;
1705		    }
1706	          GSPrintf(stderr,
1707@"\nSucceeded in treating '%@' as a timezone abbreviation,\n"
1708@"but abbreviations do not uniquely represent timezones, so this may\n"
1709@"not have found the timezone you were expecting.  The timezone found\n"
1710@"was '%@' (currently UTC%c%02d%02d)\n\n",
1711localZoneString, [zone name], sign, s/3600, (s/60)%60);
1712		}
1713	    }
1714	}
1715      else
1716	{
1717	  NSLog(@"No local time zone specified.");
1718	}
1719
1720      /*
1721       * If local time zone fails to allocate, then allocate something
1722       * that is sure to succeed (unless we run out of memory, of
1723       * course).
1724       */
1725      if (zone == nil)
1726        {
1727          NSLog(@"Using time zone with absolute offset 0.");
1728          zone = systemTimeZone;
1729        }
1730      ASSIGN(systemTimeZone, zone);
1731    }
1732  zone = AUTORELEASE(RETAIN(systemTimeZone));
1733  pthread_mutex_unlock(&zone_mutex);
1734  return zone;
1735}
1736
1737/**
1738 * Returns an array of all the known regions.<br />
1739 * There are 24 elements, of course, one for each time zone.
1740 * Each element contains an array of NSStrings which are
1741 * the region names.
1742 */
1743+ (NSArray*) timeZoneArray
1744{
1745  static NSArray *regionsArray = nil;
1746
1747  /* We create the array only when we need it to reduce overhead. */
1748  if (regionsArray != nil)
1749    {
1750      return regionsArray;
1751    }
1752  pthread_mutex_lock(&zone_mutex);
1753  if (regionsArray == nil)
1754    {
1755      NSAutoreleasePool	*pool = [NSAutoreleasePool new];
1756      int		index;
1757      int		i;
1758      char		name[80];
1759      FILE		*fp;
1760      NSMutableArray	*temp_array[24];
1761      NSString		*path;
1762
1763      for (i = 0; i < 24; i++)
1764	{
1765	  temp_array[i] = [NSMutableArray array];
1766	}
1767
1768      path = _time_zone_path (REGIONS_FILE, nil);
1769      if (path != nil)
1770	{
1771#if	defined(_WIN32)
1772	  unichar	mode[3];
1773
1774	  mode[0] = 'r';
1775	  mode[1] = 'b';
1776	  mode[2] = '\0';
1777
1778	  fp = _wfopen((const unichar*)[path fileSystemRepresentation], mode);
1779#else
1780	  fp = fopen([path fileSystemRepresentation], "r");
1781#endif
1782	  if (fp == NULL)
1783	    {
1784              pthread_mutex_unlock(&zone_mutex);
1785	      [NSException
1786		raise: NSInternalInconsistencyException
1787		format: @"Failed to open time zone regions array file."];
1788	    }
1789	  while (fscanf(fp, "%d %79s", &index, name) == 2)
1790	    {
1791              if (index < 0)
1792                index = 0;
1793              else
1794                index %= 24;
1795	      [temp_array[index]
1796		addObject: [NSString stringWithUTF8String: name]];
1797	    }
1798	  fclose(fp);
1799	}
1800      else
1801	{
1802	  NSString	*zonedir = [NSTimeZone _getTimeZoneFile: @"WET"];
1803
1804	  if (tzdir != nil)
1805	    {
1806	      NSFileManager		*mgr = [NSFileManager defaultManager];
1807	      NSDirectoryEnumerator	*enumerator;
1808	      NSString			*name;
1809
1810	      zonedir = [zonedir stringByDeletingLastPathComponent];
1811	      enumerator = [mgr enumeratorAtPath: zonedir];
1812	      while ((name = [enumerator nextObject]) != nil)
1813		{
1814		  NSTimeZone	*zone = nil;
1815		  BOOL		isDir;
1816
1817		  path = [zonedir stringByAppendingPathComponent: name];
1818		  if ([mgr fileExistsAtPath: path isDirectory: &isDir]
1819                    && isDir == NO
1820                    && [[path pathExtension] isEqual: @"tab"] == NO)
1821		    {
1822		      zone = [zoneDictionary objectForKey: name];
1823		      if (zone == nil)
1824			{
1825			  NSData	*data;
1826
1827			  data = [NSData dataWithContentsOfFile: path];
1828			  /* We should really make sure this is a real
1829			     zone file and not something extra that happens
1830			     to be in this directory, but initWithName:data:
1831			     will do this anyway and log a message if not. */
1832			  zone = [[self alloc] initWithName: name data: data];
1833			  IF_NO_GC([zone autorelease];)
1834			}
1835		      if (zone != nil)
1836			{
1837			  int			offset;
1838			  NSArray		*details;
1839			  NSTimeZoneDetail	*detail;
1840			  NSEnumerator		*e;
1841
1842			  details = [zone timeZoneDetailArray];
1843			  e = [details objectEnumerator];
1844
1845			  while ((detail = [e nextObject]) != nil)
1846			    {
1847			      if ([detail isDaylightSavingTime] == NO)
1848				{
1849				  break;	// Found a standard time
1850				}
1851			    }
1852			  if (detail == nil && [details count] > 0)
1853			    {
1854			      // If no standard time
1855			      detail = [details objectAtIndex: 0];
1856			    }
1857
1858			  offset = [detail secondsFromGMT];
1859			  if (offset < 0)
1860			    {
1861			      offset = -offset;
1862			      offset %= (60 * 60 * 24);
1863                              if (offset > 0)
1864                                {
1865                                  offset = -offset;
1866                                  offset += (60 * 60 * 24);
1867                                }
1868			    }
1869			  else
1870			    {
1871			      offset %= (60 * 60 * 24);
1872			    }
1873			  offset /= (60 * 60);
1874
1875			  [temp_array[offset] addObject: name];
1876			}
1877		    }
1878		}
1879	    }
1880	}
1881      regionsArray = [[NSArray alloc] initWithObjects: temp_array count: 24];
1882      [pool drain];
1883    }
1884  pthread_mutex_unlock(&zone_mutex);
1885  return regionsArray;
1886}
1887
1888/**
1889 * Return a timezone for the specified offset from GMT.<br />
1890 * The timezone returned does <em>not</em> use daylight savings time.
1891 * The actual timezone returned has an offset rounded to the nearest
1892 * minute.<br />
1893 * Time zones with an offset of more than +/- 18 hours  are disallowed,
1894 * and nil is returned.
1895 */
1896+ (NSTimeZone*) timeZoneForSecondsFromGMT: (NSInteger)seconds
1897{
1898  NSTimeZone	*zone;
1899  int		sign = seconds >= 0 ? 1 : -1;
1900  int           extra;
1901
1902  /*
1903   * Round the offset to the nearest minute, (for MacOS-X compatibility)
1904   * and ensure it is no more than 18 hours.
1905   */
1906  seconds *= sign;
1907  extra = seconds % 60;
1908  if (extra < 30)
1909    {
1910      seconds -= extra;
1911    }
1912  else
1913    {
1914      seconds += 60 - extra;
1915    }
1916  if (seconds > 64800)
1917    {
1918      return nil;
1919    }
1920  seconds *= sign;
1921  if (seconds % 900 == 0)
1922    {
1923      zone = commonAbsolutes[seconds/900 + 72];
1924    }
1925  else
1926    {
1927      pthread_mutex_lock(&zone_mutex);
1928      zone = (NSTimeZone*)NSMapGet(absolutes, (void*)(uintptr_t)seconds);
1929      pthread_mutex_unlock(&zone_mutex);
1930    }
1931  if (nil == zone)
1932    {
1933      zone = [[GSAbsTimeZone alloc] initWithOffset: seconds name: nil];
1934      zone = AUTORELEASE(zone);
1935    }
1936  return zone;
1937}
1938
1939/**
1940 * Returns a timezone for the specified abbreviation. The same abbreviations
1941 * are used in different regions so this isn't particularly useful.<br />
1942 * Calls NSTimeZone-abbreviation dictionary an so uses a lot of memory.
1943 */
1944+ (NSTimeZone*) timeZoneWithAbbreviation: (NSString*)abbreviation
1945{
1946  NSTimeZone	*zone;
1947  NSString	*name;
1948
1949  name = [[self abbreviationDictionary] objectForKey: abbreviation];
1950  if (name == nil)
1951    {
1952      zone = nil;
1953    }
1954  else
1955    {
1956      zone = [self timeZoneWithName: name data: nil];
1957    }
1958  return zone;
1959}
1960
1961/**
1962 * Returns a timezone for the specified name.
1963 */
1964+ (NSTimeZone*) timeZoneWithName: (NSString*)aTimeZoneName
1965{
1966  NSTimeZone	*zone;
1967
1968  zone = [defaultPlaceholderTimeZone initWithName: aTimeZoneName data: nil];
1969  return AUTORELEASE(zone);
1970}
1971
1972/**
1973 * Returns a timezone for aTimeZoneName, created from the supplied
1974 * time zone data. Data must be in TZ format as per the Olson database.
1975 */
1976+ (NSTimeZone*) timeZoneWithName: (NSString*)name data: (NSData*)data
1977{
1978  NSTimeZone	*zone;
1979
1980  zone = [defaultPlaceholderTimeZone initWithName: name data: data];
1981  return AUTORELEASE(zone);
1982}
1983
1984/**
1985 * Returns the abbreviation for this timezone now.
1986 * Invokes -abbreviationForDate:
1987 */
1988- (NSString*) abbreviation
1989{
1990  return [self abbreviationForDate: [NSDate date]];
1991}
1992
1993/**
1994 * Returns the abbreviation for this timezone at aDate.  This may differ
1995 * depending on whether daylight savings time is in effect or not.
1996 */
1997- (NSString*) abbreviationForDate: (NSDate*)aDate
1998{
1999  NSTimeZoneDetail	*detail;
2000  NSString		*abbr;
2001
2002  detail = [self timeZoneDetailForDate: aDate];
2003  abbr = [detail timeZoneAbbreviation];
2004
2005  return abbr;
2006}
2007
2008/**
2009 * Returns the Class for this object
2010 */
2011- (Class) classForCoder
2012{
2013  return NSTimeZoneClass;
2014}
2015
2016- (id) copyWithZone: (NSZone*)z
2017{
2018  return RETAIN(self);
2019}
2020
2021/**
2022 * Returns the data with which the receiver was initialised.
2023 */
2024- (NSData*) data
2025{
2026  return nil;
2027}
2028
2029/**
2030 * Returns the name of this object.
2031 */
2032- (NSString*) description
2033{
2034  return [self name];
2035}
2036
2037- (void) encodeWithCoder: (NSCoder*)aCoder
2038{
2039  [aCoder encodeObject: [self name]];
2040}
2041
2042- (NSUInteger) hash
2043{
2044  return [[self name] hash];
2045}
2046
2047- (id) init
2048{
2049  return [self initWithName: @"NSLocalTimeZone" data: nil];
2050}
2051
2052- (id) initWithCoder: (NSCoder*)aDecoder
2053{
2054  NSString	*name;
2055
2056  name = [aDecoder decodeObject];
2057  self = [self initWithName: name data: nil];
2058  return self;
2059}
2060
2061/**
2062 * Initialise a timezone with the supplied name.  May return a cached
2063 * timezone object rather than the newly created one.
2064 */
2065- (id) initWithName: (NSString*)name
2066{
2067  return [self initWithName: name data: nil];
2068}
2069
2070/**
2071 * Initialises a time zone object using the supplied data object.<br />
2072 * This method is intended for internal use by the NSTimeZone
2073 * class cluster.
2074 * Don't use it ... use -initWithName: instead.
2075 */
2076- (id) initWithName: (NSString*)name data: (NSData*)data
2077{
2078  [self notImplemented: _cmd];
2079  return nil;
2080}
2081
2082/**
2083 * Returns a boolean indicating whether daylight savings time is in
2084 * effect now.  Invokes -isDaylightSavingTimeForDate:
2085 */
2086- (BOOL) isDaylightSavingTime
2087{
2088  return [self isDaylightSavingTimeForDate: [NSDate date]];
2089}
2090
2091/**
2092 * Returns a boolean indicating whether daylight savings time is in
2093 * effect for this time zone at aDate.
2094 */
2095- (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate
2096{
2097  NSTimeZoneDetail	*detail;
2098  BOOL			isDST;
2099
2100  detail = [self timeZoneDetailForDate: aDate];
2101  isDST = [detail isDaylightSavingTimeZone];
2102
2103  return isDST;
2104}
2105
2106- (BOOL) isEqual: (id)other
2107{
2108  if (other == self)
2109    return YES;
2110  if ([other isKindOfClass: NSTimeZoneClass] == NO)
2111    return NO;
2112  return [self isEqualToTimeZone: other];
2113}
2114
2115/**
2116 * Returns YES if the time zones have the same name.
2117 */
2118- (BOOL) isEqualToTimeZone: (NSTimeZone*)aTimeZone
2119{
2120  if (aTimeZone == self)
2121    return YES;
2122  if ([[self name] isEqual: [aTimeZone name]] == NO)
2123    return NO;
2124  if (([self data] == nil && [aTimeZone data] == nil)
2125    || [[self name] isEqual: [aTimeZone name]] == YES)
2126    return YES;
2127  return NO;
2128}
2129
2130/**
2131 * Returns the name of the timezone
2132 */
2133- (NSString*) name
2134{
2135  return [self subclassResponsibility: _cmd];
2136}
2137
2138- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
2139{
2140  if ([aCoder isByref] == NO)
2141    {
2142      return self;
2143    }
2144  return [super replacementObjectForPortCoder: aCoder];
2145}
2146
2147/**
2148 * Returns the number of seconds by which the receiver differs
2149 * from Greenwich Mean Time at the current date and time.<br />
2150 * Invokes -secondsFromGMTForDate:
2151 */
2152- (NSInteger) secondsFromGMT
2153{
2154  return [self secondsFromGMTForDate: [NSDate date]];
2155}
2156
2157/**
2158 * Returns the number of seconds by which the receiver differs
2159 * from Greenwich Mean Time at the date aDate.<br />
2160 * If the time zone uses daylight savings time, the returned value
2161 * will vary at different times of year.
2162 */
2163- (NSInteger) secondsFromGMTForDate: (NSDate*)aDate
2164{
2165  NSTimeZoneDetail	*detail;
2166  int			offset;
2167
2168  detail = [self timeZoneDetailForDate: aDate];
2169  offset = [detail timeZoneSecondsFromGMT];
2170
2171  return offset;
2172}
2173
2174/**
2175 * DEPRECATED:  see NSTimeZoneDetail
2176 */
2177- (NSArray*) timeZoneDetailArray
2178{
2179  return [self subclassResponsibility: _cmd];
2180}
2181
2182/**
2183 * DEPRECATED:  see NSTimeZoneDetail
2184 */
2185- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)date
2186{
2187  return [self subclassResponsibility: _cmd];
2188}
2189
2190/**
2191 * Returns the name of this timezone.
2192 */
2193- (NSString*) timeZoneName
2194{
2195  return [self name];
2196}
2197
2198- (NSTimeInterval) daylightSavingTimeOffsetForDate: (NSDate *)aDate
2199{
2200#if GS_USE_ICU == 1
2201  NSTimeInterval result;
2202  UCalendar *cal;
2203  UErrorCode err = U_ZERO_ERROR;
2204
2205  cal = ICUCalendarSetup (self, nil);
2206  if (cal == NULL)
2207    return 0.0;
2208
2209  ucal_setMillis (cal, ([aDate timeIntervalSince1970] * 1000.0), &err);
2210  result = (double)ucal_get (cal, UCAL_DST_OFFSET, &err) / 1000.0;
2211  if (U_FAILURE(err))
2212    result = 0.0;
2213  ucal_close (cal);
2214
2215  return result;
2216#else
2217  return 0.0;   // FIXME
2218#endif
2219}
2220
2221- (NSDate *) nextDaylightSavingTimeTransitionAfterDate: (NSDate *)aDate
2222{
2223#if GS_USE_ICU == 1
2224  /* ICU doesn't provide transition information per se.
2225   * The canonical method of retrieving this piece of information is to
2226   * use binary search.
2227   */
2228
2229  int32_t originalOffset, currentOffset;
2230  UCalendar *cal;
2231  UErrorCode err = U_ZERO_ERROR;
2232  UDate currentTime;
2233  int i;
2234  NSDate* result = nil;
2235
2236  cal = ICUCalendarSetup (self, nil);
2237  if (cal == NULL)
2238    return nil;
2239
2240  currentTime = [aDate timeIntervalSince1970] * 1000.0;
2241  ucal_setMillis (cal, currentTime, &err);
2242  originalOffset = ucal_get (cal, UCAL_DST_OFFSET, &err);
2243  if (U_FAILURE(err))
2244    return nil;
2245
2246  /* First try to find the next transition by adding a week at a time */
2247  /* Avoid ending in an infinite loop in case there is no transition at all */
2248
2249  for (i = 0; i < 53; i++)
2250    {
2251      /* Add a single week */
2252      currentTime += WEEK_MILLISECONDS;
2253
2254	  ucal_setMillis (cal, currentTime, &err);
2255	  if (U_FAILURE(err))
2256        break;
2257
2258	  currentOffset = ucal_get (cal, UCAL_DST_OFFSET, &err);
2259	  if (U_FAILURE(err))
2260        break;
2261
2262	  if (currentOffset != originalOffset)
2263        {
2264          double interval = WEEK_MILLISECONDS / 2.0;
2265          /* Now use bisection to determine the exact moment */
2266
2267		  while (interval >= 1.0)
2268            {
2269              ucal_setMillis (cal, currentTime - interval, &err);
2270
2271              currentOffset = ucal_get (cal, UCAL_DST_OFFSET, &err);
2272
2273              if (currentOffset != originalOffset)
2274                currentTime -= interval; /* it is in the lower half */
2275
2276              interval /= 2.0;
2277            }
2278
2279           result =
2280             [NSDate dateWithTimeIntervalSince1970: floor(currentTime/1000.0)];
2281        }
2282    }
2283
2284  ucal_close (cal);
2285
2286  return result;
2287#else
2288  return nil;   // FIXME;
2289#endif
2290}
2291
2292- (NSTimeInterval) daylightSavingTimeOffset
2293{
2294  return [self daylightSavingTimeOffsetForDate: [NSDate date]];
2295}
2296
2297- (NSDate *) nextDaylightSavingTimeTransition
2298{
2299  return [self nextDaylightSavingTimeTransitionAfterDate: [NSDate date]];
2300}
2301
2302- (NSString *)localizedName: (NSTimeZoneNameStyle)style
2303                     locale: (NSLocale *)locale
2304{
2305#if GS_USE_ICU == 1
2306  UChar *result;
2307  const char *cLocale;
2308  int32_t len;
2309  UCalendar *cal;
2310  UErrorCode err = U_ZERO_ERROR;
2311
2312  cal = ICUCalendarSetup (self, locale);
2313  if (cal == NULL)
2314    return nil;
2315
2316  cLocale = [[locale localeIdentifier] UTF8String];
2317  result = NSZoneMalloc ([self zone], BUFFER_SIZE * sizeof(UChar));
2318  len = ucal_getTimeZoneDisplayName (cal, _NSToICUTZDisplayStyle(style),
2319    cLocale, result, BUFFER_SIZE, &err);
2320  if (len > BUFFER_SIZE)
2321    {
2322      result = NSZoneRealloc ([self zone], result, len * sizeof(UChar));
2323      ucal_getTimeZoneDisplayName (cal, _NSToICUTZDisplayStyle(style),
2324        cLocale, result, len, &err);
2325    }
2326
2327  return AUTORELEASE([[NSString alloc] initWithCharactersNoCopy: result
2328    length: len freeWhenDone: YES]);
2329#else
2330  return nil;   // FIXME;
2331#endif
2332}
2333
2334@end
2335
2336/**
2337 * This class serves no useful purpose in GNUstep other than to provide
2338 * a backup mechanism for handling abbreviations where the precomputed
2339 * data files cannot be found. It is provided primarily for backward
2340 * compatibility with the OpenStep spec.  It is missing entirely from MacOS-X.
2341 */
2342@implementation NSTimeZoneDetail
2343
2344- (NSString*) description
2345{
2346  return [NSString stringWithFormat: @"%@(%@, %s%"PRIdPTR")", [self name],
2347    [self timeZoneAbbreviation],
2348    ([self isDaylightSavingTimeZone]? "IS_DST, ": ""),
2349    [self timeZoneSecondsFromGMT]];
2350}
2351
2352/**
2353 * DEPRECATED: Class is no longer used.
2354 */
2355- (BOOL) isDaylightSavingTimeZone
2356{
2357  [self subclassResponsibility: _cmd];
2358  return NO;
2359}
2360
2361/**
2362 * DEPRECATED: Class is no longer used.
2363 */
2364- (NSString*) timeZoneAbbreviation
2365{
2366  return [self subclassResponsibility: _cmd];
2367}
2368
2369/**
2370 * DEPRECATED: Class is no longer used.
2371 */
2372- (NSInteger) timeZoneSecondsFromGMT
2373{
2374  [self subclassResponsibility: _cmd];
2375  return 0;
2376}
2377
2378@end
2379
2380
2381@implementation NSTimeZone (Private)
2382
2383/**
2384 * Common locations for timezone info on unix systems.
2385 */
2386static NSString *zoneDirs[] = {
2387#ifdef TZDIR
2388  @TZDIR,
2389#endif
2390  @"/usr/share/zoneinfo",
2391  @"/usr/lib/zoneinfo",
2392  @"/usr/local/share/zoneinfo",
2393  @"/usr/local/lib/zoneinfo",
2394  @"/etc/zoneinfo",
2395  @"/usr/local/etc/zoneinfo"
2396};
2397
2398/**
2399 * Returns the path to the named zone info file.
2400 */
2401+ (NSString*) _getTimeZoneFile: (NSString *)name
2402{
2403  static BOOL	beenHere = NO;
2404  NSString	*dir = nil;
2405  BOOL		isDir;
2406
2407  if (beenHere == NO && tzdir == nil)
2408    {
2409      pthread_mutex_lock(&zone_mutex);
2410      if (beenHere == NO && tzdir == nil)
2411	{
2412	  NSFileManager	*mgr = [NSFileManager defaultManager];
2413	  NSString	*zonedir = nil;
2414	  unsigned	i;
2415
2416	  for (i = 0; i < sizeof(zoneDirs)/sizeof(zoneDirs[0]); i++)
2417	    {
2418	      BOOL	isDir;
2419
2420	      zonedir
2421		= [zoneDirs[i] stringByAppendingPathComponent: POSIX_TZONES];
2422	      if ([mgr fileExistsAtPath: zonedir isDirectory: &isDir] && isDir)
2423		{
2424		  tzdir = RETAIN(zonedir);
2425		  break;  // use first one
2426		}
2427	    }
2428	  beenHere = YES;
2429	}
2430      pthread_mutex_unlock(&zone_mutex);
2431    }
2432  /* Use the system zone info if possible, otherwise, use our installed
2433     info.  */
2434  if (tzdir && [[NSFileManager defaultManager] fileExistsAtPath:
2435    [tzdir stringByAppendingPathComponent: name] isDirectory: &isDir] == YES
2436    && isDir == NO)
2437    {
2438      dir = tzdir;
2439    }
2440  if (dir == nil)
2441    {
2442      dir = _time_zone_path (ZONES_DIR, nil);
2443    }
2444  return [dir stringByAppendingPathComponent: name];
2445}
2446
2447+ (void) _notified: (NSNotification*)n
2448{
2449  NSString      *name;
2450
2451  /* If the name of the system time zone has changed ...
2452   * get a new system time zone.
2453   */
2454  name = [[NSUserDefaults standardUserDefaults] stringForKey: LOCALDBKEY];
2455  if ([name length] > 0 && [name isEqual: [[self systemTimeZone] name]] == NO)
2456    {
2457      [self resetSystemTimeZone];
2458      [self systemTimeZone];
2459    }
2460}
2461@end
2462
2463
2464#if	defined(_WIN32)
2465/* Timezone information data as stored in the registry */
2466typedef struct TZI_format {
2467	LONG       Bias;
2468	LONG       StandardBias;
2469	LONG       DaylightBias;
2470	SYSTEMTIME StandardDate;
2471	SYSTEMTIME DaylightDate;
2472} TZI;
2473
2474static inline unsigned int
2475lastDayOfGregorianMonth(int month, int year)
2476{
2477  switch (month)
2478    {
2479      case 2:
2480        if ((((year % 4) == 0) && ((year % 100) != 0))
2481          || ((year % 400) == 0))
2482          return 29;
2483        else
2484          return 28;
2485      case 4:
2486      case 6:
2487      case 9:
2488      case 11: return 30;
2489      default: return 31;
2490    }
2491}
2492
2493/* IMPORT from NSCalendar date */
2494void
2495GSBreakTime(NSTimeInterval when,
2496  NSInteger *year, NSInteger *month, NSInteger *day,
2497  NSInteger *hour, NSInteger *minute, NSInteger *second, NSInteger *mil);
2498
2499
2500@implementation GSWindowsTimeZone
2501
2502- (NSString*) abbreviationForDate: (NSDate*)aDate
2503{
2504  if ([self isDaylightSavingTimeForDate: aDate])
2505    return daylightZoneNameAbbr;
2506  return timeZoneNameAbbr;
2507}
2508
2509- (NSData*) data
2510{
2511  return 0;
2512}
2513
2514- (void) dealloc
2515{
2516  RELEASE(timeZoneName);
2517  RELEASE(daylightZoneName);
2518  RELEASE(timeZoneNameAbbr);
2519  RELEASE(daylightZoneNameAbbr);
2520  [super dealloc];
2521}
2522
2523- (id) initWithName: (NSString*)name data: (NSData*)data
2524{
2525  HKEY	regDirKey;
2526  BOOL	isNT = NO;
2527  BOOL	regFound=NO;
2528  BOOL	tzFound = NO;
2529
2530  /* Open the key in the local machine hive where
2531   * the time zone data is stored. */
2532  if (ERROR_SUCCESS == RegOpenKeyExW(HKEY_LOCAL_MACHINE,
2533    L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones",
2534    0,
2535    KEY_READ,
2536    &regDirKey))
2537    {
2538      isNT = YES;
2539      regFound = YES;
2540    }
2541  else
2542    {
2543      if (ERROR_SUCCESS == RegOpenKeyExW(HKEY_LOCAL_MACHINE,
2544          L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones",
2545          0,
2546          KEY_READ,
2547          &regDirKey))
2548        {
2549          regFound = YES;
2550        }
2551    }
2552
2553  if (regFound)
2554    {
2555      /* Iterate over all subKeys in the registry to find the right one.
2556         Unfortunately name is a localized value. The keys in the registry are
2557         unlocalized names. */
2558      wchar_t  achKey[255];              // buffer for subkey name
2559      DWORD    cbName;                   // size of name string
2560      wchar_t  achClass[MAX_PATH] = L""; // buffer for class name
2561      DWORD    cchClassName = MAX_PATH;  // size of class string
2562      DWORD    cSubKeys = 0;             // number of subkeys
2563      DWORD    cbMaxSubKey;              // longest subkey size
2564      DWORD    cchMaxClass;              // longest class string
2565      DWORD    cValues;                  // number of values for key
2566      DWORD    cchMaxValue;              // longest value name
2567      DWORD    cbMaxValueData;           // longest value data
2568      DWORD    cbSecurityDescriptor;     // size of security descriptor
2569      FILETIME ftLastWriteTime;          // last write time
2570      DWORD     i, retCode;
2571
2572      /* Get the class name and the value count. */
2573      retCode = RegQueryInfoKeyW(
2574        regDirKey,               // key handle
2575        achClass,                // buffer for class name
2576        &cchClassName,           // size of class string
2577        NULL,                    // reserved
2578        &cSubKeys,               // number of subkeys
2579        &cbMaxSubKey,            // longest subkey size
2580        &cchMaxClass,            // longest class string
2581        &cValues,                // number of values for this key
2582        &cchMaxValue,            // longest value name
2583        &cbMaxValueData,         // longest value data
2584        &cbSecurityDescriptor,   // security descriptor
2585        &ftLastWriteTime);       // last write time
2586
2587      if (cSubKeys && (retCode == ERROR_SUCCESS))
2588    	{
2589          unsigned wLen = [name length];
2590          wchar_t *wName = malloc((wLen+1) * sizeof(wchar_t));
2591
2592          if (wName)
2593            {
2594              [name getCharacters: wName];
2595              wName[wLen] = 0;
2596              for (i = 0; i < cSubKeys && !tzFound; i++)
2597                {
2598                  cbName = 255;
2599
2600                  retCode = RegEnumKeyExW(regDirKey, i, achKey, &cbName,
2601                    NULL, NULL, NULL, &ftLastWriteTime);
2602                  if (retCode == ERROR_SUCCESS)
2603                    {
2604                      wchar_t keyBuffer[16384];
2605                      HKEY regKey;
2606
2607                      if (isNT)
2608                        wcscpy(keyBuffer, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\");
2609                      else
2610                        wcscpy(keyBuffer, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones\\");
2611
2612                      wcscat(keyBuffer, achKey);
2613                      if (ERROR_SUCCESS == RegOpenKeyExW(HKEY_LOCAL_MACHINE,
2614                        keyBuffer, 0, KEY_READ, &regKey))
2615                        {
2616                          wchar_t buf[256];
2617                          wchar_t standardName[256];
2618                          wchar_t daylightName[256];
2619                          DWORD bufsize;
2620                          DWORD type;
2621
2622                          /* check standardname */
2623                          standardName[0] = L'\0';
2624                          bufsize = sizeof(buf);
2625                          if (ERROR_SUCCESS == RegQueryValueExW(regKey,
2626                            L"Std", 0, &type, (BYTE *)buf, &bufsize))
2627                            {
2628                              wcscpy(standardName, buf);
2629                              if (wcscmp(standardName, wName) == 0)
2630                                tzFound = YES;
2631                            }
2632
2633                          /* check daylightname */
2634                          daylightName[0] = L'\0';
2635                          bufsize = sizeof(buf);
2636                          if (ERROR_SUCCESS == RegQueryValueExW(regKey,
2637                            L"Dlt", 0, &type, (BYTE *)buf, &bufsize))
2638                            {
2639                              wcscpy(daylightName, buf);
2640                              if (wcscmp(daylightName, wName) == 0)
2641                                tzFound = YES;
2642                            }
2643
2644                          if (tzFound)
2645                            {
2646                              /* Read in the time zone data */
2647                              bufsize = sizeof(buf);
2648                              if (ERROR_SUCCESS == RegQueryValueExW(regKey,
2649                                L"TZI", 0, &type, (BYTE *)buf, &bufsize))
2650                                {
2651                                  TZI *tzi = (void*)buf;
2652                                  Bias = tzi->Bias;
2653                                  StandardBias = tzi->StandardBias;
2654                                  DaylightBias = tzi->DaylightBias;
2655                                  StandardDate = tzi->StandardDate;
2656                                  DaylightDate = tzi->DaylightDate;
2657                                }
2658
2659                              /* Set the standard name for the time zone. */
2660                              if (wcslen(standardName))
2661                                {
2662                                  int a, b;
2663
2664                                  ASSIGN(timeZoneName,
2665                                    [NSString stringWithCharacters: standardName
2666                                    length: wcslen(standardName)]);
2667
2668                                  /* Abbr generated here is IMHO
2669                                   * a bit suspicous but I kept it */
2670                                  for (a = 0, b = 0; standardName[a]; a++)
2671                                    {
2672                                      if (iswupper(standardName[a]))
2673                                        standardName[b++] = standardName[a];
2674                                    }
2675                                  standardName[b] = L'\0';
2676                                  ASSIGN(timeZoneNameAbbr,
2677                                    [NSString stringWithCharacters: standardName
2678                                    length: wcslen(standardName)]);
2679                                }
2680
2681                              /* Set the daylight savings name
2682                               * for the time zone. */
2683                              if (wcslen(daylightName))
2684                                {
2685                                  int a, b;
2686
2687                                  ASSIGN(daylightZoneName,
2688                                    [NSString stringWithCharacters: daylightName
2689                                    length: wcslen(daylightName)]);
2690
2691                                  /* Abbr generated here is IMHO
2692                                   * a bit suspicous but I kept it */
2693                                  for (a = 0, b = 0; daylightName[a]; a++)
2694                                    {
2695                                      if (iswupper(daylightName[a]))
2696                                        daylightName[b++] = daylightName[a];
2697                                    }
2698                                  daylightName[b] = L'\0';
2699                                  ASSIGN(daylightZoneNameAbbr,
2700                                    [NSString stringWithCharacters: daylightName
2701                                    length: wcslen(daylightName)]);
2702                                }
2703                            }
2704                          RegCloseKey(regKey);
2705                        }
2706                    }
2707                }
2708              free(wName);
2709            }
2710        }
2711      RegCloseKey(regDirKey);
2712    }
2713  if (NO == tzFound)
2714    {
2715      DESTROY(self);
2716    }
2717  return self;
2718}
2719
2720- (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate
2721{
2722  NSInteger year, month, day, hour, minute, second, mil;
2723  int	dow;
2724  int daylightdate, count, maxdate;
2725  NSTimeInterval when;
2726
2727  if (DaylightDate.wMonth == 0)
2728    return NO;
2729
2730  when = [aDate timeIntervalSinceReferenceDate] - Bias*60;
2731
2732  GSBreakTime(when, &year, &month, &day, &hour, &minute, &second, &mil);
2733
2734  // Check north globe
2735  if (StandardDate.wMonth >= DaylightDate.wMonth)
2736    {
2737      // Before April or after October is Std
2738      if (month < DaylightDate.wMonth || month > StandardDate.wMonth)
2739        {
2740	  return NO;
2741	}
2742      // After April and before October is DST
2743      if (month > DaylightDate.wMonth && month < StandardDate.wMonth)
2744        {
2745	  return YES;
2746	}
2747    }
2748  else
2749    {
2750      /* check south globe
2751       * Before April or after October is DST
2752       */
2753      if (month < StandardDate.wMonth || month > DaylightDate.wMonth)
2754        {
2755	  return YES;
2756	}
2757      // After April and before October is Std
2758      if (month > StandardDate.wMonth && month < DaylightDate.wMonth)
2759        {
2760	  return NO;
2761	}
2762    }
2763
2764  dow = ((NSInteger)((when / 86400.0) + GREGORIAN_REFERENCE)) % 7;
2765  if (dow < 0)
2766    dow += 7;
2767
2768  if (month == DaylightDate.wMonth /* April */)
2769    {
2770      daylightdate = day - dow + DaylightDate.wDayOfWeek;
2771      maxdate = lastDayOfGregorianMonth(DaylightDate.wMonth, year)-7;
2772      while (daylightdate > 7)
2773        daylightdate -= 7;
2774      if (daylightdate < 1)
2775        daylightdate += 7;
2776      count = DaylightDate.wDay;
2777      while (count > 1 && daylightdate < maxdate)
2778        {
2779          daylightdate += 7;
2780          count--;
2781        }
2782      if (day > daylightdate)
2783        return YES;
2784      if (day < daylightdate)
2785        return NO;
2786      if (hour > DaylightDate.wHour)
2787        return YES;
2788      if (hour < DaylightDate.wHour)
2789        return NO;
2790      if (minute > DaylightDate.wMinute)
2791        return YES;
2792      if (minute < DaylightDate.wMinute)
2793        return NO;
2794      if (second > DaylightDate.wSecond)
2795        return YES;
2796      if (second < DaylightDate.wSecond)
2797        return NO;
2798      if (mil >= DaylightDate.wMilliseconds)
2799        return YES;
2800      return NO;
2801    }
2802  if (month == StandardDate.wMonth /* October */)
2803    {
2804      daylightdate = day - dow + StandardDate.wDayOfWeek;
2805      maxdate = lastDayOfGregorianMonth(StandardDate.wMonth, year)-7;
2806      while (daylightdate > 7)
2807        daylightdate -= 7;
2808      if (daylightdate < 1)
2809        daylightdate += 7;
2810      count = StandardDate.wDay;
2811      while (count > 1 && daylightdate < maxdate)
2812        {
2813          daylightdate += 7;
2814          count--;
2815        }
2816      if (day > daylightdate)
2817        return NO;
2818      if (day < daylightdate)
2819        return YES;
2820      if (hour > StandardDate.wHour)
2821        return NO;
2822      if (hour < StandardDate.wHour)
2823        return YES;
2824      if (minute > StandardDate.wMinute)
2825        return NO;
2826      if (minute < StandardDate.wMinute)
2827        return YES;
2828      if (second > StandardDate.wSecond)
2829        return NO;
2830      if (second < StandardDate.wSecond)
2831        return YES;
2832      if (mil >= StandardDate.wMilliseconds)
2833        return NO;
2834      return YES;
2835    }
2836  return NO; // Never reached
2837}
2838
2839- (NSString*) name
2840{
2841  TIME_ZONE_INFORMATION tz;
2842  DWORD DST = GetTimeZoneInformation(&tz);
2843
2844  if (DST == TIME_ZONE_ID_DAYLIGHT)
2845    {
2846      return daylightZoneName;
2847    }
2848  else
2849    {
2850      return timeZoneName;
2851    }
2852}
2853
2854- (NSInteger) secondsFromGMTForDate: (NSDate*)aDate
2855{
2856  if ([self isDaylightSavingTimeForDate: aDate])
2857    return -Bias*60 - DaylightBias*60;
2858  return -Bias*60 - StandardBias*60;
2859}
2860
2861- (NSArray*) timeZoneDetailArray
2862{
2863  return [NSArray arrayWithObjects:
2864    [[[GSTimeZoneDetail alloc] initWithTimeZone: self
2865      withAbbrev: timeZoneNameAbbr
2866      withOffset: -Bias*60 - StandardBias*60
2867      withDST: NO] autorelease],
2868    [[[GSTimeZoneDetail alloc] initWithTimeZone: self
2869      withAbbrev: daylightZoneNameAbbr
2870      withOffset: -Bias*60 - DaylightBias*60
2871      withDST: YES] autorelease], 0];
2872}
2873
2874- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)aDate
2875{
2876  GSTimeZoneDetail	*detail;
2877  int offset;
2878  BOOL isDST = [self isDaylightSavingTimeForDate: aDate];
2879  NSString *abbr;
2880
2881  if (isDST)
2882    {
2883      offset = -Bias*60 - DaylightBias*60;
2884      abbr = daylightZoneNameAbbr;
2885    }
2886  else
2887    {
2888      offset = -Bias*60 - StandardBias*60;
2889      abbr = timeZoneNameAbbr;
2890    }
2891  detail = [GSTimeZoneDetail alloc];
2892  detail = [detail initWithTimeZone: self
2893                         withAbbrev: abbr
2894                         withOffset: offset
2895                            withDST: isDST];
2896  return detail;
2897}
2898
2899- (NSString*) timeZoneName
2900{
2901  return [self name];
2902}
2903@end
2904#endif // _WIN32
2905
2906
2907@implementation	GSTimeZone
2908
2909/**
2910 * Perform a binary search of a transitions table to locate the index
2911 * of the transition to use for a particular time interval since 1970.<br />
2912 * We locate the index of the highest transition before the date, or zero
2913 * if there is no transition before it.
2914 */
2915static TypeInfo*
2916chop(NSTimeInterval since, GSTimeZone *zone)
2917{
2918  int32_t		when = (int32_t)since;
2919  int32_t		*trans = zone->trans;
2920  unsigned		hi = zone->n_trans;
2921  unsigned		lo = 0;
2922  unsigned int		i;
2923
2924  if (hi == 0 || trans[0] > when)
2925    {
2926      unsigned	n_types = zone->n_types;
2927
2928      /*
2929       * If the first transition is greater than our date,
2930       * we locate the first non-DST transition and use that offset,
2931       * or just use the first transition.
2932       */
2933      for (i = 0; i < n_types; i++)
2934	{
2935	  if (zone->types[i].isdst == 0)
2936	    {
2937	      return &zone->types[i];
2938	    }
2939	}
2940      return &zone->types[0];
2941    }
2942  else
2943    {
2944      for (i = hi/2; hi != lo; i = (hi + lo)/2)
2945	{
2946	  if (when < trans[i])
2947	    {
2948	      hi = i;
2949	    }
2950	  else if (when > trans[i])
2951	    {
2952	      lo = ++i;
2953	    }
2954	  else
2955	    {
2956	      break;
2957	    }
2958	}
2959      /*
2960       * If we went off the top of the table or the closest transition
2961       * was later than our date, we step back to find the last
2962       * transition before our date.
2963       */
2964      if (i > 0 && (i == zone->n_trans || trans[i] > when))
2965	{
2966	  i--;
2967	}
2968      return &zone->types[zone->idxs[i]];
2969    }
2970}
2971
2972static NSTimeZoneDetail*
2973newDetailInZoneForType(GSTimeZone *zone, TypeInfo *type)
2974{
2975  GSTimeZoneDetail	*detail;
2976
2977  detail = [GSTimeZoneDetail alloc];
2978  detail = [detail initWithTimeZone: zone
2979			 withAbbrev: type->abbreviation
2980			 withOffset: type->offset
2981			    withDST: type->isdst];
2982  return detail;
2983}
2984
2985- (NSString*) abbreviationForDate: (NSDate*)aDate
2986{
2987  TypeInfo	*type = chop([aDate timeIntervalSince1970], self);
2988
2989  return type->abbreviation;
2990}
2991
2992- (NSData*) data
2993{
2994  return timeZoneData;
2995}
2996
2997- (void) dealloc
2998{
2999  RELEASE(timeZoneName);
3000  RELEASE(timeZoneData);
3001  RELEASE(abbreviations);
3002  if (types != 0)
3003    {
3004      NSZoneFree(NSDefaultMallocZone(), types);
3005    }
3006  [super dealloc];
3007}
3008
3009- (id) initWithName: (NSString*)name data: (NSData*)data
3010{
3011  static NSString	*fileException = @"GSTimeZoneFileException";
3012
3013  timeZoneName = [name copy];
3014  timeZoneData = [data copy];
3015  NS_DURING
3016    {
3017      const void	*bytes = [timeZoneData bytes];
3018      unsigned		length = [timeZoneData length];
3019      void		*buf;
3020      unsigned		pos = 0;
3021      unsigned		i, charcnt;
3022      unsigned char	*abbr;
3023      struct tzhead	*header;
3024
3025      if (length < sizeof(struct tzhead))
3026	{
3027	  [NSException raise: fileException
3028		      format: @"File is too small"];
3029	}
3030      header = (struct tzhead *)(bytes + pos);
3031      pos += sizeof(struct tzhead);
3032#ifdef TZ_MAGIC
3033      if (memcmp(header->tzh_magic, TZ_MAGIC, strlen(TZ_MAGIC)) != 0)
3034	{
3035	  [NSException raise: fileException
3036		      format: @"TZ_MAGIC is incorrect"];
3037	}
3038#endif
3039      n_trans = GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_timecnt);
3040      n_types = GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_typecnt);
3041      charcnt = GSSwapBigI32ToHost(*(int32_t*)(void*)header->tzh_charcnt);
3042
3043      i = pos;
3044      i += sizeof(int32_t)*n_trans;
3045      if (i > length)
3046	{
3047	  [NSException raise: fileException
3048		      format: @"Transitions list is truncated"];
3049	}
3050      i += n_trans;
3051      if (i > length)
3052	{
3053	  [NSException raise: fileException
3054		      format: @"Transition indexes are truncated"];
3055	}
3056      i += sizeof(struct ttinfo)*n_types;
3057      if (i > length)
3058	{
3059	  [NSException raise: fileException
3060		      format: @"Types list is truncated"];
3061	}
3062      if (i + charcnt > length)
3063	{
3064	  [NSException raise: fileException
3065		      format: @"Abbreviations list is truncated"];
3066	}
3067
3068      /*
3069       * Now calculate size we need to store the information
3070       * for efficient access ... not the same saze as the data
3071       * we received.
3072       */
3073      i = n_trans * (sizeof(int32_t)+1) + n_types * sizeof(TypeInfo);
3074      buf = NSZoneMalloc(NSDefaultMallocZone(), i);
3075      types = (TypeInfo*)buf;
3076      buf += (n_types * sizeof(TypeInfo));
3077      trans = (int32_t*)buf;
3078      buf += (n_trans * sizeof(int32_t));
3079      idxs = (unsigned char*)buf;
3080
3081      /* Read in transitions. */
3082      for (i = 0; i < n_trans; i++)
3083	{
3084	  trans[i] = GSSwapBigI32ToHost(*(int32_t*)(bytes + pos));
3085	  pos += sizeof(int32_t);
3086	}
3087      for (i = 0; i < n_trans; i++)
3088	{
3089	  idxs[i] = *(unsigned char*)(bytes + pos);
3090	  pos++;
3091	}
3092      for (i = 0; i < n_types; i++)
3093	{
3094	  struct ttinfo	*ptr = (struct ttinfo*)(bytes + pos);
3095          uint32_t      off;
3096
3097	  types[i].isdst = (ptr->isdst != 0 ? YES : NO);
3098	  types[i].abbr_idx = ptr->abbr_idx;
3099          memcpy(&off, ptr->offset, 4);
3100	  types[i].offset = GSSwapBigI32ToHost(off);
3101	  pos += sizeof(struct ttinfo);
3102	}
3103      abbr = (unsigned char*)(bytes + pos);
3104      {
3105	id		abbrevs[charcnt];
3106	unsigned	count = 0;
3107	unsigned	used = 0;
3108
3109	memset(abbrevs, '\0', sizeof(id)*charcnt);
3110	for (i = 0; i < n_types; i++)
3111	  {
3112	    int	loc = types[i].abbr_idx;
3113
3114	    if (abbrevs[loc] == nil)
3115	      {
3116		abbrevs[loc]
3117		  = [[NSString alloc] initWithUTF8String: (char*)abbr + loc];
3118		count++;
3119	      }
3120	    types[i].abbreviation = abbrevs[loc];
3121	  }
3122	/*
3123	 * Now we have created all the abbreviations, we put them in an
3124	 * array for easy access later and easy deallocation if/when
3125	 * the receiver is deallocated.
3126	 */
3127	i = charcnt;
3128	while (i-- > count)
3129	  {
3130	    if (abbrevs[i] != nil)
3131	      {
3132		while (abbrevs[used] != nil)
3133		  {
3134		    used++;
3135		  }
3136		abbrevs[used] = abbrevs[i];
3137		abbrevs[i] = nil;
3138		if (++used >= count)
3139		  {
3140		    break;
3141		  }
3142	      }
3143	  }
3144	abbreviations = [[NSArray alloc] initWithObjects: abbrevs count: count];
3145	while (count-- > 0)
3146	  {
3147	    RELEASE(abbrevs[count]);
3148	  }
3149      }
3150
3151      pthread_mutex_lock(&zone_mutex);
3152      [zoneDictionary setObject: self forKey: timeZoneName];
3153      pthread_mutex_unlock(&zone_mutex);
3154    }
3155  NS_HANDLER
3156    {
3157      DESTROY(self);
3158      NSLog(@"Unable to obtain time zone `%@'... %@", name, localException);
3159      if ([localException name] != fileException)
3160	{
3161	  [localException raise];
3162	}
3163    }
3164  NS_ENDHANDLER
3165  return self;
3166}
3167
3168- (BOOL) isDaylightSavingTimeForDate: (NSDate*)aDate
3169{
3170  TypeInfo	*type = chop([aDate timeIntervalSince1970], self);
3171
3172  return type->isdst;
3173}
3174
3175- (NSString*) name
3176{
3177  return timeZoneName;
3178}
3179
3180- (NSInteger) secondsFromGMTForDate: (NSDate*)aDate
3181{
3182  TypeInfo	*type = chop([aDate timeIntervalSince1970], self);
3183
3184  return type->offset;
3185}
3186
3187- (NSArray*) timeZoneDetailArray
3188{
3189  NSTimeZoneDetail	*details[n_types];
3190  unsigned		i;
3191  NSArray		*array;
3192
3193  for (i = 0; i < n_types; i++)
3194    {
3195      details[i] = newDetailInZoneForType(self, &types[i]);
3196    }
3197  array = [NSArray arrayWithObjects: details count: n_types];
3198  for (i = 0; i < n_types; i++)
3199    {
3200      RELEASE(details[i]);
3201    }
3202  return array;
3203}
3204
3205- (NSTimeZoneDetail*) timeZoneDetailForDate: (NSDate*)aDate
3206{
3207  TypeInfo		*type;
3208  NSTimeZoneDetail	*detail;
3209
3210  type = chop([aDate timeIntervalSince1970], self);
3211  detail = newDetailInZoneForType(self, type);
3212  return AUTORELEASE(detail);
3213}
3214
3215- (NSString*) timeZoneName
3216{
3217  return timeZoneName;
3218}
3219
3220@end
3221
3222