You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
4.7 KiB
141 lines
4.7 KiB
package excelize |
|
|
|
import ( |
|
"math" |
|
"time" |
|
) |
|
|
|
// timeLocationUTC defined the UTC time location. |
|
var timeLocationUTC, _ = time.LoadLocation("UTC") |
|
|
|
// timeToUTCTime provides a 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 a function to convert time to Excel time. |
|
func timeToExcelTime(t time.Time) float64 { |
|
// TODO in future this should probably also handle date1904 and like TimeFromExcelTime |
|
var excelTime float64 |
|
var deltaDays int64 |
|
excelTime = 0 |
|
deltaDays = 290 * 364 |
|
// check if UnixNano would be out of int64 range |
|
for t.Unix() > deltaDays*24*60*60 { |
|
// reduce by aprox. 290 years, which is max for int64 nanoseconds |
|
delta := time.Duration(deltaDays) * 24 * time.Hour |
|
excelTime = excelTime + float64(deltaDays) |
|
t = t.Add(-delta) |
|
} |
|
// finally add remainder of UnixNano to keep nano precision |
|
// and 25569 which is days between 1900 and 1970 |
|
return excelTime + float64(t.UnixNano())/8.64e13 + 25569.0 |
|
} |
|
|
|
// shiftJulianToNoon provides a function to process julian date to noon. |
|
func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) { |
|
switch { |
|
case -0.5 < julianFraction && julianFraction < 0.5: |
|
julianFraction += 0.5 |
|
case julianFraction >= 0.5: |
|
julianDays++ |
|
julianFraction -= 0.5 |
|
case julianFraction <= -0.5: |
|
julianDays-- |
|
julianFraction += 1.5 |
|
} |
|
return julianDays, julianFraction |
|
} |
|
|
|
// fractionOfADay provides a 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. |
|
func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) { |
|
|
|
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 |
|
} |
|
|
|
// julianDateToGregorianTime provides a function to convert julian date to |
|
// gregorian time. |
|
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) |
|
} |
|
|
|
// 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. |
|
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 |
|
} |
|
|
|
// timeFromExcelTime provides a function to convert an excelTime |
|
// representation (stored as a floating point number) to a time.Time. |
|
func timeFromExcelTime(excelTime float64, date1904 bool) time.Time { |
|
const MDD int64 = 106750 // Max time.Duration Days, aprox. 290 years |
|
var date time.Time |
|
var intPart = int64(excelTime) |
|
// Excel uses Julian dates prior to March 1st 1900, and Gregorian |
|
// thereafter. |
|
if intPart <= 61 { |
|
const OFFSET1900 = 15018.0 |
|
const OFFSET1904 = 16480.0 |
|
const MJD0 float64 = 2400000.5 |
|
var date time.Time |
|
if date1904 { |
|
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904) |
|
} else { |
|
date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900) |
|
} |
|
return date |
|
} |
|
var floatPart = excelTime - float64(intPart) |
|
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) |
|
} |
|
|
|
// Duration is limited to aprox. 290 years |
|
for intPart > MDD { |
|
durationDays := time.Duration(MDD) * time.Hour * 24 |
|
date = date.Add(durationDays) |
|
intPart = intPart - MDD |
|
} |
|
durationDays := time.Duration(intPart) * time.Hour * 24 |
|
durationPart := time.Duration(dayNanoSeconds * floatPart) |
|
return date.Add(durationDays).Add(durationPart) |
|
}
|
|
|