HowardHinnant / date

A date and time library based on the C++11/14/17 <chrono> header
Other
3.14k stars 677 forks source link

Does the algorithm in the year_month_day::to_days function have a name? #738

Closed gazzatav closed 2 years ago

gazzatav commented 2 years ago

I'm trying to write a class similar to year_month_date to express a version of the Mars Darian calendar. The to_days function looks really important and I'm half way to understanding it. The line below to find day of year seems particularly important.

auto const doy = (153*(m > 2 ? m-3 : m+9) + 2)/5 + d-1;

Does this algorithm have a name or is it unique to the date library? It would be great to know how it was derived. I would like to understand it better to create a similar expression for mars.

Thanks.

HowardHinnant commented 2 years ago

The main thing to know when writing a calendar is the ability to convert to and from sys_days and local_days (which is the same math under the hood). sys_days is a "calendar" where 1970-01-01 is day 0, and the numbers go positive into the future and negative into the past. Once you've got that conversion going, you can convert Darian to every other calendar that supports a conversion to/from sys_days.

To learn about the algorithms used for date::year_month_day, see: http://howardhinnant.github.io/date_algorithms.html

It includes a detailed derivation of each algorithm, including the one for doy that you quote above. That particular line of code is not unique to date. You can find it, and variations of it in many calendrical programs across many programming languages.

gazzatav commented 2 years ago

@HowardHinnant Thank you. I have a MarsClock with the epoch back in 1955 and using declarations for mars_day, mars_hour and mars_year. I was amazed (not really, I know your libraries are brilliant!) how easily I could convert from earth time to mars time. I'll read up on those algorithms, they may be common but without a name or keyword, internet searches always return the 'is_leap_year' type of algorithm. At the moment I am close with:

(167*(m-1))/6 +1 -m/6

but not perfect.

gazzatav commented 2 years ago

To close this question, the Darian calendar has four quarters of 6 months. Each month of the quarter has 28 days except the last which has 27 for a total of 167 days per quarter. Leap years add a day to the last month of the year so they can be ignored here (and elsewhere the year does not need to be decremented). An expression for doy, using mp from the range [0, 23] and extra parentheses to emphasise integer division is:-

doy = 167 * mp/6 + (mp - (mp/6) * 6) * 28 + d - 1

Possibly it would be worth calculating mp/6 before the expression and using the result twice - I checked on Compiler Explorer and it saved 10 lines of assembly :)