2

I am working with netCDF4 files which contain a time dimension. Values of this time dimension are doubles representing days since 1582-10-15 00:00. To allow users to interact with the data, I need to convert these timestamps into actual dates (like 01:01:2014T00:00:00).

While researching this I could not find a solution for this in c++. The netCDF Python library provides num2date as well as date2num, which do exactly what I need. I sadly could not find these functions in the c++ lib. I would appreciate any suggestions on how to do this.

A. Osterthun
  • 175
  • 1
  • 3
  • 18
  • Off-topic: `double` appears pretty inappropriate to count days. `int64_t` is much more precise on the issue (you have 64 bits available, whereas only 53 with double – if you need more bits, then rounding occurs and you'll get undesirable results). OK, you could use sub-day precision with double, but even 0.2 days isn't representable *exactly* (periodic in binary)... – Aconcagua Aug 27 '19 at 13:09
  • I agree, but I am not in the position to change that. The netCDF files I used are not generated by me but by a third party. – A. Osterthun Aug 27 '19 at 13:26
  • The manual recommends [this library](https://www.unidata.ucar.edu/software/udunits/udunits-current/doc/udunits/udunits2lib.html#Time). – Hans Passant Aug 27 '19 at 15:26

2 Answers2

6

This is very easy to do using Howard Hinnant's time/date library.

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

date::sys_time<std::chrono::seconds>
to_time_point(double d)
{
    using namespace date;
    using namespace std::chrono;
    using ddays = duration<double, days::period>;
    return round<seconds>(ddays{d} + sys_days{1582_y/10/15});
}

int
main()
{
    using namespace date;
    std::cout << to_time_point(159562.572297) << '\n';
}

Output:

2019-08-27 13:44:06

You didn't mention what precision you wanted, but from your example output, I presumed seconds. You can choose any chrono-precision you desire. This program simply converts your double into a double-based chrono-duration with a period of days, and then adds it to the epoch. The result is a chrono::system_clock::time_point with a precision of your choosing. Though I recommend you don't use nanoseconds. nanoseconds precision is 3 orders of magnitude finer than the double will deliver at this range, and the range of sys_time<nanoseconds> is [1677-09-21 00:12:43.145224192, 2262-04-11 23:47:16.854775807]. Whereas the range of sys_time<microseconds> is +/-292 thousand years (plenty).

You can use this same library to format this time_point in whatever style you want. For example:

std::cout << format("%d:%m:%YT%T", to_time_point(159562.572297)) << '\n';

Output:

27:08:2019T13:44:06

Use of this library will migrate very nicely to C++20 if you ever choose to use that newer standard once it becomes available.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
1
int64_t nDays = int64_t(dtime); // Integral # days
dtime -= double(nDays);         // Fraction of a day
int64_t fSecs = int64_t(dtime * 86400.0 + 0.5); // Partial day in seconds
std::time_t nSecs = nDays * 86400LL + fSecs; // Number of secs since epoch
// Then use nSecs (after adjusting the epoch) to make a std::tm variable
nSecs -= 12219292800LL;// Subract # secs between 1582-10-15 and 1970-01-01
std::tm *pTM = std::gmtime(nSecs);
// and use std::strftime (or std::wcsftime) to format it into your string
wchar_t tStr[32];   wcsftime(tStr, 32, L"%Y-%m-%dT%H:%M:%S", pTM);
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • Thank you. But that solution does not account leap days I think. So the resulting date could be a bit off. If I am wrong how would I adjust the epoch ? For some reason I am really anxious about working with dates ... – A. Osterthun Aug 27 '19 at 13:44
  • @A. Osterthun - Leap days don't come into it - assuming the values you get from netCDF4 are correct! Converting epoch: give me 5 minutes, and I'll post an edit showing how! – Adrian Mole Aug 27 '19 at 13:49
  • That would be perfect. I'm really thankful for your support.But I don't quite get why leap days aren't coming into it. – A. Osterthun Aug 27 '19 at 13:54
  • @A. Osterthun - The `gmtime()` function takes care of leap days - provided the date is not before Jan 1st 1970. – Adrian Mole Aug 27 '19 at 15:22