Add julian Date package with conversion to time.Time
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.
This commit is contained in:
		
							parent
							
								
									73fde0698c
								
							
						
					
					
						commit
						bd5c9fe692
					
				
					 1 changed files with 122 additions and 0 deletions
				
			
		
							
								
								
									
										122
									
								
								julian/julian.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								julian/julian.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,122 @@ | ||||||
|  | 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) | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 Petar Kapriš
						Petar Kapriš