How to add one day to a time obtained from time()

2019-02-08 06:11发布

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....

标签: c++ c date dst
5条回答
Fickle 薄情
2楼-- · 2019-02-08 06:27

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;
}
查看更多
Summer. ? 凉城
3楼-- · 2019-02-08 06:27

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.

查看更多
一夜七次
4楼-- · 2019-02-08 06:29

New answer for a very old question.

Rationale for the new answer: There are now better tools to solve this problem, making the result less error-prone, easier to read, and actually more efficient by minimizing serial <-> field conversions.

The new answer requires C++11/14, <chrono>, and this free, open source, timezone library.

Here's the code:

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace std::chrono;
    using namespace date;
    auto base = make_zoned("Pacific/Easter", sys_seconds{1142085600s});
    for (int i = 0; i < 4; ++i)
    {
        std::cout << format("%a %b %d %T %Y %Z", base) << '\n';
        base = base.get_local_time() + days{1};
    }
}

The starts off by creating a zoned_time by pairing whatever is the desired time zone with the Unix Time timestamp.

That is formatted out in whatever the desired format.

And the addition of 1 day is done in the time zone's local time system, which will take daylight savings into account. The output is:

Sat Mar 11 09:00:00 2006 -05
Sun Mar 12 09:00:00 2006 -06
Mon Mar 13 09:00:00 2006 -06
Tue Mar 14 09:00:00 2006 -06

As it turns out, this output is not exactly what the OP stated he desired (the requested output is at 08:00:00 every day). However I used this library to fully investigate the entire planet's time transitions on this date. And there is only one time zone that had a transition on this date: Pacific/Easter. And that transition was to move back an hour, not forward. This is the time zone used for Chile, in the southern hemisphere, where one falls back in the March time frame.

This can be demonstrated by doing the arithmetic in UTC instead of in local time. This is a tiny adjustment to the above program in one line:

        base = base.get_sys_time() + days{1};

Using base.get_sys_time(), as opposed to base.get_local_time(), causes the arithmetic to be done in "system time" which is UTC neglecting leap seconds. And now the output changes to:

Sat Mar 11 09:00:00 2006 -05
Sun Mar 12 08:00:00 2006 -06
Mon Mar 13 08:00:00 2006 -06
Tue Mar 14 08:00:00 2006 -06
查看更多
\"骚年 ilove
5楼-- · 2019-02-08 06:38

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
查看更多
SAY GOODBYE
6楼-- · 2019-02-08 06:39

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.

查看更多
登录 后发表回答