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:

1 |
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++:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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:

1 2 3 4 |
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.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#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:

1 2 3 |
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.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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; } |

Hits for this post: 3714 .