21
0
xls/date.go

120 lines
3.8 KiB
Go
Raw Normal View History

2015-03-25 07:47:26 +01:00
package xls
import (
"math"
"time"
)
2018-04-05 15:28:01 +02:00
// timeLocationUTC defined the UTC time location.
var timeLocationUTC, _ = time.LoadLocation("UTC")
2015-03-25 07:47:26 +01:00
2018-04-05 15:28:01 +02:00
// timeToUTCTime provides function to convert time to UTC time.
func timeToUTCTime(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), timeLocationUTC)
}
// timeToExcelTime provides function to convert time to Excel time.
func timeToExcelTime(t time.Time) float64 {
return float64(t.UnixNano())/8.64e13 + 25569.0
}
// shiftJulianToNoon provides function to process julian date to noon.
2015-03-25 07:47:26 +01:00
func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
switch {
case -0.5 < julianFraction && julianFraction < 0.5:
julianFraction += 0.5
case julianFraction >= 0.5:
2018-04-05 15:28:01 +02:00
julianDays++
2015-03-25 07:47:26 +01:00
julianFraction -= 0.5
case julianFraction <= -0.5:
2018-04-05 15:28:01 +02:00
julianDays--
2015-03-25 07:47:26 +01:00
julianFraction += 1.5
}
return julianDays, julianFraction
}
2018-04-05 15:28:01 +02:00
// fractionOfADay provides function to return the integer values for hour,
// minutes, seconds and nanoseconds that comprised a given fraction of a day.
// values would round to 1 us.
2015-03-25 07:47:26 +01:00
func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
2018-04-05 15:28:01 +02:00
const (
c1us = 1e3
c1s = 1e9
c1day = 24 * 60 * 60 * c1s
)
frac := int64(c1day*fraction + c1us/2)
nanoseconds = int((frac%c1s)/c1us) * c1us
frac /= c1s
seconds = int(frac % 60)
frac /= 60
minutes = int(frac % 60)
hours = int(frac / 60)
return
2015-03-25 07:47:26 +01:00
}
2018-04-05 15:28:01 +02:00
// julianDateToGregorianTime provides function to convert julian date to
// gregorian time.
2015-03-25 07:47:26 +01:00
func julianDateToGregorianTime(part1, part2 float64) time.Time {
part1I, part1F := math.Modf(part1)
part2I, part2F := math.Modf(part2)
julianDays := part1I + part2I
julianFraction := part1F + part2F
julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
}
2018-04-05 15:28:01 +02:00
// By this point generations of programmers have repeated the algorithm sent to
// the editor of "Communications of the ACM" in 1968 (published in CACM, volume
// 11, number 10, October 1968, p.657). None of those programmers seems to have
// found it necessary to explain the constants or variable names set out by
// Henry F. Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that
// jounal and expand an explanation here - that day is not today.
2015-03-25 07:47:26 +01:00
func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
l := jd + 68569
n := (4 * l) / 146097
l = l - (146097*n+3)/4
i := (4000 * (l + 1)) / 1461001
l = l - (1461*i)/4 + 31
j := (80 * l) / 2447
d := l - (2447*j)/80
l = j / 11
m := j + 2 - (12 * l)
y := 100*(n-49) + i + l
return d, m, y
}
2018-04-05 15:28:01 +02:00
// timeFromExcelTime provides function to convert an excelTime representation
// (stored as a floating point number) to a time.Time.
2015-09-30 05:17:25 +02:00
func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
2015-03-25 07:47:26 +01:00
var date time.Time
2018-04-05 15:28:01 +02:00
var intPart = int64(excelTime)
// Excel uses Julian dates prior to March 1st 1900, and Gregorian
// thereafter.
2015-03-25 07:47:26 +01:00
if intPart <= 61 {
const OFFSET1900 = 15018.0
const OFFSET1904 = 16480.0
2018-04-05 15:28:01 +02:00
const MJD0 float64 = 2400000.5
2015-03-25 07:47:26 +01:00
var date time.Time
if date1904 {
2018-04-05 15:28:01 +02:00
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
2015-03-25 07:47:26 +01:00
} else {
2018-04-05 15:28:01 +02:00
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
2015-03-25 07:47:26 +01:00
}
return date
}
2018-04-05 15:28:01 +02:00
var floatPart = excelTime - float64(intPart)
2015-03-25 07:47:26 +01:00
var dayNanoSeconds float64 = 24 * 60 * 60 * 1000 * 1000 * 1000
if date1904 {
date = time.Date(1904, 1, 1, 0, 0, 0, 0, time.UTC)
} else {
date = time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
}
durationDays := time.Duration(intPart) * time.Hour * 24
durationPart := time.Duration(dayNanoSeconds * floatPart)
return date.Add(durationDays).Add(durationPart)
}