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 { 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}!` ); }