Browse Source

Replace Moment -> Luxon for improved date handling

This commit replaces Moment.js with Luxon in our codebase. Luxon offers
a more modern and comprehensive API for date and time manipulation, and
it's also more lightweight and faster than Moment.js. This change will
help us improve performance and maintainability of our date/time related
code.
Sergiu Miclea 2 years ago
parent
commit
916ec034fa
35 changed files with 441 additions and 413 deletions
  1. 2 1
      package.json
  2. 1 1
      src/@types/Endpoint.ts
  3. 3 3
      src/@types/Execution.ts
  4. 3 3
      src/@types/Task.ts
  5. 14 15
      src/components/modules/AssessmentModule/AssessmentDetailsContent/AssessmentDetailsContent.tsx
  6. 13 12
      src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx
  7. 14 12
      src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx
  8. 15 16
      src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx
  9. 5 5
      src/components/modules/EndpointModule/EndpointListItem/EndpointListItem.tsx
  10. 13 14
      src/components/modules/LicenceModule/LicenceModule.tsx
  11. 9 9
      src/components/modules/MetalHubModule/MetalHubListItem/MetalHubListItem.tsx
  12. 13 10
      src/components/modules/MetalHubModule/MetalHubServerDetailsContent/MetalHubServerDetailsContent.tsx
  13. 8 5
      src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents.tsx
  14. 16 10
      src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx
  15. 13 16
      src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx
  16. 10 10
      src/components/modules/MinionModule/MinionPoolListItem/MinionPoolListItem.tsx
  17. 10 11
      src/components/modules/TransferModule/Executions/Executions.tsx
  18. 15 17
      src/components/modules/TransferModule/MainDetails/MainDetails.tsx
  19. 12 12
      src/components/modules/TransferModule/Schedule/Schedule.tsx
  20. 29 31
      src/components/modules/TransferModule/ScheduleItem/ScheduleItem.tsx
  21. 12 12
      src/components/modules/TransferModule/TaskItem/TaskItem.tsx
  22. 6 5
      src/components/modules/TransferModule/Timeline/Timeline.tsx
  23. 9 9
      src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx
  24. 16 17
      src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx
  25. 27 17
      src/components/smart/LogsPage/DownloadsContent.tsx
  26. 29 30
      src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx
  27. 7 8
      src/components/ui/DatetimePicker/DatetimePicker.spec.tsx
  28. 32 29
      src/components/ui/DatetimePicker/DatetimePicker.tsx
  29. 4 5
      src/sources/AzureSource.ts
  30. 8 8
      src/sources/EndpointSource.ts
  31. 15 17
      src/sources/MigrationSource.ts
  32. 18 19
      src/sources/ScheduleSource.ts
  33. 15 18
      src/utils/DateUtils.ts
  34. 9 5
      tests/testRelease.js
  35. 16 1
      yarn.lock

+ 2 - 1
package.json

@@ -30,6 +30,7 @@
     "@types/file-saver": "^2.0.1",
     "@types/jest": "^27.0.2",
     "@types/js-cookie": "^2.2.6",
+    "@types/luxon": "^3.3.2",
     "@types/moment-timezone": "^0.5.13",
     "@types/react": "^16.13.1",
     "@types/react-collapse": "^5.0.0",
@@ -79,9 +80,9 @@
     "js-cookie": "^2.2.1",
     "jszip": "^3.8.0",
     "lodash": "^4.17.19",
+    "luxon": "^3.4.3",
     "mobx": "^5.15.4",
     "mobx-react": "^6.2.2",
-    "moment": "^2.29.4",
     "moment-timezone": "^0.5.35",
     "ms-rest-azure": "^2.4.5",
     "path": "^0.12.7",

+ 1 - 1
src/@types/Endpoint.ts

@@ -25,7 +25,7 @@ export type Endpoint = {
   name: string;
   description: string;
   type: ProviderTypes;
-  created_at: Date;
+  created_at: string;
   mapped_regions: string[];
   connection_info: {
     secret_ref?: string;

+ 3 - 3
src/@types/Execution.ts

@@ -18,9 +18,9 @@ export type Execution = {
   id: string;
   number: number;
   status: string;
-  created_at: Date;
-  updated_at: Date;
-  deleted_at?: Date;
+  created_at: string;
+  updated_at: string;
+  deleted_at?: string;
   type:
     | "replica_execution"
     | "replica_disks_delete"

+ 3 - 3
src/@types/Task.ts

@@ -15,7 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 export type ProgressUpdate = {
   index: number;
   message: string;
-  created_at: Date;
+  created_at: string;
   total_steps: number | null;
   current_step: number | null;
 };
@@ -23,8 +23,8 @@ export type ProgressUpdate = {
 export type Task = {
   id: string;
   status: string;
-  created_at: Date;
-  updated_at: Date;
+  created_at: string;
+  updated_at: string;
   progress_updates: ProgressUpdate[];
   task_type: string;
   instance: string;

+ 14 - 15
src/components/modules/AssessmentModule/AssessmentDetailsContent/AssessmentDetailsContent.tsx

@@ -12,30 +12,29 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { observer } from "mobx-react";
 import React from "react";
 import styled, { css } from "styled-components";
-import moment from "moment";
-import { observer } from "mobx-react";
 
+import AssessedVmListItem from "@src/components/modules/AssessmentModule/AssessedVmListItem";
 import DetailsNavigation from "@src/components/modules/NavigationModule/DetailsNavigation";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import Button from "@src/components/ui/Button";
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
-import Table from "@src/components/ui/Table";
-import AssessedVmListItem from "@src/components/modules/AssessmentModule/AssessedVmListItem";
-import DropdownFilter from "@src/components/ui/Dropdowns/DropdownFilter";
 import Checkbox from "@src/components/ui/Checkbox";
+import DropdownFilter from "@src/components/ui/Dropdowns/DropdownFilter";
+import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
 import SmallLoading from "@src/components/ui/SmallLoading";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
+import Table from "@src/components/ui/Table";
+import DateUtils from "@src/utils/DateUtils";
+
+import arrowImage from "./images/arrow.svg";
+import azureMigrateImage from "./images/logo.svg";
 
 import type { Assessment, VmItem, AzureLocation } from "@src/@types/Assessment";
 import type { Endpoint } from "@src/@types/Endpoint";
 import type { Instance, Nic } from "@src/@types/Instance";
 import type { Network, NetworkMap } from "@src/@types/Network";
-
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import azureMigrateImage from "./images/logo.svg";
-import arrowImage from "./images/arrow.svg";
-
 const Wrapper = styled.div<any>`
   display: flex;
   justify-content: center;
@@ -264,9 +263,9 @@ class AssessmentDetailsContent extends React.Component<Props> {
               <Label>Last Update</Label>
               <Value>
                 {this.props.item
-                  ? moment(this.props.item.properties.updatedTimestamp).format(
-                      "YYYY-MM-DD HH:mm:ss"
-                    )
+                  ? DateUtils.getLocalDate(
+                      this.props.item.properties.updatedTimestamp
+                    ).toFormat("yyyy-LL-dd HH:mm:ss")
                   : "-"}
               </Value>
             </Field>

+ 13 - 12
src/components/modules/DashboardModule/DashboardExecutions/DashboardExecutions.tsx

@@ -12,18 +12,18 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import * as React from "react";
+import { DateTime, Duration } from "luxon";
 import { observer } from "mobx-react";
+import * as React from "react";
 import styled from "styled-components";
-import moment from "moment";
 
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
+import { MigrationItem, ReplicaItem, TransferItem } from "@src/@types/MainItem";
 import DashboardBarChart from "@src/components/modules/DashboardModule/DashboardBarChart";
-
 import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
+import DateUtils from "@src/utils/DateUtils";
 
-import { ReplicaItem, MigrationItem, TransferItem } from "@src/@types/MainItem";
 import emptyBackgroundImage from "./images/empty-background.svg";
 
 const INTERVALS = [
@@ -173,9 +173,9 @@ class DashboardExecutions extends React.Component<Props, State> {
 
     const periodUnit: any = this.state.selectedPeriod.split("-")[1];
     const periodValue: any = Number(this.state.selectedPeriod.split("-")[0]);
-    const oldestDate: Date = moment()
-      .subtract(periodValue, periodUnit)
-      .toDate();
+    const oldestDate: Date = DateTime.local()
+      .minus(Duration.fromObject({ [periodUnit]: periodValue }))
+      .toJSDate();
     creations = creations.filter(
       e => new Date(e.created_at).getTime() >= oldestDate.getTime()
     );
@@ -193,11 +193,12 @@ class DashboardExecutions extends React.Component<Props, State> {
       [period: string]: { replicas: number; migrations: number };
     } = {};
     transferItems.forEach(item => {
-      const date = moment(new Date(item.created_at));
+      const date = DateUtils.getUtcDate(item.created_at);
       const period: string =
         periodUnit === "days"
-          ? date.format("DD-MMM-YYYY_DD MMMM")
-          : date.format("MMM-YYYY_MMMM YYYY");
+          ? date.toFormat("dd-LLL-yyyy_dd LLLL")
+          : date.toFormat("LLL-yyyy_LLLL yyyy");
+
       if (!periods[period]) {
         periods[period] = { replicas: 0, migrations: 0 };
       }

+ 14 - 12
src/components/modules/DashboardModule/DashboardLicence/DashboardLicence.tsx

@@ -12,23 +12,20 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import * as React from "react";
 import { observer } from "mobx-react";
+import * as React from "react";
 import styled from "styled-components";
-import moment from "moment";
-
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import InfoIcon from "@src/components/ui/InfoIcon";
 
+import licenceImage from "@src/components/modules/LicenceModule/images/licence";
 import { ThemePalette, ThemeProps } from "@src/components/Theme";
-
-import type { Licence, LicenceServerStatus } from "@src/@types/Licence";
 import Button from "@src/components/ui/Button";
-
-import licenceImage from "@src/components/modules/LicenceModule/images/licence";
 import CopyMultineValue from "@src/components/ui/CopyMultilineValue";
+import InfoIcon from "@src/components/ui/InfoIcon";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
+import DateUtils from "@src/utils/DateUtils";
 import ObjectUtils from "@src/utils/ObjectUtils";
 
+import type { Licence, LicenceServerStatus } from "@src/@types/Licence";
 const Wrapper = styled.div<any>`
   flex-grow: 1;
 `;
@@ -217,16 +214,21 @@ class DashboardLicence extends React.Component<Props> {
         },
       ],
     ];
-    const expirationData = moment(info.earliestLicenceExpiryDate);
+    const expirationData = DateUtils.getLocalDate(
+      info.earliestLicenceExpiryDate
+    );
     return (
       <LicenceInfo>
         <TopInfo>
           <TopInfoText>Expires on</TopInfoText>
           <TopInfoDate>
             <TopInfoDateTop>
-              {expirationData.format("MMM")} &#39;{expirationData.format("YY")}
+              {expirationData.toFormat("LLL")} &#39;
+              {expirationData.toFormat("yy")}
             </TopInfoDateTop>
-            <TopInfoDateBottom>{expirationData.format("DD")}</TopInfoDateBottom>
+            <TopInfoDateBottom>
+              {expirationData.toFormat("dd")}
+            </TopInfoDateBottom>
           </TopInfoDate>
         </TopInfo>
         <Charts>

+ 15 - 16
src/components/modules/EndpointModule/EndpointDetailsContent/EndpointDetailsContent.tsx

@@ -12,33 +12,32 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { observer } from "mobx-react";
 import * as React from "react";
 import { Link } from "react-router-dom";
-import { observer } from "mobx-react";
 import styled from "styled-components";
 
-import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
-import PasswordValue from "@src/components/ui/PasswordValue";
-import Button from "@src/components/ui/Button";
-import CopyValue from "@src/components/ui/CopyValue";
-import CopyMultilineValue from "@src/components/ui/CopyMultilineValue";
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-
-import type { Endpoint } from "@src/@types/Endpoint";
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import DateUtils from "@src/utils/DateUtils";
-import LabelDictionary from "@src/utils/LabelDictionary";
-import configLoader from "@src/utils/Config";
-import { Region } from "@src/@types/Region";
+import { Field as FieldType } from "@src/@types/Field";
 import {
   getTransferItemTitle,
   MigrationItem,
   ReplicaItem,
   TransferItem,
 } from "@src/@types/MainItem";
-import { Field as FieldType } from "@src/@types/Field";
+import { Region } from "@src/@types/Region";
+import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import Button from "@src/components/ui/Button";
+import CopyMultilineValue from "@src/components/ui/CopyMultilineValue";
+import CopyValue from "@src/components/ui/CopyValue";
+import PasswordValue from "@src/components/ui/PasswordValue";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
+import configLoader from "@src/utils/Config";
+import DateUtils from "@src/utils/DateUtils";
 import DomUtils from "@src/utils/DomUtils";
+import LabelDictionary from "@src/utils/LabelDictionary";
 
+import type { Endpoint } from "@src/@types/Endpoint";
 const Wrapper = styled.div<any>`
   ${ThemeProps.exactWidth(ThemeProps.contentWidth)}
   margin: 0 auto;
@@ -290,7 +289,7 @@ class EndpointDetailsContent extends React.Component<Props> {
           <Field>
             <Label>Created</Label>
             {this.renderValue(
-              DateUtils.getLocalTime(createdAt).format("DD/MM/YYYY HH:mm")
+              DateUtils.getLocalDate(createdAt!).toFormat("dd/LL/yyyy HH:mm")
             )}
           </Field>
           <Field>

+ 5 - 5
src/components/modules/EndpointModule/EndpointListItem/EndpointListItem.tsx

@@ -12,18 +12,18 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
-import { observer } from "mobx-react";
 
-import type { Endpoint } from "@src/@types/Endpoint";
-import Checkbox from "@src/components/ui/Checkbox";
 import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
 import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import Checkbox from "@src/components/ui/Checkbox";
 import DateUtils from "@src/utils/DateUtils";
 
 import endpointImage from "./images/endpoint.svg";
 
+import type { Endpoint } from "@src/@types/Endpoint";
 const CheckboxStyled = styled(Checkbox)`
   opacity: ${props => (props.checked ? 1 : 0)};
   transition: all ${ThemeProps.animations.swift};
@@ -119,8 +119,8 @@ class EndpointListItem extends React.Component<Props> {
           <Created>
             <ItemLabel>Created</ItemLabel>
             <ItemValue>
-              {DateUtils.getLocalTime(this.props.item.created_at).format(
-                "DD MMMM YYYY, HH:mm"
+              {DateUtils.getLocalDate(this.props.item.created_at).toFormat(
+                "dd LLLL yyyy, HH:mm"
               )}
             </ItemValue>
           </Created>

+ 13 - 14
src/components/modules/LicenceModule/LicenceModule.tsx

@@ -12,27 +12,25 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import * as React from "react";
+import { DateTime } from "luxon";
 import { observer } from "mobx-react";
+import * as React from "react";
 import styled, { css } from "styled-components";
-import moment from "moment";
 
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import Button from "@src/components/ui/Button";
+import CopyValue from "@src/components/ui/CopyValue";
 import LoadingButton from "@src/components/ui/LoadingButton";
+import OpenInNewIcon from "@src/components/ui/OpenInNewIcon";
 import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
 import TextArea from "@src/components/ui/TextArea";
-import CopyValue from "@src/components/ui/CopyValue";
-
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import { LEGAL_URLS } from "@src/constants";
+import DateUtils from "@src/utils/DateUtils";
 import FileUtils from "@src/utils/FileUtils";
 
-import type { Licence, LicenceServerStatus } from "@src/@types/Licence";
-
-// import OpenInNewIcon from '@src/components/ui/OpenInNewIcon'
-import OpenInNewIcon from "@src/components/ui/OpenInNewIcon";
-import { LEGAL_URLS } from "@src/constants";
 import licenceImage from "./images/licence";
 
+import type { Licence, LicenceServerStatus } from "@src/@types/Licence";
 const Wrapper = styled.div<any>`
   min-height: 0;
   overflow: auto;
@@ -277,12 +275,13 @@ class LicenceModule extends React.Component<Props, State> {
   }
 
   renderExpiration(date: Date) {
-    const dateMoment = moment(date);
-    const days = dateMoment.diff(new Date(), "days");
+    const dateLuxon = DateUtils.getLocalDate(date);
+    const now = DateTime.local();
+    const days = Math.floor(dateLuxon.diff(now, "days").days);
     if (days === 0) {
       return (
         <span>
-          today at <b>{dateMoment.utc().format("HH:mm")} UTC</b>
+          today at <b>{dateLuxon.toUTC().toFormat("HH:mm")} UTC</b>
         </span>
       );
     }
@@ -290,7 +289,7 @@ class LicenceModule extends React.Component<Props, State> {
       <span>
         on{" "}
         <b>
-          {dateMoment.format("DD MMM YYYY")} ({days} days from now)
+          {dateLuxon.toFormat("dd LLL yyyy")} ({days} days from now)
         </b>
       </span>
     );

+ 9 - 9
src/components/modules/MetalHubModule/MetalHubListItem/MetalHubListItem.tsx

@@ -12,17 +12,17 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
-import { observer } from "mobx-react";
-
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
 
 import { MetalHubServer } from "@src/@types/MetalHub";
-import moment from "moment";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import Checkbox from "@src/components/ui/Checkbox";
 import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
+import DateUtils from "@src/utils/DateUtils";
+
 import serverImage from "./images/server.svg";
-import Checkbox from "@src/components/ui/Checkbox";
 
 const CheckboxStyled = styled(Checkbox)`
   opacity: ${props => (props.checked ? 1 : 0)};
@@ -136,16 +136,16 @@ class MetalHubServerListItem extends React.Component<Props> {
             <Data width={145}>
               <ItemLabel>Created At</ItemLabel>
               <ItemValue>
-                {moment(this.props.item.created_at).format(
-                  "YYYY-MM-DD HH:mm:ss"
+                {DateUtils.getLocalDate(this.props.item.created_at).toFormat(
+                  "yyyy-LL-dd HH:mm:ss"
                 )}
               </ItemValue>
             </Data>
             <Data width={145}>
               <ItemLabel>Updated At</ItemLabel>
               <ItemValue>
-                {moment(this.props.item.updated_at).format(
-                  "YYYY-MM-DD HH:mm:ss"
+                {DateUtils.getLocalDate(this.props.item.updated_at).toFormat(
+                  "yyyy-LL-dd HH:mm:ss"
                 )}
               </ItemValue>
             </Data>

+ 13 - 10
src/components/modules/MetalHubModule/MetalHubServerDetailsContent/MetalHubServerDetailsContent.tsx

@@ -12,16 +12,11 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from "react";
 import { observer } from "mobx-react";
+import React from "react";
+import { Collapse } from "react-collapse";
 import styled from "styled-components";
-import moment from "moment";
-
-import CopyValue from "@src/components/ui/CopyValue";
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import Button from "@src/components/ui/Button";
 
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import {
   MetalHubDisk,
   MetalHubNic,
@@ -39,9 +34,13 @@ import {
   RowHeader,
   RowHeaderColumn,
 } from "@src/components/modules/TransferModule/TransferDetailsTable";
-import { Collapse } from "react-collapse";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import Button from "@src/components/ui/Button";
+import CopyValue from "@src/components/ui/CopyValue";
 import LoadingButton from "@src/components/ui/LoadingButton";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
 import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
+import DateUtils from "@src/utils/DateUtils";
 
 const Wrapper = styled.div`
   ${ThemeProps.exactWidth(ThemeProps.contentWidth)}
@@ -229,13 +228,17 @@ class MetalHubServerDetailsContent extends React.Component<Props, State> {
         <Field>
           <Label>Created At</Label>
           <Value>
-            {moment(server.created_at).format("YYYY-MM-DD HH:mm:ss")}
+            {DateUtils.getLocalDate(server.created_at).toFormat(
+              "yyyy-LL-dd HH:mm:ss"
+            )}
           </Value>
         </Field>
         <Field>
           <Label>Updated At</Label>
           <Value>
-            {moment(server.updated_at).format("YYYY-MM-DD HH:mm:ss")}
+            {DateUtils.getLocalDate(server.updated_at).toFormat(
+              "yyyy-LL-dd HH:mm:ss"
+            )}
           </Value>
         </Field>
         <Field>

+ 8 - 5
src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolEvents.tsx

@@ -13,18 +13,19 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 import * as React from "react";
-import moment from "moment";
 import styled from "styled-components";
+
 import {
   MinionPoolDetails,
   MinionPoolEventProgressUpdate,
 } from "@src/@types/MinionPool";
 import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
-import Pagination from "@src/components/ui/Pagination";
-import configLoader from "@src/utils/Config";
 import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
 import InfoIcon from "@src/components/ui/InfoIcon";
+import Pagination from "@src/components/ui/Pagination";
+import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
+import configLoader from "@src/utils/Config";
+import DateUtils from "@src/utils/DateUtils";
 
 const Wrapper = styled.div``;
 const Filters = styled.div`
@@ -242,7 +243,9 @@ class MinionPoolEvents extends React.Component<Props, State> {
                 <Message>{event.message}</Message>
               </RowData>
               <RowData width="192px" secondary>
-                {moment(event.created_at).format("YYYY-MM-DD HH:mm:ss")}
+                {DateUtils.getLocalDate(event.created_at).toFormat(
+                  "yyyy-LL-dd HH:mm:ss"
+                )}
               </RowData>
             </Row>
           );

+ 16 - 10
src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMachines.tsx

@@ -13,19 +13,19 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 import * as React from "react";
-import styled, { createGlobalStyle, css } from "styled-components";
-import moment from "moment";
 import { Collapse } from "react-collapse";
-
 import { Link } from "react-router-dom";
+import styled, { createGlobalStyle, css } from "styled-components";
+
+import { MigrationItem, ReplicaItem, TransferItem } from "@src/@types/MainItem";
 import { MinionMachine, MinionPool } from "@src/@types/MinionPool";
-import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
-import { ItemReplicaBadge } from "@src/components/ui/Dropdowns/NotificationDropdown";
 import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import Arrow from "@src/components/ui/Arrow";
-
+import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
+import { ItemReplicaBadge } from "@src/components/ui/Dropdowns/NotificationDropdown";
 import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
-import { MigrationItem, ReplicaItem, TransferItem } from "@src/@types/MainItem";
+import DateUtils from "@src/utils/DateUtils";
+
 import networkImage from "./images/network.svg";
 
 const GlobalStyle = createGlobalStyle`
@@ -304,18 +304,24 @@ class MinionPoolMachines extends React.Component<Props, State> {
                 </MachineRow>
                 <MachineRow secondary>
                   Created At:{" "}
-                  {moment(machine.created_at).format("YYYY-MM-DD HH:mm:ss")}
+                  {DateUtils.getLocalDate(machine.created_at).toFormat(
+                    "yyyy-LL-dd HH:mm:ss"
+                  )}
                 </MachineRow>
                 {machine.updated_at ? (
                   <MachineRow secondary>
                     Updated At:{" "}
-                    {moment(machine.updated_at).format("YYYY-MM-DD HH:mm:ss")}
+                    {DateUtils.getLocalDate(machine.updated_at).toFormat(
+                      "yyyy-LL-dd HH:mm:ss"
+                    )}
                   </MachineRow>
                 ) : null}
                 {machine.last_used_at ? (
                   <MachineRow secondary>
                     Last Used At:{" "}
-                    {moment(machine.last_used_at).format("YYYY-MM-DD HH:mm:ss")}
+                    {DateUtils.getLocalDate(machine.last_used_at).toFormat(
+                      "yyyy-LL-dd HH:mm:ss"
+                    )}
                   </MachineRow>
                 ) : null}
                 {machine.allocated_action ? (

+ 13 - 16
src/components/modules/MinionModule/MinionPoolDetailsContent/MinionPoolMainDetails.tsx

@@ -12,28 +12,25 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { observer } from "mobx-react";
 import * as React from "react";
 import { Link } from "react-router-dom";
-import { observer } from "mobx-react";
 import styled, { css } from "styled-components";
 
+import fieldHelper from "@src/@types/Field";
+import { MigrationItem, ReplicaItem, TransferItem } from "@src/@types/MainItem";
+import { MinionPool } from "@src/@types/MinionPool";
 import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import CopyMultilineValue from "@src/components/ui/CopyMultilineValue";
 import CopyValue from "@src/components/ui/CopyValue";
 import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
-import CopyMultilineValue from "@src/components/ui/CopyMultilineValue";
-
-import type { Endpoint } from "@src/@types/Endpoint";
-import type { Field as FieldType } from "@src/@types/Field";
-import fieldHelper from "@src/@types/Field";
-
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import { OptionsSchemaPlugin } from "@src/plugins";
 import DateUtils from "@src/utils/DateUtils";
 import LabelDictionary from "@src/utils/LabelDictionary";
-import { OptionsSchemaPlugin } from "@src/plugins";
-
-import { TransferItem, ReplicaItem, MigrationItem } from "@src/@types/MainItem";
-import { MinionPool } from "@src/@types/MinionPool";
 
+import type { Endpoint } from "@src/@types/Endpoint";
+import type { Field as FieldType } from "@src/@types/Field";
 const Wrapper = styled.div<any>`
   display: flex;
   flex-direction: column;
@@ -123,8 +120,8 @@ class MinionPoolMainDetails extends React.Component<Props> {
   renderLastExecutionTime() {
     return this.props.item?.updated_at
       ? this.renderValue(
-          DateUtils.getLocalTime(this.props.item.updated_at).format(
-            "YYYY-MM-DD HH:mm:ss"
+          DateUtils.getLocalDate(this.props.item.updated_at).toFormat(
+            "yyyy-LL-dd HH:mm:ss"
           )
         )
       : "-";
@@ -305,8 +302,8 @@ class MinionPoolMainDetails extends React.Component<Props> {
               <Label>Created</Label>
               {this.props.item?.created_at ? (
                 this.renderValue(
-                  DateUtils.getLocalTime(this.props.item.created_at).format(
-                    "YYYY-MM-DD HH:mm:ss"
+                  DateUtils.getLocalDate(this.props.item.created_at).toFormat(
+                    "yyyy-LL-dd HH:mm:ss"
                   )
                 )
               ) : (

+ 10 - 10
src/components/modules/MinionModule/MinionPoolListItem/MinionPoolListItem.tsx

@@ -12,18 +12,18 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
-import { observer } from "mobx-react";
 
-import Checkbox from "@src/components/ui/Checkbox";
-import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
+import { MinionPool } from "@src/@types/MinionPool";
+import { ProviderTypes } from "@src/@types/Providers";
 import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
 import { ThemePalette, ThemeProps } from "@src/components/Theme";
-
+import Checkbox from "@src/components/ui/Checkbox";
+import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
 import DateUtils from "@src/utils/DateUtils";
-import { MinionPool } from "@src/@types/MinionPool";
-import { ProviderTypes } from "@src/@types/Providers";
+
 import itemImage from "./images/minion-pool-list-item.svg";
 
 const CheckboxStyled = styled(Checkbox)`
@@ -115,8 +115,8 @@ class MinionPoolListItem extends React.Component<Props> {
       >
         <ItemLabel>Created</ItemLabel>
         <ItemValue>
-          {DateUtils.getLocalTime(this.props.item.created_at).format(
-            "DD MMMM YYYY, HH:mm"
+          {DateUtils.getLocalDate(this.props.item.created_at).toFormat(
+            "dd LLLL yyyy, HH:mm"
           )}
         </ItemValue>
       </Column>
@@ -131,8 +131,8 @@ class MinionPoolListItem extends React.Component<Props> {
         <ItemLabel>Updated</ItemLabel>
         <ItemValue>
           {this.props.item.updated_at
-            ? DateUtils.getLocalTime(this.props.item.updated_at).format(
-                "DD MMMM YYYY, HH:mm"
+            ? DateUtils.getLocalDate(this.props.item.updated_at).toFormat(
+                "dd LLLL yyyy, HH:mm"
               )
             : "-"}
         </ItemValue>

+ 10 - 11
src/components/modules/TransferModule/Executions/Executions.tsx

@@ -12,24 +12,23 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from "react";
 import { observer } from "mobx-react";
+import React from "react";
 import styled from "styled-components";
 
-import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import CopyValue from "@src/components/ui/CopyValue";
-import Button from "@src/components/ui/Button";
-import Timeline from "@src/components/modules/TransferModule/Timeline";
+import { Instance } from "@src/@types/Instance";
 import Tasks from "@src/components/modules/TransferModule/Tasks";
-
-import type { Execution, ExecutionTasks } from "@src/@types/Execution";
+import Timeline from "@src/components/modules/TransferModule/Timeline";
 import { ThemePalette } from "@src/components/Theme";
+import Button from "@src/components/ui/Button";
+import CopyValue from "@src/components/ui/CopyValue";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
+import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
 import DateUtils from "@src/utils/DateUtils";
 
-import { Instance } from "@src/@types/Instance";
 import executionImage from "./images/execution.svg";
 
+import type { Execution, ExecutionTasks } from "@src/@types/Execution";
 const Wrapper = styled.div<any>``;
 const LoadingWrapper = styled.div<any>`
   margin-top: 32px;
@@ -370,9 +369,9 @@ class Executions extends React.Component<Props, State> {
           status={this.state.selectedExecution.status}
         />
         <ExecutionInfoDate>
-          {DateUtils.getLocalTime(
+          {DateUtils.getLocalDate(
             this.state.selectedExecution.created_at
-          ).format("DD MMMM YYYY HH:mm")}
+          ).toFormat("dd LLLL yyyy HH:mm")}
         </ExecutionInfoDate>
         <ExecutionInfoId>
           ID:&nbsp;

+ 15 - 17
src/components/modules/TransferModule/MainDetails/MainDetails.tsx

@@ -12,32 +12,30 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { observer } from "mobx-react";
 import * as React from "react";
 import { Link } from "react-router-dom";
-import { observer } from "mobx-react";
 import styled, { css } from "styled-components";
 
+import fieldHelper from "@src/@types/Field";
+import { TransferItem } from "@src/@types/MainItem";
+import { MinionPool } from "@src/@types/MinionPool";
 import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
+import TransferDetailsTable from "@src/components/modules/TransferModule/TransferDetailsTable";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import CopyValue from "@src/components/ui/CopyValue";
+import PasswordValue from "@src/components/ui/PasswordValue";
 import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
 import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import TransferDetailsTable from "@src/components/modules/TransferModule/TransferDetailsTable";
-import PasswordValue from "@src/components/ui/PasswordValue";
-
-import type { Instance } from "@src/@types/Instance";
-import type { Endpoint, StorageBackend } from "@src/@types/Endpoint";
-import type { Network } from "@src/@types/Network";
-import type { Field as FieldType } from "@src/@types/Field";
-import fieldHelper from "@src/@types/Field";
-
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import DateUtils from "@src/utils/DateUtils";
 import LabelDictionary from "@src/utils/LabelDictionary";
 
-import { TransferItem } from "@src/@types/MainItem";
-import { MinionPool } from "@src/@types/MinionPool";
 import arrowImage from "./images/arrow.svg";
 
+import type { Instance } from "@src/@types/Instance";
+import type { Endpoint, StorageBackend } from "@src/@types/Endpoint";
+import type { Network } from "@src/@types/Network";
+import type { Field as FieldType } from "@src/@types/Field";
 const Wrapper = styled.div<any>`
   display: flex;
   flex-direction: column;
@@ -170,8 +168,8 @@ class MainDetails extends React.Component<Props, State> {
   renderLastExecutionTime() {
     return this.props.item
       ? this.renderValue(
-          DateUtils.getLocalTime(this.props.item.updated_at).format(
-            "YYYY-MM-DD HH:mm:ss"
+          DateUtils.getLocalDate(this.props.item.updated_at).toFormat(
+            "yyyy-LL-dd HH:mm:ss"
           )
         )
       : "-";
@@ -391,8 +389,8 @@ class MainDetails extends React.Component<Props, State> {
               <Label>Created</Label>
               {this.props.item && this.props.item.created_at ? (
                 this.renderValue(
-                  DateUtils.getLocalTime(this.props.item.created_at).format(
-                    "YYYY-MM-DD HH:mm:ss"
+                  DateUtils.getLocalDate(this.props.item.created_at).toFormat(
+                    "yyyy-LL-dd HH:mm:ss"
                   )
                 )
               ) : (

+ 12 - 12
src/components/modules/TransferModule/Schedule/Schedule.tsx

@@ -12,27 +12,27 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { DateTime } from "luxon";
+import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
-import moment from "moment-timezone";
-import { observer } from "mobx-react";
 
-import Button from "@src/components/ui/Button";
-import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
-import Modal from "@src/components/ui/Modal";
-import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
-import AlertModal from "@src/components/ui/AlertModal";
 import ReplicaExecutionOptions from "@src/components/modules/TransferModule/ReplicaExecutionOptions";
 import ScheduleItem from "@src/components/modules/TransferModule/ScheduleItem";
-
 import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import AlertModal from "@src/components/ui/AlertModal";
+import Button from "@src/components/ui/Button";
+import DropdownLink from "@src/components/ui/Dropdowns/DropdownLink";
+import LoadingButton from "@src/components/ui/LoadingButton";
+import Modal from "@src/components/ui/Modal";
+import StatusImage from "@src/components/ui/StatusComponents/StatusImage";
 import DateUtils from "@src/utils/DateUtils";
-import type { Schedule as ScheduleType } from "@src/@types/Schedule";
-import type { Field } from "@src/@types/Field";
 
-import LoadingButton from "@src/components/ui/LoadingButton";
 import scheduleImage from "./images/schedule.svg";
 
+import type { Schedule as ScheduleType } from "@src/@types/Schedule";
+import type { Field } from "@src/@types/Field";
+
 const Wrapper = styled.div<any>`
   ${ThemeProps.exactWidth(ThemeProps.contentWidth)}
 `;
@@ -353,7 +353,7 @@ class Schedule extends React.Component<Props, State> {
 
     const timezoneItems = [
       {
-        label: `${moment.tz(moment.tz.guess()).zoneAbbr()} (local time)`,
+        label: `${DateTime.local().toFormat("ZZZZ")} (local time)`,
         value: "local",
       },
       { label: "UTC", value: "utc" },

+ 29 - 31
src/components/modules/TransferModule/ScheduleItem/ScheduleItem.tsx

@@ -12,26 +12,27 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from "react";
+import { DateTime, Info } from "luxon";
 import { observer } from "mobx-react";
+import React from "react";
 import styled, { css } from "styled-components";
-import moment from "moment";
 
-import Switch from "@src/components/ui/Switch";
-import Dropdown from "@src/components/ui/Dropdowns/Dropdown";
-import DatetimePicker from "@src/components/ui/DatetimePicker";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import Button from "@src/components/ui/Button";
-import type { Schedule, ScheduleFieldName } from "@src/@types/Schedule";
-
+import DatetimePicker from "@src/components/ui/DatetimePicker";
+import Dropdown from "@src/components/ui/Dropdowns/Dropdown";
+import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
+import Switch from "@src/components/ui/Switch";
 import { executionOptions } from "@src/constants";
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import DateUtils from "@src/utils/DateUtils";
 import notificationStore from "@src/stores/NotificationStore";
-import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
-import deleteImage from "./images/delete.svg";
+import DateUtils from "@src/utils/DateUtils";
+
 import deleteHoverImage from "./images/delete-hover.svg";
-import saveImage from "./images/save.svg";
+import deleteImage from "./images/delete.svg";
 import saveHoverImage from "./images/save-hover.svg";
+import saveImage from "./images/save.svg";
+
+import type { Schedule, ScheduleFieldName } from "@src/@types/Schedule";
 
 const Wrapper = styled.div<any>`
   display: flex;
@@ -156,9 +157,7 @@ class ScheduleItem extends React.Component<Props> {
 
   handleMonthChange(item: Field) {
     const month = item.value || 1;
-    const maxNumDays = moment()
-      .month(month - 1)
-      .daysInMonth();
+    const maxNumDays = DateTime.local().set({ month: month }).daysInMonth!;
     const change: Schedule = { schedule: { month: item.value } };
     if (
       this.props.item.schedule &&
@@ -173,8 +172,8 @@ class ScheduleItem extends React.Component<Props> {
   }
 
   handleExpirationDateChange(date: Date) {
-    const newDate = moment(date);
-    if (newDate.diff(new Date(), "minutes") < 60) {
+    const newDate = DateUtils.getLocalDate(date);
+    if (newDate.diff(DateTime.local(), "minutes").minutes < 60) {
       notificationStore.alert(
         "Please select a further expiration date.",
         "error"
@@ -182,7 +181,7 @@ class ScheduleItem extends React.Component<Props> {
       return;
     }
 
-    this.props.onChange({ expiration_date: newDate.toDate() });
+    this.props.onChange({ expiration_date: newDate.toJSDate() });
   }
 
   handleHourChange(hour: number) {
@@ -227,7 +226,7 @@ class ScheduleItem extends React.Component<Props> {
 
   renderMonthValue() {
     const items: any = [{ label: "Any", value: null }];
-    const months = moment.months();
+    const months = Info.months("long");
     months.forEach((label, value) => {
       items.push({ label, value: value + 1 });
     });
@@ -260,10 +259,7 @@ class ScheduleItem extends React.Component<Props> {
     const items: any = [{ label: "Any", value: null }];
     for (
       let i = 1;
-      i <=
-      moment()
-        .month(month - 1)
-        .daysInMonth();
+      i <= DateTime.local().set({ month: month }).daysInMonth!;
       i += 1
     ) {
       items.push({ label: i.toString(), value: i });
@@ -290,9 +286,9 @@ class ScheduleItem extends React.Component<Props> {
   renderDayOfWeekValue() {
     const items: any = [{ label: "Any", value: null }];
 
-    const days = moment.weekdays(true);
-    days.forEach((label, value) => {
-      items.push({ label, value });
+    const days = Info.weekdays("long");
+    days.forEach((label, index) => {
+      items.push({ label, value: index });
     });
 
     if (this.props.item.enabled || this.props.deleting) {
@@ -394,27 +390,29 @@ class ScheduleItem extends React.Component<Props> {
   renderExpirationValue() {
     const date =
       this.props.item.expiration_date &&
-      moment(this.props.item.expiration_date);
+      DateUtils.getLocalDate(this.props.item.expiration_date);
 
     if (this.props.item.enabled || this.props.deleting) {
       let labelDate = date;
       if (this.props.timezone === "utc" && date) {
-        labelDate = DateUtils.getUtcTime(date);
+        labelDate = date.setZone("utc");
       }
       return this.renderLabel({
-        label: (labelDate && labelDate.format("DD/MM/YYYY hh:mm A")) || "-",
+        label: (labelDate && labelDate.toFormat("dd/LL/yyyy hh:mm a")) || "-",
       });
     }
 
     return (
       <DatetimePicker
-        value={date ? date.toDate() : null}
+        value={date ? date.toJSDate() : null}
         timezone={this.props.timezone}
         useBold={this.shouldUseBold("expiration_date", true)}
         onChange={newDate => {
           this.handleExpirationDateChange(newDate);
         }}
-        isValidDate={newDate => moment(newDate).isAfter(moment())}
+        isValidDate={newDate =>
+          DateUtils.getLocalDate(newDate) > DateTime.local()
+        }
       />
     );
   }

+ 12 - 12
src/components/modules/TransferModule/TaskItem/TaskItem.tsx

@@ -12,23 +12,23 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from "react";
 import { observer } from "mobx-react";
-import styled, { css, createGlobalStyle } from "styled-components";
+import React from "react";
 import { Collapse } from "react-collapse";
+import styled, { createGlobalStyle, css } from "styled-components";
 
-import type { ProgressUpdate, Task } from "@src/@types/Task";
-import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
+import { Instance } from "@src/@types/Instance";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import Arrow from "@src/components/ui/Arrow";
-import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
+import CopyButton from "@src/components/ui/CopyButton";
 import CopyValue from "@src/components/ui/CopyValue";
 import ProgressBar from "@src/components/ui/ProgressBar";
-import CopyButton from "@src/components/ui/CopyButton";
-import DomUtils from "@src/utils/DomUtils";
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
+import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
+import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
 import DateUtils from "@src/utils/DateUtils";
-import { Instance } from "@src/@types/Instance";
+import DomUtils from "@src/utils/DomUtils";
 
+import type { ProgressUpdate, Task } from "@src/@types/Task";
 const GlobalStyle = createGlobalStyle`
   .ReactCollapse--collapse {
     transition: height 0.4s ease-in-out;
@@ -275,7 +275,7 @@ class TaskItem extends React.Component<Props> {
         </HeaderData>
         <HeaderData width={this.props.columnWidths[3]}>
           {date
-            ? DateUtils.getLocalTime(date).format("YYYY-MM-DD HH:mm:ss")
+            ? DateUtils.getLocalDate(date).toFormat("yyyy-LL-dd HH:mm:ss")
             : "-"}
         </HeaderData>
         <ArrowStyled
@@ -349,8 +349,8 @@ class TaskItem extends React.Component<Props> {
             >
               <ProgressUpdateDate width={this.props.columnWidths[0]}>
                 <span>
-                  {DateUtils.getLocalTime(update.created_at).format(
-                    "YYYY-MM-DD HH:mm:ss"
+                  {DateUtils.getLocalDate(update.created_at).toFormat(
+                    "yyyy-LL-dd HH:mm:ss"
                   )}
                 </span>
               </ProgressUpdateDate>

+ 6 - 5
src/components/modules/TransferModule/Timeline/Timeline.tsx

@@ -12,17 +12,16 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from "react";
 import { observer } from "mobx-react";
+import React from "react";
 import styled from "styled-components";
 
-import type { Execution } from "@src/@types/Execution";
+import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import Arrow from "@src/components/ui/Arrow";
 import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
-
-import { ThemePalette, ThemeProps } from "@src/components/Theme";
 import DateUtils from "@src/utils/DateUtils";
 
+import type { Execution } from "@src/@types/Execution";
 const ITEM_GAP = 96;
 
 const ArrowStyled = styled(Arrow)<any>`
@@ -197,7 +196,9 @@ class Timeline extends React.Component<Props> {
                   this.props.selectedItem.id === item.id
                 }
               >
-                {DateUtils.getLocalTime(item.created_at).format("DD MMM YYYY")}
+                {DateUtils.getLocalDate(item.created_at).toFormat(
+                  "dd LLL yyyy"
+                )}
               </ItemLabel>
             </Item>
           ))}

+ 9 - 9
src/components/modules/TransferModule/TransferListItem/TransferListItem.tsx

@@ -12,17 +12,17 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
-import { observer } from "mobx-react";
 
-import Checkbox from "@src/components/ui/Checkbox";
-import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
+import { getTransferItemTitle, TransferItem } from "@src/@types/MainItem";
 import EndpointLogos from "@src/components/modules/EndpointModule/EndpointLogos";
 import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import { getTransferItemTitle, TransferItem } from "@src/@types/MainItem";
-
+import Checkbox from "@src/components/ui/Checkbox";
+import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
 import DateUtils from "@src/utils/DateUtils";
+
 import arrowImage from "./images/arrow.svg";
 import scheduleImage from "./images/schedule.svg";
 
@@ -129,8 +129,8 @@ class TransferListItem extends React.Component<Props> {
       >
         <ItemLabel>Created</ItemLabel>
         <ItemValue>
-          {DateUtils.getLocalTime(this.props.item.created_at).format(
-            "DD MMMM YYYY, HH:mm"
+          {DateUtils.getLocalDate(this.props.item.created_at).toFormat(
+            "dd LLLL yyyy, HH:mm"
           )}
         </ItemValue>
       </Column>
@@ -145,8 +145,8 @@ class TransferListItem extends React.Component<Props> {
         <ItemLabel>Updated</ItemLabel>
         <ItemValue>
           {this.props.item.updated_at
-            ? DateUtils.getLocalTime(this.props.item.updated_at).format(
-                "DD MMMM YYYY, HH:mm"
+            ? DateUtils.getLocalDate(this.props.item.updated_at).toFormat(
+                "dd LLLL yyyy, HH:mm"
               )
             : "-"}
         </ItemValue>

+ 16 - 17
src/components/modules/WizardModule/WizardSummary/WizardSummary.tsx

@@ -12,32 +12,31 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import React from "react";
+import { Info } from "luxon";
 import { observer } from "mobx-react";
+import React from "react";
 import styled from "styled-components";
-import moment from "moment";
-
-import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
 
+import fieldHelper from "@src/@types/Field";
+import { MinionPool } from "@src/@types/MinionPool";
+import { ProviderTypes } from "@src/@types/Providers";
+import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/modules/WizardModule/WizardOptions";
+import { getDisks } from "@src/components/modules/WizardModule/WizardStorage";
 import { ThemePalette, ThemeProps } from "@src/components/Theme";
-import LabelDictionary from "@src/utils/LabelDictionary";
-import DateUtils from "@src/utils/DateUtils";
+import StatusPill from "@src/components/ui/StatusComponents/StatusPill";
 import { migrationFields } from "@src/constants";
+import configLoader from "@src/utils/Config";
+import DateUtils from "@src/utils/DateUtils";
+import LabelDictionary from "@src/utils/LabelDictionary";
+
+import networkArrowImage from "./images/network-arrow.svg";
+
 import type { Schedule } from "@src/@types/Schedule";
 import type { WizardData } from "@src/@types/WizardData";
 import type { StorageMap, StorageBackend } from "@src/@types/Endpoint";
 import type { Instance, Disk, InstanceScript } from "@src/@types/Instance";
 import type { Field } from "@src/@types/Field";
 
-import fieldHelper from "@src/@types/Field";
-import { getDisks } from "@src/components/modules/WizardModule/WizardStorage";
-
-import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/modules/WizardModule/WizardOptions";
-import { MinionPool } from "@src/@types/MinionPool";
-import { ProviderTypes } from "@src/@types/Providers";
-import configLoader from "@src/utils/Config";
-import networkArrowImage from "./images/network-arrow.svg";
-
 const Wrapper = styled.div`
   width: 100%;
   display: flex;
@@ -202,7 +201,7 @@ class WizardSummary extends React.Component<Props> {
       monthLabel = "Every month";
     } else {
       monthLabel = `Every ${
-        moment.months()[scheduleInfo.month ? scheduleInfo.month - 1 : 0]
+        Info.months()[scheduleInfo.month ? scheduleInfo.month - 1 : 0]
       }`;
     }
 
@@ -217,7 +216,7 @@ class WizardSummary extends React.Component<Props> {
     if (scheduleInfo.dow == null) {
       dayOfWeekLabel = "every weekday";
     } else {
-      dayOfWeekLabel = `every ${moment.weekdays(true)[scheduleInfo.dow]}`;
+      dayOfWeekLabel = `every ${Info.weekdays()[scheduleInfo.dow]}`;
     }
 
     const padNumber = (number: number) =>

+ 27 - 17
src/components/smart/LogsPage/DownloadsContent.tsx

@@ -12,22 +12,22 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { DateTime } from "luxon";
+import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
-import { observer } from "mobx-react";
-import moment from "moment";
-
-import type { Log as LogType } from "@src/@types/Log";
 
-import { Close } from "@src/components/ui/TextInput";
+import { ThemeProps } from "@src/components/Theme";
+import Button from "@src/components/ui/Button";
 import DatetimePicker from "@src/components/ui/DatetimePicker";
+import LoadingButton from "@src/components/ui/LoadingButton";
 import StatusIcon from "@src/components/ui/StatusComponents/StatusIcon";
-import Button from "@src/components/ui/Button";
+import { Close } from "@src/components/ui/TextInput";
+import DateUtils from "@src/utils/DateUtils";
 
-import { ThemeProps } from "@src/components/Theme";
 import downloadImage from "./images/download.svg";
-import LoadingButton from "@src/components/ui/LoadingButton";
 
+import type { Log as LogType } from "@src/@types/Log";
 const Wrapper = styled.div<any>`
   display: flex;
   flex-direction: column;
@@ -128,6 +128,8 @@ class DownloadsContent extends React.Component<Props, State> {
   }
 
   renderDates() {
+    const toUtc = (date: Date) => DateUtils.getUtcDate(date);
+
     return (
       <Dates>
         <DateWrapper>
@@ -139,7 +141,7 @@ class DownloadsContent extends React.Component<Props, State> {
                 this.handleStartDateChange(date);
               }}
               timezone="utc"
-              isValidDate={date => moment(date).isBefore(moment())}
+              isValidDate={date => toUtc(date) < DateTime.utc()}
               dispatchChangeContinously
             />
             <CloseButton
@@ -159,14 +161,22 @@ class DownloadsContent extends React.Component<Props, State> {
                 this.handleEndDateChange(date);
               }}
               timezone="utc"
-              isValidDate={date =>
-                this.state.startDate
-                  ? moment(date).isBefore(moment()) &&
-                    moment(date).isAfter(
-                      moment(this.state.startDate).subtract(1, "day")
-                    )
-                  : moment(date).isBefore(moment())
-              }
+              isValidDate={date => {
+                const utcDate = toUtc(date);
+                const diffNow = utcDate.diffNow().milliseconds;
+                if (this.state.startDate) {
+                  // Convert the start date to UTC and subtract one day
+                  const startDate = toUtc(this.state.startDate).minus({
+                    days: 1,
+                  });
+                  // Return true if the selected date is before now and after the start date
+                  return (
+                    diffNow < 0 && utcDate.diff(startDate).milliseconds > 0
+                  );
+                }
+                // If no start date is set, return true if the selected date is before now
+                return diffNow < 0;
+              }}
               dispatchChangeContinously
             />
             <CloseButton

+ 29 - 30
src/components/smart/ReplicaDetailsPage/ReplicaDetailsPage.tsx

@@ -12,45 +12,43 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import { observer } from "mobx-react";
 import React from "react";
 import styled from "styled-components";
-import { observer } from "mobx-react";
 
-import DetailsTemplate from "@src/components/modules/TemplateModule/DetailsTemplate";
-import DetailsPageHeader from "@src/components/modules/DetailsModule/DetailsPageHeader";
+import { getTransferItemTitle, ReplicaItemDetails } from "@src/@types/MainItem";
 import DetailsContentHeader from "@src/components/modules/DetailsModule/DetailsContentHeader";
+import DetailsPageHeader from "@src/components/modules/DetailsModule/DetailsPageHeader";
+import DetailsTemplate from "@src/components/modules/TemplateModule/DetailsTemplate";
+import DeleteReplicaModal from "@src/components/modules/TransferModule/DeleteReplicaModal";
 import ReplicaDetailsContent from "@src/components/modules/TransferModule/ReplicaDetailsContent";
-import Modal from "@src/components/ui/Modal";
 import ReplicaExecutionOptions from "@src/components/modules/TransferModule/ReplicaExecutionOptions";
-import AlertModal from "@src/components/ui/AlertModal";
-import TransferItemModal from "@src/components/modules/TransferModule/TransferItemModal";
 import ReplicaMigrationOptions from "@src/components/modules/TransferModule/ReplicaMigrationOptions";
-import DeleteReplicaModal from "@src/components/modules/TransferModule/DeleteReplicaModal";
-
-import type { InstanceScript } from "@src/@types/Instance";
-import type { Execution } from "@src/@types/Execution";
-import type { Schedule } from "@src/@types/Schedule";
-import type { Field } from "@src/@types/Field";
-import type { DropdownAction } from "@src/components/ui/Dropdowns/ActionDropdown";
-
-import replicaStore from "@src/stores/ReplicaStore";
-import migrationStore from "@src/stores/MigrationStore";
-import userStore from "@src/stores/UserStore";
+import TransferItemModal from "@src/components/modules/TransferModule/TransferItemModal";
+import { ThemePalette } from "@src/components/Theme";
+import AlertModal from "@src/components/ui/AlertModal";
+import Modal from "@src/components/ui/Modal";
+import { providerTypes } from "@src/constants";
 import endpointStore from "@src/stores/EndpointStore";
-import scheduleStore from "@src/stores/ScheduleStore";
 import instanceStore from "@src/stores/InstanceStore";
+import migrationStore from "@src/stores/MigrationStore";
+import minionPoolStore from "@src/stores/MinionPoolStore";
 import networkStore from "@src/stores/NetworkStore";
 import providerStore from "@src/stores/ProviderStore";
-
+import replicaStore from "@src/stores/ReplicaStore";
+import scheduleStore from "@src/stores/ScheduleStore";
+import userStore from "@src/stores/UserStore";
 import configLoader from "@src/utils/Config";
-import { providerTypes } from "@src/constants";
-
-import { ThemePalette } from "@src/components/Theme";
-import { getTransferItemTitle, ReplicaItemDetails } from "@src/@types/MainItem";
 import ObjectUtils from "@src/utils/ObjectUtils";
-import minionPoolStore from "@src/stores/MinionPoolStore";
+
 import replicaImage from "./images/replica.svg";
 
+import type { InstanceScript } from "@src/@types/Instance";
+import type { Execution } from "@src/@types/Execution";
+import type { Schedule } from "@src/@types/Schedule";
+import type { Field } from "@src/@types/Field";
+import type { DropdownAction } from "@src/components/ui/Dropdowns/ActionDropdown";
+
 const Wrapper = styled.div<any>``;
 
 type Props = {
@@ -186,11 +184,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   get replicaId() {
-    if (
-      !this.props.match ||
-      !this.props.match.params ||
-      !this.props.match.params.id
-    ) {
+    if (!this.props.match?.params?.id) {
       throw new Error("Invalid replica id");
     }
     return this.props.match.params.id;
@@ -538,7 +532,12 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   async pollData() {
-    if (this.state.pausePolling || this.stopPolling) {
+    if (
+      this.state.pausePolling ||
+      this.stopPolling ||
+      // Polling while on the schedule page is not needed and it can cause issues with the datetime pick component
+      this.props.match.params.page === "schedule"
+    ) {
       return;
     }
 

+ 7 - 8
src/components/ui/DatetimePicker/DatetimePicker.spec.tsx

@@ -13,9 +13,11 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 import React from "react";
-import moment from "moment";
+
+import DateUtils from "@src/utils/DateUtils";
 import { render } from "@testing-library/react";
 import TestUtils from "@tests/TestUtils";
+
 import DatetimePicker from "./DatetimePicker";
 
 const DATE = new Date("2021-11-12T12:32:44.426Z");
@@ -24,9 +26,7 @@ describe("DatetimePicker", () => {
   it("renders date value in UTC timezone in dropdown label", () => {
     render(<DatetimePicker onChange={() => {}} timezone="utc" value={DATE} />);
 
-    const expected = moment(DATE)
-      .add(new Date().getTimezoneOffset(), "minutes")
-      .format("DD/MM/YYYY hh:mm A");
+    const expected = DateUtils.getUtcDate(DATE).toFormat("dd/LL/yyyy hh:mm a");
 
     expect(TestUtils.select("DropdownButton__Label")?.innerHTML).toEqual(
       expected
@@ -43,10 +43,9 @@ describe("DatetimePicker", () => {
     );
     firstDay?.click();
 
-    const expected = moment(DATE)
-      .set("date", 1)
-      .add(new Date().getTimezoneOffset(), "minutes")
-      .format("DD/MM/YYYY hh:mm A");
+    const expected = DateUtils.getUtcDate(DATE)
+      .set({ day: 1 })
+      .toFormat("dd/LL/yyyy hh:mm a");
 
     expect(TestUtils.select("DropdownButton__Label")?.innerHTML).toEqual(
       expected

+ 32 - 29
src/components/ui/DatetimePicker/DatetimePicker.tsx

@@ -12,24 +12,21 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+import autobind from "autobind-decorator";
+import { DateTime } from "luxon";
+import { observer } from "mobx-react";
 import React from "react";
+import Datetime from "react-datetime";
 import ReactDOM from "react-dom";
-import { observer } from "mobx-react";
 import styled, { createGlobalStyle } from "styled-components";
-import Datetime from "react-datetime";
-import moment from "moment";
-import autobind from "autobind-decorator";
 
+import { ThemeProps } from "@src/components/Theme";
 import DropdownButton from "@src/components/ui/Dropdowns/DropdownButton";
-
-import DomUtils from "@src/utils/DomUtils";
 import DateUtils from "@src/utils/DateUtils";
+import DomUtils from "@src/utils/DomUtils";
 
-import { ThemeProps } from "@src/components/Theme";
 import style from "./style";
 
-require("moment/locale/en-gb");
-
 const GlobalStyle = createGlobalStyle`${style}`;
 
 const Wrapper = styled.div<any>`
@@ -54,14 +51,14 @@ const DatetimeStyled = styled(Datetime)<any>`
 type Props = {
   value: Date | null;
   onChange: (date: Date) => void;
-  isValidDate?: (currentDate: Date, selectedDate: Date) => boolean;
+  isValidDate?: (currentDate: Date, selectedDate?: Date) => boolean;
   timezone: "utc" | "local";
   useBold?: boolean;
   dispatchChangeContinously?: boolean;
 };
 type State = {
   showPicker: boolean;
-  date: moment.Moment | null;
+  date: DateTime | null;
 };
 @observer
 class DatetimePicker extends React.Component<Props, State> {
@@ -80,7 +77,9 @@ class DatetimePicker extends React.Component<Props, State> {
 
   UNSAFE_componentWillMount() {
     if (this.props.value) {
-      this.setState({ date: moment(this.props.value) });
+      this.setState({
+        date: DateUtils.getLocalDate(this.props.value),
+      });
     }
   }
 
@@ -93,7 +92,11 @@ class DatetimePicker extends React.Component<Props, State> {
   }
 
   UNSAFE_componentWillReceiveProps(newProps: Props) {
-    this.setState({ date: newProps.value && moment(newProps.value) });
+    if (newProps.value?.getTime() !== this.props.value?.getTime()) {
+      this.setState({
+        date: newProps.value && DateUtils.getLocalDate(newProps.value),
+      });
+    }
   }
 
   componentDidUpdate() {
@@ -134,7 +137,7 @@ class DatetimePicker extends React.Component<Props, State> {
     this.portalRef.style.left = `${leftOffset + window.pageXOffset}px`;
   }
 
-  isValidDate(currentDate: Date, selectedDate: Date): boolean {
+  isValidDate(currentDate: Date, selectedDate?: Date): boolean {
     if (!this.props.isValidDate) {
       return true;
     }
@@ -169,9 +172,9 @@ class DatetimePicker extends React.Component<Props, State> {
   }
 
   handleChange(newDate: Date) {
-    let date = moment(newDate);
+    let date = DateUtils.getLocalDate(newDate);
     if (this.props.timezone === "utc") {
-      date = DateUtils.getLocalTime(newDate);
+      date = date.setZone("utc");
     }
 
     this.setState({ date }, () => {
@@ -185,14 +188,14 @@ class DatetimePicker extends React.Component<Props, State> {
     if (
       this.state.date &&
       this.state.showPicker &&
-      this.state.date.toDate().getTime() !==
-        (this.props.value && this.props.value.getTime())
+      this.state.date.valueOf() !==
+        (this.props.value && this.props.value.valueOf())
     ) {
-      this.props.onChange(this.state.date.toDate());
+      this.props.onChange(this.state.date.toJSDate());
     }
   }
 
-  renderDateTimePicker(timezoneDate: moment.Moment | null) {
+  renderDateTimePicker(timezoneDate: DateTime | null) {
     if (!this.state.showPicker) {
       return null;
     }
@@ -206,16 +209,18 @@ class DatetimePicker extends React.Component<Props, State> {
       >
         <DatetimeStyled
           input={false}
-          value={timezoneDate}
+          value={timezoneDate?.toJSDate()}
           style={{ top: 0, right: 0 }}
-          onChange={(date: Date) => {
-            this.handleChange(date);
+          onChange={(date: any) => {
+            if (date) {
+              this.handleChange(date.toDate());
+            }
           }}
           dateFormat="DD/MM/YYYY"
           timeFormat="hh:mm A"
           locale="en-gb"
-          isValidDate={(currentDate: Date, selectedDate: Date) =>
-            this.isValidDate(currentDate, selectedDate)
+          isValidDate={(currentDate: any, selectedDate: any) =>
+            this.isValidDate(currentDate.toDate(), selectedDate?.toDate())
           }
         />
       </Portal>,
@@ -226,7 +231,7 @@ class DatetimePicker extends React.Component<Props, State> {
   render() {
     let timezoneDate = this.state.date;
     if (this.props.timezone === "utc" && timezoneDate) {
-      timezoneDate = DateUtils.getUtcTime(timezoneDate);
+      timezoneDate = timezoneDate.setZone("utc");
     }
 
     return (
@@ -239,9 +244,7 @@ class DatetimePicker extends React.Component<Props, State> {
             }}
             width={207}
             value={
-              (timezoneDate &&
-                moment(timezoneDate).format("DD/MM/YYYY hh:mm A")) ||
-              "-"
+              timezoneDate ? timezoneDate.toFormat("dd/LL/yyyy hh:mm a") : "-"
             }
             centered
             useBold={this.props.useBold}

+ 4 - 5
src/sources/AzureSource.ts

@@ -12,12 +12,11 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import moment from "moment";
-
 import Api from "@src/utils/ApiCaller";
-import type { Assessment, VmItem, VmSize } from "@src/@types/Assessment";
 import DomUtils from "@src/utils/DomUtils";
 
+import type { Assessment, VmItem, VmSize } from "@src/@types/Assessment";
+
 const azureUrl = "https://management.azure.com/";
 const defaultApiVersion = "2019-10-01";
 
@@ -47,8 +46,8 @@ class Util {
   static sortAssessments(assessments: any[]) {
     assessments.sort(
       (a: any, b: any) =>
-        moment(b.properties.updatedTimestamp).toDate().getTime() -
-        moment(a.properties.updatedTimestamp).toDate().getTime()
+        new Date(b.properties.updatedTimestamp).getTime() -
+        new Date(a.properties.updatedTimestamp).getTime()
     );
     return assessments;
   }

+ 8 - 8
src/sources/EndpointSource.ts

@@ -12,17 +12,16 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import moment from "moment";
-
-import Api from "@src/utils/ApiCaller";
 import notificationStore from "@src/stores/NotificationStore";
-import ObjectUtils from "@src/utils/ObjectUtils";
-import type { Endpoint, Validation, Storage } from "@src/@types/Endpoint";
-
+import Api from "@src/utils/ApiCaller";
 import configLoader from "@src/utils/Config";
 import DomUtils from "@src/utils/DomUtils";
+import ObjectUtils from "@src/utils/ObjectUtils";
+
 import { SchemaParser } from "./Schemas";
 
+import type { Endpoint, Validation, Storage } from "@src/@types/Endpoint";
+
 const getBarbicanPayload = (data: any) => ({
   payload: JSON.stringify(data),
   payload_content_type: "text/plain",
@@ -47,8 +46,9 @@ class EndpointSource {
       });
     }
 
-    connections.sort((c1, c2) =>
-      moment(c2.created_at).diff(moment(c1.created_at))
+    connections.sort(
+      (c1, c2) =>
+        new Date(c2.created_at).getTime() - new Date(c1.created_at).getTime()
     );
     return connections;
   }

+ 15 - 17
src/sources/MigrationSource.ts

@@ -12,29 +12,26 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import moment from "moment";
-
-import { OptionsSchemaPlugin } from "@src/plugins";
-import DefaultOptionsSchemaPlugin from "@src/plugins/default/OptionsSchemaPlugin";
-
-import Api from "@src/utils/ApiCaller";
-import type { InstanceScript } from "@src/@types/Instance";
-import type { Field } from "@src/@types/Field";
-import type { NetworkMap } from "@src/@types/Network";
-import type { Endpoint, StorageMap } from "@src/@types/Endpoint";
-
-import configLoader from "@src/utils/Config";
-import { ProgressUpdate, Task } from "@src/@types/Task";
 import {
   MigrationItem,
-  MigrationItemOptions,
   MigrationItemDetails,
+  MigrationItemOptions,
   UserScriptData,
 } from "@src/@types/MainItem";
-
+import { ProgressUpdate, Task } from "@src/@types/Task";
 import { INSTANCE_OSMORPHING_MINION_POOL_MAPPINGS } from "@src/components/modules/WizardModule/WizardOptions";
+import { OptionsSchemaPlugin } from "@src/plugins";
+import DefaultOptionsSchemaPlugin from "@src/plugins/default/OptionsSchemaPlugin";
+import Api from "@src/utils/ApiCaller";
+import configLoader from "@src/utils/Config";
+
 import { sortTasks } from "./ReplicaSource";
 
+import type { InstanceScript } from "@src/@types/Instance";
+import type { Field } from "@src/@types/Field";
+import type { NetworkMap } from "@src/@types/Network";
+import type { Endpoint, StorageMap } from "@src/@types/Endpoint";
+
 class MigrationSourceUtils {
   static sortTaskUpdates(updates: ProgressUpdate[]) {
     if (!updates) {
@@ -50,8 +47,9 @@ class MigrationSourceUtils {
   }
 
   static sortMigrations(migrations: any[]) {
-    migrations.sort((a: any, b: any) =>
-      moment(b.created_at).diff(moment(a.created_at))
+    migrations.sort(
+      (a: any, b: any) =>
+        new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
     );
 
     migrations.forEach((migration: { tasks: Task[] }) => {

+ 18 - 19
src/sources/ScheduleSource.ts

@@ -12,13 +12,11 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import moment from "moment";
-
 import Api from "@src/utils/ApiCaller";
 import configLoader from "@src/utils/Config";
 import DateUtils from "@src/utils/DateUtils";
-import type { Schedule } from "@src/@types/Schedule";
 
+import type { Schedule } from "@src/@types/Schedule";
 class ScheduleSource {
   async scheduleSinge(
     replicaId: string,
@@ -35,7 +33,7 @@ class ScheduleSource {
     };
 
     if (scheduleData.expiration_date) {
-      payload.expiration_date = moment(
+      payload.expiration_date = new Date(
         scheduleData.expiration_date
       ).toISOString();
     }
@@ -84,18 +82,19 @@ class ScheduleSource {
       url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`,
       skipLog: opts && opts.skipLog,
     });
-    const schedules = [...response.data.schedules];
-    schedules.forEach(s => {
-      if (s.expiration_date) {
-        // eslint-disable-next-line no-param-reassign
-        s.expiration_date = DateUtils.getLocalTime(s.expiration_date);
-      }
-      if (s.shutdown_instance) {
-        // eslint-disable-next-line no-param-reassign
-        s.shutdown_instances = s.shutdown_instance;
-      }
-    });
-    schedules.sort((a, b) => moment(a.created_at).diff(b.created_at));
+
+    const schedules: any[] = response.data.schedules.map((s: any) => ({
+      ...s,
+      expiration_date: s.expiration_date
+        ? DateUtils.getUtcDate(s.expiration_date).toJSDate()
+        : undefined,
+      shutdown_instances:
+        s.shutdown_instance != null ? s.shutdown_instance : undefined,
+    }));
+    schedules.sort(
+      (a, b) =>
+        new Date(a.created_at).getTime() - new Date(b.created_at).getTime()
+    );
     return schedules;
   }
 
@@ -144,8 +143,8 @@ class ScheduleSource {
     if (scheduleData.shutdown_instances != null) {
       payload.shutdown_instance = scheduleData.shutdown_instances;
     }
-    if (unsavedData && unsavedData.expiration_date) {
-      payload.expiration_date = moment(
+    if (unsavedData?.expiration_date) {
+      payload.expiration_date = new Date(
         unsavedData.expiration_date
       ).toISOString();
     }
@@ -174,7 +173,7 @@ class ScheduleSource {
     });
     const s = { ...response.data.schedule };
     if (s.expiration_date) {
-      s.expiration_date = DateUtils.getLocalTime(s.expiration_date);
+      s.expiration_date = DateUtils.getUtcDate(s.expiration_date);
     }
     if (s.shutdown_instance) {
       s.shutdown_instances = s.shutdown_instance;

+ 15 - 18
src/utils/DateUtils.ts

@@ -12,34 +12,31 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-import moment from "moment";
+import { DateTime, Duration } from "luxon";
 
 class DateUtils {
-  static getLocalTime(rawDate: moment.MomentInput): moment.Moment {
-    const usableRawDate = rawDate || undefined;
-    return moment(usableRawDate).add(
-      -new Date().getTimezoneOffset(),
-      "minutes"
-    );
+  static getLocalDate(isoDate: string | Date) {
+    return this.getUtcDate(isoDate).toLocal();
   }
 
-  static getUtcTime(rawDate: moment.MomentInput): moment.Moment {
-    const usableRawDate = rawDate || undefined;
-    return moment(usableRawDate).add(new Date().getTimezoneOffset(), "minutes");
+  static getUtcDate(isoDate: string | Date) {
+    if (isoDate instanceof Date) {
+      return DateTime.fromJSDate(isoDate, { zone: "utc" });
+    }
+    return DateTime.fromISO(isoDate, { zone: "utc" });
   }
 
   static getLocalHour(hour: number): number {
-    return moment("00", "HH")
-      .add(-new Date().getTimezoneOffset(), "minutes")
-      .add(hour, "hours")
-      .get("hours");
+    return DateTime.utc()
+      .set({ hour: 0 })
+      .plus(Duration.fromObject({ hours: hour }))
+      .toLocal().hour;
   }
 
   static getUtcHour(hour: number): number {
-    return moment("00", "HH")
-      .add(new Date().getTimezoneOffset(), "minutes")
-      .add(hour, "hours")
-      .get("hours");
+    return DateTime.fromObject({ hour: 0 }, { zone: "local" })
+      .setZone("utc")
+      .plus(Duration.fromObject({ hours: hour })).hour;
   }
 
   static getOrdinalDay(number: number) {

+ 9 - 5
tests/testRelease.js

@@ -64,11 +64,15 @@ const main = async () => {
     await spawnPromise("npm", ["run", "eslint"], "ESLint checks");
     await spawnPromise("npm", ["run", "format"], "Run prettier");
     await spawnPromise("npm", ["run", "test"], "Run unit tests");
-    await spawnPromise(
-      "npx",
-      ["rimraf", "node_modules"],
-      "Deleting node_modules"
-    );
+    try {
+      await spawnPromise(
+        "npx",
+        ["rimraf", "node_modules"],
+        "Deleting node_modules"
+      );
+    } catch (e) {
+      console.error(e);
+    }
     await spawnPromise(
       "yarn",
       ["workspaces", "focus", "--all", "--production"],

+ 16 - 1
yarn.lock

@@ -3425,6 +3425,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/luxon@npm:^3.3.2":
+  version: 3.3.2
+  resolution: "@types/luxon@npm:3.3.2"
+  checksum: b9111132720eae0269538872a5a496b29587ecfc8edc3b0ff7d269aa93a5ff00a131b23d1e9d1f12ec39f2c779ad21bd8d9f90b122c85a182771aabde7f676b8
+  languageName: node
+  linkType: hard
+
 "@types/mdast@npm:^3.0.0":
   version: 3.0.13
   resolution: "@types/mdast@npm:3.0.13"
@@ -6470,6 +6477,7 @@ __metadata:
     "@types/file-saver": ^2.0.1
     "@types/jest": ^27.0.2
     "@types/js-cookie": ^2.2.6
+    "@types/luxon": ^3.3.2
     "@types/moment-timezone": ^0.5.13
     "@types/react": ^16.13.1
     "@types/react-collapse": ^5.0.0
@@ -6506,9 +6514,9 @@ __metadata:
     js-cookie: ^2.2.1
     jszip: ^3.8.0
     lodash: ^4.17.19
+    luxon: ^3.4.3
     mobx: ^5.15.4
     mobx-react: ^6.2.2
-    moment: ^2.29.4
     moment-timezone: ^0.5.35
     ms-rest-azure: ^2.4.5
     nodemon: ^2.0.4
@@ -11721,6 +11729,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"luxon@npm:^3.4.3":
+  version: 3.4.3
+  resolution: "luxon@npm:3.4.3"
+  checksum: 3eade81506224d038ed24035a0cd0dd4887848d7eba9361dce9ad8ef81380596a68153240be3988721f9690c624fb449fcf8fd8c3fc0681a6a8496faf48e92a3
+  languageName: node
+  linkType: hard
+
 "lz-string@npm:^1.5.0":
   version: 1.5.0
   resolution: "lz-string@npm:1.5.0"