mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
153 lines
4.8 KiB
TypeScript
153 lines
4.8 KiB
TypeScript
|
import {
|
||
|
CURRENT_DATE, diffUnit, formatRelBounds, IPeriod, IRelativeDateSpec, isEquivalentRelativeDate, toUnixTimestamp
|
||
|
} from "app/common/RelativeDates";
|
||
|
import { IRangeBoundType, isRelativeBound } from "app/common/FilterState";
|
||
|
import getCurrentTime from "app/common/getCurrentTime";
|
||
|
import moment from "moment-timezone";
|
||
|
|
||
|
export const DEPS = {getCurrentTime};
|
||
|
|
||
|
export interface IRelativeDateOption {
|
||
|
label: string;
|
||
|
value: number|IRelativeDateSpec;
|
||
|
}
|
||
|
|
||
|
const DEFAULT_OPTION_LIST: IRelativeDateSpec[] = [
|
||
|
CURRENT_DATE, [{
|
||
|
quantity: -3,
|
||
|
unit: 'day',
|
||
|
}], [{
|
||
|
quantity: -7,
|
||
|
unit: 'day',
|
||
|
}], [{
|
||
|
quantity: -30,
|
||
|
unit: 'day',
|
||
|
}], [{
|
||
|
quantity: 0,
|
||
|
unit: 'year',
|
||
|
}], [{
|
||
|
quantity: 3,
|
||
|
unit: 'day',
|
||
|
}], [{
|
||
|
quantity: 7,
|
||
|
unit: 'day',
|
||
|
}], [{
|
||
|
quantity: 30,
|
||
|
unit: 'day',
|
||
|
}], [{
|
||
|
quantity: 0,
|
||
|
unit: 'year',
|
||
|
endOf: true,
|
||
|
}]];
|
||
|
|
||
|
|
||
|
export function relativeDatesOptions(value: IRangeBoundType, valueFormatter: (val: any) => string
|
||
|
): Array<{label: string, spec: IRangeBoundType}> {
|
||
|
return relativeDateOptionsSpec(value)
|
||
|
.map((spec) => ({spec, label: formatBoundOption(spec, valueFormatter)}));
|
||
|
}
|
||
|
|
||
|
// Returns a list of different relative date spec that all match passed in date value. If value is
|
||
|
// undefined it returns a default list of spec meant to showcase user the different flavors of
|
||
|
// relative date.
|
||
|
function relativeDateOptionsSpec(value: IRangeBoundType): Array<IRangeBoundType> {
|
||
|
|
||
|
if (value === undefined) {
|
||
|
return DEFAULT_OPTION_LIST;
|
||
|
} else if (isRelativeBound(value)) {
|
||
|
value = toUnixTimestamp(value);
|
||
|
}
|
||
|
|
||
|
const date = moment.utc(value * 1000);
|
||
|
const res: IRangeBoundType[] = [value];
|
||
|
|
||
|
let relDate = getMatchingDoubleRelativeDate(value, {unit: 'day'});
|
||
|
if (Math.abs(relDate[0].quantity) <= 90) {
|
||
|
res.push(relDate);
|
||
|
}
|
||
|
|
||
|
relDate = getMatchingDoubleRelativeDate(value, {unit: 'week'});
|
||
|
if (Math.abs(relDate[0].quantity) <= 4) {
|
||
|
res.push(relDate);
|
||
|
}
|
||
|
|
||
|
// any day of the month (with longer limit for 1st day of the month)
|
||
|
relDate = getMatchingDoubleRelativeDate(value, {unit: 'month'});
|
||
|
if (Math.abs(relDate[0].quantity) <= (date.date() === 1 ? 12 : 3)) {
|
||
|
res.push(relDate);
|
||
|
}
|
||
|
|
||
|
// If date is 1st of Jan show 1st day of year options
|
||
|
if (date.date() === 1 && date.month() === 0) {
|
||
|
res.push(getMatchingDoubleRelativeDate(value, {unit: 'year'}));
|
||
|
}
|
||
|
|
||
|
// 31st of Dec
|
||
|
if (date.date() === 31 && date.month() === 11) {
|
||
|
res.push(getMatchingDoubleRelativeDate(value, {unit: 'year', endOf: true}));
|
||
|
}
|
||
|
|
||
|
// Last day of any month
|
||
|
if (date.clone().endOf('month').date() === date.date()) {
|
||
|
relDate = getMatchingDoubleRelativeDate(value, {unit: 'month', endOf: true});
|
||
|
if (Math.abs(relDate[0].quantity) < 12) {
|
||
|
res.push(relDate);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
function now(): moment.Moment {
|
||
|
const m = DEPS.getCurrentTime();
|
||
|
return moment.utc([m.year(), m.month(), m.date()]);
|
||
|
}
|
||
|
|
||
|
// Returns a relative date spec as a sequence of one or two IPeriod that allows to match dateValue
|
||
|
// starting from the current date. The first period has .unit, .startOf and .endOf set according to
|
||
|
// passed in option.
|
||
|
export function getMatchingDoubleRelativeDate(
|
||
|
dateValue: number,
|
||
|
option: {unit: 'day'|'week'|'month'|'year', endOf?: boolean}
|
||
|
): IPeriod[] {
|
||
|
const {unit} = option;
|
||
|
const date = moment.utc(dateValue * 1000);
|
||
|
const dateNow = now();
|
||
|
const quantity = diffUnit(date, dateNow.clone(), unit);
|
||
|
const m = dateNow.clone().add(quantity, unit);
|
||
|
if (option.endOf) { m.endOf(unit); m.startOf('day'); }
|
||
|
else { m.startOf(unit); }
|
||
|
const dayQuantity = diffUnit(date, m, 'day');
|
||
|
const res = [{quantity, ...option}];
|
||
|
// Only add a 2nd period when it is not moot.
|
||
|
if (dayQuantity) { res.push({quantity: dayQuantity, unit: 'day'}); }
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
export function formatBoundOption(bound: IRangeBoundType, valueFormatter: (val: any) => string): string {
|
||
|
return isRelativeBound(bound) ? formatRelBounds(bound) : valueFormatter(bound);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Update relativeDate to match the new date picked by user.
|
||
|
export function updateRelativeDate(relativeDate: IRelativeDateSpec, date: number): IRelativeDateSpec|number {
|
||
|
const periods = Array.isArray(relativeDate) ? relativeDate : [relativeDate];
|
||
|
|
||
|
if ([1, 2].includes(periods.length)) {
|
||
|
const {unit, endOf} = periods[0];
|
||
|
const relDate = getMatchingDoubleRelativeDate(date, {unit, endOf});
|
||
|
|
||
|
// Returns the relative date only if it is one of the suggested relative dates, otherwise
|
||
|
// returns the absolute date.
|
||
|
const options = relativeDateOptionsSpec(date);
|
||
|
if (options.find(opt => isRelativeBound(opt) && isEquivalentRelativeDate(opt, relDate))) {
|
||
|
return relDate;
|
||
|
}
|
||
|
return date;
|
||
|
}
|
||
|
|
||
|
throw new Error(
|
||
|
`Relative date spec does only support 1 or 2 periods, got ${periods.length}!`
|
||
|
);
|
||
|
}
|