Date: Tue, 23 Sep 2025 18:49:47 -0700 From: Paul Eggert <eggert@cs.ucla.edu> Message-ID: <99d51a4c-e15a-4fcb-b239-007b3918dae1@cs.ucla.edu> | I don't see that as a plausible reading. There really is no other. | The text says "one of two static objects", Yes, the wording is bizarre, attempting to specify the return data from localtime() and ctime() all in one sentence, in order to save a paragraph, is idiotic. | not "a pointer of one of two types". That distinction is not material, the functions return pointers, the object the pointer points to is what matters, not the pointer itself. | There are two | static objects, one for localtime/gmtime and the other for | asctime/ctime, and that's pretty clear from the wording. If you concentrate only on that first sentence, I can see how you might come to that conclusion, but the second sentence makes it absolutely clear that cannot be what is required. Look at it again: Execution of any of the functions that return a pointer to one of these object types may overwrite First we have "may overwrite" not "shall overwrite" - "may" means the implementation is permitted to, but not required to. If there was only permitted to be one struct tm for all of the results, that "may" would make no sense at all - every call would (necessarily) overwrite that struct. But wait, there's more: | the information in any object of the same type "In any object of the same type" - how can that make any sense to say if there's only permitted to be one of them. What other objects could that "any" be referring to? For that to make any sense it must be that it is possible that there are more than one. | And I'm not the only person to read the standards this way. That I cannot help. Unfortunately, lots of people don't spend the effort required to really understand what any of these things actually say. | Although later C standards relax this (and I'll have more to say about | that in a later email), POSIX.1-2024 still has the C89 requirement. Yes, while I don't have a C standard that old, the oldest ones I have have identical wording to POSIX (hardly surprising, POSIX "borrows" the C standard language as much as it can). If what is there is the same as in C89, then it has clearly been misinterpreted, all of this time, as that has both the "may" and "any object of the same type" which make it clear that there is no such requirement. There never was. | As you probably know, the standards did not invent this requirement. No, they didn't, as it doesn't exist. | In 7th edition Unix, This predates that, localtime() ctime() (etc) were not new in 7th edition (though lots of the rest of libc as we know it now was) - what was new was "struct tm" - in earlier versions that was int tm[9]; (or something like that) - ie: the return value was a pointer to an array of 9 integers, (to be strictly correct, it was a pointer to the first integer in that array) which contained the secs/mins/hours/mday/mon/year/wday/yday/isdst values (which morphed into struct tm in 7th edition - not actually changing anything, but giving names to the fields ... if code wanted to treat it the old way, that still worked as well, type checking back then was kind of non-existant). | localtime and gmtime calls always returned the same pointer Yes, they did, and that's why the standard allows that behaviour. There is a big difference between allowing it, and requiring it, however. There's no question that it is permitted to act like that in the standard (even C23 with its additions), and application code should not expect anything different. That does not mean (and never did) that application code is entitled to assume that that relationship between gmtime() and localtime() existed, it just has to allow for the possibility that it might. An implementation if it wanted could (assuming that the _r functions exist) implement localtime() & gmtime() like static struct ltm[N]; static int nxtltm = 0; struct tm * localtime(time_t *t) { if (nxtltm >= N) nxtltm = 0; return localtime_r(t, <m[nxtltm++]); } static struct gtm[N]; static int nxtgtm = 0; struct tm * gmtime(time_t *t) { if (nxtgtm >= N) nxtgtm = 0; return gmtime_r(t, >m[nxtgtm++]); } for whatever non-negative value of N it likes (including 0), and it could skip gtm & nxtgtm and use ltm and nxtltm in gmtime() as well if it wanted, and doing that, with N==0, achieves what you believe is required, though any implementation like this (any value of N) is permitted. This implementation may overwrite a value in any of the previously returned structs of the same type. Exactly as the wording says may be done. The CSRG (BSD 4.4(ish)) man page for this stuff includes (from 1993) BUGS Except for difftime() and mktime(), these functions leaves their result in an internal static object and return a pointer to that object. Subsequent calls to these function [sic] will modify the same object. So even way back then (actually, well before then) this behaviour was recognised as being a bug, and of course, anything which is a bug is something that's liable to be fixed at any time - no-one should be relying upon it remaining (but of course, nor should anyone be assuming that any particular implementation will have fixed it). The 6th edition man page just says (of the results of localtime() and gmtime()): The value is a pointer to an array whose components are ... While those two functions certainly used the same array for the result, the manual page doesn't say that (doesn't even really imply it). It isn't even explicit that "the value" will be the same pointer from two calls to localtime() (or gmtime()) - though there's no doubt that's how the implementation worked. It has simply never been safe for an application to assume that the result from any of these calls *must* be the same storage space as any of the others. Similarly it has never been safe to assume that it won't be - it can happen either way. kre ps: not directly relevant, but slightly, as it touches on how sloppy all of this old wording is, the paragraph in question says: Execution of any of the functions that return a pointer to one of these object types may overwrite the information in any object of the same type pointed to by the value returned from any previous call to any of them. That means, for example, that the char [] array that ctime() returns can be overwritten by another call to ctime(), or a call to asctime() (as both of those return a char *), but nothing there permits ctime() to overwrite the information returned by localtime() (or gmtime()) as that is not "information in an object of the same type returned ...". Yet we all know (I hope) that (until time_t became 64 bits and localtime() could fail, and the difference that made is immaterial here) that the implementation of ctime() is (was) just: char *ctime(time_t *t) { return asctime(localtime(t)); } and as localtime() clobbers the static struct tm it returns, ctime() clobbers it as well - even though the standard does not say that is permitted, that's not an object of the same type as that returned.