// 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 }