mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(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:
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user