blob: 5101d1123b711f2f5877571d1d9c70187795e9ac [file] [log] [blame]
import {Timestamp} from '../types/common';
/**
* @license
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const Duration = {
HOUR: 1000 * 60 * 60,
DAY: 1000 * 60 * 60 * 24,
};
export function parseDate(dateStr: Timestamp) {
// Timestamps are given in UTC and have the format
// "'yyyy-mm-dd hh:mm:ss.fffffffff'" where "'ffffffffff'" represents
// nanoseconds.
// Munge the date into an ISO 8061 format and parse that.
return new Date(dateStr.replace(' ', 'T') + 'Z');
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isValidDate(date: any): date is Date {
return date instanceof Date && !isNaN(date.valueOf());
}
// similar to fromNow from moment.js
export function fromNow(date: Date, noAgo = false) {
const now = new Date();
const ago = noAgo ? '' : ' ago';
const secondsAgo = Math.round((now.valueOf() - date.valueOf()) / 1000);
if (secondsAgo <= 59) return 'just now';
if (secondsAgo <= 119) return `1 minute${ago}`;
const minutesAgo = Math.round(secondsAgo / 60);
if (minutesAgo <= 59) return `${minutesAgo} minutes${ago}`;
if (minutesAgo === 60) return `1 hour${ago}`;
if (minutesAgo <= 119) return `1 hour ${minutesAgo - 60} min${ago}`;
const hoursAgo = Math.round(minutesAgo / 60);
if (hoursAgo <= 23) return `${hoursAgo} hours${ago}`;
if (hoursAgo === 24) return `1 day${ago}`;
if (hoursAgo <= 47) return `1 day ${hoursAgo - 24} hr${ago}`;
const daysAgo = Math.round(hoursAgo / 24);
if (daysAgo <= 30) return `${daysAgo} days${ago}`;
if (daysAgo <= 60) return `1 month${ago}`;
const monthsAgo = Math.round(daysAgo / 30);
if (monthsAgo <= 11) return `${monthsAgo} months${ago}`;
if (monthsAgo === 12) return `1 year${ago}`;
if (monthsAgo <= 24) return `1 year ${monthsAgo - 12} m${ago}`;
const yearsAgo = Math.round(daysAgo / 365);
return `${yearsAgo} years${ago}`;
}
/**
* Return true if date is within 24 hours and on the same day.
*/
export function isWithinDay(now: Date, date: Date) {
const diff = now.valueOf() - date.valueOf();
return diff < Duration.DAY && date.getDay() === now.getDay();
}
export function wasYesterday(now: Date, date: Date) {
const diff = now.valueOf() - date.valueOf();
// return true if date is withing 24 hours and not on the same day
if (diff < Duration.DAY && date.getDay() !== now.getDay()) return true;
// move now to yesterday
now.setDate(now.getDate() - 1);
return isWithinDay(now, date);
}
/**
* Returns true if date is from one to six months.
*/
export function isWithinHalfYear(now: Date, date: Date) {
const diff = now.valueOf() - date.valueOf();
return diff < 180 * Duration.DAY;
}
interface Options {
month?: string;
year?: string;
day?: string;
hour?: string;
hour12?: boolean;
minute?: string;
second?: string;
}
// TODO(dmfilippov): TS-Fix review this type. All fields here must be optional,
// but this require some changes in the code. During JS->TS migration
// we want to avoid code changes where possible, so for simplicity we
// define it with almost all fields mandatory
interface DateTimeFormatParts {
year: string;
month: string;
day: string;
hour: string;
minute: string;
second: string;
dayPeriod: string;
dayperiod?: string;
// Object can have other properties, but our code doesn't use it
[key: string]: string | undefined;
}
export function formatDate(date: Date, format: string) {
const options: Options = {};
if (format.includes('MM')) {
if (format.includes('MMM')) {
options.month = 'short';
} else {
options.month = '2-digit';
}
}
if (format.includes('YY')) {
if (format.includes('YYYY')) {
options.year = 'numeric';
} else {
options.year = '2-digit';
}
}
if (format.includes('DD')) {
options.day = '2-digit';
}
if (format.includes('HH')) {
options.hour = '2-digit';
options.hour12 = false;
}
if (format.includes('h')) {
options.hour = 'numeric';
options.hour12 = true;
}
if (format.includes('mm')) {
options.minute = '2-digit';
}
if (format.includes('ss')) {
options.second = '2-digit';
}
let locale = 'en-US';
// Workaround for Chrome 80, en-US is using h24 (midnight is 24:00),
// en-GB is using h23 (midnight is 00:00)
if (format.includes('HH')) {
locale = 'en-GB';
}
const dtf = new Intl.DateTimeFormat(locale, options);
const parts = dtf
.formatToParts(date)
.filter(o => o.type !== 'literal')
.reduce((acc, o: Intl.DateTimeFormatPart) => {
acc[o.type] = o.value;
return acc;
}, {} as DateTimeFormatParts);
if (format.includes('YY')) {
if (format.includes('YYYY')) {
format = format.replace('YYYY', parts.year);
} else {
format = format.replace('YY', parts.year);
}
}
if (format.includes('DD')) {
format = format.replace('DD', parts.day);
}
if (format.includes('HH')) {
format = format.replace('HH', parts.hour);
}
if (format.includes('h')) {
format = format.replace('h', parts.hour);
}
if (format.includes('mm')) {
format = format.replace('mm', parts.minute);
}
if (format.includes('ss')) {
format = format.replace('ss', parts.second);
}
if (format.includes('A')) {
if (parts.dayperiod) {
// Workaround for chrome 70 and below
format = format.replace('A', parts.dayperiod.toUpperCase());
} else {
format = format.replace('A', parts.dayPeriod.toUpperCase());
}
}
if (format.includes('MM')) {
if (format.includes('MMM')) {
format = format.replace('MMM', parts.month);
} else {
format = format.replace('MM', parts.month);
}
}
return format;
}
export function utcOffsetString() {
const now = new Date();
const tzo = -now.getTimezoneOffset();
const pad = (num: number) => {
const norm = Math.floor(Math.abs(num));
return (norm < 10 ? '0' : '') + norm.toString();
};
return ` UTC${tzo >= 0 ? '+' : '-'}${pad(tzo / 60)}:${pad(tzo % 60)}`;
}