Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.c > #93682 > unrolled thread

time functions weirdness

Started byRobert Latest <boblatest@yahoo.com>
First post2016-09-17 17:35 +0000
Last post2016-09-17 17:04 -0700
Articles 13 — 6 participants

Back to article view | Back to comp.lang.c


Contents

  time functions weirdness Robert Latest <boblatest@yahoo.com> - 2016-09-17 17:35 +0000
    Re: time functions weirdness Robert Latest <boblatest@yahoo.com> - 2016-09-17 17:38 +0000
    Re: time functions weirdness "James R. Kuyper" <jameskuyper@verizon.net> - 2016-09-17 14:26 -0400
      Re: time functions weirdness Robert Latest <boblatest@yahoo.com> - 2016-09-17 20:26 +0000
        Re: time functions weirdness "James R. Kuyper" <jameskuyper@verizon.net> - 2016-09-17 17:21 -0400
          Re: time functions weirdness Keith Thompson <kst-u@mib.org> - 2016-09-17 16:54 -0700
            Re: time functions weirdness "James R. Kuyper" <jameskuyper@verizon.net> - 2016-09-17 18:00 -0400
            Re: time functions weirdness Ian Collins <ian-news@hotmail.com> - 2016-09-18 10:45 +1200
        Re: time functions weirdness Keith Thompson <kst-u@mib.org> - 2016-09-17 17:24 -0700
          Re: time functions weirdness Robert Latest <boblatest@yahoo.com> - 2016-09-18 06:25 +0000
            Re: time functions weirdness Ben Bacarisse <ben.usenet@bsb.me.uk> - 2016-09-18 20:40 +0100
              Re: time functions weirdness supercat@casperkitty.com - 2016-09-18 16:00 -0700
    Re: time functions weirdness Keith Thompson <kst-u@mib.org> - 2016-09-17 17:04 -0700

#93682 — time functions weirdness

FromRobert Latest <boblatest@yahoo.com>
Date2016-09-17 17:35 +0000
Subjecttime functions weirdness
Message-ID<e45d70Fk5lU1@mid.individual.net>
Hello,

the task is simple: Convert a time string to a time_t and back. I'm not even
concerned with timezones, I want everything to be in UTC. But for some
reason it just doesn't work and I don't know why. Is it possible that
mktime()'s and gmtime()'s notions of the UNIX epoch differ by one hour?

My questions in detail:
1. Why 


dh@dotcom:~/c/gpx$ gcc -W -Wall timetest.c && ./a.out 
1. Original timestr: 1970-01-01T00:00:00Z
2. After mktime():   1970-01-01T00:00:00Z
   UNIX timestamp:   -3600
3. gmtime() =        1969-12-31T23:00:00Z
dh@dotcom:~/c/gpx$ gcc -W -Wall timetest.c && ./a.out 
1. Original timestr: 1970-08-01T00:00:00Z
2. After mktime():   1970-08-01T00:00:00Z
   UNIX timestamp:   18313200
3. gmtime() =        1970-07-31T23:00:00Z
dh@dotcom:~/c/gpx$ gcc -W -Wall timetest.c && ./a.out 
1. Original timestr: 2016-08-01T00:00:00Z
2. After mktime():   2016-08-01T01:00:00Z
   UNIX timestamp:   1470006000
3. gmtime() =        2016-07-31T23:00:00Z
dh@dotcom:~/c/gpx$

#define _XOPEN_SOURCE /* for strftime */

#include <time.h>
#include <stdio.h>
#include <locale.h>

#define FORMAT "%Y-%m-%dT%H:%M:%SZ"

int main(void) {
    struct tm tm = {0};
    time_t t;
    char buf[100];
    char *orig_time = "2016-08-01T00:00:00Z";

    setlocale(LC_ALL, "C"); /* doesn't make a difference */

    strptime(orig_time, FORMAT, &tm);
    printf("1. Original timestr: %s\n", orig_time);

    tm.tm_isdst = 0; /* doesn't matter if I set this to 1
    or to 0, or if I set it before or after mktime() */
    t = mktime(&tm);

    strftime(buf, sizeof buf, FORMAT, &tm);
    printf("2. After mktime():   %s\n", buf);
    printf("   UNIX timestamp:   %d\n", (int) t);

    strftime(buf, sizeof buf, FORMAT, gmtime(&t));
    printf("3. gmtime() =        %s\n", buf);

    return 0;
}

[toc] | [next] | [standalone]


#93683

FromRobert Latest <boblatest@yahoo.com>
Date2016-09-17 17:38 +0000
Message-ID<e45dcaFk5lU2@mid.individual.net>
In reply to#93682
Robert Latest wrote:

[...]

> My questions in detail:
> 1. Why 


OK, here I got interrupted. Buy anyway, my general question is more open:
How do I reliably and portably convert between an UTC text timestamp and
time_t?

Thanks,
robert

[toc] | [prev] | [next] | [standalone]


#93685

From"James R. Kuyper" <jameskuyper@verizon.net>
Date2016-09-17 14:26 -0400
Message-ID<47758107-38f4-9571-76db-1e5991639649@verizon.net>
In reply to#93682
On 09/17/2016 01:35 PM, Robert Latest wrote:
> Hello,
>
> the task is simple: Convert a time string to a time_t and back. I'm not even
> concerned with timezones, I want everything to be in UTC. But for some
> reason it just doesn't work and I don't know why. Is it possible that
> mktime()'s and gmtime()'s notions of the UNIX epoch differ by one hour?
>
> My questions in detail:
> 1. Why
>
>
> dh@dotcom:~/c/gpx$ gcc -W -Wall timetest.c && ./a.out
> 1. Original timestr: 1970-01-01T00:00:00Z
> 2. After mktime():   1970-01-01T00:00:00Z
>    UNIX timestamp:   -3600
> 3. gmtime() =        1969-12-31T23:00:00Z
> dh@dotcom:~/c/gpx$ gcc -W -Wall timetest.c && ./a.out
> 1. Original timestr: 1970-08-01T00:00:00Z
> 2. After mktime():   1970-08-01T00:00:00Z
>    UNIX timestamp:   18313200
> 3. gmtime() =        1970-07-31T23:00:00Z
> dh@dotcom:~/c/gpx$ gcc -W -Wall timetest.c && ./a.out
> 1. Original timestr: 2016-08-01T00:00:00Z
> 2. After mktime():   2016-08-01T01:00:00Z
>    UNIX timestamp:   1470006000
> 3. gmtime() =        2016-07-31T23:00:00Z
> dh@dotcom:~/c/gpx$
>
> #define _XOPEN_SOURCE /* for strftime */
>
> #include <time.h>
> #include <stdio.h>
> #include <locale.h>
>
> #define FORMAT "%Y-%m-%dT%H:%M:%SZ"
>
> int main(void) {
>     struct tm tm = {0};
>     time_t t;
>     char buf[100];
>     char *orig_time = "2016-08-01T00:00:00Z";
>
>     setlocale(LC_ALL, "C"); /* doesn't make a difference */
>
>     strptime(orig_time, FORMAT, &tm);

strptime() is UNIX specific, but you'd run into similar problems if you 
used purely portable C code to parse the time string and load it into 
struct tm.
Note that while your time string contains a time zone specifier (Z => 
UTC), strptime() doesn't know anything about time zones. struct tm 
doesn't have a time zone field. The time string is therefore interpreted 
as local time, and tm is filled in accordingly.

"The mktime function converts the broken-down time, expressed as local 
time, in the structure pointed to by timeptr into a calendar time value 
with the same encoding as that of the values returned by the time 
function." (7.27.2.3p2)

"The local time zone and Daylight Saving Time are 
implementation-defined." (7.27.1p1)

In UNIX you can set the time zone as follows:

     if(setenv("TZ", "GMT0", 1))
     {
         // Error Handling
     }

>     printf("1. Original timestr: %s\n", orig_time);
>
>     tm.tm_isdst = 0; /* doesn't matter if I set this to 1
>     or to 0, or if I set it before or after mktime() */
>     t = mktime(&tm);
>
>     strftime(buf, sizeof buf, FORMAT, &tm);

If you use the %Z format specifier for your strftime calls, you'll see 
what time zone it's actually using, including daylight savings time. 
However, that specifier is not recognized by strptime(), so you couldn't 
use the same format string for both calls.

>     printf("2. After mktime():   %s\n", buf);
>     printf("   UNIX timestamp:   %d\n", (int) t);
>
>     strftime(buf, sizeof buf, FORMAT, gmtime(&t));
>     printf("3. gmtime() =        %s\n", buf);
>
>     return 0;
> }

[toc] | [prev] | [next] | [standalone]


#93693

FromRobert Latest <boblatest@yahoo.com>
Date2016-09-17 20:26 +0000
Message-ID<e45n6uF2va9U1@mid.individual.net>
In reply to#93685
James R. Kuyper wrote:
> strptime() is UNIX specific, but you'd run into similar problems if you 
> used purely portable C code to parse the time string and load it into 
> struct tm.
> Note that while your time string contains a time zone specifier (Z => 
> UTC), strptime() doesn't know anything about time zones. struct tm 
> doesn't have a time zone field. The time string is therefore interpreted 
> as local time, and tm is filled in accordingly.
>

Hi James, thanks for the clarification. I've come up with the following code
(that compiles cleanly with gcc -W -Wall -ansi -pedantic) in which I first
determine the beliefs of mktime() and use that to correct the timestamp of
the parsed struct tm.

BTW, what where the early C'ers smoking when they came up with the idea of
making the tm_mon field zero-based. Of course real coders always start
counting at zero, but then why does tm_mday start at 1?

Here's the code. It works with all dates that I've tested. Would this be
portable on any conforming implementation?

#include <time.h>
#include <stdio.h>

#define FORMAT "%Y-%m-%dT%H:%M:%SZ"

int isolocal2ts(const char *s, time_t *t) {
    struct tm tm = {0};
    int year, month;
    if (6 == sscanf(s, "%d-%d-%dT%d:%d:%dZ",
            &year, &month, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec)) {
        tm.tm_year = year-1900;
        tm.tm_mon = month-1;
        *t = mktime(&tm);
        return 0;
    } else {
        return -1;
    }
}

time_t ts_offset(void) {
    struct tm o = {0};

    o.tm_sec = 0; o.tm_min = 0; o.tm_hour = 0;
    o.tm_mday = 1; o.tm_mon = 0; o.tm_year = 70;
    return mktime(&o);
}

int main(void) {
    time_t t, t_offset;
    char buf[100];
    char *orig_time = "2016-08-01T00:00:00Z";

    t_offset = ts_offset();
    if (0 != isolocal2ts(orig_time, &t)) {
        fprintf(stderr, "Error during time conversion\n");
        return 1;
    }
    t -= t_offset;

    printf("Original timestr: %s\n", orig_time);

    strftime(buf, sizeof buf, FORMAT, gmtime(&t));
    printf("Converted back:   %s\n", buf);

    return 0;
}

-- 
robert

[toc] | [prev] | [next] | [standalone]


#93694

From"James R. Kuyper" <jameskuyper@verizon.net>
Date2016-09-17 17:21 -0400
Message-ID<723258e8-5b49-0399-f34f-c4303ab43584@verizon.net>
In reply to#93693
On 09/17/2016 04:26 PM, Robert Latest wrote:
> James R. Kuyper wrote:
>> strptime() is UNIX specific, but you'd run into similar problems if you
>> used purely portable C code to parse the time string and load it into
>> struct tm.
>> Note that while your time string contains a time zone specifier (Z =>
>> UTC), strptime() doesn't know anything about time zones. struct tm
>> doesn't have a time zone field. The time string is therefore interpreted
>> as local time, and tm is filled in accordingly.
>>
>
> Hi James, thanks for the clarification. I've come up with the following code
> (that compiles cleanly with gcc -W -Wall -ansi -pedantic) in which I first
> determine the beliefs of mktime() and use that to correct the timestamp of
> the parsed struct tm.
>
> BTW, what where the early C'ers smoking when they came up with the idea of
> making the tm_mon field zero-based. Of course real coders always start
> counting at zero, but then why does tm_mday start at 1?
>
> Here's the code. It works with all dates that I've tested. Would this be
> portable on any conforming implementation?

No.

> #include <time.h>
> #include <stdio.h>
>
> #define FORMAT "%Y-%m-%dT%H:%M:%SZ"
>
> int isolocal2ts(const char *s, time_t *t) {
>     struct tm tm = {0};
>     int year, month;
>     if (6 == sscanf(s, "%d-%d-%dT%d:%d:%dZ",
>             &year, &month, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec)) {
>         tm.tm_year = year-1900;
>         tm.tm_mon = month-1;
>         *t = mktime(&tm);
>         return 0;
>     } else {
>         return -1;
>     }
> }
>
> time_t ts_offset(void) {
>     struct tm o = {0};
>
>     o.tm_sec = 0; o.tm_min = 0; o.tm_hour = 0;
>     o.tm_mday = 1; o.tm_mon = 0; o.tm_year = 70;
>     return mktime(&o);
> }
> int main(void) {
>     time_t t, t_offset;
>     char buf[100];
>     char *orig_time = "2016-08-01T00:00:00Z";
>
>     t_offset = ts_offset();
>     if (0 != isolocal2ts(orig_time, &t)) {
>         fprintf(stderr, "Error during time conversion\n");
>         return 1;
>     }
>     t -= t_offset;

Here's 100% of what the C standard guarantees about time_t: it is a 
"type capable of representing times" (7.27.1p3). In particular, it does 
not say anything about HOW it represents time. In particular, it does 
not guarantee that it is even an arithmetic type, in which case 
subtraction would not even be permitted. It doesn't say anything about 
the result from a subtraction of two time_t values would be.

In UNIX time_t is an integer type, and time_t values are interpreted as 
the number of seconds of local since 1970-01-01T00:00:00Z, so your code 
will probably work on UNIX machines, but not necessarily anywhere else.

>     printf("Original timestr: %s\n", orig_time);
>
>     strftime(buf, sizeof buf, FORMAT, gmtime(&t));
>     printf("Converted back:   %s\n", buf);
>
>     return 0;
> }

[toc] | [prev] | [next] | [standalone]


#93698

FromKeith Thompson <kst-u@mib.org>
Date2016-09-17 16:54 -0700
Message-ID<ln37kyytdc.fsf@kst-u.example.com>
In reply to#93694
"James R. Kuyper" <jameskuyper@verizon.net> writes:
[...]
> Here's 100% of what the C standard guarantees about time_t: it is a 
> "type capable of representing times" (7.27.1p3). In particular, it does 
> not say anything about HOW it represents time. In particular, it does 
> not guarantee that it is even an arithmetic type, in which case 
> subtraction would not even be permitted. It doesn't say anything about 
> the result from a subtraction of two time_t values would be.

C11 requires time_t to be a real type, meaning it's either an
integer type or a non-complex floating-point type.

C90 and C99 both required it to be an arithmetic type.  (As far as
I know no C99 implementation took advantage of the permission to
make it a complex type.)

Everything else you said is correct; subtraction is defined, but
it needn't be meaningful.  For example, an implementation could
plausibly use an integer type with bit sequences used to represent
year, month, day, etc., or it could represent the current time
(Sat 2016-09-17 21:37:28 UTC) as the integer value 20160917213728.

[...]

[Speaking of times, the clock on the system I'm posting this from is
currently about two hours fast, which affects the timestamps of my
articles.  I'm doing what I can about it, which is close to nothing.]

-- 
Keith Thompson (The_Other_Keith) kst-u@mib.org  <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something.  This is something.  Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"

[toc] | [prev] | [next] | [standalone]


#93704

From"James R. Kuyper" <jameskuyper@verizon.net>
Date2016-09-17 18:00 -0400
Message-ID<c8e246aa-1210-abba-967a-32a427233ccc@verizon.net>
In reply to#93698
On 09/17/2016 07:54 PM, Keith Thompson wrote:
> "James R. Kuyper" <jameskuyper@verizon.net> writes:
> [...]
>> Here's 100% of what the C standard guarantees about time_t: it is a
>> "type capable of representing times" (7.27.1p3). In particular, it does
>> not say anything about HOW it represents time. In particular, it does
>> not guarantee that it is even an arithmetic type, in which case
>> subtraction would not even be permitted. It doesn't say anything about
>> the result from a subtraction of two time_t values would be.
>
> C11 requires time_t to be a real type, meaning it's either an
> integer type or a non-complex floating-point type.

I dropped one word from my quotation - but it was one of the most 
important ones: "real". I need some more sleep.

[toc] | [prev] | [next] | [standalone]


#93709

FromIan Collins <ian-news@hotmail.com>
Date2016-09-18 10:45 +1200
Message-ID<e45vcdFdjj5U1@mid.individual.net>
In reply to#93698
On 09/18/16 11:54 AM, Keith Thompson wrote:
>
> [Speaking of times, the clock on the system I'm posting this from is
> currently about two hours fast, which affects the timestamps of my
> articles.  I'm doing what I can about it, which is close to nothing.]

NTP.

If you system is too old for NTP, open the glass and move the hands back..

-- 
Ian

[toc] | [prev] | [next] | [standalone]


#93706

FromKeith Thompson <kst-u@mib.org>
Date2016-09-17 17:24 -0700
Message-ID<lnpoo2xdeu.fsf@kst-u.example.com>
In reply to#93693
Robert Latest <boblatest@yahoo.com> writes:
[...]
> BTW, what where the early C'ers smoking when they came up with the idea of
> making the tm_mon field zero-based. Of course real coders always start
> counting at zero, but then why does tm_mday start at 1?
[...]

For human-readable display, months are very commonly represented
as strings ("Jan" or "January", etc., or the equivalent in the
local language).  The obvious way to do that is to use the month
number as an index into an array:

    const char *const month_name = { "Jan", "Feb", ... };

It was probably considered cleaner not to give the array an ignored
zeroth element.

Days of the month do not have names (at least in the cultures
considered at the time), so there's no need to use them as indices.
If they did, tm_mday would likely have been given a range of 0..30
rather than 1..31.

I do think it would have been cleaner to give tm_mon a range of
1..12 rather than 0..11, but the decision that was made was fairly
reasonable, and we're stuck with it.  Offsetting tm_year by 1900 was
less defensible, but when the interface was developed, probably in
the 1970s, it was common to use just the last 2 digits of the year.

-- 
Keith Thompson (The_Other_Keith) kst-u@mib.org  <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something.  This is something.  Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"

[toc] | [prev] | [next] | [standalone]


#93724

FromRobert Latest <boblatest@yahoo.com>
Date2016-09-18 06:25 +0000
Message-ID<e46qaqFbdu0U1@mid.individual.net>
In reply to#93706
Keith Thompson wrote:
> Robert Latest <boblatest@yahoo.com> writes:
> [...]
>> BTW, what where the early C'ers smoking when they came up with the idea of
>> making the tm_mon field zero-based. Of course real coders always start
>> counting at zero, but then why does tm_mday start at 1?
> [...]
>
> For human-readable display, months are very commonly represented
> as strings ("Jan" or "January", etc., or the equivalent in the
> local language).  The obvious way to do that is to use the month
> number as an index into an array:
>
>     const char *const month_name = { "Jan", "Feb", ... };
>
> It was probably considered cleaner not to give the array an ignored
> zeroth element.

That's an excellent thought! I had really been puzzled about this for a long
time because this is an apparent inconsistency that must have been designed
into the struct tm type on the same day by the same person (unlike other
inconsistencies that come from different philosophies held by different
people at different times). It sounds really plausible, so I'll stick with it.

Best regards,
robert

[toc] | [prev] | [next] | [standalone]


#93748

FromBen Bacarisse <ben.usenet@bsb.me.uk>
Date2016-09-18 20:40 +0100
Message-ID<87eg4hj8tr.fsf@bsb.me.uk>
In reply to#93724
Robert Latest <boblatest@yahoo.com> writes:

> Keith Thompson wrote:
>> Robert Latest <boblatest@yahoo.com> writes:
>> [...]
>>> BTW, what where the early C'ers smoking when they came up with the idea of
>>> making the tm_mon field zero-based. Of course real coders always start
>>> counting at zero, but then why does tm_mday start at 1?
>> [...]
>>
>> For human-readable display, months are very commonly represented
>> as strings ("Jan" or "January", etc., or the equivalent in the
>> local language).  The obvious way to do that is to use the month
>> number as an index into an array:
>>
>>     const char *const month_name = { "Jan", "Feb", ... };
>>
>> It was probably considered cleaner not to give the array an ignored
>> zeroth element.
>
> That's an excellent thought! I had really been puzzled about this for a long
> time because this is an apparent inconsistency that must have been designed
> into the struct tm type on the same day by the same person (unlike other
> inconsistencies that come from different philosophies held by different
> people at different times). It sounds really plausible, so I'll stick
> with it.

Note that tm_wday (the week day that would often also have to be mapped
to strings) is also zero based.

-- 
Ben.

[toc] | [prev] | [next] | [standalone]


#93766

Fromsupercat@casperkitty.com
Date2016-09-18 16:00 -0700
Message-ID<1f620281-fc4d-4134-862c-e40d4247d603@googlegroups.com>
In reply to#93748
On Sunday, September 18, 2016 at 2:40:09 PM UTC-5, Ben Bacarisse wrote:
> Note that tm_wday (the week day that would often also have to be mapped
> to strings) is also zero based.

The advantage there is obvious: to reconcile the opinion of those who
think Sunday should be the first day of the week with those who think that
Monday should be day #1.  >:*3

[toc] | [prev] | [next] | [standalone]


#93700

FromKeith Thompson <kst-u@mib.org>
Date2016-09-17 17:04 -0700
Message-ID<lny42qxedp.fsf@kst-u.example.com>
In reply to#93682
Robert Latest <boblatest@yahoo.com> writes:
> the task is simple: Convert a time string to a time_t and back. I'm not even
> concerned with timezones, I want everything to be in UTC. But for some
> reason it just doesn't work and I don't know why. Is it possible that
> mktime()'s and gmtime()'s notions of the UNIX epoch differ by one hour?
[...]

localtime() converts a time_t value to a struct tm value representing
a local time.  gmtime() converts a time_t value to a struct tm
value representing a UTC time.

mktime() is essentially the inverse of localtime(); it converts
a struct tm value representing a local time to a time_t value.
There is no inverse of the gmtime() function.

This is IMHO quite unfortunate, and I don't know why something like a
mktime_utc() function (perhaps with a better name) was never added
to the standard.

-- 
Keith Thompson (The_Other_Keith) kst-u@mib.org  <http://www.ghoti.net/~kst>
Working, but not speaking, for JetHead Development, Inc.
"We must do something.  This is something.  Therefore, we must do this."
    -- Antony Jay and Jonathan Lynn, "Yes Minister"

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.c


csiph-web