This is an issue that occured to me in the process of converting tzcode to support my proposed thread-safe API. I think the following restriction is necessary to the POSIX 200x spec. If a 'struct tm' broken-down time structure is created by localtime() or localtime_r(), or modified by mktime(), and the value of the "TZ" environment variable is subsequently modified, the results of the %Z and %z strftime() and wcsftime() conversion specifiers are undefined, when strftime() or wcsftime() is called with such a broken-down time structure. If the broken-down time structure is subsequently modified by a successful call to mktime(), the results of these conversion specifiers are once again defined. If a 'struct tm' broken-down time structure is modified by gmtime(), it is implementation-defined whether the result of the %Z and %z strftime() and wcsftime() conversion specifiers shall refer to UTC or the current local time zone, when strftime() or wcsftime() is called with such a broken-down time structure. I think it's too late to get this restriction into the Austin Group revised POSIX spec, but some version of it probably ought to go into the tzcode man pages (along with documentation of what tzcode does for the second case, which is dependent on the value of TM_ZONE and TM_GMTOFF at compile-time). The motivating example for the first two paragraphs is the code attached below (test-tzchange.c). It produces the following output on FreeBSD 4.3 (which uses a slightly old version of tzcode, with some local changes that shouldn't affect this; it defines TM_ZONE and TM_GMTOFF): $ ./test-tzchange Fri 04 May 11:11:00 2001 EDT Fri 04 May 11:11:00 2001 PPT Fri 04 May 11:11:00 2001 PDT This occurs because lclptr->chars has a different value for the two time zones; since test_tm->tm_zone is a pointer into lclptr->chars, it ends up pointing somewhere bogus, and thus claiming that "Pacific Peace Time" was used in 2001. I don't think it's possible to fix this in tzcode without either breaking binary compatibility of 'struct tm', leaking memory on a TZ change, or breaking %Z for non-current time zones. The motivating example for the third paragraph is that System V (at least Solaris) and tzcode do different things, and I don't think either one is about to change. (System V always gets its %Z from tzname[i], I think.) The underlying motivation for all this is that I don't see any sensible way to set tm_zone, for struct tm's produced by reentrant timezone functions, that doesn't end up leading to free pointer dereferences following tz_free(). However, if the analogous operation for process-wide timezones is *already* undefined, it's clearly not nearly as much of a problem. What do people think about this? #include <stdio.h> #include <time.h> #include <stdlib.h> /* #define OLD_NAMES */ #ifdef OLD_NAMES /* Define this on Solaris */ #define FIRST_NAME "US/Eastern" #define SECOND_NAME "US/Pacific" #else /* Proper Olson tzdata names. */ #define FIRST_NAME "America/New_York" #define SECOND_NAME "America/Los_Angeles" #endif int main() { struct tm* test_tm; time_t then = 988989060; char buf[256]; /* Compatibility names are used, rather than new Olson names, so as to * test on Solaris. */ if (putenv("TZ=" FIRST_NAME) != 0) { fprintf(stderr, "putenv failed (1)\n"); exit(1); } if ((test_tm = localtime(&then)) == NULL) { fprintf(stderr, "localtime failed\n"); exit(1); } if (strftime(buf, sizeof(buf), "%a %d %h %H:%M:%S %Y %Z", test_tm) == 0) { fprintf(stderr, "strftime failed (1)\n"); exit(1); } printf("%s\n", buf); if (putenv("TZ=" SECOND_NAME) != 0) { fprintf(stderr, "putenv failed (2)\n"); exit(1); } tzset(); if (strftime(buf, sizeof(buf), "%a %d %h %H:%M:%S %Y %Z", test_tm) == 0) { fprintf(stderr, "strftime failed (2)\n"); exit(1); } printf("%s\n", buf); test_tm->tm_isdst = -1; if (mktime(test_tm) == -1) { fprintf(stderr, "mktime failed\n"); exit(1); } if (strftime(buf, sizeof(buf), "%a %d %h %H:%M:%S %Y %Z", test_tm) == 0) { fprintf(stderr, "strftime failed (3)\n"); exit(1); } printf("%s\n", buf); exit(0); } -- Jonathan Lennox lennox@cs.columbia.edu