From ff03d326884c584785cbcca927ac1475879ece68 Mon Sep 17 00:00:00 2001 From: George Gevoian Date: Tue, 23 May 2023 22:53:20 -0400 Subject: [PATCH] (core) Set DateTime timezone during xlsx import Summary: DateTime columns had a blank timezone after xlsx imports because the timezone was not included in the column type. We now append the document's timezone to the type of all imported DateTime columns. Test Plan: Server test. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3896 --- app/common/RelativeDates.ts | 8 ++++++++ app/server/lib/ActiveDocImport.ts | 11 +++++++++++ test/fixtures/uploads/DateTimeData.xlsx | Bin 0 -> 4759 bytes 3 files changed, 19 insertions(+) create mode 100644 test/fixtures/uploads/DateTimeData.xlsx diff --git a/app/common/RelativeDates.ts b/app/common/RelativeDates.ts index c1d85684..c3012dd0 100644 --- a/app/common/RelativeDates.ts +++ b/app/common/RelativeDates.ts @@ -98,6 +98,14 @@ export function formatRelBounds(periods: IPeriod[]): string { ); } +/** + * Returns a new timestamp that is the UTC equivalent of the original local `timestamp`, offset + * according to the delta between`timezone` and UTC. + */ +export function localTimestampToUTC(timestamp: number, timezone: string): number { + return moment.unix(timestamp).utc().tz(timezone, true).unix(); +} + function formatDay(quantity: number, refUnit: IPeriod['unit']): string { if (refUnit === 'week') { diff --git a/app/server/lib/ActiveDocImport.ts b/app/server/lib/ActiveDocImport.ts index ca4bba85..d257c0b6 100644 --- a/app/server/lib/ActiveDocImport.ts +++ b/app/server/lib/ActiveDocImport.ts @@ -11,6 +11,7 @@ import {ApplyUAResult, DataSourceTransformed, ImportOptions, ImportResult, Impor import {ApiError} from 'app/common/ApiError'; import {BulkColValues, CellValue, fromTableDataAction, UserAction} from 'app/common/DocActions'; import * as gutil from 'app/common/gutil'; +import {localTimestampToUTC} from 'app/common/RelativeDates'; import {DocStateComparison} from 'app/common/UserAPI'; import {guessColInfoForImports} from 'app/common/ValueGuesser'; import {ParseFileResult, ParseOptions} from 'app/plugin/FileParserAPI'; @@ -689,6 +690,7 @@ function getMergeFunction({type}: MergeStrategy): MergeFunction { * columns using the values set for the column ids. * For columns of type Any, guess the type and parse data according to it, or mark as empty * formula columns when they should be empty. + * For columns of type DateTime, add the document timezone to the type. */ function cleanColumnMetadata(columns: GristColumn[], tableData: unknown[][], activeDoc: ActiveDoc) { return columns.map((c, index) => { @@ -706,6 +708,15 @@ function cleanColumnMetadata(columns: GristColumn[], tableData: unknown[][], act Object.assign(newCol, colMetadata); } } + const timezone = activeDoc.docData!.docInfo().timezone; + if (c.type === "DateTime" && timezone) { + newCol.type = `DateTime:${timezone}`; + for (const [i, localTimestamp] of tableData[index].entries()) { + if (typeof localTimestamp !== 'number') { continue; } + + tableData[index][i] = localTimestampToUTC(localTimestamp, timezone); + } + } return newCol; }); } diff --git a/test/fixtures/uploads/DateTimeData.xlsx b/test/fixtures/uploads/DateTimeData.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..d9bc4db4e93b1465bd093c449fae38f52a3fa5e4 GIT binary patch literal 4759 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 fCf)Dc-@wb#|5Dz%Fk+G;bPD_<6W