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 }