The following issue has been SUBMITTED.
======================================================================
https://austingroupbugs.net/view.php?id=1627
======================================================================
Reported By: kre
Assigned To:
======================================================================
Project: 1003.1(2016/18)/Issue7+TC2
Issue ID: 1627
Category: System Interfaces
Type: Enhancement Request
Severity: Objection
Priority: normal
Status: New
Name: Robert Elz
Organization:
User Reference:
Section: XSH 3/mktime
Page Number: 1331
Line Number: 44310-44332, 44361
Interp Status: ---
Final Accepted Text:
======================================================================
Date Submitted: 2023-01-05 12:17 UTC
Last Modified: 2023-01-05 12:17 UTC
======================================================================
Summary: XSH 3 / mktime() is woefully underspecified
Description:
Following on from notes added to bug:1614 and a lengthy
mailing list discussion, it is evident that the specification
of XSH/mktime is woefully inadequate.
New text is specified in the Desired Action to remedy those defects.
This is currently missing anything dealing with what should be done
if the input tm_isdst is not < 0, and does not agree (in sign, if 0
can be said to have a sign) with the final value for tm_isdst in the
struct tm on a successful return.
That's because my inclination is to simply do nothing in that case,
return the correct tm_isdst, but otherwise ignore it - but I admit that's
not how the implementations behave, and that may be being depended upon
by some applications (though the current behaviour is definitely not
required by any standard). So I will leave it for someone who cares
about that to add suitable text to (properly) specify what is to happen.
Also, given that it is too late now to consider adding a timegm()
function (an analog to mktime() which has existed for decades, but
never been standardised) I thought what might be possible would be
to specify enough in the FUTURE DIRECTIONS here to indicate that that
will happen (since it is being added to the C standard, it will happen,
eventually) and to indicate why using it is a much better idea when
the purpose is time_t arithmetic than using localtime()/mktime().
The intent is to get applications to start writing safe code, rather
than nonsense, and do that asap - since in practice, timegm() is
already widely available.
As usual, formatting and wording changes, which keep to the general
intent expressed below are welcome. One thing I considered, but
wasn't sure of a good way to handle, was to find some shorter way
to express "the converted value of the struct tm referred to by
timeptr" (or a field thereof) - which occurs far too often in the
text below, and is unwieldy.
Desired Action:
Delete the (CX shaded) paragraph that starts (line 44310)
A positive or 0 value for tm_isdst ...
and ends (line 44313)
... is in effect for the specified time.
Replace the (CX) shaded paragraph that starts (line 44315)
The relationship between the tm structure ...
and ends(line 44321)
... the other tm structure members specified in <time.h>
(excluding tm_wday).
with the following (presumably also CX shaded) paragraphs:
The mktime() function will first convert the tm_sec, tm_min,
tm_hour, tm_mon, tm_mday and tm_mon (again) fields of the tm
structure referenced by timeptr (or a local internal copy
thereof),
in that order, so that their values become within the ranges
specified
by <time.h>, but also within the ranges applicable to a Gregorian
Calendar date (tm_sec shall not be more than 59, and tm_mday shall
not be more than the number of days in the month specified by
the tm_mon field of the year specified by the tm_year field).
If _field_ represents the field being converted, and _next_
represents the field specified immediately after it in <time.h>
then this shall be done, for each field, by an algorithm
equivalent
to:
if (timeptr->_field_ < MINIMUM_VALUE) {
while (timeptr->_field_ < MINIMUM_VALUE) {
timeptr->_field_ += RANGE;
timeptr->_next_--; /* check overflow
*/
}
} else if (timeptr->_field_ > MAXIMUM_VALUE) {
while (timeptr->_field_ > MAXIMUM_VALUE) {
timeptr->_field_ -= RANGE;
timeptr->_next_++; /* check overflow
*/
}
} /* else do nothing, value of _field_ is OK */
where MINIMUM_VALUE is the minimum allowed value for the
field _field_ as specified in <time.h> MAXIMUM_VALUE is the
maximum allowed value for the field _field_ as specified in
<time.h> except that it shall be 59 where _field_ is tm_sec,
and shall be the appropriate number of days in the specific
month selected by the tm_mon and tm_year fields, where _field_
is tm_mday, and thus is subject to change during each iteration
of the loop, and RANGE is (MAXIMUM_VALUE - MINIMUM_VALUE + 1)
(which is also subject to change, in each iteration of both loops
above, where the field is tm_mday).
Note that there is no requirement that the actual structure
passed via *timeptr be the one being modified by this code.
Should overflow (absolute value of the field becomes too large
to be represented in an int) occur at the places indicated,
the implementation shall return an error if the _next_ field is
tm_year, and may return an error for other fields, though if
_next_ is not tm_year, it may adjust the value of any later field,
and reduce the magnitude of the _next_ field by an appropriate
amount to compensate. Adjustments made this way should be chosen
so as to minimise the effects of the adjustment upon the meaning
of the later field, for example, if tm_hour were to overflow,
the implementation might adjust tm_mday by 146101 (the number of
days in a 400 year period - since in the Gregorian calendar, that
is
a constant) and reduce the magnitude of tm_hour by 3506424
(24*146101,
the number of hours in 400 years). Alternatively it might alter
tm_mon by 4800 (the number of months in a 400 year period), and
adjust tm_hour by the same amount (3506424). Overflow produced
when making any such adjustment should be handled in a similar
way, including, if an adjustment to tm_mon requires an adjustment
to tm_year, and that causes tm_year to overflow, then an error
shall be returned.
The tm_isdst field of the structure referred to by timeptr (or
a local copy thereof) shall be converted by altering any
value that is less than 0 to be -1, and any value that is
greater than 0 to be 1. If supplied as 0, no change shall
be made.
Once all fields are within the appropriate ranges, the
implementation shall determine if there is a unique value
of the type returned by time() (which is expressed as a value
in Coordinated Universal Time) which when converted to a
struct tm by a function equivalent to localtime() would
produce identical values for the tm_sec tm_min tm_hour tm_mday
tm_mon and tm_year fields of the converted input struct tm.
This may be accomplished by applying a formula, similar to
that specified for Coordinated Universal Time in <xref XBD 4.17>
adjusted to account for local timezone offsets, and time
alterations, or by any other means.
If such a unique result is found, then that shall be the
result from mktime().
If no result is found because the tm structure represents
a value outside the range of values that can be represented
by a value returned by time(), then an error shall be returned.
Otherwise if no result is able to be found, then the local time
specified represents a time which does not exist as a local time
value. In this case, if the value of tm_isdst in the struct tm
specified by timeptr is greater than or equal to 0, and there
are two values or the type returned by time(), representing times
that are one second apart, (t1 and t2, where t2 == t1 + 1 second)
which can be found of the type returned by time(), such that
one of those, when converted by a function equivalent to
localtime()
returns a time which occurs before the converted time referred to
by timeptr, and the other returns a time which occurs later, and
also one of those would produce a struct tm with tm_isdst == 0,
and the other when converted by localtime would produce a struct
tm
with tm_isdst == 1, then if the application's converted tm_isdst
field the same as that produced by t1, then the implementation
shall calculate the difference, in seconds, between the converted
time specified by timeptr, and that produced by a conversion of
t1,
add the number of seconds to t1, and that shall be the result of
mktime. Otherwise, if the applications converted tm_isdst is
the same as that produced by t2, the implementation shall
calculate the difference (in seconds) between the struct tm
produced by t2, and that specified by the converted struct tm
referred to by timeptr, and subtract that number of seconds from
t2, and that shall be the result from mktime(). In any other
case the result is unspecified. The implementation may
arbitrarily return one of the results as if it had been one of
the two specified cases, or may return an error.
If more than one possible result is found, then if there are
exactly two possible results, and one of those, when converted by
a function equivalent to localtime(), produces a value with
tm_isdst
having the same value as the converted value of that field in the
struct tm referred to by timeptr, and the other does not, then
the result of mktime() shall be the single unique result which
produces a struct tm with the same tm_sec tm_min tm_hour tm_mday
tm_mon tm_year and tm_isdst fields as the converted values in the
struct tm referred to by timeptr. In any other case, the result
is unspecified. The implementation may arbitrarily return any
of the plausible ambiguous results, or may return an error.
This should then be followed by the new (bug 1613 inserted) text about
what happens to the struct tm in the case of a successful return. This
I believe has already replaced the "Upon successful completion, the values
of the tm_wday..." paragraph. If not, delete whatever is left of it.
A new paragraph (or just sentence perhaps) should be added after the 1613
inserted paragraph:
When mktime() returns an error, the contents of the structure
referred to by timeptr, after mktime() returns, shall be
unspecified.
The RETURN VALUE section (lines 44327-9) should be replaced by:
The mktime() function shall return the calculated time since the
epoch, as specified in the DESCRIPTION, encoded as a value of
type time_t. If an error is to be returned, then the function
shall return the value (time_t)-1, and set errno to indicate the
error.
The ERRORS section (lines 44331-2) should be replaced by
The mktime() function shall fail if:
[EOVERFLOW] The value of the time returned by time() which
represents the converted struct tm passed by
timeptr falls outside the range of values that
can be represented as a time_t.
[EOVERFLOW] While correcting the values of the fields of the
struct tm referred to by timeptr to be within the
required ranges, a required adjustment of the
tm_year
field caused that field to overflow.
The mktime() function may fail if:
[EOVERFLOW] Adjusting a field of the struct tm referred to
by timeptr caused an adjustment to be required to
another field, and that adjustment caused that
other
field to overflow.
[EINVAL] The converted struct tm referred to by timeptr
cannot be represented by a unique number of
seconds
past the epoch, Coordinated Universal Time, and
the input values, and/or circumstances are not
such
that an alternative is required to be selected.
In the FUTURE DIRECTIONS section (line 44361) replace "None." by
A later edition of the standard is expected to add a timegm()
function that is similar to mktime(), except that the struct tm
referred to by timeptr represents a calendar time in Coordinated
Universal Time (rather than the local time zone), where references
to localtime() are replaced by references to gmtime(), and where
there are no zone offset adjustments, or missing or ambiguous
times,
tm_isdst is always 0, and EINVAL cannot be returned. A
combination
of gmtime() and timegm() will be the expected way to perform
arithmetic upon a time_t value and remain compatible with the C
standard (where the internal structure of a time_t is not
specified).
Attempting such manipulations using localtime() and mktime() can
lead
to unexpected results.
======================================================================
Issue History
Date Modified Username Field Change
======================================================================
2023-01-05 12:17 kre New Issue
2023-01-05 12:17 kre Name => Robert Elz
2023-01-05 12:17 kre Section => XSH 3/mktime
2023-01-05 12:17 kre Page Number => 1331
2023-01-05 12:17 kre Line Number => 44310-44332, 44361
======================================================================