QDateTime: add conversions for time_point and zoned_time
In C++20, QDateTime is a direct equivalent of a sys_time<milliseconds> time point. (Before, it might not have been, because system_clock before C++20 was not guaranteed to be tracking Unix time, AKA UTC time without leap seconds.) To be specific, sys_time<milliseconds> corresponds to a QDateTime using the Qt::UTC timespec. This patch: 1) adds named constructors taking time_points: * a generic one taking any time_point convertible (via clock_cast) to a system_clock (this obviously includes system_clock, but also e.g. utc_clock) * another couple taking local_time, interpreted as a duration from 1/1/1970 in local time. 2) adds a named constructor from zoned_time (i.e. a sys_time + a timezone), that we can easily support via QTimeZone. 3) add conversion functions towards sys_time, matching the existing to(M)SecsSinceEpoch() functions. [ChangeLog][QtCore][QDateTime] QDateTime can now be constructed from std::chrono::time_point objects (including local_time), as well as from std::chrono::zoned_time objects. Moreover, they can be converted to std::chrono::time_point using system_clock as their clock. Change-Id: Ic6409bde43bc3e745d9df6257e0a77157472352d Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
parent
a5b9600356
commit
7de83f06c1
@ -4963,6 +4963,85 @@ bool QDateTime::precedes(const QDateTime &other) const
|
||||
\sa currentMSecsSinceEpoch()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Clock, typename Duration> QDateTime QDateTime::fromStdTimePoint(const std::chrono::time_point<Clock, Duration> &time)
|
||||
\since 6.4
|
||||
|
||||
Constructs a datetime representing the same point in time as \a time,
|
||||
using Qt::UTC as its specification.
|
||||
|
||||
The clock of \a time must be compatible with \c{std::chrono::system_clock},
|
||||
and the duration type must be convertible to \c{std::chrono::milliseconds}.
|
||||
|
||||
\note This function requires C++20.
|
||||
|
||||
\sa toStdSysMilliseconds(), fromMSecsSinceEpoch()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QDateTime QDateTime::fromStdTimePoint(const std::chrono::local_time<std::chrono::milliseconds> &time)
|
||||
\since 6.4
|
||||
|
||||
Constructs a datetime whose date and time are the number of milliseconds
|
||||
represented by \a time, counted since 1970-01-01T00:00:00.000 in local
|
||||
time (Qt::LocalTime).
|
||||
|
||||
\note This function requires C++20.
|
||||
|
||||
\sa toStdSysMilliseconds(), fromMSecsSinceEpoch()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QDateTime QDateTime::fromStdLocalTime(const std::chrono::local_time<std::chrono::milliseconds> &time)
|
||||
\since 6.4
|
||||
|
||||
Constructs a datetime whose date and time are the number of milliseconds
|
||||
represented by \a time, counted since 1970-01-01T00:00:00.000 in local
|
||||
time (Qt::LocalTime).
|
||||
|
||||
\note This function requires C++20.
|
||||
|
||||
\sa toStdSysMilliseconds(), fromMSecsSinceEpoch()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn QDateTime QDateTime::fromStdZonedTime(const std::chrono::zoned_time<std::chrono::milliseconds, const std::chrono::time_zone *> &time);
|
||||
\since 6.4
|
||||
|
||||
Constructs a datetime representing the same point in time as \a time.
|
||||
The result will be expressed in \a{time}'s time zone.
|
||||
|
||||
\note This function requires C++20.
|
||||
|
||||
\sa QTimeZone
|
||||
|
||||
\sa toStdSysMilliseconds(), fromMSecsSinceEpoch()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn std::chrono::sys_time<std::chrono::milliseconds> QDateTime::toStdSysMilliseconds() const
|
||||
\since 6.4
|
||||
|
||||
Converts this datetime object to the equivalent time point expressed in
|
||||
milliseconds, using \c{std::chrono::system_clock} as a clock.
|
||||
|
||||
\note This function requires C++20.
|
||||
|
||||
\sa fromStdTimePoint(), toMSecsSinceEpoch()
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn std::chrono::sys_seconds QDateTime::toStdSysSeconds() const
|
||||
\since 6.4
|
||||
|
||||
Converts this datetime object to the equivalent time point expressed in
|
||||
seconds, using \c{std::chrono::system_clock} as a clock.
|
||||
|
||||
\note This function requires C++20.
|
||||
|
||||
\sa fromStdTimePoint(), toSecsSinceEpoch()
|
||||
*/
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
static inline uint msecsFromDecomposed(int hour, int minute, int sec, int msec = 0)
|
||||
{
|
||||
|
@ -438,6 +438,64 @@ public:
|
||||
NSDate *toNSDate() const Q_DECL_NS_RETURNS_AUTORELEASED;
|
||||
#endif
|
||||
|
||||
#if __cpp_lib_chrono >= 201907L || defined(Q_QDOC)
|
||||
#if __cpp_concepts >= 201907L || defined(Q_QDOC)
|
||||
// Generic clock, as long as it's compatible with us (= system_clock)
|
||||
template <typename Clock, typename Duration>
|
||||
static QDateTime fromStdTimePoint(const std::chrono::time_point<Clock, Duration> &time)
|
||||
requires
|
||||
requires(const std::chrono::time_point<Clock, Duration> &t) {
|
||||
// the clock can be converted to system_clock
|
||||
std::chrono::clock_cast<std::chrono::system_clock>(t);
|
||||
// the duration can be converted to milliseconds
|
||||
requires std::is_convertible_v<Duration, std::chrono::milliseconds>;
|
||||
}
|
||||
{
|
||||
const auto sysTime = std::chrono::clock_cast<std::chrono::system_clock>(time);
|
||||
// clock_cast can change the duration, so convert it again to milliseconds
|
||||
const auto timeInMSec = std::chrono::time_point_cast<std::chrono::milliseconds>(sysTime);
|
||||
return fromMSecsSinceEpoch(timeInMSec.time_since_epoch().count(), Qt::UTC);
|
||||
}
|
||||
#endif // __cpp_concepts
|
||||
|
||||
// local_time
|
||||
QT_POST_CXX17_API_IN_EXPORTED_CLASS
|
||||
static QDateTime fromStdTimePoint(const std::chrono::local_time<std::chrono::milliseconds> &time)
|
||||
{
|
||||
return fromStdLocalTime(time);
|
||||
}
|
||||
|
||||
QT_POST_CXX17_API_IN_EXPORTED_CLASS
|
||||
static QDateTime fromStdLocalTime(const std::chrono::local_time<std::chrono::milliseconds> &time)
|
||||
{
|
||||
QDateTime result(QDate(1970, 1, 1), QTime(0, 0, 0), Qt::LocalTime);
|
||||
return result.addMSecs(time.time_since_epoch().count());
|
||||
}
|
||||
|
||||
#if QT_CONFIG(timezone)
|
||||
// zoned_time. defined in qtimezone.h
|
||||
QT_POST_CXX17_API_IN_EXPORTED_CLASS
|
||||
static QDateTime fromStdZonedTime(const std::chrono::zoned_time<
|
||||
std::chrono::milliseconds,
|
||||
const std::chrono::time_zone *
|
||||
> &time);
|
||||
#endif // QT_CONFIG(timezone)
|
||||
|
||||
QT_POST_CXX17_API_IN_EXPORTED_CLASS
|
||||
std::chrono::sys_time<std::chrono::milliseconds> toStdSysMilliseconds() const
|
||||
{
|
||||
const std::chrono::milliseconds duration(toMSecsSinceEpoch());
|
||||
return std::chrono::sys_time(duration);
|
||||
}
|
||||
|
||||
QT_POST_CXX17_API_IN_EXPORTED_CLASS
|
||||
std::chrono::sys_seconds toStdSysSeconds() const
|
||||
{
|
||||
const std::chrono::seconds duration(toSecsSinceEpoch());
|
||||
return std::chrono::sys_seconds(duration);
|
||||
}
|
||||
#endif // __cpp_lib_chrono >= 201907L
|
||||
|
||||
friend std::chrono::milliseconds operator-(const QDateTime &lhs, const QDateTime &rhs)
|
||||
{
|
||||
return std::chrono::milliseconds(rhs.msecsTo(lhs));
|
||||
|
@ -200,6 +200,20 @@ Q_CORE_EXPORT QDataStream &operator>>(QDataStream &ds, QTimeZone &tz);
|
||||
Q_CORE_EXPORT QDebug operator<<(QDebug dbg, const QTimeZone &tz);
|
||||
#endif
|
||||
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
// zoned_time
|
||||
template <typename> // QT_POST_CXX17_API_IN_EXPORTED_CLASS
|
||||
inline QDateTime QDateTime::fromStdZonedTime(const std::chrono::zoned_time<
|
||||
std::chrono::milliseconds,
|
||||
const std::chrono::time_zone *
|
||||
> &time)
|
||||
{
|
||||
const auto sysTime = time.get_sys_time();
|
||||
const QTimeZone timeZone = QTimeZone::fromStdTimeZonePtr(time.get_time_zone());
|
||||
return fromMSecsSinceEpoch(sysTime.time_since_epoch().count(), timeZone);
|
||||
}
|
||||
#endif
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QTIMEZONE_H
|
||||
|
@ -152,6 +152,15 @@ private Q_SLOTS:
|
||||
|
||||
void macTypes();
|
||||
|
||||
void stdCompatibilitySysTime_data();
|
||||
void stdCompatibilitySysTime();
|
||||
void stdCompatibilityLocalTime_data();
|
||||
void stdCompatibilityLocalTime();
|
||||
#if QT_CONFIG(timezone)
|
||||
void stdCompatibilityZonedTime_data();
|
||||
void stdCompatibilityZonedTime();
|
||||
#endif
|
||||
|
||||
private:
|
||||
enum { LocalTimeIsUtc = 0, LocalTimeAheadOfUtc = 1, LocalTimeBehindUtc = -1} localTimeType;
|
||||
int preZoneFix;
|
||||
@ -4121,5 +4130,223 @@ void tst_QDateTime::macTypes()
|
||||
#endif
|
||||
}
|
||||
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
using StdSysMillis = std::chrono::sys_time<std::chrono::milliseconds>;
|
||||
Q_DECLARE_METATYPE(StdSysMillis);
|
||||
#endif
|
||||
|
||||
void tst_QDateTime::stdCompatibilitySysTime_data()
|
||||
{
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
QTest::addColumn<StdSysMillis>("sysTime");
|
||||
QTest::addColumn<QDateTime>("expected");
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
QTest::newRow("zero")
|
||||
<< StdSysMillis(0s)
|
||||
<< QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0), Qt::UTC);
|
||||
QTest::newRow("1s")
|
||||
<< StdSysMillis(1s)
|
||||
<< QDateTime(QDate(1970, 1, 1), QTime(0, 0, 1), Qt::UTC);
|
||||
QTest::newRow("1ms")
|
||||
<< StdSysMillis(1ms)
|
||||
<< QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0, 1), Qt::UTC);
|
||||
QTest::newRow("365d")
|
||||
<< StdSysMillis(days(365))
|
||||
<< QDateTime(QDate(1971, 1, 1), QTime(0, 0, 0), Qt::UTC);
|
||||
QTest::newRow("-1s")
|
||||
<< StdSysMillis(-1s)
|
||||
<< QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), Qt::UTC);
|
||||
QTest::newRow("-1ms")
|
||||
<< StdSysMillis(-1ms)
|
||||
<< QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59, 999), Qt::UTC);
|
||||
|
||||
{
|
||||
// The first leap second occurred on 30 June 1972 at 23:59:60.
|
||||
// Check that QDateTime does not take that leap second into account (like sys_time)
|
||||
const year_month_day firstLeapSecondDate = 1972y/July/1;
|
||||
const sys_days firstLeapSecondDateAsSysDays = firstLeapSecondDate;
|
||||
QTest::newRow("first_leap_second")
|
||||
<< StdSysMillis(firstLeapSecondDateAsSysDays)
|
||||
<< QDateTime(QDate(1972, 7, 1), QTime(0, 0, 0), Qt::UTC);
|
||||
}
|
||||
|
||||
{
|
||||
// Random date
|
||||
const sys_days date = 2000y/January/31;
|
||||
const StdSysMillis dateTime = date + 3h + 10min + 42s;
|
||||
QTest::newRow("2000-01-31 03:10:42")
|
||||
<< dateTime
|
||||
<< QDateTime(QDate(2000, 1, 31), QTime(3, 10, 42), Qt::UTC);
|
||||
}
|
||||
#else
|
||||
QSKIP("This test requires C++20's <chrono>.");
|
||||
#endif
|
||||
}
|
||||
|
||||
void tst_QDateTime::stdCompatibilitySysTime()
|
||||
{
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
QFETCH(StdSysMillis, sysTime);
|
||||
QFETCH(QDateTime, expected);
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
// system_clock in milliseconds -> QDateTime
|
||||
QDateTime dtFromSysTime = QDateTime::fromStdTimePoint(sysTime);
|
||||
QCOMPARE(dtFromSysTime, expected);
|
||||
QCOMPARE(dtFromSysTime.timeSpec(), Qt::UTC);
|
||||
|
||||
// QDateTime -> system_clock in milliseconds
|
||||
StdSysMillis sysTimeFromDt = dtFromSysTime.toStdSysMilliseconds();
|
||||
QCOMPARE(sysTimeFromDt, sysTime);
|
||||
|
||||
// system_clock in seconds -> QDateTime
|
||||
sys_seconds sysTimeSecs = floor<seconds>(sysTime);
|
||||
QDateTime dtFromSysSeconds = QDateTime::fromStdTimePoint(sysTimeSecs);
|
||||
QDateTime expectedInSeconds = expected.addMSecs(-expected.time().msec()); // "floor"
|
||||
QCOMPARE(dtFromSysSeconds, expectedInSeconds);
|
||||
QCOMPARE(dtFromSysSeconds.timeSpec(), Qt::UTC);
|
||||
|
||||
// QDateTime -> system_clock in seconds
|
||||
sys_seconds sysTimeFromDtSecs = dtFromSysSeconds.toStdSysSeconds();
|
||||
QCOMPARE(sysTimeFromDtSecs, sysTimeSecs);
|
||||
|
||||
// utc_clock in milliseconds -> QDateTime
|
||||
utc_time<std::chrono::milliseconds> utcTime = utc_clock::from_sys(sysTime);
|
||||
QDateTime dtFromUtcTime = QDateTime::fromStdTimePoint(utcTime);
|
||||
QCOMPARE(dtFromUtcTime, expected);
|
||||
QCOMPARE(dtFromUtcTime.timeSpec(), Qt::UTC);
|
||||
|
||||
// QDateTime -> system_clock in milliseconds
|
||||
sysTimeFromDt = dtFromUtcTime.toStdSysMilliseconds();
|
||||
QCOMPARE(sysTimeFromDt, sysTime);
|
||||
#else
|
||||
QSKIP("This test requires C++20's <chrono>.");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
using StdLocalMillis = std::chrono::local_time<std::chrono::milliseconds>;
|
||||
Q_DECLARE_METATYPE(StdLocalMillis);
|
||||
#endif
|
||||
|
||||
void tst_QDateTime::stdCompatibilityLocalTime_data()
|
||||
{
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
QTest::addColumn<StdLocalMillis>("localTime");
|
||||
QTest::addColumn<QDateTime>("expected");
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
QTest::newRow("zero")
|
||||
<< StdLocalMillis(0s)
|
||||
<< QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0), Qt::LocalTime);
|
||||
QTest::newRow("1s")
|
||||
<< StdLocalMillis(1s)
|
||||
<< QDateTime(QDate(1970, 1, 1), QTime(0, 0, 1), Qt::LocalTime);
|
||||
QTest::newRow("1ms")
|
||||
<< StdLocalMillis(1ms)
|
||||
<< QDateTime(QDate(1970, 1, 1), QTime(0, 0, 0, 1), Qt::LocalTime);
|
||||
QTest::newRow("365d")
|
||||
<< StdLocalMillis(days(365))
|
||||
<< QDateTime(QDate(1971, 1, 1), QTime(0, 0, 0), Qt::LocalTime);
|
||||
QTest::newRow("-1s")
|
||||
<< StdLocalMillis(-1s)
|
||||
<< QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59), Qt::LocalTime);
|
||||
QTest::newRow("-1ms")
|
||||
<< StdLocalMillis(-1ms)
|
||||
<< QDateTime(QDate(1969, 12, 31), QTime(23, 59, 59, 999), Qt::LocalTime);
|
||||
{
|
||||
// Random date
|
||||
const local_days date = local_days(2000y/January/31);
|
||||
const StdLocalMillis dateTime = date + 3h + 10min + 42s;
|
||||
QTest::newRow("2000-01-31 03:10:42")
|
||||
<< dateTime
|
||||
<< QDateTime(QDate(2000, 1, 31), QTime(3, 10, 42), Qt::LocalTime);
|
||||
}
|
||||
#else
|
||||
QSKIP("This test requires C++20's <chrono>.");
|
||||
#endif
|
||||
}
|
||||
|
||||
void tst_QDateTime::stdCompatibilityLocalTime()
|
||||
{
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
QFETCH(StdLocalMillis, localTime);
|
||||
QFETCH(QDateTime, expected);
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
QDateTime dtFromLocalTime = QDateTime::fromStdLocalTime(localTime);
|
||||
QCOMPARE(dtFromLocalTime, expected);
|
||||
QCOMPARE(dtFromLocalTime.timeSpec(), Qt::LocalTime);
|
||||
|
||||
const time_zone *tz = current_zone();
|
||||
QVERIFY(tz);
|
||||
const StdSysMillis sysMillis = tz->to_sys(localTime);
|
||||
QCOMPARE(dtFromLocalTime.toStdSysMilliseconds(), sysMillis);
|
||||
#else
|
||||
QSKIP("This test requires C++20's <chrono>.");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if QT_CONFIG(timezone)
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
using StdZonedMillis = std::chrono::zoned_time<std::chrono::milliseconds>;
|
||||
Q_DECLARE_METATYPE(StdZonedMillis);
|
||||
#endif
|
||||
|
||||
void tst_QDateTime::stdCompatibilityZonedTime_data()
|
||||
{
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
QTest::addColumn<StdZonedMillis>("zonedTime");
|
||||
QTest::addColumn<QDateTime>("expected");
|
||||
|
||||
using namespace std::chrono;
|
||||
using namespace std::literals;
|
||||
|
||||
const char timeZoneName[] = "Europe/Oslo";
|
||||
const QTimeZone timeZone(timeZoneName);
|
||||
|
||||
{
|
||||
StdZonedMillis zs(timeZoneName, local_days(2021y/1/1));
|
||||
QTest::addRow("localTimeOslo")
|
||||
<< zs
|
||||
<< QDateTime(QDate(2021, 1, 1), QTime(0, 0, 0), timeZone);
|
||||
}
|
||||
{
|
||||
StdZonedMillis zs(timeZoneName, sys_days(2021y/1/1));
|
||||
QTest::addRow("sysTimeOslo")
|
||||
<< zs
|
||||
<< QDateTime(QDate(2021, 1, 1), QTime(1, 0, 0), timeZone);
|
||||
}
|
||||
{
|
||||
StdZonedMillis zs(timeZoneName, sys_days(2021y/7/1));
|
||||
QTest::addRow("sysTimeOslo summer")
|
||||
<< zs
|
||||
<< QDateTime(QDate(2021, 7, 1), QTime(2, 0, 0), timeZone);
|
||||
}
|
||||
#else
|
||||
QSKIP("This test requires C++20's <chrono>.");
|
||||
#endif
|
||||
}
|
||||
|
||||
void tst_QDateTime::stdCompatibilityZonedTime()
|
||||
{
|
||||
#if __cpp_lib_chrono >= 201907L
|
||||
QFETCH(StdZonedMillis, zonedTime);
|
||||
QFETCH(QDateTime, expected);
|
||||
|
||||
QDateTime dtFromZonedTime = QDateTime::fromStdZonedTime(zonedTime);
|
||||
QCOMPARE(dtFromZonedTime, expected);
|
||||
QCOMPARE(dtFromZonedTime.timeSpec(), Qt::TimeZone);
|
||||
#else
|
||||
QSKIP("This test requires C++20's <chrono>.");
|
||||
#endif
|
||||
}
|
||||
#endif // QT_CONFIG(timezone)
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_QDateTime)
|
||||
#include "tst_qdatetime.moc"
|
||||
|
Loading…
Reference in New Issue
Block a user