Функция mktime LIBC возвращает разные значения для одного и того же ввода.

Мы знаем, что часовой пояс UTC+8 имеет некоторые изменения часов. Например, 00:00:00 1 января 1928 года часы были переведены назад на 0:05:52 часов до 31 декабря 1927 года, 23:54:08.
Кроме того, в 1940-1941 и 1986-1991 годах использовалось летнее время. Когда я тестирую функцию mktime под linux с этими датами, у меня разные возвращаемые значения. Код выглядит следующим образом:

#include <stdio.h>
#include <string.h>
#include <time.h>
int main(int argc, char *argv[])
{
    struct tm timeinfo;
    memset(&timeinfo, 0, sizeof(timeinfo));

    while(fscanf(stdin, "%d%d%d%d%d%d",
            &timeinfo.tm_year, &timeinfo.tm_mon, &timeinfo.tm_mday,
            &timeinfo.tm_hour, &timeinfo.tm_min, &timeinfo.tm_sec) != EOF)
    {
        timeinfo.tm_year -= 1900;
        timeinfo.tm_mon -= 1;
        fprintf(stdout, "%lld\n", mktime(&timeinfo));
    }

    return 0;
}

Тестовый ввод и вывод, например, таков: один и тот же ввод «1940 6 2 23 59 59» и «1940 6 3 1 0 0» будет иметь разное возвращаемое значение в зависимости от вызывающей последовательности:

1940 6 2 23 59 59
-933494401    
1940 6 3 1 0 0
-933490800
1940 6 3 1 0 0
-933494400
1940 6 2 23 59 59
-933498001
1940 6 2 23 59 59
-933494401

Тот же ввод 1940 6 3 1 0 0

Почему это? Почему возвращаемое значение mktime отличается в зависимости от вызывающей последовательности?

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

Кто-нибудь может объяснить, почему это происходит? Большое спасибо.

Недавно добавленные случаи:

1927 12 31 23 54 8
-1325491552
1927 12 31 23 54 7
-1325491905
1927 12 31 23 54 8
-1325491904
1928 1 1 0 0 0
-1325491200
1927 12 31 23 54 8
-1325491552

person Ziyu Chen    schedule 19.08.2014    source источник
comment
Предложение: лучше использовать while(fscanf(stdin, "%d%d%d%d%d%d", ...) == 6)   -  person chux - Reinstate Monica    schedule 19.08.2014
comment
Перед вызовом mktime(&timeinfo) используйте timeinfo.tm_isdst = -1 (летнее время недоступно), чтобы избежать наследования предыдущего значения. Только tm_wday и tm_yday можно оставить неинициализированными.   -  person chux - Reinstate Monica    schedule 19.08.2014
comment
попробуйте "%lld\n" --› "%ld\n" и проверьте Является ли time_t типом long long int?   -  person BLUEPIXY    schedule 19.08.2014
comment
@BLUEPIXY верен, рассмотрите fprintf(stdout, "%lld\n", (long long) mktime(&timeinfo));, чтобы устранить потенциальные printf() проблемы.   -  person chux - Reinstate Monica    schedule 19.08.2014
comment
Почему возвращаемое значение mktime отличается и зависит от последовательности вызова? Потому что только 6 из необходимых 7 полей инициализируются.   -  person chux - Reinstate Monica    schedule 19.08.2014


Ответы (2)


Вы начинаете с timeinfo.tm_isdst, установленного на 0, что просит mktime рассматривать время как не летнее.

Затем mktime нормализует переданное struct tm, чтобы поля находились в своих надлежащих диапазонах; часть этого процесса будет регулировать флаг летнего времени в зависимости от того, действительно ли действовало летнее время в указанное время. (См. документацию.) Если действует летнее время, присвойте флагу положительное значение и соответствующим образом настройте другие поля struct tm.

Более поздние итерации вашего цикла перезапишут шесть полей, переданных вашему fscanf, но не поле DST. Таким образом, если более ранняя итерация цикла привела к установке этого флага, более поздняя итерация все еще будет иметь установленный флаг. В результате вы на самом деле не передаете одно и то же время в mktime, и он возвращает разные результаты.

Судя по тому, что вы печатаете, сценарий выглядит так:

1940 6 2 23 59 59  // tm_isdst == 0, asks mktime to consider this non-DST, and DST not in effect at this time
-933494401         // tm_isdst still 0
1940 6 3 1 0 0     // tm_isdst == 0, asks mktime to consider this non-DST, but at this time DST was in effect
-933490800         // tm_isdst now positive
1940 6 3 1 0 0     // tm_isdst > 0, asks mktime to consider this DST, and DST was actually in effect
-933494400         // tm_isdst still positive
1940 6 2 23 59 59  // tm_isdst > 0, asks mktime to consider this DST, but actually DST wasn't in effect
-933498001         // tm_isdst becomes 0
1940 6 2 23 59 59  // tm_isdst == 0, asks mktime to consider this non-DST
-933494401         // tm_isdst still 0

Демо.

person T.C.    schedule 19.08.2014
comment
это может объяснить различие между первым и вторым, но не объясняет второй и третий вызовы. поле DST уже установлено на то, что его устанавливает mktime. Также я не уверен, что верю вам в том, что mktime настраивает его ввод. Я был бы ужасно плохим дизайном. - person Evan Dark; 19.08.2014
comment
@EvanDark Не стесняйтесь обращаться к документации самостоятельно, если вы мне не верите. - person T.C.; 19.08.2014
comment
@Т.С. Это действительно полезно. Большое спасибо. У меня есть случаи, которые невозможно объяснить. Эти случаи добавлены в текст - person Ziyu Chen; 19.08.2014
comment
@ZiyuChen Я не могу воспроизвести это, но сначала распечатайте все поля вашего struct tm до и после вызова mktime, чтобы увидеть, не изменилось ли что-нибудь. - person T.C.; 19.08.2014
comment
@Т.С. Поле tm_isdst, tm_year,.., tm_sec до и после вызова mktime не изменяется, когда я пробую эти случаи. Перевод часов в Шанхае в 1927 году не связан с переходом на летнее время. Эта страница может оказаться полезной. - person Ziyu Chen; 19.08.2014
comment
@ZiyuChen Что произойдет, если вы добавите memset(&timeinfo, 0, sizeof(timeinfo)); сразу после звонка fprintf? - person T.C.; 19.08.2014
comment
@Т.С. Что касается добавленных случаев, результат тот же. Добавленные случаи не о dst. Поведение действительно странное. - person Ziyu Chen; 19.08.2014

Проблема скорее всего не в mktime, а в sscanf. Я не могу проверить это, так как для меня работает ваш пример (ну, я не уверен, что это правильное время, но оно соответствует), но очень вероятно, что scanf читает меньше чисел, чем должен, и таким образом ввод из предыдущего строка смешивается с текущей.

Известно, что fscanf имеет проблемы с окончаниями строк, поэтому проверьте возвращаемое значение (оно должно быть 6) value попробуйте прочитать строку за строкой с fgets в буфер, а затем используйте sscanf.

person Evan Dark    schedule 19.08.2014