[tz] [PROPOSED 2/2] strftime: conform better to POSIX+

Robert Elz kre at munnari.OZ.AU
Fri Aug 14 05:52:38 UTC 2020


    Date:        Thu, 13 Aug 2020 16:18:13 -0700
    From:        Paul Eggert <eggert at cs.ucla.edu>
    Message-ID:  <bd31f6a2-b365-88b6-f341-9a3324142fc7 at 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



More information about the tz mailing list