Modify the julian package to use the GDate type

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.
This commit is contained in:
Petar Kapriš 2024-08-18 15:57:35 +02:00
parent 299832cb4b
commit f14ed81d81
2 changed files with 164 additions and 157 deletions

View file

@ -15,15 +15,22 @@ import "time"
// 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, Gregorian dates are represented with the
// time.Time type.
type Date struct {
// and only in the Julian calendar.
type JDate struct {
year int
month time.Month
day int
}
func greaterOrEqual(a, b Date) bool {
// 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 {
@ -33,6 +40,7 @@ func greaterOrEqual(a, b Date) bool {
}
}
// 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,
@ -57,9 +65,9 @@ func monthLength(year int, month time.Month) int {
return 28
}
// The dateDiff function calculates the Julian date which falls the given
// The jDateDiff function calculates the Julian date which falls the given
// number of dates before or after the given date.
func dateDiff(d Date, days int) 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
@ -101,23 +109,28 @@ func nextMonth(year int, month time.Month) (int, time.Month) {
}
// The NewDate function creates a new Julian date
func NewDate(year int, month time.Month, day int) 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 Date{year, month, day}
return JDate{year, month, day}
}
// The function JDateToTime converts a given Julian date into a Gregorian date
// 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 JDateToTime(d Date) time.Time {
func JDateToGDate(d JDate) GDate {
offset := -2
borderDate := Date{100, time.March, 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
@ -129,27 +142,27 @@ func JDateToTime(d Date) time.Time {
// year y+100
borderDate.year += 100
if skipYear%4 != 0 {
borderDate = dateDiff(borderDate, -1)
borderDate = jDateDiff(borderDate, -1)
offset++
}
skipYear++
}
d = dateDiff(d, offset)
return time.Date(d.year, d.month, d.day, 0, 0, 0, 0, time.UTC)
d = jDateDiff(d, offset)
return GDate{d.year, d.month, d.day}
}
// The function TimeToJDate converts a given Gregorian date into a Julian date.
// 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 TimeToJDate(t time.Time) Date {
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 := t.Year()
if t.Month() < time.March {
year := g.year
if g.month < time.March {
year--
}
@ -161,7 +174,7 @@ func TimeToJDate(t time.Time) Date {
borderYear += 100
}
d := Date{t.Year(), t.Month(), t.Day()}
d = dateDiff(d, -offset)
d := JDate{g.year, g.month, g.day}
d = jDateDiff(d, -offset)
return d
}

View file

@ -38,21 +38,21 @@ func TestMonthLength(t *testing.T) {
}
}
func TestDateDiff(t *testing.T) {
func TestJDateDiff(t *testing.T) {
var tests = []struct {
d Date
d JDate
days int
want Date
want JDate
}{
{Date{2001, time.January, 20}, 5, Date{2001, time.January, 25}},
{Date{2001, time.February, 28}, 5, Date{2001, time.March, 5}},
{Date{2004, time.February, 28}, 5, Date{2004, time.March, 4}},
{JDate{2001, time.January, 20}, 5, JDate{2001, time.January, 25}},
{JDate{2001, time.February, 28}, 5, JDate{2001, time.March, 5}},
{JDate{2004, time.February, 28}, 5, JDate{2004, time.March, 4}},
}
for _, tt := range tests {
testname := fmt.Sprintf("%v, %d", tt.d, tt.days)
t.Run(testname, func(t *testing.T) {
ans := dateDiff(tt.d, tt.days)
ans := jDateDiff(tt.d, tt.days)
if ans != tt.want {
t.Errorf("got %d, want %d", ans, tt.want)
}
@ -67,78 +67,75 @@ func TestNextMonth(t *testing.T) {
}
}
func TestJDateToTime(t *testing.T) {
gDate := func(year int, month time.Month, day int) time.Time {
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
}
func TestJDateToGDate(t *testing.T) {
var tests = []struct {
d Date
want time.Time
d JDate
want GDate
}{
{Date{100, time.February, 29}, gDate(100, time.February, 27)},
{Date{100, time.March, 1}, gDate(100, time.February, 28)},
{Date{100, time.March, 2}, gDate(100, time.March, 1)},
{Date{200, time.February, 28}, gDate(200, time.February, 27)},
{Date{200, time.February, 29}, gDate(200, time.February, 28)},
{Date{200, time.March, 1}, gDate(200, time.March, 1)},
{Date{300, time.February, 28}, gDate(300, time.February, 28)},
{Date{300, time.February, 29}, gDate(300, time.March, 1)},
{Date{300, time.March, 1}, gDate(300, time.March, 2)},
{Date{500, time.February, 28}, gDate(500, time.March, 1)},
{Date{500, time.February, 29}, gDate(500, time.March, 2)},
{Date{500, time.March, 1}, gDate(500, time.March, 3)},
{Date{600, time.February, 28}, gDate(600, time.March, 2)},
{Date{600, time.February, 29}, gDate(600, time.March, 3)},
{Date{600, time.March, 1}, gDate(600, time.March, 4)},
{Date{700, time.February, 28}, gDate(700, time.March, 3)},
{Date{700, time.February, 29}, gDate(700, time.March, 4)},
{Date{700, time.March, 1}, gDate(700, time.March, 5)},
{Date{900, time.February, 28}, gDate(900, time.March, 4)},
{Date{900, time.February, 29}, gDate(900, time.March, 5)},
{Date{900, time.March, 1}, gDate(900, time.March, 6)},
{Date{1000, time.February, 28}, gDate(1000, time.March, 5)},
{Date{1000, time.February, 29}, gDate(1000, time.March, 6)},
{Date{1000, time.March, 1}, gDate(1000, time.March, 7)},
{Date{1100, time.February, 28}, gDate(1100, time.March, 6)},
{Date{1100, time.February, 29}, gDate(1100, time.March, 7)},
{Date{1100, time.March, 1}, gDate(1100, time.March, 8)},
{Date{1300, time.February, 28}, gDate(1300, time.March, 7)},
{Date{1300, time.February, 29}, gDate(1300, time.March, 8)},
{Date{1300, time.March, 1}, gDate(1300, time.March, 9)},
{Date{1400, time.February, 28}, gDate(1400, time.March, 8)},
{Date{1400, time.February, 29}, gDate(1400, time.March, 9)},
{Date{1400, time.March, 1}, gDate(1400, time.March, 10)},
{Date{1500, time.February, 28}, gDate(1500, time.March, 9)},
{Date{1500, time.February, 29}, gDate(1500, time.March, 10)},
{Date{1500, time.March, 1}, gDate(1500, time.March, 11)},
{Date{1582, time.October, 4}, gDate(1582, time.October, 14)},
{Date{1582, time.October, 5}, gDate(1582, time.October, 15)},
{Date{1582, time.October, 6}, gDate(1582, time.October, 16)},
{Date{1700, time.February, 18}, gDate(1700, time.February, 28)},
{Date{1700, time.February, 19}, gDate(1700, time.March, 1)},
{Date{1700, time.February, 28}, gDate(1700, time.March, 10)},
{Date{1700, time.February, 29}, gDate(1700, time.March, 11)},
{Date{1700, time.March, 1}, gDate(1700, time.March, 12)},
{Date{1800, time.February, 17}, gDate(1800, time.February, 28)},
{Date{1800, time.February, 18}, gDate(1800, time.March, 1)},
{Date{1800, time.February, 28}, gDate(1800, time.March, 11)},
{Date{1800, time.February, 29}, gDate(1800, time.March, 12)},
{Date{1800, time.March, 1}, gDate(1800, time.March, 13)},
{Date{1900, time.February, 16}, gDate(1900, time.February, 28)},
{Date{1900, time.February, 17}, gDate(1900, time.March, 1)},
{Date{1900, time.February, 28}, gDate(1900, time.March, 12)},
{Date{1900, time.February, 29}, gDate(1900, time.March, 13)},
{Date{1900, time.March, 1}, gDate(1900, time.March, 14)},
{Date{2100, time.February, 15}, gDate(2100, time.February, 28)},
{Date{2100, time.February, 16}, gDate(2100, time.March, 1)},
{Date{2100, time.February, 28}, gDate(2100, time.March, 13)},
{Date{2100, time.February, 29}, gDate(2100, time.March, 14)},
{JDate{100, time.February, 29}, GDate{100, time.February, 27}},
{JDate{100, time.March, 1}, GDate{100, time.February, 28}},
{JDate{100, time.March, 2}, GDate{100, time.March, 1}},
{JDate{200, time.February, 28}, GDate{200, time.February, 27}},
{JDate{200, time.February, 29}, GDate{200, time.February, 28}},
{JDate{200, time.March, 1}, GDate{200, time.March, 1}},
{JDate{300, time.February, 28}, GDate{300, time.February, 28}},
{JDate{300, time.February, 29}, GDate{300, time.March, 1}},
{JDate{300, time.March, 1}, GDate{300, time.March, 2}},
{JDate{500, time.February, 28}, GDate{500, time.March, 1}},
{JDate{500, time.February, 29}, GDate{500, time.March, 2}},
{JDate{500, time.March, 1}, GDate{500, time.March, 3}},
{JDate{600, time.February, 28}, GDate{600, time.March, 2}},
{JDate{600, time.February, 29}, GDate{600, time.March, 3}},
{JDate{600, time.March, 1}, GDate{600, time.March, 4}},
{JDate{700, time.February, 28}, GDate{700, time.March, 3}},
{JDate{700, time.February, 29}, GDate{700, time.March, 4}},
{JDate{700, time.March, 1}, GDate{700, time.March, 5}},
{JDate{900, time.February, 28}, GDate{900, time.March, 4}},
{JDate{900, time.February, 29}, GDate{900, time.March, 5}},
{JDate{900, time.March, 1}, GDate{900, time.March, 6}},
{JDate{1000, time.February, 28}, GDate{1000, time.March, 5}},
{JDate{1000, time.February, 29}, GDate{1000, time.March, 6}},
{JDate{1000, time.March, 1}, GDate{1000, time.March, 7}},
{JDate{1100, time.February, 28}, GDate{1100, time.March, 6}},
{JDate{1100, time.February, 29}, GDate{1100, time.March, 7}},
{JDate{1100, time.March, 1}, GDate{1100, time.March, 8}},
{JDate{1300, time.February, 28}, GDate{1300, time.March, 7}},
{JDate{1300, time.February, 29}, GDate{1300, time.March, 8}},
{JDate{1300, time.March, 1}, GDate{1300, time.March, 9}},
{JDate{1400, time.February, 28}, GDate{1400, time.March, 8}},
{JDate{1400, time.February, 29}, GDate{1400, time.March, 9}},
{JDate{1400, time.March, 1}, GDate{1400, time.March, 10}},
{JDate{1500, time.February, 28}, GDate{1500, time.March, 9}},
{JDate{1500, time.February, 29}, GDate{1500, time.March, 10}},
{JDate{1500, time.March, 1}, GDate{1500, time.March, 11}},
{JDate{1582, time.October, 4}, GDate{1582, time.October, 14}},
{JDate{1582, time.October, 5}, GDate{1582, time.October, 15}},
{JDate{1582, time.October, 6}, GDate{1582, time.October, 16}},
{JDate{1700, time.February, 18}, GDate{1700, time.February, 28}},
{JDate{1700, time.February, 19}, GDate{1700, time.March, 1}},
{JDate{1700, time.February, 28}, GDate{1700, time.March, 10}},
{JDate{1700, time.February, 29}, GDate{1700, time.March, 11}},
{JDate{1700, time.March, 1}, GDate{1700, time.March, 12}},
{JDate{1800, time.February, 17}, GDate{1800, time.February, 28}},
{JDate{1800, time.February, 18}, GDate{1800, time.March, 1}},
{JDate{1800, time.February, 28}, GDate{1800, time.March, 11}},
{JDate{1800, time.February, 29}, GDate{1800, time.March, 12}},
{JDate{1800, time.March, 1}, GDate{1800, time.March, 13}},
{JDate{1900, time.February, 16}, GDate{1900, time.February, 28}},
{JDate{1900, time.February, 17}, GDate{1900, time.March, 1}},
{JDate{1900, time.February, 28}, GDate{1900, time.March, 12}},
{JDate{1900, time.February, 29}, GDate{1900, time.March, 13}},
{JDate{1900, time.March, 1}, GDate{1900, time.March, 14}},
{JDate{2100, time.February, 15}, GDate{2100, time.February, 28}},
{JDate{2100, time.February, 16}, GDate{2100, time.March, 1}},
{JDate{2100, time.February, 28}, GDate{2100, time.March, 13}},
{JDate{2100, time.February, 29}, GDate{2100, time.March, 14}},
}
for _, tt := range tests {
testname := fmt.Sprintf("%v, %v", tt.d, tt.want)
t.Run(testname, func(t *testing.T) {
ans := JDateToTime(tt.d)
ans := JDateToGDate(tt.d)
if ans != tt.want {
t.Errorf("got %v, want %v", ans, tt.want)
}
@ -146,78 +143,75 @@ func TestJDateToTime(t *testing.T) {
}
}
func TestTimeToJDate(t *testing.T) {
gDate := func(year int, month time.Month, day int) time.Time {
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
}
func TestGDateToJDate(t *testing.T) {
var tests = []struct {
gd time.Time
want Date
gd GDate
want JDate
}{
{gDate(100, time.February, 27), Date{100, time.February, 29}},
{gDate(100, time.February, 28), Date{100, time.March, 1}},
{gDate(100, time.March, 1), Date{100, time.March, 2}},
{gDate(200, time.February, 27), Date{200, time.February, 28}},
{gDate(200, time.February, 28), Date{200, time.February, 29}},
{gDate(200, time.March, 1), Date{200, time.March, 1}},
{gDate(300, time.February, 28), Date{300, time.February, 28}},
{gDate(300, time.March, 1), Date{300, time.February, 29}},
{gDate(300, time.March, 2), Date{300, time.March, 1}},
{gDate(500, time.March, 1), Date{500, time.February, 28}},
{gDate(500, time.March, 2), Date{500, time.February, 29}},
{gDate(500, time.March, 3), Date{500, time.March, 1}},
{gDate(600, time.March, 2), Date{600, time.February, 28}},
{gDate(600, time.March, 3), Date{600, time.February, 29}},
{gDate(600, time.March, 4), Date{600, time.March, 1}},
{gDate(700, time.March, 3), Date{700, time.February, 28}},
{gDate(700, time.March, 4), Date{700, time.February, 29}},
{gDate(700, time.March, 5), Date{700, time.March, 1}},
{gDate(900, time.March, 4), Date{900, time.February, 28}},
{gDate(900, time.March, 5), Date{900, time.February, 29}},
{gDate(900, time.March, 6), Date{900, time.March, 1}},
{gDate(1000, time.March, 5), Date{1000, time.February, 28}},
{gDate(1000, time.March, 6), Date{1000, time.February, 29}},
{gDate(1000, time.March, 7), Date{1000, time.March, 1}},
{gDate(1100, time.March, 6), Date{1100, time.February, 28}},
{gDate(1100, time.March, 7), Date{1100, time.February, 29}},
{gDate(1100, time.March, 8), Date{1100, time.March, 1}},
{gDate(1300, time.March, 7), Date{1300, time.February, 28}},
{gDate(1300, time.March, 8), Date{1300, time.February, 29}},
{gDate(1300, time.March, 9), Date{1300, time.March, 1}},
{gDate(1400, time.March, 8), Date{1400, time.February, 28}},
{gDate(1400, time.March, 9), Date{1400, time.February, 29}},
{gDate(1400, time.March, 10), Date{1400, time.March, 1}},
{gDate(1500, time.March, 9), Date{1500, time.February, 28}},
{gDate(1500, time.March, 10), Date{1500, time.February, 29}},
{gDate(1500, time.March, 11), Date{1500, time.March, 1}},
{gDate(1582, time.October, 14), Date{1582, time.October, 4}},
{gDate(1582, time.October, 15), Date{1582, time.October, 5}},
{gDate(1582, time.October, 16), Date{1582, time.October, 6}},
{gDate(1700, time.February, 28), Date{1700, time.February, 18}},
{gDate(1700, time.March, 1), Date{1700, time.February, 19}},
{gDate(1700, time.March, 10), Date{1700, time.February, 28}},
{gDate(1700, time.March, 11), Date{1700, time.February, 29}},
{gDate(1700, time.March, 12), Date{1700, time.March, 1}},
{gDate(1800, time.February, 28), Date{1800, time.February, 17}},
{gDate(1800, time.March, 1), Date{1800, time.February, 18}},
{gDate(1800, time.March, 11), Date{1800, time.February, 28}},
{gDate(1800, time.March, 12), Date{1800, time.February, 29}},
{gDate(1800, time.March, 13), Date{1800, time.March, 1}},
{gDate(1900, time.February, 28), Date{1900, time.February, 16}},
{gDate(1900, time.March, 1), Date{1900, time.February, 17}},
{gDate(1900, time.March, 12), Date{1900, time.February, 28}},
{gDate(1900, time.March, 13), Date{1900, time.February, 29}},
{gDate(1900, time.March, 14), Date{1900, time.March, 1}},
{gDate(2100, time.February, 28), Date{2100, time.February, 15}},
{gDate(2100, time.March, 1), Date{2100, time.February, 16}},
{gDate(2100, time.March, 13), Date{2100, time.February, 28}},
{gDate(2100, time.March, 14), Date{2100, time.February, 29}},
{GDate{100, time.February, 27}, JDate{100, time.February, 29}},
{GDate{100, time.February, 28}, JDate{100, time.March, 1}},
{GDate{100, time.March, 1}, JDate{100, time.March, 2}},
{GDate{200, time.February, 27}, JDate{200, time.February, 28}},
{GDate{200, time.February, 28}, JDate{200, time.February, 29}},
{GDate{200, time.March, 1}, JDate{200, time.March, 1}},
{GDate{300, time.February, 28}, JDate{300, time.February, 28}},
{GDate{300, time.March, 1}, JDate{300, time.February, 29}},
{GDate{300, time.March, 2}, JDate{300, time.March, 1}},
{GDate{500, time.March, 1}, JDate{500, time.February, 28}},
{GDate{500, time.March, 2}, JDate{500, time.February, 29}},
{GDate{500, time.March, 3}, JDate{500, time.March, 1}},
{GDate{600, time.March, 2}, JDate{600, time.February, 28}},
{GDate{600, time.March, 3}, JDate{600, time.February, 29}},
{GDate{600, time.March, 4}, JDate{600, time.March, 1}},
{GDate{700, time.March, 3}, JDate{700, time.February, 28}},
{GDate{700, time.March, 4}, JDate{700, time.February, 29}},
{GDate{700, time.March, 5}, JDate{700, time.March, 1}},
{GDate{900, time.March, 4}, JDate{900, time.February, 28}},
{GDate{900, time.March, 5}, JDate{900, time.February, 29}},
{GDate{900, time.March, 6}, JDate{900, time.March, 1}},
{GDate{1000, time.March, 5}, JDate{1000, time.February, 28}},
{GDate{1000, time.March, 6}, JDate{1000, time.February, 29}},
{GDate{1000, time.March, 7}, JDate{1000, time.March, 1}},
{GDate{1100, time.March, 6}, JDate{1100, time.February, 28}},
{GDate{1100, time.March, 7}, JDate{1100, time.February, 29}},
{GDate{1100, time.March, 8}, JDate{1100, time.March, 1}},
{GDate{1300, time.March, 7}, JDate{1300, time.February, 28}},
{GDate{1300, time.March, 8}, JDate{1300, time.February, 29}},
{GDate{1300, time.March, 9}, JDate{1300, time.March, 1}},
{GDate{1400, time.March, 8}, JDate{1400, time.February, 28}},
{GDate{1400, time.March, 9}, JDate{1400, time.February, 29}},
{GDate{1400, time.March, 10}, JDate{1400, time.March, 1}},
{GDate{1500, time.March, 9}, JDate{1500, time.February, 28}},
{GDate{1500, time.March, 10}, JDate{1500, time.February, 29}},
{GDate{1500, time.March, 11}, JDate{1500, time.March, 1}},
{GDate{1582, time.October, 14}, JDate{1582, time.October, 4}},
{GDate{1582, time.October, 15}, JDate{1582, time.October, 5}},
{GDate{1582, time.October, 16}, JDate{1582, time.October, 6}},
{GDate{1700, time.February, 28}, JDate{1700, time.February, 18}},
{GDate{1700, time.March, 1}, JDate{1700, time.February, 19}},
{GDate{1700, time.March, 10}, JDate{1700, time.February, 28}},
{GDate{1700, time.March, 11}, JDate{1700, time.February, 29}},
{GDate{1700, time.March, 12}, JDate{1700, time.March, 1}},
{GDate{1800, time.February, 28}, JDate{1800, time.February, 17}},
{GDate{1800, time.March, 1}, JDate{1800, time.February, 18}},
{GDate{1800, time.March, 11}, JDate{1800, time.February, 28}},
{GDate{1800, time.March, 12}, JDate{1800, time.February, 29}},
{GDate{1800, time.March, 13}, JDate{1800, time.March, 1}},
{GDate{1900, time.February, 28}, JDate{1900, time.February, 16}},
{GDate{1900, time.March, 1}, JDate{1900, time.February, 17}},
{GDate{1900, time.March, 12}, JDate{1900, time.February, 28}},
{GDate{1900, time.March, 13}, JDate{1900, time.February, 29}},
{GDate{1900, time.March, 14}, JDate{1900, time.March, 1}},
{GDate{2100, time.February, 28}, JDate{2100, time.February, 15}},
{GDate{2100, time.March, 1}, JDate{2100, time.February, 16}},
{GDate{2100, time.March, 13}, JDate{2100, time.February, 28}},
{GDate{2100, time.March, 14}, JDate{2100, time.February, 29}},
}
for _, tt := range tests {
testname := fmt.Sprintf("%v, %v", tt.gd, tt.want)
t.Run(testname, func(t *testing.T) {
ans := TimeToJDate(tt.gd)
ans := GDateToJDate(tt.gd)
if ans != tt.want {
t.Errorf("got %v, want %v", ans, tt.want)
}