Subject: Thread-safe timezones: running code
From: Jonathan Lennox <[email-masked]>
To: [email-masked]
Date: Mon, 18 Jun 2001 16:57:42 -0400 (EDT)

I've finished a first cut (call it beta 1?) of a tzcode-based implementation
of my thread-safe time zone API.

The API as defined here was fairly straightforward to implement.  Only a few
changes were necessary.

* The most noticable change is that the type representing a time zone
  object is now 'struct tzinfo', not 'struct timezone'.  I discovered that
  'struct timezone' is the type of the deprecated second argument to BSD
  gettimeofday().

* Other than that, I clarified some things, notably the fact that tzname and
  the other externs aren't defined when you're using pthread_settz().  (I
  could have implemented them using the trick used for thread-safe errno,
  but it seemed like too much trouble for a badly-defined API.)

The attached patch also contains code to make the 'normal' time functions
thread-safe.   This is based on FreeBSD's changes to tzcode to make it
thread-safe, but rather than sticking #ifdef's directly into the code, as
FreeBSD does, I instead implemented them as macros.  You can control your
thread model based on a Makefile setting.

The only part of the thread-safety API that isn't currently implemented in
my patch is that the error return value of strftime_z() is still the same as
that of strftime(), rather than using the snprintf() error return style.
This requires annoyingly intrusive changes to tzcode's strftime(), so I've
put it off for now.

Attached, find two files:
1.  The revised (v3) API proposal.
2.  A patch to tzcode2001c implementing the proposal.

Comments are very welcome!

A proposal for thread-safe time zone information.  A set of extensions to
ISO C (99) and IEEE POSIX (200x).

by Jonathan Lennox, Columbia University.

Version 3.

Summary:

A new data type is defined, 'struct tzinfo', that represents the time zone
information for a particular region.  Versions of the ISO C time conversion
functions are defined that take this data type as an argument.

Definitions:

Time zone: The set of information necessary to correctly convert
  bidirectionally between a time_t and a struct tm, for a particular
  geographic region.

Wall clock time: The system's best approximation of the local time in its
  physical location, independent of the physical location of any user.

Functions defined in this proposal:

Functions manipulating struct tzinfo values:

Name
  tz_prep -- prepare a time zone object, based on a descriptive string.

Synopsis
  #include <time.h>
  int tz_prep(struct tzinfo** tz, const char *tzstring);

Description
  The tz_prep() function creates a new time zone object, corresponding to the
  given time zone name.  tzstring is a pointer to a string, or NULL.

  The two defined values of tzstring are NULL, representing the system's
  best approximation of its wall clock time, and the zero-length string "",
  representing the system's best approximation of Coordinated Universal Time
  (UTC).

  On successful completion, *tz will contain a pointer to a struct tzinfo,
  contains all necessary information to represent times in the specified
  time zone.  It has no externally-visible elements.

  POSIX extension:
  In a POSIX-based environment, the syntax of tzstring is the same as 
  that of the "TZ" environment variable, including the
  implementation-defined values beginning with ':'.

Return Value
  On successful completion, tz_prep() returns a value of 0 and fills in *tz.
  On failure, an error number is returned and no resources are allocated.

Errors
  The tz_prep() function shall fail if:

  ENOMEM
    Not enough memory is available to create the time zone object.

  ENOENT
    No known time zone corresponds to tzname.

  The tz_prep() function may fail if:

  * There is a problem with the system's configuration (such as an on-disk
    time zone database) which caused retrieval of the time zone information
    to fail unexpectedly.  In this case any appropriate error number may be
    returned.

Rationale and commentary
  Systems which define extensions beyond POSIX (such as the Olson time
  zone names) for the "TZ" environment variable should support the same
  extensions for tzstring.

  Under the POSIX rules, strings such as "UTC0" can also represent UTC, but
  the empty string "" is the portable representation.  This representation
  is also correctly locale-independent -- "UTC0" in fr_FR generates times
  with the incorrect time zone abbreviation "UTC", whereas "" in that locale
  should use the correct time zone abbreviation "TUC" if the time zone code
  correctly supports locales.

  This interface is designed so that tz_prep(getenv("TZ")) will return an
  object describing the default time zone object that non-thread-aware
  versions of the time functions will use by default, provided TZ (if
  set) is set to a valid time zone name.

  Question for discussion: should there be an ISO C way of saying "the
  timezone which localtime() and mktime() would use by default"?  Currently
  this is only possible for POSIX, using thee tz_prep(getenv("TZ")) idiom
  mentioned above.

Name
  tz_free -- Free resources allocated for a time zone object

Synopsis
  #include <time.h>
  void tz_free(struct tzinfo* tzobj);

Description
  The tz_free() function frees the resources allocated for a time zone
  object returned by a call to tz_prep().

Return Value
  None.

Errors
  None.

Rationale and commentary
  None needed.

Functions using struct tzinfo to manipulate time values:

Name
  time_make - derive a time_t from a struct tm, in a specified time zone.

Synopsis
  #include <time.h>  
  int time_make(time_t *clock, struct tm *tm, const struct tzinfo *tz);

Description
  This function interprets the broken-down time in *tm as a local time in
  the timezone specified in *tz, and writes the corresponding value
  into *clock, using the same encoding as that of the values returned by the
  'time' function.

  The original values of the tm_wday and tm_yday components of *tm are
  ignored, and the original values of the other components are not
  restricted to their normal ranges.  (A positive or zero value for tm_isdst
  causes time_make() to presume initially that summer time (for example,
  Daylight Saving Time) is or is not in effect for the specified time,
  respectively.  A negative value for tm_isdst causes the time_make()
  function to attempt to divine whether summer time is in effect for the
  specified time.)

  On successful completion, the values of the tm_wday and tm_yday components
  of *tm are set appropriately, and the other components are set to
  represent the specified calendar time, but with their values forced to
  their normal ranges; the final value of tm_mday is not set until tm_mon
  and tm_year are determined.

Returns
  time_make returns 0 on sucessful completion, sets *clock, and normalizes
  *tm.  On failure, it returns an error number and leaves the values of
  *clock and *tm undefined.

Errors
  time_make() shall fail if:

  ERANGE
    *tm does not represent a time representable by a time_t value.

  time_make() may fail if:

  EINVAL
    *tm does not represent a possible time in the timezone *tz.  (For
    instance, a leap-forward interval.)

Rationale and commentary
  This function is the generalization of the ISO C function mktime() and the
  BSD/tzcode function timegm().

  The name and calling conventions of this function are inspired by Markus
  Kuhn's proposed xtime_make().  struct xtime is a much more sophisticated
  and better-defined representation of time than time_t; this proposal is
  designed to be less ambitious while still leaving room for these future
  improvements.

  Following Kuhn, this function corrects a flaw of mktime()/timegm().  Those
  functions use the value (time_t)-1 to represent an error return status.
  However, this value can also be a correct translation of a struct tm
  representing the time December 31, 1969, 23:59:59 GMT (assuming POSIX
  time_t's).  This function, instead, uses an out-of-band method to indicate
  error conditions, leaving the entire time_t space free to represent
  valid values.

  Given an impossible time value for the time zone (e.g. one that occurs
  during a leap-forward interval), implemenetations have a choice of failing
  and returning EINVAL, or normalizing *tm to a nearby valid time.

Name
  time_breakup - derive a struct tm from a time_t, in a specified time
                 zone.

Synopsis
  #include <time.h>
  int time_breakup(struct tm *result, const time_t *clock,
                   const struct tzinfo *tz);

Description
  Fill in *result with the values corresponding to *clock in the time zone
  *tz.

Return value
  time_breakup() returns 0, and fills in *result, always.

Errors
  None.

Rationale and commentary
  This function is the generalization of the ISO C functions localtime() and
  gmtime(), and the POSIX functions localtime_r() and gmtime_r().

  It is inspired by Markus Kuhn's xtime_breakup().

  The function assumes that a 'struct tzinfo' will be able to do something
  reasonable over the whole range of a time_t.  This isn't necessarily
  true; it might be sensible to define an ERANGE error return value?

  This function omits the localtime side effect that it sets the value of
  the global variable char *tzname[2].

  It's not clear how best to handle the BSD/tzcode tm_zone field of struct
  tm.  This is defined as a char*, but since there is no tm_free() function,
  the data it points to cannot be dynamically allocated by time_breakup().
  One possibility would be to have tm_zone point into data stored within
  'struct tz', but then it would be invalidated when tz_free() was called.
  A second possibility would be to define tm_zone as char
  tm_zone[TZNAME_MAX], but this would break binary compatibility.  A third
  possibility would be to deprecate tm_zone, and have time_breakup leave it
  NULL.  Zone names can be obtained by calling strftime with a "%Z" format
  string (assuming POSIX strftime).

Name
  strftime_z - generate a text representation of a time value, in a given
  time zone.

Synopsis
  #include <time.h>
  size_t
  strftime_z(char * restrict buf, size_t maxsize,
             const char * restrict format,
             const struct tm * restrict timeptr,
             const struct tzinfo * restrict tz);

Description
  strftime_z() formats the information from 'timeptr' into the buffer 'buf'
  according to the format specified by 'format' and the system locale.

  The format specified by 'format' is the same as that of strftime().

  POSIX note: All POSIX extensions to strftime() apply to strftime_z() as
  well.

Return value
  On successful completion, strftime_z fills in 'buf' and returns the number
  of bytes converted.  On failure, strftime_z returns the number of bytes of
  buffer that would be required to fully perform the conversion (including
  the terminating NUL), and leaves 'buf' in an indeterminate state.

Errors
  None.

Rationale and commentary
  This function is a generalization of strftime().

  The return value of this function has changed from strftime(), however.
  strftime() returns 0 if the buffer is not large enough.  This function,
  instead, follows the example of ISO C 99's snprintf(), which allows an
  appropriately-sized buffer to be allocated in one step after a failure,
  rather than requiring a binary search.  The error status can be easily
  checked by checking (ret <= maxbuf). 

  The formatting strings of ISO C strftime() are not affected by the time
  zone, so this function is strictly speaking not necessary in a pure-C
  environment.  POSIX strftime(), however, has the '%z' and '%Z'
  conversions, which require knowledge of the relevant time zone.

  It has been argued that this function is redundant, because time_breakup()
  could embed time zone information into struct tm (either in extension
  fields or private fields), and thus the standard POSIX strftime could
  suffice to convert struct tm's in a thread-safe way.  However, this is
  only true for struct tm's created from time_breakup() or the mktime()
  family.  Creation of struct tm values "by hand" is still fairly common,
  and a time zone specification is needed to print these.

Name
  strftime_zl - generate a text representation of a time value, in a given
  time zone and locale.

Synopsis
  #include <time.h>
  size_t
  strftime_zl(char * restrict buf, size_t maxsize,
              const char * restrict format,
              const struct tm * restrict timeptr,
              const struct tzinfo * restrict tz,
              const locale_t * restrict l);

Description
  (This is an extension based on Ulrich Drepper's thread-aware locale
  proposal; see rationale.)
  strftime_z() formats the information from 'timeptr' into the buffer 'buf'
  according to the format specified by 'format' and the locale specified by
  'l'.

  Other than the source of locale information, this is in all ways identical
  to strftime_z.

Rationale and commentary
  This function follows Ulrich Drepper's thread-safe locale proposal; it is
  the combination of his strftime_l and strftime_z defined above.  See
  <http://www.cygnus.com/~drepper/tllocale.ps.bz2> for details.  This
  function should only be implemented if the rest of Drepper's proposal is
  as well.

  The _zl suffix is ugly.  More natural would be to define a single function
  which takes all the necessary thread-safe data as arguments, but I don't
  want to force implementation of this proposal to be blocked on Drepper's.

Thread-support functions

Name
  pthread_settz -- set time zone object for the current POSIX thread.

Synopsis
  #include <time.h>
  #include <pthread.h>
  int pthread_settz(const struct tzinfo *tz);

Description
  (This function is only relevant in a POSIX system with the Threads
  option.)

  pthread_settz() sets the time zone object to be used for time functions
  called from the current POSIX thread.  The time zone setting for all other
  threads remains the same.

  Once pthread_settz() has been called, all calls to the functions
  localtime(), localtime_r(), ctime(), ctime_r(), asctime(), asctime_r(),
  mktime(), and strftime() in the thread from which it was called will use
  the specified time zone rather than the process-wide time zone.

  If pthread_settz() is called with a NULL pointer as its argument, the
  current thread will again use the global time zone object.

  pthread_settz() leaves the values of the global variable tzname, and the
  XSI global variables daylight and timezone, undefined.

Return value
  Upon successful completion, pthread_settz() returns a value of zero.
  Otherwise, an error code is returned to indicate an error.

Errors
  pthread_settz() may fail if:

  ENOMEM
    Insufficient memory exists to store the time zone information for the
    thread.

  EAGAIN
    The system lacked the necessary resources to associate the time zone
    information with the thread.

Rationale and commentary
  This function allows existing code which uses the ISO C APIs to work
  correctly, unmodified, in a threaded environment.  The extended parts of
  the POSIX APIs may not work correctly unmodified.

  This function would typically be implemented as a wrapper around
  pthread_setspecific().

  It's not clear if a parallel pthread_gettz() is also needed.

General rationale and commentary
  Thanks to many comments from the people of the [email-masked]
  mailing list.

  Much inspiration for this work was drawn from Markus Kuhn's proposed
  extended time APIs for ISO C 200x, and from Ulrich Drepper's proposed
  thread-aware locale functions.

  Markus Kuhn's proposed time zone API defined an additional time zone
  manipulation function tz_jump(), which gets the next or previous
  discontinuity in a given time zone.  This is a potentially useful
  function, but it was omitted here so as to keep the API definitions
  modest.  (Additionally, it should probably wait on the proper definition
  of an extended time type, to avoid unnecessary redundancy.)

  Time zone specific versions of asctime[_r] and ctime[_r] were omitted, as
  they can be constructed trivially out of the functions defined here.

  A time zone specific version of strptime is not necessary.  The only
  zone-aware function of strptime that I know of is the BSD extension to
  scan "%Z"; POSIX 200x does not define this, and it generally doesn't seem
  to work very well as zone abbreviations are ambiguous.  (BSD strptime only
  recognizes the current local timezone, in standard and summer forms, and
  "GMT".  Even this is not necessarily unambiguous: consider for example the
  Australian "EST" meaning both "Eastern Standard Time" and "Eastern Summer
  Time".)

  wcsftime_z and wcsftime_zl functions are needed, analogous to strftime_z
  and strftime_zl for wide strings.  Their definitions are obvious analogies
  to the existing functions.

Changes from Version 2:
  The structure was renamed to 'struct tzinfo' from 'struct timezone',
  because the latter structure is already defined on BSD systems.  (It is
  the deprecated second argument to gettimeofday().)

  Clarified that pthread_settz() leaves the state of global variables
  undefined.

  Corrected incorrect errno value in the time_make() rationale.

Changes from Version 1:
  The time-zone creation and destruction functions were renamed from newtz()
  and freetz() to tz_prep() and tz_free().  This puts them in a name
  space, and aligns them with Markus Kuhn's functions.  (These functions are
  slightly different from Kuhn's, though, in that they are defined to return
  errno values on errors.)

  tzdup() was dropped as not useful.

  mktime_z() and localtime_z() were renamed to time_make() and
  time_breakup(), by analogy with Kuhn's xtime_* functions.

  Zone-specific versions of asctime(), ctime(), and strptime() were dropped
  as unnecessary.

  The text was greatly clarified to make clear the distinctions between ISO
  C extensions, POSIX extensions, and rationale and commentary.

  The externally-visible tz_name elements of struct tz were dropped.

  The strftime_zl function was defined.

diff -crN -x *~ ../tzcode2001c/Makefile thread-safe-2/Makefile
*** ../tzcode2001c/Makefile	Tue Jun  5 13:48:21 2001
--- thread-safe-2/Makefile	Mon Jun 18 15:49:47 2001
***************
*** 83,88 ****
--- 83,89 ----
  
  # Non-default libraries needed to link.
  # Add -lintl if you want to use `gettext' on Solaris.
+ # Add -lpthread if you're compiling with non-weak POSIX threads in libpthread.
  LDLIBS=
  
  # Add the following to the end of the "CFLAGS=" line as needed.
***************
*** 211,216 ****
--- 212,220 ----
  
  CFLAGS=
  
+ # The threading model for this system.  "dummy" or "posix"
+ THREADMODEL=	dummy
+ 
  # If you want zic's -s option used when installing, uncomment the next line
  # ZFLAGS=	-s
  
***************
*** 242,260 ****
  cc=		cc
  CC=		$(cc) -DTZDIR=\"$(TZDIR)\"
  
! TZCSRCS=	zic.c localtime.c asctime.c scheck.c ialloc.c
! TZCOBJS=	zic.o localtime.o asctime.o scheck.o ialloc.o
! TZDSRCS=	zdump.c localtime.c asctime.c ialloc.c
! TZDOBJS=	zdump.o localtime.o asctime.o ialloc.o
! DATESRCS=	date.c localtime.c logwtmp.c strftime.c asctime.c
! DATEOBJS=	date.o localtime.o logwtmp.o strftime.o asctime.o
! LIBSRCS=	localtime.c asctime.c difftime.c
! LIBOBJS=	localtime.o asctime.o difftime.o
! HEADERS=	tzfile.h private.h
  NONLIBSRCS=	zic.c zdump.c scheck.c ialloc.c
  NEWUCBSRCS=	date.c logwtmp.c strftime.c
! SOURCES=	$(HEADERS) $(LIBSRCS) $(NONLIBSRCS) $(NEWUCBSRCS) tzselect.ksh
  MANS=		newctime.3 newstrftime.3 newtzset.3 time2posix.3 \
  			tzfile.5 tzselect.8 zic.8 zdump.8
  DOCS=		README Theory $(MANS) date.1 Makefile
  PRIMARY_YDATA=	africa antarctica asia australasia \
--- 246,274 ----
  cc=		cc
  CC=		$(cc) -DTZDIR=\"$(TZDIR)\"
  
! THREADSRC=	tzthread-$(THREADMODEL).c
! THREADOBJ=	tzthread-$(THREADMODEL).o
! THREADHDR=	tzthread-$(THREADMODEL).h
! 
! CFLAGS += -DTZTHREAD_HEADER_H=\"$(THREADHDR)\"
! 
! TZCSRCS=	zic.c localtime.c $(THREADSRC) asctime.c scheck.c ialloc.c
! TZCOBJS=	zic.o localtime.o $(THREADOBJ) asctime.o scheck.o ialloc.o
! TZDSRCS=	zdump.c localtime.c $(THREADSRC) asctime.c ialloc.c
! TZDOBJS=	zdump.o localtime.o $(THREADOBJ) asctime.o ialloc.o
! DATESRCS=	date.c localtime.c $(THREADSRC) logwtmp.c strftime.c asctime.c
! DATEOBJS=	date.o localtime.o $(THREADOBJ) logwtmp.o strftime.o asctime.o
! LIBSRCS=	localtime.c $(THREADSRC) asctime.c difftime.c
! LIBOBJS=	localtime.o $(THREADOBJ) asctime.o difftime.o
! HEADERS=	tzfile.h private.h struct_tzinfo.h tzthread-dummy.h \
! 		tzthread-posix.h tztimeext.h
! VARLIBSRCS=	tzthread-dummy.c tzthread-posix.c
  NONLIBSRCS=	zic.c zdump.c scheck.c ialloc.c
  NEWUCBSRCS=	date.c logwtmp.c strftime.c
! SOURCES=	$(HEADERS) $(LIBSRCS) $(VARLIBSRCS) $(NONLIBSRCS) \
! 		$(NEWUCBSRCS) tzselect.ksh
  MANS=		newctime.3 newstrftime.3 newtzset.3 time2posix.3 \
+ 			tz_prep.3 time_make.3 pthread_settz.3 \
  			tzfile.5 tzselect.8 zic.8 zdump.8
  DOCS=		README Theory $(MANS) date.1 Makefile
  PRIMARY_YDATA=	africa antarctica asia australasia \
***************
*** 353,359 ****
  		ar r ,lib.a logwtmp.o
  		if [ -x /usr/ucb/ranlib -o -x /usr/bin/ranlib ] ; \
  			then ranlib ,lib.a ; fi
! 		$(CC) $(CFLAGS) date.o localtime.o asctime.o strftime.o \
  			$(LDLIBS) -lc ,lib.a -o $@
  		rm -f ,lib.a
  
--- 367,373 ----
  		ar r ,lib.a logwtmp.o
  		if [ -x /usr/ucb/ranlib -o -x /usr/bin/ranlib ] ; \
  			then ranlib ,lib.a ; fi
! 		$(CC) $(CFLAGS) date.o localtime.o $(THREADOBJ) asctime.o strftime.o \
  			$(LDLIBS) -lc ,lib.a -o $@
  		rm -f ,lib.a
  
***************
*** 396,404 ****
  date.o:		private.h
  difftime.o:	private.h
  ialloc.o:	private.h
! localtime.o:	private.h tzfile.h
  scheck.o:	private.h
! strftime.o:	tzfile.h
  zic.o:		private.h tzfile.h
  
  .KEEP_STATE:
--- 410,419 ----
  date.o:		private.h
  difftime.o:	private.h
  ialloc.o:	private.h
! localtime.o:	private.h tzfile.h tztimeext.h struct_tzinfo.h $(THREADHDR)
  scheck.o:	private.h
! strftime.o:	tzfile.h private.h struct_tzinfo.h
  zic.o:		private.h tzfile.h
+ $(THREADOBJ):	private.h tzfile.h struct_tzinfo.h $(THREADHDR)
  
  .KEEP_STATE:
diff -crN -x *~ ../tzcode2001c/asctime.c thread-safe-2/asctime.c
*** ../tzcode2001c/asctime.c	Tue Jun  5 13:49:48 2001
--- thread-safe-2/asctime.c	Mon Jun 18 15:46:04 2001
***************
*** 14,19 ****
--- 14,21 ----
  #include "private.h"
  #include "tzfile.h"
  
+ #include TZTHREAD_HEADER_H
+ 
  /*
  ** A la ISO/IEC 9945-1, ANSI/IEEE Std 1003.1, Second Edition, 1996-07-12.
  */
***************
*** 60,74 ****
  asctime(timeptr)
  register const struct tm *	timeptr;
  {
! 	/*
! 	** Big enough for something such as
! 	** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n
! 	** (two three-character abbreviations, five strings denoting integers,
! 	** three explicit spaces, two explicit colons, a newline,
! 	** and a trailing ASCII nul).
! 	*/
! 	static char		result[3 * 2 + 5 * INT_STRLEN_MAXIMUM(int) +
! 					3 + 2 + 1 + 1];
  
  	return asctime_r(timeptr, result);
  }
--- 62,68 ----
  asctime(timeptr)
  register const struct tm *	timeptr;
  {
! 	char *result = GET_ASCTIME_BUF();
  
  	return asctime_r(timeptr, result);
  }
diff -crN -x *~ ../tzcode2001c/localtime.c thread-safe-2/localtime.c
*** ../tzcode2001c/localtime.c	Tue Jun  5 13:59:55 2001
--- thread-safe-2/localtime.c	Mon Jun 18 16:26:37 2001
***************
*** 21,26 ****
--- 21,32 ----
  #include "tzfile.h"
  #include "fcntl.h"
  
+ #include "struct_tzinfo.h"
+ 
+ #include "tztimeext.h"
+ 
+ #include TZTHREAD_HEADER_H
+ 
  /*
  ** SunOS 4.1.1 headers lack O_BINARY.
  */
***************
*** 70,110 ****
  #define TZDEFRULESTRING ",M4.1.0,M10.5.0"
  #endif /* !defined TZDEFDST */
  
- struct ttinfo {				/* time type information */
- 	long		tt_gmtoff;	/* UTC offset in seconds */
- 	int		tt_isdst;	/* used to set tm_isdst */
- 	int		tt_abbrind;	/* abbreviation list index */
- 	int		tt_ttisstd;	/* TRUE if transition is std time */
- 	int		tt_ttisgmt;	/* TRUE if transition is UTC */
- };
- 
- struct lsinfo {				/* leap second information */
- 	time_t		ls_trans;	/* transition time */
- 	long		ls_corr;	/* correction to apply */
- };
- 
- #define BIGGEST(a, b)	(((a) > (b)) ? (a) : (b))
- 
- #ifdef TZNAME_MAX
- #define MY_TZNAME_MAX	TZNAME_MAX
- #endif /* defined TZNAME_MAX */
- #ifndef TZNAME_MAX
- #define MY_TZNAME_MAX	255
- #endif /* !defined TZNAME_MAX */
- 
- struct state {
- 	int		leapcnt;
- 	int		timecnt;
- 	int		typecnt;
- 	int		charcnt;
- 	time_t		ats[TZ_MAX_TIMES];
- 	unsigned char	types[TZ_MAX_TIMES];
- 	struct ttinfo	ttis[TZ_MAX_TYPES];
- 	char		chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof gmt),
- 				(2 * (MY_TZNAME_MAX + 1)))];
- 	struct lsinfo	lsis[TZ_MAX_LEAPS];
- };
- 
  struct rule {
  	int		r_type;		/* type of rule--see below */
  	int		r_day;		/* day number of rule */
--- 76,81 ----
***************
*** 128,175 ****
  static const char *	getsecs P((const char * strp, long * secsp));
  static const char *	getoffset P((const char * strp, long * offsetp));
  static const char *	getrule P((const char * strp, struct rule * rulep));
! static void		gmtload P((struct state * sp));
! static void		gmtsub P((const time_t * timep, long offset,
! 				struct tm * tmp));
! static void		localsub P((const time_t * timep, long offset,
! 				struct tm * tmp));
  static int		increment_overflow P((int * number, int delta));
  static int		normalize_overflow P((int * tensptr, int * unitsptr,
  				int base));
! static void		settzname P((void));
! static time_t		time1 P((struct tm * tmp,
! 				void(*funcp) P((const time_t *,
! 				long, struct tm *)),
  				long offset));
! static time_t		time2 P((struct tm *tmp,
! 				void(*funcp) P((const time_t *,
! 				long, struct tm*)),
! 				long offset, int * okayp));
! static time_t		time2sub P((struct tm *tmp,
! 				void(*funcp) P((const time_t *,
! 				long, struct tm*)),
! 				long offset, int * okayp, int do_norm_secs));
  static void		timesub P((const time_t * timep, long offset,
! 				const struct state * sp, struct tm * tmp));
  static int		tmcomp P((const struct tm * atmp,
  				const struct tm * btmp));
  static time_t		transtime P((time_t janfirst, int year,
  				const struct rule * rulep, long offset));
! static int		tzload P((const char * name, struct state * sp));
! static int		tzparse P((const char * name, struct state * sp,
  				int lastditch));
  
- #ifdef ALL_STATE
- static struct state *	lclptr;
- static struct state *	gmtptr;
- #endif /* defined ALL_STATE */
- 
- #ifndef ALL_STATE
- static struct state	lclmem;
- static struct state	gmtmem;
- #define lclptr		(&lclmem)
- #define gmtptr		(&gmtmem)
- #endif /* State Farm */
  
  #ifndef TZ_STRLEN_MAX
  #define TZ_STRLEN_MAX 255
--- 99,145 ----
  static const char *	getsecs P((const char * strp, long * secsp));
  static const char *	getoffset P((const char * strp, long * offsetp));
  static const char *	getrule P((const char * strp, struct rule * rulep));
! static void		gmtload P((struct tzinfo * sp));
! static void		nulltzsub P((const time_t * timep,
! 				long offset, struct tm * tmp));
! static void		tzsub P((const time_t * timep,
! 				const struct tzinfo *tz,
! 				long offset, struct tm * tmp, int));
  static int		increment_overflow P((int * number, int delta));
  static int		normalize_overflow P((int * tensptr, int * unitsptr,
  				int base));
! static void		settzname P((const struct tzinfo * const));
! static int		time1 P((time_t * timep,
! 				struct tm * tmp,
! 				const struct tzinfo * tz,
! 				long offset,
! 				int accomodate_gap));
! static int		time2 P((time_t * timep,
! 				struct tm *tmp,
! 				const struct tzinfo * tz,
  				long offset));
! static int		time2sub P((time_t * timep,
! 				struct tm *tmp,
! 				const struct tzinfo * tz,
! 				long offset, int do_norm_secs));
  static void		timesub P((const time_t * timep, long offset,
! 				const struct tzinfo * sp, struct tm * tmp));
  static int		tmcomp P((const struct tm * atmp,
  				const struct tm * btmp));
  static time_t		transtime P((time_t janfirst, int year,
  				const struct rule * rulep, long offset));
! static int		tzload P((const char * name, struct tzinfo * sp));
! static int		tzparse P((const char * name, struct tzinfo * sp,
  				int lastditch));
+ static void		tzsetwall_basic P((struct tzinfo * const sp));
+ static void		tzset_basic P((struct tzinfo * const sp));
+ #ifndef STD_INSPIRED
+ static void		tzsetwall P((void));
+ #endif
+ #ifdef STD_INSPIRED
+ static long		leapcorr P((time_t *, const struct tzinfo *));
+ #endif

  #ifndef TZ_STRLEN_MAX
  #define TZ_STRLEN_MAX 255
***************
*** 184,189 ****
--- 154,164 ----
  	wildabbr
  };
  
+ #define MIN_TIME (TYPE_SIGNED(time_t) ? \
+ 	(((time_t) 1) << (TYPE_BIT(time_t) - 1)) : 0)
+ 
+ #define MAX_TIME (~MIN_TIME)
+ 
  /*
  ** Section 4.12.3 of X3.159-1989 requires that
  **	Except for the strftime function, these functions [asctime,
***************
*** 192,199 ****
  ** Thanks to Paul Eggert ([email-masked]) for noting this.
  */
  
- static struct tm	tm;
- 
  #ifdef USG_COMPAT
  time_t			timezone = 0;
  int			daylight = 0;
--- 167,172 ----
***************
*** 217,225 ****
  }
  
  static void
! settzname P((void))
  {
- 	register struct state * const	sp = lclptr;
  	register int			i;
  
  	tzname[0] = wildabbr;
--- 190,198 ----
  }
  
  static void
! settzname(sp)
! const struct tzinfo * const	sp;
  {
  	register int			i;
  
  	tzname[0] = wildabbr;
***************
*** 231,242 ****
  #ifdef ALTZONE
  	altzone = 0;
  #endif /* defined ALTZONE */
- #ifdef ALL_STATE
  	if (sp == NULL) {
  		tzname[0] = tzname[1] = gmt;
  		return;
  	}
- #endif /* defined ALL_STATE */
  	for (i = 0; i < sp->typecnt; ++i) {
  		register const struct ttinfo * const	ttisp = &sp->ttis[i];
  
--- 204,213 ----
***************
*** 269,275 ****
  static int
  tzload(name, sp)
  register const char *		name;
! register struct state * const	sp;
  {
  	register const char *	p;
  	register int		i;
--- 240,246 ----
  static int
  tzload(name, sp)
  register const char *		name;
! register struct tzinfo * const	sp;
  {
  	register const char *	p;
  	register int		i;
***************
*** 698,704 ****
  static int
  tzparse(name, sp, lastditch)
  const char *			name;
! register struct state * const	sp;
  const int			lastditch;
  {
  	const char *			stdname;
--- 669,675 ----
  static int
  tzparse(name, sp, lastditch)
  const char *			name;
! register struct tzinfo * const	sp;
  const int			lastditch;
  {
  	const char *			stdname;
***************
*** 910,921 ****
  
  static void
  gmtload(sp)
! struct state * const	sp;
  {
  	if (tzload(gmt, sp) != 0)
  		(void) tzparse(gmt, sp, TRUE);
  }
  
  #ifndef STD_INSPIRED
  /*
  ** A non-static declaration of tzsetwall in a system header file
--- 881,934 ----
  
  static void
  gmtload(sp)
! struct tzinfo * const	sp;
  {
  	if (tzload(gmt, sp) != 0)
  		(void) tzparse(gmt, sp, TRUE);
  }
  
+ 
+ static void
+ gmtset(sp)
+ struct tzinfo * const sp;
+ {
+ 	/*
+ 	** Fast-path the common-case (no locking needed once gmt is set).
+ 	*/
+ 	if (gmt_is_set) {
+ 		return;
+ 	}
+ 
+ 	LOCK_GMTPTR();
+ 	/*
+ 	** Another thread might have set it, so check gmt_is_set again.
+ 	*/
+ 	if (!gmt_is_set) {
+ 		gmt_is_set = TRUE;
+ 		if (sp != NULL)
+ 			gmtload(sp);
+ 	}
+ 	UNLOCK_GMTPTR();     
+ }
+ 
+ 
+ static void
+ tzsetwall_basic(sp)
+ struct tzinfo * const sp;
+ {
+ 	if (lcl_is_set < 0)
+ 		return;
+ 	lcl_is_set = -1;
+ 
+ 	if (sp == NULL) {
+ 		settzname(NULL);	/* all we can do */
+ 		return;
+ 	}
+ 	if (tzload((char *) NULL, sp) != 0)
+ 		gmtload(sp);
+ 	settzname(sp);
+ }
+ 
  #ifndef STD_INSPIRED
  /*
  ** A non-static declaration of tzsetwall in a system header file
***************
*** 926,957 ****
  void
  tzsetwall P((void))
  {
! 	if (lcl_is_set < 0)
! 		return;
! 	lcl_is_set = -1;
  
! #ifdef ALL_STATE
! 	if (lclptr == NULL) {
! 		lclptr = (struct state *) malloc(sizeof *lclptr);
! 		if (lclptr == NULL) {
! 			settzname();	/* all we can do */
! 			return;
! 		}
  	}
! #endif /* defined ALL_STATE */
! 	if (tzload((char *) NULL, lclptr) != 0)
! 		gmtload(lclptr);
! 	settzname();
  }
  
! void
! tzset P((void))
  {
  	register const char *	name;
  
  	name = getenv("TZ");
  	if (name == NULL) {
! 		tzsetwall();
  		return;
  	}
  
--- 939,967 ----
  void
  tzsetwall P((void))
  {
! 	struct tzinfo * sp;
! 	int process_wide;
  
! 	LOCK_LCLPTR();
! 	sp = GET_LCLPTR(&process_wide);
! 	if (!process_wide) {
! 		UNLOCK_LCLPTR();
! 		return;
  	}
! 
! 	tzsetwall_basic(sp);
! 	UNLOCK_LCLPTR();
  }
  
! static void
! tzset_basic(lclptr)
! struct tzinfo * const lclptr;
  {
  	register const char *	name;
  
  	name = getenv("TZ");
  	if (name == NULL) {
! 		tzsetwall_basic(lclptr);
  		return;
  	}
  
***************
*** 961,975 ****
  	if (lcl_is_set)
  		(void) strcpy(lcl_TZname, name);
  
- #ifdef ALL_STATE
  	if (lclptr == NULL) {
! 		lclptr = (struct state *) malloc(sizeof *lclptr);
! 		if (lclptr == NULL) {
! 			settzname();	/* all we can do */
! 			return;
! 		}
  	}
- #endif /* defined ALL_STATE */
  	if (*name == '\0') {
  		/*
  		** User wants it fast rather than right.
--- 971,980 ----
  	if (lcl_is_set)
  		(void) strcpy(lcl_TZname, name);
  
  	if (lclptr == NULL) {
! 		settzname(NULL);	/* all we can do */
! 		return;
  	}
  	if (*name == '\0') {
  		/*
  		** User wants it fast rather than right.
***************
*** 984,995 ****
  	} else if (tzload(name, lclptr) != 0)
  		if (name[0] == ':' || tzparse(name, lclptr, FALSE) != 0)
  			(void) gmtload(lclptr);
! 	settzname();
  }
  
  /*
  ** The easy way to behave "as if no library function calls" localtime
! ** is to not call it--so we drop its guts into "localsub", which can be
  ** freely called.  (And no, the PANS doesn't require the above behavior--
  ** but it *is* desirable.)
  **
--- 989,1041 ----
  	} else if (tzload(name, lclptr) != 0)
  		if (name[0] == ':' || tzparse(name, lclptr, FALSE) != 0)
  			(void) gmtload(lclptr);
! 	settzname(lclptr);
! }
! 
! void
! tzset P((void))
! {
! 	struct tzinfo * lclptr;
! 	int process_wide;
! 
! 	LOCK_LCLPTR();
! 	lclptr = GET_LCLPTR(&process_wide);
! 	if (!process_wide) {
! 		UNLOCK_LCLPTR();
! 		return;
! 	}
! 
! 	tzset_basic(lclptr);
! 	UNLOCK_LCLPTR();
! }
! 
! const struct ttinfo *
! _tz_getttype(timep, sp)
! const time_t * const timep;
! const struct tzinfo * sp;
! {
! 	register int			i;
! 	const time_t			t = *timep;
! 
! 	if (sp->timecnt == 0 || t < sp->ats[0]) {
! 		i = 0;
! 		while (sp->ttis[i].tt_isdst)
! 			if (++i >= sp->typecnt) {
! 				i = 0;
! 				break;
! 			}
! 	} else {
! 		for (i = 1; i < sp->timecnt; ++i)
! 			if (t < sp->ats[i])
! 				break;
! 		i = sp->types[i - 1];
! 	}
! 	return &sp->ttis[i];
  }
  
  /*
  ** The easy way to behave "as if no library function calls" localtime
! ** is to not call it--so we drop its guts into "tzsub", which can be
  ** freely called.  (And no, the PANS doesn't require the above behavior--
  ** but it *is* desirable.)
  **
***************
*** 998,1045 ****
  
  /*ARGSUSED*/
  static void
! localsub(timep, offset, tmp)
  const time_t * const	timep;
  const long		offset;
  struct tm * const	tmp;
  {
- 	register struct state *		sp;
  	register const struct ttinfo *	ttisp;
- 	register int			i;
  	const time_t			t = *timep;
  
- 	sp = lclptr;
- #ifdef ALL_STATE
  	if (sp == NULL) {
! 		gmtsub(timep, offset, tmp);
  		return;
  	}
! #endif /* defined ALL_STATE */
! 	if (sp->timecnt == 0 || t < sp->ats[0]) {
! 		i = 0;
! 		while (sp->ttis[i].tt_isdst)
! 			if (++i >= sp->typecnt) {
! 				i = 0;
! 				break;
! 			}
! 	} else {
! 		for (i = 1; i < sp->timecnt; ++i)
! 			if (t < sp->ats[i])
! 				break;
! 		i = sp->types[i - 1];
! 	}
! 	ttisp = &sp->ttis[i];
  	/*
  	** To get (wrong) behavior that's compatible with System V Release 2.0
  	** you'd replace the statement below with
! 	**	t += ttisp->tt_gmtoff;
  	**	timesub(&t, 0L, sp, tmp);
  	*/
! 	timesub(&t, ttisp->tt_gmtoff, sp, tmp);
  	tmp->tm_isdst = ttisp->tt_isdst;
! 	tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind];
  #ifdef TM_ZONE
! 	tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind];
  #endif /* defined TM_ZONE */
  }
  
--- 1044,1089 ----
  
  /*ARGSUSED*/
  static void
! tzsub(timep, sp, offset, tmp, alter_tzname)
  const time_t * const	timep;
+ const struct tzinfo * const	sp;
  const long		offset;
  struct tm * const	tmp;
+ int			alter_tzname;
  {
  	register const struct ttinfo *	ttisp;
  	const time_t			t = *timep;
  
  	if (sp == NULL) {
! 		nulltzsub(timep, offset, tmp);
  		return;
  	}
! 	ttisp = _tz_getttype(timep, sp);
  	/*
  	** To get (wrong) behavior that's compatible with System V Release 2.0
  	** you'd replace the statement below with
! 	**	t += ttisp->tt_gmtoff + offset;
  	**	timesub(&t, 0L, sp, tmp);
+ 	**
+ 	** ttisp->tt_gmtoff and offset should never both be non-zero
+ 	** simultaneously.
  	*/
! 	timesub(&t, ttisp->tt_gmtoff + offset, sp, tmp);
  	tmp->tm_isdst = ttisp->tt_isdst;
! 	if (alter_tzname) {
! 		tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind];
! 	}
  #ifdef TM_ZONE
! 	/*
! 	** Could get fancy here and deliver something such as
! 	** "UTC+xxxx" or "UTC-xxxx" if offset is non-zero,
! 	** but this is no time for a treasure hunt.
! 	*/
! 	if (offset != 0)
! 		tmp->TM_ZONE = wildabbr;
! 	else {
! 		tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind];
! 	}
  #endif /* defined TM_ZONE */
  }
  
***************
*** 1047,1055 ****
  localtime(timep)
  const time_t * const	timep;
  {
! 	tzset();
! 	localsub(timep, 0L, &tm);
! 	return &tm;
  }
  
  /*
--- 1091,1114 ----
  localtime(timep)
  const time_t * const	timep;
  {
! 	struct tm * tm;
! 	struct tzinfo * tz;
! 	int process_wide;
! 	tm = GET_TM_BUF();
! 	if (tm == NULL)
! 		return NULL;
! 
! 	LOCK_LCLPTR();
! 	tz = GET_LCLPTR(&process_wide);
! 	if (tz == NULL) {
! 		UNLOCK_LCLPTR();
! 		return NULL;
! 	}
! 	if (process_wide)
! 		tzset_basic(tz);
! 	tzsub(timep, tz, 0L, tm, process_wide);
! 	UNLOCK_LCLPTR();
! 	return tm;
  }
  
  /*
***************
*** 1060,1088 ****
  const time_t * const	timep;
  struct tm *		tm;
  {
! 	localsub(timep, 0L, tm);
  	return tm;
  }
  
  /*
! ** gmtsub is to gmtime as localsub is to localtime.
  */
  
  static void
! gmtsub(timep, offset, tmp)
  const time_t * const	timep;
  const long		offset;
  struct tm * const	tmp;
  {
! 	if (!gmt_is_set) {
! 		gmt_is_set = TRUE;
! #ifdef ALL_STATE
! 		gmtptr = (struct state *) malloc(sizeof *gmtptr);
! 		if (gmtptr != NULL)
! #endif /* defined ALL_STATE */
! 			gmtload(gmtptr);
! 	}
! 	timesub(timep, offset, gmtptr, tmp);
  #ifdef TM_ZONE
  	/*
  	** Could get fancy here and deliver something such as
--- 1119,1150 ----
  const time_t * const	timep;
  struct tm *		tm;
  {
! 	struct tzinfo * tz;
! 	int process_wide;
! 	LOCK_LCLPTR();
! 	tz = GET_LCLPTR(&process_wide);
! 	if (tz == NULL) {
! 		UNLOCK_LCLPTR();
! 		return NULL;
! 	}
! 	if (process_wide)
! 		tzset_basic(tz);
! 	tzsub(timep, tz, 0L, tm, process_wide);
! 	UNLOCK_LCLPTR();
  	return tm;
  }
  
  /*
! ** nulltzsub is a stripped-down version of tzsub used if tz is NULL.
  */
  
  static void
! nulltzsub(timep, offset, tmp)
  const time_t * const	timep;
  const long		offset;
  struct tm * const	tmp;
  {
! 	timesub(timep, offset, NULL, tmp);
  #ifdef TM_ZONE
  	/*
  	** Could get fancy here and deliver something such as
***************
*** 1092,1105 ****
  	if (offset != 0)
  		tmp->TM_ZONE = wildabbr;
  	else {
! #ifdef ALL_STATE
! 		if (gmtptr == NULL)
! 			tmp->TM_ZONE = gmt;
! 		else	tmp->TM_ZONE = gmtptr->chars;
! #endif /* defined ALL_STATE */
! #ifndef ALL_STATE
! 		tmp->TM_ZONE = gmtptr->chars;
! #endif /* State Farm */
  	}
  #endif /* defined TM_ZONE */
  }
--- 1154,1160 ----
  	if (offset != 0)
  		tmp->TM_ZONE = wildabbr;
  	else {
! 		tmp->TM_ZONE = gmt;
  	}
  #endif /* defined TM_ZONE */
  }
***************
*** 1108,1115 ****
  gmtime(timep)
  const time_t * const	timep;
  {
! 	gmtsub(timep, 0L, &tm);
! 	return &tm;
  }
  
  /*
--- 1163,1181 ----
  gmtime(timep)
  const time_t * const	timep;
  {
! 	struct tm * tm;
! 	struct tzinfo * tz;
! 	tm = GET_TM_BUF();
! 	if (tm == NULL)
! 		return NULL;
! 
! 	tz = GET_GMTPTR();
! 	if (tz == NULL)
! 		return NULL;
! 	gmtset(tz);
! 	tzsub(timep, tz, 0L, tm, 0);
! 
! 	return tm;
  }
  
  /*
***************
*** 1120,1126 ****
  const time_t * const	timep;
  struct tm *		tm;
  {
! 	gmtsub(timep, 0L, tm);
  	return tm;
  }
  
--- 1186,1199 ----
  const time_t * const	timep;
  struct tm *		tm;
  {
! 	struct tzinfo * tz;
! 
! 	tz = GET_GMTPTR();
! 	if (tz == NULL)
! 		return NULL;
! 	gmtset(tz);
! 	tzsub(timep, tz, 0L, tm, 0);
! 
  	return tm;
  }
  
***************
*** 1131,1138 ****
  const time_t * const	timep;
  const long		offset;
  {
! 	gmtsub(timep, offset, &tm);
! 	return &tm;
  }
  
  #endif /* defined STD_INSPIRED */
--- 1204,1223 ----
  const time_t * const	timep;
  const long		offset;
  {
! 	struct tm * tm;
! 	struct tzinfo * tz;
! 
! 	tm = GET_TM_BUF();
! 	if (tm == NULL)
! 		return NULL;
! 
! 	tz = GET_GMTPTR();
! 	if (tz == NULL)
! 		return NULL;
! 	gmtset(tz);
! 	tzsub(timep, tz, offset, tm, 0);
! 
! 	return tm;
  }
  
  #endif /* defined STD_INSPIRED */
***************
*** 1141,1147 ****
  timesub(timep, offset, sp, tmp)
  const time_t * const			timep;
  const long				offset;
! register const struct state * const	sp;
  register struct tm * const		tmp;
  {
  	register const struct lsinfo *	lp;
--- 1226,1232 ----
  timesub(timep, offset, sp, tmp)
  const time_t * const			timep;
  const long				offset;
! register const struct tzinfo * const	sp;
  register struct tm * const		tmp;
  {
  	register const struct lsinfo *	lp;
***************
*** 1156,1167 ****
  
  	corr = 0;
  	hit = 0;
- #ifdef ALL_STATE
  	i = (sp == NULL) ? 0 : sp->leapcnt;
- #endif /* defined ALL_STATE */
- #ifndef ALL_STATE
- 	i = sp->leapcnt;
- #endif /* State Farm */
  	while (--i >= 0) {
  		lp = &sp->lsis[i];
  		if (*timep >= lp->ls_trans) {
--- 1241,1247 ----
***************
*** 1322,1336 ****
  	return result;
  }
  
! static time_t
! time2sub(tmp, funcp, offset, okayp, do_norm_secs)
  struct tm * const	tmp;
! void (* const		funcp) P((const time_t*, long, struct tm*));
  const long		offset;
- int * const		okayp;
  const int		do_norm_secs;
  {
- 	register const struct state *	sp;
  	register int			dir;
  	register int			bits;
  	register int			i, j ;
--- 1402,1415 ----
  	return result;
  }
  
! static int
! time2sub(timep, tmp, sp, offset, do_norm_secs)
! time_t *		timep;
  struct tm * const	tmp;
! const struct tzinfo *	sp;
  const long		offset;
  const int		do_norm_secs;
  {
  	register int			dir;
  	register int			bits;
  	register int			i, j ;
***************
*** 1339,1366 ****
  	time_t				t;
  	struct tm			yourtm, mytm;
  
- 	*okayp = FALSE;
  	yourtm = *tmp;
  	if (do_norm_secs) {
  		if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec,
  			SECSPERMIN))
! 				return WRONG;
  	}
  	if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR))
! 		return WRONG;
  	if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY))
! 		return WRONG;
  	if (normalize_overflow(&yourtm.tm_year, &yourtm.tm_mon, MONSPERYEAR))
! 		return WRONG;
  	/*
  	** Turn yourtm.tm_year into an actual year number for now.
  	** It is converted back to an offset from TM_YEAR_BASE later.
  	*/
  	if (increment_overflow(&yourtm.tm_year, TM_YEAR_BASE))
! 		return WRONG;
  	while (yourtm.tm_mday <= 0) {
  		if (increment_overflow(&yourtm.tm_year, -1))
! 			return WRONG;
  		i = yourtm.tm_year + (1 < yourtm.tm_mon);
  		yourtm.tm_mday += year_lengths[isleap(i)];
  	}
--- 1418,1444 ----
  	time_t				t;
  	struct tm			yourtm, mytm;
  
  	yourtm = *tmp;
  	if (do_norm_secs) {
  		if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec,
  			SECSPERMIN))
! 				return ERANGE;
  	}
  	if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR))
! 		return ERANGE;
  	if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY))
! 		return ERANGE;
  	if (normalize_overflow(&yourtm.tm_year, &yourtm.tm_mon, MONSPERYEAR))
! 		return ERANGE;
  	/*
  	** Turn yourtm.tm_year into an actual year number for now.
  	** It is converted back to an offset from TM_YEAR_BASE later.
  	*/
  	if (increment_overflow(&yourtm.tm_year, TM_YEAR_BASE))
! 		return ERANGE;
  	while (yourtm.tm_mday <= 0) {
  		if (increment_overflow(&yourtm.tm_year, -1))
! 			return ERANGE;
  		i = yourtm.tm_year + (1 < yourtm.tm_mon);
  		yourtm.tm_mday += year_lengths[isleap(i)];
  	}
***************
*** 1368,1374 ****
  		i = yourtm.tm_year + (1 < yourtm.tm_mon);
  		yourtm.tm_mday -= year_lengths[isleap(i)];
  		if (increment_overflow(&yourtm.tm_year, 1))
! 			return WRONG;
  	}
  	for ( ; ; ) {
  		i = mon_lengths[isleap(yourtm.tm_year)][yourtm.tm_mon];
--- 1446,1452 ----
  		i = yourtm.tm_year + (1 < yourtm.tm_mon);
  		yourtm.tm_mday -= year_lengths[isleap(i)];
  		if (increment_overflow(&yourtm.tm_year, 1))
! 			return ERANGE;
  	}
  	for ( ; ; ) {
  		i = mon_lengths[isleap(yourtm.tm_year)][yourtm.tm_mon];
***************
*** 1378,1388 ****
  		if (++yourtm.tm_mon >= MONSPERYEAR) {
  			yourtm.tm_mon = 0;
  			if (increment_overflow(&yourtm.tm_year, 1))
! 				return WRONG;
  		}
  	}
  	if (increment_overflow(&yourtm.tm_year, -TM_YEAR_BASE))
! 		return WRONG;
  	if (yourtm.tm_year + TM_YEAR_BASE < EPOCH_YEAR) {
  		/*
  		** We can't set tm_sec to 0, because that might push the
--- 1456,1466 ----
  		if (++yourtm.tm_mon >= MONSPERYEAR) {
  			yourtm.tm_mon = 0;
  			if (increment_overflow(&yourtm.tm_year, 1))
! 				return ERANGE;
  		}
  	}
  	if (increment_overflow(&yourtm.tm_year, -TM_YEAR_BASE))
! 		return ERANGE;
  	if (yourtm.tm_year + TM_YEAR_BASE < EPOCH_YEAR) {
  		/*
  		** We can't set tm_sec to 0, because that might push the
***************
*** 1393,1399 ****
  		** which is a safer assumption than using 58 would be.
  		*/
  		if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN))
! 			return WRONG;
  		saved_seconds = yourtm.tm_sec;
  		yourtm.tm_sec = SECSPERMIN - 1;
  	} else {
--- 1471,1477 ----
  		** which is a safer assumption than using 58 would be.
  		*/
  		if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN))
! 			return ERANGE;
  		saved_seconds = yourtm.tm_sec;
  		yourtm.tm_sec = SECSPERMIN - 1;
  	} else {
***************
*** 1412,1424 ****
  	*/
  	t = TYPE_SIGNED(time_t) ? 0 : (((time_t) 1) << bits);
  	for ( ; ; ) {
! 		(*funcp)(&t, offset, &mytm);
  		dir = tmcomp(&mytm, &yourtm);
  		if (dir != 0) {
! 			if (bits-- < 0)
! 				return WRONG;
! 			if (bits < 0)
! 				--t; /* may be needed if new t is minimal */
  			else if (dir > 0)
  				t -= ((time_t) 1) << bits;
  			else	t += ((time_t) 1) << bits;
--- 1490,1517 ----
  	*/
  	t = TYPE_SIGNED(time_t) ? 0 : (((time_t) 1) << bits);
  	for ( ; ; ) {
! 		tzsub(&t, sp, offset, &mytm, 0);
  		dir = tmcomp(&mytm, &yourtm);
  		if (dir != 0) {
! 			if (bits-- < 0) {
! 				/*
! 				** Assume that MAX_TIME and MIN_TIME aren't
! 				** near a leap-forward interval.
! 				** With signed 32-bit time_t, they're in
! 				** January and December respectively,
! 				** so it's unlikely.
! 				*/
! 				if (t == MAX_TIME || t == MIN_TIME)
! 					return ERANGE;
! 				return EINVAL;
! 			}
! 			if (bits < 0) {
! 				/* may be needed if new t is minimal,
! 				** since we started above the median.
! 				*/
! 				if (dir > 0)
! 					--t;
! 			}
  			else if (dir > 0)
  				t -= ((time_t) 1) << bits;
  			else	t += ((time_t) 1) << bits;
***************
*** 1435,1447 ****
  		/*
  		** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's.
  		*/
- 		sp = (const struct state *)
- 			(((void *) funcp == (void *) localsub) ?
- 			lclptr : gmtptr);
- #ifdef ALL_STATE
  		if (sp == NULL)
! 			return WRONG;
! #endif /* defined ALL_STATE */
  		for (i = sp->typecnt - 1; i >= 0; --i) {
  			if (sp->ttis[i].tt_isdst != yourtm.tm_isdst)
  				continue;
--- 1528,1535 ----
  		/*
  		** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's.
  		*/
  		if (sp == NULL)
! 			return EINVAL;
  		for (i = sp->typecnt - 1; i >= 0; --i) {
  			if (sp->ttis[i].tt_isdst != yourtm.tm_isdst)
  				continue;
***************
*** 1450,1456 ****
  					continue;
  				newt = t + sp->ttis[j].tt_gmtoff -
  					sp->ttis[i].tt_gmtoff;
! 				(*funcp)(&newt, offset, &mytm);
  				if (tmcomp(&mytm, &yourtm) != 0)
  					continue;
  				if (mytm.tm_isdst != yourtm.tm_isdst)
--- 1538,1544 ----
  					continue;
  				newt = t + sp->ttis[j].tt_gmtoff -
  					sp->ttis[i].tt_gmtoff;
! 				tzsub(&newt, sp, offset, &mytm, 0);
  				if (tmcomp(&mytm, &yourtm) != 0)
  					continue;
  				if (mytm.tm_isdst != yourtm.tm_isdst)
***************
*** 1462,1524 ****
  				goto label;
  			}
  		}
! 		return WRONG;
  	}
  label:
  	newt = t + saved_seconds;
  	if ((newt < t) != (saved_seconds < 0))
! 		return WRONG;
  	t = newt;
! 	(*funcp)(&t, offset, tmp);
! 	*okayp = TRUE;
! 	return t;
  }
  
! static time_t
! time2(tmp, funcp, offset, okayp)
  struct tm * const	tmp;
! void (* const		funcp) P((const time_t*, long, struct tm*));
  const long		offset;
- int * const		okayp;
  {
  	time_t	t;
  
  	/*
  	** First try without normalization of seconds
  	** (in case tm_sec contains a value associated with a leap second).
  	** If that fails, try with normalization of seconds.
  	*/
! 	t = time2sub(tmp, funcp, offset, okayp, FALSE);
! 	return *okayp ? t : time2sub(tmp, funcp, offset, okayp, TRUE);
  }
  
! static time_t
! time1(tmp, funcp, offset)
  struct tm * const	tmp;
! void (* const		funcp) P((const time_t *, long, struct tm *));
  const long		offset;
  {
! 	register time_t			t;
! 	register const struct state *	sp;
  	register int			samei, otheri;
! 	int				okay;
  
  	if (tmp->tm_isdst > 1)
  		tmp->tm_isdst = 1;
! 	t = time2(tmp, funcp, offset, &okay);
  #ifdef PCTS
! 	/*
! 	** PCTS code courtesy Grant Sullivan ([email-masked]).
! 	*/
! 	if (okay)
! 		return t;
! 	if (tmp->tm_isdst < 0)
! 		tmp->tm_isdst = 0;	/* reset to std and try again */
! #endif /* defined PCTS */
! #ifndef PCTS
! 	if (okay || tmp->tm_isdst < 0)
! 		return t;
  #endif /* !defined PCTS */
  	/*
  	** We're supposed to assume that somebody took a time of one type
  	** and did some math on it that yielded a "struct tm" that's bad.
--- 1550,1621 ----
  				goto label;
  			}
  		}
! 		return EINVAL;
  	}
  label:
  	newt = t + saved_seconds;
  	if ((newt < t) != (saved_seconds < 0))
! 		return ERANGE;
  	t = newt;
! 	tzsub(&t, sp, offset, tmp, 0);
! 	*timep = t;
! 	return 0;
  }
  
! static int
! time2(timep, tmp, tz, offset)
! time_t *		timep;
  struct tm * const	tmp;
! const struct tzinfo *	tz;
  const long		offset;
  {
  	time_t	t;
+ 	int err;
  
  	/*
  	** First try without normalization of seconds
  	** (in case tm_sec contains a value associated with a leap second).
  	** If that fails, try with normalization of seconds.
  	*/
! 	err = time2sub(&t, tmp, tz, offset, FALSE);
! 	if (err == 0) {
! 		*timep = t;
! 		return 0;
! 	}
! 	return time2sub(timep, tmp, tz, offset, TRUE);
  }
  
! static int
! time1(timep, tmp, sp, offset, accomodate_gap)
! time_t *		timep;
  struct tm * const	tmp;
! const struct tzinfo *	sp;
  const long		offset;
+ int			accomodate_gap;
  {
! 	time_t				t;
  	register int			samei, otheri;
! 	int				err;
  
  	if (tmp->tm_isdst > 1)
  		tmp->tm_isdst = 1;
! 	err = time2(&t, tmp, sp, offset);
! 	if (err == 0) {
! 		*timep = t;
! 		return 0;
! 	}
! 	if (tmp->tm_isdst < 0) {
  #ifdef PCTS
! 		/*
! 		** PCTS code courtesy Grant Sullivan ([email-masked]).
! 		*/
! 		if (accomodate_gap)
! 			/* reset to std and try again */
! 			tmp->tm_isdst = 0;
! 	        else
  #endif /* !defined PCTS */
+ 			return (err != 0 ? err : EINVAL);
+ 	}
  	/*
  	** We're supposed to assume that somebody took a time of one type
  	** and did some math on it that yielded a "struct tm" that's bad.
***************
*** 1528,1539 ****
  	/*
  	** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's.
  	*/
- 	sp = (const struct state *) (((void *) funcp == (void *) localsub) ?
- 		lclptr : gmtptr);
- #ifdef ALL_STATE
  	if (sp == NULL)
! 		return WRONG;
! #endif /* defined ALL_STATE */
  	for (samei = sp->typecnt - 1; samei >= 0; --samei) {
  		if (sp->ttis[samei].tt_isdst != tmp->tm_isdst)
  			continue;
--- 1625,1632 ----
  	/*
  	** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's.
  	*/
  	if (sp == NULL)
! 		return (err != 0 ? err : EINVAL);
  	for (samei = sp->typecnt - 1; samei >= 0; --samei) {
  		if (sp->ttis[samei].tt_isdst != tmp->tm_isdst)
  			continue;
***************
*** 1543,1565 ****
  			tmp->tm_sec += sp->ttis[otheri].tt_gmtoff -
  					sp->ttis[samei].tt_gmtoff;
  			tmp->tm_isdst = !tmp->tm_isdst;
! 			t = time2(tmp, funcp, offset, &okay);
! 			if (okay)
! 				return t;
  			tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff -
  					sp->ttis[samei].tt_gmtoff;
  			tmp->tm_isdst = !tmp->tm_isdst;
  		}
  	}
! 	return WRONG;
  }
  
  time_t
  mktime(tmp)
  struct tm * const	tmp;
  {
! 	tzset();
! 	return time1(tmp, localsub, 0L);
  }
  
  #ifdef STD_INSPIRED
--- 1636,1670 ----
  			tmp->tm_sec += sp->ttis[otheri].tt_gmtoff -
  					sp->ttis[samei].tt_gmtoff;
  			tmp->tm_isdst = !tmp->tm_isdst;
! 			err = time2(&t, tmp, sp, offset);
! 			if (err == 0) {
! 				*timep = t;
! 				return 0;
! 			}
  			tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff -
  					sp->ttis[samei].tt_gmtoff;
  			tmp->tm_isdst = !tmp->tm_isdst;
  		}
  	}
! 	return (err != 0 ? err : EINVAL);
  }
  
  time_t
  mktime(tmp)
  struct tm * const	tmp;
  {
! 	time_t mktime_return_value;
! 	int err;
! 	struct tzinfo *tz;
! 	int process_wide;
! 
! 	LOCK_LCLPTR();
! 	tz = GET_LCLPTR(&process_wide);
! 	if (process_wide)
! 		tzset_basic(tz);
! 	err = time1(&mktime_return_value, tmp, tz, 0L, 1);
! 	UNLOCK_LCLPTR();
! 	return (err == 0 ? mktime_return_value : WRONG);
  }
  
  #ifdef STD_INSPIRED
***************
*** 1576,1583 ****
  timegm(tmp)
  struct tm * const	tmp;
  {
  	tmp->tm_isdst = 0;
! 	return time1(tmp, gmtsub, 0L);
  }
  
  time_t
--- 1681,1692 ----
  timegm(tmp)
  struct tm * const	tmp;
  {
+ 	time_t timegm_return_value;
+ 	int err;
+ 
  	tmp->tm_isdst = 0;
! 	err = time1(&timegm_return_value, tmp, GET_GMTPTR(), 0L, 1);
! 	return (err == 0 ? timegm_return_value : WRONG);
  }
  
  time_t
***************
*** 1585,1594 ****
  struct tm * const	tmp;
  const long		offset;
  {
  	tmp->tm_isdst = 0;
! 	return time1(tmp, gmtsub, offset);
  }
  
  #endif /* defined STD_INSPIRED */
  
  #ifdef CMUCS
--- 1694,1811 ----
  struct tm * const	tmp;
  const long		offset;
  {
+ 	time_t timeoff_return_value;
+ 	int err;
+ 
  	tmp->tm_isdst = 0;
! 	err = time1(&timeoff_return_value, tmp, GET_GMTPTR(), offset, 1);
! 	return (err == 0 ? timeoff_return_value : WRONG);
! }
! 
! 
! /*
! ** Thread-safety functions
! */
! 
! int
! tz_prep(tz_p, name)
! struct tzinfo** tz_p;
! const char * name;
! {
! 	struct tzinfo * result;
! 
! 	result = (struct tzinfo *) malloc(sizeof(struct tzinfo));
! 	if (result == NULL) {
! 		return errno;
! 	}
! 
! 	if (name != NULL && *name == '\0') {
! 		/* Zero-length string.  Indicates GMT. */
! #ifdef TZCODE_GMT_USAGE
! 		/*
! 		** Traditional tzcode usage is that the zero-length string
! 		** means GMT without leap seconds.
! 		*/
! 		result->leapcnt = 0;		/* so, we're off a little */
! 		result->timecnt = 0;
! 		result->typecnt = 0;
! 		result->ttis[0].tt_isdst = 0;
! 		result->ttis[0].tt_gmtoff = 0;
! 		result->ttis[0].tt_abbrind = 0;
! 		(void) strcpy(result->chars, gmt);
! #else
! 		gmtload(result);
! #endif
! 	}
! 	else {
! 		/*
! 		** 1.  Try to tzload.
! 		** 2.  If name is non-NULL and doesn't begin with :,
! 		**     try to tzparse.
! 		** 3.  Fail ENOENT.
! 		*/
! 		if (tzload(name, result) != 0) {
! 			if (name != NULL && name[0] != ':') {
! 				if (tzparse(name, result, FALSE) != 0) {
! 					goto noentry;
! 				}
! 			}
! 			else {
! 				goto noentry;
! 			}
! 		}
! 	}
! 
! 	*tz_p = result;
! 	return 0;
! 
!  noentry:
! 	free(result);
! 	return ENOENT;
  }
  
+ 
+ void
+ tz_free(tz)
+ struct tzinfo* tz;
+ {
+ 	free(tz);
+ }
+ 
+ 
+ int
+ time_make(timep, tmp, tz)
+ time_t * timep;
+ struct tm *tmp;
+ const struct tzinfo *tz;
+ {
+ 	return time1(timep, tmp, tz, 0L, 0);
+ }
+ 
+ 
+ int time_breakup(tmp, timep, tz)
+ struct tm * tmp;
+ const time_t * timep;
+ const struct tzinfo * tz;
+ {
+ 	tzsub(timep, tz, 0L, tmp, 0);
+ 	return 0;
+ }
+ 
+ 
+ int
+ pthread_settz(tz)
+ const struct tzinfo * tz;
+ {
+ 	if (tz != NULL) {
+ 		return SET_THREAD_LCLPTR(tz);
+ 	}
+ 	else {
+ 		return CLEAR_THREAD_LCLPTR();
+ 	}
+ }
+ 
+ 
  #endif /* defined STD_INSPIRED */
  
  #ifdef CMUCS
***************
*** 1626,1640 ****
  */
  
  static long
! leapcorr(timep)
! time_t *	timep;
  {
! 	register struct state *		sp;
! 	register struct lsinfo *	lp;
  	register int			i;
  
! 	sp = lclptr;
! 	i = sp->leapcnt;
  	while (--i >= 0) {
  		lp = &sp->lsis[i];
  		if (*timep >= lp->ls_trans)
--- 1843,1856 ----
  */
  
  static long
! leapcorr(timep, sp)
! time_t *			timep;
! const struct tzinfo * const	sp;
  {
! 	register const struct lsinfo *	lp;
  	register int			i;
  
! 	i = (sp == NULL ? 0 : sp->leapcnt);
  	while (--i >= 0) {
  		lp = &sp->lsis[i];
  		if (*timep >= lp->ls_trans)
***************
*** 1647,1654 ****
  time2posix(t)
  time_t	t;
  {
! 	tzset();
! 	return t - leapcorr(&t);
  }
  
  time_t
--- 1863,1873 ----
  time2posix(t)
  time_t	t;
  {
! 	const struct tzinfo * sp;
! 
! 	sp = GET_GMTPTR();
! 	gmtset(sp);
! 	return t - leapcorr(&t, sp);
  }
  
  time_t
***************
*** 1657,1683 ****
  {
  	time_t	x;
  	time_t	y;
  
- 	tzset();
  	/*
  	** For a positive leap second hit, the result
  	** is not unique.  For a negative leap second
  	** hit, the corresponding time doesn't exist,
  	** so we return an adjacent second.
  	*/
! 	x = t + leapcorr(&t);
! 	y = x - leapcorr(&x);
  	if (y < t) {
  		do {
  			x++;
! 			y = x - leapcorr(&x);
  		} while (y < t);
  		if (t != y)
  			return x - 1;
  	} else if (y > t) {
  		do {
  			--x;
! 			y = x - leapcorr(&x);
  		} while (y > t);
  		if (t != y)
  			return x + 1;
--- 1876,1905 ----
  {
  	time_t	x;
  	time_t	y;
+ 	const struct tzinfo * sp;
+ 
+ 	sp = GET_GMTPTR();
+ 	gmtset(sp);
  
  	/*
  	** For a positive leap second hit, the result
  	** is not unique.  For a negative leap second
  	** hit, the corresponding time doesn't exist,
  	** so we return an adjacent second.
  	*/
! 	x = t + leapcorr(&t, sp);
! 	y = x - leapcorr(&x, sp);
  	if (y < t) {
  		do {
  			x++;
! 			y = x - leapcorr(&x, sp);
  		} while (y < t);
  		if (t != y)
  			return x - 1;
  	} else if (y > t) {
  		do {
  			--x;
! 			y = x - leapcorr(&x, sp);
  		} while (y > t);
  		if (t != y)
  			return x + 1;
diff -crN -x *~ ../tzcode2001c/private.h thread-safe-2/private.h
*** ../tzcode2001c/private.h	Tue Jun  5 13:48:21 2001
--- thread-safe-2/private.h	Mon Jun 18 15:59:54 2001
***************
*** 206,211 ****
--- 206,215 ----
  void	ifree P((char * pointer));
  char *	scheck P((const char *string, const char *format));
  
+ struct tzinfo;
+ const struct ttinfo * _tz_getttype P((const time_t * const timep,
+ 				      const struct tzinfo * sp));
+ 
  
  /*
  ** Finally, some convenience items.
***************
*** 237,242 ****
--- 241,256 ----
  #define INT_STRLEN_MAXIMUM(type) \
      ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + 1 + TYPE_SIGNED(type))
  #endif /* !defined INT_STRLEN_MAXIMUM */
+ 
+ /*
+ ** Big enough for something such as
+ ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n
+ ** (two three-character abbreviations, five strings denoting integers,
+ ** three explicit spaces, two explicit colons, a newline,
+ ** and a trailing ASCII nul).
+ */
+ 
+ #define ASCTIME_BUF_SIZE (3 * 2 + 5 * INT_STRLEN_MAXIMUM(int) + 3 + 2 + 1 + 1)
  
  /*
  ** INITIALIZE(x)
diff -crN -x *~ ../tzcode2001c/pthread_settz.3 thread-safe-2/pthread_settz.3
*** ../tzcode2001c/pthread_settz.3	Wed Dec 31 19:00:00 1969
--- thread-safe-2/pthread_settz.3	Mon Jun 18 15:46:08 2001
***************
*** 0 ****
--- 1,100 ----
+ .TH PTHREAD_SETTZ 3
+ .SH NAME
+ pthread_settz \- locally set the time zone for the current POSIX thread
+ .SH SYNOPSIS
+ .nf
+ .B #include <time.h>
+ .B #include <pthread.h>
+ .PP
+ .B int pthread_settz(tz)
+ .B const struct tzinfo *tz;
+ .PP
+ .B cc ... -ltz -lpthread
+ .fi
+ .SH DESCRIPTION
+ .B Pthread_settz()
+ sets the time zone object to be used for time functions called from the
+ current POSIX thread to the time zone object specified by
+ .IR tz ,
+ if it is non-\fBNULL\fR.  If
+ .IR tz
+ is
+ .BR NULL ,
+ the time zone for the current POSIX thread is reset to the global time zone
+ object. The time zone setting for all other threads remains the same.
+ .PP
+ Once
+ .B pthread_settz()
+ has been called with a non-\fBNULL\fR value, all calls to the functions
+ .BR localtime(3) ,
+ .BR localtime_r(3) ,
+ .BR ctime(3) ,
+ .BR ctime_r(3) ,
+ .BR asctime(3) ,
+ .BR asctime_r(3) ,
+ .BR mktime(3) ,
+ and
+ .BR strftime(3)
+ in the thread from which 
+ .B pthread_settz()
+ was called will use the time zone specified by
+ .IR tz ,
+ rather than the process-wide time zone.
+ .PP
+ .B pthread_settz()
+ does not alter the global variable
+ .BR tzname .
+ No function which normally implicitly sets
+ .B tzname
+ will do so while a thread-local time zone is in effect.
+ .PP
+ Calls to 
+ .B tzset(3)
+ or
+ .BR tzsetwall(3) ,
+ while a thread-local time zone is in effect, will alter the global time
+ zone, but will not affect the local time zone.
+ .PP
+ .B pthread_settz()
+ may allocate a thread-specific data key, which may count towards the number
+ of keys
+ .B PTHREAD_KEYS_MAX
+ that can be allocated by
+ .BR pthread_key_create() .
+ .SH RETURN VALUES
+ Upon successful completion,
+ .B pthread_settz()
+ function returns a value of
+ .BR 0 .
+ Otherwise, an error number is returned to indicate an error.
+ .SH ERRORS
+ .B pthread_settz()
+ may fail if:
+ .IP \fBENOMEM\fR
+ Insufficient memory exists to store the time zone information for the
+ thread.
+ .PP
+ .IP \fBEAGAIN\fR
+ The system lacked the necessary resources to associate the time zone
+ information with the thread.
+ .SH SEE ALSO
+ getenv(3),
+ newctime(3),
+ newstrftime(3),
+ newtzset(3),
+ pthread_key_create(3),
+ time(2),
+ time_make(3),
+ tz_prep(3),
+ tzfile(5)
+ .SH NOTES
+ This function allows existing code which uses the ISO C APIs to work
+ correctly, unmodified, in a threaded environment.  However, code which uses
+ use the extended parts of the POSIX APIs (involving global variables, or
+ .BR tzset() )
+ may not work correctly unmodified.
+ .PP
+ No parallel function
+ .B pthread_gettz()
+ is defined.
+ .\" @(#)pthread_settz.3	1.0
diff -crN -x *~ ../tzcode2001c/strftime.c thread-safe-2/strftime.c
*** ../tzcode2001c/strftime.c	Tue Jun  5 13:49:50 2001
--- thread-safe-2/strftime.c	Mon Jun 18 16:11:40 2001
***************
*** 9,14 ****
--- 9,15 ----
  #endif /* !defined lint */
  
  #include "private.h"
+ #include "struct_tzinfo.h"
  
  /*
  ** Copyright (c) 1989 The Regents of the University of California.
***************
*** 37,42 ****
--- 38,47 ----
  #include "fcntl.h"
  #include "locale.h"
  
+ #include "tztimeext.h"
+ 
+ #include TZTHREAD_HEADER_H
+ 
  struct lc_time_T {
  	const char *	mon[MONSPERYEAR];
  	const char *	month[MONSPERYEAR];
***************
*** 108,114 ****
  
  static char *	_add P((const char *, char *, const char *));
  static char *	_conv P((int, const char *, char *, const char *));
! static char *	_fmt P((const char *, const struct tm *, char *, const char *, int *));
  
  size_t strftime P((char *, size_t, const char *, const struct tm *));
  
--- 113,119 ----
  
  static char *	_add P((const char *, char *, const char *));
  static char *	_conv P((int, const char *, char *, const char *));
! static char *	_fmt P((const char *, const struct tm *, char *, const char *, int *, const struct tzinfo *));
  
  size_t strftime P((char *, size_t, const char *, const struct tm *));
  
***************
*** 125,145 ****
  #define IN_ALL	3
  
  size_t
! strftime(s, maxsize, format, t)
  char * const		s;
  const size_t		maxsize;
  const char * const	format;
  const struct tm * const	t;
  {
  	char *	p;
  	int	warn;
  
- 	tzset();
  #ifdef LOCALE_HOME
  	localebuf.mon[0] = 0;
  #endif /* defined LOCALE_HOME */
  	warn = IN_NONE;
! 	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn);
  #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
  	if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
  		(void) fprintf(stderr, "\n");
--- 130,151 ----
  #define IN_ALL	3
  
  size_t
! strftime_z(s, maxsize, format, t, tz)
  char * const		s;
  const size_t		maxsize;
  const char * const	format;
  const struct tm * const	t;
+ const struct tzinfo * tz;
  {
  	char *	p;
  	int	warn;
  
  #ifdef LOCALE_HOME
  	localebuf.mon[0] = 0;
  #endif /* defined LOCALE_HOME */
  	warn = IN_NONE;
! 	p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn,
! 		 tz);
  #ifndef NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU
  	if (warn != IN_NONE && getenv(YEAR_2000_NAME) != NULL) {
  		(void) fprintf(stderr, "\n");
***************
*** 156,174 ****
  		(void) fprintf(stderr, "\n");
  	}
  #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
! 	if (p == s + maxsize)
  		return 0;
  	*p = '\0';
  	return p - s;
  }
  
  static char *
! _fmt(format, t, pt, ptlim, warnp)
  const char *		format;
  const struct tm * const	t;
  char *			pt;
  const char * const	ptlim;
  int *			warnp;
  {
  	for ( ; *format; ++format) {
  		if (*format == '%') {
--- 162,206 ----
  		(void) fprintf(stderr, "\n");
  	}
  #endif /* !defined NO_RUN_TIME_WARNINGS_ABOUT_YEAR_2000_PROBLEMS_THANK_YOU */
! 	if (p >= s + maxsize)
! 		/* XXX */
  		return 0;
  	*p = '\0';
  	return p - s;
  }
  
+ 
+ size_t
+ strftime(s, maxsize, format, t)
+ char * const		s;
+ const size_t		maxsize;
+ const char * const	format;
+ const struct tm * const	t;
+ {
+ 	struct tzinfo * lclptr;
+ 	int dummy;
+ 	size_t ret;
+ 	tzset();
+ 
+ 	LOCK_LCLPTR();
+ 	lclptr = GET_LCLPTR(&dummy);
+ 
+ 	ret = strftime_z(s, maxsize, format, t, lclptr);
+ 	UNLOCK_LCLPTR();
+ 
+ 	if (ret > maxsize)
+ 		ret = 0;
+ 	return ret;
+ }
+ 
  static char *
! _fmt(format, t, pt, ptlim, warnp, tz)
  const char *		format;
  const struct tm * const	t;
  char *			pt;
  const char * const	ptlim;
  int *			warnp;
+ const struct tzinfo *	tz;
  {
  	for ( ; *format; ++format) {
  		if (*format == '%') {
***************
*** 217,223 ****
  				{
  				int warn2 = IN_SOME;
  
! 				pt = _fmt(Locale->c_fmt, t, pt, ptlim, warnp);
  				if (warn2 == IN_ALL)
  					warn2 = IN_THIS;
  				if (warn2 > *warnp)
--- 249,255 ----
  				{
  				int warn2 = IN_SOME;
  
! 				pt = _fmt(Locale->c_fmt, t, pt, ptlim, warnp, tz);
  				if (warn2 == IN_ALL)
  					warn2 = IN_THIS;
  				if (warn2 > *warnp)
***************
*** 225,231 ****
  				}
  				continue;
  			case 'D':
! 				pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
  				continue;
  			case 'd':
  				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
--- 257,263 ----
  				}
  				continue;
  			case 'D':
! 				pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp, tz);
  				continue;
  			case 'd':
  				pt = _conv(t->tm_mday, "%02d", pt, ptlim);
***************
*** 246,252 ****
  				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
  				continue;
  			case 'F':
! 				pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
  				continue;
  			case 'H':
  				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
--- 278,284 ----
  				pt = _conv(t->tm_mday, "%2d", pt, ptlim);
  				continue;
  			case 'F':
! 				pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp, tz);
  				continue;
  			case 'H':
  				pt = _conv(t->tm_hour, "%02d", pt, ptlim);
***************
*** 310,319 ****
  					pt, ptlim);
  				continue;
  			case 'R':
! 				pt = _fmt("%H:%M", t, pt, ptlim, warnp);
  				continue;
  			case 'r':
! 				pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
  				continue;
  			case 'S':
  				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
--- 342,351 ----
  					pt, ptlim);
  				continue;
  			case 'R':
! 				pt = _fmt("%H:%M", t, pt, ptlim, warnp, tz);
  				continue;
  			case 'r':
! 				pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp, tz);
  				continue;
  			case 'S':
  				pt = _conv(t->tm_sec, "%02d", pt, ptlim);
***************
*** 336,342 ****
  				}
  				continue;
  			case 'T':
! 				pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
  				continue;
  			case 't':
  				pt = _add("\t", pt, ptlim);
--- 368,374 ----
  				}
  				continue;
  			case 'T':
! 				pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp, tz);
  				continue;
  			case 't':
  				pt = _add("\t", pt, ptlim);
***************
*** 449,455 ****
  				** "date as dd-bbb-YYYY"
  				** (ado, 1993-05-24)
  				*/
! 				pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
  				continue;
  			case 'W':
  				pt = _conv((t->tm_yday + DAYSPERWEEK -
--- 481,487 ----
  				** "date as dd-bbb-YYYY"
  				** (ado, 1993-05-24)
  				*/
! 				pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp, tz);
  				continue;
  			case 'W':
  				pt = _conv((t->tm_yday + DAYSPERWEEK -
***************
*** 462,474 ****
  				pt = _conv(t->tm_wday, "%d", pt, ptlim);
  				continue;
  			case 'X':
! 				pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
  				continue;
  			case 'x':
  				{
  				int	warn2 = IN_SOME;
  
! 				pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
  				if (warn2 == IN_ALL)
  					warn2 = IN_THIS;
  				if (warn2 > *warnp)
--- 494,506 ----
  				pt = _conv(t->tm_wday, "%d", pt, ptlim);
  				continue;
  			case 'X':
! 				pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp, tz);
  				continue;
  			case 'x':
  				{
  				int	warn2 = IN_SOME;
  
! 				pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2, tz);
  				if (warn2 == IN_ALL)
  					warn2 = IN_THIS;
  				if (warn2 > *warnp)
***************
*** 490,498 ****
  					pt = _add(t->TM_ZONE, pt, ptlim);
  				else
  #endif /* defined TM_ZONE */
! 				if (t->tm_isdst >= 0)
! 					pt = _add(tzname[t->tm_isdst != 0],
  						pt, ptlim);
  				/*
  				** C99 says that %Z must be replaced by the
  				** empty string if the time zone is not
--- 522,539 ----
  					pt = _add(t->TM_ZONE, pt, ptlim);
  				else
  #endif /* defined TM_ZONE */
! 				if (t->tm_isdst >= 0) {
! 					time_t tval;
! 					struct tm tcopy = *t;
! 					const struct ttinfo *	ttisp;
! 
! 				if (time_make(&tval, &tcopy, tz) != 0)
! 					continue;
! 				ttisp = _tz_getttype(&tval, tz);
! 
! 				pt = _add(&tz->chars[ttisp->tt_abbrind],
  						pt, ptlim);
+ 				}
  				/*
  				** C99 says that %Z must be replaced by the
  				** empty string if the time zone is not
***************
*** 514,545 ****
  				** be computed by looking only at
  				** tm_isdst.  This requirement is
  				** incorrect, since it means the code
! 				** must rely on magic (in this case
! 				** altzone and timezone), and the
  				** magic might not have the correct
! 				** offset.  Doing things correctly is
! 				** tricky and requires disobeying C99;
! 				** see GNU C strftime for details.
! 				** For now, punt and conform to the
! 				** standard, even though it's incorrect.
! 				**
! 				** C99 says that %z must be replaced by the
! 				** empty string if the time zone is not
! 				** determinable, so output nothing if the
! 				** appropriate variables are not available.
  				*/
! 				if (t->tm_isdst == 0)
! #ifdef USG_COMPAT
! 					diff = -timezone;
! #else /* defined USG_COMPAT */
! 					continue;
! #endif /* !defined USG_COMPAT */
! 				else
! #ifdef ALTZONE
! 					diff = -altzone;
! #else /* !defined ALTZONE */
! 					continue;
! #endif /* !defined ALTZONE */
  #endif /* !defined TM_GMTOFF */
  				if (diff < 0) {
  					sign = "-";
--- 555,579 ----
  				** be computed by looking only at
  				** tm_isdst.  This requirement is
  				** incorrect, since it means the code
! 				** must rely on magic, and the
  				** magic might not have the correct
! 				** offset.
! 				** We perform hopefully-correct magic by
! 				** peeking inside the struct tzinfo
! 				** structure, in an encapsulation
! 				** violation, to find the appropriate
! 				** transition type.
  				*/
! 				{
! 					time_t tval;
! 					struct tm tcopy = *t;
! 					const struct ttinfo *	ttisp;
! 				  
! 					if (time_make(&tval, &tcopy, tz) != 0)
! 						continue;
! 					ttisp = _tz_getttype(&tval, tz);
! 					diff = ttisp->tt_gmtoff;
! 				}
  #endif /* !defined TM_GMTOFF */
  				if (diff < 0) {
  					sign = "-";
***************
*** 553,559 ****
  				continue;
  			case '+':
  				pt = _fmt(Locale->date_fmt, t, pt, ptlim,
! 					warnp);
  				continue;
  			case '%':
  			/*
--- 587,593 ----
  				continue;
  			case '+':
  				pt = _fmt(Locale->date_fmt, t, pt, ptlim,
! 					warnp, tz);
  				continue;
  			case '%':
  			/*
***************
*** 603,609 ****
  	static const char	locale_home[] = LOCALE_HOME;
  	static const char	lc_time[] = "LC_TIME";
  	static char *		locale_buf;
- 	static char		locale_buf_C[] = "C";
  
  	int			fd;
  	int			oldsun;	/* "...ain't got nothin' to do..." */
--- 637,642 ----
***************
*** 664,671 ****
  		goto bad_locale;
  	bufsize = namesize + st.st_size;
  	locale_buf = NULL;
! 	lbuf = (lbuf == NULL || lbuf == locale_buf_C) ?
! 		malloc(bufsize) : realloc(lbuf, bufsize);
  	if (lbuf == NULL)
  		goto bad_locale;
  	(void) strcpy(lbuf, name);
--- 697,703 ----
  		goto bad_locale;
  	bufsize = namesize + st.st_size;
  	locale_buf = NULL;
! 	lbuf = (lbuf == NULL) ?	 malloc(bufsize) : realloc(lbuf, bufsize);
  	if (lbuf == NULL)
  		goto bad_locale;
  	(void) strcpy(lbuf, name);
***************
*** 714,720 ****
  	(void) close(fd);
  no_locale:
  	localebuf = C_time_locale;
! 	locale_buf = locale_buf_C;
  	return &localebuf;
  }
  #endif /* defined LOCALE_HOME */
--- 746,752 ----
  	(void) close(fd);
  no_locale:
  	localebuf = C_time_locale;
! 	locale_buf = NULL;
  	return &localebuf;
  }
  #endif /* defined LOCALE_HOME */
diff -crN -x *~ ../tzcode2001c/struct_tzinfo.h thread-safe-2/struct_tzinfo.h
*** ../tzcode2001c/struct_tzinfo.h	Wed Dec 31 19:00:00 1969
--- thread-safe-2/struct_tzinfo.h	Mon Jun 18 15:46:07 2001
***************
*** 0 ****
--- 1,58 ----
+ #ifndef STRUCT_TIMEZONE_H
+ 
+ #define STRUCT_TIMEZONE_H
+ 
+ #include "private.h"
+ #include "tzfile.h"
+ 
+ /*
+ ** This file is in the public domain, so clarified as of
+ ** 1996-06-05 by Arthur David Olson ([email-masked]).
+ */
+ 
+ /*
+ ** This header is for use ONLY with the time conversion code.
+ ** There is no guarantee that it will remain unchanged,
+ ** or that it will remain at all.
+ ** Do NOT copy it to any system include directory.
+ ** Thank you!
+ */
+ 
+ struct ttinfo {				/* time type information */
+ 	long		tt_gmtoff;	/* UTC offset in seconds */
+ 	int		tt_isdst;	/* used to set tm_isdst */
+ 	int		tt_abbrind;	/* abbreviation list index */
+ 	int		tt_ttisstd;	/* TRUE if transition is std time */
+ 	int		tt_ttisgmt;	/* TRUE if transition is UTC */
+ };
+ 
+ struct lsinfo {				/* leap second information */
+ 	time_t		ls_trans;	/* transition time */
+ 	long		ls_corr;	/* correction to apply */
+ };
+ 
+ #define BIGGEST(a, b)	(((a) > (b)) ? (a) : (b))
+ 
+ #ifdef TZNAME_MAX
+ #define MY_TZNAME_MAX	TZNAME_MAX
+ #endif /* defined TZNAME_MAX */
+ #ifndef TZNAME_MAX
+ #define MY_TZNAME_MAX	255
+ #endif /* !defined TZNAME_MAX */
+ 
+ struct tzinfo {
+ 	int		leapcnt;
+ 	int		timecnt;
+ 	int		typecnt;
+ 	int		charcnt;
+ 	time_t		ats[TZ_MAX_TIMES];
+ 	unsigned char	types[TZ_MAX_TIMES];
+ 	struct ttinfo	ttis[TZ_MAX_TYPES];
+ 	char		chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof "GMT"),
+ 				(2 * (MY_TZNAME_MAX + 1)))];
+ 	struct lsinfo	lsis[TZ_MAX_LEAPS];
+ };
+ 
+ 
+ 
+ #endif /* !defined STRUCT_TIMEZONE_H */
diff -crN -x *~ ../tzcode2001c/time_make.3 thread-safe-2/time_make.3
*** ../tzcode2001c/time_make.3	Wed Dec 31 19:00:00 1969
--- thread-safe-2/time_make.3	Mon Jun 18 15:46:08 2001
***************
*** 0 ****
--- 1,277 ----
+ .TH TIME_MAKE 3
+ .SH NAME
+ time_make, time_breakup, strftime_z \- manipulate time-zone-dependent
+ time conversions in a thread-safe manner
+ .SH SYNOPSIS
+ .nf
+ .B #include <time.h>
+ .B #include <sys/types.h>
+ .PP
+ .B int time_make(clock, tm, tz)
+ .B time_t *clock;
+ .B struct tm *tm;
+ .B const struct tzinfo *tz;
+ .PP
+ .B int time_breakup(result, clock, tz)
+ .B struct tm *result;
+ .B const time_t *clock;
+ .B const struct tzinfo *tz;
+ .PP
+ .B size_t strftime_z(buf, maxsize, format, timeptr, tz)
+ .B char * restrict buf;
+ .B size_t maxsize;
+ .B const char * restrict format;
+ .B const struct tm * restrict timeptr;
+ .B const struct tzinfo * restrict tz;
+ .PP
+ .B cc ... -ltz
+ .fi
+ .SH DESCRIPTION
+ .B Time_make()
+ interprets the broken-down time in
+ .I *tm
+ as a local time in the timezone specified in
+ .IR *tz,
+ and writes the corresponding value into
+ .IR *clock,
+ using the same encoding as that of the values returned by the
+ .I time function.
+ .PP
+ The original values of the
+ .B tm_wday
+ and
+ .B tm_yday
+ components of
+ .I *tm
+ are ignored, and the original values of the other components are not
+ restricted to their normal ranges.  (A positive or zero value for
+ .B tm_isdst
+ causes
+ .B time_make()
+ to presume initially that summer time (for example, Daylight Saving Time) is
+ or is not in effect for the specified time, respectively.  A negative value
+ for
+ .B tm_isdst
+ causes the
+ .B time_make() 
+ function to attempt to divine whether summer time is in effect for the
+ specified time.)
+ .PP
+ On successful completion, the values of the
+ .B tm_wday
+ and
+ .B tm_yday
+ components of
+ .I *tm
+ are set appropriately, and the other components are set to represent the
+ specified calendar time, but with their values forced to their normal
+ ranges; the final value of
+ .B tm_mday
+ is not set until
+ .B tm_mon
+ and
+ .B tm_year
+ are determined.
+ .PP
+ .B Time_breakup()
+ converts the time value in
+ .I *clock
+ to a broken-out time for the value, which it places in
+ .IR *result ,
+ after adjusting for the time zone specified in
+ .IR *tz .
+ .PP
+ .B Strftime_z()
+ formats the information from
+ .I timeptr
+ into the buffer
+ .I buf
+ according to the string pointed to by
+ .IR format .
+ The 
+ .I format
+ string follows the same specification as for the function strftime(3),
+ except that the values for the conversion specifiers
+ .B %Z
+ and
+ .B %z
+ are obtained from the time zone
+ .IR tz .
+ If
+ .B strftime_z()
+ is passed a
+ .B struct tm
+ created by a call to
+ .B time_make()
+ or
+ .B time_breakup()
+ which was given a different time zone object than
+ .IR tz ,
+ or if it is passed a
+ .B struct tm
+ created by
+ .BR localtime() ,
+ .BR localtime_r() ,
+ .BR gmtime() ,
+ .BR gmtime_r() ,
+ .BR mktime() , 
+ or
+ .BR timegm() ,
+ the behavior of the
+ .B %Z
+ and 
+ .B %z
+ conversion specifiers is undefined.
+ .SH RETURN VALUES
+ The
+ .B time_make()
+ function returns a value of
+ .B 0
+ on sucessful completion, sets
+ .IR *clock ,
+ and normalizes
+ .IR *tm .
+ On failure, it returns an error number, and leaves 
+ .I *clock
+ and
+ .I *tm
+ in an indeterminate state.
+ .PP
+ The 
+ .B time_breakup()
+ function returns
+ .BR 0 ,
+ and fills in
+ .IR *result ,
+ always.
+ .PP
+ On successful completion,
+ .B strftime_z()
+ fills in
+ .I buf
+ and returns the number of bytes converted.  On failure,
+ .B strftime_z()
+ returns the number of bytes of buffer that would be required to fully
+ perform the conversion (including the terminating
+ .BR NUL ),
+ and leaves 
+ .I buf
+ in an indeterminate state.
+ .SH ERRORS
+ .B time_make()
+ shall fail if:
+ .IP \fBERANGE\fR
+ .I *tm
+ does not represent a time representable by a time_t value.
+ .PP
+ .B time_make()
+ may fail if:
+ .IP \fBEINVAL\fR
+ .I *tm
+ does not represent a possible time in the timezone
+ .IR *tz .
+ (For instance, it indicates a time occuring during a leap-forward interval.)
+ .PP
+ No error status codes are defined for
+ .BR time_breakup() .
+ .PP
+ .B Strftime_z()
+ does not set or return error statuses.
+ .SH SEE ALSO
+ getenv(3),
+ newctime(3),
+ newstrftime(3),
+ newtzset(3),
+ pthread_settz(3),
+ time(2),
+ tz_prep(3),
+ tzfile(5)
+ .SH NOTES
+ .B time_make()
+ is a generalization of the ISO C function 
+ .B mktime(3)
+ and the BSD/tzcode function
+ .BR timegm(3) .
+ .B time_breakup()
+ is a generalization of the ISO C functions
+ .B localtime(3)
+ and
+ .BR gmtime(3) ,
+ and the POSIX functions
+ .B localtime_r(3)
+ and
+ .BR gmtime_r(3) .
+ .B strftime_z()
+ is a generalization of the ISO C function
+ .BR strftime(3) .
+ .PP
+ Depending on how
+ .B tzcode
+ was compiled,
+ .B time_make()
+ may handle impossible time values (e.g., values that fall during a
+ leap-forward interval) by failing and returning
+ .BR EINVAL ,
+ or by normalizing 
+ .I *tm
+ to a nearby valid time.
+ .PP
+ The return value of 
+ .B strftime_z()
+ on error has changed from that of 
+ .BR strftime() .
+ .B Strftime()
+ returns
+ .B 0
+ if 
+ .I buf
+ is not large enough. 
+ .BR Strftime_z() ,
+ instead, follows the example of ISO C 99's 
+ .BR snprintf(3) ,
+ by returning the number of bytes that would be required to fully convert the
+ format string.  This allows an appropriately-sized buffer to be allocated in
+ one step, rather than requiring a binary search.  Error returns can be
+ easily detected by checking (\fIret\fR <= \fImaxbuf\fR).
+ .PP
+ The names and calling conventions of 
+ .B time_make()
+ and 
+ .B time_breakup()
+ are inspired by Markus Kuhn's proposed
+ .B xtime_make()
+ and
+ .B xtime_breakup()
+ functions.  The
+ .B xtime_*
+ functions represent time using
+ .BR "struct xtime" ,
+ which is a much more sophisticated and better-defined representation of time
+ than
+ .BR time_t .
+ The
+ .B time_*
+ functions are designed to be less ambitious while still leaving room
+ for these future improvements.
+ .PP
+ Following Kuhn, 
+ .B time_make()
+ corrects a flaw of
+ .B mktime() 
+ and
+ .BR timegm() .
+ Those functions use the value
+ .B (time_t)-1
+ to represent an error return status.  However, this value can also be a
+ correct translation of a 
+ .B struct tm
+ representing the time "December 31, 1969, 23:59:59 GMT" (assuming POSIX 
+ .B time_t 
+ values),
+ and these cases are not distinguished.  Instead, 
+ .B time_make()
+ uses an out-of-band method to indicate error conditions, leaving
+ the entire
+ .B time_t
+ space free to represent valid values.
+ .\" @(#)time_make.3	1.0
diff -crN -x *~ ../tzcode2001c/tz_prep.3 thread-safe-2/tz_prep.3
*** ../tzcode2001c/tz_prep.3	Wed Dec 31 19:00:00 1969
--- thread-safe-2/tz_prep.3	Mon Jun 18 15:46:08 2001
***************
*** 0 ****
--- 1,120 ----
+ .TH TZ_PREP 3
+ .SH NAME
+ tz_prep, tz_free \- create and destroy time zone objects
+ .SH SYNOPSIS
+ .nf
+ .B #include <time.h>
+ .B #include <sys/types.h>
+ .PP
+ .B int tz_prep(tz, tzstring)
+ .B struct tzinfo** tz;
+ .B const char *tzstring;
+ .PP
+ .B void tz_free()
+ .B struct tzinfo* tzobj;
+ .PP
+ .B cc ... -ltz
+ .fi
+ .SH DESCRIPTION
+ The
+ .B tz_prep()
+ function creates a new time zone object, corresponding to the
+ given time zone name
+ .IR tzstring .
+ .I tzstring
+ is a pointer to a string, or 
+ .BR NULL .
+ .PP
+ If
+ .I tzstring
+ is
+ .BR NULL ,
+ the returned time zone will represent the system's best approximation of its
+ current wall clock time.  If
+ .I tzstring
+ is the zero-length string \fB""\fR, the returned time zone will represent the
+ system's best approximation of Coordinated Universal Time (UTC).
+ .PP
+ All other values of
+ .I tzstring
+ are interpreted in the same manner as the
+ .B TZ
+ environment variable, as specified in environ(7).
+ .PP
+ The
+ .B tz_free()
+ function frees a time zone object allocated by
+ .BR tz_prep() .
+ .SH RETURN VALUES
+ On successful completion,
+ .B tz_prep()
+ returns a value of
+ .BR 0
+ and fills in 
+ .IR *tz .
+ On failure, an error number is returned and no resources are allocated.
+ .PP
+ The
+ .B tz_free()
+ function returns no value.
+ .SH ERRORS
+ The
+ .B tz_prep()
+ function will fail if:
+ .IP \fBENOMEM\fR
+ Not enough memory is available to create the time zone object.
+ .IP \fBENOENT\fR
+ No known time zone corresponds to tzname.
+ .PP
+ The
+ .B tz_prep()
+ function may fail if there is a problem with the system time
+ zone database which caused retrieval of the time zone information to fail
+ unexpectedly.  In this case any appropriate error number may be returned.
+ .PP
+ The
+ .B tz_free()
+ function does not return any errors.
+ .SH FILES
+ .ta \w'/usr/local/etc/zoneinfo/posixrules\0\0'u
+ /usr/local/etc/zoneinfo	time zone information directory
+ .br
+ /usr/local/etc/zoneinfo/localtime	local time zone file
+ .br
+ /usr/local/etc/zoneinfo/posixrules	used with POSIX-style TZ's
+ .br
+ /usr/local/etc/zoneinfo/GMT	for UTC leap seconds
+ .sp
+ If
+ .B /usr/local/etc/zoneinfo/GMT
+ is absent,
+ UTC leap seconds are loaded from
+ .BR /usr/local/etc/zoneinfo/posixrules .
+ .SH SEE ALSO
+ environ(7),
+ getenv(3),
+ newlocaltime(3),
+ newstrftime(3),
+ newtzset(3),
+ pthread_settz(3),
+ time(2),
+ time_make(3),
+ tzfile(5)
+ .SH NOTES
+ This interface is designed so that calling
+ \fBtz_prep\fR(\fBgetenv\fR(\fB"TZ"\fR))
+ will return an object describing the default time zone object that
+ non-thread-aware versions of the time functions will use by default,
+ provided
+ .B TZ
+ (if set) is set to a valid time zone name.
+ .PP
+ .B tzset()
+ interprets a 
+ .B TZ
+ value of the empty string \fB""\fR as meaning "UTC without leap-seconds".
+ .BR tz_prep() ,
+ however, defines it to mean the standard UTC value, with whatever
+ leap-second support the system uses by default.  On POSIX-compliant systems,
+ which may not use leap second support by default, these will be equivalent.
+ .\" @(#)tz_prep.3	1.0
diff -crN -x *~ ../tzcode2001c/tzthread-dummy.c thread-safe-2/tzthread-dummy.c
*** ../tzcode2001c/tzthread-dummy.c	Wed Dec 31 19:00:00 1969
--- thread-safe-2/tzthread-dummy.c	Mon Jun 18 16:15:27 2001
***************
*** 0 ****
--- 1,42 ----
+ /*
+ ** This file is in the public domain.  Contributed by Jonathan Lennox
+ ** <[email-masked]>
+ */
+ 
+ #include "private.h"
+ #include "tzthread-dummy.h"
+ #include "struct_tzinfo.h"
+ 
+ /*
+ ** "Dummy" definitions of the TZ thread-safety operations.  For use in
+ ** a non-threaded environment.
+ */
+ 
+ #ifdef ALL_STATE
+ struct tzinfo *	_tz_lclptr;
+ struct tzinfo *	_tz_gmtptr;
+ 
+ struct tzinfo * _tz_alloc_lclptr P((void))
+ {
+ 	_tz_lclptr = (struct tzinfo *) malloc(sizeof *_tz_lclptr);
+ 	return _tz_lclptr;
+ }
+ 
+ 
+ struct tzinfo * _tz_alloc_gmtptr P((void))
+ {
+ 	_tz_gmtptr = (struct tzinfo *) malloc(sizeof *_tz_gmtptr);
+ 	return _tz_gmtptr;
+ }
+ 
+ #else /* !defined ALL_STATE */
+ 
+ struct tzinfo	_tz_lclmem;
+ struct tzinfo	_tz_gmtmem;
+ 
+ #endif /* !defined ALL_STATE */
+ 
+ struct tm _tz_tm;
+ 
+ char _tz_asctime_buf[ASCTIME_BUF_SIZE];
+ 
diff -crN -x *~ ../tzcode2001c/tzthread-dummy.h thread-safe-2/tzthread-dummy.h
*** ../tzcode2001c/tzthread-dummy.h	Wed Dec 31 19:00:00 1969
--- thread-safe-2/tzthread-dummy.h	Mon Jun 18 16:15:17 2001
***************
*** 0 ****
--- 1,127 ----
+ #ifndef TZTHREAD_DUMMY_H
+ #define TZTHREAD_DUMMY_H
+ 
+ /*
+ ** This file is in the public domain.  Contributed by Jonathan Lennox
+ ** <[email-masked]>
+ */
+ 
+ #include "private.h"
+ 
+ /*
+ ** Thread primitives for tzcode.  TZcode requires the following primitive
+ ** functions to be defined (probably as macros):
+ ** void LOCK_LCLPTR(void)	- serialize references to the process-wide or
+ **				  thread-specific local timezone object.
+ ** void LOCK_GMTPTR(void)	- serialize references to write to the global
+ **				  UTC timezone object.
+ ** void UNLOCK_LCLPTR(void)	- Unlock the local timezone object.
+ ** void UNLOCK_GMTPTR(void)	- Unlock the UTC timezone object.
+ ** 
+ ** struct tzinfo * GET_LCLPTR(int *process)
+ **				- Return the current local timezone object.
+ **				  The argument 'process' is filled in with
+ **				  a boolean value indicating whether the
+ **				  returned object is process-wide (true)
+ **				  or thread-specific (false).
+ **				  This will only be called when the local
+ **				  pointer is locked.
+ **				  The thread primitive is not responsible for
+ **				  filling in the value, only for assuring that
+ **				  sizeof(struct tzinfo) bytes are available.
+ **				  This function returns NULL on failure.
+ **
+ ** struct tzinfo * GET_GMTPTR(void)
+ **				- Return the UTC timezone object.
+ **				  This *can* be called when the GMT pointer
+ **				  is not locked.  This function is responsible
+ **				  for any resulting serialization necessary.
+ **				  Any attempts to write to the object *will*
+ **				  lock the object first, however.
+ **				  The thread primitive is not responsible for
+ **				  filling in the value, only for assuring that
+ **				  sizeof(struct tzinfo) bytes are available.
+ **				  This function returns NULL on failure.
+ **
+ ** struct tm *GET_TM_BUF(void)	- Get a pointer to a struct tm, suitable for 
+ **				  use as a return value for the localtime()
+ **				  family of functions.  In a threaded
+ **				  environment, this buffer should be thread-
+ **				  local.
+ **
+ ** int SET_THREAD_LCLPTR(const struct tzinfo *)
+ **				- Set the thread-specific local timezone
+ **				  object to (a copy of) the argument.  Calls to
+ **				  GET_LCLPTR() from the current thread should
+ **				  from now on return this value.
+ **				  If a previous local pointer was set, free
+ **				  it first.
+ **				  Return an errno error code on failure, or 0.
+ **
+ ** int CLEAR_THREAD_LCLPTR(void)
+ **				- Clear the thread-specific local timezone
+ **				  object to the argument.  Calls to
+ **				  GET_LCLPTR() from the current thread should
+ **				  from now on return the global pointer.
+ **				  If a previous local pointer was set, free
+ **				  it first.
+ **				  Return an errno error code on failure, or 0.
+ */
+ 
+ /*
+ ** "Dummy" definitions of the TZ thread-safety operations.  For use in
+ ** a non-threaded environment.
+ */
+ 
+ /*
+ ** There are actually two dummy TZ implementations -- one for ALL_STATE
+ ** (structures allocated as-needed) and one for static.
+ */
+ 
+ #ifdef ALL_STATE
+ extern struct tzinfo *	_tz_lclptr;
+ extern struct tzinfo *	_tz_gmtptr;
+ 
+ #define GET_LCLPTR(_process_wide) \
+ 	(*(_process_wide) = 1, \
+ 		(_tz_lclptr != NULL ? _tz_lclptr : _tz_alloc_lclptr()))
+ #define GET_GMTPTR() \
+ 	(_tz_gmtptr != NULL ? _tz_gmtptr : _tz_alloc_gmtptr())
+ 
+ extern struct tzinfo * _tz_alloc_lclptr P((void));
+ extern struct tzinfo * _tz_alloc_gmtptr P((void));
+ 
+ #else /* !defined ALL_STATE */
+ 
+ extern struct tzinfo	_tz_lclmem;
+ extern struct tzinfo	_tz_gmtmem;
+ #define GET_LCLPTR(_process_wide)	(*(_process_wide) = 1, &_tz_lclmem)
+ #define GET_GMTPTR()			(&_tz_gmtmem)
+ 
+ #endif /* !defined ALL_STATE */
+ 
+ /*
+ ** These dummy implementations apply regardless of ALL_STATE, since we're
+ ** non-threaded.
+ */
+ 
+ #define LOCK_LCLPTR()    do { } while(0)
+ #define LOCK_GMTPTR()    do { } while(0)
+ 
+ #define UNLOCK_LCLPTR()    do { } while(0)
+ #define UNLOCK_GMTPTR()    do { } while(0)
+ 
+ extern struct tm _tz_tm;
+ 
+ #define GET_TM_BUF() (&_tz_tm)
+ 
+ extern char _tz_asctime_buf[ASCTIME_BUF_SIZE];
+ 
+ #define GET_ASCTIME_BUF() (_tz_asctime_buf)
+ 
+ #define SET_THREAD_LCLPTR(x) (ENOSYS)
+ #define CLEAR_THREAD_LCLPTR() (ENOSYS)
+ 
+ #endif /* !defined TZTHREAD_DUMMY_H */
+ 
+ 
diff -crN -x *~ ../tzcode2001c/tzthread-posix.c thread-safe-2/tzthread-posix.c
*** ../tzcode2001c/tzthread-posix.c	Wed Dec 31 19:00:00 1969
--- thread-safe-2/tzthread-posix.c	Mon Jun 18 16:15:47 2001
***************
*** 0 ****
--- 1,282 ----
+ /*
+ ** This file is in the public domain.  Contributed by Jonathan Lennox
+ ** <[email-masked]>
+ */
+ 
+ #include "private.h"
+ #include "tzthread-posix.h"
+ #include "struct_tzinfo.h"
+ 
+ #include <pthread.h>
+ 
+ /*
+ ** POSIX Thread versions of the TZ thread-safety operations.
+ */
+ 
+ static pthread_mutex_t _tz_lcl_mutex = PTHREAD_MUTEX_INITIALIZER;
+ static pthread_mutex_t _tz_gmt_mutex = PTHREAD_MUTEX_INITIALIZER;
+ 
+ static int get_lclptr_key P((pthread_key_t *));
+ 
+ #ifdef ALL_STATE
+ static struct tzinfo *	_tz_lclptr;
+ static struct tzinfo *	_tz_gmtptr;
+ 
+ struct tzinfo*
+ _tz_get_lclptr(process_wide)
+ int *	process_wide;
+ {
+ 	pthread_key_t lclptr_key;
+ 	struct tzinfo *ret;
+ 
+ 	if (get_lclptr_key(&lclptr_key) != 0) {
+ 		return NULL;
+ 	}
+ 	if ((ret = pthread_getspecific(lclptr_key)) != NULL) {
+ 		*process_wide = 0;
+ 		return ret;
+ 	}
+ 
+ 	*process_wide = 1;
+ 
+ 	if (_tz_lclptr == NULL) {
+ 		_tz_lclptr = (struct tzinfo *) malloc(sizeof *_tz_lclptr);
+ 	}
+ 	return _tz_lclptr;
+ }
+ 
+ 
+ struct tzinfo*
+ _tz_get_gmtptr P((void))
+ {
+ 	/*
+ 	** Fast-path the common case. Nothing sets or alters gmtptr
+ 	** once it's set.
+ 	*/
+ 	if (_tz_gmtptr != NULL) {
+ 		return _tz_gmtptr;
+ 	}
+ 
+ 	_tz_lock_gmtptr();
+ 	/* 
+ 	** Check the pointer again, in case another thread just 
+ 	** allocated it.
+ 	*/
+ 	if (_tz_gmtptr != NULL) {
+ 		_tz_unlock_gmtptr();
+ 		return _tz_gmtptr;
+ 	}
+ 
+ 	_tz_gmtptr = (struct tzinfo *) malloc(sizeof *_tz_gmtptr);
+ 	_tz_unlock_gmtptr();
+ 
+ 	return _tz_gmtptr;
+ }
+ 
+ #else /* !defined ALL_STATE */
+ 
+ static struct tzinfo _tz_lclmem;
+ static struct tzinfo _tz_gmtmem;
+ 
+ struct tzinfo *
+ _tz_get_lclptr(process_wide)
+ int *	process_wide;
+ {
+ 	pthread_key_t lclptr_key;
+ 	struct tzinfo *ret;
+ 
+ 	if (get_lclptr_key(&lclptr_key) != 0) {
+ 		return NULL;
+ 	}
+ 	if ((ret = pthread_getspecific(lclptr_key)) != NULL) {
+ 		*process_wide = 0;
+ 		return ret;
+ 	}
+ 
+ 	*process_wide = 1;
+ 
+ 	return &_tz_lclmem;
+ }
+ 
+ 
+ struct tzinfo *
+ _tz_get_gmtptr P((void))
+ {
+ 	return &_tz_gmtmem;
+ }
+ 
+ #endif /* defined ALL_STATE */
+ 
+ void _tz_lock_lclptr P((void))
+ {
+ 	pthread_mutex_lock(&_tz_lcl_mutex);
+ }
+ 
+ 
+ void _tz_lock_gmtptr P((void))
+ {
+ 	pthread_mutex_lock(&_tz_gmt_mutex);
+ }
+ 
+ 
+ extern void _tz_unlock_lclptr P((void))
+ {
+ 	pthread_mutex_unlock(&_tz_lcl_mutex);
+ }
+ 
+ 
+ extern void _tz_unlock_gmtptr P((void))
+ {
+ 	pthread_mutex_unlock(&_tz_gmt_mutex);
+ }
+ 
+ 
+ 
+ struct tm*
+ _tz_get_tm_buf P((void))
+ {
+ 	static pthread_mutex_t tmbuf_mutex = PTHREAD_MUTEX_INITIALIZER;
+ 	static pthread_key_t tmbuf_key;
+ 	static int tmbuf_initialized = 0;
+ 	struct tm *p_tm;
+ 	int err;
+ 
+ 	if (!tmbuf_initialized) {
+ 		pthread_mutex_lock(&tmbuf_mutex);
+ 		if (!tmbuf_initialized) {
+ 			if ((err = pthread_key_create(&tmbuf_key, free))
+ 					!= 0) {
+ 				errno = err;
+ 				pthread_mutex_unlock(&tmbuf_mutex);
+ 				return NULL;
+ 			}
+ 			tmbuf_initialized = 1;
+ 		}
+ 		pthread_mutex_unlock(&tmbuf_mutex);
+ 	}
+ 
+ 	p_tm = pthread_getspecific(tmbuf_key);
+ 	if (p_tm == NULL) {
+ 		if ((p_tm = (struct tm *) malloc(sizeof(struct tm))) == NULL) {
+ 			return NULL;
+ 		}
+ 		if ((err = pthread_setspecific(tmbuf_key, p_tm)) != 0) {
+ 			errno = err;
+ 			free(p_tm);
+ 			return NULL;
+ 		}
+ 	}
+ 	return p_tm;
+ }
+ 
+ 
+ char* _tz_get_asctime_buf P((void))
+ {
+ 	static pthread_mutex_t asctime_buf_mutex = PTHREAD_MUTEX_INITIALIZER;
+ 	static pthread_key_t asctime_buf_key;
+ 	static int asctime_buf_initialized = 0;
+ 	char *asctime_buf;
+ 	int err;
+ 
+ 	if (!asctime_buf_initialized) {
+ 		pthread_mutex_lock(&asctime_buf_mutex);
+ 		if (!asctime_buf_initialized) {
+ 			if ((err = pthread_key_create(&asctime_buf_key, free))
+ 					!= 0) {
+ 				errno = err;
+ 				pthread_mutex_unlock(&asctime_buf_mutex);
+ 				return NULL;
+ 			}
+ 			asctime_buf_initialized = 1;
+ 		}
+ 		pthread_mutex_unlock(&asctime_buf_mutex);
+ 	}
+ 
+ 	asctime_buf = pthread_getspecific(asctime_buf_key);
+ 	if (asctime_buf == NULL) {
+ 		if ((asctime_buf = (char *) malloc(ASCTIME_BUF_SIZE))
+ 				== NULL) {
+ 			return NULL;
+ 		}
+ 		if ((err = pthread_setspecific(asctime_buf_key, asctime_buf))
+ 				!= 0) {
+ 			errno = err;
+ 			free(asctime_buf);
+ 			return NULL;
+ 		}
+ 	}
+ 	return asctime_buf;
+ }
+ 
+ 
+ int _tz_set_thread_lclptr(tz)
+ const struct tzinfo * tz;
+ {
+ 	pthread_key_t lclptr_key;
+ 	int err;
+ 	struct tzinfo *old, *new;
+ 
+ 	if ((err = get_lclptr_key(&lclptr_key)) != 0) {
+ 		return err;
+ 	}
+ 	if ((old = pthread_getspecific(lclptr_key)) != NULL) {
+ 	  free(old);
+ 	}
+ 
+ 	new = (struct tzinfo *) malloc(sizeof(struct tzinfo));
+ 	if (new == NULL) {
+ 	  return ENOMEM;
+ 	}
+ 
+ 	*new = *tz;
+ 
+ 	if ((err = pthread_setspecific(lclptr_key, new)) != 0) {
+ 	  free(new);
+ 	  return err;
+ 	}
+ 	return 0;
+ }
+ 
+ 
+ int _tz_clear_thread_lclptr P((void))
+ {
+ 	pthread_key_t lclptr_key;
+ 	int err;
+ 	struct tzinfo *old;
+ 
+ 	if ((err = get_lclptr_key(&lclptr_key)) != 0) {
+ 		return err;
+ 	}
+ 	if ((old = pthread_getspecific(lclptr_key)) != NULL) {
+ 	  free(old);
+ 	}
+ 
+ 	return pthread_setspecific(lclptr_key, NULL);
+ }
+ 
+ 
+ static int get_lclptr_key(lclptr_key_p)
+ pthread_key_t *		lclptr_key_p;
+ {
+ 	static pthread_mutex_t lclptr_key_mutex = PTHREAD_MUTEX_INITIALIZER;
+ 	static pthread_key_t lclptr_key;
+ 	static int lclptr_key_initialized = 0;
+ 
+ 	int err;
+ 
+ 	if (!lclptr_key_initialized) {
+ 		pthread_mutex_lock(&lclptr_key_mutex);
+ 		if (!lclptr_key_initialized) {
+ 			if ((err = pthread_key_create(&lclptr_key, free))
+ 					!= 0) {
+ 				pthread_mutex_unlock(&lclptr_key_mutex);
+ 				return err;
+ 			}
+ 			lclptr_key_initialized = 1;
+ 		}
+ 		pthread_mutex_unlock(&lclptr_key_mutex);
+ 	}
+ 
+ 	*lclptr_key_p = lclptr_key;
+ 	return 0;
+ }
diff -crN -x *~ ../tzcode2001c/tzthread-posix.h thread-safe-2/tzthread-posix.h
*** ../tzcode2001c/tzthread-posix.h	Wed Dec 31 19:00:00 1969
--- thread-safe-2/tzthread-posix.h	Mon Jun 18 16:20:38 2001
***************
*** 0 ****
--- 1,109 ----
+ #ifndef TZTHREAD_POSIX_H
+ #define TZTHREAD_POSIX_H
+ 
+ /*
+ ** This file is in the public domain.  Contributed by Jonathan Lennox
+ ** <[email-masked]>
+ */
+ 
+ /*
+ ** Thread primitives for tzcode.  TZcode requires the following primitive
+ ** functions to be defined (probably as macros):
+ ** void LOCK_LCLPTR(void)	- serialize references to the process-wide or
+ **				  thread-specific local timezone object.
+ ** void LOCK_GMTPTR(void)	- serialize references to write to the global
+ **				  UTC timezone object.
+ ** void UNLOCK_LCLPTR(void)	- Unlock the local timezone object.
+ ** void UNLOCK_GMTPTR(void)	- Unlock the UTC timezone object.
+ ** 
+ ** struct tzinfo * GET_LCLPTR(int *process)
+ **				- Return the current local timezone object.
+ **				  The argument 'process' is filled in with
+ **				  a boolean value indicating whether the
+ **				  returned object is process-wide (true)
+ **				  or thread-specific (false).
+ **				  This will only be called when the local
+ **				  pointer is locked.
+ **				  The thread primitive is not responsible for
+ **				  filling in the value, only for assuring that
+ **				  sizeof(struct tzinfo) bytes are available.
+ **				  This function returns NULL on failure.
+ **
+ ** struct tzinfo * GET_GMTPTR(void)
+ **				- Return the UTC timezone object.
+ **				  This *can* be called when the GMT pointer
+ **				  is not locked.  This function is responsible
+ **				  for any resulting serialization necessary.
+ **				  Any attempts to write to the object *will*
+ **				  lock the object first, however.
+ **				  The thread primitive is not responsible for
+ **				  filling in the value, only for assuring that
+ **				  sizeof(struct tzinfo) bytes are available.
+ **				  This function returns NULL on failure.
+ **
+ ** struct tm *GET_TM_BUF(void)	- Get a pointer to a struct tm, suitable for 
+ **				  use as a return value for the localtime()
+ **				  family of functions.  In a threaded
+ **				  environment, this buffer should be thread-
+ **				  local.
+ **
+ ** int SET_THREAD_LCLPTR(struct tzinfo *)
+ **				- Set the thread-specific local timezone
+ **				  object to the argument.  Calls to
+ **				  GET_LCLPTR() from the current thread should
+ **				  from now on return this value.
+ **				  If a previous local pointer was set, free
+ **				  it first.
+ **				  Return an errno error code on failure, or 0.
+ **
+ ** int CLEAR_THREAD_LCLPTR(void)
+ **				- Clear the thread-specific local timezone
+ **				  object to the argument.  Calls to
+ **				  GET_LCLPTR() from the current thread should
+ **				  from now on return the global pointer.
+ **				  If a previous local pointer was set, free
+ **				  it first.
+ **				  Return an errno error code on failure, or 0.
+ */
+ 
+ #include "private.h"
+ 
+ /*
+ ** POSIX Thread versions of the TZ thread-safety operations.
+ */
+ 
+ extern struct tzinfo *	_tz_get_lclptr P((int *));
+ extern struct tzinfo *	_tz_get_gmtptr P((void));
+ 
+ #define GET_LCLPTR _tz_get_lclptr
+ #define GET_GMTPTR _tz_get_gmtptr
+ 
+ extern void _tz_lock_lclptr P((void));
+ extern void _tz_lock_gmtptr P((void));
+ 
+ extern void _tz_unlock_lclptr P((void));
+ extern void _tz_unlock_gmtptr P((void));
+ 
+ #define LOCK_LCLPTR    _tz_lock_lclptr
+ #define LOCK_GMTPTR    _tz_lock_gmtptr
+ 
+ #define UNLOCK_LCLPTR    _tz_unlock_lclptr
+ #define UNLOCK_GMTPTR    _tz_unlock_gmtptr
+ 
+ extern struct tm* _tz_get_tm_buf P((void));
+ 
+ #define GET_TM_BUF _tz_get_tm_buf
+ 
+ extern char* _tz_get_asctime_buf P((void));
+ 
+ #define GET_ASCTIME_BUF _tz_get_asctime_buf
+ 
+ extern int _tz_set_thread_lclptr P((const struct tzinfo *));
+ extern int _tz_clear_thread_lclptr P((void));
+ 
+ #define SET_THREAD_LCLPTR _tz_set_thread_lclptr
+ #define CLEAR_THREAD_LCLPTR _tz_clear_thread_lclptr
+ 
+ #endif /* !defined TZTHREAD_POSIX_H */
+ 
+ 
diff -crN -x *~ ../tzcode2001c/tztimeext.h thread-safe-2/tztimeext.h
*** ../tzcode2001c/tztimeext.h	Wed Dec 31 19:00:00 1969
--- thread-safe-2/tztimeext.h	Mon Jun 18 15:46:07 2001
***************
*** 0 ****
--- 1,75 ----
+ #ifndef TZTIMEEXT_H
+ #define TZTIMEEXT_H
+ 
+ /*
+ ** This file is in the public domain.  Contributed by Jonathan Lennox
+ ** <[email-masked]>
+ */
+ 
+ /*
+ ** This is an extended version of <time.h> which provides function definitions
+ ** for the public functions defined by tzcode.
+ */
+ 
+ #include <time.h>
+ 
+ #if __STDC_VERSION__ < 199901
+ #undef  restrict
+ #define restrict
+ #endif
+ 
+ struct tzinfo;
+ 
+ #if defined(__STDC__)
+ 
+ extern void		tzsetwall(void);
+ extern struct tm *	offtime(const time_t *, const long);
+ extern time_t		timelocal(struct tm * const);
+ extern time_t		timegm(struct tm * const);
+ extern time_t		timeoff(struct tm * const, const long);
+ 
+ extern time_t		time2posix(time_t);
+ extern time_t		posix2time(time_t);
+ 
+ extern int		tz_prep(struct tzinfo**, const char *);
+ extern void		tz_free(struct tzinfo*);
+ 
+ extern int		time_make(time_t *, struct tm *,
+ 					const struct tzinfo *);
+ extern int		time_breakup(struct tm *, const time_t *,
+ 					const struct tzinfo *);
+ extern size_t		strftime_z(char * restrict, size_t,
+ 					const char * restrict,
+ 					const struct tm * restrict,
+ 					const struct tzinfo * restrict);
+ 
+ extern int		pthread_settz(const struct tzinfo *);
+ 
+ #else /* !defined(__STDC__) */
+ 
+ extern void		tzsetwall();
+ extern struct tm *	offtime();
+ extern time_t		timelocal();
+ extern time_t		timegm();
+ extern time_t		timeoff();
+ 
+ extern time_t		time2posix();
+ extern time_t		posix2time();
+ 
+ extern int		tz_prep();
+ extern void		tz_free();
+ 
+ extern int		time_make();
+ extern int		time_breakup();
+ extern size_t		strftime_z();
+ 
+ extern int		pthread_settz();
+ 
+ #endif /* defined(__STDC__) */
+ 
+ 
+ #if __STDC_VERSION__ < 199901
+ #undef restrict
+ #endif
+ 
+ #endif /* TZTIMEEXT_H */

-- 
Jonathan Lennox
[email-masked]