1+ // XLSX does have "d" type for dates, but it's not commonly used.
2+ // Instead, it prefers using "n" type for storing dates as timestamps.
3+ //
4+ // Whether a numeric value is a number or a date timestamp, it sometimes could be
5+ // detected by looking at the value "format" and seeing if it's a date-specific one.
6+ // https://github.com/catamphetamine/read-excel-file/issues/3#issuecomment-395770777
7+ //
8+ // The list of generic numeric value "formats":
9+ // https://xlsxwriter.readthedocs.io/format.html#format-set-num-format
10+ //
11+ export default function isDateTimestamp ( value , styleId , styles , options ) {
12+ if ( styleId ) {
13+ const style = styles [ styleId ]
14+ if ( ! style ) {
15+ throw new Error ( `Cell style not found: ${ styleId } ` )
16+ }
17+ if (
18+ // Whether it's a "number format" that's conventionally used for storing date timestamps.
19+ BUILT_IN_DATE_NUMBER_FORMAT_IDS . indexOf ( parseInt ( style . numberFormat . id ) ) >= 0 ||
20+ // Whether it's a "number format" that uses a "formatting template"
21+ // that the developer is certain is a date formatting template.
22+ ( options . dateFormat && style . numberFormat . template === options . dateFormat ) ||
23+ // Whether the "smart formatting template" feature is not disabled
24+ // and it has detected that it's a date formatting template by looking at it.
25+ ( options . smartDateParser !== false && style . numberFormat . template && isDateTemplate ( style . numberFormat . template ) )
26+ ) {
27+ return true
28+ }
29+ }
30+ }
31+
32+ // https://hexdocs.pm/xlsxir/number_styles.html
33+ const BUILT_IN_DATE_NUMBER_FORMAT_IDS = [ 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 27 , 30 , 36 , 45 , 46 , 47 , 50 , 57 ]
34+
35+ // On some date formats, there's an "[$-414]" prefix.
36+ // I don't have any idea what that is.
37+ //
38+ // https://stackoverflow.com/questions/4730152/what-indicates-an-office-open-xml-cell-contains-a-date-time-value
39+ //
40+ // Examples:
41+ //
42+ // * 27 (built-in format) "[$-404]e/m/d"
43+ // * 164 (custom format) "[$-414]mmmm\ yyyy;@"
44+ //
45+ const DATE_FORMAT_WEIRD_PREFIX = / ^ \[ \$ - 4 1 4 \] /
46+
47+ // On some date formats, there's an ";@" postfix.
48+ // I don't have any idea what that is.
49+ // Examples:
50+ //
51+ // * 164 (custom format) "m/d/yyyy;@"
52+ // * 164 (custom format) "[$-414]mmmm\ yyyy;@"
53+ //
54+ const DATE_FORMAT_WEIRD_POSTFIX = / ; @ $ /
55+
56+ function isDateTemplate ( template ) {
57+ // Date format tokens could be in upper case or in lower case.
58+ // There seems to be no single standard.
59+ // So lowercase the template first.
60+ template = template . toLowerCase ( )
61+
62+ // On some date formats, there's an "[$-414]" prefix.
63+ // I don't have any idea what that is. Trim it.
64+ template = template . replace ( DATE_FORMAT_WEIRD_PREFIX , '' )
65+
66+ // On some date formats, there's an ";@" postfix.
67+ // I don't have any idea what that is. Trim it.
68+ template = template . replace ( DATE_FORMAT_WEIRD_POSTFIX , '' )
69+
70+ const tokens = template . split ( / \W + / )
71+ for ( const token of tokens ) {
72+ if ( DATE_TEMPLATE_TOKENS . indexOf ( token ) < 0 ) {
73+ return false
74+ }
75+ }
76+ return true
77+ }
78+
79+ // These tokens could be in upper case or in lower case.
80+ // There seems to be no single standard, so using lower case.
81+ const DATE_TEMPLATE_TOKENS = [
82+ // Seconds (min two digits). Example: "05".
83+ 'ss' ,
84+ // Minutes (min two digits). Example: "05". Could also be "Months". Weird.
85+ 'mm' ,
86+ // Hours. Example: "1".
87+ 'h' ,
88+ // Hours (min two digits). Example: "01".
89+ 'hh' ,
90+ // "AM" part of "AM/PM". Lowercased just in case.
91+ 'am' ,
92+ // "PM" part of "AM/PM". Lowercased just in case.
93+ 'pm' ,
94+ // Day. Example: "1"
95+ 'd' ,
96+ // Day (min two digits). Example: "01"
97+ 'dd' ,
98+ // Month (numeric). Example: "1".
99+ 'm' ,
100+ // Month (numeric, min two digits). Example: "01". Could also be "Minutes". Weird.
101+ 'mm' ,
102+ // Month (shortened month name). Example: "Jan".
103+ 'mmm' ,
104+ // Month (full month name). Example: "January".
105+ 'mmmm' ,
106+ // Two-digit year. Example: "20".
107+ 'yy' ,
108+ // Full year. Example: "2020".
109+ 'yyyy' ,
110+
111+ // I don't have any idea what "e" means.
112+ // It's used in "built-in" XLSX formats:
113+ // * 27 '[$-404]e/m/d';
114+ // * 36 '[$-404]e/m/d';
115+ // * 50 '[$-404]e/m/d';
116+ // * 57 '[$-404]e/m/d';
117+ 'e'
118+ ] ;
0 commit comments