diff --git a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx index 82186d297d9..de0e55165c9 100644 --- a/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx +++ b/packages/react-core/src/components/CalendarMonth/CalendarMonth.tsx @@ -8,6 +8,7 @@ import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-i import { css } from '@patternfly/react-styles'; import styles from '@patternfly/react-styles/css/components/CalendarMonth/calendar-month'; import { getUniqueId } from '../../helpers/util'; +import { isValidDate } from '../../helpers/datetimeUtils'; export enum Weekday { Sunday = 0, @@ -109,8 +110,6 @@ const buildCalendar = (year: number, month: number, weekStart: number, validator const isSameDate = (d1: Date, d2: Date) => d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate(); -export const isValidDate = (date: Date) => Boolean(date && !isNaN(date as any)); - const today = new Date(); /** The main calendar month component. */ diff --git a/packages/react-core/src/components/DatePicker/DatePicker.tsx b/packages/react-core/src/components/DatePicker/DatePicker.tsx index 8ab88887d78..3b8a915917e 100644 --- a/packages/react-core/src/components/DatePicker/DatePicker.tsx +++ b/packages/react-core/src/components/DatePicker/DatePicker.tsx @@ -6,9 +6,10 @@ import { TextInput, TextInputProps } from '../TextInput/TextInput'; import { Popover, PopoverProps } from '../Popover/Popover'; import { InputGroup } from '../InputGroup/InputGroup'; import OutlinedCalendarAltIcon from '@patternfly/react-icons/dist/esm/icons/outlined-calendar-alt-icon'; -import { CalendarMonth, CalendarFormat, isValidDate } from '../CalendarMonth'; +import { CalendarMonth, CalendarFormat } from '../CalendarMonth'; import { useImperativeHandle } from 'react'; import { KeyTypes } from '../../helpers'; +import { isValidDate } from '../../helpers/datetimeUtils'; /** The main date picker component. */ diff --git a/packages/react-core/src/components/Timestamp/Timestamp.tsx b/packages/react-core/src/components/Timestamp/Timestamp.tsx index da10ddb2483..b1e6a8c0366 100644 --- a/packages/react-core/src/components/Timestamp/Timestamp.tsx +++ b/packages/react-core/src/components/Timestamp/Timestamp.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import styles from '@patternfly/react-styles/css/components/Timestamp/timestamp'; import { css } from '@patternfly/react-styles'; import { Tooltip } from '../Tooltip'; +import { isValidDate } from '../../helpers/datetimeUtils'; export enum TimestampFormat { full = 'full', @@ -78,7 +79,7 @@ export const Timestamp: React.FunctionComponent = ({ children, className, customFormat, - date: dateProp = new Date(), + date: dateProp, dateFormat, displaySuffix = '', is12Hour, @@ -87,6 +88,24 @@ export const Timestamp: React.FunctionComponent = ({ tooltip, ...props }: TimestampProps) => { + const [date, setDate] = React.useState(() => { + const initDate = new Date(dateProp); + if (isValidDate(initDate)) { + return initDate; + } + + return new Date(); + }); + + React.useEffect(() => { + const dateFromProp = new Date(dateProp); + if (isValidDate(dateFromProp) && dateFromProp.toString() !== new Date(date).toString()) { + setDate(dateFromProp); + } else if (!dateProp) { + setDate(new Date()); + } + }, [dateProp]); + const hasTimeFormat = timeFormat && !customFormat; const formatOptions = { ...(dateFormat && !customFormat && { dateStyle: dateFormat }), @@ -94,7 +113,7 @@ export const Timestamp: React.FunctionComponent = ({ ...(is12Hour !== undefined && { hour12: is12Hour }) }; - const dateAsLocaleString = new Date(dateProp).toLocaleString(locale, { + const dateAsLocaleString = new Date(date).toLocaleString(locale, { ...formatOptions, ...(hasTimeFormat && { timeStyle: timeFormat }) }); @@ -102,19 +121,21 @@ export const Timestamp: React.FunctionComponent = ({ const utcTimeFormat = timeFormat !== 'short' ? 'medium' : 'short'; const convertToUTCString = (date: Date) => new Date(date).toUTCString().slice(0, -3); - const utcDateString = new Date(convertToUTCString(dateProp)).toLocaleString(locale, { + const utcDateString = new Date(convertToUTCString(date)).toLocaleString(locale, { ...formatOptions, ...(hasTimeFormat && { timeStyle: utcTimeFormat }) }); const defaultTooltipContent = `${utcDateString}${tooltip?.suffix ? ' ' + tooltip.suffix : ' UTC'}`; + const { dateTime, ...propsWithoutDateTime } = props; + const timestamp = ( - diff --git a/packages/react-core/src/components/Timestamp/__tests__/Timestamp.test.tsx b/packages/react-core/src/components/Timestamp/__tests__/Timestamp.test.tsx index def82f4a8fe..482378b2420 100644 --- a/packages/react-core/src/components/Timestamp/__tests__/Timestamp.test.tsx +++ b/packages/react-core/src/components/Timestamp/__tests__/Timestamp.test.tsx @@ -36,12 +36,31 @@ test('Renders with current date by default with default formatting', () => { expect(screen.getByText(new Date().toLocaleString())).toBeInTheDocument(); }); +test('Renders with correct datetime attribute with current date by default', () => { + render(); + // Because there could be a .001 ms difference in the expected and received datetime value, + // we want an ISO value without the ms to expect as the datetime value. + const isoDateWithoutMS = new Date().toISOString().split('.')[0]; + + expect(screen.getByText(new Date().toLocaleString())).toHaveAttribute( + 'datetime', + expect.stringMatching(isoDateWithoutMS) + ); +}); + test('Renders passed in date with default formatting', () => { render(); expect(screen.getByText('1/1/2022, 12:00:00 AM')).toBeInTheDocument(); }); +test('Renders with correct datetime attribute when date is passed in', () => { + const passedDate = new Date(2022, 0, 1); + render(); + + expect(screen.getByText('1/1/2022, 12:00:00 AM')).toHaveAttribute('datetime', passedDate.toISOString()); +}); + test('Renders with custom formatting when dateFormat and timeFormat are passed in', () => { render( @@ -51,7 +70,7 @@ test('Renders with custom formatting when dateFormat and timeFormat are passed i }); test('Renders with only date when dateFormat is passed in', () => { - render(); + render(); expect(screen.getByText('Saturday, January 1, 2022')).toBeInTheDocument(); }); @@ -152,9 +171,7 @@ test('Renders with pf-m-help-text class when tooltip is passed in with custom va }); test('Renders with default tooltip content for default variant', () => { - render( - - ); + render(); expect(screen.getByText('1/1/2022, 5:00:00 AM UTC')).toBeInTheDocument(); }); @@ -162,7 +179,7 @@ test('Renders with default tooltip content for default variant', () => { test('Renders with custom tooltip suffix for default variant', () => { render( ); diff --git a/packages/react-core/src/components/Timestamp/__tests__/__snapshots__/Timestamp.test.tsx.snap b/packages/react-core/src/components/Timestamp/__tests__/__snapshots__/Timestamp.test.tsx.snap index 57b1ddd0047..6eaa5d032d2 100644 --- a/packages/react-core/src/components/Timestamp/__tests__/__snapshots__/Timestamp.test.tsx.snap +++ b/packages/react-core/src/components/Timestamp/__tests__/__snapshots__/Timestamp.test.tsx.snap @@ -4,7 +4,6 @@ exports[`Matches snapshot 1`] = `