Сохраняет ли структура tm информацию о часовом поясе в качестве члена данных - PullRequest
8 голосов
/ 18 октября 2019

Рассмотрим следующий код C ++

#include <ctime>
#include <iostream>

int main()
{
    std::time_t now = std::time(nullptr);
    struct tm local = *std::localtime(&now);
    struct tm gm = *std::gmtime(&now);
    char str[20];
    std::strftime(str, 20, "%Z", &local);
    std::cout << str << std::endl;          // HKT
    std::strftime(str, 20, "%Z", &gm);
    std::cout << str << std::endl;          // UTC

    return 0;
}

То, что хранится в now, является однозначным целочисленным значением, тогда как local и gm являются struct tm, которые хранят информацию о дате и времени, читаемую человеком. ,Затем я распечатываю отформатированную информацию (часовой пояс), основанную только на объектах struct tm.

Согласно cplusplus reference , элементы данных struct tm являются

tm_sec  
tm_min  
tm_hour 
tm_mday 
tm_mon  
tm_year 
tm_wday 
tm_yday 
tm_isdst

Если это все, что struct tm содержит, как программа узнает, что информация о часовом поясе от него? То есть как он узнает, что часовой пояс равен HKT для local и что часовой пояс равен UTC для gm?

Если это еще не все, что struct tm содержит, пожалуйста,объясните, как она хранит информацию о часовом поясе.

Кстати, хотя демонстрационный код написан на C ++, я думаю, что этот вопрос по сути стоит и как законный вопрос на языке C.

Ответы [ 3 ]

5 голосов
/ 18 октября 2019

Стандарт C гласит в 7.27.1 Компоненты времени:

Структура tm должна содержать не менее следующих членов в любом порядке. Семантика членов и их нормальные диапазоны выражены в комментариях. 318)

int tm_sec;    // seconds after the minute — [0, 60]
int tm_min;    // minutes after the hour — [0, 59]
int tm_hour;   // hours since midnight — [0, 23]
int tm_mday;   // day of the month — [1, 31]
int tm_mon;    // months since January — [0, 11]
int tm_year;   // years since 1900
int tm_wday;   // days since Sunday — [0, 6]
int tm_yday;   // days since January 1 — [0, 365]
int tm_isdst;  // Daylight Saving Time flag

(выделение мое)

То есть реализацииразрешено добавлять дополнительных членов к tm, как вы обнаружили с glibc/time/bits/types/struct_tm.h. Спецификация POSIX имеет почти идентичную формулировку.

В результате %Z (или даже %z) нельзя считать переносимым в strftime. Спецификация для %Z отражает это:

%Z заменяется именем или сокращением часового пояса локали, или никакими символами, если часовой пояс не определяется. [tm_isdst]

То есть поставщикам разрешено поднимать руки и просто говорить: «Часовой пояс не был определен, поэтому я вообще не выводил никаких символов».

Мое мнение: API синхронизации C - беспорядок.


Я пытаюсь улучшить готовящийся стандарт C ++ 20 в библиотеке <chrono>.

ЧерновикСпецификация C ++ 20 изменяет это с «без символов» на исключение, которое выдается, если сокращение time_zone недоступно:

http://eel.is/c++draft/time.format#3

Если явно не запрошено,Результат форматирования хронографического типа не содержит аббревиатуру часового пояса и информацию о смещении часового пояса. Если информация доступна, спецификаторы преобразования %Z и %z отформатируют эту информацию (соответственно). [ Примечание: Если информация недоступна и спецификатор преобразования %Z или %z появляется в chrono-format-spec , возникает исключение типа format_­error, как описано выше. - конечная нота ]

За исключением того, что в приведенном выше абзаце описывается не C * strftime, а новая функция format, которая работает с типами std::chrono, а не tm. Кроме того, появился новый тип: std::chrono::zoned_time (http://eel.is/c++draft/time.zone.zonedtime), который всегда имеет доступное сокращение time_zone (и смещение) и может быть отформатирован с помощью упомянутой выше функции format.

Пример кода:

#include <chrono>
#include <iostream>

int
main()
{
    using namespace std;
    using namespace std::chrono;
    auto now = system_clock::now();
    std::cout << format("%Z\n", zoned_time{current_zone(), now});   // HKT (or whatever)
    std::cout << format("%Z\n", zoned_time{"Asia/Hong_Kong", now}); // HKT or HKST
    std::cout << format("%Z\n", zoned_time{"Etc/UTC", now});        // UTC
    std::cout << format("%Z\n", now);                               // UTC
}

(Отказ от ответственности: Окончательный синтаксис строки форматирования в функции format, вероятно, будет немного отличаться, но функциональность будет там.)

Если вы хотите поэкспериментировать с предварительным просмотром этой библиотеки, она является бесплатной и с открытым исходным кодом здесь: https://github.com/HowardHinnant/date

Требуется некоторая установка: https://howardhinnant.github.io/date/tz.html#Installation

В этомДля предварительного просмотра вам потребуется использовать заголовок "date/tz.h", а содержимое библиотеки находится в namespace date вместо namespace std::chrono.

Библиотека предварительного просмотра может использоваться с C ++ 11 или более поздней версией.

zoned_time настроен на std::chrono::duration, который задает точность момента времени, и выводится в приведенном выше примере кода, используя функцию CTAD C ++ 17 . Если выиспользуя эту библиотеку предварительного просмотра в C ++ 11 или C ++ 14, синтаксис would будет выглядеть примерно так:

cout << format("%Z\n", zoned_time<system_clock::duration>{current_zone(), now});

Или существует фабричная функция помощника, которая не предлагается для стандартизации и которая сделает за вас вывод:

cout << format("%Z\n", make_zoned(current_zone(), now));

(# CTAD_eliminates_factory_functions)

2 голосов
/ 18 октября 2019

Спасибо за все комментарии к вопросу, которые помогают указать правильное направление. Я публикую некоторые из моих собственных исследований ниже. Я говорю на основе архивного репо библиотеки GNU C, которую я нашел на GitHub. Его версия 2.28.9000.

В glibc/time/bits/types/struct_tm.h есть

struct tm
{
  int tm_sec;           /* Seconds. [0-60] (1 leap second) */
  int tm_min;           /* Minutes. [0-59] */
  int tm_hour;          /* Hours.   [0-23] */
  int tm_mday;          /* Day.     [1-31] */
  int tm_mon;           /* Month.   [0-11] */
  int tm_year;          /* Year - 1900.  */
  int tm_wday;          /* Day of week. [0-6] */
  int tm_yday;          /* Days in year.[0-365] */
  int tm_isdst;         /* DST.     [-1/0/1]*/

# ifdef __USE_MISC
  long int tm_gmtoff;       /* Seconds east of UTC.  */
  const char *tm_zone;      /* Timezone abbreviation.  */
# else
  long int __tm_gmtoff;     /* Seconds east of UTC.  */
  const char *__tm_zone;    /* Timezone abbreviation.  */
# endif
};

Кажется, что struct tm хранит информацию о часовом поясе, по крайней мере, в этой реализации.

1 голос
/ 18 октября 2019

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

Но другая причина, по которой это трудно, состоит в том, что слишком много библиотек и языков делают из этого идеальный беспорядок, и C, к сожалению, не исключение. (C ++ пытается добиться большего успеха, как упоминает Говард в своем ответе.)

Несмотря на то, что все знают, что глобальные переменные являются плохими, функции даты / времени в C в основном используют пару из них. По сути, концепция «текущего часового пояса этой системы» является глобальной переменной, и глобальные данные, описывающие этот часовой пояс, разделены в произвольном порядке между localtime и strftime и рядом других функций.

Таким образом, strftime может заполнять %z и %Z на основе этих глобальных данных, даже если они не передаются как часть значения struct tm.

Это, очевидно, неоптимальныйдоговоренности, и это могло бы вызвать реальные проблемы, если бы у программы был способ динамически изменить часовой пояс, который она хочет использовать для localtime и остальных. (И эта договоренность сохраняется частично потому, что , а не на самом деле хороший, переносимый, стандартный способ для программы изменить местный часовой пояс, который она использует.)

За прошедшие годы были различныенерешительная попытка убрать часть путаницы (при сохранении обратной совместимости, конечно). Одна из этих попыток включает расширенные поля tm_gmtoff и tm_zone, которые вы обнаружили в некоторых системных версиях struct tm. Эти добавления являются огромным улучшением - я не могу себе представить серьезное программирование даты / времени в системе без них - но они все еще не являются стандартными, и есть еще много систем, которые неу них их нет (даже с «скрытыми» написаниями __tm_gmtoff и __tm_zone).

Вы можете прочитать намного больше о грязной истории поддержки даты / времени в C в этой статье: Программирование времени, часов и календаря в C , Эрик Рэймонд.

...