3

I'm having troubles building a UTC date in c++ using the standard library. For example (any is fine):

  • building from separate variables containing day month year...

or parsing

  • 01/01/1970 00:00:00 UTC

  • 01/01/1970 00:00:00 +0000

  • 01/01/1970 00:00:00 # (implicitely in UTC)

Conditions:

  • The timezone string does not need to be interpreted but I need to be sure the date is interpreted as UTC.
  • the date is always correctly formated
  • any formatting is accepted as long as it does the job
  • should be portable (UNIX variants and Windows)
  • avoid external libraries if possible

The best candidates so far are:

  • std::time_get but specification doesn't say anything about the timezone (for what I understood of it :-) ).
  • boost's date_time, but its a pretty heavy dependency (mentioned in C++ library (unix) to parse date/time string Including timezones, but the answer predates C++11)
  • unix strptime is not portable on windows
  • messing around with std::tm directly, but on my system (glibc), it has non-standard undocumented fields (namely tm_zone ...). I suppose I could initialize it with gmtime(0) then modify the days, month, year fields.

How can I build an std::chrono::time_point::time_point from the UTC date?

Community
  • 1
  • 1
pixelou
  • 748
  • 6
  • 17

2 Answers2

2

How can I build a std::chrono::system_clock::time_point from the UTC date?

Just for this part of it, here is a header-only high-performance C++11/14 modern solution. It can be used like this:

int y = 2016;
int m = 1;
int d = 14;
int h = 18;
int min = 2;
int sec = 15;
std::chrono::system_clock::time_point tp = 
    date::sys_days{date::year{y}/m/d} +
    std::chrono::hours{h} +
    std::chrono::minutes{m} +
    std::chrono::seconds{sec};

This is an open source, free library on github. Here is a video presentation of the library. This library is portable to all recent implementations of C++11 and C++14 (including VS2013 and VS2015). It offers a better interface than tm inherited from C, and a greater range of validity. It also offers higher performance, and a great deal of compile-time abilities in C++14. It is designed to be a simple extension of <chrono>, and is thus fully interoperable with <chrono>.

For parsing, use tz.h:

01/01/1970 00:00:00 UTC

#include "tz.h"
#include <iostream>
#include <sstream>

int
main()
{
    std::istringstream in{"01/01/1970 00:00:00 UTC"};
    date::sys_seconds tp;
    date::parse(in, "%d/%m/%Y %T %Z", tp);
    using namespace date;
    if (!in.fail())
        std::cout << tp << '\n';
}

In the above code, sys_seconds is a std::chrono::time_point<system_clock, seconds>.

01/01/1970 00:00:00 +0000

Change %Z to %zabove.

01/01/1970 00:00:00

Remove the %Z/%z.

  • avoid external libraries if possible

Sorry, but otherwise you have to:

  • messing around with std::tm directly,...

This library allows you to parse both %z and %Z. You can throw away the abbreviation, or discover it, and try to figure out what it means (in general the abbreviation is ambiguous). With %z the offset will alter the parsed value to give you UTC:

std::istringstream in{"01/01/1970 00:00:00 +0500"};
date::sys_seconds tp;
date::parse(in, "%d/%m/%Y %T %z", tp);
using namespace date;
if (!in.fail())
    std::cout << tp << '\n';

Output:

1969-12-31 19:00:00

If you would rather get the local time, that is easy too:

std::istringstream in{"01/01/1970 00:00:00 +0500"};
date::local_seconds tp;
date::parse(in, "%d/%m/%Y %T %z", tp);
using namespace date;
if (!in.fail())
    std::cout << tp << '\n';

Output:

1970-01-01 00:00:00

local_seconds is a std::chrono::time_point, but based on a "clock" which has no now().

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • Correct me if I am wrong, but I see : `using day_point = std::chrono::time_point;` which `counts the days since (or before) jan/1/1970`, however, c++ specification gives no assurance that the origin is actually 1 jan 1970. – pixelou Jan 15 '16 at 16:24
  • @pixelou: You are exactly correct. This is a de facto standard, not an official one. Therefore the onus is on my date.h library to make sure it takes the correct epoch into account everywhere it is ported to. So far, everywhere it has been ported, `std::chrono::system_clock` is measuring Unix Time (https://en.wikipedia.org/wiki/Unix_time). I have a long track record of writing non-portable code so that others don't have to. ;-) – Howard Hinnant Jan 15 '16 at 16:36
  • Here is a record of my low-level study on the issue of `system_clock` epochs. It contains code you can run yourself, on any platform supporting `system_clock` which will either confirm or refute the use of New Years 1970 UTC for that platform. http://howardhinnant.github.io/date_algorithms.html#How%20can%20I%20confirm%20that%20your%20assertions%20about%20chrono%20compatibility%20are%20correct? This paper also describes the major low-level algorithms used in date.h. – Howard Hinnant Jan 15 '16 at 16:55
  • Thank you for the clarification. More than its obvious usefulness, your library is truly a nice demonstration of c++ coding. I'm still accepting the first answer as it still solves the problem with the (rather poorly furnished) standard library, but out of curiosity, have you considered submitting your work for standardization? – pixelou Jan 15 '16 at 18:55
  • I am considering, but have not done so at this time. I'm waiting for enough people like you to make enough noise in asking/demanding that this is what they want in the standard. Without a grassroots groundswell of support, the committee won't accept this solution. Every proposal in this space will be *very* controversial. `` itself was almost too controversial to get into C++11. – Howard Hinnant Jan 15 '16 at 19:55
  • I will try to spread the word ;-) and wish you all the best for your work. I will upvote as soon as I earn enough badges... – pixelou Jan 16 '16 at 12:22
0

Better use std::tm... As it is standard.

Don't worry about tm_zone. It's an optional field, only available in some environment like Linux/GNU or freeBSD... (actually, it comes with glibc, as you pointed out).

It as, nevertheless NO functionnal effect. A program using it should have a #ifndef or #ifdef directive to embed the statement, allowing compilation on systems without the field defined in the structure.

To answer your last question... Unsure of your your needs, but a starting point can be :

system_clock::time_point tp = system_clock::now();

as found here :

http://www.cplusplus.com/reference/chrono/time_point/time_since_epoch/

Hope it helps,

PS : throw an eye on this : Difference between steady_clock vs system_clock?

Community
  • 1
  • 1
SKZ 81
  • 313
  • 3
  • 9
  • From what I read, the spec of std::tm doesn't say if it is UTC. – pixelou Jan 14 '16 at 07:49
  • Indeed, that's why `tm_zone` was introduced by glibc to allow discrimination. I didn't check, but i guess it will be filled accordingly, depending if you use `localtime()` or `gmtime()` to get your std::tm object. – SKZ 81 Jan 14 '16 at 19:09
  • Hummm sorry i didn't noticed `system_clock` is available in C++11 only. But it seems you do C++11 since you try to build a time_point !! Anyway, for C++98, `std::time_t now = std::time(NULL); std::tm *t = std::gmtime(&now);` will do the job, it returns current UTC date. **WARNING** `std::gmtime( 0 )` returns a NULL pointer. – SKZ 81 Jan 14 '16 at 19:44
  • please consider accepting the answer if it helps :) – SKZ 81 Jan 14 '16 at 19:44
  • +1 for the warning, you may also integrate your previous comment to your answer as gmtime() is part of the solution. – pixelou Jan 14 '16 at 22:43