date.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. // Copyright 2016 - 2021 The excelize Authors. All rights reserved. Use of
  2. // this source code is governed by a BSD-style license that can be found in
  3. // the LICENSE file.
  4. //
  5. // Package excelize providing a set of functions that allow you to write to
  6. // and read from XLSX / XLSM / XLTM files. Supports reading and writing
  7. // spreadsheet documents generated by Microsoft Excel™ 2007 and later. Supports
  8. // complex components by high compatibility, and provided streaming API for
  9. // generating or reading data from a worksheet with huge amounts of data. This
  10. // library needs Go version 1.15 or later.
  11. package excelize
  12. import (
  13. "errors"
  14. "math"
  15. "time"
  16. )
  17. const (
  18. dayNanoseconds = 24 * time.Hour
  19. maxDuration = 290 * 364 * dayNanoseconds
  20. )
  21. var (
  22. excelMinTime1900 = time.Date(1899, time.December, 31, 0, 0, 0, 0, time.UTC)
  23. excelBuggyPeriodStart = time.Date(1900, time.March, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond)
  24. )
  25. // timeToExcelTime provides a function to convert time to Excel time.
  26. func timeToExcelTime(t time.Time) (float64, error) {
  27. // TODO in future this should probably also handle date1904 and like TimeFromExcelTime
  28. // Force user to explicit convet passed value to UTC time.
  29. // Because for example 1900-01-01 00:00:00 +0300 MSK converts to 1900-01-01 00:00:00 +0230 LMT
  30. // probably due to daylight saving.
  31. if t.Location() != time.UTC {
  32. return 0.0, errors.New("only UTC time expected")
  33. }
  34. if t.Before(excelMinTime1900) {
  35. return 0.0, nil
  36. }
  37. tt := t
  38. diff := t.Sub(excelMinTime1900)
  39. result := float64(0)
  40. for diff >= maxDuration {
  41. result += float64(maxDuration / dayNanoseconds)
  42. tt = tt.Add(-maxDuration)
  43. diff = tt.Sub(excelMinTime1900)
  44. }
  45. rem := diff % dayNanoseconds
  46. result += float64(diff-rem)/float64(dayNanoseconds) + float64(rem)/float64(dayNanoseconds)
  47. // Excel dates after 28th February 1900 are actually one day out.
  48. // Excel behaves as though the date 29th February 1900 existed, which it didn't.
  49. // Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet
  50. // program that had the majority market share at the time; Lotus 1-2-3.
  51. // https://www.myonlinetraininghub.com/excel-date-and-time
  52. if t.After(excelBuggyPeriodStart) {
  53. result += 1.0
  54. }
  55. return result, nil
  56. }
  57. // shiftJulianToNoon provides a function to process julian date to noon.
  58. func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
  59. switch {
  60. case -0.5 < julianFraction && julianFraction < 0.5:
  61. julianFraction += 0.5
  62. case julianFraction >= 0.5:
  63. julianDays++
  64. julianFraction -= 0.5
  65. case julianFraction <= -0.5:
  66. julianDays--
  67. julianFraction += 1.5
  68. }
  69. return julianDays, julianFraction
  70. }
  71. // fractionOfADay provides a function to return the integer values for hour,
  72. // minutes, seconds and nanoseconds that comprised a given fraction of a day.
  73. // values would round to 1 us.
  74. func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
  75. const (
  76. c1us = 1e3
  77. c1s = 1e9
  78. c1day = 24 * 60 * 60 * c1s
  79. )
  80. frac := int64(c1day*fraction + c1us/2)
  81. nanoseconds = int((frac%c1s)/c1us) * c1us
  82. frac /= c1s
  83. seconds = int(frac % 60)
  84. frac /= 60
  85. minutes = int(frac % 60)
  86. hours = int(frac / 60)
  87. return
  88. }
  89. // julianDateToGregorianTime provides a function to convert julian date to
  90. // gregorian time.
  91. func julianDateToGregorianTime(part1, part2 float64) time.Time {
  92. part1I, part1F := math.Modf(part1)
  93. part2I, part2F := math.Modf(part2)
  94. julianDays := part1I + part2I
  95. julianFraction := part1F + part2F
  96. julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
  97. day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
  98. hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
  99. return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
  100. }
  101. // doTheFliegelAndVanFlandernAlgorithm; By this point generations of
  102. // programmers have repeated the algorithm sent to the editor of
  103. // "Communications of the ACM" in 1968 (published in CACM, volume 11, number
  104. // 10, October 1968, p.657). None of those programmers seems to have found it
  105. // necessary to explain the constants or variable names set out by Henry F.
  106. // Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that jounal and
  107. // expand an explanation here - that day is not today.
  108. func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
  109. l := jd + 68569
  110. n := (4 * l) / 146097
  111. l = l - (146097*n+3)/4
  112. i := (4000 * (l + 1)) / 1461001
  113. l = l - (1461*i)/4 + 31
  114. j := (80 * l) / 2447
  115. d := l - (2447*j)/80
  116. l = j / 11
  117. m := j + 2 - (12 * l)
  118. y := 100*(n-49) + i + l
  119. return d, m, y
  120. }
  121. // timeFromExcelTime provides a function to convert an excelTime
  122. // representation (stored as a floating point number) to a time.Time.
  123. func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
  124. const MDD int64 = 106750 // Max time.Duration Days, aprox. 290 years
  125. var date time.Time
  126. var intPart = int64(excelTime)
  127. // Excel uses Julian dates prior to March 1st 1900, and Gregorian
  128. // thereafter.
  129. if intPart <= 61 {
  130. const OFFSET1900 = 15018.0
  131. const OFFSET1904 = 16480.0
  132. const MJD0 float64 = 2400000.5
  133. var date time.Time
  134. if date1904 {
  135. date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
  136. } else {
  137. date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
  138. }
  139. return date
  140. }
  141. var floatPart = excelTime - float64(intPart)
  142. var dayNanoSeconds float64 = 24 * 60 * 60 * 1000 * 1000 * 1000
  143. if date1904 {
  144. date = time.Date(1904, 1, 1, 0, 0, 0, 0, time.UTC)
  145. } else {
  146. date = time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
  147. }
  148. // Duration is limited to aprox. 290 years
  149. for intPart > MDD {
  150. durationDays := time.Duration(MDD) * time.Hour * 24
  151. date = date.Add(durationDays)
  152. intPart = intPart - MDD
  153. }
  154. durationDays := time.Duration(intPart) * time.Hour * 24
  155. durationPart := time.Duration(dayNanoSeconds * floatPart)
  156. return date.Add(durationDays).Add(durationPart)
  157. }
  158. // ExcelDateToTime converts a float-based excel date representation to a time.Time.
  159. func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
  160. if excelDate < 0 {
  161. return time.Time{}, newInvalidExcelDateError(excelDate)
  162. }
  163. return timeFromExcelTime(excelDate, use1904Format), nil
  164. }