[tz] [PROPOSED] Clean up configuration of tzname etc.

Paul Eggert eggert at cs.ucla.edu
Fri Jun 9 00:50:08 UTC 2017


This documents a bit of software archaeology better.
tzname is standardized by POSIX, whereas timezone and daylight are
standardized by POSIX SI, so allow them to be configured separately.
* Makefile, NEWS, Theory: Document this.
* date.c, strftime.c, zdump.c (tzname): Remove decl;
private.h declares tzname as needed now.
* localtime.c (tzname, timezone, daylight, altzone): Also
define if TZ_TIME_T.  However, define only if the relevant
other macro (HAVE_TZNAME, USG_COMPAT, ALTZONE) is set or defined.
(update_tzname_etc, settzname): Set tzname only if HAVE_TZNAME.
At the end, simplify ifdef by using TZ_TIME_T.
* newctime.3: Document optional vars and fields more carefully.
* private.h (USG_COMPAT): Set default from _XOPEN_VERSION.
All uses changed to assume it is 1 or 0.
(HAVE_TZNAME): Set default from that and from _POSIX_VERSION.
(TZ_TIME_T): New macro.
(daylight, timezone, tzname, altzone): Rename if needed to avoid
collision with standard library.
(tzname): Declare if (!HAVE_POSIX_DECLS || TZ_TIME_T) && HAVE_TZNAME.
(timezone, daylight): Declare if (!HAVE_POSIX_DECLS || TZ_TIME_T)
&& USG_COMPAT, not if not defined and !HAVE_POSIX_DECLS.
(altzone): Declare even if not defined.
(tzsetwall, offtime, timegm, timelocal, timeoff, time2posix)
(posix2time, posix2time_z, time2posix_z):
Use TZ_TIME_T instead of (defined time_tz).
* strftime.c (_fmt):
* zdump.c (abbr): Use tzname only if HAVE_TZNAME.
---
 Makefile    | 37 +++++++++++++++++++++++----------
 NEWS        |  8 +++++++
 Theory      |  6 +++---
 date.c      |  1 -
 localtime.c | 34 ++++++++++++++++--------------
 newctime.3  | 46 ++++++++++++++++++++++++++---------------
 private.h   | 69 ++++++++++++++++++++++++++++++++++++++++++++++---------------
 strftime.c  | 38 ++++++++++++++++------------------
 zdump.c     |  9 ++++----
 9 files changed, 160 insertions(+), 88 deletions(-)

diff --git a/Makefile b/Makefile
index 476f259..e6d497d 100644
--- a/Makefile
+++ b/Makefile
@@ -164,6 +164,7 @@ LDLIBS=
 #	not needed by the main-program tz code, which is single-threaded.
 #	Append other compiler flags as needed, e.g., -pthread on GNU/Linux.
 #  -Dtime_tz=\"T\" to use T as the time_t type, rather than the system time_t
+#	This is intended for internal use only; it mangles external names.
 #  -DTZ_DOMAIN=\"foo\" to use "foo" for gettext domain name; default is "tz"
 #  -DTZ_DOMAINDIR=\"/path\" to use "/path" for gettext directory;
 #	the default is system-supplied, typically "/usr/lib/locale"
@@ -199,12 +200,6 @@ GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 -fno-common \
   -Wno-address -Wno-format-nonliteral -Wno-sign-compare \
   -Wno-type-limits -Wno-unused-parameter
 #
-# If you want to use System V compatibility code, add
-#	-DUSG_COMPAT
-# to the end of the "CFLAGS=" line.  This arrange for "timezone" and "daylight"
-# variables to be kept up-to-date by the time conversion functions.  Neither
-# "timezone" nor "daylight" is described in X3J11's work.
-#
 # If your system has a "GMT offset" field in its "struct tm"s
 # (or if you decide to add such a field in your system's "time.h" file),
 # add the name to a define such as
@@ -216,6 +211,31 @@ GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 -fno-common \
 # and define NO_TM_ZONE to suppress any guessing.  These two fields are not
 # required by POSIX, but are widely available on GNU/Linux and BSD systems.
 #
+# The next batch of options control support for external variables
+# exported by tzcode.  In practice these variables are less useful
+# than TM_GMTOFF and TM_ZONE.  However, most of them are standardized.
+# #
+# # To omit or support the external variable "tzname", add one of:
+# #	-DHAVE_TZNAME=0
+# #	-DHAVE_TZNAME=1
+# # to the "CFLAGS=" line.  "tzname" is required by POSIX 1988 and later.
+# # If not defined, the code attempts to guess HAVE_TZNAME from other macros.
+# # Warning: unless time_tz is also defined, HAVE_TZNAME=1 can cause
+# # crashes when combined with some platforms' standard libraries,
+# # presumably due to memory allocation issues.
+# #
+# # To omit or support the external variables "timezone" and "daylight", add
+# #	-DUSG_COMPAT=0
+# #	-DUSG_COMPAT=1
+# # to the "CFLAGS=" line; "timezone" and "daylight" are inspired by
+# # Unix Systems Group code and are required by POSIX 2008 (with XSI) and later.
+# # If not defined, the code attempts to guess USG_COMPAT from other macros.
+# #
+# # To support the external variable "altzone", add
+# #	-DALTZONE
+# # to the end of the "CFLAGS=" line; although "altzone" appeared in
+# # System V Release 3.1 it has not been standardized.
+#
 # If you want functions that were inspired by early versions of X3J11's work,
 # add
 #	-DSTD_INSPIRED
@@ -253,11 +273,6 @@ GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 -fno-common \
 #	-DALL_STATE
 # to the end of the "CFLAGS=" line.  Storage is obtained by calling malloc.
 #
-# If you want an "altzone" variable (a la System V Release 3.1), add
-#	-DALTZONE
-# to the end of the "CFLAGS=" line.
-# This variable is not described in X3J11's work.
-#
 # NIST-PCTS:151-2, Version 1.4, (1993-12-03) is a test suite put
 # out by the National Institute of Standards and Technology
 # which claims to test C and Posix conformance.  If you want to pass PCTS, add
diff --git a/NEWS b/NEWS
index 99f5d64..44135ae 100644
--- a/NEWS
+++ b/NEWS
@@ -57,6 +57,14 @@ Unreleased, experimental changes
     ordinary lines in leap second input.  Formerly, zic sometimes
     warned about this undocumented usage and handled it incorrectly.
 
+    The new macro HAVE_TZNAME governs whether the tzname external
+    variable is exported, instead of USG_COMPAT.  USG_COMPAT now
+    governs only the external variables "timezone" and "daylight".
+    This change is needed because the three variables are not in the
+    same category: although POSIX requires tzname, it specifies the
+    other two variables as optional.  Also, USG_COMPAT is now 1 or 0:
+    if not defined, the code attempts to guess it from other macros.
+
     Several minor changes have been made to the code to make it a
     bit easier to port to MS-Windows.  (Thanks to Kees Dekker for
     reporting the problems.)
diff --git a/Theory b/Theory
index 2bfadc6..8aeccb2 100644
--- a/Theory
+++ b/Theory
@@ -581,9 +581,9 @@ Points of interest to folks with other systems:
 	of GMT" value and a "daylight saving time in effect" flag) to a
 	time zone abbreviation, and we refuse to guess.
 	Programs that in the past used the timezone function may now examine
-	tzname[localtime(&clock)->tm_isdst] to learn the correct time
-	zone abbreviation to use.  Alternatively, use
-	localtime(&clock)->tm_zone if this has been enabled.
+	localtime(&clock)->tm_zone (if TM_ZONE is defined) or
+	tzname[localtime(&clock)->tm_isdst] (if HAVE_TZNAME is defined)
+	to learn the correct time zone abbreviation to use.
 
 *	The 4.2BSD gettimeofday function is not used in this package.
 	This formerly let users obtain the current UTC offset and DST flag,
diff --git a/date.c b/date.c
index 269825d..0d64b8a 100644
--- a/date.c
+++ b/date.c
@@ -45,7 +45,6 @@
 #if !HAVE_POSIX_DECLS
 extern char *		optarg;
 extern int		optind;
-extern char *		tzname[];
 #endif
 
 static int		retval = EXIT_SUCCESS;
diff --git a/localtime.c b/localtime.c
index 8f103aa..b23fe43 100644
--- a/localtime.c
+++ b/localtime.c
@@ -188,20 +188,21 @@ static int		lcl_is_set;
 
 static struct tm	tm;
 
-#if !HAVE_POSIX_DECLS
+#if !HAVE_POSIX_DECLS || TZ_TIME_T
+# if HAVE_TZNAME
 char *			tzname[2] = {
 	(char *) wildabbr,
 	(char *) wildabbr
 };
-# ifdef USG_COMPAT
+# endif
+# if USG_COMPAT
 long			timezone;
 int			daylight;
 # endif
-#endif
-
-#ifdef ALTZONE
+# ifdef ALTZONE
 long			altzone;
-#endif /* defined ALTZONE */
+# endif
+#endif
 
 /* Initialize *S to a value based on GMTOFF, ISDST, and ABBRIND.  */
 static void
@@ -263,8 +264,10 @@ detzcode64(const char *const codep)
 static void
 update_tzname_etc(struct state const *sp, struct ttinfo const *ttisp)
 {
+#if HAVE_TZNAME
   tzname[ttisp->tt_isdst] = (char *) &sp->chars[ttisp->tt_abbrind];
-#ifdef USG_COMPAT
+#endif
+#if USG_COMPAT
   if (!ttisp->tt_isdst)
     timezone = - ttisp->tt_gmtoff;
 #endif
@@ -280,16 +283,17 @@ settzname(void)
 	register struct state * const	sp = lclptr;
 	register int			i;
 
-	tzname[0] = tzname[1] = (char *) wildabbr;
-#ifdef USG_COMPAT
+#if HAVE_TZNAME
+	tzname[0] = tzname[1] = (char *) (sp ? wildabbr : gmt);
+#endif
+#if USG_COMPAT
 	daylight = 0;
 	timezone = 0;
-#endif /* defined USG_COMPAT */
+#endif
 #ifdef ALTZONE
 	altzone = 0;
 #endif /* defined ALTZONE */
 	if (sp == NULL) {
-		tzname[0] = tzname[1] = (char *) gmt;
 		return;
 	}
 	/*
@@ -304,10 +308,10 @@ settzname(void)
 							&sp->ttis[
 								sp->types[i]];
 		update_tzname_etc(sp, ttisp);
-#ifdef USG_COMPAT
+#if USG_COMPAT
 		if (ttisp->tt_isdst)
 			daylight = 1;
-#endif /* defined USG_COMPAT */
+#endif
 	}
 }
 
@@ -2286,9 +2290,9 @@ posix2time(time_t t)
 
 #endif /* defined STD_INSPIRED */
 
-#if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0
+#if TZ_TIME_T
 
-# ifndef USG_COMPAT
+# if !USG_COMPAT
 #  define daylight 0
 #  define timezone 0
 # endif
diff --git a/newctime.3 b/newctime.3
index 169c919..8b89935 100644
--- a/newctime.3
+++ b/newctime.3
@@ -7,7 +7,7 @@ asctime, ctime, difftime, gmtime, localtime, mktime \- convert date and time
 .el ds - \-
 .B #include <time.h>
 .PP
-.B extern char *tzname[2];
+.BR "extern char *tzname[];" " /\(** (optional) \(**/"
 .PP
 .B char *ctime(time_t const *clock);
 .PP
@@ -213,7 +213,7 @@ includes the following fields:
 .RS
 .PP
 .nf
-.ta .5i +\w'long tm_gmtoff;\0\0'u
+.ta 2n +\w'long tm_gmtoff;nn'u
 	int tm_sec;	/\(** seconds (0\*(en60) \(**/
 	int tm_min;	/\(** minutes (0\*(en59) \(**/
 	int tm_hour;	/\(** hours (0\*(en23) \(**/
@@ -223,21 +223,11 @@ includes the following fields:
 	int tm_wday;	/\(** day of week (Sunday = 0) \(**/
 	int tm_yday;	/\(** day of year (0\*(en365) \(**/
 	int tm_isdst;	/\(** is summer time in effect? \(**/
-	char \(**tm_zone;	/\(** abbreviation of time zone name \(**/
-	long tm_gmtoff;	/\(** offset from UT in seconds \(**/
+	char \(**tm_zone;	/\(** time zone abbreviation (optional) \(**/
+	long tm_gmtoff;	/\(** offset from UT in seconds (optional) \(**/
 .fi
 .RE
 .PP
-The
-.I tm_zone
-and
-.I tm_gmtoff
-fields exist, and are filled in, only if arrangements to do
-so were made when the library containing these functions was
-created.
-There is no guarantee that these fields will continue to exist
-in this form in future releases of this code.
-.PP
 .I Tm_isdst
 is non-zero if summer time is in effect.
 .PP
@@ -246,6 +236,21 @@ is the offset (in seconds) of the time represented
 from UT, with positive values indicating east
 of the Prime Meridian.
 The field's name is derived from Greenwich Mean Time, a precursor of UT.
+.PP
+In
+.B struct tm
+the
+.I tm_zone
+and
+.I tm_gmtoff
+fields exist, and are filled in, only if arrangements to do
+so were made when the library containing these functions was
+created.
+Similarly, the
+.B tzname
+variable is optional.
+There is no guarantee that these fields and this variable will
+continue to exist in this form in future releases of this code.
 .SH FILES
 .ta \w'/usr/local/etc/zoneinfo/posixrules\0\0'u
 /usr/local/etc/zoneinfo	time zone information directory
@@ -277,12 +282,19 @@ and
 point to static data
 overwritten by each call.
 The
+.B tzname
+variable (once set) and the
 .B tm_zone
 field of a returned
 .B "struct tm"
-points to a static array of characters, which
-can be overwritten by later calls to
-.IR tzset .
+both point to an array of characters that
+can be freed or overwritten by later calls to the functions
+.IR localtime ,
+.IR tzfree ,
+and
+.IR tzset ,
+if these functions affect the time zone information that specifies the
+abbreviation in question.
 The remaining functions and data are thread-safe.
 .PP
 .IR Asctime ,
diff --git a/private.h b/private.h
index 1e61592..ad9fcb9 100644
--- a/private.h
+++ b/private.h
@@ -153,6 +153,22 @@
 # endif
 #endif
 
+#ifndef USG_COMPAT
+# ifndef _XOPEN_VERSION
+#  define USG_COMPAT 0
+# else
+#  define USG_COMPAT 1
+# endif
+#endif
+
+#ifndef HAVE_TZNAME
+# if _POSIX_VERSION < 198808 && !USG_COMPAT
+#  define HAVE_TZNAME 0
+# else
+#  define HAVE_TZNAME 1
+# endif
+#endif
+
 #ifndef F_OK
 #define F_OK	0
 #endif /* !defined F_OK */
@@ -339,6 +355,12 @@ typedef unsigned long uintmax_t;
 ** typical platforms.
 */
 #if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0
+# define TZ_TIME_T 1
+#else
+# define TZ_TIME_T 0
+#endif
+
+#if TZ_TIME_T
 # ifdef LOCALTIME_IMPLEMENTATION
 static time_t sys_time(time_t *x) { return time(x); }
 # endif
@@ -393,6 +415,20 @@ typedef time_tz tz_time_t;
 # define tzset tz_tzset
 # undef  tzsetwall
 # define tzsetwall tz_tzsetwall
+# if HAVE_TZNAME
+#  undef  tzname
+#  define tzname tz_tzname
+# endif
+# if USG_COMPAT
+#  undef  daylight
+#  define daylight tz_daylight
+#  undef  timezone
+#  define timezone tz_timezone
+# endif
+# ifdef ALTZONE
+#  undef  altzone
+#  define altzone tz_altzone
+# endif
 
 char *ctime(time_t const *);
 char *ctime_r(time_t const *, char *);
@@ -422,18 +458,17 @@ extern char *asctime_r(struct tm const *restrict, char *restrict);
 extern char **environ;
 #endif
 
-#if !HAVE_POSIX_DECLS
-# ifdef USG_COMPAT
-#  ifndef timezone
+#if TZ_TIME_T || !HAVE_POSIX_DECLS
+# if HAVE_TZNAME
+extern char *tzname[];
+# endif
+# if USG_COMPAT
 extern long timezone;
-#  endif
-#  ifndef daylight
 extern int daylight;
-#  endif
 # endif
 #endif
 
-#if defined ALTZONE && !defined altzone
+#ifdef ALTZONE
 extern long altzone;
 #endif
 
@@ -443,25 +478,25 @@ extern long altzone;
 */
 
 #ifdef STD_INSPIRED
-# if !defined tzsetwall || defined time_tz
+# if TZ_TIME_T || !defined tzsetwall
 void tzsetwall(void);
 # endif
-# if !defined offtime || defined time_tz
+# if TZ_TIME_T || !defined offtime
 struct tm *offtime(time_t const *, long);
 # endif
-# if !defined timegm || defined time_tz
+# if TZ_TIME_T || !defined timegm
 time_t timegm(struct tm *);
 # endif
-# if !defined timelocal || defined time_tz
+# if TZ_TIME_T || !defined timelocal
 time_t timelocal(struct tm *);
 # endif
-# if !defined timeoff || defined time_tz
+# if TZ_TIME_T || !defined timeoff
 time_t timeoff(struct tm *, long);
 # endif
-# if !defined time2posix || defined time_tz
+# if TZ_TIME_T || !defined time2posix
 time_t time2posix(time_t);
 # endif
-# if !defined posix2time || defined time_tz
+# if TZ_TIME_T || !defined posix2time
 time_t posix2time(time_t);
 # endif
 #endif
@@ -495,10 +530,10 @@ time_t mktime_z(timezone_t restrict, struct tm *restrict);
 timezone_t tzalloc(char const *);
 void tzfree(timezone_t);
 # ifdef STD_INSPIRED
-#  if !defined posix2time_z || defined time_tz
+#  if TZ_TIME_T || !defined posix2time_z
 time_t posix2time_z(timezone_t, time_t) ATTRIBUTE_PURE;
 #  endif
-#  if !defined time2posix_z || defined time_tz
+#  if TZ_TIME_T || !defined time2posix_z
 time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_PURE;
 #  endif
 # endif
@@ -649,7 +684,7 @@ char *ctime_r(time_t const *, char *);
 ** or
 **	isleap(a + b) == isleap(a % 400 + b % 400)
 ** This is true even if % means modulo rather than Fortran remainder
-** (which is allowed by C89 but not C99).
+** (which is allowed by C89 but not by C99 or later).
 ** We use this to avoid addition overflow problems.
 */
 
diff --git a/strftime.c b/strftime.c
index 4b9fd2c..d09a0db 100644
--- a/strftime.c
+++ b/strftime.c
@@ -73,7 +73,7 @@ static const struct lc_time_T	C_time_locale = {
 
 	/*
 	** x_fmt
-	** C99 requires this format.
+	** C99 and later require this format.
 	** Using just numbers (as here) makes Quakers happier;
 	** it's also compatible with SVR4.
 	*/
@@ -81,7 +81,7 @@ static const struct lc_time_T	C_time_locale = {
 
 	/*
 	** c_fmt
-	** C99 requires this format.
+	** C99 and later require this format.
 	** Previously this code used "%D %X", but we now conform to C99.
 	** Note that
 	**	"%a %b %d %H:%M:%S %Y"
@@ -105,10 +105,6 @@ static char *	_fmt(const char *, const struct tm *, char *, const char *,
 			int *);
 static char *	_yconv(int, int, bool, bool, char *, char const *);
 
-#if !HAVE_POSIX_DECLS
-extern char *	tzname[];
-#endif
-
 #ifndef YEAR_2000_NAME
 #define YEAR_2000_NAME	"CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
 #endif /* !defined YEAR_2000_NAME */
@@ -223,7 +219,7 @@ label:
 			case 'E':
 			case 'O':
 				/*
-				** C99 locale modifiers.
+				** Locale modifiers of C99 and later.
 				** The sequences
 				**	%Ec %EC %Ex %EX %Ey %EY
 				**	%Od %oe %OH %OI %Om %OM
@@ -483,19 +479,19 @@ label:
 			case 'Z':
 #ifdef TM_ZONE
 				pt = _add(t->TM_ZONE, pt, ptlim);
-#else
+#elif HAVE_TZNAME
 				if (t->tm_isdst >= 0)
 					pt = _add(tzname[t->tm_isdst != 0],
 						pt, ptlim);
 #endif
 				/*
-				** C99 says that %Z must be replaced by the
-				** empty string if the time zone is not
-				** determinable.
+				** C99 and later say that %Z must be
+				** replaced by the empty string if the
+				** time zone is not determinable.
 				*/
 				continue;
 			case 'z':
-#if defined TM_GMTOFF || defined USG_COMPAT || defined ALTZONE
+#if defined TM_GMTOFF || USG_COMPAT || defined ALTZONE
 				{
 				long		diff;
 				char const *	sign;
@@ -505,7 +501,7 @@ label:
 				diff = t->TM_GMTOFF;
 # else
 				/*
-				** C99 says that the UT offset must
+				** C99 and later say that the UT offset must
 				** be computed by looking only at
 				** tm_isdst. This requirement is
 				** incorrect, since it means the code
@@ -513,20 +509,20 @@ label:
 				** altzone and timezone), and the
 				** magic might not have the correct
 				** offset. Doing things correctly is
-				** tricky and requires disobeying C99;
+				** tricky and requires disobeying the standard;
 				** 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
+				** C99 and later 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)
 					continue;
 				if (t->tm_isdst == 0)
-#  ifdef USG_COMPAT
+#  if USG_COMPAT
 					diff = -timezone;
 #  else
 					continue;
@@ -543,9 +539,11 @@ label:
 #ifdef TM_ZONE
 				  negative = t->TM_ZONE[0] == '-';
 #else
-				  negative
-				    = (t->tm_isdst < 0
-				       || tzname[t->tm_isdst != 0][0] == '-');
+				  negative = t->tm_isdst < 0;
+# if HAVE_TZNAME
+				  if (tzname[t->tm_isdst != 0][0] == '-')
+				    negative = true;
+# endif
 #endif
 				}
 				if (negative) {
diff --git a/zdump.c b/zdump.c
index 7cde3d3..b69b1e9 100644
--- a/zdump.c
+++ b/zdump.c
@@ -77,7 +77,6 @@ extern int	getopt(int argc, char * const argv[],
 			const char * options);
 extern char *	optarg;
 extern int	optind;
-extern char *	tzname[];
 #endif
 
 /* The minimum and maximum finite time values.  */
@@ -997,9 +996,11 @@ abbr(struct tm const *tmp)
 #ifdef TM_ZONE
 	return tmp->TM_ZONE;
 #else
-	return (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst]
-		? tzname[0 < tmp->tm_isdst]
-		: "");
+# if HAVE_TZNAME
+	if (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst])
+	  return tzname[0 < tmp->tm_isdst];
+# endif
+	return "";
 #endif
 }
 
-- 
2.9.4



More information about the tz mailing list