(core) Converting big number (9 digits or more) to date directly

Summary:
Interpret huge numbers (>8 digits) as timestamps when converting numeric column to date.
Convert date/date time columns to timestamp when converted from numeric/int column.

Test Plan: Updated

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: dsagal, alexmojaki

Differential Revision: https://phab.getgrist.com/D4030
This commit is contained in:
Jarosław Sadziński
2023-10-05 08:48:14 +02:00
parent 498ad07d38
commit ad299f338a
5 changed files with 77 additions and 7 deletions

View File

@@ -11,6 +11,7 @@ import {csvDecodeRow} from 'app/common/csvFormat';
import * as gristTypes from 'app/common/gristTypes';
import {isFullReferencingType} from 'app/common/gristTypes';
import * as gutil from 'app/common/gutil';
import {isNonNullish} from 'app/common/gutil';
import NumberParse from 'app/common/NumberParse';
import {dateTimeWidgetOptions, guessDateFormat, timeFormatOptions} from 'app/common/parseDate';
import {TableData} from 'app/common/TableData';
@@ -134,7 +135,8 @@ export async function prepTransformColInfo(docModel: DocModel, origCol: ColumnRe
if (!dateFormat) {
// Guess date and time format if there aren't any already
const colValues = tableData.getColValues(sourceCol.colId()) || [];
dateFormat = guessDateFormat(colValues.map(String));
const strValues = colValues.map(v => isNonNullish(v) ? String(v) : null);
dateFormat = guessDateFormat(strValues);
widgetOptions = {...widgetOptions, ...(dateTimeWidgetOptions(dateFormat, true))};
}
if (toType === 'DateTime' && !widgetOptions.timeFormat) {

View File

@@ -928,6 +928,13 @@ export function isNonNullish<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
/**
* Ensures that a value is truthy, with a type guard for the return type.
*/
export function truthy<T>(value: T | null | undefined): value is Exclude<T, false | "" | 0> {
return Boolean(value);
}
/**
* Returns the value of both grainjs and knockout observable without creating a dependency.
*/

View File

@@ -108,6 +108,13 @@ export function parseDate(date: string, options: ParseOptions = {}): number | nu
if (!date) {
return null;
}
// If this looks like a timestamp (string with 9 or more digits), just return it.
const timestamp = parseTimeStamp(date);
if (timestamp !== null) {
return timestamp;
}
const dateFormat = options.dateFormat || "YYYY-MM-DD";
const dateFormats = [..._buildVariations(dateFormat, date), ...PARSER_FORMATS];
const cleanDate = date.replace(SEPARATORS, ' ');
@@ -125,11 +132,11 @@ export function parseDate(date: string, options: ParseOptions = {}): number | nu
datetime += ' ' + time + tzOffset;
timeformat = ' HH:mm:ss' + (tzOffset ? 'Z' : '');
}
for (const f of dateFormats) {
const fullFormat = f + timeformat;
for (const format of dateFormats) {
const fullFormat = format + timeformat;
const m = moment.tz(datetime, fullFormat, true, options.timezone || 'UTC');
if (m.isValid()) {
return m.valueOf() / 1000;
return m.unix();
}
}
return null;
@@ -149,6 +156,11 @@ export function parseDateStrict(
if (!date) {
return;
}
// If this looks like a timestamp (string with 9 or more digits), just return it.
const timestamp = parseTimeStamp(date);
if (timestamp !== null) {
return timestamp;
}
dateFormat = dateFormat || "YYYY-MM-DD";
const dateFormats = [..._buildVariations(dateFormat, date), ...UNAMBIGUOUS_FORMATS];
const cleanDate = date.replace(SEPARATORS, ' ').trim();
@@ -419,3 +431,22 @@ export function dateTimeWidgetOptions(fullFormat: string, defaultTimeFormat: boo
isCustomTimeFormat: !timeFormatOptions.includes(timeFormat),
};
}
/**
* Attempts to parse a timestamp string. Returns the timestamp in seconds
* since epoch, or returns null on failure. Accepts only strings with 9 to 11 digits.
* Lowest 11 digit timestamp is 2286-11-20, so we don't consider them valid.
*/
export function parseTimeStamp(date: string): number | null {
// If this looks like a timestamp (number with 9 or more digits), just return it.
// This covers most of the cases leaving some time around the unix epoch not covered.
// So time before 100 000 000 (1974-04-26) is not covered. Also negative values
// are also not supported, as they overlap with the YYYYYY date format.
if (date && /^[1-9]\d{8,9}$/.test(date)) {
const parsedDate = moment(date, 'X');
if (parsedDate.isValid()) {
return parsedDate.unix();
}
}
return null;
}