Austin Group Defect Tracker

Aardvark Mark IV

Viewing Issue Simple Details Jump to Notes ] Issue History ] Print ]
ID Category Severity Type Date Submitted Last Update
0001533 [1003.1(2016/18)/Issue7+TC2] Base Definitions and Headers Editorial Enhancement Request 2021-11-08 22:04 2021-11-22 15:36
Reporter steffen View Status public  
Assigned To
Priority normal Resolution Open  
Status New  
Name steffen
User Reference
Section time.h
Page Number 425
Line Number 14451
Interp Status ---
Final Accepted Text
Summary 0001533: struct tm: add tm_gmtoff (and tm_zone) field(s)
Description Hello.

Regarding the MUA i maintain i was pinged by a user who needs to
use the timezone Europe/Dublin. He wrote

      In 2018, the tzdata maintainers (IANA) corrected a historical mistake
      with the Europe/Dublin timezone. The mistake was rooted in a
      misunderstanding of whether IST meant "Irish Summer Time" or "Irish
      Standard Time".

      The problem was discussed at great length
      ( [^] and it was
      concluded that IST really meant Irish *Standard* Time (in constrast
      with, say, British *Summer* Time), and that this standard time is
      defined as UTC+0100.
      Once the question was settled, the only possible solution for keeping
      the Irish local time in sync with the rest of the world (for example,
      Belfast & London) was for IANA to _reverse_ the functioning of the DST
      flag for Ireland. The result is that in the current IANA timezone
      database (2021e), Europe/Dublin has DST applied in *winter*, with an
      adjustment of -1h (that is, negative).
      It turns out that the introduction of a negative DST adjustment caused
      all sorts of bugs back in 2018; in the source distribution of IANA's
      tzdata, one can spot this inside ./europe:

        # In January 2018 we discovered that the negative SAVE values in the
        # Eire rules cause problems with tests for ICU [...] and with tests
        # for OpenJDK [...]
        # To work around this problem, the build procedure can translate the
        # following data into two forms, one with negative SAVE values and the
        # other form with a traditional approximation for Irish timestamps
        # after 1971-10-31 02:00 UTC; although this approximation has tm_isdst
        # flags that are reversed, its UTC offsets are correct and this often
        # suffices. This source file currently uses only nonnegative SAVE
        # values, but this is intended to change and downstream code should
        # not rely on it.

      So, a temporary hack was put in place in order to allow distro
      maintainers to retain the old broken convention of IST and support
      buggy software, but it is clear that the current (and technically, and
      politically, correct) implementation of a negative DST adjustment for
      Ireland is there to stay.
      As a matter of fact, the distro maintainer can choose to compile
      tzdata to keep buggy software happy ("make DATAFORM=rearguard"),
      which replicates the behaviour of tzdata prior to 2018. Many distros
      seem to be doing that for one reason or another, while some have passed
      the upstream change down to their users (probably, without knowing).

Anyhow, all the simple minded software, including the MUA
i maintain, used to do something like

   if((t2 = mktime(gmtime(&t))) == (time_t)-1){
      t = 0;
      goto jredo;
   tzdiff = t - t2;
   if((tmp = localtime(&t)) == NULL){
      t = 0;
      goto jredo;

   tzdiff_hour = (int)(tzdiff / 60);
   tzdiff_min = tzdiff_hour % 60;
   tzdiff_hour /= 60;
   if (tmp->tm_isdst > 0)

Note the .tm_isdst plus positive summer time adjustment.
This was overly primitive, and i recognize that POSIX supports the
%z (and %Z) formats for strftime(3), and in general code as below
is used by projects, so doing it right is very expensive but
doable with POSIX as of today.

However, all BSDs and Linux with either of GNU and musl C library
support the .tm_gmtoff (and .tm_zone) members of "struct tm", in
general all users of the public domain (and standardized) IANA TZ
project can bake it in upon their own desire.. With .tm_gmtoff
being available, code gets as simple as
  time_tzdiff(s64 secsepoch, struct tm const *utcp_or_nil,
        struct tm const *localp_or_nil){
     struct tm tmbuf[2], *tmx;
     time_t t;
     s64 rv;

     rv = 0;

     if(localp_or_nil == NIL){
        t = S(time_t,secsepoch);
        while((tmx = localtime(&t)) == NIL){
           if(t == 0)
              goto jleave;
           t = 0;
        tmbuf[0] = *tmx;
        localp_or_nil = &tmbuf[0];

     rv = localp_or_nil->tm_gmtoff;

     if(utcp_or_nil == NIL){
        t = S(time_t,secsepoch);
        while((tmx = gmtime(&t)) == NIL){
           if(t == 0)
              goto jleave;
           t = 0;
        tmbuf[1] = *tmx;
        utcp_or_nil = &tmbuf[1];

     rv = ((((localp_or_nil->tm_hour - utcp_or_nil->tm_hour) * 60) +
           (localp_or_nil->tm_min - utcp_or_nil->tm_min)) * 60) +
           (localp_or_nil->tm_sec - utcp_or_nil->tm_sec);

     if((t = (localp_or_nil->tm_yday - utcp_or_nil->tm_yday)) != 0){
        s64 const ds = 24 * 60 * 60;

        rv += (t == 1) ? ds : -S(s64,ds);

     return rv;
Desired Action Insert at the given location

 int tm_gmtoff Seconds east of UTC
 const char *tm_zone Timezone abbreviation

Maybe cross-reference with strftime(3) and note relationship to the %z and %Z formats.
Tags No tags attached.
Attached Files

- Relationships

-  Notes
steffen (reporter)
2021-11-21 22:28

P.S.: even though "Object or binary code portability" is outside the "1.1 Scope" of POSIX i want to add, just in case that the strftime(3) formats are yet part of the standard whereas tm_gmtoff is not even though it exists for long is because of binary compatibility nonetheless, that with dynamic library symbol versioning binary compatibility is overall not an issue, or, simpler via compile-time preprocessor switches, or even specific #pragma compiler directives (Solaris seems to use "#pragma redefine_extname") at least newly compiled code does not have issues.
As you all know, of course.
geoffclare (manager)
2021-11-22 09:47

Re Note: 0005529 The compatibility issue is not simply one of handling the increased structure size; there is also a behavioural difference.

On an implementation that lacks tm_gmtoff and tm_zone, if you change TZ after obtaining a struct tm from localtime(), the strftime() %z and %Z conversions use the values for the new TZ. Adding tm_gmtoff and tm_zone would change the behaviour such that %z and %Z use the values for the old TZ. This could break existing applications when they are recompiled and consequently start using the new ABI.
geoffclare (manager)
2021-11-22 10:12

I started wondering about where tm_zone points (since there is no function to free it), and came across this on the MacOS localtime(3) man page (under BUGS):
The tm_zone field of a returned tm structure points to a static array of characters, which will also be overwritten by any subsequent calls (as well as by subsequent calls to tzset(3) and tzsetwall(3)).
So perhaps what I wrote in my previous note about %Z using the old TZ is not true (at least for some implementations that support tm_zone).

If we want to add tm_gmtoff and tm_zone, the standard will need to go into this kind of detail.
geoffclare (manager)
2021-11-22 10:50

The desired action has tm_gmtoff as an int, but in glibc and MacOS it is a long. If there are implementations where it is an int, then to add it to the standard we would need to introduce a new type such as gmtoff_t.

MacOS also doesn't have the const on tm_zone, but I doubt if adding it would cause any problems.
steffen (reporter)
2021-11-22 15:26

Re Note: 0005530 and Note: 0005531
I see. Yes, strftime(3) notes on page 2047 regarding %Z on TZ change!
Standard is hard .. i had a single file DB with a TOC of names at the end pointing to data at the front, if a non-builtin one was needed then all was there to stay after being loaded. I see.
musl does it like

const char *__tm_to_tzname(const struct tm *tm)
        const void *p = tm->__tm_zone;
        if (p != __utc && p != __tzname[0] && p != __tzname[1] &&
            (!zi || (uintptr_t)p-(uintptr_t)abbrevs >= abbrevs_end - abbrevs))
                p = "";
        return p;

therefore even using .tm_zone as a verification that %Z applies to the correct struct tm, which is an assertion beyond the usual "behaviour is undefined".

Re 0005532:
Oh! Indeed they use long not int, sorry! No, i do not know a system where this is int, actually -- sorry for this! I am afraid the standard cannot correct this; i personally never worked on a system where int was 16-bit, where that decision would make some sense. gmtoff_t does not seem to be needed:

#?0|kent:free-src.git$ git grep tm_gmtoff origin/main:include
origin/main:include:time.h: long tm_gmtoff; /* offset from UTC in seconds */
#?0|kent:free-src.git$ cd ../open-src.git/
#?0|kent:open-src.git$ git grep tm_gmtoff origin/master:include
origin/master:include:time.h: long tm_gmtoff; /* offset from UTC in seconds */
#?0|kent:open-src.git$ cd ../net-src.git/
#?0|kent:net-src.git$ git grep tm_gmtoff origin/trunk:include
origin/trunk:include:time.h: long tm_gmtoff; /* offset from UTC in seconds */
#?0|kent:net-src.git$ cd ../dfly-src.git/
#?0|kent:dfly-src.git$ git grep tm_gmtoff origin/master:include
origin/master:include:time.h: long tm_gmtoff; /* offset from UTC in seconds */


#?0|kent:unix-hist$ git grep tm_gmtoff BSD-SCCS-END
BSD-SCCS-END:usr/src/include/time.h: long tm_gmtoff; /* offset from CUT in seconds */
BSD-SCCS-END:usr/src/lib/libc/gen/ctime.3:long tm_gmtoff; /\(** offset from UTC in seconds \(**/
BSD-SCCS-END:usr/src/lib/libc/gen/ctime.3:.Fa tm_gmtoff
BSD-SCCS-END:usr/src/lib/libc/gen/ctime.c: tmp->tm_gmtoff = offset;
BSD-SCCS-END:usr/src/lib/libcompat/4.1/ftime.c: if (tm->tm_isdst) { /* tm_gmtoff has an offset applied */
BSD-SCCS-END:usr/src/lib/libcompat/4.1/ftime.c: tp->timezone = -localtime(&zero)->tm_gmtoff / 60;
BSD-SCCS-END:usr/src/lib/libcompat/4.1/ftime.c: tp->timezone = -tm->tm_gmtoff / 60;
BSD-SCCS-END:usr/src/share/zoneinfo/Makefile:# -DTM_GMTOFF=tm_gmtoff
BSD-SCCS-END:usr/src/share/zoneinfo/Makefile:# -DTM_GMTOFF=_tm_gmtoff
BSD-SCCS-END:usr/src/share/zoneinfo/Makefile:CFLAGS= -DTM_GMTOFF=tm_gmtoff -DTM_ZONE=tm_zone
BSD-SCCS-END:usr/src/usr.bin/chpass/util.c: tval -= lt->tm_gmtoff;


#?128|kent:unix-hist$ git blame BSD-SCCS-END -- usr/src/include/time.h
aec2dfdbbb2 (Keith Bostic 1989-03-08 23:45:57 -0800 49) long tm_gmtoff; /* offset from CUT in seconds */
aec2dfdbbb2 (Keith Bostic 1989-03-08 23:45:57 -0800 50) char *tm_zone; /* timezone abbreviation */

#?0|kent:unix-hist$ git blame aec2dfdbbb2^ -- usr/src/include/time.h
434fabe335c (Keith Bostic 1987-03-05 01:28:35 -0800 22) long tm_gmtoff;
434fabe335c (Keith Bostic 1987-03-05 01:28:35 -0800 23) char *tm_zone;

..and we come to..

commit 434fabe335c23dd3f16ab2d4ff3878b9bc69be50
Author: Keith Bostic <bostic@ucbvax.Berkeley.EDU>
AuthorDate: 1987-03-05 01:28:35 -0800
Commit: Keith Bostic <bostic@ucbvax.Berkeley.EDU>
CommitDate: 1987-03-05 01:28:35 -0800

    added Robert Elz's additions for new time package

    SCCS-vsn: include/time.h 1.2
 usr/src/include/time.h | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/usr/src/include/time.h b/usr/src/include/time.h
index 8eb62c0453..0233380029 100644
--- a/usr/src/include/time.h
+++ b/usr/src/include/time.h
@@ -1,4 +1,10 @@
-/* time.h 1.1 85/03/13 */
+ * Copyright (c) 1983, 1987 Regents of the University of California.
+ * All rights reserved. The Berkeley software License Agreement
+ * specifies the terms and conditions for redistribution.
+ *
+ * @(#)time.h 1.2 (Berkeley) %G%
+ */

  * Structure returned by gmtime and localtime calls (see ctime(3)).
@@ -13,6 +19,8 @@ struct tm {
        int tm_wday;
        int tm_yday;
        int tm_isdst;
+ long tm_gmtoff;
+ char *tm_zone;

 extern struct tm *gmtime(), *localtime();

at which time i have to say Hello and a nice day dear Robert Elz!

So given this is from 1987 i would think 34 years give the guarantee that all possible UNIX systems go for long here. Apologies!
steffen (reporter)
2021-11-22 15:36

P.S.: it seems to me not having "const" for tm_zone roots in incomplete transition to modern times here and there. ..Is this only a documentation miss or also a code negligence? Bit rot here, bit rot there, bit rot is just everywhere, ha-ha. Hrm, sorry. Was it Wheeler with "Programming is an iterative task"? Also applies to bit rot anyhow, const, volatile, restrict, atomic_t, ten years ago i was overrun when i said it needs to be volatile atomic_t instead of simple int or volatile int on some perl list/forum, just a few weeks ago i was corrected for not doing it, ... anyway this is all off-topic. const i always liked, today even rodata is put into the text section .. likely to have an excuse to create hundreds of specialized object file sections for whatever purpose. Terrible, volatile ok, but why atomic_t, aehh, ...

- Issue History
Date Modified Username Field Change
2021-11-08 22:04 steffen New Issue
2021-11-08 22:04 steffen Name => steffen
2021-11-08 22:04 steffen Section => time.h
2021-11-08 22:04 steffen Page Number => 425
2021-11-08 22:04 steffen Line Number => 14451
2021-11-21 22:28 steffen Note Added: 0005529
2021-11-22 09:47 geoffclare Note Added: 0005530
2021-11-22 10:12 geoffclare Note Added: 0005531
2021-11-22 10:50 geoffclare Note Added: 0005532
2021-11-22 15:26 steffen Note Added: 0005533
2021-11-22 15:36 steffen Note Added: 0005534

Mantis 1.1.6[^]
Copyright © 2000 - 2008 Mantis Group
Powered by Mantis Bugtracker