![Petar Kapriš](/assets/img/avatar_default.png)
Up until now the julian package relied on the time.Time type to represent Gregorian dates, and julian.Date to represent Julian dates, however this both looks inconsistent, and is more opaque and complicated because time.Time carries with it a lot of information we don't care about. Therefore this has been modified, now the conversion package has two types julian.JDate and julian.GDate for their respective calendars.
181 lines
5.2 KiB
Go
181 lines
5.2 KiB
Go
// 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}
|
|
}
|
|
|
|
// 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 {
|
|
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 {
|
|
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)
|
|
return d
|
|
}
|