2024-07-23 23:00:30 +02:00
|
|
|
// The julian package enables working with the Julian calendar, as well as
|
|
|
|
// conversion from the Julian to the Gregorian and vice versa. Gregorian dates
|
|
|
|
// are usually represented using the Go builtin time.Time type, and Julian
|
|
|
|
// Dates are represented with the julian.Date type.
|
|
|
|
// BUG(kayprish) the Go time.Time type doesn't work properly on dates before
|
|
|
|
// the Christian era, for example, it has the non-existant year 0, and since
|
|
|
|
// this package depends on that type, and the ck module has no need for BCE
|
|
|
|
// dates, no special care was taken to make sure BC dates work, only AD date
|
|
|
|
// conversions are supported
|
2024-07-21 00:21:34 +02:00
|
|
|
package julian
|
|
|
|
|
|
|
|
import "time"
|
|
|
|
|
2024-07-23 23:00:30 +02:00
|
|
|
// `proleptic`, ie. there is no
|
|
|
|
// need to give special care to dates before 15. October 1582.
|
|
|
|
|
|
|
|
// The julian.Date type is meant to represent a date in the Julian calendar,
|
|
|
|
// and only in the Julian calendar, Gregorian dates are represented with the
|
|
|
|
// time.Time type.
|
2024-07-21 00:21:34 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-07-23 23:00:30 +02:00
|
|
|
// The dateDiff function calculates the Julian date which falls the given
|
|
|
|
// number of dates before or after the given date.
|
2024-07-21 00:21:34 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-23 23:00:30 +02:00
|
|
|
// The NewDate function creates a new Julian date
|
|
|
|
func NewDate(year int, month time.Month, day int) Date {
|
2024-07-21 00:21:34 +02:00
|
|
|
// 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}
|
|
|
|
}
|
|
|
|
|
2024-07-23 23:00:30 +02:00
|
|
|
// The function JDateToTime converts a given Julian date into a Gregorian date
|
|
|
|
// represented with the time.Time type.
|
|
|
|
//
|
2024-07-21 00:21:34 +02:00
|
|
|
// The algorithm, in large part: follows this article:
|
|
|
|
// https://en.wikipedia.org/wiki/Conversion_between_Julian_and_Gregorian_calendars
|
2024-07-22 21:15:20 +02:00
|
|
|
func JDateToTime(d Date) time.Time {
|
2024-07-21 00:21:34 +02:00
|
|
|
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)
|
|
|
|
}
|
2024-07-22 21:16:24 +02:00
|
|
|
|
2024-07-23 23:00:30 +02:00
|
|
|
// The function TimeToJDate converts a given Gregorian date into a Julian date.
|
|
|
|
//
|
|
|
|
// The algorithm, in large part: follows this article:
|
|
|
|
// https://en.wikipedia.org/wiki/Conversion_between_Julian_and_Gregorian_calendars
|
2024-07-22 21:16:24 +02:00
|
|
|
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
|
|
|
|
}
|