1

Let's say I have the following program to export an object to JSON:

struct MyChronoObject {
  std::string name;
  std::chrono::system_clock::time_point birthday;

  MyChronoObject(const std::string name_) : name(name_) {
    birthday = std::chrono::system_clock::now();
  }

  std::string toJSON1() {
    std::string s = "{ \"name\": \"" + name + "\", \"birthday\": ";
    s += std::to_string((birthday.time_since_epoch().count() / 1000000));
    s += " }";
    return s;
  }

  std::string toJSON2() {
    std::string s = "{ \"name\": \"" + name + "\", \"birthday\": ";
    s += std::to_string((birthday.time_since_epoch().count()));
    s += " }";
    return s;
  }

  void setBirthday(int birthday_in_seconds) {
    // how do I properly cast this?
  }
};

Which, for toJSON1(), has the output { "name": "Steve", "birthday": 16687719115 }

There are several problems with this code (which I will mention but will probably address them in separate threads), but first and foremost...

  • The number 16687747280 is not correct. It should be one digit shorter for seconds or 2 digits longer for milliseconds if I go by this: EpochConverter
  • Not dividing the the birthday by one million, toJSON2(), leads to a number that is one digit too long for microseconds and 2 digits too short for nanoseconds: 16687747280849928.

So which way would be correct (and most efficient) to store and convert the stored epoch time so that I can export it to something that can be used by Javascript?

Thank you in advance!  


P.S.: Other questions that I have are:

  • How do I cast back a number that the C++ program receives from the frontend (like in setBirthday)?
  • Should I even store the date as chrono object if seconds are sufficient?
  • How do I add exactly one year so that I land on the same date (e.g. 25.1.2019 to 25.1.2020), considering things like leap years, etc.).
  • What about dates before 1970?
Konrad
  • 21,590
  • 4
  • 28
  • 64
Markstar
  • 639
  • 9
  • 24

2 Answers2

6

Note the default unit of std::chrono::system_clock is undefined. Instead of using birthday.time_since_epoch().count() / 1000000, you should cast the time unit to the appropriate unit for you:

std::chrono::duration_cast<std::chrono::seconds>(birthday.time_since_epoch())
    .count()

This will give you the amount of second.

You might also use std::chrono::ceil<seconds>, std::chrono::floor<seconds>, std::chrono::round<seconds> for different rounding modes.


  • To cast from integer back to a time_point, you can do:
std::chrono::sys_time{std::chrono::seconds{birthday_in_second}};

or:

std::chrono::sys_time{std::chrono::milliseconds{birthday_in_millisecond}};

  • To add/subtract years, you can use:
auto now = std::chrono::system_clock::now();
auto today = std::chrono::year_month_day{std::chrono::floor<std::chrono::days>(now)};
auto today_10_years_ago = today - std::chrono::years{10};

And you can also use using namespace std::chrono::literals to write 10y instead of years{10}. *This is incorrect, see more information in the comment

Note today and today_10_years_ago are year_month_day object now. If you want a time_point with h/m/s information as well, you can do:

auto today_as_time_point = std::chrono::sys_days{today_10_years_ago};

auto time_only = today - now;
auto today_with_time_info = today_as_time_point + time_only;

  • Yes, the <chrono> library works with time before epoch.

By requirement, all predefined duration types up to chrono::hours are guaranteed to work in the range for at least ±292 years(in practice, many of them will still work in much larger ranges). And types greater than hour, ie. chrono::days, chrono::years are guaranteed for ±40000 years.

So if you know you are going to do something like now - years{1000}, it would be a good idea to convert now to a larger unit first.


I'm still a bit overwhelmed by the casting...

Part of the goal of the <chrono> library is to give high level access to time/date data while avoiding possible errors.

If you want to cast a more precise duration to a less precise duration, ie. from millisecond to second, you are forced to use an explicit cast or through roundings, which will avoid unintended truncation errors.

For more, you might search for Howard Hinnant's talk on the library.


... and extracting something like year out of it

Depends on your need, if your full goal was to serialize a date information, you might want to utilize std::format or {fmt} library to do it automatically:

auto now = std::chrono::system_clock::now();
std::cout << std::format("{:%Y}", now);       // This will print only the year

Demo further utilizing formatting libraries. *Note {fmt} library is used as gcc haven't implemented the full std::format library.

Ranoiaetep
  • 5,872
  • 1
  • 14
  • 39
  • 1
    Good answer. But `10y` is `year{10}`, not `years{10}`. See https://stackoverflow.com/q/62249520/576911 for an analogous discussion for `month` and `months`. – Howard Hinnant Nov 18 '22 at 14:53
  • 1
    @HowardHinnant Aww, thank you! Removed the `10y` parts. – Ranoiaetep Nov 18 '22 at 14:57
  • Thank you very much for your detailed answer! I have to admit I'm still a bit overwhelmed by the casting (and extracting something like `year` out of it). However, the line `std::chrono::sys_time{std::chrono::seconds{birthday_in_second}};` did not work for me. Are you sure it is correct? – Markstar Nov 18 '22 at 14:59
  • 1
    `sys_time` is a C++20 name. If you're in C++11/14/17, you can use this much more verbose name (which `sys_time` is a type alias for): `std::chrono::time_point`. – Howard Hinnant Nov 18 '22 at 15:02
  • @Markstar Note some of the features are added only in C++20, and not all compilers have implemented all feature. You might check Howard Hinnant(author of `` library)'s [date library](https://github.com/HowardHinnant/date) for more information – Ranoiaetep Nov 18 '22 at 15:04
  • I'm using Visual Studio and have set it to `/std:c++20`. Hmm, I'm also getting an error for `auto today = std::chrono::year_month_day{std::chrono::floor{now}};` ("no instance of constructor ... matches the argument list"). – Markstar Nov 18 '22 at 15:05
  • https://gcc.godbolt.org/z/xxaKKff4r – Howard Hinnant Nov 18 '22 at 15:11
  • @Markstar Updated the answer, it should be `year_month_day{std::chrono::floor(now)}` – Ranoiaetep Nov 18 '22 at 15:11
  • @Markstar *'extracting something like `year` out of it'* -- `std::format` or [{fmt}](https://fmt.dev/latest/index.html) will be great friend for you. – Ranoiaetep Nov 18 '22 at 15:25
2

duration.count() returns a number of ticks. Not seconds, nor microseconds, nor nanoseconds. Unless the tick period happens to be 1, 10⁻⁶, or 10⁻⁹ seconds.

You have to multiply that by the period, which is given in class attributes of your duration. Which in you case, depends on the clock type (all those are templates, and the period depend on the actual class)

One way is to cast the duration into a duration whose you know the period.

Like

std::chrono::duration_cast<std::chrono::seconds>(birthday.time_since_epoch()).count()
// Instead of birthday.time_since_epoch().count()
chrslg
  • 9,023
  • 5
  • 17
  • 31
  • Thank you for the explanation and the fix. Is this also how you would implement this or is there a better/more efficient way? – Markstar Nov 18 '22 at 13:06
  • Honestly, I am not a c++ coder (this is probably the first time I answer a `c++` tagged question. I just happen to have already encounter theses types). In my world, that is a very convoluted way to get epoch time in integer. But I also know that in C++, many code seem to be overly convoluted, but in reality are static code, and generates only a few operation in the generated code. So, honestly, I don't know. Maybe all this just means "divide by 100000", in which case, you can't do better. – chrslg Nov 18 '22 at 14:45