date.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. // Copyright 2016 - 2023 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 and
  6. // read from XLAM / XLSM / XLSX / XLTM / XLTX files. Supports reading and
  7. // writing spreadsheet documents generated by Microsoft Excel™ 2007 and later.
  8. // Supports complex components by high compatibility, and provided streaming
  9. // API for generating or reading data from a worksheet with huge amounts of
  10. // data. This library needs Go version 1.16 or later.
  11. package excelize
  12. import (
  13. "math"
  14. "time"
  15. )
  16. const (
  17. nanosInADay = float64((24 * time.Hour) / time.Nanosecond)
  18. dayNanoseconds = 24 * time.Hour
  19. maxDuration = 290 * 364 * dayNanoseconds
  20. roundEpsilon = 1e-9
  21. )
  22. var (
  23. daysInMonth = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
  24. excel1900Epoc = time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC)
  25. excel1904Epoc = time.Date(1904, time.January, 1, 0, 0, 0, 0, time.UTC)
  26. excelMinTime1900 = time.Date(1899, time.December, 31, 0, 0, 0, 0, time.UTC)
  27. excelBuggyPeriodStart = time.Date(1900, time.March, 1, 0, 0, 0, 0, time.UTC).Add(-time.Nanosecond)
  28. )
  29. // timeToExcelTime provides a function to convert time to Excel time.
  30. func timeToExcelTime(t time.Time, date1904 bool) (float64, error) {
  31. date := excelMinTime1900
  32. if date1904 {
  33. date = excel1904Epoc
  34. }
  35. if t.Before(date) {
  36. return 0, nil
  37. }
  38. tt, diff, result := t, t.Sub(date), 0.0
  39. for diff >= maxDuration {
  40. result += float64(maxDuration / dayNanoseconds)
  41. tt = tt.Add(-maxDuration)
  42. diff = tt.Sub(date)
  43. }
  44. rem := diff % dayNanoseconds
  45. result += float64(diff-rem)/float64(dayNanoseconds) + float64(rem)/float64(dayNanoseconds)
  46. // Excel dates after 28th February 1900 are actually one day out.
  47. // Excel behaves as though the date 29th February 1900 existed, which it didn't.
  48. // Microsoft intentionally included this bug in Excel so that it would remain compatible with the spreadsheet
  49. // program that had the majority market share at the time; Lotus 1-2-3.
  50. // https://www.myonlinetraininghub.com/excel-date-and-time
  51. if !date1904 && t.After(excelBuggyPeriodStart) {
  52. result++
  53. }
  54. return result, nil
  55. }
  56. // shiftJulianToNoon provides a function to process julian date to noon.
  57. func shiftJulianToNoon(julianDays, julianFraction float64) (float64, float64) {
  58. switch {
  59. case -0.5 < julianFraction && julianFraction < 0.5:
  60. julianFraction += 0.5
  61. case julianFraction >= 0.5:
  62. julianDays++
  63. julianFraction -= 0.5
  64. case julianFraction <= -0.5:
  65. julianDays--
  66. julianFraction += 1.5
  67. }
  68. return julianDays, julianFraction
  69. }
  70. // fractionOfADay provides a function to return the integer values for hour,
  71. // minutes, seconds and nanoseconds that comprised a given fraction of a day.
  72. // values would round to 1 us.
  73. func fractionOfADay(fraction float64) (hours, minutes, seconds, nanoseconds int) {
  74. const (
  75. c1us = 1e3
  76. c1s = 1e9
  77. c1day = 24 * 60 * 60 * c1s
  78. )
  79. frac := int64(c1day*fraction + c1us/2)
  80. nanoseconds = int((frac%c1s)/c1us) * c1us
  81. frac /= c1s
  82. seconds = int(frac % 60)
  83. frac /= 60
  84. minutes = int(frac % 60)
  85. hours = int(frac / 60)
  86. return
  87. }
  88. // julianDateToGregorianTime provides a function to convert julian date to
  89. // gregorian time.
  90. func julianDateToGregorianTime(part1, part2 float64) time.Time {
  91. part1I, part1F := math.Modf(part1)
  92. part2I, part2F := math.Modf(part2)
  93. julianDays := part1I + part2I
  94. julianFraction := part1F + part2F
  95. julianDays, julianFraction = shiftJulianToNoon(julianDays, julianFraction)
  96. day, month, year := doTheFliegelAndVanFlandernAlgorithm(int(julianDays))
  97. hours, minutes, seconds, nanoseconds := fractionOfADay(julianFraction)
  98. return time.Date(year, time.Month(month), day, hours, minutes, seconds, nanoseconds, time.UTC)
  99. }
  100. // doTheFliegelAndVanFlandernAlgorithm; By this point generations of
  101. // programmers have repeated the algorithm sent to the editor of
  102. // "Communications of the ACM" in 1968 (published in CACM, volume 11, number
  103. // 10, October 1968, p.657). None of those programmers seems to have found it
  104. // necessary to explain the constants or variable names set out by Henry F.
  105. // Fliegel and Thomas C. Van Flandern. Maybe one day I'll buy that jounal and
  106. // expand an explanation here - that day is not today.
  107. func doTheFliegelAndVanFlandernAlgorithm(jd int) (day, month, year int) {
  108. l := jd + 68569
  109. n := (4 * l) / 146097
  110. l = l - (146097*n+3)/4
  111. i := (4000 * (l + 1)) / 1461001
  112. l = l - (1461*i)/4 + 31
  113. j := (80 * l) / 2447
  114. d := l - (2447*j)/80
  115. l = j / 11
  116. m := j + 2 - (12 * l)
  117. y := 100*(n-49) + i + l
  118. return d, m, y
  119. }
  120. // timeFromExcelTime provides a function to convert an excelTime
  121. // representation (stored as a floating point number) to a time.Time.
  122. func timeFromExcelTime(excelTime float64, date1904 bool) time.Time {
  123. var date time.Time
  124. wholeDaysPart := int(excelTime)
  125. // Excel uses Julian dates prior to March 1st 1900, and Gregorian
  126. // thereafter.
  127. if wholeDaysPart <= 61 {
  128. const OFFSET1900 = 15018.0
  129. const OFFSET1904 = 16480.0
  130. const MJD0 float64 = 2400000.5
  131. var date time.Time
  132. if date1904 {
  133. date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1904)
  134. } else {
  135. date = julianDateToGregorianTime(MJD0, excelTime+OFFSET1900)
  136. }
  137. return date
  138. }
  139. floatPart := excelTime - float64(wholeDaysPart) + roundEpsilon
  140. if date1904 {
  141. date = excel1904Epoc
  142. } else {
  143. date = excel1900Epoc
  144. }
  145. durationPart := time.Duration(nanosInADay * floatPart)
  146. date = date.AddDate(0, 0, wholeDaysPart).Add(durationPart)
  147. if date.Nanosecond()/1e6 > 500 {
  148. return date.Round(time.Second)
  149. }
  150. return date.Truncate(time.Second)
  151. }
  152. // ExcelDateToTime converts a float-based excel date representation to a time.Time.
  153. func ExcelDateToTime(excelDate float64, use1904Format bool) (time.Time, error) {
  154. if excelDate < 0 {
  155. return time.Time{}, newInvalidExcelDateError(excelDate)
  156. }
  157. return timeFromExcelTime(excelDate, use1904Format), nil
  158. }
  159. // isLeapYear determine if leap year for a given year.
  160. func isLeapYear(y int) bool {
  161. if y == y/400*400 {
  162. return true
  163. }
  164. if y == y/100*100 {
  165. return false
  166. }
  167. return y == y/4*4
  168. }
  169. // getDaysInMonth provides a function to get the days by a given year and
  170. // month number.
  171. func getDaysInMonth(y, m int) int {
  172. if m == 2 && isLeapYear(y) {
  173. return 29
  174. }
  175. return daysInMonth[m-1]
  176. }
  177. // validateDate provides a function to validate if a valid date by a given
  178. // year, month, and day number.
  179. func validateDate(y, m, d int) bool {
  180. if m < 1 || m > 12 {
  181. return false
  182. }
  183. if d < 1 {
  184. return false
  185. }
  186. return d <= getDaysInMonth(y, m)
  187. }
  188. // formatYear converts the given year number into a 4-digit format.
  189. func formatYear(y int) int {
  190. if y < 1900 {
  191. if y < 30 {
  192. y += 2000
  193. } else {
  194. y += 1900
  195. }
  196. }
  197. return y
  198. }