2015-05-12 2 views
2

Странная ситуация. Проверьте this Coliru code:C++ и mktime: 26/6/1943 и 27/6/1943 в тот же день

#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; 
} 

Комментарии после каждого примера те из Coliru и мой ноутбук. Выходы Coliru верны, но моя машина печатает неверные цифры.

Если вы прочитали код, разница между днями правильно вычисляется (первый пример, с 1/1/2000 по 31/12/2030).

Но если год 1943 находится в середине интервала даты, кажется, что день потерян. Второй пример: 1/1/1930 - 31/12/2030.

После многих испытаний я выяснил, что проблема была в Juny/1943. Третий пример: 1/6/1943 - 30/6/1943, вернувшийся на 28 дней вместо 29.

Более конкретно, это похоже на 26-е и 27-е числа в тот же день. Четвертый пример: 26/6/1943 - 27/6/1943, возвращение 0 дней.

Моя машина Ubuntu 14.02.2 LTS с Linux 3.13.0-52-generic x86_64, с использованием gcc (g ++) 4.8.2.

В чем проблема? Какая-то ошибка в реализации GNU libc?

+0

Каков ваш часовой пояс? Возможно, это что-то вроде [этого] (https://stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/6841479#6841479)? – Nobody

+1

Разве вы не пытались точно определить проблему? Распечатайте значения tm для определенных временных моментов в этот день и найдите, сколько секунд точно потеряно и какой момент? – Petr

+0

Я пробовал свой код и в своей системе Debian 7.8 (Италия), все в порядке: 11322, -12821, 364,26,1 – LPs

ответ

3

Это проблема с часовым поясом. Добавьте это в начале основного():

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

Кроме того, ваш дней() функция должна быть записана следующим образом:

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

Основной причиной является то, что time_t представляет секунды, так как эпоха в UTC, тогда как mktime() интерпретирует свой аргумент как дату в локальном часовом поясе. Следовательно, он должен преобразовать его в UTC для создания time_t. Таким образом, если есть разрывы относительно UTC между двумя датами в локальном часовом поясе, результат может быть неправильным.

Реальное решение, хотя и не должно использовать mktime(). Он не предназначен для вашего приложения (и в дополнение к DST есть другие проблемы, такие как секунды прыжка).

Что вы пытаетесь сделать, это рассчитать количество дней между двумя датами в (пролептическом) gregorian календаре. Вы должны реализовать это самостоятельно, например, путем преобразования их в число юлианских дней.

+0

Я не уверен. Я получаю те же результаты для 'TZ', установленного в' '' '' или '' UTC ''. –

+0

Он решает мою проблему. Благодарю. Но так или иначе, почему проблема с часовым поясом? Это не имеет смысла. Количество секунд с даты на сегодняшний день не изменяется, независимо от часового пояса. –

+1

@ Peregring-lk: «Количество секунд с даты на дату shoudn't change» - это может, если определенный часовой пояс изменил его смещение от UTC (или даже к календарю, который он использовал) в определенный момент времени. Какой часовой пояс вы используете? –

0

В Ubuntu 15.04, 32 бит, я получаю

[email protected]:/tmp$ ./mkdate 
11322 
-12821 
364 
29 
1 
[email protected]:/tmp$ 

, где, как на Ubuntu 15.04, 64 бит, я получаю

[email protected]:/tmp$ ./mkdate 
11322 
36889 
364 
29 
1 
[email protected]:/tmp$ 

То есть г ++ в обоих случаях 4.9.2. Разница заключается в том, что time_t составляет четыре и восемь байтов соответственно, так что расчет разницы дат в вашем коде получает переполнение. Но я не вижу, что может быть ошибкой библиотеки в вашей старой версии.

1

Это не проблема с часовым поясом, даже если проблема не проявляется в UTC.

Есть несколько проблем с этой программой, но ключевым является тот факт, что поля tm_isdst не инициализируются. Это поле влияет на поведение mktime, которое вводит корректировку в полях tm_hour.

С этими конкретными датами (26/6/1943, 27/6/1943), часовой пояс (Европа/Мадрид) и 0 в качестве содержимого полей tm_isdst, вы получаете разницу в 23 часа (82800 с) между обе даты вместо 24 часов (86400 с). Затем в функции days 82800 делится на 86400 с целым делением . Результат равен 0, а выходной день полностью потерян.

Plese, просто добавьте:

date.tm_isdst = -1; 

к вашей mkdate функции, чтобы избежать этого, рассказав mktime проверить даты против часового пояса для летнего времени перед вычислением количества секунд с началом эпохи.

+1

Если вы установили tm_isdst на -1, то, например, разница между 2000-03-27: 00: 00: 00 CET и 2000-03-26: 00: 00: 00 CET - 82800 секунд. Кроме того, вы можете в принципе иметь другие сюрпризы, что DST в определенные часы, например пропущенные дни. Использование UTC - единственный способ избежать этих проблем, конечно, если time_t игнорирует секунды прыжка. –

+1

Да, и это правильный ответ, так как 2000-03-26 02:00:00 CET меняется на CEST (+1 час, начало DST). Здесь речь идет о вычислении различий внутри локального часового пояса. Конечно, эти различия отражают главным образом политические изменения во времени. Дни были потеряны много раз в истории, от Рима, до Французской революции, до русской революции и т. Д. Если вы хотите точное количество дней, независимо от вашего часового пояса, просто добавьте 'TZ = ''' или 'TZ = 'UTC'' к вашей среде. –

+0

Почему отрицательное значение вместо положительного? Флаг «Летнее время». Значение положительное, если DST действует, ноль, если нет, и отрицательный, если информация отсутствует ». Должно ли быть указано« tm_isdst = 1 », чтобы сказать, что DST необходимо учитывать? –

Смежные вопросы