I have been recently asked on my post on the date library if the library has a function for computing the day of the year. It actually does not, although it is fairly simple to compute it.
UPDATE: Howard Hinnant has shown in a comment below how to write a day_of_year() function using the date libray.
Let’s look at the days of the year.
Day | Day of year |
---|---|
January 1 | 1 |
January 2 | 2 |
… | … |
January 31 | 31 |
February 1 | 32 |
… | … |
February 28 | 59 |
Here is where things complicate a bit, because during leap years February has 29 days. So we actually need to have two counts of days.
Day | Day of non-leap year | Day of leap year |
---|---|---|
January 1 | 1 | 1 |
January 2 | 2 | 2 |
… | … | … |
January 31 | 31 | 31 |
February 1 | 32 | 32 |
… | … | … |
February 28 | 59 | 59 |
February 29 | N/A | 60 |
March 1 | 60 | 61 |
… | … | … |
December 31 | 365 | 366 |
It is fairly simple to compute the day of the year based on the day of the month if we knew the day of the year of each first day of the month. That can be also put in a table.
Day of month | Day of non-leap year | Day of leap year |
---|---|---|
January 1 | 1 | 1 |
February 1 | 32 | 32 |
March 1 | 60 | 61 |
April 1 | 91 | 92 |
May 1 | 121 | 122 |
June 1 | 152 | 153 |
July 1 | 182 | 183 |
August 1 | 213 | 214 |
September 1 | 244 | 245 |
October 1 | 274 | 275 |
November 1 | 305 | 306 |
December 1 | 335 | 336 |
So we can compute the day of the year as:
day_of_year = day_of_year_of_first_month_day + day_of_month - 1
We can simplify that a bit by subtracking 1 from the day of the year values in the table above, such that January 1st is the day 0, February 1st is day 31, etc.
The following code sample shows how this can be written in C++:
namespace datetools { namespace details { constexpr unsigned int days_to_month[2][12] = { // non-leap year { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, // leap year { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }, }; } constexpr bool is_leap(int const year) noexcept { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } constexpr unsigned int day_of_year(int const year, unsigned int const month, unsigned int const day) { return details::days_to_month[is_leap(year)][month - 1] + day; } }
And how it can be used:
auto doy1 = datetools::day_of_year(2017, 1, 1); // doy1 = 1 auto doy2 = datetools::day_of_year(2017, 8, 3); // doy2 = 215 auto doy3 = datetools::day_of_year(2017, 12, 31); // doy3 = 365 auto doy4 = datetools::day_of_year(2020, 12, 31); // doy4 = 366
This day_of_year() function can be used with the date library too. I’ll just add one more utility function that takes a date::year_month_day value and returns the day of the year.
#include "date.h" unsigned int day_of_year(date::year_month_day const & dt) { return datetools::day_of_year( static_cast<int>(dt.year()), static_cast<unsigned>(dt.month()), static_cast<unsigned>(dt.day())); } int main() { using namespace date::literals; auto doy1 = day_of_year(2017_y / 1 / 1); // doy1 = 1 auto doy2 = day_of_year(2017_y / 8 / 3); // doy2 = 215 auto doy3 = day_of_year(2017_y / 12 / 31); // doy3 = 365 auto doy4 = day_of_year(2020_y / dec / 31);// doy4 = 366 }
And we want to know what day of the year today is then we can do that too:
date::year_month_day today = date::floor<date::days>(std::chrono::system_clock::now()); auto dayt = day_of_year(today);
The day_of_year() function is very simple and does not do any argument checks. That makes it possible to compute dates such as 2017.08.55 or 2017.55.100. Obviously, these not only that do not make sense, but indexing the days_to_month array beyond its bounds is undefined behavior. That means that in practice you should write a function that validates the arguments and throws an exception upon error. However, in this case, the day_of_year() can not be constexpr anymore.
namespace datetools { constexpr bool is_leap(int const year) noexcept { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); } unsigned int day_of_year(int const year, unsigned int const month, unsigned int const day) { static const unsigned int days_to_month[2][12] = { // non-leap year { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, // leap year { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }, }; if (month == 0 || month > 12) throw std::out_of_range("invalid month"); if (day == 0 || day > 31) throw std::out_of_range("invalid day"); return days_to_month[is_leap(year)][month - 1] + day; } }
This would throw an exception on dates like 2017.13.1 or 2017.1.50, but would not do so for 2017.2.30 or 2017.11.31 that are also invalid dates. That can be further corrected by verifying that the day of the month does not exceed the number of days that month can have in the given year.
unsigned int day_of_year(int const year, unsigned int const month, unsigned int const day) { static const unsigned int days_to_month[2][12] = { // non-leap year { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }, // leap year { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }, }; static const unsigned int days_of_month[2][12] = { // non-leap year { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, // leap-year { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, }; if (month == 0 || month > 12) throw std::out_of_range("invalid month"); auto leap = is_leap(year); if (day == 0 || day > days_of_month[leap][month-1]) throw std::out_of_range("invalid day"); return days_to_month[leap][month - 1] + day; }
Here’s another way to write day_of_year:
date::days
day_of_year(date::sys_days sd)
{
using namespace date;
auto y = year_month_day{sd}.year();
return sd - sys_days{y/jan/0};
}
It can be exercised like this:
int
main()
{
using namespace date;
std::cout << day_of_year(2017_y/aug/3) << '\n';
}
Output:
215[86400]s
This translates to 2017-08-03 is the 215th day of the year (starting with Jan 1 as day 1).
Thank you for the example. I updated my post with a reference to your comment.
Thanks for the contribution. Though Hinnant’s solution is obviously less verbose (and doesn’t rely on homegrown techniques), it’s likely slower than yours (but most won’t care). However, even in your case, why not condense it to this:
namespace datetools
{
constexpr bool is_leap(int const year) noexcept
{
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
}
constexpr unsigned int day_of_year(int const year, unsigned int const month, unsigned int const day) noexcept
{
constexpr unsigned int days_to_month[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
unsigned int doy = days_to_month[month – 1] + day;
if (month >= 3 && is_leap(year))
{
doy++;
}
return doy;
}
}