c++c++-chrono

Why does the order of std::chrono::floor matter here?


This works:

  year_month_day day() {
    return year_month_day(floor<days>(
            zoned_time{
              zone,
              system_clock::now()
            }.get_local_time()));
  }

But I need to floor() the zoned_time, as above - since that's templated on the duration, I'd expect to be able to floor either that, or the result of now(), i.e. do this:

  year_month_day day2() {
    return year_month_day(
            zoned_time{
              zone,
              floor<days>(system_clock::now())
            }.get_local_time());
  }

So I'm puzzled as to why I get the following error when I try that - should the zoned_time above not be in units of days, and so fine to convert to a year_month_day?

error: no matching function for call to ‘std::chrono::year_month_day::year_month_day(std::chrono::local_time<std::chrono::duration<long int> >)’
   48 |             }.get_local_time());

I'm using g++ (GCC) 14.2.1 20240912 (Red Hat 14.2.1-3) (called as c++) with -std=gnu++20.


Solution

  • year_month_day can convert from a time point with a precision of days. That time point can be based on system_clock or local_t. But in either case, it must have a precision of days.

    The reverse conversion can also be made, and when done, results in exactly the same time point as the original. This is known as a lossless conversion. Information is conserved.

    If conversions were allowed from a time point with precision finer than days, say seconds, then this would be a lossy conversion and break one of the fundamental design principles of chrono: You can't loose information implicitly. You must explicitly request a lossy conversion with expressions such as floor.

    This sub-expression:

    floor<days>(system_clock::now())
    

    creates a days-precision time_point based on system_clock. So far so good.

    This sub-expression:

    zoned_time{
              zone,
              floor<days>(system_clock::now())
            }.get_local_time()
    

    takes that days-precision time_point and adds the associated time_zone's UTC offset to it to compute the local time. The UTC offset from the IANA time zone database has units of seconds. And if you add seconds to a days-precision time_point the result is a seconds-precision time_point. And this seconds-precision time_point can not be directly converted to a year_month_day.

    All of the above is the type system turning a potential run-time error into a compile-time error. If the lossy conversion were allowed, it would sometimes give the wrong answer.

    For example, let's say the UTC offset is 5h, and the current system_clock time of day (UTC) is 20h. The correct local time (before the floor) is 01:00 the next day: floor<days>(20h + 5h) gives the next day.

    But if one instead computed floor<days>(20h) + 5h you would get 05:00 of the same day (one-off error).