Стандартный совместимый способ преобразования 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
из предоставленных значений и создает UTCstd::time_t
из этого. В настоящее время это осуществляется с использованием_mkgmtime
потому что наш код взаимодействия может безопасно полагаться на существование расширений Microsoft. Тем не менее, версия UTCmktime
легко доступен и в других компиляторах (стандартно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
}