HowardHinnant / date

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

Era prerequisites #739

Open gazzatav opened 2 years ago

gazzatav commented 2 years ago

In the civil_to_days and days_to_civil algorithms the concept of an era is explained. Does an era have any prerequisites to function correctly? I ask this because in the function below to convert a serial number to a date in the Darian calendar for Mars, (which is entirely based on the days_to_civil function) the era concept seems not to work:-

template <class Int>
constexpr
std::tuple<Int, unsigned, unsigned>
Darian_from_mars_days(Int z) noexcept
{
  static_assert(std::numeric_limits<unsigned>::digits >= 18,
        "This algorithm has not been ported to a 16 bit unsigned integer");
  static_assert(std::numeric_limits<Int>::digits >= 20,
        "This algorithm has not been ported to a 16 bit signed integer");
  z += 668;
  const Int era = (z >= 0 ? z : z - 6685906) / 6685907;
  const unsigned doe = static_cast<unsigned>(z - era * 6685907);   // [0, 6685906]
  const unsigned yoe = (doe - doe/1336 - doe/6685 + doe/66858 - doe/936026) / 668;  // [0, 9999] wrong at 756854
  const Int y = static_cast<Int>(yoe) + era * 10000;
  const unsigned doy = doe - (668*yoe + yoe/2 + yoe/10 - yoe/100 + yoe/1400);  // [0, 670]
  const unsigned mp = mp_from_doy(doy);                 // [0, 11]
  const unsigned m = mp + 1;                            // [1, 12]
  const unsigned d = doy - doy_from_mp(mp) + 1;         // [1, 29]

  return std::tuple<Int, unsigned, unsigned>(y, m, d);
}

There is a bug after 1132 years, where the first day of every other year is skipped and in the inverse function two dates at this point both produce the same serial number. Mars has a very long era. It just so happens that 1132 is the year by the end of which, a complete year (668 days) of extra days has been added to the calendar. For the yoe expression to work, is there a requirement on the era to have less added days than a year? Should I invent a sub-era?

HowardHinnant commented 2 years ago

Shooting from the hip: In my algorithms, an era contains an integral number of days (146097), and an integral number of years (400). I haven't studied the Darian calendar. But it looks like your era is 10000 years. Do 10000 years contain exactly 6685907 days?

And by "contain exactly" I'm referring to the Darian calendar, as opposed to the orbital mechanics of Mars. It is known that the civil calendar does not exactly model the orbital mechanics of Earth.

gazzatav commented 2 years ago

Thanks for the reply. In my model the era contains exactly 6685907 days which gets it to an accuracy of some seconds. The best figure I could find after a little bit of research was 668.5907 days in a year so I multiplied it out. I chose to add a day every other year, add another day every 10 years, take one away every 100 years and add one every 1400 years to get the last seven days. I thought at first that the error was caused by the arbitrary choice of 1400 years but the error first occurs at 1132 to 1133 which, as I said is the point where a year of leap days has been added. The other thing about my arrangement is that some years may have more than one day added (Mars needs so many leap years).

I've attached the source file (renamed as .txt). In it is a test for conversion to date and inversion to serial which passes and a test for catching the wrong year starts. I've stared at it so long now I thought I might have missed something really obvious.

For info: the Darian calendar has 24 months. Each quarter has 167 days (5 x 28 + 27). Any extra days are added to the last month of the year.

mars_time.txt