Some months ago I was trying to create a timeline of British
pre-history and needed to be able to store dates. The .NET
DateOnly structures are completely unsuitable
for these as their ranges are far too small, nor do they allow
for partial dates.
I did spent a little time poking around to see if there was some existing code, but there doesn't seem to be a lot of detail out there. I found a malformed blog post which was missing the bulk of the source code, and HistoricalDate class which was more fully fleshed, but still not fully suitable. So as typical, I went my own way and wrote my own.
Getting the library
The easiest way of obtaining the library is via NuGet.
If you don't use NuGet, pre-compiled binaries can be obtained from the GitHub Releases page.
Of course, you can always grab the source and build it yourself!
About this library
The library currently offers two read-only structs,
JulianDate structure can represent a partial date between
2147483647 BP and 2147483647 AD. By partial, I mean that a year
and the era are always required, but the month and/or day is
optional. After all, a date such as the Battle of Hastings may
be documented but the start of the Mesolithic is a little more
HistoricalTimeSpan is a cut down version of the more
TimeSpan and is currently mainly used by the
Subtract methods of a
Using the library
There are several constructors for specifying fully qualified or partial dates.
JulianDate(int year); // assumes AD JulianDate(int year, JulianEra era); JulianDate(int year, int month); // assumes AD JulianDate(int year, int month, JulianEra era); JulianDate(int year, int month, int day); // assumes AD JulianDate(int year, int month, int day, JulianEra era); // fully known var fk = new JulianDate(2021, 9, 5); // September 5th, 2021 AD // partial var pd = new JulianDate(13000, JulianEra.Bc); // 130,000 BC
You can also use an explicit operator to convert the date
portion of a
DateTime instance into a
var now = (JulianDate)DateTime.UtcNow;
You can also try and parse a string into a
Note: String parsing (and formatting) is somewhat basic (and potentially confusing) and will be improved in future updates to the library. Except with regards to month names, parsing is not culture-aware.
static JulianDate Parse(string s); static bool TryParse(string s, out JulianDate result) // fully known var fk = JulianDate.Parse("2021-09-17"); // September 17, 2021 AD // partial var pd = JulianDate.Parse("40000 BP"); // 40,000 BC
JulianDate supports partial dates, where only part of a date
is known. The year and era are always required, but month and
day are optional.
If day is specified, then the month is also available. The
HasMonth properties allow you to query what
partial components are set, or if you just want to know if a
date is fully known or partial, the
IsPartial properties can be used.
Day properties will return
0 if the
component has not been set.
var pd = JulianDate.Parse("40000 BP"); pd.HasMonth; // false pd.HasDay; // false var pd = JulianDate.Parse("09 2021"); pd.HasMonth; // true pd.HasDay; // false
IsLeapYear method will return if a given year is a
leap year. This is calculated according to Scaliger.
JulianDate.IsLeapYear(42, JulianEra.Bc); // true JulianDate.IsLeapYear(2021, JulianEra.Ad); // false
Things to improve
Currently, I'm unhappy with the string parsing as the list of accepted formats is too vague, and I haven't yet built in culture support.
Subtract method could have serious performance issues when
used with massive AD dates.
.NET Framework 2.0 or later.
Pre-built binaries are available via a signed NuGet package containing the following targets.
- .NET 3.5
- .NET 4.0
- .NET 4.5.2
- .NET 4.6.2
- .NET 4.7.2
- .NET 4.8
- .NET 5.0
- .NET Standard 2.0
- .NET Standard 2.1
- .NET Core 2.1
- .NET Core 3.1