2

It's a strange situation. Check out this Coliru code:

#include <iostream>
#include <utility>
#include <ctime>

using namespace std;

#define SEGS 60
#define MINS 60
#define HOURS 24

int days(tm* date1, tm* date2)
{ return (mktime(date1) - mktime(date2)) / SEGS / MINS / HOURS; }

tm mkdate(int day, int mon, int year)
{
    tm date = {0, 0, 0};

    date.tm_mday = day;
    date.tm_mon = mon - 1;
    date.tm_year = year - 1900;

    return date;
}

int main()
{
    tm date1 = mkdate(31, 12, 2030);
    tm date2 = mkdate(1, 1, 2000);

    cout << days(&date1, &date2) << endl;
    // 11322... OK 30 * 365  (1/1/2000 - 1/1/2030)
    // + 8 (leap years) + 364 (from 1/1/2030 - 31/12/2030).

    date1 = mkdate(31, 12, 2030);
    date2 = mkdate(1, 1, 1930);
    cout << days(&date1, &date2) << endl;
    // 36889... OK; but in my machine, it returns 36888.

    date1 = mkdate(31, 12, 1943);
    date2 = mkdate(1, 1, 1943);
    cout << days(&date1, &date2) << endl;
    // 364... OK: but in my machine, it returns 363.

    date1 = mkdate(30, 6, 1943);
    date2 = mkdate(1, 6, 1943);
    cout << days(&date1, &date2) << endl;
    // 29... OK; but in my machine, it returns 28.

    date1 = mkdate(27, 6, 1943);
    date2 = mkdate(26, 6, 1943);
    cout << days(&date1, &date2) << endl;
    // 1... OK; but in my machine, it returns 0.

    return 0;
}

The comments after each example are those from Coliru and my laptop. The Coliru outputs are corrects, but my machine is who prints the wrong numbers.

If you read the code, the difference between days is correctly computed (first example, from 1/1/2000 to 31/12/2030).

But if year 1943 is in the middle of a date interval, it seems a day is lost. Second example: 1/1/1930 - 31/12/2030.

After lots of tests, I found out the problem was in Juny/1943. Third example: 1/6/1943 - 30/6/1943, returning 28 days instead of 29.

More concretly, it seems 26th and 27th are the same day. Fourth example: 26/6/1943 - 27/6/1943, returning 0 days.

My machine is a Ubuntu 14.02.2 LTS with Linux 3.13.0-52-generic x86_64, using gcc (g++) 4.8.2.

What does the problem come from? Some kind of bug of GNU libc implementation?

Deanie
  • 2,316
  • 2
  • 19
  • 35
ABu
  • 10,423
  • 6
  • 52
  • 103
  • What is your timezone? Maybe this is something like [this](https://stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/6841479#6841479)? – Nobody moving away from SE May 12 '15 at 13:06
  • 1
    Did not you try to pinpoint the problem further? Print the tm values for particular time moments on that day and find, how much seconds exactly are lost and an which moment? – Petr May 12 '15 at 13:07
  • I tried your code and on my system, Debian 7.8 (Italy), all is ok: 11322, -12821, 364,26,1 – LPs May 12 '15 at 13:13
  • On an Ubuntu 14.04.02 system with g++ 4.8.2 I get the correct output in timezone CEST. – martin May 12 '15 at 13:17
  • 1
    Please, could you tell us what timezone you are in, I'm curious. – martin May 12 '15 at 13:24
  • @martin My timezone is CEST – ABu May 12 '15 at 13:33
  • For the record, I can reproduce this with `TZ="Europe/Madrid" ./mkdate` – martin May 12 '15 at 13:38
  • The solution of Antoine Mathys seems enough. Establishing TZ as null, makes tzset sets the timezone to UCT (the default one). – ABu May 12 '15 at 13:43

3 Answers3

3

It is a timezone issue. Add this at the beginning of main():

// set the timezone to UTC
setenv("TZ", "", 1);
tzset();

Also your days() function should be written like this:

double days (tm *date1, tm *date2) {
  return difftime (mktime(date1), mktime(date2)) / 86400;
}

The root cause is that time_t represents seconds since the epoch in UTC whereas mktime() interprets its argument as a date in the local timezone. Hence it must convert it to UTC to produce a time_t. Thus if there are discontinuities relative to UTC between the two dates in the local timezone the result might be wrong.

The real solution though is not to use mktime(). It is not intended for your application (and in addition to DST there are other issues like leap seconds).

What your are trying to do is calculate the number of days between two dates in the (proleptic) gregorian calendar. You should implement this yourself, for example by converting them to julian day numbers.

  • I am not sure. I get the same results for `TZ` set to `""` or `"UTC"`. – Dirk Eddelbuettel May 12 '15 at 13:11
  • It solve my problem. Thanks. But anyway, why a timezone issue? It has no sense. The amount of seconds from date to date shoudn't change, irrespective of the timezone. – ABu May 12 '15 at 13:23
  • 1
    @Peregring-lk: "The amount of seconds from date to date shoudn't change" - it can if a particular timezone changed its offset from UTC (or even the calendar it used) at some point in time. Which timezone are you using? – Mike Seymour May 12 '15 at 13:34
1

This is not a timezone problem, even if the problem does not manifest with UTC.

There are several issues with this program, but the key one one is the fact that tm_isdst fields are not initialized. This field affect the behaviour of mktime, which introduces an adjustment in the tm_hour fields.

With this particular dates (26/6/1943, 27/6/1943), timezone (Europe/Madrid) and 0 as the content of both tm_isdst fields, you get a difference of 23 hours (82800 s) between both dates instead of 24 hours (86400 s). Then, in function days, 82800 is divided by 86400 with an integer division. The result is 0 and the day off is totally lost.

Plese, just add:

date.tm_isdst = -1;

to your mkdate function to avoid this, by telling mktimeto check the dates against the timezone for DST before computing the number of seconds since the epoch.

  • 1
    Yes, and that is the right answer, as 2000-03-26 02:00:00 CET changes to CEST (+1 hour, DST start). The point here is computing differences inside the local timezone. Of course, these differences reflect mainly political changes in time counting. Days were lost many times in History, from Rome, to French Revolution, to Russian Revolution, etc. If you want the exact number of days regardless your timezone, then just add `TZ=''` or `TZ='UTC'`to your environment. – Francisco Palomo-Lozano May 13 '15 at 05:01
  • Why a negative value instead of a positive one? "Daylight Saving Time flag. The value is positive if DST is in effect, zero if not and negative if no information is available" Shouldn't it be `tm_isdst = 1` to say DST must be taken into account? – ABu May 13 '15 at 10:22
  • Other question. Each summer, a hour is lost. Why does it cause problem only with 26/6/1943 and 27/6/1943? – ABu May 13 '15 at 10:25
  • A value of 1 is not what you want, because you will be telling `mktime` that at that time DST was active in the local timezone, which could serve in your particular example but not in others. A negative value tells `mktime` to look up the local timezone to determine whether that was the case. – Francisco Palomo-Lozano May 13 '15 at 12:37
  • It seems that the after civil war period between 1940 and 1946 was very special in Spain timezone history (well, the war period was even more interesting). Please, read: (http://www.fomento.es/NR/rdonlyres/4EDBC646-BC69-4EC0-9F74-DB0C2E1EF781/120583/pere2013.pdf). If you can trace back the exact reason why this happens at that particular dates (26/6/1943 and 27/6/1943) when you trick `mktime` to think that `tm_isdst` is 0, please, tell me. I guess that this is linked to the internal workings of `mktime` when DST corrections are off. – Francisco Palomo-Lozano May 13 '15 at 12:44
0

On Ubuntu 15.04, 32 bit, I get

edd@don:/tmp$ ./mkdate 
11322
-12821
364
29
1
edd@don:/tmp$ 

where as on Ubuntu 15.04, 64 bit, I get

edd@max:/tmp$ ./mkdate 
11322
36889
364
29
1
edd@max:/tmp$ 

That is g++ 4.9.2 in both cases. The difference is due to time_t being four and eight bytes, respectively, so that the date difference calculation in your code gets an overflow. But I do not see what may be a library bug in your older release.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725