[PROPOSED 1/2] Minor editorial improvements for newstrftime.3

* newstrftime.3: Improve this a bit to have it match POSIX better. Resurrect the BSD manual’s phase-of-the-moon joke. --- newstrftime.3 | 103 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 66 insertions(+), 37 deletions(-) diff --git a/newstrftime.3 b/newstrftime.3 index df38d1a..63842c7 100644 --- a/newstrftime.3 +++ b/newstrftime.3 @@ -50,12 +50,14 @@ strftime \- format date and time .B cc ... \-ltz .fi .SH DESCRIPTION -.ie '\(en'' .ds en \- -.el .ds en \(en .ie '\(lq'' .ds lq \&"\" .el .ds lq \(lq\" .ie '\(rq'' .ds rq \&"\" .el .ds rq \(rq\" +.de c +.ie \n(.g \f(CW\\$1\fP\\$2 +.el \\$1\\$2 +.. .de q \\$3\*(lq\\$1\*(rq\\$2 .. @@ -63,7 +65,7 @@ The .B strftime function formats the information from .I timeptr -into the buffer +into the array pointed to by .I buf according to the string pointed to by .IR format . @@ -72,24 +74,24 @@ The .I format string consists of zero or more conversion specifications and ordinary characters. -All ordinary characters are copied directly into the buffer. +All ordinary characters are copied directly into the array. A conversion specification consists of a percent sign .Ql % and one other character. .PP No more than .I maxsize -characters are placed into the array. -If the total number of resulting characters, including the terminating -null character, is not more than +bytes are placed into the array. +If the total number of resulting bytes, including the terminating +NUL character, is not more than .IR maxsize , .B strftime -returns the number of characters in the array, not counting the -terminating null. -Otherwise, zero is returned. +returns the number of bytes placed into the array, not counting the +terminating NUL. +Otherwise, zero is returned and the array contents are unspecified. .PP Each conversion specification is replaced by the characters as -follows which are then copied into the buffer. +follows which are then copied into the array. .TP %A is replaced by the locale's full weekday name. @@ -105,99 +107,122 @@ is replaced by the locale's abbreviated month name. .TP %C is replaced by the century (a year divided by 100 and truncated to an integer) -as a decimal number (00\*(en99). +as a decimal number [00,99]. .TP %c is replaced by the locale's appropriate date and time representation. .TP %D -is replaced by the date in the format %m/%d/%y. +is equivalent to +.c %m/%d/%y . .TP %d -is replaced by the day of the month as a decimal number (01\*(en31). +is replaced by the day of the month as a decimal number [01,31]. .TP %e -is replaced by the day of month as a decimal number (1\*(en31); +is replaced by the day of month as a decimal number [1,31]; single digits are preceded by a blank. .TP %F -is replaced by the date in the format %Y\*-%m\*-%d. +is equivalent to +.c %Y-%m-%d +(the ISO 8601 date format). .TP %G is replaced by the ISO 8601 year with century as a decimal number. +See also the +.c %V +conversion specification. .TP %g -is replaced by the ISO 8601 year without century as a decimal number (00\*(en99). +is replaced by the ISO 8601 year without century as a decimal number [00,99]. +This is the year that includes the greater part of the week. +(Monday as the first day of a week). +See also the +.c %V +conversion specification. .TP %H -is replaced by the hour (24-hour clock) as a decimal number (00\*(en23). +is replaced by the hour (24-hour clock) as a decimal number [00,23]. .TP %I -is replaced by the hour (12-hour clock) as a decimal number (01\*(en12). +is replaced by the hour (12-hour clock) as a decimal number [01,12]. .TP %j -is replaced by the day of the year as a decimal number (001\*(en366). +is replaced by the day of the year as a decimal number [001,366]. .TP %k -is replaced by the hour (24-hour clock) as a decimal number (0\*(en23); +is replaced by the hour (24-hour clock) as a decimal number [0,23]; single digits are preceded by a blank. .TP %l -is replaced by the hour (12-hour clock) as a decimal number (1\*(en12); +is replaced by the hour (12-hour clock) as a decimal number [1,12]; single digits are preceded by a blank. .TP %M -is replaced by the minute as a decimal number (00\*(en59). +is replaced by the minute as a decimal number [00,59]. .TP %m -is replaced by the month as a decimal number (01\*(en12). +is replaced by the month as a decimal number [01,12]. .TP %n is replaced by a newline. .TP %p -is replaced by the locale's equivalent of either AM or PM. +is replaced by the locale's equivalent of either +.q AM +or +.q PM . .TP %R -is replaced by the time in the format %H:%M. +is replaced by the time in the format +.c %H:%M . .TP %r is replaced by the locale's representation of 12-hour clock time using AM/PM notation. .TP %S -is replaced by the second as a decimal number (00\*(en60). +is replaced by the second as a decimal number [00,60]. +The range of +seconds is [00,60] instead of [00,59] to allow for the periodic occurrence +of leap seconds. .TP %s -is replaced by the number of seconds since the Epoch (see newctime(3)). +is replaced by the number of seconds since the Epoch (see +.BR ctime (3)). .TP %T -is replaced by the time in the format %H:%M:%S. +is replaced by the time in the format +.c %H:%M:%S . .TP %t is replaced by a tab. .TP %U is replaced by the week number of the year (Sunday as the first day of -the week) as a decimal number (00\*(en53). +the week) as a decimal number [00,53]. .TP %u is replaced by the weekday (Monday as the first day of the week) -as a decimal number (1\*(en7). +as a decimal number [1,7]. .TP %V is replaced by the week number of the year (Monday as the first day of -the week) as a decimal number (01\*(en53). If the week containing January +the week) as a decimal number [01,53]. If the week containing January 1 has four or more days in the new year, then it is week 1; otherwise it is week 53 of the previous year, and the next week is week 1. +The year is given by the +.c %G +conversion specification. .TP %W is replaced by the week number of the year (Monday as the first day of -the week) as a decimal number (00\*(en53). +the week) as a decimal number [00,53]. .TP %w is replaced by the weekday (Sunday as the first day of the week) -as a decimal number (0\*(en6). +as a decimal number [0,6]. .TP %X is replaced by the locale's appropriate time representation. @@ -209,7 +234,7 @@ is replaced by the locale's appropriate date representation. is replaced by the year with century as a decimal number. .TP %y -is replaced by the year without century as a decimal number (00\*(en99). +is replaced by the year without century as a decimal number [00,99]. .TP %Z is replaced by the time zone abbreviation, @@ -217,7 +242,7 @@ or by the empty string if this is not determinable. .TP %z is replaced by the offset from the Prime Meridian -in the format +HHMM or \*-HHMM as appropriate, +in the format +HHMM or \*-HHMM (ISO 8601) as appropriate, with positive values representing locations east of Greenwich, or by the empty string if this is not determinable. The numeric time zone abbreviation \*-0000 is used when the time is @@ -231,7 +256,9 @@ time zone abbreviation begins with is replaced by a single %. .TP %+ -is replaced by the date and time in date(1) format. +is replaced by the locale's date and time in +.BR date (1) +format. .SH SEE ALSO date(1), getenv(3), @@ -239,3 +266,5 @@ newctime(3), newtzset(3), time(2), tzfile(5) +.SH BUGS +There is no conversion specification for the phase of the moon. -- 2.17.1

The latest POSIX draft specifies errno values for some strftime errors. Implement those, plus one other one: a reliable way to determine whether 0 represents failure or buffer exhaustion (I’ll propose this to POSIX). * newstrftime.3 (RETURN VALUE): Document this. * strftime.c (strftime): Set errno according to current draft of POSIX. Also, set errno to ERANGE on overflow, and preserve errno if there is no error. (_fmt): Return NULL if %s would be out of range. Callers changed. --- newstrftime.3 | 34 +++++++++++++++++++++++++++------- strftime.c | 24 +++++++++++++++++++++++- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/newstrftime.3 b/newstrftime.3 index 63842c7..887aba5 100644 --- a/newstrftime.3 +++ b/newstrftime.3 @@ -82,13 +82,6 @@ and one other character. No more than .I maxsize bytes are placed into the array. -If the total number of resulting bytes, including the terminating -NUL character, is not more than -.IR maxsize , -.B strftime -returns the number of bytes placed into the array, not counting the -terminating NUL. -Otherwise, zero is returned and the array contents are unspecified. .PP Each conversion specification is replaced by the characters as follows which are then copied into the array. @@ -259,6 +252,33 @@ is replaced by a single %. is replaced by the locale's date and time in .BR date (1) format. +.SH "RETURN VALUE" +If the conversion is successful, +.B strftime +returns the number of bytes placed into the array, not counting the +terminating NUL; +.B errno +is unchanged if the returned value is zero. +Otherwise, +.B errno +is set to indicate the error, zero is returned, +and the array contents are unspecified. +.SH ERRORS +This function fails if: +.TP +[ERANGE] +The total number of resulting bytes, including the terminating +NUL character, is more than +.IR maxsize . +.PP +This function may fail if: +.TP +[EOVERFLOW] +The format includes an +.c %s +conversion and the number of seconds since the Epoch cannot be represented +in a +.c time_t . .SH SEE ALSO date(1), getenv(3), diff --git a/strftime.c b/strftime.c index 14cbc9a..4f871cd 100644 --- a/strftime.c +++ b/strftime.c @@ -130,10 +130,15 @@ size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *t) { char * p; + int saved_errno = errno; enum warn warn = IN_NONE; tzset(); p = _fmt(format, t, s, s + maxsize, &warn); + if (!p) { + errno = EOVERFLOW; + return 0; + } if (DEPRECATE_TWO_DIGIT_YEARS && warn != IN_NONE && getenv(YEAR_2000_NAME)) { fprintf(stderr, "\n"); @@ -146,9 +151,12 @@ strftime(char *s, size_t maxsize, const char *format, const struct tm *t) else fprintf(stderr, "all locales"); fprintf(stderr, "\n"); } - if (p == s + maxsize) + if (p == s + maxsize) { + errno = ERANGE; return 0; + } *p = '\0'; + errno = saved_errno; return p - s; } @@ -312,7 +320,21 @@ label: time_t mkt; tm = *t; + tm.tm_yday = -1; mkt = mktime(&tm); + if (mkt == (time_t) -1) { + /* Fail unless this -1 represents + a valid time. */ + struct tm tm_1; + if (!localtime_r(&mkt, &tm_1)) + return NULL; + if (!(tm.tm_year == tm_1.tm_year + && tm.tm_yday == tm_1.tm_yday + && tm.tm_hour == tm_1.tm_hour + && tm.tm_min == tm_1.tm_min + && tm.tm_sec == tm_1.tm_sec)) + return NULL; + } if (TYPE_SIGNED(time_t)) sprintf(buf, "%"PRIdMAX, (intmax_t) mkt); -- 2.17.1

Date: Sat, 8 Aug 2020 12:29:23 -0700 From: Paul Eggert <eggert@cs.ucla.edu> Message-ID: <20200808192923.23487-2-eggert@cs.ucla.edu> | a reliable way | to determine whether 0 represents failure or buffer exhaustion This is not necessary. The cases can be distinguished by setting errno to 0 before the call, and checking afterwards (if 0 is returned) - if errno is set there was an error, if it is still 0, it is something else. A 0 return can indicate a successful return with no data, not just conversion failure or buffer exhaustion (eg: strftime(buf, sizeof buf, "%Z", tm); in a timezone where there are no timezone names). This is easy to avoid by simply using res = strftime(buf, sizeof buf, " %Z", tm); where any successful return must be at least 1, so 0 always indicates some kind of error (including when sizeof(buf) == 0, though that's not a case worth worrying about). Then the code simply uses buf+1 as the result (and res-1 as the length) after it has been determined that there was no error (ie: res >= 1). kre

Robert Elz said:
| a reliable way | to determine whether 0 represents failure or buffer exhaustion
This is not necessary.
The cases can be distinguished by setting errno to 0 before the call, and checking afterwards (if 0 is returned) - if errno is set there was an error, if it is still 0, it is something else.
C has no requirement to leave errno unchanged on success. | The value of errno is zero at program startup, but is | never set to zero by any library function. The value of | errno may be set to nonzero by a library function call | whether or not there is an error, provided the use of errno | is not documented in the description of the function in this | International Standard. strftime is not documented as using errno, so that wording applies. This is because the library function may have to make a system call to check how to behave and that may fail. -- Clive D.W. Feather | If you lie to the compiler, Email: clive@davros.org | it will get its revenge. Web: http://www.davros.org | - Henry Spencer Mobile: +44 7973 377646

Date: Sat, 8 Aug 2020 22:25:47 +0100 From: "Clive D.W. Feather" <clive@davros.org> Message-ID: <20200808212547.GK1036@davros.org> | C has no requirement to leave errno unchanged on success. Yes, I know, but functions can be defined to work that way. It turns out not to matter here really, as it is trivial to arrange that a successful call always returns >= 1, leaving 0 to always mean that an error occurred (after which errno can be checked). | strftime is not documented as using errno, so that wording applies. It is going to be specified to use errno in the next POSIX update. This is already in the draft: ERRORS CX These functions shall fail if: [EOVERFLOW] The format string includes a %s conversion and the number of seconds since the Epoch cannot be represented in a time_t. These functions may fail if: [EINVAL] The format string includes a %s conversion and the number of seconds since the Epoch would be negative. kre

On 8/8/20 3:01 PM, Robert Elz wrote:
It turns out not to matter here really, as it is trivial to arrange that a successful call always returns >= 1, leaving 0 to always mean that an error occurred (after which errno can be checked).
It's not that easy in general-purpose functions that call strerror. The tzdb's date.c program, for example, does this: cp[0] = '\1'; result = strftime(cp, size, format, tmp); if (result != 0 || cp[0] == '\0') /* strftime worked */; and this relies on undocumented but we-hope-portable-in-practice properties of strftime. If we knew that strftime preserved errno on a successful return of 0, we could make this code use that property and it'd be portable and easier to understand. One can work around the problem by allocating memory for a larger format (take the original format and prepend a space, then strip a leading space from the output), but that's awkward and inefficient and hardly anybody does it. Really, the strftime API needs to be fixed. I submitted a bug report to POSIX about this, here: https://www.austingroupbugs.net/view.php?id=1386 which proposes that errno should be preserved on successful returns of 0 from strftime. As it happens, Dennis Wölfing already reported the same spec bug here: https://www.austingroupbugs.net/view.php?id=1353 I hope that the POSIX committee accepts one or the other of the suggested changes.

Date: Sun, 9 Aug 2020 00:03:55 -0700 From: Paul Eggert <eggert@cs.ucla.edu> Message-ID: <b07884cb-f690-54db-4127-459b9ad8c502@cs.ucla.edu> | The tzdb's date.c program, This is for handling date +fmt ? right What we do is simply pass the "+fmt" arg to strftime, unaltered. The '+' that we know must be there, or we wouldn't be calling strftime() from date at all, serves as the extra dummy padding character. It doesn't matter what that char is, as long as it isn't '\0' or '%', so the '+' is fine. if (*argv && **argv == '+') format = *argv; [...] while (strftime(buf, bufsiz, format, tm) == 0) /* make buffer bigger, etc, if we can */ The '+' then gets dropped from the result. (void)printf("%s\n", buf + 1); | One can work around the problem by allocating memory for a larger format | (take the original format and prepend a space, then strip a leading space | from the output), One certainly could, but as the format already has that '+' in it, one doesn't really need that complication, it all just works. There might be some cases where something like that is actually needed, but those seem rare, I don't think I have encountered one. | I submitted a bug report to POSIX about this, here: | https://www.austingroupbugs.net/view.php?id=1386 Yes, I saw, I added a note to it similar to my e-mail here. | which proposes that errno should be preserved on successful returns | of 0 from strftime. I have no problem with that part of it. We just don't need the ERANGE stuff added. kre

On 8/9/20 9:32 AM, Robert Elz wrote:
pass the "+fmt" arg to strftime, unaltered.
Thanks, I hadn't thought of that trick. I installed the attached patch to do it. I remain convinced that an ERANGE errno is useful, though. General-purpose routines don't always have the luxury of knowing that format[-1] is available.

Date: Sun, 9 Aug 2020 11:00:35 -0700 From: Paul Eggert <eggert@cs.ucla.edu> Message-ID: <35abe9d1-18c3-dd9b-cd6b-5d46866f66df@cs.ucla.edu> | I installed the attached patch to do it. The only comment I'd make about that, and this is just style, is that instead of ... | + timeout(stdout, format ? format : "+%+", tmp); we simply init format before (actually during) the arg processing, as ... format = "+%a %b %e %H:%M:%S %Z %Y"; (other than the leading '+' (which could be a space there, or other) the content of the string isn't important here). and then allow a '+' arg to override it. There's actually a bit more to the arg processing than I showed, but nothing important for this issue. | General-purpose routines don't always have the luxury of knowing that | format[-1] is available. No, they don't, but someone designing a general purpose routine to use strftime (or for that matter, anything) should design it around the characteristics of strftime (or whatever). Do it with that in mind, and whatever extra needs doing to handle strftime (etc) properly becomes much easier, than if one ignores the properties of the interfaces to be used to implement the routine, and has to coerce a poorly designed API to fit. kre ps: to get the ERANGE accepted by posix, it needs to be the standard (or at least common) behaviour first, POSIX specifies what the standard is, it doesn't (usually anyway) invent it (or adopt someone else's invention). It is very rare for something to be added which would make a formerly conforming implementation become non-conforming.

On 8/10/20 12:58 AM, Robert Elz wrote:
The only comment I'd make about that, and this is just style, is that instead of ...
| + timeout(stdout, format ? format : "+%+", tmp);
we simply init format before (actually during) the arg processing
Good suggestion; done via the attached proposed patch ("Subject:" line misspelling and all :-).
| General-purpose routines don't always have the luxury of knowing that | format[-1] is available.
No, they don't, but someone designing a general purpose routine to use strftime (or for that matter, anything) should design it around the characteristics of strftime (or whatever).
It's too late for that. Many APIs have already been designed. Plus, this really is an botch/awkwardness in the strftime API, and it'd be a shame for it to force similar botches/awkwardnesses in new APIs.
ps: to get the ERANGE accepted by posix, it needs to be the standard (or at least common) behaviour first, POSIX specifies what the standard is, it doesn't (usually anyway) invent it
The POSIX draft is already doing that to strftime, by requiring EOVERFLOW where many implementations don't do EOVERFLOW, and similarly for EINVAL for the odd implementations that don't support negative time_t. As long as it's specifying errno de novo anyway, it might as well fix this longtime botch. I have a similar beef with mktime, by the way: there's not a well-designed way to distinguish success from failure. But that's trickier since the botch has been in POSIX for a while. All this strftime errno stuff is new.

Date: Tue, 11 Aug 2020 19:05:39 -0700 From: Paul Eggert <eggert@cs.ucla.edu> Message-ID: <85aad8e3-99dd-d428-947b-3f53b30be64f@cs.ucla.edu> | It's too late for that. Many APIs have already been designed. APIs that are ever used in a way that the ambiguous case can arise?? I think it might be just %Z (rarely) and %p (sometimes) which ever produce an empty string. How many of those other APIs would ever be given either "%Z" or "%p" as their arg? If that doesn't happen they don't have a problem. | The POSIX draft is already doing that to strftime, by requiring EOVERFLOW | where many implementations don't do EOVERFLOW, As I'm sure you have seen on the Austin list, I agree with your point there, that should not be required. But to add it as a "may fail" all that is needed is any one implementation that does that. | and similarly for EINVAL for the odd | implementations that don't support negative time_t. Same there. | As long as it's specifying | errno de novo anyway, it might as well fix this longtime botch. The problem with this one is that all implementations need to support it, or it is worthless. If code has to deal with the possibility that the implementation doesn't return ERANGE (and of course, most don't currently) then that it might happen to be running on one that does is largely immaterial. The burden to add this kind of change is considerably greater. | I have a similar beef with mktime, by the way: there's not a | well-designed way to distinguish success from failure. It isn't trivial, but if -1 is returned, it isn't all that difficult to check whether there's any possibility the tm might be set to 23:59:59 Dec 31 1969 (UTC). In most cases it is safe to simply assume that won't be the case, and simply treat -1 as the error indicator only, and not a possibly valid time_t but when more is needed, the check is not all that difficult (after a successful return, which is what it would be if the -1 indicates that 1969 date, the tm has been normalised, Dec 31, 1969 was a Wednesday, so set tm_wday to 0, and then check if on return it has become 3 or 4 (4, in case of a timezone east of UTC). If not, then we know the -1 means error. After that there are just two cases to check for, if tm_wday is 3, then tm_mon would be 11, tm_year 69, tm_mday 31, and tm_sec 59 (leave hour and min unchecked initially to avoid needing to worry about the zone offset). If wday is 4, then it should be tm_mon 0, tm_mday 1, tm_year 70, tm_sec 59. If those checks fail (any) then the -1 was an error return. After that if you really wanted you could look at the zone offset, and check tm_hour and tm_min as well, but I think it would be reasonable in that case, to assume that the -1 really did mean 31 Dec 1969 23:59:59. Another way, if -1 is returned, is to take the original struct tm do tm_sec++ and then mktime() again. If you now get 0 as the result than the original -1 was the 31 Dec 1969 version, otherwise it was an error (whether the 2nd mktime() returns -1 or not - anything but 0). kre

On 8/12/20 6:25 AM, Robert Elz wrote:
I think it might be just %Z (rarely) and %p (sometimes) which ever produce an empty string. How many of those other APIs would ever be given either "%Z" or "%p" as their arg?
It's pretty common to design a general-purpose API that is a wrapper around strftime. For example, Emacs has a function 'emacs_nmemftime' that is a wrapper around strftime that also allows formats containing NUL bytes (as these are allowed in Emacs strings). An Emacs Lisp program supplied by the user can invoke (format-time-string "%p") which invokes emacs_nmemftime which in turn invokes strftime with a "%p" argument. (The actual code is a bit more complicated than this, but for now let's assume this simpler version.) Now, emacs_nmemftime could be modified to treat formats like "%p%Z" as special cases, and format " %p%Z" instead and discard the leading space in the output. Or, we could modify emacs_nmemftime to always make a copy of the format with an extra space in it, call strftime, and then remove the leading space in the result. Or, we could document 'format-time-string' to mishandle formats like "%p%Z" in some cases. But none of these alternatives are good, for obvious reasons. They're all hacks to work around the deficiency in the C11 strftime API. Since POSIX is fixing strftime errno deficiencies anyway in other areas, by defining what to do with out-of-range times, it might as well fix this deficiency with "%p" too, while it's at it.
The problem with this one is that all implementations need to support it, or it is worthless.
That same objection applies to the EOVERFLOW errno change that is already in the POSIX strftime draft, as well as to many of the other changes in the POSIX draft, so evidently it's not a fatal objection.
[for mktime] ... In most cases it is safe to simply assume that won't be the case, and simply treat -1 as the error indicator only That isn't safe in general-purpose routines.
when more is needed, the check is not all that difficult (after a successful return, which is what it would be if the -1 indicates that 1969 date, the tm has been normalised, Dec 31, 1969 was a Wednesday, so set tm_wday to 0, and then check if on return it has become 3 or 4
I've used that hack too, but neither POSIX nor C11 require the hack to work. What I'd propose is that the standard specifies that mktime must not not update tm_yday when it fails. This matches all implementations that I know of, and allows the hack to work. The variant that I use is to set tm_yday to -1 before calling mktime, and test whether it's negative afterwards.
Another way, if -1 is returned, is to take the original struct tm do tm_sec++ and then mktime() again.
This won't work if tm_sec == INT_MAX.

Date: Thu, 13 Aug 2020 16:18:13 -0700 From: Paul Eggert <eggert@cs.ucla.edu> Message-ID: <bd31f6a2-b365-88b6-f341-9a3324142fc7@cs.ucla.edu> | It's pretty common to design a general-purpose API I think "common" is a stretch here. Yes, I'm sure there are a few cases, but it is hardly common. Further, those that already exist either have had to already deal with this problem, or treat it as irrelevant. Either way we don't really need to be overly concerned (that is, if this issue were breaking applications it would have become more publicised long before now). Anything for the future can easily be designed to deal with it. | > The problem with this one is that all implementations need to support it, | > or it is worthless. | | That same objection applies to the EOVERFLOW errno change Not really, as in many (perhaps most) applications, the tm being passed to strftime came from a time_t (via localtime) and hence the tm is known to fit in a time_t and EOVERFLOW cannot occur. The only cases where it is possible is where the tm comes from elsewhere (eg: a parsedate variant that returned the tm instead of a time_t) and wasn't subject to an earlier mktime() or similar. I think that is quite rare. Apps can mostly ignore the EOVERFLOW possibility, as generally it cannot happen - it is there (though should be "may fail" in strftime) for completeness rather than any real necessity. That is, some implementation coding very carefully wondered "what do I do here?" It is safe to simply ignore that one. On the contrary it is also safe to check for it, ever in an implementation which never generates the error. Adding code to deal with an error that never occurs is wasteful perhaps, but harmless. This isn't true of ERANGE, if you're coding to deal with the possibility that it might not be implemented, then it is useless, if you're assuming that ERANGE will be generated, then your code breaks on implementations (standards compliant or not) which don't generate it. | > when more is needed, the check is not all that difficult | > (after a successful return, which is what it would be if the -1 indicates | > that 1969 date, the tm has been normalised, Dec 31, 1969 was a Wednesday, | > so set tm_wday to 0, and then check if on return it has become 3 or 4 | | I've used that hack too, but neither POSIX nor C11 require the hack to work. You mean because POSIX doesn't say what will be in the struct tm (*timeptr) after a failure, and you're imagining the possibility that the implementation might return -1, and simultaneously set the struct tm to be the Dec 31 23:59:59 UTC value ? Really? I suppose that might be technically allowed, as the spec doesn't indicate what will be in the struct after an error, but I don't really believe that this is something to be concerned about. We could have added to POSIX in the "ERRORS" case "the contents of the structure pointed to by timepointer shall be unspecified, except that they shall not represent any value which may have a time_t representation of (time_t)-1." (or words to that effect). That wouldn't break any actual implementations, and would allow this "hack" to work. | What I'd propose is that the standard specifies that mktime must | not not update tm_yday when it fails. | This matches all implementations that I know of, and | allows the hack to work. That's reasonable too. | > Another way, if -1 is returned, is to take the original struct tm | > do tm_sec++ and then mktime() again. | | This won't work if tm_sec == INT_MAX. Really! Can you find a reasonable value for INT_MAX where value%60 == 59 ? I can't... So all that is needed to deal with that case is if (tm_sec == INT_MAX) /* error case */; else { tm_sec++; if (mktime() != 0) /*error case*; else /* really was Dec 31 1969! */; } But if you're really concerned about that, then do it as if (tm_sec == INT_MAX) { tm_sec--; if (mktime() != (time_t)-2) /* error case */; else /* was Dec 31, 1969, UTC */; } else { tm_sec++; if (mktime() != (time_t)0) /* error case */; else /* was Dec 31, 1969 */; } which could be coded more compactly (just one mktime call) by adding a new var to hold the "not an error" value expected from the two cases. kre

On 8/13/20 10:52 PM, Robert Elz wrote:
I suppose that might be technically allowed
Yes, it's allowed.
if (tm_sec == INT_MAX) { tm_sec--; if (mktime() != (time_t)-2) /* error case */; else /* was Dec 31, 1969, UTC */; } else { tm_sec++; if (mktime() != (time_t)0) /* error case */; else /* was Dec 31, 1969 */; }
If this is the sort of thing portable code needs to do in order to determine whether mktime has succeeded, then mktime's API needs improving.

Date: Fri, 14 Aug 2020 15:48:28 -0700 From: Paul Eggert <eggert@cs.ucla.edu> Message-ID: <4e86e8ac-767c-1d63-15a1-83ed3fb4b5e9@cs.ucla.edu> | If this is the sort of thing portable code needs to do in order | to determine whether mktime has succeeded, then mktime's API | needs improving. It is actually more than that, struct tm has outlived its useful lifetime, and because of the way it is used there's no really good way to extend it - we need a whole new struct, and a whole set of new functions to deal with it. Attempting to make minor improvements to what we have now isn't worth the effort. kre

On Sat 2020-08-08T12:29:22-0700 Paul Eggert hath writ:
* newstrftime.3: Improve this a bit to have it match POSIX better. Resurrect the BSD manual’s phase-of-the-moon joke.
Alas, that is no a joke around here. Many cron jobs resign themselves to running daily and then invoking a phase of the moon application before deciding whether to run other tasks. -- Steve Allen <sla@ucolick.org> WGS-84 (GPS) UCO/Lick Observatory--ISB 260 Natural Sciences II, Room 165 Lat +36.99855 1156 High Street Voice: +1 831 459 3046 Lng -122.06015 Santa Cruz, CA 95064 https://www.ucolick.org/~sla/ Hgt +250 m
participants (4)
-
Clive D.W. Feather
-
Paul Eggert
-
Robert Elz
-
Steve Allen