Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fractional day of the year computation in C++14

I wrote the following code using Howard Hinnants date.h library, to compute the fractional day of the year of the current time. I was wondering if there are shorter ways of doing it, because my code feels like an overkill of std::chrono and date calls. Can I directly calculate the number of fractional days since the start of the year (at microsecond precision) and avoid my two-step approach?

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

int main()
{
    // Get actual time.
    auto now = std::chrono::system_clock::now();

    // Get the number of days since start of the year.
    auto ymd = date::year_month_day( date::floor<date::days>(now) );
    auto ymd_ref = date::year{ymd.year()}/1/1;
    int days = (date::sys_days{ymd} - date::sys_days{ymd_ref}).count();

    // Get the fractional number of seconds of the day.
    auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now - date::floor<date::days>(now));
    double seconds_since_midnight = 1e-6*microseconds.count();

    // Get fractional day number.
    std::cout << "Fractional day of the year: " << days + seconds_since_midnight / 86400. << std::endl;

    return 0;
}
like image 830
Chiel Avatar asked Mar 27 '19 00:03

Chiel


1 Answers

Good question (upvoted).

I think first we need to decide on what the right answer is. There's your answer, and currently the only other answer is Matteo's. For demonstration purposes, I've modified both answers to substitute in a "fake now" so that we can compare apples to apples:

using namespace std::chrono_literals;
auto now = date::sys_days{date::March/27/2019} + 0h +  32min + 22s + 123456us;

(approximately now at the time I'm writing this)

Chiel's code gives:

Fractional day of the year: 85.0225

Matteo's code gives:

Fractional day of the year: 85.139978280740735

They are close, but not close enough to both be considered right.

Matteo's code works with "average years":

auto this_year = date::floor<date::years>(now);

The length of a date::years is 365.2425 days, which is exactly right if you average all civil years over a 400 year period. And working with the average year length can be very useful, especially when dealing with systems that don't care about human made calendars (e.g. physics or biology).

I'm going to guess that because of the way Chiel's code is written, he would prefer a result that refers more precisely to this specific year. Therefore the code presented below is Chiel's's algorithm, resulting in exactly the same result, only slightly more efficient and concise.

// Get actual time.
auto now = std::chrono::system_clock::now();

// Get the number of days since start of the year.
auto sd = date::floor<date::days>(now);
auto ymd = date::year_month_day( sd );
auto ymd_ref = ymd.year()/1/1;
std::chrono::duration<double, date::days::period> days = sd - date::sys_days{ymd_ref};

// Get the fractional number of seconds of the day.
days += now - sd;

// Get fractional day number.
std::cout << "Fractional day of the year: " << days.count() << std::endl;

The first thing I noted was that date::floor<date::days>(now) was being computed in 3 places, so I'm computing it once and saving it in sd.

Next, since the final answer is a double-based representation of days, I'm going to let <chrono> do that work for me by storing the answer in a duration<double, days>. Any time you find yourself converting units, it is better to let <chrono> do it for you. It probably won't be faster. But it definitely won't be slower, or wrong.

Now it is a simple matter to add the fractional day to the result:

days += now - sd;

using whatever precision now has (microseconds or whatever). And the result is now simply days.count().

Update

And with just a little bit more time to reflect ...

I noticed that with the simplified code above, one can more easily see the entire algorithm as a single expression. That is (removing namespace qualification in order to get everything on one line):

duration<double, days::period> days = sd - sys_days{ymd_ref} + now - sd;

And this clearly algebraically simplifies down to:

duration<double, days::period> days = now - sys_days{ymd_ref};

In summary:

using namespace std::chrono;
using namespace date;

// Get actual time.
auto now = system_clock::now();

// Get the start of the year and subract it from now.
using ddays = duration<double, days::period>;
ddays fd = now - sys_days{year_month_day{floor<days>(now)}.year()/1/1};

// Get fractional day number.
std::cout << "Fractional day of the year: " << fd.count() << '\n';

In this case, letting <chrono> do the conversions for us, allowed the code to be sufficiently simplified such that the algorithm itself could be algebraically simplified, resulting in cleaner and more efficient code that is provably equivalent to the original algorithm in the OP's question.

like image 192
Howard Hinnant Avatar answered Oct 23 '22 15:10

Howard Hinnant