-
Notifications
You must be signed in to change notification settings - Fork 685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature Request - Julian Date Conversion (interesting) #849
Comments
I should probably add this to the Examples and Recipes section. There is a SO answer on this that has evolved over the years. The C++20 version is really quite nice. And if you have C++20 that should be the preferred version. To make it work this this library (pre-C++20) one just needs to throw in Here is the C++20 code from the SO answer answer in full: #include <chrono>
struct jdate_clock;
template <class Duration>
using jdate_time = std::chrono::time_point<jdate_clock, Duration>;
struct jdate_clock
{
using rep = double;
using period = std::chrono::days::period;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<jdate_clock>;
static constexpr bool is_steady = false;
static time_point now() noexcept;
template <class Duration>
static
auto
from_sys(std::chrono::sys_time<Duration> const& tp) noexcept;
template <class Duration>
static
auto
to_sys(jdate_time<Duration> const& tp) noexcept;
};
template <class Duration>
auto
jdate_clock::from_sys(std::chrono::sys_time<Duration> const& tp) noexcept
{
using namespace std;
using namespace chrono;
auto constexpr epoch = sys_days{November/24/-4713} + 12h;
using ddays = std::chrono::duration<long double, days::period>;
if constexpr (sys_time<ddays>{sys_time<Duration>::min()} < sys_time<ddays>{epoch})
{
return jdate_time{tp - epoch};
}
else
{
// Duration overflows at the epoch. Sub in new Duration that won't overflow.
using D = std::chrono::duration<int64_t, ratio<1, 10'000'000>>;
return jdate_time{round<D>(tp) - epoch};
}
}
template <class Duration>
auto
jdate_clock::to_sys(jdate_time<Duration> const& tp) noexcept
{
using namespace std::chrono;
return sys_time{tp - clock_cast<jdate_clock>(sys_days{})};
}
jdate_clock::time_point
jdate_clock::now() noexcept
{
using namespace std::chrono;
return clock_cast<jdate_clock>(system_clock::now());
} Here is how I had to tweak it to use this library under C++17: #include "date/tz.h"
#include <chrono>
struct jdate_clock;
template <class Duration>
using jdate_time = std::chrono::time_point<jdate_clock, Duration>;
struct jdate_clock
{
using rep = double;
using period = date::days::period;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<jdate_clock>;
static constexpr bool is_steady = false;
static time_point now() noexcept;
template <class Duration>
static
auto
from_sys(date::sys_time<Duration> const& tp) noexcept;
template <class Duration>
static
auto
to_sys(jdate_time<Duration> const& tp) noexcept;
};
template <class Duration>
auto
jdate_clock::from_sys(date::sys_time<Duration> const& tp) noexcept
{
using namespace date;
using namespace std;
using namespace chrono;
auto constexpr epoch = sys_days{November/24/-4713} + 12h;
using ddays = std::chrono::duration<long double, days::period>;
if constexpr (sys_time<ddays>{sys_time<Duration>::min()} < sys_time<ddays>{epoch})
{
return jdate_time<decltype(tp - epoch)>{tp - epoch};
}
else
{
// Duration overflows at the epoch. Sub in new Duration that won't overflow.
using D = std::chrono::duration<int64_t, ratio<1, 10'000'000>>;
return jdate_time<decltype(round<D>(tp) - epoch)>{round<D>(tp) - epoch};
}
}
template <class Duration>
auto
jdate_clock::to_sys(jdate_time<Duration> const& tp) noexcept
{
using namespace date;
using namespace std::chrono;
return sys_time<Duration>{tp - clock_cast<jdate_clock>(sys_days{})};
}
jdate_clock::time_point
jdate_clock::now() noexcept
{
using namespace date;
using namespace std::chrono;
return clock_cast<jdate_clock>(system_clock::now());
} Here is how you would use it to print out the current time as a // Current julian day time
auto now = jdate_clock::now();
std::cout << now.time_since_epoch().count() << '\n'; Here is how you would convert it to the Gregorian calendar: // Convert to sys_time
auto now_utc = clock_cast<system_clock>(now);
std::cout << now_utc << '\n'; To convert it to the Julian calendar it is convenient to use this user-written Julian calendar: // Convert to the Julian calendar
auto sd = floor<days>(now_utc);
auto tod = now_utc - sd;
julian::year_month_day jymd{sd};
std::cout << jymd << ' ' << hh_mm_ss{tod} << '\n'; This all just output for me:
Porting it to earlier than C++17 would take a little more work. By casting it as a clock that can convert to and from |
That is perfect. I had a sneaking suspicion you had traveled this road before. I can't believe I missed the SO answer in jumping down this rabbit hole. Thank you very much! And thankfully, after moving to Tumbleweed on openSUSE, we now have c++23 available on all boxes (except for a couple Raspberry Pi boxes still running Buster) |
One question, in your Gregorian calendar output example:
Doesn't
The output above is with Measuring from To handle the different Gregorian calendar discontinuities depending upon which adoption is being used in a given setting for the conversion from Julian Day back to Gregorian calendar date I suppose separate clock implementations, or another member variable holding a value indicating which adoption to use would be needed that could handle the different adoption date and times by adding or subtracting the accumulated leap second resets to the conversion from Julian back to Gregorian calendar. I'll definitively have to read a bit more to learn how best to do that. It's remarkable how easily the library handles the new calendar just by defining a new |
Ah, my bad. The problem is that the C++ 20 spec doesn't deal well with time_points that have a floating point representation such as double. The fix is to cast it to integral: auto now_utc = round<nanoseconds>(clock_cast<system_clock>(now)); nanoseconds is integral based, and round will convert to the nearest nanosecond in this case. You could give any precision you desire (seconds, milliseconds, whatever). I didn't pick this up because I've added an extension to my library to handle double-based time points. |
On switching between Julian and Gregorian I have't coded anything up. But I'm imagining something along the lines of: std::map<std::string, sys_days> change{{"Italy", October/15/1582},
{"England", September/14/1752},
...};
std::string country = ...
sys_days date = ...
if (date < change[country])
// use Julian
else
// use Gregorian It'd be really cool if you can figure out how to make that map constexpr. And if you need to deal with Sweden, I think you'll have to create a Swedish calendar. That one is much more complex than switching on a given date. |
Howard, I recently ran across the need to convert from Gregorian Calendar to Julian Date and back working with various orbital data and ephemerides from JPL1. I checked the
std::chrono
library, but didn't find anything Julian Date related. This is really something that would make a good addition to the library, although it may not be the area that gets the most use. It's critical that it be something contained in a language library as the nuances are significant and that is what makes it something that shouldn't be reinvented by the user each time the conversion is needed, there is just far too much room for error.The conversions are straight-forward, but the nuances are in providing an implementation that allows the user to choose which adoption of the Gregorian calendar to use when converting from Julian Date, e.g. the Catholic Church adoption in 1582 resulting in a 10-day reset for accumulated leap seconds, or say the adoption by Great Britain and the Colonies in 1752 resulting in an 11-day reset. The implementation providing a user-choice would be similar to what is done choosing the pseudo rando number generation engine, e.g. Mersenne twister, etc..
I don't know if you've considered it and decided against it or not, so I thought I'd suggest it as a feature. A few links that provide a good overview of the conversion are:
Not a giant high-priority issue, but something that would make a nice and much appreciated addition. (if you already have it in the library somewhere and I missed it, where is it squirreled-away?)
footnotes:
The text was updated successfully, but these errors were encountered: