blob: 05b7fb5cfef43ee6a2f56efa8cd69a0fcb647993 [file] [log] [blame]
/**
* @license
* Copyright 2015 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../gr-tooltip-content/gr-tooltip-content';
import {css, html, LitElement} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
import {
parseDate,
fromNow,
isValidDate,
isWithinDay,
isWithinHalfYear,
formatDate,
utcOffsetString,
wasYesterday,
} from '../../../utils/date-util';
import {TimeFormat, DateFormat} from '../../../constants/constants';
import {assertNever} from '../../../utils/common-util';
import {PreferencesInfo, Timestamp} from '../../../types/common';
import {resolve} from '../../../models/dependency';
import {userModelToken} from '../../../models/user/user-model';
import {subscribe} from '../../lit/subscription-controller';
const TimeFormats = {
TIME_12: 'h:mm A', // 2:14 PM
TIME_12_WITH_SEC: 'h:mm:ss A', // 2:14:00 PM
TIME_24: 'HH:mm', // 14:14
TIME_24_WITH_SEC: 'HH:mm:ss', // 14:14:00
};
const DateFormats = {
STD: {
short: 'MMM DD', // Aug 29
full: 'MMM DD, YYYY', // Aug 29, 1997
},
US: {
short: 'MM/DD', // 08/29
full: 'MM/DD/YY', // 08/29/97
},
ISO: {
short: 'MM-DD', // 08-29
full: 'YYYY-MM-DD', // 1997-08-29
},
EURO: {
short: 'DD. MMM', // 29. Aug
full: 'DD.MM.YYYY', // 29.08.1997
},
UK: {
short: 'DD/MM', // 29/08
full: 'DD/MM/YYYY', // 29/08/1997
},
};
interface DateFormatPair {
short: string;
full: string;
}
declare global {
interface HTMLElementTagNameMap {
'gr-date-formatter': GrDateFormatter;
}
}
@customElement('gr-date-formatter')
export class GrDateFormatter extends LitElement {
@property({type: String})
dateStr: string | undefined = undefined;
@property({type: Boolean})
showDateAndTime = false;
/**
* When true, the detailed date appears in a GR-TOOLTIP rather than in the
* native browser tooltip.
*/
@property({type: Boolean})
withTooltip = false;
@property({type: Boolean})
showYesterday = false;
@property({type: Boolean})
forceRelative = false;
@property({type: Boolean})
relativeOptionNoAgo = false;
@state()
dateFormat?: DateFormatPair;
@state()
timeFormat?: string;
@state()
relative = false;
private readonly getUserModel = resolve(this, userModelToken);
static override get styles() {
return [
css`
host {
color: inherit;
display: inline;
}
`,
];
}
constructor() {
super();
subscribe(
this,
() => this.getUserModel().preferences$,
prefs => this.setPreferences(prefs)
);
}
// private but used by tests
setPreferences(prefs: PreferencesInfo) {
this.decideDateFormat(prefs.date_format);
this.decideTimeFormat(prefs.time_format);
this.relative =
this.forceRelative || Boolean(prefs?.relative_date_in_change_table);
}
override render() {
if (!this.withTooltip) return this.renderDateString();
const tooltip = this.computeFullDateStr();
if (!tooltip) return this.renderDateString();
return html`
<gr-tooltip-content has-tooltip title=${tooltip}>
${this.renderDateString()}
</gr-tooltip-content>
`;
}
private renderDateString() {
return html` <span>${this.computeDateStr()}</span>`;
}
// private but used by tests
getUtcOffsetString() {
return utcOffsetString();
}
private decideTimeFormat(timeFormat: TimeFormat) {
switch (timeFormat) {
case TimeFormat.HHMM_12:
this.timeFormat = TimeFormats.TIME_12;
break;
case TimeFormat.HHMM_24:
this.timeFormat = TimeFormats.TIME_24;
break;
default:
assertNever(timeFormat, `Invalid time format: ${timeFormat}`);
}
}
private decideDateFormat(dateFormat: DateFormat) {
switch (dateFormat) {
case DateFormat.STD:
this.dateFormat = DateFormats.STD;
break;
case DateFormat.US:
this.dateFormat = DateFormats.US;
break;
case DateFormat.ISO:
this.dateFormat = DateFormats.ISO;
break;
case DateFormat.EURO:
this.dateFormat = DateFormats.EURO;
break;
case DateFormat.UK:
this.dateFormat = DateFormats.UK;
break;
default:
assertNever(dateFormat, `Invalid date format: ${dateFormat}`);
}
}
private computeDateStr() {
if (!this.dateStr || !this.timeFormat || !this.dateFormat) {
return '';
}
const date = parseDate(this.dateStr as Timestamp);
if (!isValidDate(date)) {
return '';
}
if (this.relative) {
return fromNow(date, this.relativeOptionNoAgo);
}
const now = new Date();
let format = this.dateFormat.full;
if (isWithinDay(now, date)) {
format = this.timeFormat;
} else if (this.showYesterday && wasYesterday(now, date)) {
return `Yesterday at ${formatDate(date, this.timeFormat)}`;
} else {
if (isWithinHalfYear(now, date)) {
format = this.dateFormat.short;
}
if (this.showDateAndTime) {
format = `${format} ${this.timeFormat}`;
}
}
return formatDate(date, format);
}
private computeFullDateStr(): string {
if (!this.dateStr) return '';
if (!this.timeFormat) return '';
if (!this.dateFormat) return '';
const date = parseDate(this.dateStr as Timestamp);
if (!isValidDate(date)) return '';
const timeFormat =
this.timeFormat === TimeFormats.TIME_12
? TimeFormats.TIME_12_WITH_SEC
: TimeFormats.TIME_24_WITH_SEC;
const format = `dddd, ${this.dateFormat.full}, ${timeFormat}`;
return formatDate(date, format) + this.getUtcOffsetString();
}
}