![Petar Kapriš](/assets/img/avatar_default.png)
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.
123 lines
3.2 KiB
Go
123 lines
3.2 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 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)
|
|
}
|