Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > comp.lang.c > #93682 > unrolled thread
| Started by | Robert Latest <boblatest@yahoo.com> |
|---|---|
| First post | 2016-09-17 17:35 +0000 |
| Last post | 2016-09-17 17:04 -0700 |
| Articles | 13 — 6 participants |
Back to article view | Back to comp.lang.c
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
| From | Robert Latest <boblatest@yahoo.com> |
|---|---|
| Date | 2016-09-17 17:35 +0000 |
| Subject | time 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]
| From | Robert Latest <boblatest@yahoo.com> |
|---|---|
| Date | 2016-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]
| From | "James R. Kuyper" <jameskuyper@verizon.net> |
|---|---|
| Date | 2016-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]
| From | Robert Latest <boblatest@yahoo.com> |
|---|---|
| Date | 2016-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]
| From | "James R. Kuyper" <jameskuyper@verizon.net> |
|---|---|
| Date | 2016-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]
| From | Keith Thompson <kst-u@mib.org> |
|---|---|
| Date | 2016-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]
| From | "James R. Kuyper" <jameskuyper@verizon.net> |
|---|---|
| Date | 2016-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]
| From | Ian Collins <ian-news@hotmail.com> |
|---|---|
| Date | 2016-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]
| From | Keith Thompson <kst-u@mib.org> |
|---|---|
| Date | 2016-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]
| From | Robert Latest <boblatest@yahoo.com> |
|---|---|
| Date | 2016-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]
| From | Ben Bacarisse <ben.usenet@bsb.me.uk> |
|---|---|
| Date | 2016-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]
| From | supercat@casperkitty.com |
|---|---|
| Date | 2016-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]
| From | Keith Thompson <kst-u@mib.org> |
|---|---|
| Date | 2016-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