This commit is contained in:
SheetJS 2023-06-23 16:24:44 -04:00
parent edaa2e6b5e
commit 881a848c93
10 changed files with 310 additions and 22 deletions

@ -143,7 +143,7 @@ const u8 = XLSX.write(wb, { bookType: "xlsx", type: "buffer" });
/* create Blob */
const blob = new Blob([u8]);
/* create object URL */
const url = URL.createObjectURL(new Blob([u8]));
const url = URL.createObjectURL(blob);
/* create `A` DOM element */
const a = document.createElement("a");

@ -29,7 +29,7 @@ command-line tool for reading data from files.
:::note
Many QuickJS functions are not documented. The explanation was verified against
the latest release (version `2021-03-27`, commit `2788d71`).
the latest release (commit `2788d71`).
:::
@ -268,7 +268,7 @@ This demo was tested in the following deployments:
| `darwin-arm` | `2788d71` | 2023-06-05 |
| `linux-x64` | `2788d71` | 2023-06-02 |
Git commit `2788d71` corresponds to the latest release (`2021-03-27`)
When the demo was tested, commit `2788d71` corresponded to the latest release.
:::
@ -329,7 +329,7 @@ first worksheet name, and the contents of the first sheet as CSV rows.
:::note
This demo was last tested on 2023 March 11 against QuickJS `2021-03-27`.
This demo was last tested on 2023 March 11 against QuickJS commit `2788d71`.
:::

@ -4,6 +4,55 @@ sidebar_position: 3
# Dates and Times
<details>
<summary><b>File Format Support</b> (click to show)</summary>
Dates are a core concept in nearly every spreadsheet application in existence.
Some legacy spreadsheet apps only supported dates. Others supported times as a
distinct concept from dates.
Some file formats store dates in a textual format, while others store dates with
[numbers representing a difference from an epoch](#relative-epochs).
Many spreadsheet apps use special number formats to signal that values are dates
or times. Quattro Pro for DOS had a distinct set of Date number formats and
Time number formats, but did not have a mixed Date + Time format.
Lotus 1-2-3 used a ["1900" date system](#1904-and-1900-date-systems), while
Numbers exclusively supports 1904 under the hood. Excel file formats typically
include options for specifying the date system
| Formats | Date | Time | D+T | Date Storage | Date System |
|:------------------|:----:|:----:|:---:|:--------------------:|:------------|
| NUMBERS | ✔ | ✔ | ✔ | Number | 1904 Only |
| XLSX / XLSM | ✔ | ✔ | ✔ | Number | 1900 + 1904 |
| XLSX (Strict ISO) | ✔ | ✔ | ✔ | Relative Date | 1900 + 1904 |
| XLSB | ✔ | ✔ | ✔ | Number | 1900 + 1904 |
| XLML | ✔ | ✔ | ✔ | Relative Date | 1900 + 1904 |
| XLS (BIFF5/8) | ✔ | ✔ | ✔ | Relative Date | 1900 + 1904 |
| XLS (BIFF2/3/4) | ✔ | ✔ | ✔ | Relative Date | 1900 + 1904 |
| XLR (Works) | ✔ | ✔ | ✔ | Relative Date | 1900 + 1904 |
| ET (WPS 电子表格) | ✔ | ✔ | ✔ | Relative Date | 1900 + 1904 |
| ODS / FODS / UOS | ✔ | ✔ | ✔ | ISO Duration or Date | Arbitrary |
| HTML | ✔ | ✔ | ✔ | Plaintext | Calendar |
| CSV / TSV / Text | ✔ | ✔ | ✔ | Plaintext | Calendar |
| DBF | ✔ | * | * | Number or Plaintext | Calendar |
| DIF | ✔ | ✔ | ✔ | Plaintext | Calendar |
| WK1 | ✔ | ✔ | ✕ | Number | 1900 |
| WKS (Works) | ✔ | ✔ | ✕ | Number | 1900 |
| WQ1 | ✔ | | ✕ | Number | 1900 |
| QPW | ✔ | ✔ | * | Number | 1900 |
X (✕) marks features that are not supported by the file formats. For example,
the WK1 file format had date-only formats and time-only formats but no mixed
date-time formats.
Newer DBF levels support a special `T` field type that represents date + time.
The QPW file format supports mixed date + time formats in custom number formats.
</details>
Lotus 1-2-3, Excel, and other spreadsheet software do not have a true concept
of date or time. Instead, dates and times are stored as offsets from an epoch.
The magic behind date interpretations is hidden in functions or number formats.
@ -11,6 +60,44 @@ The magic behind date interpretations is hidden in functions or number formats.
SheetJS attempts to create a friendly JS date experience while also exposing
options to use the traditional date codes
:::tip pass
Date and time handling was overhauled in version `0.20.0`. It is strongly
recommended to [upgrade](/docs/getting-started/installation/).
:::
The following example exports the current time to XLSX spreadsheet. The time
shown on this page will be the time displayed in Excel
```jsx live
function SheetJSNow() {
const [date, setDate] = React.useState(new Date());
const xport = React.useCallback(() => {
/* generate array of arrays */
const aoa = [[date]];
/* to avoid confusion, set milliseconds to 0 */
aoa[0][0].setMilliseconds(0);
/* generate workbook */
const ws = XLSX.utils.aoa_to_sheet(aoa, {dense: true});
/* set cell A1 number format */
ws["!data"][0][0].z = "yyyy-mm-dd hh:mm:ss"
ws["!cols"] = [{wch: 20}];
/* generate workbook and export */
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
XLSX.writeFile(wb, "SheetJSNow.xlsx");
}, []);
return ( <>
<p>
<b>Local Time:</b>{date.toString()}
<button onClick={()=>setDate(new Date())}>Refresh</button>
</p>
<button onClick={xport}>Export XLSX</button>
</> )
}
```
## How Spreadsheets Understand Time
@ -38,7 +125,7 @@ the date code.
The following table covers some common formats:
<details open><summary><b>Common Date-Time Formats</b> (click to hide)</summary>
<details><summary><b>Common Date-Time Formats</b> (click to show)</summary>
| Fragment | Interpretation |
|:---------|:-----------------------------|
@ -134,11 +221,6 @@ JavaScript Date objects are timezone-aware)
This is a deficiency of the spreadsheet software. Excel has no native concept
of universal time.
The library attempts to normalize the dates. All times are specified in the
local time zone. SheetJS cannot magically fix the technical problems with
Excel and other spreadsheet software, but this represents .
## How Files Store Dates and Times
XLS, XLSB, and most binary formats store the raw date codes. Special number
@ -155,27 +237,173 @@ support is poor.
ODS does support absolute time values but drops the actual timezone indicator
when parsing. In that sense, LibreOffice follows the same behavior as Excel.
Numbers uses a calendar date system, but records pure time values as if they are
absolute times in 1904 January 01. It is spiritually equivalent to the 1904 mode
in Excel and other spreadsheet applications.
## How JavaScript Engines Understand Time
JavaScript provides a `Date` object which represents an *absolute* time. Under
the hood, `Date` uses the "UNIX" epoch of 1970 January 01 midnight in UTC. This
means the actual zero date is different in different timezones!
| Location | IANA Timezone | `new Date(0)` in local time |
|:------------|:----------------------|:----------------------------|
| Honolulu | `Pacific/Honolulu` | `1969-12-31 02:00 PM` |
| Los Angeles | `America/Los_Angeles` | `1969-12-31 04:00 PM` |
| New York | `America/New_York` | `1969-12-31 07:00 PM` |
| Sao Paulo | `America/Sao_Paulo` | `1969-12-31 09:00 PM` |
| London | `Europe/London` | `1970-01-01 01:00 AM` |
| Cairo | `Africa/Cairo` | `1970-01-01 02:00 AM` |
| Djibouti | `Africa/Djibouti` | `1970-01-01 03:00 AM` |
| Chennai | `Asia/Kolkata` | `1970-01-01 05:30 AM` |
| Shanghai | `Asia/Shanghai` | `1970-01-01 08:00 AM` |
| Seoul | `Asia/Seoul` | `1970-01-01 09:00 AM` |
| Sydney | `Australia/Sydney` | `1970-01-01 10:00 AM` |
In modern environments, the IANA Timezone and timezone offset can be discovered
through the `Intl` and `Date` objects:
```jsx live
function LocalInfo() {
const date = new Date();
return ( <>
<b>Local Time</b>: {date.toString()}<br/>
<b>Time offset (relative to UTC)</b>: {-date.getTimezoneOffset()/60} hours <br/>
<b>IANA Timezone</b>: {Intl.DateTimeFormat().resolvedOptions().timeZone}
</>)}
```
:::caution
The timezone information is provided by the JavaScript engine and local settings.
There are outstanding Google Chrome and V8 bugs related to rounded offsets for
timezones under a lunar calendar. The last timezone to switch to the Gregorian
calendar was `Africa/Monrovia` (in 1972).
SheetJS utilities attempt to work around the browser bugs.
:::
### UTC and Local Time
The `Date` object has a number of prototype methods for inspecting the object.
Some methods interact with the true value, while others convert to the local
timezone. Some methods are listed in the table below:
| Feature | Local Time method | UTC method |
|:-----------------|:------------------|:-----------------|
| Year | `getFullYear` | `getUTCFullYear` |
| Month (0-11) | `getMonth` | `getUTCMonth` |
| Day of the month | `getDate` | `getUTCDate` |
| Hours | `getHours` | `getUTCHours` |
| Minutes | `getMinutes` | `getUTCMinutes` |
| Seconds | `getSeconds` | `getUTCSeconds` |
| Entire date | `toString` | `toUTCString` |
It is typical for websites and other applications to present data in local time.
To serve an international audience, backend servers typically use UTC time.
The following example shows the time when the page was loaded. The same absolute
time will appear to be different under local and UTC interpretations:
```jsx live
function LocalUTC() {
const d = new Date();
/* display number with 2 digits, prepending `0` if necessary */
const f = (n) => n.toString().padStart(2, "0");
/* HH:MM:SS using local interpretation */
const local = `${f(d.getHours())}:${f(d.getMinutes())}:${f(d.getSeconds())}`;
/* HH:MM:SS using UTC interpretation */
const utc = `${f(d.getUTCHours())}:${f(d.getUTCMinutes())}:${f(d.getUTCSeconds())}`;
return ( <>
<b>Local Interpretation</b><br/>
<code>toString</code>: {d.toString()}<br/>
24-hour time: {local}<br/>
<br/>
<b>UTC Interpretation</b><br/>
<code>toUTCString</code>: {d.toUTCString()}<br/>
24-hour time: {utc}<br/>
</>)}
```
## How SheetJS handles Dates and Times
The default behavior for all parsers is to generate number cells. Passing the
`cellDates` to true will force the parsers to store dates:
SheetJS attempts to reconcile the spreadsheet and JavaScript date concepts.
```js
// cell A1 will be { t: 'n', v: 54337 }
var wb_sans_date = XLSX.read("10/6/2048", {type:"binary"});
The default behavior for all parsers is to generate number cells. Setting
`cellDates` to true will force the parsers to store dates.
// cell A1 will be { t: 'd', v: <Date object representing 2048 October 06> }
var wb_with_date = XLSX.read("10/6/2048", {type:"binary", cellDates: true});
```jsx live
function SheetJSCellDates() {
var csv = "Date,10/6/2048";
// cell B1 will be { t: 'n', v: 54337 }
var wb_sans_date = XLSX.read(csv, {type:"binary"});
var ws_sans_date = wb_sans_date.Sheets.Sheet1;
// cell B1 will be { t: 'd', v: <Date: 2048-10-06 00:00:00 UTC> }
var wb_with_date = XLSX.read(csv, {type:"binary", cellDates: true});
var ws_with_date = wb_with_date.Sheets.Sheet1;
return (<>
<b>CSV:</b><pre>{csv}</pre>
<b>Cell B1:</b><br/><br/>
<table><tr><th>cellDates</th><th>type</th><th>value</th></tr>
<tr><td>(unspecified)</td>
<td><code>{ws_sans_date["B1"].t}</code></td>
<td><code>{ws_sans_date["B1"].v}</code></td>
</tr>
<tr><td>true</td>
<td><code>{ws_with_date["B1"].t}</code></td>
<td><code>{ws_with_date["B1"].v.toISOString()}</code> (Date object)</td>
</tr>
</table>
</>);
}
```
When writing, date cells are automatically translated back to numeric cells
with an appropriate number format.
The actual values stored in cells are intended to be correct from the
perspective of an Excel user in the current timezone.
The value formatting logic understands date formats and converts when relevant.
It always uses the UTC interpretation of Date objects.
### Date Objects
The actual values stored in cells are intended to be correct when interpreted
using UTC date methods.
For example, [`DateTime.xlsx`](pathname:///DateTime.xlsx) is a test file with the following data:
| Type | Value |
|:---------|----------------------:|
| Date | `2048-10-06` |
| Time | `15:00` |
| DateTime | `2048-10-06 15:00:00` |
The raw data values are shown in the live demo. The UTC date string will show
the same value as Excel irrespective of the local timezone.
```jsx live
function SheetJSDateTimeXlsxValues() {
const [data, setData] = React.useState([[]]);
React.useEffect(() => { (async() => {
const ab = await (await fetch("/DateTime.xlsx")).arrayBuffer();
const wb = XLSX.read(ab, {cellDates: true, dense: true});
setData(wb.Sheets.Sheet1["!data"]);
})(); });
return ( <table><thead>
<th>Excel Date</th><th>UTC Date</th><th>Local Date</th>
</thead><tbody>
{data.slice(1).map((row,R) => ( <tr key={R}>
<td>{row[1].w}</td>
<td>{row[1].v.toUTCString()}</td>
<td>{row[1].v.toString()}</td>
</tr> ))}
</tbody></table> );
}
```
### Utility Functions
@ -206,6 +434,37 @@ var ws = XLSX.utils.aoa_to_sheet([[new Date()]], { cellDates: true });
var A1 = XLSX.utils.sheet_to_json(ws, { header: 1 })[0][0];
```
### UTC Option
Some API functions support the `UTC` option to control how dates are handled.
**[`sheet_to_json`](/docs/api/utilities/array#array-output)**
If `UTC` is true, the dates will be correct when interpreted in UTC. By default,
the dates will be correct when interpreted in local time.
Typically `UTC` is used for data from an API endpoint, as servers typically emit
UTC dates and expect scripts to localize. The local interpretation is sensible
when users submit data, as they will be providing times in their local timezone.
**[`aoa_to_sheet` / `sheet_add_aoa`](/docs/api/utilities/array#array-of-arrays-input)** / **[`json_to_sheet` / `sheet_add_json`](/docs/api/utilities/array#array-of-objects-input)**
If `UTC` is true, the UTC interpretation of dates will be used.
Typically `UTC` is used for data from an API endpoint, as servers typically emit
UTC dates and expect scripts to localize. The local interpretation is sensible
when date objects are generated in the browser.
**[`table_to_book` / `table_to_sheet` / `sheet_add_dom`](/docs/api/utilities/html#html-table-input)**
If `UTC` is true, potential dates are interpreted as if they represent UTC times.
By default, potential dates are interpreted in local time.
Typically `UTC` is used for data exported from Excel or other spreadsheet apps.
If the table is programmatically generated in the frontend, the dates and times
will be in the local timezone and the local interpretation is preferable.
### Number Formats
By default, the number formats are not emitted. For Excel-based file formats,

@ -38,6 +38,7 @@ The read functions accept an options argument:
|`PRN` | false | If true, allow parsing of PRN files ** |
|`xlfn` | false | If true, preserve `_xlfn.` prefixes in formulae ** |
|`FS` | | DSV Field Separator override |
|`UTC` | true | If explicitly false, parse text dates in local time |
- Even if `cellNF` is false, formatted text will be generated and saved to `.w`
- In some cases, sheets may be parsed even if `bookSheets` is false.
@ -72,6 +73,9 @@ The read functions accept an options argument:
- By default, "sparse" mode worksheets are generated. Individual cells are
accessed by indexing the worksheet object with an A1-Style address. "dense"
worksheets store cells in an array of arrays at `sheet["!data"]`.
- `UTC` applies to CSV, Text and HTML formats. When explicitly set to `false`,
the parsers will assume the files are specified in local time. By default, as
is the case for other file formats, dates and times are interpreted in UTC.
### Input Type

@ -85,6 +85,8 @@ The write functions accept an options argument:
|`themeXLSX` | | Override theme XML when writing XLSX/XLSB/XLSM ** |
|`ignoreEC` | `true` | Suppress "number as text" errors ** |
|`numbers` | | Payload for NUMBERS export ** |
|`FS` | `","` | "Field Separator" delimiter between fields ** |
|`RS` | `"\n"` | "Record Separator" delimiter between rows ** |
- `bookSST` is slower and more memory intensive, but has better compatibility
with older versions of iOS Numbers
@ -102,6 +104,8 @@ The write functions accept an options argument:
- Due to a bug in the program, some features like "Text to Columns" will crash
Excel on worksheets where error conditions are ignored. The writer will mark
files to ignore the error by default. Set `ignoreEC` to `false` to suppress.
- `FS` and `RS` apply to CSV and Text output formats. The options are discussed
in ["CSV and Text"](/docs/api/utilities/csv#delimiter-separated-output)
<details open>
<summary><b>Exporting NUMBERS files</b> (click to show)</summary>

@ -140,6 +140,9 @@ The function takes an options argument:
|`cellDates` | false | Store dates as type `d` (default is `n`) |
|`sheetStubs` | false | Create cell objects of type `z` for `null` values |
|`nullError` | false | If true, emit `#NULL!` error cells for `null` values |
|`UTC` | false | If true, dates are interpreted using UTC methods ** |
[UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
The following live example reproduces the [example worksheet](#example-sheet):
@ -181,6 +184,9 @@ accepts an options argument:
|`sheetStubs` | false | Create cell objects of type `z` for `null` values |
|`nullError` | false | If true, emit `#NULL!` error cells for `null` values |
|`origin` | | Use specified cell as starting point (see below) |
|`UTC` | false | If true, dates are interpreted using UTC methods ** |
[UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
`origin` is expected to be one of:
@ -272,6 +278,9 @@ default column order is determined by the first appearance of the field using
|`cellDates` | false | Store dates as type `d` (default is `n`) |
|`skipHeader` | false | If true, do not include header row in output |
|`nullError` | false | If true, emit `#NULL!` error cells for `null` values |
|`UTC` | false | If true, dates are interpreted using UTC methods ** |
[UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
:::caution
@ -381,6 +390,9 @@ an options argument:
|`skipHeader` | false | If true, do not include header row in output |
|`nullError` | false | If true, emit `#NULL!` error cells for `null` values |
|`origin` | | Use specified cell as starting point (see below) |
|`UTC` | false | If true, dates are interpreted using UTC methods ** |
[UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
`origin` is expected to be one of:
@ -512,6 +524,7 @@ an options argument:
|`dateNF` | FMT 14 | Use specified date format in string output |
|`defval` | | Use specified value in place of null or undefined |
|`blankrows` | ** | Include blank lines in the output ** |
|`UTC` | false | If true, dates will be correct in UTC ** |
- `raw` only affects cells which have a format code (`.z`) field or a formatted
text (`.w`) field.
@ -527,6 +540,8 @@ an options argument:
be set to `false` to skip blank rows.
- When `header` is not `1`, the default is to skip blank rows. `blankrows` must
be true to generate blank rows
- [UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
`range` is expected to be one of:

@ -97,6 +97,9 @@ Both functions accept options arguments:
|`cellDates` | false | Store dates as type `d` (default is `n`) |
|`sheetRows` | 0 | If >0, read the first `sheetRows` rows of the table |
|`display` | false | If true, hidden rows and cells will not be parsed |
|`UTC` | false | If true, dates are interpreted as UTC ** |
[UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
Exporting a table to a spreadsheet file in the web browser involves 3 steps:
"find the table", "generate a workbook object", and "export to file".
@ -165,6 +168,9 @@ an options argument:
|`cellDates` | false | Store dates as type `d` (default is `n`) |
|`sheetRows` | 0 | If >0, read the first `sheetRows` rows of the table |
|`display` | false | If true, hidden rows and cells will not be parsed |
|`UTC` | false | If true, dates are interpreted as UTC ** |
[UTC option is explained in "Dates"](/docs/csf/features/dates#utc-option)
`origin` is expected to be one of:

@ -26,7 +26,7 @@
"prism-react-renderer": "1.3.5",
"react": "17.0.2",
"react-dom": "17.0.2",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz"
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.4.1"

BIN
docz/static/DateTime.xlsx Normal file

Binary file not shown.

@ -1,3 +1,3 @@
//const version = "0.19.3";
//const version = "0.20.0";
import { version } from "xlsx";
export default version;