Add julian Date package with conversion to time.Time

The julian.Date type will be used within the project to support indexing
of holidays using the Julian calendar, Gregorian dates are implemented
by using time.Time (since it is already gregorian). Currently only
conversion Julian->Gregorian has been implemented, but the opposite will
be added soon.
This commit is contained in:
Petar Kapriš 2024-07-21 00:21:34 +02:00
parent 73fde0698c
commit bd5c9fe692

122
julian/julian.go Normal file
View file

@ -0,0 +1,122 @@
package julian
import "time"
// TODO: describe how time doesn't go past BCE, and therefore we don't either
// Keep in mind, Go's time.Time gregorian dates are `proleptic`, ie. there is
// no need to give special care to dates before 15. October 1582.
type Date struct {
year int
month time.Month
day int
}
func greaterOrEqual(a, b Date) bool {
if a.year != b.year {
return a.year > b.year
} else if a.month != b.month {
return a.month > b.month
} else {
return a.day >= b.day
}
}
func monthLength(year int, month time.Month) int {
lengths := map[time.Month]int{
time.January: 31,
time.March: 31,
time.April: 30,
time.May: 31,
time.June: 30,
time.July: 31,
time.August: 31,
time.September: 30,
time.October: 31,
time.November: 30,
time.December: 31,
}
if _, ok := lengths[month]; ok {
return lengths[month]
}
// February
if year%4 == 0 {
return 29
}
return 28
}
func dateDiff(d Date, days int) Date {
// keep in mind throughout this code that the original "days" value can
// be negative
var fourYearPeriods int
var periodLen int = (3 * 365) + 366
// a difference which is an exact multiple of 4 years can be dealt with
// easily, so we only have to take greater care with the remainder
fourYearPeriods, days = days/periodLen, days%periodLen
// this `if' will happen if days was originally <0, AND is not an exact
// multiple of periodLen
if days < 0 {
fourYearPeriods -= 1
days += periodLen
}
// now days must be 0 <= days < periodLen, so our job is easier
// first we shift in the big periods
d.year += fourYearPeriods * 4
// We shift the date to the first of the month to simplify our
// calculation
days += d.day - 1
d.day = 1
// since days < periodLen+31-1, we will go through this loop at most
// 4*12+1 = 49 times
for days >= monthLength(d.year, d.month) {
days -= monthLength(d.year, d.month)
d.year, d.month = nextMonth(d.year, d.month)
}
d.day += days
return d
}
func nextMonth(year int, month time.Month) (int, time.Month) {
if month == time.December {
return year + 1, time.January
} else {
return year, month + 1
}
}
func New(year int, month time.Month, day int) Date {
// TODO: deal with users giving negative dates
for day > monthLength(year, month) {
day -= monthLength(year, month)
year, month = nextMonth(year, month)
}
return Date{year, month, day}
}
// The algorithm, in large part: follows this article:
// https://en.wikipedia.org/wiki/Conversion_between_Julian_and_Gregorian_calendars
func ToTime(d Date) time.Time {
offset := -2
borderDate := Date{100, time.March, 2}
// We don't increase the offset for years divisible by 400, ie. every
// fourth loop, we keep track of that with skipYear, we can't simply
// check borderDate.year % 400, because when we reach 31. December
// 8299, the year will not be divisible by 100 anymore
skipYear := 1
for greaterOrEqual(d, borderDate) {
// We can simply add 100 to the year because in the julian
// calendar, if a date exists in year y, it always exists in
// year y+100
borderDate.year += 100
if skipYear%4 != 0 {
borderDate = dateDiff(borderDate, -1)
offset++
}
skipYear++
}
d = dateDiff(d, offset)
return time.Date(d.year, d.month, d.day, 0, 0, 0, 0, time.UTC)
}