Я неправильно использую tm/mktime, и если нет, то есть ли обходной путь?

Я думаю, что следующая программа должна выводить секунды до 1970 в первый день каждого года с 1 AD по 1970, предваряя размер time_t в системе, на которой она скомпилирована (CHAR_BIT - это макрос, поэтому я думаю, что вы не можете просто скопировать скомпилированный исполняемый файл и предположим, что он правильный, хотя на практике в наши дни все использует 8-битные char).

#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

void do_time(int year)
{
  time_t utc;
  struct tm tp;

  memset(&tp, 0, sizeof(tp));

  tp.tm_sec = 0;
  tp.tm_min = 0;
  tp.tm_hour = 0;
  tp.tm_mday = 1;
  tp.tm_mon = 0;
  tp.tm_year = year - 1900;
  tp.tm_wday = 1;
  tp.tm_yday = 0;
  tp.tm_isdst = -1;

  printf("%d %ld\n",year, mktime(&tp));
}

int main(){
  printf("time_t is %lu bits\n",sizeof(time_t)*CHAR_BIT);
  for (int i = 1; i<1971; i++)
    do_time(i);
  exit(0);
}

Однако в OS X (10.11.3 15D21) это работает только для лет >= 1902, несмотря на то, что time_t имеет 64-битную подпись. Я мог бы потенциально понять, если бы программисты Apple были ленивы и не поддерживали какие-либо годы до 1970 года, но правильное поведение, начиная с 1902 года, а затем останавливаясь, больше похоже на ошибку с моей стороны.


person Camden Narzt    schedule 09.02.2016    source источник
comment
Реализация OS X ограничивается 32-битами для дат до 1970 года, но использует 64-биты для дат после 1970 года. Странно...   -  person user3386109    schedule 09.02.2016
comment
Реализация с открытым исходным кодом: открытый исходный код. apple.com/source/Libc/Libc-997.1.1/stdtime/FreeBSD/. Интересная часть, кажется, находится в time2sub(). Видно, что mktime никогда не возвращает дату раньше 1900 года: if (yourtm.tm_year < 0) return WRONG с загадочным (по крайней мере для меня) комментарием /* Don't go below 1900 for POLA */. Это все еще не полностью объясняет ваше наблюдение, хотя ...   -  person Martin R    schedule 09.02.2016
comment
Это также наблюдалось на форуме разработчиков Apple: devforums.apple.com/message/756830#756830 (требуется вход в систему разработчика).   -  person Martin R    schedule 09.02.2016
comment
Вы уверены, что это даты ›= 1902? Это может не работать для 1900, потому что он использует mktime вместо gmtime. mktime зависит от часового пояса, и 1900/01/01 00:00:00 в вашем часовом поясе может выпасть из диапазона 1900+ для GMT. Но это не объяснило бы проблему с 1901 годом.   -  person Kurt Stutsman    schedule 09.02.2016
comment
@KurtStutsman: я могу подтвердить, что mktime дает сбой для года ‹= 1901 в OS X 10.11.   -  person Martin R    schedule 09.02.2016
comment
Примечание: установка tp.tm_wday и tp.tm_yday не требуется перед вызовом mktime(). Тоже никакого вреда.   -  person chux - Reinstate Monica    schedule 09.02.2016
comment
Кстати, обходным путем в OS X является использование платформы Core Foundation (CFDate, CFCalendar,...) или Foundation (NSCalendar, NSDate,...)   -  person Martin R    schedule 10.02.2016
comment
К сожалению, я не в состоянии изменить используемую библиотеку, поэтому мне приходится хранить количество секунд между 0001-01-01 и 1970-01-01 в константе в моей программе, так как я не могу полагаться на libc чтобы понять это.   -  person Camden Narzt    schedule 10.02.2016
comment
@Camden Narzt определить точное количество секунд назад до 0001-01-01 сложно, учитывая эволюцию календарей (григорианский, юлианский, римская республика) и т. д. Лучше думать о 1970-01-01 как о значении нескольких секунд (эпохе) и отсчете времени от этого. IOWs, преобразование time_t в древние даты - это полная банка червей. Попробуйте день рождения Вашингтона   -  person chux - Reinstate Monica    schedule 10.02.2016


Ответы (2)


Консультация по стандарту C:

Диапазон и точность времени, представляемого в clock_t и time_t, определяются реализацией. [..]

[N1570 §7.27.1/4] (выделено мной)

И далее по поводу mktime:

Функция mktime возвращает указанное календарное время, закодированное как значение типа time_t. Если календарное время не может быть представлено, функция возвращает значение (time_t)(-1).

[N1570 §7.27.2.3/3]

Таким образом, пока возвращаемое значение mktime равно (time_t)(-1) за те годы, когда оно не работает... вы сами по себе.


На самом деле, ИМО, стандарт немного молчит обо всем этом:

[..] int tm_year; // years since 1900 [..]

[N1570 §7.27.1/4]

Это может означать (положительные) годы с 1900 года, но тогда зачем использовать целое число со знаком.


В качестве примечания: в моей системе (Linux 3.14.40 x86_64 glibc-2.21) я получаю...

time_t is 64 bits
1 -62135600008
...
1969 -31539600
1970 -3600

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

person Daniel Jour    schedule 09.02.2016
comment
Он упомянул, что time_t составляет 64 бита, что достаточно для обработки дат вплоть до Большого взрыва. - person Kurt Stutsman; 09.02.2016
comment
@KurtStutsman Это может быть правдой, но цитата не о значениях, представляемых типами, а о представимых временах. Поскольку они определены реализацией, вы не можете (переносимо) ожидать, что они будут иметь какой-либо допустимый (временной) диапазон. - person Daniel Jour; 09.02.2016
comment
Даты рассчитываются математически, поэтому было бы странно не поддерживать их для полной разрядности типов возврата/ввода. Также спецификация неверна в отношении 1900 года. Это годы с 1970, а не с 1900. Они даже противоречат сами себе ранее на странице руководства. И отрицательные даты определенно работают в целом. - person Kurt Stutsman; 09.02.2016
comment
@KurtStutsman В официальном стандарте C11 написано лет с 1900 года. Это, конечно, не означает, что должны поддерживаться годы до 1970 года (или любое произвольное значение). - person 2501; 09.02.2016
comment
@ 2501 Извините, что вы правы насчет 1900 года. Я думал, что их страница неверна, но после поиска по всем справочным страницам говорится то же самое. - person Kurt Stutsman; 09.02.2016
comment
@KurtStutsman Нет проблем, только что разобравшись с mktime, я тоже сначала был сбит с толку. - person 2501; 09.02.2016
comment
@Kurt Stutsman Мысль о time_t составляет 64 бита, чего достаточно для обработки дат вплоть до Большого взрыва. time_t не обязательно должно быть в секундах. Если бы единицами измерения были наносекунды, то 64-битной системы хватило бы примерно на 600 лет. Конечно, каждая ОС может иметь свою спецификацию, так как C не определяет единицы измерения. - person chux - Reinstate Monica; 09.02.2016
comment
@Martin R Поскольку сообщение не помечено конкретной ОС, но с [libc], комментарии об общих возможностях time_t имели смысл, поскольку [C] не указывает секунды, как подразумевается в комментарии Курта Штутсмана. ОТО. ваш комментарий тоже полезен. - person chux - Reinstate Monica; 10.02.2016
comment
@chux: ты прав. – С другой стороны, мне интересно, должен ли вопрос быть помечен [osx], поскольку он, похоже, касается проблемы, наблюдаемой только в OS X. - person Martin R; 10.02.2016
comment
Расширенное обсуждение других реализаций libc помогло решить, как обойти мою проблему, и пока я разрабатываю для OS X, я, вероятно, буду развертывать на Linux, а также на Mac. - person Camden Narzt; 10.02.2016

В ОС UNIX часто есть 64-битные версии функций времени. В OS X может быть что-то подобное, хотя я не смог найти его при быстром поиске. Дополнительную информацию см. в разделе преобразование 64-битной временной метки unix.

РЕДАКТИРОВАТЬ: я нашел Mac, чтобы проверить это, и, к сожалению, у него нет функции mktime64. Я нашел эту эту библиотеку, которая может работать как обходной путь, хотя я не проверял ее лично. .

person Kurt Stutsman    schedule 09.02.2016
comment
Это ли не комментарий, раз вы не смогли найти ответ, а ссылки как ответы не годятся? - person Weather Vane; 09.02.2016
comment
OS X — это операционная система на базе UNIX, поэтому ответ может сработать. Я не могу это проверить. - person Kurt Stutsman; 09.02.2016