10

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?

Museful
  • 6,711
  • 5
  • 42
  • 68

3 Answers3

13

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

TrentP
  • 4,240
  • 24
  • 35
  • As a side comment, the code is actually dependent on designated initializers that is available only on C++20. – ceztko Sep 17 '19 at 22:39
  • They've been supported, it a limited extent that allows this code to work, in gcc for some time. One could just as easily remove the field names and not use designated initializers. – TrentP Sep 19 '19 at 00:26
  • Yes, in gcc it should be available as a gcc extension in pre c++20 compliance profiles. Do you mind if I edit the post myself? – ceztko Sep 19 '19 at 09:10
12

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).

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Doesn't this assume the date/time is in UNIX time? Which is not UTC since it doesn't have leap seconds. E.g., `sys_days{December/31/2005} + 23h + 59min + 61s` would be Jan 1st 00:00:01, when the time 23 hours 59 minutes and 61 seconds after that date was in fact Jan 1st 00:00:00 in UTC. – TrentP Sep 11 '18 at 18:57
  • You are correct. `std::chrono::system_clock::time_point` has no way to represent any time point that is during a leap second. My tz.h library has another clock/time_point set: `utc_clock`, which can represent leap seconds, but one would not be able to form a `constexpr utc_clock::time_point` because the leap second data is not compile-time information. One can create a `utc_clock::time_point` from constants at run-time though, including a time point that represents a leap second insertion. `utc_clock` is also in the draft C++20 spec. – Howard Hinnant Sep 11 '18 at 21:32
2

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

Clare Macrae
  • 3,670
  • 2
  • 31
  • 45