Date Arithmetic

  • Thread starter Michael B Allen
  • Start date
M

Michael B Allen

Are there any localized facilites for performing date arithmetic that
properly considers leap years, daylight savings time, and whatever
other peculiarities of the gregorian lunar calendar?

Specifically, considering the following program and it's output below
can someone provide a suitable body for the time_subtract function?

Mike

--8<-- program

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

time_t *
time_subtract(time_t *timep, struct tm *adj)
{
/* ???? */
}

int
main(int argc, char *argv[])
{
struct tm adj;
time_t now;
time_t *time3hoursago;

memset(&adj, 0, sizeof adj);
adj.tm_hour = 3; /* 3 hours, not 3rd hour */
now = time(NULL);
time3hoursago = time_subtract(&now, &adj);
puts(ctime(time3hoursago));

return EXIT_SUCCESS;
}

--8<-- output

$ date
Sat Nov 1 02:36:09 EST 2003
$ ./time_sub
Fri Oct 31 23:36:09 2003
 
M

Mike Wahler

Michael B Allen said:
Are there any localized facilites for performing date arithmetic that
properly considers leap years, daylight savings time, and whatever
other peculiarities of the gregorian lunar calendar?

Specifically, considering the following program and it's output below
can someone provide a suitable body for the time_subtract function?

Mike

--8<-- program

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

time_t *
time_subtract(time_t *timep, struct tm *adj)
{
/* ???? */
time_t t = mktime(adj);
return difftime(*timep, t)
}

int
main(int argc, char *argv[])
{
struct tm adj;
time_t now;
time_t *time3hoursago;

memset(&adj, 0, sizeof adj);
adj.tm_hour = 3; /* 3 hours, not 3rd hour */
now = time(NULL);
time3hoursago = time_subtract(&now, &adj);
puts(ctime(time3hoursago));

return EXIT_SUCCESS;
}

--8<-- output

$ date
Sat Nov 1 02:36:09 EST 2003
$ ./time_sub
Fri Oct 31 23:36:09 2003

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

time_t offset_hours(const time_t *t, int hours)
{
struct tm *cal = localtime(t);
cal->tm_hour += hours;
return mktime(cal);
}

int main()
{
time_t now;
time_t other;
int offset = -3;

now = time(NULL);
other = offset_hours(&now, offset);

printf("%s", asctime(localtime(&now)));
printf(" offset by %d hours is\n", offset);
printf("%s", asctime(localtime(&other)));

return 0;
}


-Mike
 
M

Mark McIntyre

Are there any localized facilites for performing date arithmetic that
properly considers leap years, daylight savings time, and whatever
other peculiarities of the gregorian lunar calendar?

No, but they're easy to write. For instance since we know that the
year 1900 is a leap-year, we can trivially determine the number of
days between any two dates stored in a struct tm. Extending this to
larger ranges is also relatively simple, provider you don't want to go
back before the Great Calendar Change, or past the next one. If you
want to consider leap-seconds its much more fiddly.
Specifically, considering the following program and it's output below
can someone provide a suitable body for the time_subtract function?

You can probably just subtract the various members of a struct tm from
each other. Many implementations of struct tm renormalise the elements
after arithmetic.
 
M

Michael B Allen

time_t offset_hours(const time_t *t, int hours) {
struct tm *cal = localtime(t);
cal->tm_hour += hours;
return mktime(cal);
}

Well this is sort of what I want but more generally like this:

time_t
time_offset(const time *t, struct tm *adj)
{
unsigned long secs = (unsigned long)*t; /* ? */

secs += adj->tm_sec;
secs += adj->tm_min * 60;
secs += adj->tm_hour * 60 * 60;
secs += adj->tm_mday * 24 * 60 * 60;
secs += adj->tm_mon * 30 * 24 * 60 * 60;
secs += adj->tm_year * 12 * 30 * 24 * 60 * 60;

return secs;
}

but with proper consideration for the number of days in a month, leap
seconds, DST, etc...
 
A

Alex Fraser

Mark McIntyre said:
No, but they're easy to write.

Daylight savings time adds a whole heap of difficulty.
For instance since we know that the year 1900 is a leap-year,

Err, no it isn't (or rather, wasn't).
we can trivially determine the number of days between any two dates
stored in a struct tm.

Dates aren't much of a problem. Times are.
You can probably just subtract the various members of a struct tm from
each other. Many implementations of struct tm renormalise the elements
after arithmetic.

I believe this is required of mktime(), but not of any of the other related
functions. Unless I'm missing something (and if I am, I hope someone will
point it out), this means there is no portable method of calculating a new
GMT time from a GMT time and an offset.

Alex
 
A

Alex Fraser

Michael B Allen said:
Well this is sort of what I want but more generally like this:

time_t
time_offset(const time *t, struct tm *adj)
^^^^ ITYM time_t
{
unsigned long secs = (unsigned long)*t; /* ? */

It is not guaranteed that time_t holds a number of seconds (although it
often does), so this doesn't quite cut it.
secs += adj->tm_sec;
secs += adj->tm_min * 60;
secs += adj->tm_hour * 60 * 60;
secs += adj->tm_mday * 24 * 60 * 60;
secs += adj->tm_mon * 30 * 24 * 60 * 60;
secs += adj->tm_year * 12 * 30 * 24 * 60 * 60;

return secs;
}

but with proper consideration for the number of days in a month, leap
seconds, DST, etc...

AIUI, leap seconds are basically impossible to handle (because they are
added as required, not on a regular basis - a quick web search seemed to
confirm this). But the two implementations I tested seemed to correctly
handle the other aspects:

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

time_t time_offset(const time_t *base, const struct tm *off) {
struct tm *t;

t = localtime(base);
if (!t) return (time_t)(-1);

t->tm_sec += off->tm_sec;
t->tm_min += off->tm_min;
t->tm_hour += off->tm_hour;
t->tm_mday += off->tm_mday;
t->tm_mon += off->tm_mon;
t->tm_year += off->tm_year;

return mktime(t);
}

int main(void) {
time_t now = time(0);
struct tm off = {0};
time_t new;

printf("local time now: %s", ctime(&now));

off.tm_hour = -23;
new = time_offset(&now, &off);
printf("local time 23 hours ago: %s", ctime(&new));

off.tm_hour = 0;
off.tm_mday = -20;
new = time_offset(&now, &off);
printf("local time 20 days ago: %s", ctime(&new));

return 0;
}

Output:
local time now: Sat Nov 1 11:19:53 2003
local time 23 hours ago: Fri Oct 31 12:19:53 2003
local time 20 days ago: Sun Oct 12 12:19:53 2003

(The last is correct due to the end of BST last week, here in the UK.)

Alex
 
M

Mike Wahler

Michael B Allen said:
Well this is sort of what I want but more generally like this:

time_t
time_offset(const time *t, struct tm *adj)
{
unsigned long secs = (unsigned long)*t; /* ? */

secs += adj->tm_sec;
secs += adj->tm_min * 60;
secs += adj->tm_hour * 60 * 60;
secs += adj->tm_mday * 24 * 60 * 60;
secs += adj->tm_mon * 30 * 24 * 60 * 60;
secs += adj->tm_year * 12 * 30 * 24 * 60 * 60;

return secs;
}

Note that 'time_t' does not necessarily represent seconds.
but with proper consideration for the number of days in a month, leap
seconds, DST, etc...

The code I posted does that.

-Mike
 
M

Michael B Allen

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

time_t time_offset(const time_t *base, const struct tm *off) {
struct tm *t;

t = localtime(base);
if (!t) return (time_t)(-1);

t->tm_sec += off->tm_sec;
t->tm_min += off->tm_min;

What happends if t->tm_min is 55 and off->tm_min 25? That will result in
t->tm_min being 80. I didn't think this (nor Mike's code) would work
because of this. No?

Mike
 
M

Michael B Allen

I believe this is required of mktime(), but not of any of the other
related functions. Unless I'm missing something (and if I am, I hope
someone will point it out), this means there is no portable method of
calculating a new GMT time from a GMT time and an offset.

I can see glibc does this but the standard just reads:

....and the original values of the other components are not restricted to
the ranges indicated above.267)

....but with their values forced to the ranges indicated above; the final
value of tm_mday is not set until tm_mon and tm_year are determined.

and note 267 reads:

267) Thus, a positive or zero value for tm_isdst causes the mktime
function to presume initially that Daylight Saving Time, respectively, is
or is not in effect for the specified time. A neg ative value causes it to
attempt to determine whether Daylight Saving Time is in effect for the
specified time.

The POSIX standard is virtually identical of course. So does tm->tm_min +=
200; mktime(tm) mean the time will be advanced by 200 * 60 seconds or by
the truncated remainder (i.e. 20 seconds)?

Mike
 
A

Alex Fraser

Michael B Allen said:
Many implementations of struct tm renormalise the elements
after arithmetic.

I believe this is required of mktime(), [...]

I can see glibc does this but the standard just reads:

...and the original values of the other components are not restricted to
the ranges indicated above.267)

...but with their values forced to the ranges indicated above; the final
value of tm_mday is not set until tm_mon and tm_year are determined.

and note 267 reads:

267) [you can set tm_isdst < 0 and mktime() will try to work it out]

The POSIX standard is virtually identical of course. So does
tm->tm_min += 200; mktime(tm) mean the time will be advanced by 200 * 60
seconds or by the truncated remainder (i.e. 20 seconds)?

The statement "the final value of tm_mday is not set until tm_mon and
tm_year are determined" makes a lot more sense if the latter is the case,
and you assume the same logic applies to all members. But I would hope there
was something more explicit somewhere in the standard...

Alex
 
M

Mark McIntyre

Daylight savings time adds a whole heap of difficulty.

Only you're worrying about places that don't/didn't observe it via a
rule.
Err, no it isn't (or rather, wasn't).

that should indeed have read isn't".
Dates aren't much of a problem. Times are.

Never had a problem with them myself. Unfortunately the date/time
algos I've written are owned by my employer but really, I've found it
pretty easy.
I believe this is required of mktime(), but not of any of the other related
functions. Unless I'm missing something (and if I am, I hope someone will
point it out), this means there is no portable method of calculating a new
GMT time from a GMT time and an offset.

Quite probably.
 
A

Alex Fraser

Mark McIntyre said:
Only you're worrying about places that don't/didn't observe it via a
rule.

There's not much you can do if there's no rule. I was actually thinking of
the difficulty of handling the various rules for finding the dates, and (in
particular) handling the change time correctly, although the latter is
easily generalised. Tricky might be a more accurate description than
difficult, it's very easy to get wrong.

I still don't understand why there's no "time_t mkgmtime(struct tm *)"
(counterpart of mktime()) and/or "time_t addtime(time_t, double)"
(counterpart of difftime()) though...

Alex
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,160
Latest member
CollinStri
Top