read-excel-file 9.3.4 → 9.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/CHANGELOG.md +6 -2
  2. package/README.md +32 -2
  3. package/browser/index.cjs +3 -0
  4. package/browser/index.d.ts +16 -0
  5. package/browser/index.js +4 -1
  6. package/bundle/read-excel-file.min.js +1 -1
  7. package/bundle/read-excel-file.min.js.map +1 -1
  8. package/commonjs/export/convertInputToNodeStream.js +0 -12
  9. package/commonjs/export/convertInputToNodeStream.js.map +1 -1
  10. package/commonjs/export/parseSheet.js +14 -7
  11. package/commonjs/export/parseSheet.js.map +1 -1
  12. package/commonjs/export/readSheetBrowser.js +3 -6
  13. package/commonjs/export/readSheetBrowser.js.map +1 -1
  14. package/commonjs/export/readSheetNode.js +3 -6
  15. package/commonjs/export/readSheetNode.js.map +1 -1
  16. package/commonjs/export/readSheetUniversal.js +2 -2
  17. package/commonjs/export/readSheetUniversal.js.map +1 -1
  18. package/commonjs/export/readSheetWebWorker.js +5 -5
  19. package/commonjs/export/readSheetWebWorker.js.map +1 -1
  20. package/commonjs/export/readXlsxFileBrowser.js +3 -6
  21. package/commonjs/export/readXlsxFileBrowser.js.map +1 -1
  22. package/commonjs/export/readXlsxFileNode.js +3 -6
  23. package/commonjs/export/readXlsxFileNode.js.map +1 -1
  24. package/commonjs/export/readXlsxFileUniversal.js +2 -2
  25. package/commonjs/export/readXlsxFileUniversal.js.map +1 -1
  26. package/commonjs/export/readXlsxFileWebWorker.js +5 -5
  27. package/commonjs/export/readXlsxFileWebWorker.js.map +1 -1
  28. package/commonjs/export/unpackXlsxFileBrowser.js +15 -4
  29. package/commonjs/export/unpackXlsxFileBrowser.js.map +1 -1
  30. package/commonjs/export/unpackXlsxFileBrowser.test.js.map +1 -0
  31. package/commonjs/export/unpackXlsxFileNode.js +32 -5
  32. package/commonjs/export/unpackXlsxFileNode.js.map +1 -1
  33. package/commonjs/export/unpackXlsxFileNode.test.js.map +1 -0
  34. package/commonjs/export/unpackXlsxFileUniversal.js +14 -3
  35. package/commonjs/export/unpackXlsxFileUniversal.js.map +1 -1
  36. package/commonjs/export/unpackXlsxFileUniversal.test.js.map +1 -0
  37. package/commonjs/parseSheetData/parseSheetData.js +9 -6
  38. package/commonjs/parseSheetData/parseSheetData.js.map +1 -1
  39. package/commonjs/utility/convertValuesFromUint8ArraysToStrings.js +100 -0
  40. package/commonjs/utility/convertValuesFromUint8ArraysToStrings.js.map +1 -0
  41. package/commonjs/xlsx/InvalidSpreadsheetError.js +55 -0
  42. package/commonjs/xlsx/InvalidSpreadsheetError.js.map +1 -0
  43. package/commonjs/xlsx/SheetNotFoundError.js +55 -0
  44. package/commonjs/xlsx/SheetNotFoundError.js.map +1 -0
  45. package/commonjs/xlsx/file/InputValidationStream.js +101 -0
  46. package/commonjs/xlsx/file/InputValidationStream.js.map +1 -0
  47. package/commonjs/xlsx/file/InputValidationStream.test.js.map +1 -0
  48. package/commonjs/xlsx/file/InvalidInputError.js +105 -0
  49. package/commonjs/xlsx/file/InvalidInputError.js.map +1 -0
  50. package/commonjs/xlsx/file/createFileTypeDetector.js +70 -0
  51. package/commonjs/xlsx/file/createFileTypeDetector.js.map +1 -0
  52. package/commonjs/xlsx/file/createFileTypeDetector.test.js.map +1 -0
  53. package/commonjs/xlsx/file/validateLeadingBytes.js +47 -0
  54. package/commonjs/xlsx/file/validateLeadingBytes.js.map +1 -0
  55. package/commonjs/xlsx/file/validateLeadingBytes.test.js.map +1 -0
  56. package/commonjs/xlsx/isDateFormat.js +32 -27
  57. package/commonjs/xlsx/isDateFormat.js.map +1 -1
  58. package/commonjs/xlsx/isDateFormatStyle.js +19 -21
  59. package/commonjs/xlsx/isDateFormatStyle.js.map +1 -1
  60. package/commonjs/xlsx/parseCell.js +252 -115
  61. package/commonjs/xlsx/parseCell.js.map +1 -1
  62. package/commonjs/xlsx/parseCellAddress.js +22 -18
  63. package/commonjs/xlsx/parseCellAddress.js.map +1 -1
  64. package/commonjs/xlsx/parseExcelDate.js +4 -124
  65. package/commonjs/xlsx/parseExcelDate.js.map +1 -1
  66. package/commonjs/xlsx/parseExcelDate.test.js.map +1 -1
  67. package/commonjs/xlsx/parseExcelTimestamp.js +139 -0
  68. package/commonjs/xlsx/parseExcelTimestamp.js.map +1 -0
  69. package/commonjs/xlsx/parseFilePaths.js +48 -47
  70. package/commonjs/xlsx/parseFilePaths.js.map +1 -1
  71. package/commonjs/xlsx/parseSharedStrings.js +91 -36
  72. package/commonjs/xlsx/parseSharedStrings.js.map +1 -1
  73. package/commonjs/xlsx/parseSheet.js +527 -69
  74. package/commonjs/xlsx/parseSheet.js.map +1 -1
  75. package/commonjs/xlsx/parseSpreadsheetContents.js +154 -83
  76. package/commonjs/xlsx/parseSpreadsheetContents.js.map +1 -1
  77. package/commonjs/xlsx/parseSpreadsheetInfo.js +60 -59
  78. package/commonjs/xlsx/parseSpreadsheetInfo.js.map +1 -1
  79. package/commonjs/xlsx/parseStyles.js +75 -75
  80. package/commonjs/xlsx/parseStyles.js.map +1 -1
  81. package/commonjs/xml/parseXml.js +112 -0
  82. package/commonjs/xml/parseXml.js.map +1 -0
  83. package/commonjs/xml/parseXml.test.js.map +1 -0
  84. package/commonjs/xml/parseXmlStream.js.map +1 -1
  85. package/commonjs/xml/parseXmlStream.sax.js +69 -28
  86. package/commonjs/xml/parseXmlStream.sax.js.map +1 -1
  87. package/commonjs/xml/parseXmlStream.saxen.js +66 -40
  88. package/commonjs/xml/parseXmlStream.saxen.js.map +1 -1
  89. package/commonjs/zip/UnzipError.js +55 -0
  90. package/commonjs/zip/UnzipError.js.map +1 -0
  91. package/commonjs/zip/unzipFromArrayBuffer.js +21 -1
  92. package/commonjs/zip/unzipFromArrayBuffer.js.map +1 -1
  93. package/commonjs/zip/unzipFromArrayBuffer.test.js.map +1 -0
  94. package/commonjs/zip/unzipFromStream.fflate.js +43 -35
  95. package/commonjs/zip/unzipFromStream.fflate.js.map +1 -1
  96. package/commonjs/zip/unzipFromStream.js +188 -6
  97. package/commonjs/zip/unzipFromStream.js.map +1 -1
  98. package/commonjs/zip/unzipFromStream.unzipper.js +19 -34
  99. package/commonjs/zip/unzipFromStream.unzipper.js.map +1 -1
  100. package/modules/export/convertInputToNodeStream.js +0 -12
  101. package/modules/export/convertInputToNodeStream.js.map +1 -1
  102. package/modules/export/parseSheet.js +13 -6
  103. package/modules/export/parseSheet.js.map +1 -1
  104. package/modules/export/readSheetBrowser.js +3 -5
  105. package/modules/export/readSheetBrowser.js.map +1 -1
  106. package/modules/export/readSheetNode.js +3 -5
  107. package/modules/export/readSheetNode.js.map +1 -1
  108. package/modules/export/readSheetUniversal.js +2 -2
  109. package/modules/export/readSheetUniversal.js.map +1 -1
  110. package/modules/export/readSheetWebWorker.js +7 -8
  111. package/modules/export/readSheetWebWorker.js.map +1 -1
  112. package/modules/export/readXlsxFileBrowser.js +3 -5
  113. package/modules/export/readXlsxFileBrowser.js.map +1 -1
  114. package/modules/export/readXlsxFileNode.js +3 -5
  115. package/modules/export/readXlsxFileNode.js.map +1 -1
  116. package/modules/export/readXlsxFileUniversal.js +2 -2
  117. package/modules/export/readXlsxFileUniversal.js.map +1 -1
  118. package/modules/export/readXlsxFileWebWorker.js +7 -8
  119. package/modules/export/readXlsxFileWebWorker.js.map +1 -1
  120. package/modules/export/unpackXlsxFileBrowser.js +15 -4
  121. package/modules/export/unpackXlsxFileBrowser.js.map +1 -1
  122. package/modules/export/unpackXlsxFileBrowser.test.js.map +1 -0
  123. package/modules/export/unpackXlsxFileNode.js +32 -5
  124. package/modules/export/unpackXlsxFileNode.js.map +1 -1
  125. package/modules/export/unpackXlsxFileNode.test.js.map +1 -0
  126. package/modules/export/unpackXlsxFileUniversal.js +14 -3
  127. package/modules/export/unpackXlsxFileUniversal.js.map +1 -1
  128. package/modules/export/unpackXlsxFileUniversal.test.js.map +1 -0
  129. package/modules/parseSheetData/parseSheetData.js +9 -6
  130. package/modules/parseSheetData/parseSheetData.js.map +1 -1
  131. package/modules/utility/convertValuesFromUint8ArraysToStrings.js +92 -0
  132. package/modules/utility/convertValuesFromUint8ArraysToStrings.js.map +1 -0
  133. package/modules/xlsx/InvalidSpreadsheetError.js +50 -0
  134. package/modules/xlsx/InvalidSpreadsheetError.js.map +1 -0
  135. package/modules/xlsx/SheetNotFoundError.js +50 -0
  136. package/modules/xlsx/SheetNotFoundError.js.map +1 -0
  137. package/modules/xlsx/file/InputValidationStream.js +96 -0
  138. package/modules/xlsx/file/InputValidationStream.js.map +1 -0
  139. package/modules/xlsx/file/InputValidationStream.test.js.map +1 -0
  140. package/modules/xlsx/file/InvalidInputError.js +100 -0
  141. package/modules/xlsx/file/InvalidInputError.js.map +1 -0
  142. package/modules/xlsx/file/createFileTypeDetector.js +63 -0
  143. package/modules/xlsx/file/createFileTypeDetector.js.map +1 -0
  144. package/modules/xlsx/file/createFileTypeDetector.test.js.map +1 -0
  145. package/modules/xlsx/file/validateLeadingBytes.js +37 -0
  146. package/modules/xlsx/file/validateLeadingBytes.js.map +1 -0
  147. package/modules/xlsx/file/validateLeadingBytes.test.js.map +1 -0
  148. package/modules/xlsx/isDateFormat.js +31 -27
  149. package/modules/xlsx/isDateFormat.js.map +1 -1
  150. package/modules/xlsx/isDateFormatStyle.js +18 -21
  151. package/modules/xlsx/isDateFormatStyle.js.map +1 -1
  152. package/modules/xlsx/parseCell.js +251 -111
  153. package/modules/xlsx/parseCell.js.map +1 -1
  154. package/modules/xlsx/parseCellAddress.js +21 -18
  155. package/modules/xlsx/parseCellAddress.js.map +1 -1
  156. package/modules/xlsx/parseExcelDate.js +4 -124
  157. package/modules/xlsx/parseExcelDate.js.map +1 -1
  158. package/modules/xlsx/parseExcelDate.test.js.map +1 -1
  159. package/modules/xlsx/parseExcelTimestamp.js +133 -0
  160. package/modules/xlsx/parseExcelTimestamp.js.map +1 -0
  161. package/modules/xlsx/parseFilePaths.js +48 -47
  162. package/modules/xlsx/parseFilePaths.js.map +1 -1
  163. package/modules/xlsx/parseSharedStrings.js +91 -37
  164. package/modules/xlsx/parseSharedStrings.js.map +1 -1
  165. package/modules/xlsx/parseSheet.js +526 -68
  166. package/modules/xlsx/parseSheet.js.map +1 -1
  167. package/modules/xlsx/parseSpreadsheetContents.js +153 -81
  168. package/modules/xlsx/parseSpreadsheetContents.js.map +1 -1
  169. package/modules/xlsx/parseSpreadsheetInfo.js +60 -59
  170. package/modules/xlsx/parseSpreadsheetInfo.js.map +1 -1
  171. package/modules/xlsx/parseStyles.js +75 -75
  172. package/modules/xlsx/parseStyles.js.map +1 -1
  173. package/modules/xml/parseXml.js +106 -0
  174. package/modules/xml/parseXml.js.map +1 -0
  175. package/modules/xml/parseXml.test.js.map +1 -0
  176. package/modules/xml/parseXmlStream.js +2 -0
  177. package/modules/xml/parseXmlStream.js.map +1 -1
  178. package/modules/xml/parseXmlStream.sax.js +69 -28
  179. package/modules/xml/parseXmlStream.sax.js.map +1 -1
  180. package/modules/xml/parseXmlStream.saxen.js +67 -40
  181. package/modules/xml/parseXmlStream.saxen.js.map +1 -1
  182. package/modules/zip/UnzipError.js +49 -0
  183. package/modules/zip/UnzipError.js.map +1 -0
  184. package/modules/zip/unzipFromArrayBuffer.js +21 -1
  185. package/modules/zip/unzipFromArrayBuffer.js.map +1 -1
  186. package/modules/zip/unzipFromArrayBuffer.test.js.map +1 -0
  187. package/modules/zip/unzipFromStream.fflate.js +41 -35
  188. package/modules/zip/unzipFromStream.fflate.js.map +1 -1
  189. package/modules/zip/unzipFromStream.js +124 -1
  190. package/modules/zip/unzipFromStream.js.map +1 -1
  191. package/modules/zip/unzipFromStream.unzipper.js +19 -35
  192. package/modules/zip/unzipFromStream.unzipper.js.map +1 -1
  193. package/node/index.cjs +3 -0
  194. package/node/index.d.ts +16 -0
  195. package/node/index.js +3 -0
  196. package/package.json +5 -3
  197. package/types/InvalidInputError.d.ts +16 -0
  198. package/types/InvalidSpreadsheetError.d.ts +4 -0
  199. package/types/SheetNotFoundError.d.ts +4 -0
  200. package/universal/index.cjs +3 -0
  201. package/universal/index.d.ts +16 -0
  202. package/universal/index.js +3 -0
  203. package/web-worker/index.cjs +3 -0
  204. package/web-worker/index.d.ts +16 -0
  205. package/web-worker/index.js +3 -0
  206. package/commonjs/export/convertValuesFromUint8ArraysToStrings.js +0 -23
  207. package/commonjs/export/convertValuesFromUint8ArraysToStrings.js.map +0 -1
  208. package/commonjs/xlsx/convertCellsToData2dArray.js +0 -84
  209. package/commonjs/xlsx/convertCellsToData2dArray.js.map +0 -1
  210. package/commonjs/xlsx/dropEmptyColumns.js +0 -40
  211. package/commonjs/xlsx/dropEmptyColumns.js.map +0 -1
  212. package/commonjs/xlsx/dropEmptyColumns.test.js.map +0 -1
  213. package/commonjs/xlsx/dropEmptyRows.js +0 -43
  214. package/commonjs/xlsx/dropEmptyRows.js.map +0 -1
  215. package/commonjs/xlsx/dropEmptyRows.test.js.map +0 -1
  216. package/commonjs/xlsx/parseCellValue.js +0 -189
  217. package/commonjs/xlsx/parseCellValue.js.map +0 -1
  218. package/commonjs/xlsx/parseNumber.js +0 -20
  219. package/commonjs/xlsx/parseNumber.js.map +0 -1
  220. package/commonjs/xlsx/parseSharedString.js +0 -64
  221. package/commonjs/xlsx/parseSharedString.js.map +0 -1
  222. package/commonjs/xlsx/parseSheetData.js +0 -68
  223. package/commonjs/xlsx/parseSheetData.js.map +0 -1
  224. package/commonjs/xlsx/reconstructSheetDimensionsFromSheetCells.js +0 -29
  225. package/commonjs/xlsx/reconstructSheetDimensionsFromSheetCells.js.map +0 -1
  226. package/commonjs/xml/parseXmlStream.saxen.code.js +0 -953
  227. package/commonjs/xml/parseXmlStream.saxen.code.js.map +0 -1
  228. package/commonjs/xml/parseXmlStream.test.js.map +0 -1
  229. package/modules/export/convertValuesFromUint8ArraysToStrings.js +0 -17
  230. package/modules/export/convertValuesFromUint8ArraysToStrings.js.map +0 -1
  231. package/modules/xlsx/convertCellsToData2dArray.js +0 -77
  232. package/modules/xlsx/convertCellsToData2dArray.js.map +0 -1
  233. package/modules/xlsx/dropEmptyColumns.js +0 -34
  234. package/modules/xlsx/dropEmptyColumns.js.map +0 -1
  235. package/modules/xlsx/dropEmptyColumns.test.js.map +0 -1
  236. package/modules/xlsx/dropEmptyRows.js +0 -37
  237. package/modules/xlsx/dropEmptyRows.js.map +0 -1
  238. package/modules/xlsx/dropEmptyRows.test.js.map +0 -1
  239. package/modules/xlsx/parseCellValue.js +0 -183
  240. package/modules/xlsx/parseCellValue.js.map +0 -1
  241. package/modules/xlsx/parseNumber.js +0 -14
  242. package/modules/xlsx/parseNumber.js.map +0 -1
  243. package/modules/xlsx/parseSharedString.js +0 -55
  244. package/modules/xlsx/parseSharedString.js.map +0 -1
  245. package/modules/xlsx/parseSheetData.js +0 -55
  246. package/modules/xlsx/parseSheetData.js.map +0 -1
  247. package/modules/xlsx/reconstructSheetDimensionsFromSheetCells.js +0 -23
  248. package/modules/xlsx/reconstructSheetDimensionsFromSheetCells.js.map +0 -1
  249. package/modules/xml/parseXmlStream.saxen.code.js +0 -947
  250. package/modules/xml/parseXmlStream.saxen.code.js.map +0 -1
  251. package/modules/xml/parseXmlStream.test.js.map +0 -1
@@ -1,3 +1,5 @@
1
+ import parseExcelTimestamp from './parseExcelTimestamp.js';
2
+
1
3
  // Parses an Excel Date (represented by a "serial" floating-point number)
2
4
  // into a javascript `Date` in UTC+0 timezone (with time is set to 00:00).
3
5
  //
@@ -5,129 +7,7 @@
5
7
  // "If you need to calculate dates in your spreadsheets,
6
8
  // Excel uses its own unique system, which it calls Serial Numbers".
7
9
  //
8
- export default function parseExcelDate(excelSerialDate, options) {
9
- // Windows operating system uses floating-point numbers to represent dates,
10
- // where the number represents the count of days elapsed since January 0th, 1900.
11
- //
12
- // This also means that there're 2 aspects associated with this choice:
13
- //
14
- // * January 1st, 1900, 00:00 is represented by `1` rather than `0`, which looks a bit weird.
15
- // * 1900 is a special year because it's a "one in a 100 years" occasion when it's not a leap year.
16
- //
17
- // To work around those two aspects, Mac OS chose another baseline — January 1st, 1900.
18
- // Although, that only postponed the second issue because 2100 is going to be the next "speciaL" year
19
- // which is not going to be a "leap" one.
20
- //
21
- // Older versions of Excel on Mac OS used year 1904 as the default baseline for numeric dates.
22
- // Since 2011, Microsoft Excel on Mac OS uses year 1900 as the default baseline for cross-platform consistency.
23
- // https://support.microsoft.com/en-us/office/date-systems-in-excel-e7fe7167-48a9-4b96-bb53-5612a800b487
24
- //
25
- // So the 1904 baseline is now deprecated, although still available to be configured manually.
26
- // So it still might be encountered in Excel files created on MacOS.
27
- // In that case, the Excel file contains a special flag — `<workbook><workbookPr date1904="1"/>...` —
28
- // that tells the application which baseline is being used for numeric date timestamps.
29
- //
30
- if (options && options.epoch1904) {
31
- // Convert the numeric date timestamp from 1904 baseline to 1900 baseline.
32
- excelSerialDate += (1904 - 1900) * DAYS_IN_YEAR + JANUARY_0TH_1900_DAY + ERRONEOUS_FEBRUARY_29_1990_DAY;
33
- }
34
- var daysBeforeUnixEpoch = JANUARY_0TH_1900_DAY + ERRONEOUS_FEBRUARY_29_1990_DAY + (1970 - 1900) * DAYS_IN_YEAR + NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970;
35
- return new Date(Math.floor((excelSerialDate - daysBeforeUnixEpoch) * DAY));
10
+ export default function parseExcelDate(excelSerialDate, epoch1904) {
11
+ return new Date(parseExcelTimestamp(excelSerialDate, epoch1904));
36
12
  }
37
-
38
- // "Excel serial date" is just a (fractional) count of days passed since `00/01/1900`.
39
- //
40
- // In contrast, "Unix timestamps" use `01/01/1970` as the baseline for numeric dates.
41
- //
42
- // In order to convert one into another, it should calculate the count of days elapsed
43
- // since `00/01/1900` (Excel epoch) till `01/01/1970` (Unix epoch), or the count of days
44
- // between year `1900` and year `1970`, plus one day.
45
- //
46
- // It also should account for the number of "leap years" between year `1900` and year `1970`,
47
- // which is 17 of them. https://kalender-365.de/leap-years.php
48
- //
49
- // "One year has the length of 365 days, 5 hours, 48 minutes and 45 seconds.
50
- // These are 365.2421875 days. This is hard to calculate with, so for practical reasons
51
- // a normal year has been given 365 days and a leap year 366 days. In leap years,
52
- // February 29th is added as leap day, which doesn't exist in a normal year.
53
- // A leap year is every 4 years, but not every 100 years, then again every 400 years.
54
- // So the year 1900 wasn't a leap year, but 2000 was".
55
- //
56
- // And also, Excel has a historical bug when it incorrectly assumes year `1900` to be a leap year,
57
- // and, as a result, all Excel serial dates starting from March 1st, 1900 lag 1 day behind
58
- // and require an additional 1 day to be added to them in order to be converted to a proper timestamp.
59
- //
60
- // https://learn.microsoft.com/en-us/answers/questions/5249322/why-does-microsoft-excel-considers-29-02-1900-to-b?forum=msoffice-all&referrer=answers#:~:text=This%20made%20it%20easier%20for,other%20programs%20that%20use%20dates.
61
- //
62
- // "When Lotus 1-2-3 was first released, the program assumed that the year 1900 was a leap year,
63
- // even though it actually was not a leap year. This made it easier for the program to handle
64
- // leap years and caused no harm to almost all date calculations in Lotus 1-2-3.
65
- //
66
- // When Microsoft Multiplan and Microsoft Excel were released, they also assumed that 1900
67
- // was a leap year. This assumption allowed Microsoft Multiplan and Microsoft Excel to use
68
- // the same serial date system used by Lotus 1-2-3 and provide greater compatibility with Lotus 1-2-3.
69
- // Treating 1900 as a leap year also made it easier for users to move worksheets from one program
70
- // to the other".
71
- //
72
- // So the historical bug is basically that Excel thinks that February 29th, 1900 existed
73
- // while in reality it didn't. That's why it's actually 1 day off for any date after that one.
74
- //
75
- var NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970 = 17;
76
- var JANUARY_0TH_1900_DAY = 1;
77
- var ERRONEOUS_FEBRUARY_29_1990_DAY = 1;
78
-
79
- // An approximate count of seconds in a day is:
80
- // 24 hours * 60 minutes in an hour * 60 seconds in a minute
81
- //
82
- // It is approximate because a minute could be longer than 60 seconds, due to "leap seconds".
83
- //
84
- // Still, javascript `Date`, and UNIX time in general, intentionally
85
- // drop the concept of "leap seconds" in order to make things simpler.
86
- // So this approximation is valid and doesn't result in any bugs.
87
- // https://stackoverflow.com/questions/53019726/where-are-the-leap-seconds-in-javascript
88
- //
89
- // "The JavaScript Date object specifically adheres to the concept of Unix Time
90
- // (albeit with higher precision). This is part of the POSIX specification,
91
- // and thus is sometimes called "POSIX Time". It does not count leap seconds,
92
- // but rather assumes every day had exactly 86,400 seconds. You can read about
93
- // this in section 20.3.1.1 of the current ECMAScript specification, which states:
94
- //
95
- // "Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC.
96
- // In time values leap seconds are ignored. It is assumed that there are exactly
97
- // 86,400,000 milliseconds per day."
98
- //
99
- // The reason is that the unpredictable nature of leap seconds makes them very
100
- // difficult to work with in APIs. One can't generally pass timestamps around
101
- // that need leap seconds tables to be interpreted correctly, and expect that
102
- // one system will interpret them the same as another. For example, a timestamp
103
- // `1483228826` that accounts for "leap seconds" should've been interpreted as
104
- // "2017-01-01T00:00:00Z", but if the receiver doesn't account for "leap seconds",
105
- // they would interpret it as "2017-01-01T00:00:26Z" (e.g. POSIX-based systems like Linux),
106
- // So "leap seconds" aren't really portable.
107
- // Even on systems that have full frequently-updated "leap second" tables,
108
- // there's no telling what adjustments those tables will contain in the future
109
- // (i.e. beyond the 6-month IERS announcement period) because "leap seconds" can't be
110
- // determined by a fixed mathematical formula or something like that. Instead,
111
- // scientists introduce them as needed based on the observed Earth's rotation around the sun.
112
- //
113
- // "Because the Earth's rotational speed varies in response to climatic and geological events,
114
- // UTC leap seconds are irregularly spaced and not precisely predictable. The decision to insert
115
- // a leap second is made by the International Earth Rotation and Reference Systems Service (IERS),
116
- // typically about six months in advance, to ensure that the difference between UTC and UT1
117
- // does not exceed ±0.9 seconds."
118
- //
119
- // One example is year `1900` which is "every fourth year" but it still is not a "leap year".
120
- //
121
- // To reiterate: to support leap seconds in a programming language, the implementation
122
- // must go out of its way to do so, and must make tradeoffs that are not always acceptable.
123
- // Though there are exceptions, the general position is to not support them - not because
124
- // of any subversion or active countermeasures, but because supporting them properly is much,
125
- // much harder.
126
- //
127
- // https://en.wikipedia.org/wiki/Unix_time#Leap_seconds
128
- // https://en.wikipedia.org/wiki/Leap_year
129
- // https://en.wikipedia.org/wiki/Leap_second
130
- //
131
- var DAY = 24 * 60 * 60 * 1000;
132
- var DAYS_IN_YEAR = 365;
133
13
  //# sourceMappingURL=parseExcelDate.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parseExcelDate.js","names":["parseExcelDate","excelSerialDate","options","epoch1904","DAYS_IN_YEAR","JANUARY_0TH_1900_DAY","ERRONEOUS_FEBRUARY_29_1990_DAY","daysBeforeUnixEpoch","NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970","Date","Math","floor","DAY"],"sources":["../../source/xlsx/parseExcelDate.js"],"sourcesContent":["// Parses an Excel Date (represented by a \"serial\" floating-point number)\r\n// into a javascript `Date` in UTC+0 timezone (with time is set to 00:00).\r\n//\r\n// https://www.pcworld.com/article/3063622/software/mastering-excel-date-time-serial-numbers-networkdays-datevalue-and-more.html\r\n// \"If you need to calculate dates in your spreadsheets,\r\n// Excel uses its own unique system, which it calls Serial Numbers\".\r\n//\r\nexport default function parseExcelDate(excelSerialDate, options) {\r\n // Windows operating system uses floating-point numbers to represent dates,\r\n // where the number represents the count of days elapsed since January 0th, 1900.\r\n //\r\n // This also means that there're 2 aspects associated with this choice:\r\n //\r\n // * January 1st, 1900, 00:00 is represented by `1` rather than `0`, which looks a bit weird.\r\n // * 1900 is a special year because it's a \"one in a 100 years\" occasion when it's not a leap year.\r\n //\r\n // To work around those two aspects, Mac OS chose another baseline — January 1st, 1900.\r\n // Although, that only postponed the second issue because 2100 is going to be the next \"speciaL\" year\r\n // which is not going to be a \"leap\" one.\r\n //\r\n // Older versions of Excel on Mac OS used year 1904 as the default baseline for numeric dates.\r\n // Since 2011, Microsoft Excel on Mac OS uses year 1900 as the default baseline for cross-platform consistency.\r\n // https://support.microsoft.com/en-us/office/date-systems-in-excel-e7fe7167-48a9-4b96-bb53-5612a800b487\r\n //\r\n // So the 1904 baseline is now deprecated, although still available to be configured manually.\r\n // So it still might be encountered in Excel files created on MacOS.\r\n // In that case, the Excel file contains a special flag — `<workbook><workbookPr date1904=\"1\"/>...` —\r\n // that tells the application which baseline is being used for numeric date timestamps.\r\n //\r\n if (options && options.epoch1904) {\r\n // Convert the numeric date timestamp from 1904 baseline to 1900 baseline.\r\n excelSerialDate += (1904 - 1900) * DAYS_IN_YEAR + JANUARY_0TH_1900_DAY + ERRONEOUS_FEBRUARY_29_1990_DAY\r\n }\r\n\r\n const daysBeforeUnixEpoch = JANUARY_0TH_1900_DAY + ERRONEOUS_FEBRUARY_29_1990_DAY + (1970 - 1900) * DAYS_IN_YEAR + NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970\r\n\r\n return new Date(Math.floor((excelSerialDate - daysBeforeUnixEpoch) * DAY))\r\n}\r\n\r\n// \"Excel serial date\" is just a (fractional) count of days passed since `00/01/1900`.\r\n//\r\n// In contrast, \"Unix timestamps\" use `01/01/1970` as the baseline for numeric dates.\r\n//\r\n// In order to convert one into another, it should calculate the count of days elapsed\r\n// since `00/01/1900` (Excel epoch) till `01/01/1970` (Unix epoch), or the count of days\r\n// between year `1900` and year `1970`, plus one day.\r\n//\r\n// It also should account for the number of \"leap years\" between year `1900` and year `1970`,\r\n// which is 17 of them. https://kalender-365.de/leap-years.php\r\n//\r\n// \"One year has the length of 365 days, 5 hours, 48 minutes and 45 seconds.\r\n// These are 365.2421875 days. This is hard to calculate with, so for practical reasons\r\n// a normal year has been given 365 days and a leap year 366 days. In leap years,\r\n// February 29th is added as leap day, which doesn't exist in a normal year.\r\n// A leap year is every 4 years, but not every 100 years, then again every 400 years.\r\n// So the year 1900 wasn't a leap year, but 2000 was\".\r\n//\r\n// And also, Excel has a historical bug when it incorrectly assumes year `1900` to be a leap year,\r\n// and, as a result, all Excel serial dates starting from March 1st, 1900 lag 1 day behind\r\n// and require an additional 1 day to be added to them in order to be converted to a proper timestamp.\r\n//\r\n// https://learn.microsoft.com/en-us/answers/questions/5249322/why-does-microsoft-excel-considers-29-02-1900-to-b?forum=msoffice-all&referrer=answers#:~:text=This%20made%20it%20easier%20for,other%20programs%20that%20use%20dates.\r\n//\r\n// \"When Lotus 1-2-3 was first released, the program assumed that the year 1900 was a leap year,\r\n// even though it actually was not a leap year. This made it easier for the program to handle\r\n// leap years and caused no harm to almost all date calculations in Lotus 1-2-3.\r\n//\r\n// When Microsoft Multiplan and Microsoft Excel were released, they also assumed that 1900\r\n// was a leap year. This assumption allowed Microsoft Multiplan and Microsoft Excel to use\r\n// the same serial date system used by Lotus 1-2-3 and provide greater compatibility with Lotus 1-2-3.\r\n// Treating 1900 as a leap year also made it easier for users to move worksheets from one program\r\n// to the other\".\r\n//\r\n// So the historical bug is basically that Excel thinks that February 29th, 1900 existed\r\n// while in reality it didn't. That's why it's actually 1 day off for any date after that one.\r\n//\r\nconst NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970 = 17\r\nconst JANUARY_0TH_1900_DAY = 1\r\nconst ERRONEOUS_FEBRUARY_29_1990_DAY = 1\r\n\r\n// An approximate count of seconds in a day is:\r\n// 24 hours * 60 minutes in an hour * 60 seconds in a minute\r\n//\r\n// It is approximate because a minute could be longer than 60 seconds, due to \"leap seconds\".\r\n//\r\n// Still, javascript `Date`, and UNIX time in general, intentionally\r\n// drop the concept of \"leap seconds\" in order to make things simpler.\r\n// So this approximation is valid and doesn't result in any bugs.\r\n// https://stackoverflow.com/questions/53019726/where-are-the-leap-seconds-in-javascript\r\n//\r\n// \"The JavaScript Date object specifically adheres to the concept of Unix Time\r\n// (albeit with higher precision). This is part of the POSIX specification,\r\n// and thus is sometimes called \"POSIX Time\". It does not count leap seconds,\r\n// but rather assumes every day had exactly 86,400 seconds. You can read about\r\n// this in section 20.3.1.1 of the current ECMAScript specification, which states:\r\n//\r\n// \"Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC.\r\n// In time values leap seconds are ignored. It is assumed that there are exactly\r\n// 86,400,000 milliseconds per day.\"\r\n//\r\n// The reason is that the unpredictable nature of leap seconds makes them very\r\n// difficult to work with in APIs. One can't generally pass timestamps around\r\n// that need leap seconds tables to be interpreted correctly, and expect that\r\n// one system will interpret them the same as another. For example, a timestamp\r\n// `1483228826` that accounts for \"leap seconds\" should've been interpreted as\r\n// \"2017-01-01T00:00:00Z\", but if the receiver doesn't account for \"leap seconds\",\r\n// they would interpret it as \"2017-01-01T00:00:26Z\" (e.g. POSIX-based systems like Linux),\r\n// So \"leap seconds\" aren't really portable.\r\n// Even on systems that have full frequently-updated \"leap second\" tables,\r\n// there's no telling what adjustments those tables will contain in the future\r\n// (i.e. beyond the 6-month IERS announcement period) because \"leap seconds\" can't be\r\n// determined by a fixed mathematical formula or something like that. Instead,\r\n// scientists introduce them as needed based on the observed Earth's rotation around the sun.\r\n//\r\n// \"Because the Earth's rotational speed varies in response to climatic and geological events,\r\n// UTC leap seconds are irregularly spaced and not precisely predictable. The decision to insert\r\n// a leap second is made by the International Earth Rotation and Reference Systems Service (IERS),\r\n// typically about six months in advance, to ensure that the difference between UTC and UT1\r\n// does not exceed ±0.9 seconds.\"\r\n//\r\n// One example is year `1900` which is \"every fourth year\" but it still is not a \"leap year\".\r\n//\r\n// To reiterate: to support leap seconds in a programming language, the implementation\r\n// must go out of its way to do so, and must make tradeoffs that are not always acceptable.\r\n// Though there are exceptions, the general position is to not support them - not because\r\n// of any subversion or active countermeasures, but because supporting them properly is much,\r\n// much harder.\r\n//\r\n// https://en.wikipedia.org/wiki/Unix_time#Leap_seconds\r\n// https://en.wikipedia.org/wiki/Leap_year\r\n// https://en.wikipedia.org/wiki/Leap_second\r\n//\r\nconst DAY = 24 * 60 * 60 * 1000\r\nconst DAYS_IN_YEAR = 365\r\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,SAASA,cAAcA,CAACC,eAAe,EAAEC,OAAO,EAAE;EAC/D;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,IAAIA,OAAO,IAAIA,OAAO,CAACC,SAAS,EAAE;IAChC;IACAF,eAAe,IAAI,CAAC,IAAI,GAAG,IAAI,IAAIG,YAAY,GAAGC,oBAAoB,GAAGC,8BAA8B;EACzG;EAEA,IAAMC,mBAAmB,GAAGF,oBAAoB,GAAGC,8BAA8B,GAAG,CAAC,IAAI,GAAG,IAAI,IAAIF,YAAY,GAAGI,0CAA0C;EAE7J,OAAO,IAAIC,IAAI,CAACC,IAAI,CAACC,KAAK,CAAC,CAACV,eAAe,GAAGM,mBAAmB,IAAIK,GAAG,CAAC,CAAC;AAC5E;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAMJ,0CAA0C,GAAG,EAAE;AACrD,IAAMH,oBAAoB,GAAG,CAAC;AAC9B,IAAMC,8BAA8B,GAAG,CAAC;;AAExC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,IAAMM,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;AAC/B,IAAMR,YAAY,GAAG,GAAG"}
1
+ {"version":3,"file":"parseExcelDate.js","names":["parseExcelTimestamp","parseExcelDate","excelSerialDate","epoch1904","Date"],"sources":["../../source/xlsx/parseExcelDate.js"],"sourcesContent":["import parseExcelTimestamp from './parseExcelTimestamp.js'\r\n\r\n// Parses an Excel Date (represented by a \"serial\" floating-point number)\r\n// into a javascript `Date` in UTC+0 timezone (with time is set to 00:00).\r\n//\r\n// https://www.pcworld.com/article/3063622/software/mastering-excel-date-time-serial-numbers-networkdays-datevalue-and-more.html\r\n// \"If you need to calculate dates in your spreadsheets,\r\n// Excel uses its own unique system, which it calls Serial Numbers\".\r\n//\r\nexport default function parseExcelDate(excelSerialDate, epoch1904) {\r\n return new Date(parseExcelTimestamp(excelSerialDate, epoch1904))\r\n}"],"mappings":"AAAA,OAAOA,mBAAmB,MAAM,0BAA0B;;AAE1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,SAASC,cAAcA,CAACC,eAAe,EAAEC,SAAS,EAAE;EACjE,OAAO,IAAIC,IAAI,CAACJ,mBAAmB,CAACE,eAAe,EAAEC,SAAS,CAAC,CAAC;AAClE"}
@@ -1 +1 @@
1
- {"version":3,"file":"parseExcelDate.test.js","names":["describe","it","expect","parseExcelDate","date","Date","UTC","getTime","to","equal","DAYS_BETWEEN_1900_EPOCH_AND_1904_EPOCH","epoch1904"],"sources":["../../source/xlsx/parseExcelDate.test.js"],"sourcesContent":["import { describe, it } from 'mocha'\r\nimport { expect } from 'chai'\r\n\r\nimport parseExcelDate from './parseExcelDate.js'\r\n\r\ndescribe('parseExcelDate', () => {\r\n\tit('should parse Excel \"serial\" dates', () => {\r\n\t\tconst date = new Date(Date.UTC(2018, 3 - 1, 24))\r\n // Excel stores dates as integers.\r\n // E.g. '24/03/2018' === 43183\r\n\t\texpect(parseExcelDate(43183).getTime()).to.equal(date.getTime())\r\n\t})\r\n\r\n\tit('should parse Excel \"serial\" dates (1904 baseline)', () => {\r\n\t\tconst date = new Date(Date.UTC(2018, 3 - 1, 24))\r\n // Excel stores dates as integers.\r\n // E.g. '24/03/2018' === 43183\r\n\t\tconst DAYS_BETWEEN_1900_EPOCH_AND_1904_EPOCH = 1462\r\n\t\texpect(parseExcelDate(43183 - DAYS_BETWEEN_1900_EPOCH_AND_1904_EPOCH, { epoch1904: true }).getTime()).to.equal(date.getTime())\r\n\t})\r\n})\r\n"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,EAAE,QAAQ,OAAO;AACpC,SAASC,MAAM,QAAQ,MAAM;AAE7B,OAAOC,cAAc,MAAM,qBAAqB;AAEhDH,QAAQ,CAAC,gBAAgB,EAAE,YAAM;EAChCC,EAAE,CAAC,mCAAmC,EAAE,YAAM;IAC7C,IAAMG,IAAI,GAAG,IAAIC,IAAI,CAACA,IAAI,CAACC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9C;IACA;IACFJ,MAAM,CAACC,cAAc,CAAC,KAAK,CAAC,CAACI,OAAO,CAAC,CAAC,CAAC,CAACC,EAAE,CAACC,KAAK,CAACL,IAAI,CAACG,OAAO,CAAC,CAAC,CAAC;EACjE,CAAC,CAAC;EAEFN,EAAE,CAAC,mDAAmD,EAAE,YAAM;IAC7D,IAAMG,IAAI,GAAG,IAAIC,IAAI,CAACA,IAAI,CAACC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9C;IACA;IACF,IAAMI,sCAAsC,GAAG,IAAI;IACnDR,MAAM,CAACC,cAAc,CAAC,KAAK,GAAGO,sCAAsC,EAAE;MAAEC,SAAS,EAAE;IAAK,CAAC,CAAC,CAACJ,OAAO,CAAC,CAAC,CAAC,CAACC,EAAE,CAACC,KAAK,CAACL,IAAI,CAACG,OAAO,CAAC,CAAC,CAAC;EAC/H,CAAC,CAAC;AACH,CAAC,CAAC"}
1
+ {"version":3,"file":"parseExcelDate.test.js","names":["describe","it","expect","parseExcelDate","date","Date","UTC","getTime","to","equal","DAYS_BETWEEN_1900_EPOCH_AND_1904_EPOCH"],"sources":["../../source/xlsx/parseExcelDate.test.js"],"sourcesContent":["import { describe, it } from 'mocha'\r\nimport { expect } from 'chai'\r\n\r\nimport parseExcelDate from './parseExcelDate.js'\r\n\r\ndescribe('parseExcelDate', () => {\r\n\tit('should parse Excel \"serial\" dates', () => {\r\n\t\tconst date = new Date(Date.UTC(2018, 3 - 1, 24))\r\n // Excel stores dates as integers.\r\n // E.g. '24/03/2018' === 43183\r\n\t\texpect(parseExcelDate(43183).getTime()).to.equal(date.getTime())\r\n\t})\r\n\r\n\tit('should parse Excel \"serial\" dates (1904 baseline)', () => {\r\n\t\tconst date = new Date(Date.UTC(2018, 3 - 1, 24))\r\n // Excel stores dates as integers.\r\n // E.g. '24/03/2018' === 43183\r\n\t\tconst DAYS_BETWEEN_1900_EPOCH_AND_1904_EPOCH = 1462\r\n\t\texpect(parseExcelDate(43183 - DAYS_BETWEEN_1900_EPOCH_AND_1904_EPOCH, true).getTime()).to.equal(date.getTime())\r\n\t})\r\n})\r\n"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,EAAE,QAAQ,OAAO;AACpC,SAASC,MAAM,QAAQ,MAAM;AAE7B,OAAOC,cAAc,MAAM,qBAAqB;AAEhDH,QAAQ,CAAC,gBAAgB,EAAE,YAAM;EAChCC,EAAE,CAAC,mCAAmC,EAAE,YAAM;IAC7C,IAAMG,IAAI,GAAG,IAAIC,IAAI,CAACA,IAAI,CAACC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9C;IACA;IACFJ,MAAM,CAACC,cAAc,CAAC,KAAK,CAAC,CAACI,OAAO,CAAC,CAAC,CAAC,CAACC,EAAE,CAACC,KAAK,CAACL,IAAI,CAACG,OAAO,CAAC,CAAC,CAAC;EACjE,CAAC,CAAC;EAEFN,EAAE,CAAC,mDAAmD,EAAE,YAAM;IAC7D,IAAMG,IAAI,GAAG,IAAIC,IAAI,CAACA,IAAI,CAACC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9C;IACA;IACF,IAAMI,sCAAsC,GAAG,IAAI;IACnDR,MAAM,CAACC,cAAc,CAAC,KAAK,GAAGO,sCAAsC,EAAE,IAAI,CAAC,CAACH,OAAO,CAAC,CAAC,CAAC,CAACC,EAAE,CAACC,KAAK,CAACL,IAAI,CAACG,OAAO,CAAC,CAAC,CAAC;EAChH,CAAC,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,133 @@
1
+ // Parses an Excel Date (represented by a "serial" floating-point number)
2
+ // into a javascript `Date` timestamp in UTC+0 timezone (with time is set to 00:00).
3
+ //
4
+ // https://www.pcworld.com/article/3063622/software/mastering-excel-date-time-serial-numbers-networkdays-datevalue-and-more.html
5
+ // "If you need to calculate dates in your spreadsheets,
6
+ // Excel uses its own unique system, which it calls Serial Numbers".
7
+ //
8
+ export default function parseExcelTimestamp(excelSerialDate, epoch1904) {
9
+ // "Excel serial date" is just a (fractional) count of days passed since `00/01/1900`.
10
+ //
11
+ // In contrast, "Unix timestamps" use `01/01/1970` as the baseline for numeric dates.
12
+ //
13
+ // In order to convert one into another, it should calculate the count of days elapsed
14
+ // since `00/01/1900` (Excel epoch) till `01/01/1970` (Unix epoch), or the count of days
15
+ // between year `1900` and year `1970`, plus one day.
16
+ //
17
+ // It also should account for the number of "leap years" between year `1900` and year `1970`,
18
+ // which is 17 of them. https://kalender-365.de/leap-years.php
19
+ //
20
+ // "One year has the length of 365 days, 5 hours, 48 minutes and 45 seconds.
21
+ // These are 365.2421875 days. This is hard to calculate with, so for practical reasons
22
+ // a normal year has been given 365 days and a leap year 366 days. In leap years,
23
+ // February 29th is added as leap day, which doesn't exist in a normal year.
24
+ // A leap year is every 4 years, but not every 100 years, then again every 400 years.
25
+ // So the year 1900 wasn't a leap year, but 2000 was".
26
+ //
27
+ // And also, Excel has a historical bug when it incorrectly assumes year `1900` to be a leap year,
28
+ // and, as a result, all Excel serial dates starting from March 1st, 1900 lag 1 day behind
29
+ // and require an additional 1 day to be added to them in order to be converted to a proper timestamp.
30
+ //
31
+ // https://learn.microsoft.com/en-us/answers/questions/5249322/why-does-microsoft-excel-considers-29-02-1900-to-b?forum=msoffice-all&referrer=answers#:~:text=This%20made%20it%20easier%20for,other%20programs%20that%20use%20dates.
32
+ //
33
+ // "When Lotus 1-2-3 was first released, the program assumed that the year 1900 was a leap year,
34
+ // even though it actually was not a leap year. This made it easier for the program to handle
35
+ // leap years and caused no harm to almost all date calculations in Lotus 1-2-3.
36
+ //
37
+ // When Microsoft Multiplan and Microsoft Excel were released, they also assumed that 1900
38
+ // was a leap year. This assumption allowed Microsoft Multiplan and Microsoft Excel to use
39
+ // the same serial date system used by Lotus 1-2-3 and provide greater compatibility with Lotus 1-2-3.
40
+ // Treating 1900 as a leap year also made it easier for users to move worksheets from one program
41
+ // to the other".
42
+ //
43
+ // So the historical bug is basically that Excel thinks that February 29th, 1900 existed
44
+ // while in reality it didn't. That's why it's actually 1 day off for any date after that one.
45
+ //
46
+ var NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970 = 17;
47
+ var JANUARY_0TH_1900_DAY = 1;
48
+ var ERRONEOUS_FEBRUARY_29_1990_DAY = 1;
49
+
50
+ // An approximate count of seconds in a day is:
51
+ // 24 hours * 60 minutes in an hour * 60 seconds in a minute
52
+ //
53
+ // It is approximate because a minute could be longer than 60 seconds, due to "leap seconds".
54
+ //
55
+ // Still, javascript `Date`, and UNIX time in general, intentionally
56
+ // drop the concept of "leap seconds" in order to make things simpler.
57
+ // So this approximation is valid and doesn't result in any bugs.
58
+ // https://stackoverflow.com/questions/53019726/where-are-the-leap-seconds-in-javascript
59
+ //
60
+ // "The JavaScript Date object specifically adheres to the concept of Unix Time
61
+ // (albeit with higher precision). This is part of the POSIX specification,
62
+ // and thus is sometimes called "POSIX Time". It does not count leap seconds,
63
+ // but rather assumes every day had exactly 86,400 seconds. You can read about
64
+ // this in section 20.3.1.1 of the current ECMAScript specification, which states:
65
+ //
66
+ // "Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC.
67
+ // In time values leap seconds are ignored. It is assumed that there are exactly
68
+ // 86,400,000 milliseconds per day."
69
+ //
70
+ // The reason is that the unpredictable nature of leap seconds makes them very
71
+ // difficult to work with in APIs. One can't generally pass timestamps around
72
+ // that need leap seconds tables to be interpreted correctly, and expect that
73
+ // one system will interpret them the same as another. For example, a timestamp
74
+ // `1483228826` that accounts for "leap seconds" should've been interpreted as
75
+ // "2017-01-01T00:00:00Z", but if the receiver doesn't account for "leap seconds",
76
+ // they would interpret it as "2017-01-01T00:00:26Z" (e.g. POSIX-based systems like Linux),
77
+ // So "leap seconds" aren't really portable.
78
+ // Even on systems that have full frequently-updated "leap second" tables,
79
+ // there's no telling what adjustments those tables will contain in the future
80
+ // (i.e. beyond the 6-month IERS announcement period) because "leap seconds" can't be
81
+ // determined by a fixed mathematical formula or something like that. Instead,
82
+ // scientists introduce them as needed based on the observed Earth's rotation around the sun.
83
+ //
84
+ // "Because the Earth's rotational speed varies in response to climatic and geological events,
85
+ // UTC leap seconds are irregularly spaced and not precisely predictable. The decision to insert
86
+ // a leap second is made by the International Earth Rotation and Reference Systems Service (IERS),
87
+ // typically about six months in advance, to ensure that the difference between UTC and UT1
88
+ // does not exceed ±0.9 seconds."
89
+ //
90
+ // One example is year `1900` which is "every fourth year" but it still is not a "leap year".
91
+ //
92
+ // To reiterate: to support leap seconds in a programming language, the implementation
93
+ // must go out of its way to do so, and must make tradeoffs that are not always acceptable.
94
+ // Though there are exceptions, the general position is to not support them - not because
95
+ // of any subversion or active countermeasures, but because supporting them properly is much,
96
+ // much harder.
97
+ //
98
+ // https://en.wikipedia.org/wiki/Unix_time#Leap_seconds
99
+ // https://en.wikipedia.org/wiki/Leap_year
100
+ // https://en.wikipedia.org/wiki/Leap_second
101
+ //
102
+ var DAY = 24 * 60 * 60 * 1000;
103
+ var DAYS_IN_YEAR = 365;
104
+
105
+ // Windows operating system uses floating-point numbers to represent dates,
106
+ // where the number represents the count of days elapsed since January 0th, 1900.
107
+ //
108
+ // This also means that there're 2 aspects associated with this choice:
109
+ //
110
+ // * January 1st, 1900, 00:00 is represented by `1` rather than `0`, which looks a bit weird.
111
+ // * 1900 is a special year because it's a "one in a 100 years" occasion when it's not a leap year.
112
+ //
113
+ // To work around those two aspects, Mac OS chose another baseline — January 1st, 1900.
114
+ // Although, that only postponed the second issue because 2100 is going to be the next "speciaL" year
115
+ // which is not going to be a "leap" one.
116
+ //
117
+ // Older versions of Excel on Mac OS used year 1904 as the default baseline for numeric dates.
118
+ // Since 2011, Microsoft Excel on Mac OS uses year 1900 as the default baseline for cross-platform consistency.
119
+ // https://support.microsoft.com/en-us/office/date-systems-in-excel-e7fe7167-48a9-4b96-bb53-5612a800b487
120
+ //
121
+ // So the 1904 baseline is now deprecated, although still available to be configured manually.
122
+ // So it still might be encountered in Excel files created on MacOS.
123
+ // In that case, the Excel file contains a special flag — `<workbook><workbookPr date1904="1"/>...` —
124
+ // that tells the application which baseline is being used for numeric date timestamps.
125
+ //
126
+ if (epoch1904) {
127
+ // Convert the numeric date timestamp from 1904 baseline to 1900 baseline.
128
+ excelSerialDate += (1904 - 1900) * DAYS_IN_YEAR + JANUARY_0TH_1900_DAY + ERRONEOUS_FEBRUARY_29_1990_DAY;
129
+ }
130
+ var daysBeforeUnixEpoch = JANUARY_0TH_1900_DAY + ERRONEOUS_FEBRUARY_29_1990_DAY + (1970 - 1900) * DAYS_IN_YEAR + NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970;
131
+ return Math.floor((excelSerialDate - daysBeforeUnixEpoch) * DAY);
132
+ }
133
+ //# sourceMappingURL=parseExcelTimestamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseExcelTimestamp.js","names":["parseExcelTimestamp","excelSerialDate","epoch1904","NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970","JANUARY_0TH_1900_DAY","ERRONEOUS_FEBRUARY_29_1990_DAY","DAY","DAYS_IN_YEAR","daysBeforeUnixEpoch","Math","floor"],"sources":["../../source/xlsx/parseExcelTimestamp.js"],"sourcesContent":["// Parses an Excel Date (represented by a \"serial\" floating-point number)\r\n// into a javascript `Date` timestamp in UTC+0 timezone (with time is set to 00:00).\r\n//\r\n// https://www.pcworld.com/article/3063622/software/mastering-excel-date-time-serial-numbers-networkdays-datevalue-and-more.html\r\n// \"If you need to calculate dates in your spreadsheets,\r\n// Excel uses its own unique system, which it calls Serial Numbers\".\r\n//\r\nexport default function parseExcelTimestamp(excelSerialDate, epoch1904) {\r\n // \"Excel serial date\" is just a (fractional) count of days passed since `00/01/1900`.\r\n //\r\n // In contrast, \"Unix timestamps\" use `01/01/1970` as the baseline for numeric dates.\r\n //\r\n // In order to convert one into another, it should calculate the count of days elapsed\r\n // since `00/01/1900` (Excel epoch) till `01/01/1970` (Unix epoch), or the count of days\r\n // between year `1900` and year `1970`, plus one day.\r\n //\r\n // It also should account for the number of \"leap years\" between year `1900` and year `1970`,\r\n // which is 17 of them. https://kalender-365.de/leap-years.php\r\n //\r\n // \"One year has the length of 365 days, 5 hours, 48 minutes and 45 seconds.\r\n // These are 365.2421875 days. This is hard to calculate with, so for practical reasons\r\n // a normal year has been given 365 days and a leap year 366 days. In leap years,\r\n // February 29th is added as leap day, which doesn't exist in a normal year.\r\n // A leap year is every 4 years, but not every 100 years, then again every 400 years.\r\n // So the year 1900 wasn't a leap year, but 2000 was\".\r\n //\r\n // And also, Excel has a historical bug when it incorrectly assumes year `1900` to be a leap year,\r\n // and, as a result, all Excel serial dates starting from March 1st, 1900 lag 1 day behind\r\n // and require an additional 1 day to be added to them in order to be converted to a proper timestamp.\r\n //\r\n // https://learn.microsoft.com/en-us/answers/questions/5249322/why-does-microsoft-excel-considers-29-02-1900-to-b?forum=msoffice-all&referrer=answers#:~:text=This%20made%20it%20easier%20for,other%20programs%20that%20use%20dates.\r\n //\r\n // \"When Lotus 1-2-3 was first released, the program assumed that the year 1900 was a leap year,\r\n // even though it actually was not a leap year. This made it easier for the program to handle\r\n // leap years and caused no harm to almost all date calculations in Lotus 1-2-3.\r\n //\r\n // When Microsoft Multiplan and Microsoft Excel were released, they also assumed that 1900\r\n // was a leap year. This assumption allowed Microsoft Multiplan and Microsoft Excel to use\r\n // the same serial date system used by Lotus 1-2-3 and provide greater compatibility with Lotus 1-2-3.\r\n // Treating 1900 as a leap year also made it easier for users to move worksheets from one program\r\n // to the other\".\r\n //\r\n // So the historical bug is basically that Excel thinks that February 29th, 1900 existed\r\n // while in reality it didn't. That's why it's actually 1 day off for any date after that one.\r\n //\r\n const NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970 = 17\r\n const JANUARY_0TH_1900_DAY = 1\r\n const ERRONEOUS_FEBRUARY_29_1990_DAY = 1\r\n\r\n // An approximate count of seconds in a day is:\r\n // 24 hours * 60 minutes in an hour * 60 seconds in a minute\r\n //\r\n // It is approximate because a minute could be longer than 60 seconds, due to \"leap seconds\".\r\n //\r\n // Still, javascript `Date`, and UNIX time in general, intentionally\r\n // drop the concept of \"leap seconds\" in order to make things simpler.\r\n // So this approximation is valid and doesn't result in any bugs.\r\n // https://stackoverflow.com/questions/53019726/where-are-the-leap-seconds-in-javascript\r\n //\r\n // \"The JavaScript Date object specifically adheres to the concept of Unix Time\r\n // (albeit with higher precision). This is part of the POSIX specification,\r\n // and thus is sometimes called \"POSIX Time\". It does not count leap seconds,\r\n // but rather assumes every day had exactly 86,400 seconds. You can read about\r\n // this in section 20.3.1.1 of the current ECMAScript specification, which states:\r\n //\r\n // \"Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC.\r\n // In time values leap seconds are ignored. It is assumed that there are exactly\r\n // 86,400,000 milliseconds per day.\"\r\n //\r\n // The reason is that the unpredictable nature of leap seconds makes them very\r\n // difficult to work with in APIs. One can't generally pass timestamps around\r\n // that need leap seconds tables to be interpreted correctly, and expect that\r\n // one system will interpret them the same as another. For example, a timestamp\r\n // `1483228826` that accounts for \"leap seconds\" should've been interpreted as\r\n // \"2017-01-01T00:00:00Z\", but if the receiver doesn't account for \"leap seconds\",\r\n // they would interpret it as \"2017-01-01T00:00:26Z\" (e.g. POSIX-based systems like Linux),\r\n // So \"leap seconds\" aren't really portable.\r\n // Even on systems that have full frequently-updated \"leap second\" tables,\r\n // there's no telling what adjustments those tables will contain in the future\r\n // (i.e. beyond the 6-month IERS announcement period) because \"leap seconds\" can't be\r\n // determined by a fixed mathematical formula or something like that. Instead,\r\n // scientists introduce them as needed based on the observed Earth's rotation around the sun.\r\n //\r\n // \"Because the Earth's rotational speed varies in response to climatic and geological events,\r\n // UTC leap seconds are irregularly spaced and not precisely predictable. The decision to insert\r\n // a leap second is made by the International Earth Rotation and Reference Systems Service (IERS),\r\n // typically about six months in advance, to ensure that the difference between UTC and UT1\r\n // does not exceed ±0.9 seconds.\"\r\n //\r\n // One example is year `1900` which is \"every fourth year\" but it still is not a \"leap year\".\r\n //\r\n // To reiterate: to support leap seconds in a programming language, the implementation\r\n // must go out of its way to do so, and must make tradeoffs that are not always acceptable.\r\n // Though there are exceptions, the general position is to not support them - not because\r\n // of any subversion or active countermeasures, but because supporting them properly is much,\r\n // much harder.\r\n //\r\n // https://en.wikipedia.org/wiki/Unix_time#Leap_seconds\r\n // https://en.wikipedia.org/wiki/Leap_year\r\n // https://en.wikipedia.org/wiki/Leap_second\r\n //\r\n const DAY = 24 * 60 * 60 * 1000\r\n const DAYS_IN_YEAR = 365\r\n\r\n // Windows operating system uses floating-point numbers to represent dates,\r\n // where the number represents the count of days elapsed since January 0th, 1900.\r\n //\r\n // This also means that there're 2 aspects associated with this choice:\r\n //\r\n // * January 1st, 1900, 00:00 is represented by `1` rather than `0`, which looks a bit weird.\r\n // * 1900 is a special year because it's a \"one in a 100 years\" occasion when it's not a leap year.\r\n //\r\n // To work around those two aspects, Mac OS chose another baseline — January 1st, 1900.\r\n // Although, that only postponed the second issue because 2100 is going to be the next \"speciaL\" year\r\n // which is not going to be a \"leap\" one.\r\n //\r\n // Older versions of Excel on Mac OS used year 1904 as the default baseline for numeric dates.\r\n // Since 2011, Microsoft Excel on Mac OS uses year 1900 as the default baseline for cross-platform consistency.\r\n // https://support.microsoft.com/en-us/office/date-systems-in-excel-e7fe7167-48a9-4b96-bb53-5612a800b487\r\n //\r\n // So the 1904 baseline is now deprecated, although still available to be configured manually.\r\n // So it still might be encountered in Excel files created on MacOS.\r\n // In that case, the Excel file contains a special flag — `<workbook><workbookPr date1904=\"1\"/>...` —\r\n // that tells the application which baseline is being used for numeric date timestamps.\r\n //\r\n if (epoch1904) {\r\n // Convert the numeric date timestamp from 1904 baseline to 1900 baseline.\r\n excelSerialDate += (1904 - 1900) * DAYS_IN_YEAR + JANUARY_0TH_1900_DAY + ERRONEOUS_FEBRUARY_29_1990_DAY\r\n }\r\n\r\n const daysBeforeUnixEpoch = JANUARY_0TH_1900_DAY + ERRONEOUS_FEBRUARY_29_1990_DAY + (1970 - 1900) * DAYS_IN_YEAR + NUMBER_OF_LEAP_YEARS_BETWEEN_1900_AND_1970\r\n\r\n return Math.floor((excelSerialDate - daysBeforeUnixEpoch) * DAY)\r\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,SAASA,mBAAmBA,CAACC,eAAe,EAAEC,SAAS,EAAE;EACtE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,IAAMC,0CAA0C,GAAG,EAAE;EACrD,IAAMC,oBAAoB,GAAG,CAAC;EAC9B,IAAMC,8BAA8B,GAAG,CAAC;;EAExC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,IAAMC,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;EAC/B,IAAMC,YAAY,GAAG,GAAG;;EAExB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,IAAIL,SAAS,EAAE;IACb;IACAD,eAAe,IAAI,CAAC,IAAI,GAAG,IAAI,IAAIM,YAAY,GAAGH,oBAAoB,GAAGC,8BAA8B;EACzG;EAEA,IAAMG,mBAAmB,GAAGJ,oBAAoB,GAAGC,8BAA8B,GAAG,CAAC,IAAI,GAAG,IAAI,IAAIE,YAAY,GAAGJ,0CAA0C;EAE7J,OAAOM,IAAI,CAACC,KAAK,CAAC,CAACT,eAAe,GAAGO,mBAAmB,IAAIF,GAAG,CAAC;AAClE"}
@@ -4,10 +4,10 @@
4
4
  * is `xl/_rels/workbook.xml.rels` file.
5
5
  * https://github.com/tidyverse/readxl/issues/104
6
6
  * @param {string} content — `xl/_rels/workbook.xml.rels` file contents.
7
- * @param {function} parseXmlStream — SAX XML parser.
7
+ * @param {function} parseXml — SAX XML parser.
8
8
  * @return {object} — An object of shape `{ sheets: Record<string, string>, sharedStrings: string?, styles: string? }`
9
9
  */
10
- export default function parseFilePaths(content, parseXmlStream) {
10
+ export default function parseFilePaths(content, parseXml) {
11
11
  // Example:
12
12
  // <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
13
13
  // ...
@@ -16,54 +16,55 @@ export default function parseFilePaths(content, parseXmlStream) {
16
16
  // Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
17
17
  // Target="worksheets/sheet1.xml"/>
18
18
  // </Relationships>
19
- return parseXmlStream(content, {
20
- createInitialState: createInitialStateInWorkbookRelationships,
21
- onOpenTag: onOpenTagInWorkbookRelationships
22
- }).then(function (state) {
23
- return state;
19
+ var state = createInitialState();
20
+ return parseXml(content, state, onOpenTag, null, null).then(function () {
21
+ return getResultFromState(state);
24
22
  });
25
- }
26
- function createInitialStateInWorkbookRelationships() {
27
- return {
28
- sheets: {},
29
- sharedStrings: undefined,
30
- styles: undefined
31
- };
32
- }
33
- function onOpenTagInWorkbookRelationships(tagName, attributes, state) {
34
- if (tagName === 'Relationship') {
35
- addFilePathForRelation(state, attributes.Id, attributes.Type, attributes.Target);
23
+ function createInitialState() {
24
+ return {
25
+ sheets: {},
26
+ sharedStrings: undefined,
27
+ styles: undefined
28
+ };
36
29
  }
37
- }
38
- function addFilePathForRelation(state, id, type, target) {
39
- switch (type) {
40
- case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles':
41
- state.styles = getFilePathFromRelationTarget(target);
42
- break;
43
- case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings':
44
- state.sharedStrings = getFilePathFromRelationTarget(target);
45
- break;
46
- case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet':
47
- state.sheets[id] = getFilePathFromRelationTarget(target);
48
- break;
30
+ function getResultFromState(state) {
31
+ return state;
49
32
  }
50
- }
51
- function getFilePathFromRelationTarget(path) {
52
- // Normally, `path` is a relative path inside the ZIP archive,
53
- // like "worksheets/sheet1.xml", or "sharedStrings.xml", or "styles.xml".
54
- // There has been one weird case when file path was an absolute path,
55
- // like "/xl/worksheets/sheet1.xml" (specifically for sheets):
56
- // https://github.com/catamphetamine/read-excel-file/pull/95
57
- // Other libraries (like `xlsx`) and software (like Google Docs)
58
- // seem to support such absolute file paths, so this library does too.
59
- if (path[0] === '/') {
60
- return path.slice('/'.length);
33
+ function onOpenTag(tagName, attributes, state) {
34
+ if (tagName === 'Relationship') {
35
+ addFilePathForRelation(state, attributes.Id, attributes.Type, attributes.Target);
36
+ }
37
+ }
38
+ function addFilePathForRelation(state, id, type, target) {
39
+ switch (type) {
40
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles':
41
+ state.styles = getFilePathFromRelationTarget(target);
42
+ break;
43
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings':
44
+ state.sharedStrings = getFilePathFromRelationTarget(target);
45
+ break;
46
+ case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet':
47
+ state.sheets[id] = getFilePathFromRelationTarget(target);
48
+ break;
49
+ }
50
+ }
51
+ function getFilePathFromRelationTarget(path) {
52
+ // Normally, `path` is a relative path inside the ZIP archive,
53
+ // like "worksheets/sheet1.xml", or "sharedStrings.xml", or "styles.xml".
54
+ // There has been one weird case when file path was an absolute path,
55
+ // like "/xl/worksheets/sheet1.xml" (specifically for sheets):
56
+ // https://github.com/catamphetamine/read-excel-file/pull/95
57
+ // Other libraries (like `xlsx`) and software (like Google Docs)
58
+ // seem to support such absolute file paths, so this library does too.
59
+ if (path[0] === '/') {
60
+ return path.slice('/'.length);
61
+ }
62
+ // // Seems like a path could also be a URL.
63
+ // // http://officeopenxml.com/anatomyofOOXML-xlsx.php
64
+ // if (/^[a-z]+\:\/\//.test(path)) {
65
+ // return path
66
+ // }
67
+ return 'xl/' + path;
61
68
  }
62
- // // Seems like a path could also be a URL.
63
- // // http://officeopenxml.com/anatomyofOOXML-xlsx.php
64
- // if (/^[a-z]+\:\/\//.test(path)) {
65
- // return path
66
- // }
67
- return 'xl/' + path;
68
69
  }
69
70
  //# sourceMappingURL=parseFilePaths.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parseFilePaths.js","names":["parseFilePaths","content","parseXmlStream","createInitialState","createInitialStateInWorkbookRelationships","onOpenTag","onOpenTagInWorkbookRelationships","then","state","sheets","sharedStrings","undefined","styles","tagName","attributes","addFilePathForRelation","Id","Type","Target","id","type","target","getFilePathFromRelationTarget","path","slice","length"],"sources":["../../source/xlsx/parseFilePaths.js"],"sourcesContent":["/**\r\n * Returns sheet file paths.\r\n * Seems that the correct place to look for the `sheetId` -> `filename` mapping\r\n * is `xl/_rels/workbook.xml.rels` file.\r\n * https://github.com/tidyverse/readxl/issues/104\r\n * @param {string} content — `xl/_rels/workbook.xml.rels` file contents.\r\n * @param {function} parseXmlStream — SAX XML parser.\r\n * @return {object} — An object of shape `{ sheets: Record<string, string>, sharedStrings: string?, styles: string? }`\r\n */\r\nexport default function parseFilePaths(content, parseXmlStream) {\r\n // Example:\r\n // <Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\r\n // ...\r\n // <Relationship\r\n // Id=\"rId3\"\r\n // Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\"\r\n // Target=\"worksheets/sheet1.xml\"/>\r\n // </Relationships>\r\n return parseXmlStream(content, {\r\n createInitialState: createInitialStateInWorkbookRelationships,\r\n onOpenTag: onOpenTagInWorkbookRelationships\r\n }).then((state) => {\r\n return state\r\n })\r\n}\r\n\r\nfunction createInitialStateInWorkbookRelationships() {\r\n return {\r\n sheets: {},\r\n sharedStrings: undefined,\r\n styles: undefined\r\n }\r\n}\r\n\r\nfunction onOpenTagInWorkbookRelationships(tagName, attributes, state) {\r\n if (tagName === 'Relationship') {\r\n addFilePathForRelation(state, attributes.Id, attributes.Type, attributes.Target)\r\n }\r\n}\r\n\r\nfunction addFilePathForRelation(state, id, type, target) {\r\n switch (type) {\r\n case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles':\r\n state.styles = getFilePathFromRelationTarget(target)\r\n break\r\n case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings':\r\n state.sharedStrings = getFilePathFromRelationTarget(target)\r\n break\r\n case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet':\r\n state.sheets[id] = getFilePathFromRelationTarget(target)\r\n break\r\n }\r\n}\r\n\r\nfunction getFilePathFromRelationTarget(path) {\r\n // Normally, `path` is a relative path inside the ZIP archive,\r\n // like \"worksheets/sheet1.xml\", or \"sharedStrings.xml\", or \"styles.xml\".\r\n // There has been one weird case when file path was an absolute path,\r\n // like \"/xl/worksheets/sheet1.xml\" (specifically for sheets):\r\n // https://github.com/catamphetamine/read-excel-file/pull/95\r\n // Other libraries (like `xlsx`) and software (like Google Docs)\r\n // seem to support such absolute file paths, so this library does too.\r\n if (path[0] === '/') {\r\n return path.slice('/'.length)\r\n }\r\n // // Seems like a path could also be a URL.\r\n // // http://officeopenxml.com/anatomyofOOXML-xlsx.php\r\n // if (/^[a-z]+\\:\\/\\//.test(path)) {\r\n // return path\r\n // }\r\n return 'xl/' + path\r\n}\r\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,SAASA,cAAcA,CAACC,OAAO,EAAEC,cAAc,EAAE;EAC9D;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,OAAOA,cAAc,CAACD,OAAO,EAAE;IAC7BE,kBAAkB,EAAEC,yCAAyC;IAC7DC,SAAS,EAAEC;EACb,CAAC,CAAC,CAACC,IAAI,CAAC,UAACC,KAAK,EAAK;IACjB,OAAOA,KAAK;EACd,CAAC,CAAC;AACJ;AAEA,SAASJ,yCAAyCA,CAAA,EAAG;EACnD,OAAO;IACLK,MAAM,EAAE,CAAC,CAAC;IACVC,aAAa,EAAEC,SAAS;IACxBC,MAAM,EAAED;EACV,CAAC;AACH;AAEA,SAASL,gCAAgCA,CAACO,OAAO,EAAEC,UAAU,EAAEN,KAAK,EAAE;EACpE,IAAIK,OAAO,KAAK,cAAc,EAAE;IAC9BE,sBAAsB,CAACP,KAAK,EAAEM,UAAU,CAACE,EAAE,EAAEF,UAAU,CAACG,IAAI,EAAEH,UAAU,CAACI,MAAM,CAAC;EAClF;AACF;AAEA,SAASH,sBAAsBA,CAACP,KAAK,EAAEW,EAAE,EAAEC,IAAI,EAAEC,MAAM,EAAE;EACvD,QAAQD,IAAI;IACV,KAAK,4EAA4E;MAC/EZ,KAAK,CAACI,MAAM,GAAGU,6BAA6B,CAACD,MAAM,CAAC;MACpD;IACF,KAAK,mFAAmF;MACtFb,KAAK,CAACE,aAAa,GAAGY,6BAA6B,CAACD,MAAM,CAAC;MAC3D;IACF,KAAK,+EAA+E;MAClFb,KAAK,CAACC,MAAM,CAACU,EAAE,CAAC,GAAGG,6BAA6B,CAACD,MAAM,CAAC;MACxD;EACJ;AACF;AAEA,SAASC,6BAA6BA,CAACC,IAAI,EAAE;EAC3C;EACA;EACA;EACA;EACA;EACA;EACA;EACA,IAAIA,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;IACnB,OAAOA,IAAI,CAACC,KAAK,CAAC,GAAG,CAACC,MAAM,CAAC;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA,OAAO,KAAK,GAAGF,IAAI;AACrB"}
1
+ {"version":3,"file":"parseFilePaths.js","names":["parseFilePaths","content","parseXml","state","createInitialState","onOpenTag","then","getResultFromState","sheets","sharedStrings","undefined","styles","tagName","attributes","addFilePathForRelation","Id","Type","Target","id","type","target","getFilePathFromRelationTarget","path","slice","length"],"sources":["../../source/xlsx/parseFilePaths.js"],"sourcesContent":["/**\r\n * Returns sheet file paths.\r\n * Seems that the correct place to look for the `sheetId` -> `filename` mapping\r\n * is `xl/_rels/workbook.xml.rels` file.\r\n * https://github.com/tidyverse/readxl/issues/104\r\n * @param {string} content — `xl/_rels/workbook.xml.rels` file contents.\r\n * @param {function} parseXml — SAX XML parser.\r\n * @return {object} — An object of shape `{ sheets: Record<string, string>, sharedStrings: string?, styles: string? }`\r\n */\r\nexport default function parseFilePaths(content, parseXml) {\r\n // Example:\r\n // <Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\r\n // ...\r\n // <Relationship\r\n // Id=\"rId3\"\r\n // Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\"\r\n // Target=\"worksheets/sheet1.xml\"/>\r\n // </Relationships>\r\n const state = createInitialState()\r\n return parseXml(\r\n content,\r\n state,\r\n onOpenTag,\r\n null,\r\n null\r\n ).then(() => getResultFromState(state))\r\n\r\n function createInitialState() {\r\n return {\r\n sheets: {},\r\n sharedStrings: undefined,\r\n styles: undefined\r\n }\r\n }\r\n\r\n function getResultFromState(state) {\r\n return state\r\n }\r\n\r\n function onOpenTag(tagName, attributes, state) {\r\n if (tagName === 'Relationship') {\r\n addFilePathForRelation(state, attributes.Id, attributes.Type, attributes.Target)\r\n }\r\n }\r\n\r\n function addFilePathForRelation(state, id, type, target) {\r\n switch (type) {\r\n case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles':\r\n state.styles = getFilePathFromRelationTarget(target)\r\n break\r\n case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings':\r\n state.sharedStrings = getFilePathFromRelationTarget(target)\r\n break\r\n case 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet':\r\n state.sheets[id] = getFilePathFromRelationTarget(target)\r\n break\r\n }\r\n }\r\n\r\n function getFilePathFromRelationTarget(path) {\r\n // Normally, `path` is a relative path inside the ZIP archive,\r\n // like \"worksheets/sheet1.xml\", or \"sharedStrings.xml\", or \"styles.xml\".\r\n // There has been one weird case when file path was an absolute path,\r\n // like \"/xl/worksheets/sheet1.xml\" (specifically for sheets):\r\n // https://github.com/catamphetamine/read-excel-file/pull/95\r\n // Other libraries (like `xlsx`) and software (like Google Docs)\r\n // seem to support such absolute file paths, so this library does too.\r\n if (path[0] === '/') {\r\n return path.slice('/'.length)\r\n }\r\n // // Seems like a path could also be a URL.\r\n // // http://officeopenxml.com/anatomyofOOXML-xlsx.php\r\n // if (/^[a-z]+\\:\\/\\//.test(path)) {\r\n // return path\r\n // }\r\n return 'xl/' + path\r\n }\r\n}"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,SAASA,cAAcA,CAACC,OAAO,EAAEC,QAAQ,EAAE;EACxD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,IAAMC,KAAK,GAAGC,kBAAkB,CAAC,CAAC;EAClC,OAAOF,QAAQ,CACbD,OAAO,EACPE,KAAK,EACLE,SAAS,EACT,IAAI,EACJ,IACF,CAAC,CAACC,IAAI,CAAC;IAAA,OAAMC,kBAAkB,CAACJ,KAAK,CAAC;EAAA,EAAC;EAEvC,SAASC,kBAAkBA,CAAA,EAAG;IAC5B,OAAO;MACLI,MAAM,EAAE,CAAC,CAAC;MACVC,aAAa,EAAEC,SAAS;MACxBC,MAAM,EAAED;IACV,CAAC;EACH;EAEA,SAASH,kBAAkBA,CAACJ,KAAK,EAAE;IACjC,OAAOA,KAAK;EACd;EAEA,SAASE,SAASA,CAACO,OAAO,EAAEC,UAAU,EAAEV,KAAK,EAAE;IAC7C,IAAIS,OAAO,KAAK,cAAc,EAAE;MAC9BE,sBAAsB,CAACX,KAAK,EAAEU,UAAU,CAACE,EAAE,EAAEF,UAAU,CAACG,IAAI,EAAEH,UAAU,CAACI,MAAM,CAAC;IAClF;EACF;EAEA,SAASH,sBAAsBA,CAACX,KAAK,EAAEe,EAAE,EAAEC,IAAI,EAAEC,MAAM,EAAE;IACvD,QAAQD,IAAI;MACV,KAAK,4EAA4E;QAC/EhB,KAAK,CAACQ,MAAM,GAAGU,6BAA6B,CAACD,MAAM,CAAC;QACpD;MACF,KAAK,mFAAmF;QACtFjB,KAAK,CAACM,aAAa,GAAGY,6BAA6B,CAACD,MAAM,CAAC;QAC3D;MACF,KAAK,+EAA+E;QAClFjB,KAAK,CAACK,MAAM,CAACU,EAAE,CAAC,GAAGG,6BAA6B,CAACD,MAAM,CAAC;QACxD;IACJ;EACF;EAEA,SAASC,6BAA6BA,CAACC,IAAI,EAAE;IAC3C;IACA;IACA;IACA;IACA;IACA;IACA;IACA,IAAIA,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE;MACnB,OAAOA,IAAI,CAACC,KAAK,CAAC,GAAG,CAACC,MAAM,CAAC;IAC/B;IACA;IACA;IACA;IACA;IACA;IACA,OAAO,KAAK,GAAGF,IAAI;EACrB;AACF"}