Velocity Reviews > mktime

# mktime

Heinrich Wolf
Guest
Posts: n/a

 12-07-2012

"Keith Thompson" <(E-Mail Removed)> schrieb im Newsbeitrag
news:(E-Mail Removed)...
> Eric Sosman <(E-Mail Removed)> writes:
> [...]
>> Clearly, the intent is to relieve the programmer of the burden
>> of all that date arithmetic. If you've got a struct tm representing
>> some time T and you want to determine the time at T plus 01:23:45,
>> you can blithely add 1*3600+23*60+45 to the tm_sec field and call
>> mktime(), and mktime() will figure things out.

> [...]
>
> Or you can add 1 to tm_hour, 23 to tm_min, and 45 to tm_sec, and let
> mktime() figure *everything* out.

These may result in different values around a leap second (at the end of
some years) or around the transition from normal time to daylight savings
time or vice versa.

James Kuyper
Guest
Posts: n/a

 12-07-2012
On 12/07/2012 03:48 AM, Heinrich Wolf wrote:
>
> "Keith Thompson" <(E-Mail Removed)> schrieb im Newsbeitrag
> news:(E-Mail Removed)...
>> Eric Sosman <(E-Mail Removed)> writes:
>> [...]
>>> Clearly, the intent is to relieve the programmer of the burden
>>> of all that date arithmetic. If you've got a struct tm representing
>>> some time T and you want to determine the time at T plus 01:23:45,
>>> you can blithely add 1*3600+23*60+45 to the tm_sec field and call
>>> mktime(), and mktime() will figure things out.

>> [...]
>>
>> Or you can add 1 to tm_hour, 23 to tm_min, and 45 to tm_sec, and let
>> mktime() figure *everything* out.

>
> These may result in different values around a leap second (at the end of
> some years) or around the transition from normal time to daylight savings
> time or vice versa.

Yes - and I think the different result you would get in those cases may
often be the one that was actually intended.

--
James Kuyper

James Kuyper
Guest
Posts: n/a

 12-07-2012
On 12/07/2012 12:23 PM, christian.bau wrote:
> followed by adding two days, will get you 28th Feb 2013, then 2nd Mar
> 2013. But adding two days, followed by adding one year, will get you
> 1st Mar 2012, followed by 1st Mar 2013. Not the same.
>
> Are there rules for this kind of thing? Is it unspecified or
> undefined?
>
> What's supposed to happen if you add one month to March 31st 2012? The
> result can't be April 31st; is it April 30th, May 1st, or something
> else?

No, the rules for such things aren't specified, and that lack of
specification is a problem.

given:
#include <time.h>
// Two different ways to describe 2012-03-31:
struct tm a = {.tm_year=2012-1900, .tm_month = 3-1, .tm_day = 31};
struct tm b = {.tm_year=2012-1900, .tm_month = 4-1, .tm_day = 0};
a.tm_month += 1;
mktime(&a);
b.tm_month += 1;
mktime(&b);

My preference would be that this point, 'a' represents 2012-05-01, while
'b' represents 2012-04-30. But the standard doesn't address such questions.

Eric Sosman
Guest
Posts: n/a

 12-07-2012
On 12/7/2012 12:23 PM, christian.bau wrote:
> followed by adding two days, will get you 28th Feb 2013, then 2nd Mar
> 2013. But adding two days, followed by adding one year, will get you
> 1st Mar 2012, followed by 1st Mar 2013. Not the same.

mktime() converts dates, which is not the same as doing
arithmetic on them. You may imagine that you are "adding one
year" to 2012-02-28, and you may even use ...tm_year++ to do
it, but when you pass the struct to mktime() it doesn't know
the history of the tm_year value. All it sees is tm_year = 113
(2013 - 1900), and it says "Here I am in 2013; now let's look
at the other fields." If tm_mday is 30, mktime() will make an
adjustment based on the length of 2013's February; it has no
way of knowing that the struct once held a different tm_year.

> Are there rules for this kind of thing? Is it unspecified or
> undefined?

The only rule I see is 7.27.2.2p2: "[...] the final value
of tm_mday is not set until tm_mon and tm_year are determined,"
which I understand as meaning that day-of-month yields to
month and year rather than the other way around.

> What's supposed to happen if you add one month to March 31st 2012? The
> result can't be April 31st; is it April 30th, May 1st, or something
> else?

Again, mktime() can't know what values formerly occupied the
struct fields before the current ones were stored. All it sees
is that tm_mon = 3 (April) and tm_mday = 31; it knows that 31 is
out of range for April and adjusts to May 1.

You could certainly get strange results by doing "the same"
set of adjustments in sequence, with intervening mktime() calls:

struct tm x = ...; // 2012-02-29
x.tm_mon++; // 2012-03-29
mktime(&x); // 2012-03-29
x.tm_mday++; // 2012-03-30
mktime(&x); // 2012-03-30

struct tm y = ...; // 2012-02-29
y.tm_mday++; // 2012-02-30
mktime(&y); // 2012-03-01 corrected
y.tm_mon++; // 2012-04-01
mktime(&y); // 2012-04-01, April Fool!

Each mktime() call has a well-defined outcome, yet the two
sequences lead to different results -- Well, that's just the
way our creaky old calendar crumbles. Still, the creakiness
shouldn't be too terribly surprising to a C programmer, who's
already sensitive to the fact that Order Matters:

int x = 12 / 5 * 3; // 6
int y = 12 * 3 / 5; // 7

If you're comfortable with the fact that doing "the same"
multiplications and divisions in different orders yields
different outcomes, maybe you can reconcile yourself to the

--
Eric Sosman
http://www.velocityreviews.com/forums/(E-Mail Removed)d

Heinrich Wolf
Guest
Posts: n/a

 12-07-2012

"christian.bau" <(E-Mail Removed)> schrieb im Newsbeitrag
news:(E-Mail Removed)...
> followed by adding two days, will get you 28th Feb 2013, then 2nd Mar
> 2013. But adding two days, followed by adding one year, will get you
> 1st Mar 2012, followed by 1st Mar 2013. Not the same.
>
> Are there rules for this kind of thing? Is it unspecified or
> undefined?
>
> What's supposed to happen if you add one month to March 31st 2012? The
> result can't be April 31st; is it April 30th, May 1st, or something
> else?

I tried with my Borland C++Builder 5 on Windows XP. But that is only one of
many implementations.

2012-2-28 + 1-0-0 = 2013-2-28
2013-2-28 + 0-0-2 = mktime(2013-2-30) = 2013-3-2

2012-2-28 + 0-0-2 = mktime(2012-2-30) = 2012-3-1
2012-3-1 + 1-0-0 = 2013-3-1

2012-2-28 + 1-0-2 = mktime(2013-2-30) = 2013-3-2

2012-3-31 + 0-1-0 = mktime(2012-4-31) = 2012-5-1

Heinrich Wolf
Guest
Posts: n/a

 12-07-2012

"Heinrich Wolf" <(E-Mail Removed)> schrieb im Newsbeitrag
news:k9saf7\$jn\$(E-Mail Removed)-online.net...
....
> These may result in different values around a leap second (at the end of
> some years) or around the transition from normal time to daylight savings
> time or vice versa.

....
My Fedora 14 man page for mktime tells me that leap seconds exist, but is
there anybody out there who has seen them on a system?

My Fedora 14 man page for mktime in German:
....
Die Elemente der Struktur tm sind:

tm_sec Die Anzahl der Sekunden nach der vollen Minute,
normalerweise im Bereich 0 bis 59, jedoch in Ausnahmefällen bis 61 um
Schaltsekunden zu
erlauben.
....
Translated into English:
....
The elements of struct tm are:

tm_sec The number of seconds after the full minute, normally in the
range 0 to 59, but in exceptional cases up to 61 in order to allow leap
seconds.
....

I searched for leap seconds on 2 different systems: Fedora 14 Linux, and
Windows XP with Borland C++Builder 5.
I expected the calculation localtime(mktime(1970-1-2 00-00-00) + 10000 * 24
* 60 * 60 seconds) to result in a tm struct with some odd seconds, but on
both systems I got 1997-05-20 01-00-00

Eric Sosman
Guest
Posts: n/a

 12-07-2012
On 12/7/2012 3:39 PM, Heinrich Wolf wrote:
>
> "Heinrich Wolf" <(E-Mail Removed)> schrieb im Newsbeitrag
> news:k9saf7\$jn\$(E-Mail Removed)-online.net...
> ...
>> These may result in different values around a leap second (at the end
>> of some years) or around the transition from normal time to daylight
>> savings time or vice versa.

> ...
> My Fedora 14 man page for mktime tells me that leap seconds exist, but
> is there anybody out there who has seen them on a system?
>
> My Fedora 14 man page for mktime in German:
> ...
> Die Elemente der Struktur tm sind:
>
> tm_sec Die Anzahl der Sekunden nach der vollen Minute,
> normalerweise im Bereich 0 bis 59, jedoch in Ausnahmefällen bis 61 um
> Schaltsekunden zu
> erlauben.
> ...
> Translated into English:
> ...
> The elements of struct tm are:
>
> tm_sec The number of seconds after the full minute, normally in
> the range 0 to 59, but in exceptional cases up to 61 in order to allow
> leap seconds.
> ...
>
> I searched for leap seconds on 2 different systems: Fedora 14 Linux, and
> Windows XP with Borland C++Builder 5.
> I expected the calculation localtime(mktime(1970-1-2 00-00-00) + 10000 *
> 24 * 60 * 60 seconds) to result in a tm struct with some odd seconds,
> but on both systems I got 1997-05-20 01-00-00

I remember seeing the [0..61] range for tm_sec long ago, and
perhaps it still persists in some non-C documents. Even in the
original 1989 ANSI C Standard, though, the range was [0..60] in
keeping with the fact that leap seconds are added (or removed)
one at a time, never two at once.

As to the calculation -- well, timekeeping is complicated.
POSIX *defines* the day as 86400 seconds, intentionally ignoring
leap seconds (see <http://en.wikipedia.org/wiki/Unix_time>), so
we should not expect a C implementation based on POSIX time to
generate tm_sec==60. The C Standard does not require the library
to overcome this problem:

7.27.1p4: "The range and precision of times representable
in clock_t and time_t are implementation-defined."

7.27.2.4p3: "The time function returns the implementation’s
*best approximation* [emphasis mine] to the current calendar
time."

Timekeeping and time calculation on POSIX and POSIX-like systems
is "close enough for jazz," sufficiently accurate for many purposes
but not astonishingly good. C implementations don't have to do any
better (and on a strictly-conforming POSIX system, I guess they're
actually forbidden to do better).

--
Eric Sosman
(E-Mail Removed)d

Alan Curry
Guest
Posts: n/a

 12-07-2012
In article <k9tk5a\$caq\$(E-Mail Removed)-online.net>,
Heinrich Wolf <(E-Mail Removed)> wrote:
>
>I searched for leap seconds on 2 different systems: Fedora 14 Linux, and
>Windows XP with Borland C++Builder 5.
>I expected the calculation localtime(mktime(1970-1-2 00-00-00) + 10000 * 24
>* 60 * 60 seconds) to result in a tm struct with some odd seconds, but on
>both systems I got 1997-05-20 01-00-00
>

On unix-like systems the time_t has two interpretations, "posix" and "right".
The posix time_t leaps with the leap seconds, making them mostly invisible
unless your program is running during a leap. Adding 86400 to a posix time_t
always gets you the same time the next day (plus or minus an hour for
daylight savings).

With the posix time_t, mktime covers up evidence of leap seconds especially
well. You can set a struct tm to out-of-range 23:59:60 on a non-leap-second
day and mktime will normalize it to 0:00:00 the next day and return the exact
same time_t it would have returned for the legitimate 23:59:60 if the day had
been a leap second day.

If you indicate with the TZ environment variable that you want the "right"
time, then mktime and localtime will adjust their behavior and the expected
odd seconds will show up.

Here's a program demonstrating the differences:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>

static void showtm(int year, int mon, int day, int hour, int min, int sec)
{
time_t t;
struct tm tm = {
.tm_year = year-1900,
.tm_mon = mon-1,
.tm_mday = day,
.tm_hour = hour,
.tm_min = min,
.tm_sec = sec
};
t = mktime(&tm);
printf("%d-%d-%d %d:%02d:%02d = %ju\n",
year, mon, day, hour, min, sec, (uintmax_t)t);
}

int main(void)
{
time_t now;
struct tm *nowtm;
time_t tenyearsago;
struct tm tenyearsagotm;
const char *tzlist[] = { "posix/Etc/GMT", "right/Etc/GMT" };
int i;

for(i=0;i<2;++i) {
printf("%sTZ=%s\n", i?"\n":"", tzlist[i]);
setenv("TZ", tzlist[i], 1);
tzset();

now = time(0);
nowtm = localtime(&now);

tenyearsagotm = *nowtm;
tenyearsagotm.tm_year -= 12;
tenyearsago = mktime(&tenyearsagotm);

printf("Days between now and this time 12 years ago: %.10f\n",
(now-tenyearsago)/86400.);

printf("This is a leap second:\n");
showtm(2012, 6, 30, 23, 59, 59);
showtm(2012, 6, 30, 23, 59, 60);
showtm(2012, 7, 1, 0, 0, 0);
printf("This is not a leap second:\n");
showtm(2011, 6, 30, 23, 59, 59);
showtm(2011, 6, 30, 23, 59, 60);
showtm(2011, 7, 1, 0, 0, 0);
}

return 0;
}

--
Alan Curry

Keith Thompson
Guest
Posts: n/a

 12-08-2012
Eric Sosman <(E-Mail Removed)> writes:
> On 12/7/2012 3:39 PM, Heinrich Wolf wrote:

[...]
>> My Fedora 14 man page for mktime in German:
>> ...
>> Die Elemente der Struktur tm sind:
>>
>> tm_sec Die Anzahl der Sekunden nach der vollen Minute,
>> normalerweise im Bereich 0 bis 59, jedoch in AusnahmefÃ¤llen bis 61 um
>> Schaltsekunden zu
>> erlauben.
>> ...
>> Translated into English:
>> ...
>> The elements of struct tm are:
>>
>> tm_sec The number of seconds after the full minute, normally in
>> the range 0 to 59, but in exceptional cases up to 61 in order to allow
>> leap seconds.
>> ...
>>
>> I searched for leap seconds on 2 different systems: Fedora 14 Linux, and
>> Windows XP with Borland C++Builder 5.
>> I expected the calculation localtime(mktime(1970-1-2 00-00-00) + 10000 *
>> 24 * 60 * 60 seconds) to result in a tm struct with some odd seconds,
>> but on both systems I got 1997-05-20 01-00-00

>
> I remember seeing the [0..61] range for tm_sec long ago, and
> perhaps it still persists in some non-C documents. Even in the
> original 1989 ANSI C Standard, though, the range was [0..60] in
> keeping with the fact that leap seconds are added (or removed)
> one at a time, never two at once.

The 1990 ISO C standard specifies a range of [0, 61] for tm_sec:

int tm_sec; /* seconds after the minute -- [0, 61] */

with a footnote:

The range [0,61] for tm_sec allows for as many as two leap seconds.

C99 changes the range to [0, 60] and changes the footnote to:

The range [0, 60] for tm_sec allows for a positive leap second.

There's no mention of the change in the C99 rationale.

I've heard (not sure where) that the [0, 61] range in C90 was
the result of a misunderstanding; if it were necessary to have
two leap seconds in a year (which hasn't happened so far) they'd
be added at the end of June and the end of December. I *guess*
that if more than two were required, they'd be spread more or less
evenly through the year.

Are you *sure* that the 1989 ANSI standard specifies [0, 60]?
If so, that would be an inconsistency between ANSI C89 and ISO C90,
and I didn't think there were any.

Interestingly, a 1988 ANSI C draft ("ansi.c.txt", I'm not sure
where I got it) shows a range of [0, 60].

[...]

--
Keith Thompson (The_Other_Keith) (E-Mail Removed) <http://www.ghoti.net/~kst>
Will write code for food.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Heinrich Wolf
Guest
Posts: n/a

 12-08-2012
Thanks a lot for the code.

With posix TZ the days between 12 years is an integer.
With right TZ the days between 12 years have a fractional part.

showtm gives different time_t values for posix TZ and right TZ.

However the time_t is always the same
for 6-30 23:59:60 and 7-1 0:00:00
regardless of the year (leap second or not).
Maybe my Fedora 14 is too old and does not know about the leap second
on 2012-6-30?