Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a `std::chrono::time_point` from a calendar date known at compile time

Tags:

c++

c++14

chrono

This answer shows how to parse a string to a std::chrono::time_point, as follows:

std::tm tm = {};
std::stringstream ss("Jan 9 2014 12:35:34");
ss >> std::get_time(&tm, "%b %d %Y %H:%M:%S");
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));

If I want to create a std::chrono::time_point from a (Gregorian) calendar date whose year, month, and day-of-month are known at compile time, is there any simpler way than parsing it from a string as suggested above?

like image 615
Museful Avatar asked Sep 08 '18 20:09

Museful


3 Answers

If you have c++20, or will use Howard Hinnant date/time library, then Howard Hannant's answer is better, as it gives you a constexpr time_point.

However, if one doesn't yet have c++20 and wants to avoid adding more external libraries, then this answer is still useful.

You can set the members of the std::tm individually in the initializer, to avoid parsing a string.

// 9th January, 2014
#define DAY 9
#define MONTH 1
#define YEAR 2014

std::tm tm = { /* .tm_sec  = */ 0,
               /* .tm_min  = */ 0,
               /* .tm_hour = */ 0,
               /* .tm_mday = */ (DAY),
               /* .tm_mon  = */ (MONTH) - 1,
               /* .tm_year = */ (YEAR) - 1900,
             };
tm.tm_isdst = -1; // Use DST value from local time zone
auto tp = std::chrono::system_clock::from_time_t(std::mktime(&tm));

The designated initializers are commented out since they are only available in C++20 (though gcc has supported trivial designated initializers as an extension for some time and would work with this case). The fields initialized to zero could be omitted if one had full C++20 designated initializers and wanted midnight on the target date.

It is important to note that mktime will interpret the tm as local time, not GMT nor UTC. If tm_isdst is not set to -1, it will be local standard time, even if daylight savings (summer time) would be in use in the local time zone for the time specified.

Producing a UTC time point from a std::tm, a problem shared with your example, is addressed in other questions, such as Easy way to convert a struct tm (expressed in UTC) to time_t type

like image 103
TrentP Avatar answered Nov 06 '22 13:11

TrentP


Yes, you can do the entire computation at compile time, creating a constexpr system_clock::time_point using Howard Hinnant's date/time library.

#include "date/date.h"
#include <chrono>

int
main()
{
    using namespace date;
    using namespace std::chrono;
    constexpr system_clock::time_point tp = sys_days{January/9/2014} + 12h + 35min + 34s;
    static_assert(tp == system_clock::time_point{1389270934s}, "");
}

This is assuming that the date/time is UTC. If it isn't, you will have to manually add/subtract the UTC offset to make it so. As time zone rules are changed at the whim of politicians all the time, there is little hope in making them constexpr. Even historical time zone rules are updated when misunderstandings come to light.

Also this program will port to C++20 by dropping #include "date/date.h" and using namespace date;. Also using Howard Hinnant's date/time library requires C++14 constexpr muscle. C++11 constexpr is not sufficient (but you can do it at run-time, dropping the constexpr and static_assert).

like image 11
Howard Hinnant Avatar answered Nov 06 '22 12:11

Howard Hinnant


This works with C++11 and above:

#include <chrono>

std::chrono::system_clock::time_point
createDateTime(int year,
               int month,
               int day,
               int hour,
               int minute,
               int second) // these are UTC values
{
    tm timeinfo1 = tm();
    timeinfo1.tm_year = year - 1900;
    timeinfo1.tm_mon = month - 1;
    timeinfo1.tm_mday = day;
    timeinfo1.tm_hour = hour;
    timeinfo1.tm_min = minute;
    timeinfo1.tm_sec = second;
    tm timeinfo = timeinfo1;
    time_t tt = toUTC(timeinfo);
    return std::chrono::system_clock::from_time_t(tt);
}

time_t toUTC(std::tm& timeinfo)
{
#ifdef _WIN32
    std::time_t tt = _mkgmtime(&timeinfo);
#else
    time_t tt = timegm(&timeinfo);
#endif
    return tt;
}

Taken from ApprovalTests/utilities/DateUtils.cpp

like image 1
Clare Macrae Avatar answered Nov 06 '22 13:11

Clare Macrae