tags:

views:

1297

answers:

4

I have a time represented as the number of seconds elapsed since midnight, January 1, 1970, UTC (the results of an earlier call to time()). How do I add one day to this time?

Adding 24 * 60 * 60 works in most cases, but fails if the daylight saving time comes on or off in between. In other words, I mostly want to add 24 hours, but sometimes 23 or 25 hours.

To illustrate - the program:

#include <time.h>
#include <iostream>

int main()
{
  time_t base = 1142085600;
  for(int i = 0; i < 4; ++i) {
    time_t time = base + i * 24 * 60 * 60;
    std::cout << ctime(&time);
  }
  return 0;

}

Produces:

Sat Mar 11 08:00:00 2006
Sun Mar 12 09:00:00 2006
Mon Mar 13 09:00:00 2006
Tue Mar 14 09:00:00 2006

I want the times for March 12, 13, ... to also be 8 AM.


The answer provided by FigBug pointed me in the right direction. But I had to use localtime instead of gmtime.

int main()
{
  time_t base = 1142085600;
  for(int i = 0; i < 4; ++i) {
    struct tm* tm = localtime(&base);
    tm->tm_mday += i;
    std::cout << asctime(tm);
 }
 return 0;
}

Give me:

Sat Mar 11 08:00:00 2006
Sat Mar 12 08:00:00 2006
Sat Mar 13 08:00:00 2006
Sat Mar 14 08:00:00 2006

Which is what I want. Using gmtime gives me the times at 14:00:00

However, note that all days are Sat. Also, it goes to March 32, 33, etc. If I throw in the mktime function I am back where I started:

#include <time.h>
#include <iostream>

int main()
{
  time_t base = 1142085600;
  for(int i = 0; i < 4; ++i) {
    struct tm* tm = localtime(&base);
    tm->tm_mday += i;
    time_t time = mktime(tm);
    std::cout << asctime(tm);
 }
 return 0;
}

Gives me:

Sat Mar 11 08:00:00 2006
Sun Mar 12 09:00:00 2006
Mon Mar 13 09:00:00 2006
Tue Mar 14 09:00:00 2006

What am I missing???


OK, I have tried out FigBug's latest suggestion that is to use:

 std::cout << ctime(&time);

instead of asctime, but I get the same results. So I guess that my library and/or compiler is messed up. I am using g++ 3.4.4 on cygwin. I copied the files over to Solaris 5.8 and used g++ 3.3 there to compile. I get the correct results there! In fact I get the correct results whether I use ctime or asctime for output:

Sat Mar 11 08:00:00 2006
Sun Mar 12 08:00:00 2006
Mon Mar 13 08:00:00 2006
Tue Mar 14 08:00:00 2006

I also get the correct results (with both output functions) on Red Hut Linux with g++ 3.4.6.

So I guess that I have come across a Cygwin bug.

Thank you for all your help and advice....

+3  A: 

I always had the best result with keeping the timestamps UTC and convert them to the specified timezone (including daylight saving) when you want to display the values.

This saves a lot of hassle like this (and makes your program independent of time zones.

Gamecat
The timestamps are UTC.
Andrew Stein
+10  A: 

use gmtime() to convert the time_t to a struct tm

add one to the day (tm_mday)

use mktime() to convert the struct tm back to a time_t

see time.h for more info

Edit:

I just tried it, this works:

int main()
{
  time_t base = 1142085600;
  for(int i = 0; i < 4; ++i) {
    struct tm* tm = localtime(&base);
    tm->tm_mday += i;
    time_t next = mktime(tm);
    std::cout << ctime(&next);
 }
 return 0;
}
FigBug
Can words with an underscore not be in italics?
FigBug
use <i>some_word_with_underscores</i> instead of *word*
Jimmy
Or put a backslash in front of the underscore.
Jonathan Leffler
Doesn't using gmtime() instead of localtime() mean that you ignore the very problem the question asks about? In effect, you are always going to add 86400, and never 90000 or 82800?
Jonathan Leffler
Corollary: the algorithm using localtime() in place of gmtime() should work - if localtime() is sane.
Jonathan Leffler
loacltime gives me what I want. I have posted this separately. Thanks...
Andrew Stein
Whoops, yes localtime() is correct.
FigBug
Just noticed that I don't get what I want. I have edited the posting accordingly.
Andrew Stein
+4  A: 

Just add 24*60*60. It shouldn't fail during DST, since UTC won't ever use DST.

If it is failing, then you are not using UTC somewhere in your code. Remove the timezone dependence.

Pyrolistical
The point is that adding one day to say 3/31/2008 8:00:00 AM local time should give me 4/1/2008 8:00:00 AM local time even if DST changes locally overnight on 3/31.
Andrew Stein
I don't understand, are you using unix timestamp in UTC or not?Unix time stamp has nothing to do with DST.
Pyrolistical
The conversion from UTC to local time is where the DST hour should be added or subtracted - the UTC shouldn't be affected, as Pyrolistical says.
Mark Ransom
I have added an example showing what I mean (local for me is US Central time zone),
Andrew Stein
Then do what FigBug suggests below, but use localtime() instead
Pyrolistical
Just posted that... Thanks
Andrew Stein
Just noticed that I don't get what I want. I have edited the posting accordingly.
Andrew Stein
+3  A: 

FigBug's solution will work almost every time, but it needs DST fix: tm->tm_isdst = -1

A positive or 0 value for tm_isdst causes mktime() to presume initially that Daylight Savings Time, respectively, is or is not in effect for the specified time. A negative value for tm_isdst causes mktime() to attempt to determine whether Daylight Saving Time is in effect for the specified time.

(quoted from mktime spec)

int main()
{
  time_t base = 1142085600;
  for(int i = 0; i < 4; ++i) {
    struct tm* tm = localtime(&base);
    tm->tm_mday += i;
    tm->tm_isdst = -1;        // don't know if DST is in effect, please determine
                              // this for me
    time_t next = mktime(tm);
    std::cout << ctime(&next);
 }
 return 0;
}

Otherwise there will be a bug (example for Moscow Daylight Saving Time which starts 29 March 2009 01:59:59):

int main()
{
    // 28 March 2009 05:00:00 GMT ( local - 08:00 (MSK) )
    time_t base = 1238216400;

    std::time_t start_date_t = base;
    std::time_t end_date_t = base;

    std::tm start_date = *std::localtime(&start_date_t);
    std::tm end_date = *std::localtime(&end_date_t);

    end_date.tm_mday += 1;
//    end_date.tm_isdst = -1;

    std::time_t b = mktime(&start_date);
    std::time_t e = mktime(&end_date);

    std::string start_date_str(ctime(&b));
    std::string stop_date_str(ctime(&e));

    cout << " begin (MSK) (DST is not active): " << start_date_str;
    cout << " end   (MSD) (DST is active):     " << stop_date_str;
}

Output:

begin (MSK) (DST is not active): Sat Mar 28 08:00:00 2009
end   (MSD) (DST is active):     Sun Mar 29 09:00:00 2009
mraq