import { Sentry } from '@audentio/utils/src/Sentry'; import formatTime from 'date-fns/format'; import formatDistance from 'date-fns/formatDistance'; import formatRelative from 'date-fns/formatRelative'; import React, { Component } from 'react'; import { handleTimezone } from './handleTimezone'; import { parseTimestring } from './parseTimestring'; export interface TimestampProps { className?: string; /** * Valid date string or object * new Date() / UNIX time / ISO 8601 / (deprecated) RFC2822 */ children: string | Date; /** * Custom date format * https://date-fns.org/v2.0.0-alpha.6/docs/format * * @example format="MMMM do, yyyy" // (May 7th, 2018) */ format?: string; /** * Output type (overrides format prop) * - **relative**: "4 days ago", "about one year ago" * - **calendar**: "Sunday at 04:30 a.m.", "12/31/2017" * - **absolute**: "January 15th 2018, 5:30 pm" */ output?: 'relative' | 'calendar' | 'absolute'; /** * Format Options * https://date-fns.org/v2.0.0-alpha.6/docs/Options */ formatOptions?: Object; /** * Live update timestamp * defaults to true when output is defined */ liveUpdate?: boolean; /** * Return string without wrapping element * when true, className will have no effect */ noWrap?: boolean; } type FormatOptions = { weekStartsOn?: 0 | 1 | 2 | 3 | 4 | 5 | 6; additionalDigits?: 0 | 1 | 2; locale?: Locale; includeSeconds?: boolean; addSuffix?: boolean; unit?: 's' | 'm' | 'h' | 'd' | 'M' | 'Y'; roundingMethod?: 'floor' | 'ceil' | 'round'; }; /** * Render date string/object as timestamp * * @example Render a relative timestamp with live updates * {new Date('Mon May 03 2018 05:41:46')} * * @example Render timestamp with custom format * Mon May 03 2018 05:41:46 */ export class Timestamp extends Component { _timer: number; static format = (date: string | Date | number, format: string, options?: FormatOptions) => { if (!date) { return null; } return handleTimezone( formatTime(parseTimestring(date), format, { useAdditionalDayOfYearTokens: true, useAdditionalWeekYearTokens: true, ...options, }) ); }; static defaultProps = { format: 'MMMM do yyyy, h:mm a', formatOptions: {}, }; public componentDidMount(): void { const { output, liveUpdate } = this.props; let shouldLiveUpdate = output === 'relative'; if (typeof liveUpdate === 'boolean') { shouldLiveUpdate = liveUpdate; } if (shouldLiveUpdate) { this.startUpdating(); } } public componentWillUnmount(): void { this.stopUpdating(); } startUpdating() { this._timer = window.setInterval(() => { this.forceUpdate(); }, 60 * 1000); } stopUpdating() { clearInterval(this._timer); } format(time: string | Date): string { if (!time) { return null; } try { const { output, format, formatOptions } = this.props; const baseDate = new Date(); if (output) { switch (output) { case 'calendar': return handleTimezone(formatRelative(parseTimestring(time), baseDate, formatOptions)); case 'relative': return formatDistance( parseTimestring(time), baseDate, Object.assign({ addSuffix: true }, formatOptions) ); // absolute by default default: return handleTimezone( formatTime(parseTimestring(time), 'MMMM do yyyy, h:mm a', { useAdditionalDayOfYearTokens: true, useAdditionalWeekYearTokens: true, ...formatOptions, }) ); } } return handleTimezone( formatTime(parseTimestring(time), format, { useAdditionalDayOfYearTokens: true, useAdditionalWeekYearTokens: true, ...formatOptions, }) ); } catch (e) { Sentry.captureException(e); return 'Invalid Date'; } } public render(): React.ReactNode { const { children, className, noWrap, formatOptions } = this.props; let time_abs; try { time_abs = formatTime(parseTimestring(children), 'MMMM do yyyy, h:mm a', { useAdditionalDayOfYearTokens: true, useAdditionalWeekYearTokens: true, ...formatOptions, }); } catch (e) { time_abs = 'Invalid Date'; } if (noWrap) { return this.format(children); } return ( {this.format(children)} ); } }