ck/julian/julian.go

181 lines
5.2 KiB
Go
Raw Permalink Normal View History

// 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
package julian
import "time"
// `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.
type JDate struct {
year int
month time.Month
day int
}
// The julian.GDate type is meant to represent a date in the Gregorian
// calendar.
type GDate struct {
year int
month time.Month
day int
}
func greaterOrEqual(a, b JDate) 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
}
}
// Specifically for returning the length of a given month in the julian calendar.
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
}
// The jDateDiff function calculates the Julian date which falls the given
// number of dates before or after the given date.
func jDateDiff(d JDate, days int) JDate {
// 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
}
}
// The NewDate function creates a new Julian date
func NewJDate(year int, month time.Month, day int) JDate {
// TODO: deal with users giving negative dates
for day > monthLength(year, month) {
day -= monthLength(year, month)
year, month = nextMonth(year, month)
}
return JDate{year, month, day}
}
// The NewGDateFromTime creates a new Gregorian date, given a time.Time object
func NewGDateFromTime(t time.Time) GDate {
return GDate{t.Year(), t.Month(), t.Day()}
}
// The function JDateToGDate converts a given Julian date into a Gregorian date
// represented with the time.Time type.
//
// The algorithm, in large part: follows this article:
// https://en.wikipedia.org/wiki/Conversion_between_Julian_and_Gregorian_calendars
func JDateToGDate(d JDate) GDate {
offset := -2
borderDate := JDate{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 = jDateDiff(borderDate, -1)
offset++
}
skipYear++
}
d = jDateDiff(d, offset)
return GDate{d.year, d.month, d.day}
}
2024-07-22 21:16:24 +02:00
// The function GDateToJDate 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
func GDateToJDate(g GDate) JDate {
2024-07-22 21:16:24 +02:00
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 := g.year
if g.month < time.March {
2024-07-22 21:16:24 +02:00
year--
}
borderYear := 100
for year >= borderYear {
if borderYear%400 != 0 {
offset++
}
borderYear += 100
}
d := JDate{g.year, g.month, g.day}
d = jDateDiff(d, -offset)
2024-07-22 21:16:24 +02:00
return d
}