From 304ec80d80b3867a6a8fb6e00465b72b0b5705cc Mon Sep 17 00:00:00 2001 From: Kia Lam Date: Thu, 22 Jul 2021 12:12:35 -0400 Subject: [PATCH] Convert dates to use luxon.js --- awx/ui/package-lock.json | 32 +++++++--- awx/ui/package.json | 1 + .../DetailList/NumberSinceDetail.js | 9 +-- .../Schedule/ScheduleAdd/ScheduleAdd.test.js | 30 ++++----- .../Schedule/ScheduleDetail/ScheduleDetail.js | 14 +++-- .../ScheduleEdit/ScheduleEdit.test.js | 34 +++++------ .../Schedule/ScheduleList/ScheduleListItem.js | 2 +- .../ScheduleOccurrences.js | 12 ++-- .../Schedule/shared/ScheduleForm.js | 30 +++++---- .../Schedule/shared/ScheduleForm.test.js | 11 ++-- .../Schedule/shared/buildRuleObj.js | 12 ++-- .../SubscriptionDetail/SubscriptionDetail.js | 11 ++-- .../SubscriptionEdit/SubscriptionModal.js | 7 ++- awx/ui/src/util/dates.js | 61 +++++++------------ awx/ui/src/util/dates.test.js | 45 +++++--------- 15 files changed, 148 insertions(+), 163 deletions(-) diff --git a/awx/ui/package-lock.json b/awx/ui/package-lock.json index 1b9312bf38..806ff65921 100644 --- a/awx/ui/package-lock.json +++ b/awx/ui/package-lock.json @@ -21,6 +21,7 @@ "has-ansi": "4.0.0", "html-entities": "2.3.2", "js-yaml": "^3.13.1", + "luxon": "^2.0.1", "prop-types": "^15.6.2", "react": "^16.13.1", "react-ace": "^9.3.0", @@ -14601,10 +14602,9 @@ } }, "node_modules/luxon": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.26.0.tgz", - "integrity": "sha512-+V5QIQ5f6CDXQpWNICELwjwuHdqeJM1UenlZWx5ujcRMc9venvluCjFb4t5NYLhb6IhkbMVOxzVuOqkgMxee2A==", - "optional": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.0.1.tgz", + "integrity": "sha512-8Eawf81c9ZlQj62W3eq4mp+C7SAIAnmaS7ZuEAiX503YMcn+0C1JnMQRtfaQj6B5qTZLgHv0F4H5WabBCvi1fw==", "engines": { "node": "*" } @@ -21000,6 +21000,15 @@ "luxon": "^1.21.3" } }, + "node_modules/rrule/node_modules/luxon": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -38379,10 +38388,9 @@ } }, "luxon": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.26.0.tgz", - "integrity": "sha512-+V5QIQ5f6CDXQpWNICELwjwuHdqeJM1UenlZWx5ujcRMc9venvluCjFb4t5NYLhb6IhkbMVOxzVuOqkgMxee2A==", - "optional": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.0.1.tgz", + "integrity": "sha512-8Eawf81c9ZlQj62W3eq4mp+C7SAIAnmaS7ZuEAiX503YMcn+0C1JnMQRtfaQj6B5qTZLgHv0F4H5WabBCvi1fw==" }, "magic-string": { "version": "0.25.7", @@ -43724,6 +43732,14 @@ "requires": { "luxon": "^1.21.3", "tslib": "^1.10.0" + }, + "dependencies": { + "luxon": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==", + "optional": true + } } }, "rst-selector-parser": { diff --git a/awx/ui/package.json b/awx/ui/package.json index 114e4526fe..9e981eaf13 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -21,6 +21,7 @@ "has-ansi": "4.0.0", "html-entities": "2.3.2", "js-yaml": "^3.13.1", + "luxon": "^2.0.1", "prop-types": "^15.6.2", "react": "^16.13.1", "react-ace": "^9.3.0", diff --git a/awx/ui/src/components/DetailList/NumberSinceDetail.js b/awx/ui/src/components/DetailList/NumberSinceDetail.js index 66a8e03726..b486525ebf 100644 --- a/awx/ui/src/components/DetailList/NumberSinceDetail.js +++ b/awx/ui/src/components/DetailList/NumberSinceDetail.js @@ -2,7 +2,6 @@ import React from 'react'; import { node, string } from 'prop-types'; import { t } from '@lingui/macro'; import styled from 'styled-components'; -import { formatDateString } from 'util/dates'; import _Detail from './Detail'; const Detail = styled(_Detail)` @@ -10,14 +9,8 @@ const Detail = styled(_Detail)` `; function NumberSinceDetail({ label, number, date, dataCy = null }) { - const dateStr = formatDateString(date); - return ( - + ); } NumberSinceDetail.propTypes = { diff --git a/awx/ui/src/components/Schedule/ScheduleAdd/ScheduleAdd.test.js b/awx/ui/src/components/Schedule/ScheduleAdd/ScheduleAdd.test.js index d3aa6539da..5ce1f547b7 100644 --- a/awx/ui/src/components/Schedule/ScheduleAdd/ScheduleAdd.test.js +++ b/awx/ui/src/components/Schedule/ScheduleAdd/ScheduleAdd.test.js @@ -85,7 +85,7 @@ describe('', () => { interval: 1, name: 'Run once schedule', startDate: '2020-03-25', - startTime: '10:00:00', + startTime: '10:00 AM', timezone: 'America/New_York', }); }); @@ -108,7 +108,7 @@ describe('', () => { name: 'Run every 10 minutes 10 times', occurrences: 10, startDate: '2020-03-25', - startTime: '10:30:00', + startTime: '10:30 AM', timezone: 'America/New_York', }); }); @@ -127,12 +127,12 @@ describe('', () => { description: 'test description', end: 'onDate', endDate: '2020-03-26', - endTime: '10:45:00', + endTime: '10:45 AM', frequency: 'hour', interval: 1, name: 'Run every hour until date', startDate: '2020-03-25', - startTime: '10:45:00', + startTime: '10:45 AM', timezone: 'America/New_York', }); }); @@ -154,7 +154,7 @@ describe('', () => { interval: 1, name: 'Run daily', startDate: '2020-03-25', - startTime: '10:45:00', + startTime: '10:45 AM', timezone: 'America/New_York', }); }); @@ -178,7 +178,7 @@ describe('', () => { name: 'Run weekly on mon/wed/fri', occurrences: 1, startDate: '2020-03-25', - startTime: '10:45:00', + startTime: '10:45 AM', timezone: 'America/New_York', }); }); @@ -201,7 +201,7 @@ describe('', () => { occurrences: 1, runOn: 'day', runOnDayNumber: 1, - startTime: '10:45', + startTime: '10:45 AM', startDate: '2020-04-01', timezone: 'America/New_York', }); @@ -221,7 +221,7 @@ describe('', () => { description: 'test description', end: 'never', endDate: '2020-03-26', - endTime: '11:00:00', + endTime: '11:00 AM', frequency: 'month', interval: 1, name: 'Run monthly on the last Tuesday', @@ -230,7 +230,7 @@ describe('', () => { runOnTheDay: 'tuesday', runOnTheOccurrence: -1, startDate: '2020-03-31', - startTime: '11:00', + startTime: '11:00 AM', timezone: 'America/New_York', }); }); @@ -255,7 +255,7 @@ describe('', () => { runOnDayMonth: 3, runOnDayNumber: 1, startDate: '2020-03-01', - startTime: '00:00', + startTime: '12:00 AM', timezone: 'America/New_York', }); }); @@ -282,7 +282,7 @@ describe('', () => { runOnTheDay: 'friday', runOnTheMonth: 4, startDate: '2020-04-10', - startTime: '11:15', + startTime: '11:15 AM', timezone: 'America/New_York', }); }); @@ -309,7 +309,7 @@ describe('', () => { runOnTheDay: 'weekday', runOnTheMonth: 10, startDate: '2020-04-10', - startTime: '11:15', + startTime: '11:15 AM', timezone: 'America/New_York', }); }); @@ -378,7 +378,7 @@ describe('', () => { name: 'Schedule', end: 'never', endDate: '2021-01-29', - endTime: '14:15:00', + endTime: '2:15 PM', frequency: 'none', occurrences: 1, runOn: 'day', @@ -394,7 +394,7 @@ describe('', () => { { name: 'cred 2', id: 20 }, ], startDate: '2021-01-28', - startTime: '14:15:00', + startTime: '2:15 PM', timezone: 'America/New_York', }); }); @@ -467,7 +467,7 @@ describe('', () => { interval: 1, name: 'Run once schedule', startDate: '2020-03-25', - startTime: '10:00:00', + startTime: '10:00 AM', timezone: 'America/New_York', }); }); diff --git a/awx/ui/src/components/Schedule/ScheduleDetail/ScheduleDetail.js b/awx/ui/src/components/Schedule/ScheduleDetail/ScheduleDetail.js index bff40141e0..fd2d717acb 100644 --- a/awx/ui/src/components/Schedule/ScheduleDetail/ScheduleDetail.js +++ b/awx/ui/src/components/Schedule/ScheduleDetail/ScheduleDetail.js @@ -250,15 +250,21 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) { - - - + + + {hasDaysToKeepField ? ( ) : null} - + ', () => { interval: 1, name: 'Run once schedule', startDate: '2020-03-25', - startTime: '10:00:00', + startTime: '10:00 AM', timezone: 'America/New_York', }); }); @@ -228,7 +224,7 @@ describe('', () => { name: 'Run every 10 minutes 10 times', occurrences: 10, startDate: '2020-03-25', - startTime: '10:30:00', + startTime: '10:30 AM', timezone: 'America/New_York', }); }); @@ -248,12 +244,12 @@ describe('', () => { description: 'test description', end: 'onDate', endDate: '2020-03-26', - endTime: '10:45:00', + endTime: '10:45 AM', frequency: 'hour', interval: 1, name: 'Run every hour until date', startDate: '2020-03-25', - startTime: '10:45:00', + startTime: '10:45 AM', timezone: 'America/New_York', }); }); @@ -276,7 +272,7 @@ describe('', () => { interval: 1, name: 'Run daily', startDate: '2020-03-25', - startTime: '10:45:00', + startTime: '10:45 AM', timezone: 'America/New_York', }); }); @@ -299,7 +295,7 @@ describe('', () => { name: 'Run weekly on mon/wed/fri', occurrences: 1, startDate: '2020-03-25', - startTime: '10:45:00', + startTime: '10:45 AM', timezone: 'America/New_York', }); }); @@ -324,7 +320,7 @@ describe('', () => { runOn: 'day', runOnDayNumber: 1, startDate: '2020-04-01', - startTime: '10:45', + startTime: '10:45 AM', timezone: 'America/New_York', }); }); @@ -352,7 +348,7 @@ describe('', () => { runOnTheDay: 'tuesday', runOnTheOccurrence: -1, startDate: '2020-03-31', - startTime: '11:00', + startTime: '11:00 AM', timezone: 'America/New_York', }); }); @@ -380,7 +376,7 @@ describe('', () => { runOn: 'day', runOnDayMonth: 3, runOnDayNumber: 1, - startTime: '00:00', + startTime: '12:00 AM', startDate: '2020-03-01', timezone: 'America/New_York', }); @@ -408,7 +404,7 @@ describe('', () => { runOnTheOccurrence: 2, runOnTheDay: 'friday', runOnTheMonth: 4, - startTime: '11:15', + startTime: '11:15 AM', startDate: '2020-04-10', timezone: 'America/New_York', }); @@ -437,7 +433,7 @@ describe('', () => { runOnTheOccurrence: 1, runOnTheDay: 'weekday', runOnTheMonth: 10, - startTime: '11:15', + startTime: '11:15 AM', startDate: '2020-04-10', timezone: 'America/New_York', }); @@ -528,7 +524,7 @@ describe('', () => { name: mockSchedule.name, end: 'never', endDate: '2021-01-29', - endTime: '14:15:00', + endTime: '2:15 PM', frequency: 'none', occurrences: 1, runOn: 'day', @@ -539,7 +535,7 @@ describe('', () => { runOnTheOccurrence: 1, skip_tags: '', startDate: '2021-01-28', - startTime: '14:15:00', + startTime: '2:15 PM', timezone: 'America/New_York', credentials: [ { id: 3, name: 'Credential 3', kind: 'ssh', url: '' }, @@ -630,7 +626,7 @@ describe('', () => { name: 'foo', inventory: 702, rrule: - 'DTSTART;TZID=America/New_York:20200402T184500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY', + 'DTSTART;TZID=America/New_York:20200402T144500 RRULE:INTERVAL=1;COUNT=1;FREQ=MINUTELY', }); }); @@ -732,7 +728,7 @@ describe('', () => { interval: 1, name: 'Run once schedule', startDate: '2020-03-25', - startTime: '10:00:00', + startTime: '10:00 AM', timezone: 'America/New_York', }); }); diff --git a/awx/ui/src/components/Schedule/ScheduleList/ScheduleListItem.js b/awx/ui/src/components/Schedule/ScheduleList/ScheduleListItem.js index 4dbed67c12..1b33cf4875 100644 --- a/awx/ui/src/components/Schedule/ScheduleList/ScheduleListItem.js +++ b/awx/ui/src/components/Schedule/ScheduleList/ScheduleListItem.js @@ -103,7 +103,7 @@ function ScheduleListItem({ )} diff --git a/awx/ui/src/components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js b/awx/ui/src/components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js index 209b965ebe..d1c9ad0274 100644 --- a/awx/ui/src/components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js +++ b/awx/ui/src/components/Schedule/ScheduleOccurrences/ScheduleOccurrences.js @@ -1,11 +1,11 @@ import 'styled-components/macro'; import React, { useState } from 'react'; -import { shape } from 'prop-types'; +import { shape, string } from 'prop-types'; import styled from 'styled-components'; import { t } from '@lingui/macro'; import { Split, SplitItem, TextListItemVariants } from '@patternfly/react-core'; -import { formatDateString, formatDateStringUTC } from 'util/dates'; +import { formatDateString } from 'util/dates'; import { DetailName, DetailValue } from '../../DetailList'; import MultiButtonToggle from '../../MultiButtonToggle'; @@ -22,7 +22,7 @@ const OccurrencesLabel = styled.div` } `; -function ScheduleOccurrences({ preview = { local: [], utc: [] } }) { +function ScheduleOccurrences({ preview = { local: [], utc: [] }, tz }) { const [mode, setMode] = useState('local'); if (preview.local.length < 2) { @@ -64,8 +64,8 @@ function ScheduleOccurrences({ preview = { local: [], utc: [] } }) { {preview[mode].map((dateStr) => (
{mode === 'local' - ? formatDateString(dateStr) - : formatDateStringUTC(dateStr)} + ? formatDateString(dateStr, tz) + : formatDateString(dateStr, 'UTC')}
))} @@ -75,10 +75,12 @@ function ScheduleOccurrences({ preview = { local: [], utc: [] } }) { ScheduleOccurrences.propTypes = { preview: shape(), + tz: string, }; ScheduleOccurrences.defaultProps = { preview: { local: [], utc: [] }, + tz: Intl.DateTimeFormat().resolvedOptions().timeZone, }; export default ScheduleOccurrences; diff --git a/awx/ui/src/components/Schedule/shared/ScheduleForm.js b/awx/ui/src/components/Schedule/shared/ScheduleForm.js index e788b84831..782c192b87 100644 --- a/awx/ui/src/components/Schedule/shared/ScheduleForm.js +++ b/awx/ui/src/components/Schedule/shared/ScheduleForm.js @@ -1,6 +1,7 @@ import React, { useEffect, useCallback, useState } from 'react'; import { shape, func } from 'prop-types'; +import { DateTime } from 'luxon'; import { t } from '@lingui/macro'; import { Formik, useField } from 'formik'; import { RRule } from 'rrule'; @@ -190,13 +191,11 @@ function ScheduleForm({ const [isSaveDisabled, setIsSaveDisabled] = useState(false); let rruleError; - const now = new Date(); - const closestQuarterHour = new Date( - Math.ceil(now.getTime() / 900000) * 900000 + const now = DateTime.now(); + const closestQuarterHour = DateTime.fromMillis( + Math.ceil(now.ts / 900000) * 900000 ); - const tomorrow = new Date(closestQuarterHour); - tomorrow.setDate(tomorrow.getDate() + 1); - + const tomorrow = closestQuarterHour.plus({ days: 1 }); const isTemplate = resource.type === 'workflow_job_template' || resource.type === 'job_template'; @@ -376,9 +375,9 @@ function ScheduleForm({ ) { showPromptButton = true; } - const [currentDate, time] = dateToInputDateTime(closestQuarterHour); + const [currentDate, time] = dateToInputDateTime(closestQuarterHour.toISO()); - const [tomorrowDate] = dateToInputDateTime(tomorrow); + const [tomorrowDate] = dateToInputDateTime(tomorrow.toISO()); const initialValues = { daysOfWeek: [], description: schedule.description || '', @@ -448,7 +447,10 @@ function ScheduleForm({ } = RRule.fromString(schedule.rrule.replace(' ', '\n')); if (dtstart) { - const [startDate, startTime] = dateToInputDateTime(schedule.dtstart); + const [startDate, startTime] = dateToInputDateTime( + schedule.dtstart, + schedule.timezone + ); overriddenValues.startDate = startDate; overriddenValues.startTime = startTime; @@ -457,7 +459,10 @@ function ScheduleForm({ if (schedule.until) { overriddenValues.end = 'onDate'; - const [endDate, endTime] = dateToInputDateTime(schedule.until); + const [endDate, endTime] = dateToInputDateTime( + schedule.until, + schedule.timezone + ); overriddenValues.endDate = endDate; overriddenValues.endTime = endTime; @@ -550,7 +555,10 @@ function ScheduleForm({ startDate, } = values; - if (end === 'onDate' && new Date(startDate) >= new Date(endDate)) { + if ( + end === 'onDate' && + DateTime.fromISO(startDate) >= DateTime.fromISO(endDate) + ) { errors.endDate = t`Please select an end date/time that comes after the start date/time.`; } diff --git a/awx/ui/src/components/Schedule/shared/ScheduleForm.test.js b/awx/ui/src/components/Schedule/shared/ScheduleForm.test.js index 288f07cd08..1cb4e0a0b0 100644 --- a/awx/ui/src/components/Schedule/shared/ScheduleForm.test.js +++ b/awx/ui/src/components/Schedule/shared/ScheduleForm.test.js @@ -1,5 +1,6 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; +import { DateTime } from 'luxon'; import { dateToInputDateTime } from 'util/dates'; import { SchedulesAPI, JobTemplatesAPI, InventoriesAPI } from 'api'; @@ -105,7 +106,7 @@ const nonRRuleValuesMatch = () => { wrapper.find('DatePicker[aria-label="Start date"]').prop('value') ).toBe('2020-04-02'); expect(wrapper.find('TimePicker[aria-label="Start time"]').prop('time')).toBe( - '6:45 PM' + '2:45 PM' ); expect(wrapper.find('select#schedule-timezone').prop('value')).toBe( 'America/New_York' @@ -474,11 +475,11 @@ describe('', () => { }); test('initially renders expected fields and values', () => { - const now = new Date(); - const closestQuarterHour = new Date( - Math.ceil(now.getTime() / 900000) * 900000 + const now = DateTime.now(); + const closestQuarterHour = DateTime.fromMillis( + Math.ceil(now.ts / 900000) * 900000 ); - const [date, time] = dateToInputDateTime(closestQuarterHour); + const [date, time] = dateToInputDateTime(closestQuarterHour.toISO()); expect(wrapper.find('ScheduleForm').length).toBe(1); defaultFieldsVisible(); expect(wrapper.find('FormGroup[label="Run every"]').length).toBe(0); diff --git a/awx/ui/src/components/Schedule/shared/buildRuleObj.js b/awx/ui/src/components/Schedule/shared/buildRuleObj.js index 7d6000e701..ae375c672f 100644 --- a/awx/ui/src/components/Schedule/shared/buildRuleObj.js +++ b/awx/ui/src/components/Schedule/shared/buildRuleObj.js @@ -1,14 +1,12 @@ import { t } from '@lingui/macro'; import { RRule } from 'rrule'; +import { DateTime } from 'luxon'; import { getRRuleDayConstants } from 'util/dates'; -const parseTime = (time) => { - const [hour, minute, ampm] = time.split(/[: ]/); - const timeHour = - ampm === 'PM' && hour !== '12' ? `${parseInt(hour, 10) + 12}` : `${hour}`; - - return [timeHour, minute]; -}; +const parseTime = (time) => [ + DateTime.fromFormat(time, 'h:mm a').hour, + DateTime.fromFormat(time, 'h:mm a').minute, +]; export default function buildRuleObj(values) { // Dates are formatted like "YYYY-MM-DD" diff --git a/awx/ui/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js b/awx/ui/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js index 7e52e9d936..83452f5fe8 100644 --- a/awx/ui/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js +++ b/awx/ui/src/screens/Setting/Subscription/SubscriptionDetail/SubscriptionDetail.js @@ -12,11 +12,7 @@ import RoutedTabs from 'components/RoutedTabs'; import { CardBody, CardActionsRow } from 'components/Card'; import { DetailList, Detail, NumberSinceDetail } from 'components/DetailList'; import { useConfig } from 'contexts/Config'; -import { - formatDateString, - formatDateStringUTC, - secondsToDays, -} from 'util/dates'; +import { formatDateString, secondsToDays } from 'util/dates'; function SubscriptionDetail() { const { me = {}, license_info, version } = useConfig(); @@ -98,8 +94,9 @@ function SubscriptionDetail() { label={t`Expires on UTC`} value={ license_info.license_date && - formatDateStringUTC( - new Date(license_info.license_date * 1000).toISOString() + formatDateString( + new Date(license_info.license_date * 1000).toISOString(), + 'UTC' ) } /> diff --git a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js b/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js index 7701017f0a..54ecd612a6 100644 --- a/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js +++ b/awx/ui/src/screens/Setting/Subscription/SubscriptionEdit/SubscriptionModal.js @@ -20,7 +20,7 @@ import { import { ExclamationTriangleIcon } from '@patternfly/react-icons'; import { ConfigAPI } from 'api'; -import { formatDateStringUTC } from 'util/dates'; +import { formatDateString } from 'util/dates'; import useRequest from 'hooks/useRequest'; import useSelected from 'hooks/useSelected'; import ErrorDetail from 'components/ErrorDetail'; @@ -168,8 +168,9 @@ function SubscriptionModal({ {subscription.instance_count} - {formatDateStringUTC( - new Date(subscription.license_date * 1000).toISOString() + {formatDateString( + new Date(subscription.license_date * 1000).toISOString(), + 'UTC' )} diff --git a/awx/ui/src/util/dates.js b/awx/ui/src/util/dates.js index 4c77d883c0..552ebc95d9 100644 --- a/awx/ui/src/util/dates.js +++ b/awx/ui/src/util/dates.js @@ -1,62 +1,43 @@ /* eslint-disable import/prefer-default-export */ import { t } from '@lingui/macro'; import { RRule } from 'rrule'; -import { getLanguage } from './language'; +import { DateTime, Duration } from 'luxon'; -const prependZeros = (value) => value.toString().padStart(2, 0); - -export function formatDateString(dateString, lang = getLanguage(navigator)) { - if (dateString === null) { +export function formatDateString(dateObj, tz = null) { + if (dateObj === null) { return null; } - return new Date(dateString).toLocaleString(lang); -} -export function formatDateStringUTC(dateString, lang = getLanguage(navigator)) { - if (dateString === null) { - return null; - } - return new Date(dateString).toLocaleString(lang, { timeZone: 'UTC' }); + return tz !== null + ? DateTime.fromISO(dateObj, { zone: tz }).toLocaleString( + DateTime.DATETIME_SHORT_WITH_SECONDS + ) + : DateTime.fromISO(dateObj).toLocaleString( + DateTime.DATETIME_SHORT_WITH_SECONDS + ); } export function secondsToHHMMSS(seconds) { - return new Date(seconds * 1000).toISOString().substr(11, 8); + return Duration.fromObject({ seconds }).toFormat('hh:mm:ss'); } export function secondsToDays(seconds) { - let duration = Math.floor(parseInt(seconds, 10) / 86400); - if (duration < 0) { - duration = 0; - } - return duration.toString(); + return Duration.fromObject({ seconds }).toFormat('d'); } export function timeOfDay() { - const date = new Date(); - const hour = date.getHours(); - const minute = prependZeros(date.getMinutes()); - const second = prependZeros(date.getSeconds()); - const time = - hour > 12 - ? `${hour - 12}:${minute}:${second} PM` - : `${hour}:${minute}:${second} AM`; - return time; + const dateTime = DateTime.local(); + return dateTime.toFormat('hh:mm a'); } -export function dateToInputDateTime(dateObj) { - let date = dateObj; - if (typeof dateObj === 'string') { - date = new Date(dateObj); +export function dateToInputDateTime(dt, tz = null) { + let dateTime; + if (tz) { + dateTime = DateTime.fromISO(dt, { zone: tz }); + } else { + dateTime = DateTime.fromISO(dt); } - const year = date.getFullYear(); - const month = prependZeros(date.getMonth() + 1); - const day = prependZeros(date.getDate()); - const hour = - date.getHours() > 12 ? parseInt(date.getHours(), 10) - 12 : date.getHours(); - const minute = prependZeros(date.getMinutes()); - const amPmText = date.getHours() > 11 ? 'PM' : 'AM'; - - return [`${year}-${month}-${day}`, `${hour}:${minute} ${amPmText}`]; + return [dateTime.toFormat('yyyy-LL-dd'), dateTime.toFormat('h:mm a')]; } export function getRRuleDayConstants(dayString) { diff --git a/awx/ui/src/util/dates.test.js b/awx/ui/src/util/dates.test.js index ae62932ac8..fc71d4d85f 100644 --- a/awx/ui/src/util/dates.test.js +++ b/awx/ui/src/util/dates.test.js @@ -2,7 +2,6 @@ import { RRule } from 'rrule'; import { dateToInputDateTime, formatDateString, - formatDateStringUTC, getRRuleDayConstants, secondsToDays, secondsToHHMMSS, @@ -21,35 +20,20 @@ const i18n = { describe('formatDateString', () => { test('it returns the expected value', () => { - const lang = 'en-US'; - expect(formatDateString(null, lang)).toEqual(null); - expect(formatDateString('', lang)).toEqual('Invalid Date'); - expect(formatDateString({}, lang)).toEqual('Invalid Date'); - expect(formatDateString(undefined, lang)).toEqual('Invalid Date'); - expect(formatDateString('foobar', lang)).toEqual('Invalid Date'); - expect(formatDateString('2018-011-31T01:14:52.969227Z', lang)).toEqual( - 'Invalid Date' + expect(formatDateString(null)).toEqual(null); + expect(formatDateString('')).toEqual('Invalid DateTime'); + expect(formatDateString({})).toEqual('Invalid DateTime'); + expect(formatDateString(undefined)).toEqual('Invalid DateTime'); + expect(formatDateString('foobar')).toEqual('Invalid DateTime'); + expect(formatDateString('2018-011-31T01:14:52.969227Z', undefined)).toEqual( + 'Invalid DateTime' ); - expect(formatDateString('2018-01-31T01:14:52.969227Z', lang)).toEqual( - '1/31/2018, 1:14:52 AM' - ); - }); -}); - -describe('formatDateStringUTC', () => { - test('it returns the expected value', () => { - const lang = 'en-US'; - expect(formatDateStringUTC(null, lang)).toEqual(null); - expect(formatDateStringUTC('', lang)).toEqual('Invalid Date'); - expect(formatDateStringUTC({}, lang)).toEqual('Invalid Date'); - expect(formatDateStringUTC(undefined, lang)).toEqual('Invalid Date'); - expect(formatDateStringUTC('foobar', lang)).toEqual('Invalid Date'); - expect(formatDateStringUTC('2018-011-31T01:14:52.969227Z', lang)).toEqual( - 'Invalid Date' - ); - expect(formatDateStringUTC('2018-01-31T01:14:52.969227Z', lang)).toEqual( + expect(formatDateString('2018-01-31T01:14:52.969227Z')).toEqual( '1/31/2018, 1:14:52 AM' ); + expect( + formatDateString('2018-01-31T01:14:52.969227Z', 'America/Los_Angeles') + ).toEqual('1/30/2018, 5:14:52 PM'); }); }); @@ -68,9 +52,10 @@ describe('secondsToHHMMSS', () => { describe('dateToInputDateTime', () => { test('it returns the expected value', () => { - expect( - dateToInputDateTime(new Date('2018-01-31T01:14:52.969227Z')) - ).toEqual(['2018-01-31', '1:14 AM']); + expect(dateToInputDateTime('2018-01-31T01:14:52.969227Z')).toEqual([ + '2018-01-31', + '1:14 AM', + ]); }); });