ck/julian/julian.go
2024-07-22 21:16:24 +02:00

146 lines
3.7 KiB
Go

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 JDateToTime(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)
}
func TimeToJDate(t time.Time) Date {
offset := -2
// The date when the offset changes in the gregorian calendar is always
// 1st of March, so we can always just check the year, and see if we're
// past 1st of March year
year := t.Year()
if t.Month() < time.March {
year--
}
borderYear := 100
for year >= borderYear {
if borderYear%400 != 0 {
offset++
}
borderYear += 100
}
d := Date{t.Year(), t.Month(), t.Day()}
d = dateDiff(d, -offset)
return d
}