(core) Fix display of formatted dates in reference columns

Summary:
Reference/referencelist columns displaying date/datetime columns didn't show the formatting of that column, formatting them as ISO instead. One weird effect of this was that opening the editor suddenly changed the format because the editor formatted the dates correctly. You can see this in the checkin doc as an example.

This was discussed in https://grist.slack.com/archives/C0234CPPXPA/p1636482208111800. Here's the main point:

> both use the visible column formatter's formatAny. the editor uses the value from the visible column, which for a date column is a raw timestamp number. the cell display uses the value from the display column which is of type Any so the value is wrapped in a list starting with 'd'. the former gets formatted according to the formatting options, but the latter just gets formatted as ISO.

Probably a good solution to the broader problem is to ensure that the display column has the same type and widget options as the visible column. That seems potentially messy, so I did something easier: fix `DateFormatter` to accept encoded date/datetime objects. It still receives the correct widget options from the visible column as before but can handle the values from the display column. This might also have other uses in the future.

Test Plan:
- Fixed several tests which previously expected the buggy behaviour.
- Converted ValueFormatter.js tests to typescript and cleaned up the existing code slightly.
- Added tests for DateFormatter and DateTimeFormatter to the ValueFormatter test suite, which only tested numbers before.

Reviewers: dsagal

Reviewed By: dsagal

Subscribers: dsagal

Differential Revision: https://phab.getgrist.com/D3190
pull/118/head
Alex Hall 3 years ago
parent c470c4041b
commit 9d62e67369

@ -1,13 +1,14 @@
// tslint:disable:max-classes-per-file
import {CellValue} from 'app/common/DocActions';
import {DocumentSettings} from 'app/common/DocumentSettings';
import * as gristTypes from 'app/common/gristTypes';
import * as gutil from 'app/common/gutil';
import {buildNumberFormat, NumberFormatOptions} from 'app/common/NumberFormat';
import {GristObjCode} from 'app/plugin/GristData';
import {decodeObject, GristDateTime} from 'app/plugin/objtypes';
import isPlainObject = require('lodash/isPlainObject');
import * as moment from 'moment-timezone';
import {DocumentSettings} from 'app/common/DocumentSettings';
import isPlainObject = require('lodash/isPlainObject');
export {PENDING_DATA_PLACEHOLDER} from 'app/plugin/objtypes';
@ -49,7 +50,7 @@ export function formatDecoded(value: unknown, isTopLevel: boolean = true): strin
export type IsRightTypeFunc = (value: CellValue) => boolean;
export class BaseFormatter {
public readonly isRightType: IsRightTypeFunc;
protected isRightType: IsRightTypeFunc;
constructor(public type: string, public widgetOpts: object, public docSettings: DocumentSettings) {
this.isRightType = gristTypes.isRightType(gristTypes.extractTypeFromColType(type)) ||
@ -121,13 +122,40 @@ class DateFormatter extends BaseFormatter {
constructor(type: string, widgetOpts: DateFormatOptions, docSettings: DocumentSettings, timezone: string = 'UTC') {
super(type, widgetOpts, docSettings);
// Allow encoded dates/datetimes ([d, number] or [D, number, timezone])
// which are found in formula columns of type Any,
// particularly reference display columns which are formatted here according to the visible column
// which will have the correct column type and options.
// Since these encoded objects are not expected in a Date/Datetime column and require
// being handled differently from just a number,
// we don't change `gristTypes.isRightType` which is used elsewhere.
this.isRightType = (value: any) => (
value === null ||
typeof value === "number" ||
Array.isArray(value) && (
value[0] === GristObjCode.Date ||
value[0] === GristObjCode.DateTime
)
);
this._dateTimeFormat = widgetOpts.dateFormat || 'YYYY-MM-DD';
this._timezone = timezone;
}
public format(value: any): string {
if (value === null) { return ''; }
const time = moment.tz(value * 1000, this._timezone);
if (value === null) {
return '';
}
// For a DateTime object in an Any column, use the provided timezone (`value[2]`)
// Otherwise use the timezone configured for a DateTime column.
let timezone = this._timezone;
if (Array.isArray(value)) {
timezone = value[2] || timezone;
value = value[1];
}
// Now `value` is a number
const time = moment.tz(value * 1000, timezone);
return time.format(this._dateTimeFormat);
}
}

Loading…
Cancel
Save