2017-02-16 19 views
0

Я пытаюсь получить текущий год, хранящийся в дате от 1970 года, используя std::chrono::time_point<std::chrono::system_clock>, однако я столкнулся с проблемой, связанной с чтением из ее содержимого в структуру std::tm.localtime_s не удается, когда gmtime_s преуспевает с датами до 1-1-1970

Сначала конвертируйте time_point в time_t, после чего я прочитал его значения, чтобы получить значение tm_year. Однако при попытке сделать это код не работает при использовании localtime_s, однако он преуспевает, когда я использую gmtime_s. Это только для дат до 1-1-1970, даты после этого работают отлично, используя обе функции.

Приведенный ниже код воспроизводит ошибку. Если terstGmTimeVsLocalTime вызывается с utc=true, он работает, если он вызывается с utc=false, он не производит правильный вывод.

#include <iomanip> 
#include <time.h> 
#include <iostream> 

void testGmTimeVsLocaltime(const bool& utc) { 
    // Create time 
    std::tm timeInfoWrite = std::tm(); 
    timeInfoWrite.tm_year = 1969 - 1900; // Year to parse, here it is 1969 
    timeInfoWrite.tm_mon = 0; 
    timeInfoWrite.tm_mday = 1; 
    timeInfoWrite.tm_hour = 1; 
    timeInfoWrite.tm_min = 0; 
    timeInfoWrite.tm_sec = 0; 
    timeInfoWrite.tm_isdst = -1; 

    std::chrono::time_point<std::chrono::system_clock> timePoint = std::chrono::system_clock::from_time_t(utc ? _mkgmtime(&timeInfoWrite) : std::mktime(&timeInfoWrite)); 

    // Convert to time_t 
    std::time_t timeT = std::chrono::system_clock::to_time_t(timePoint); 

    // Read values 
    std::tm timeInfoRead; 
    if (utc) { 
     gmtime_s(&timeInfoRead, &timeT); 
    } else { 
     localtime_s(&timeInfoRead, &timeT); 
    } 

    // Output result 
    std::cout << (timeInfoRead.tm_year + 1900) << '\n'; 

    // Wait for input 
    std::getchar(); 
} 

int main() { 
    testGmTimeVsLocaltime(true); // Set to false to show bug 

    return 0; 
} 

utc=true выходов 1969, как и следовало ожидать. Однако utc=false выдает 1899 (предположительно, так как возникает ошибка, а tm_year устанавливается в -1).

Есть ли что-нибудь, что мне не хватает? The documentation конкретно не указывает, что localtime_s должен завершиться с даты до 1-1-1970.

Я нахожусь в Windows 10 x64, если это имеет значение.

+0

Документы MSDN предполагают, что это не удастся: «До полуночи, 1 января 1970 года». См. Https://msdn.microsoft.com/en-us/library/bf12f0hc.aspx –

+0

@RichardCritten Ах, я вижу, я, должно быть, использовал конкретную реализацию ОС. Не понял, что «localtime_s» не входит в стандарт. Будет ли еще одна функция, которая не подведет значения до полуночи, 1 января 1970 года? Или, может быть, другая стратегия? Я предполагаю, что могу получить разницу между UTC и localtime, просто используйте функцию 'gmtime_s', а затем добавьте разницу в результат, когда требуется локальное время, однако похоже, что это можно сделать проще. – Qub1

+0

Документы, которые вы связываете, говорят _ «Преобразует заданное время с __epoch __» _; где эпоха - это когда реализация определяет время начала.В Windows есть вызовы ОС, которые будут работать в любое время, но я не знаю ни одного стандартного API. –

ответ

1

Использование Howard Hinnant's free, open-source date lib, вы можете полностью боковой шаг неуклюжей, ошибку и ошибку подверженной C API, и работать непосредственно с современной <chrono> системы, основанной:

#include "chrono_io.h" 
#include "date.h" 
#include <iostream> 

void 
testGmTimeVsLocaltime() 
{ 
    using namespace date; 
    // Create time 
    auto timeInfoWrite = 1969_y/jan/1; 
    sys_days timePoint = timeInfoWrite; // this is a chrono::time_point 
    std::cout << timePoint.time_since_epoch() << '\n'; // -365 days 

    // Convert to time_t 
    // no need 

    // Read values 
    year_month_day timeInfoRead = timePoint; 

    // Output result 
    std::cout << timeInfoRead.year() << '\n'; 
} 

int 
main() 
{ 
    testGmTimeVsLocaltime(); 
} 

Выход:

-365[86400]s 
1969 

Существуют литералы, которые упрощают заполнение структуры year_month_day, которая является аналогом года, месяца и дня, составляющих tm. Вы можете легко преобразовать это в std::chrono::time_point<system_clock, days> (sys_days). Это то же самое, что и system_clock::time_point, но с точностью до нескольких дней. Он сам будет неявно преобразовать в точность секунды time_point (typedef'd до sys_seconds) или к system_clock::time_point.

Выше я только что привел его time_since_epoch(), который показывает, что это -365 дней до эпохи.

Нет необходимости конвертировать в структуры данных API C, но это легко, если вы хотите. Например, если предположить time_t находится в секундах с 1970-01-01:

std::time_t timeT = sys_seconds{timePoint}.time_since_epoch().count(); 
std::cout << timeT << '\n'; 

, который выводит:

-31536000 

Обратное преобразование (обратно в year_month_day) так же легко.Если вы хотите конвертировать timeT это просто немного сложнее:

year_month_day timeInfoRead = floor<days>(sys_seconds{seconds{timeT}}); 

Это первые обращенные к time_tchrono::seconds, а затем к seconds -precsion time_point, а затем к days -precsion time_point, наконец, year_month_day поле type (tm -like).

Идентификационный номер year_month_day имеет функцию-член-член year(), которая является плавной. Вы можете явно преобразовать year в int при желании:

int{timeInfoRead.year()} 

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

И, наконец, если вы действительно хотели, чтобы вы набрали номер вашего компьютера , местный часовой пояс, there's a library, чтобы сделать это. И это всего лишь незначительная модификация простой программы выше.

#include "tz.h" 
#include <iostream> 

void 
testGmTimeVsLocaltime() 
{ 
    using namespace date; 
    using namespace std::chrono; 
    // Create time 
    auto timeInfoWrite = 1969_y/jan/1; 
    auto timePoint = make_zoned(current_zone(), local_days{timeInfoWrite}); 

    // Convert to time_t 
    std::time_t timeT = timePoint.get_sys_time().time_since_epoch().count(); 
    std::cout << timeT << '\n'; 

    // Read values 
    timePoint = sys_seconds{seconds{timeT}}; 
    year_month_day timeInfoRead{floor<days>(timePoint.get_local_time())}; 

    // Output result 
    std::cout << timeInfoRead.year() << '\n'; 
} 

int 
main() 
{ 
    testGmTimeVsLocaltime(); 
} 

Выход:

-31518000 
1969 

Теперь вы создаете zoned_seconds используя current_zone() часовой пояс компьютера и преобразования вашего timeInfoWrite в local_days вместо к sys_days.

Вы можете получить сообщение по времени по времени выхода из timePoint. Для преобразования в time_t, время система имеет наибольший смысл:

std::time_t timeT = timePoint.get_sys_time().time_since_epoch().count(); 

и теперь выход (для меня) является 5h позже (18000s).

-31518000 

Вы можете получить обратно либо локальный год, или система (UTC) год, либо с помощью .get_local_time() или .get_sys_time(). Для меня это не имеет значения ("America/New_York"). Но если вы в "Australia/Sydney", вы получите 1968 год, если вы запросите год UTC вместо 1969 года. И это очень легко имитировать, просто заменив "Australia/Sydney" или "America/New_York" на current_zone() в программе выше.

Да, это работает на Windows, VS-2013 и позже. Существует некоторая установка, требуемая для часового пояса lib: https://howardhinnant.github.io/date/tz.html#Installation