Стандартный совместимый способ преобразования std::time_t в System::DateTime?

Я уже нашел несколько ответов, связанных с преобразованием std::time_t значение для System::DateTime и назад. Тем не менее, почти все ответы, кажется, пренебрегают, что тип std::time_t на самом деле не определено в стандарте. Большинство решений просто брошены std::time_t к чему угодно или применять арифметические операции к std::time_t объект, который возможен, так как это арифметический тип, но нет никакой спецификации о результате такой операции. Я знаю, что большинство компиляторов определяют time_t как int некоторого размера, но тот факт, что он изменился с int32 в int64 во многих реализациях в последнее время показано, что изменения действительно возможны.

Итак, я пришел с этим решением, которое должно работать с любым типом std::time_t, Это работает из того, что я видел. Но мне было интересно - есть ли какие-нибудь возможные подводные камни, о которых я мог не знать?

template <>
inline System::DateTime marshal_as(const std::time_t &from_object)
{
    // Returns DateTime in Local time format from time_t (assumed to be UTC)
    const auto unix_epoch = makeUtcTime(1970, 1, 1, 0, 0, 0);
    const auto unix_epoch_dt = System::DateTime(1970, 1, 1, 0, 0, 0, System::DateTimeKind::Utc);
    const auto secondsSinceEpoch = std::difftime(from_object, unix_epoch);
    return const_cast<System::DateTime&>(unix_epoch_dt).AddSeconds(secondsSinceEpoch).ToLocalTime();
} // end of System::DateTime marshal_as(const std::time_t &from_object)

template <>
inline std::time_t marshal_as(const System::DateTime &from_object)
{
    // Returns time_t in UTC format from DateTime
    auto from_dt = const_cast<System::DateTime&>(from_object).ToUniversalTime();
    return makeUtcTime(from_dt.Year, from_dt.Month, from_dt.Day, from_dt.Hour, from_dt.Minute, from_dt.Second);
} // end of std::time_t marshal_as(const System::DateTime &from_object)

Было сделано 3 предположения:

  • Результирующий std::time_t должен быть в UTC, так как он не содержит никакой информации о локализации
  • Результирующий System::DateTime должно быть местное время с System::DateTime::Now возвращает локализованный DateTime
  • makeUtcTime вспомогательная функция, создающая std::tm из предоставленных значений и создает UTC std::time_t из этого. В настоящее время это осуществляется с использованием _mkgmtime потому что наш код взаимодействия может безопасно полагаться на существование расширений Microsoft. Тем не менее, версия UTC mktime легко доступен и в других компиляторах (стандартно mktime ожидает местное время).

2 менее важные вещи для рассмотрения:

  • const_cast необходимо, потому что шаблон marshal_as ожидает const T& в качестве параметра, и я не могу получить доступ к свойствам объекта типа const .NET. Однако может быть лучшее решение.
  • Если unix_epoch... вещи быть static const?

(Я не был уверен, стоит ли это публиковать на "Бирже программистов", так как это скорее дискуссия, но, поскольку это очень специфический вопрос C++, я подумал, что SO может быть лучше задать)

2 ответа

Решение

Просто не очень продуктивно настаивать на "стандартном совместимом" способе сделать это преобразование. Единственное место, где когда-либо встречаются std::time_t и System::DateTime, - это стандарт Ecma-372. Из которых есть сейчас и, безусловно, когда-либо будет, только одна реализация. Можно предположить, что проект Mono является наиболее вероятным источником другого проекта, но сейчас они кажутся совершенно не заинтересованными в реализации смешанного режима, единственной причины, по которой вы когда-либо рассматривали использование C++ / CLI.

std::time_t неуклонно движется к катастрофе Y2K38. Microsoft упреждающе что-то сделала с этим, и действительно должна была это сделать, потому что они выбрали LLP64, но все остальные рассчитывают на то, что их модель данных LP64 не позволит им избежать проблем. Другими словами, ни один из оставшихся 32-разрядных процессоров еще не будет запущен в 2038 году. Это вполне может быть самоисполняющимся пророчеством.

Несмотря на это, преобразование будет работать с истекшими секундами с 1 января 1970 года. И это может быть 32-разрядным или 64-разрядным интегральным значением, в зависимости от реализации. Единственная гарантия, которую я могу дать, заключается в том, что этот код по крайней мере хорош до 2038 года:

#include <ctime>

using namespace System;

public ref class Conversions {
public:
    static DateTime time_t2DateTime(std::time_t date) {
        double sec = static_cast<double>(date);
        return DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind::Utc).AddSeconds(sec);
    }
    static std::time_t DateTime2time_t(DateTime date) {
        TimeSpan diff = date.ToUniversalTime() - DateTime(1970, 1, 1);
        return static_cast<std::time_t>(diff.TotalSeconds);
    }
};

Вот решение, к которому пришла моя команда:

DateTime представляет число дней со смешанной дробью с полуночи 30 декабря 1899 года, выраженное в виде двойного числа. Я полагаю, что эта дата эпохи использовалась для объяснения того факта, что 1900 год не был високосным, и он предусматривает дополнительные два дня (почему два, а не один? - мне не понятно, почему 31 декабря 1899 года не было выбрал свою эпоху.)

Таким образом, DateTime в 2,50 будет эквивалентно 1 января 1900 года в 12:00:00 (то есть, дробь составляет 1/2 дня - 12 вечера).

Мы подсчитали, что 1 января 1970 года - эпоха Unix - через 25569 дней после эпохи DateTime.

Таким образом, эквивалентная формула будет:

#include <time.h>
System::DateTime toDateTime(time_t &t)
{
    return 25569.0 + t / 86400.0; // ensure you use floating point math or you will truncate the fractional portion
}
Другие вопросы по тегам