mktime() дает другие результаты, чем библиотека дат Говарда Хиннанта (на основе std::chrono)

Я использую библиотеку C++ даты Говарда Хиннанта (https://howardhinnant.github.io/date/date.html), но у меня возникают некоторые затруднения при его использовании. Ниже приведена программа, в которой я использую эту библиотеку для печати года-месяца-дня 3-й пятницы ноября 2017 года. Класс date::year_month_weekday при использовании с date::sys_days() показывает правильную дату (17 ноября 2017 года), но когда я ее конвертирую на struct tm с std::chrono::system_clock::to_time_t, результаты, хранящиеся в этом tm, становятся 16 ноября 2017 года. Я тестировал другие случаи, кажется, что struct tm преобразуется из date::year_month_weekday всегда на один день позже. Я что-то пропустил в своей программе? Программа указана ниже, для ее компиляции требуется C++ 11.

#include <iostream>
#include <chrono>
#include <sys/time.h>
#include "date.h"

using namespace std; 
using namespace std::chrono; 
using namespace date;

int main(int argc, char *argv[]) {  
    date::year y(2017);
    date::month m(11);
    date::weekday wd((unsigned)5);
    date::weekday_indexed wi(wd,3);
    date::year_month_weekday dmwd(y, m, wi);
    std::cout <<  date::sys_days(dmwd) << std::endl; //prints 2017-11-17, which is the 3rd Friday of Nov 2017

    time_t tt = std::chrono::system_clock::to_time_t(date::sys_days(dmwd));        
    struct tm tm1;
    localtime_r(&tt, &tm1);
    std::cout << "tm1.tm_year = " << tm1.tm_year << std::endl;
    std::cout << "tm1.tm_mon = " << tm1.tm_mon << std::endl;
    std::cout << "tm1.tm_mday = " << tm1.tm_mday << std::endl;  //prints 16 instead of 17, one day behind. tm.mday is from 1 to 31.

    return 0; 
}

Вывод этой программы выглядит следующим образом

2017-11-17
tm1.tm_year = 117  <-- 117+1900=2017
tm1.tm_mon = 10    <-- tm_mon starts form 0, so 10 means November
tm1.tm_mday = 16  <-- tm_mday starts from 1, so 16 is the 16-th day in a month

person Work Only    schedule 25.03.2017    source источник
comment
Вы пробовали использовать gmtime_r() вместо localtime_r()?   -  person Freddie Chopin    schedule 25.03.2017
comment
Да, я только что попробовал, gmtime_r дал стабильный результат, спасибо за подсказку. Поскольку date::year_month_weekday не содержит информации о времени (часах и минутах), мне интересно, как библиотека дат справляется с этим при преобразовании в struct tm. Я также печатаю tm_hour и tm_min из struct tm, преобразованных из date::year_month_weekday. Кажется, tm_hour == 18 и tm_min == 0 для всех случаев - возможно, потому что я нахожусь в часовом поясе UTC-6?   -  person Work Only    schedule 25.03.2017
comment
Fwiw, вот альтернативный синтаксис для создания dmwd: auto dmwd = fri[3]/nov/2017;   -  person Howard Hinnant    schedule 25.03.2017


Ответы (1)


дата Говарда Хиннанта.h отслеживает Время Unix, которое для большинства практических целей соответствует UTC. В этом же репозитории GitHub также есть библиотека часовых поясов, если вам нужно иметь дело с вашим местным временем или с любым другим часовым поясом, кроме UTC.

Так что да, как отметил Фредди Шопен в комментариях, вы видите эффекты localtime_r с учетом местного часового пояса вашего компьютера.

Вы можете добавить любое время дня в sys_days, которое хотите. sys_days равно std::chrono::time_point, но с точностью days. Итак, когда у вас есть sys_days, вы фактически находитесь в библиотеке <chrono>, а не в библиотеке дат:

system_clock::time_point t = date::sys_days(dmwd) + 6h + 53min + 4s + 123us:
person Howard Hinnant    schedule 25.03.2017
comment
Большое спасибо за объяснение. Еще один вопрос, я пытался изменить sys_days(dmwd) на local_days(dmwd), но он не компилируется, говоря что-то о несоответствующей функции. Как правильно использовать local_days? Должен ли я также добавить библиотеку часовых поясов в свою программу? Спасибо еще раз! - person Work Only; 25.03.2017
comment
@WorkOnly: я только что попробовал этот эксперимент, и он скомпилировался для меня. Какой компилятор/версия? Можете ли вы установить демонстрацию в каком-нибудь месте, например: wandbox.org? Вот песочница для начала: wandbox.org/permlink/HMMMIqtuG6Ia1AHY . - person Howard Hinnant; 25.03.2017
comment
Я могу без проблем скомпилировать программу в исходном посте, но когда я заменил sys_days(dmwd) на local_days(dmwd), выдается следующее сообщение об ошибке (gcc 5.4 в Ubuntu 16.04, с g++ -std=c++11 date_time.cpp): date_time.cpp: In function ‘int main(int, char**)’: date_time.cpp:18:73: error: no matching function for call to ‘std::chrono::_V2::system_clock::to_time_t(date::local_days)’ time_t tt = std::chrono::system_clock::to_time_t(date::local_days(dmwd)); ^ In file included from date_time.cpp:2:0: - person Work Only; 26.03.2017
comment
(продолжение) /usr/include/c++/5/chrono:734:7: note: candidate: static time_t std::chrono::_V2::system_clock::to_time_t(const time_point&) to_time_t(const time_point& __t) noexcept ^ /usr/include/c++/5/chrono:734:7: note: no known conversion for argument 1 from ‘date::local_days {aka std::chrono::time_point<date::local_t, std::chrono::duration<int, std::ratio<86400l, 1l> > >}’ to ‘const time_point& {aka const std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long int, std::ratio<1l, 1000000000l> > >&}’ - person Work Only; 26.03.2017
comment
@WorkOnly: Хорошо, это имеет смысл. system_clock::to_time_t принимает system_clock::time_point в качестве аргумента. sys_days является typedef для грубого system_clock::time_point и неявно преобразуется в system_clock::time_point. Принимая во внимание, что local_days — это chrono::time_point, не имеющий никакого отношения к system_clock. Ошибка времени компиляции служит для того, чтобы вы случайно не перепутали эти два семейства time_point. - person Howard Hinnant; 26.03.2017
comment
В моем собственном эксперименте я заменил неправильный date::sys_days(dmwd). :-) - person Howard Hinnant; 26.03.2017
comment
Спасибо за объяснение, Говард. (Извините, я не знаю, как @ здесь) - person Work Only; 26.03.2017