refactor: improve time handling code
authorJérôme Benoit <jerome.benoit@sap.com>
Wed, 26 Jul 2023 05:48:33 +0000 (07:48 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Wed, 26 Jul 2023 05:48:33 +0000 (07:48 +0200)
Signed-off-by: Jérôme Benoit <jerome.benoit@sap.com>
src/charging-station/AutomaticTransactionGenerator.ts
src/charging-station/ChargingStation.ts
src/charging-station/broadcast-channel/ChargingStationWorkerBroadcastChannel.ts
src/charging-station/ocpp/1.6/OCPP16IncomingRequestService.ts
src/charging-station/ocpp/1.6/OCPP16ResponseService.ts
src/performance/PerformanceStatistics.ts
src/utils/Utils.ts
test/utils/Utils.test.ts

index 578f47e942d89e38c0700c3fec919b80bc750948..5b27a258c9e2e73d0424f04c8d68e6c7cc2ec24c 100644 (file)
@@ -2,6 +2,8 @@
 
 import { AsyncResource } from 'node:async_hooks';
 
+import { hoursToMilliseconds, secondsToMilliseconds } from 'date-fns';
+
 import type { ChargingStation } from './ChargingStation';
 import { checkChargingStation } from './ChargingStationUtils';
 import { IdTagsCache } from './IdTagsCache';
@@ -241,13 +243,14 @@ export class AutomaticTransactionGenerator extends AsyncResource {
           await sleep(Constants.CHARGING_STATION_ATG_INITIALIZATION_TIME);
         } while (!this.chargingStation?.ocppRequestService);
       }
-      const wait =
+      const wait = secondsToMilliseconds(
         getRandomInteger(
           this.chargingStation.getAutomaticTransactionGeneratorConfiguration()
             .maxDelayBetweenTwoTransactions,
           this.chargingStation.getAutomaticTransactionGeneratorConfiguration()
             .minDelayBetweenTwoTransactions,
-        ) * 1000;
+        ),
+      );
       logger.info(`${this.logPrefix(connectorId)} waiting for ${formatDurationMilliSeconds(wait)}`);
       await sleep(wait);
       const start = secureRandom();
@@ -260,11 +263,12 @@ export class AutomaticTransactionGenerator extends AsyncResource {
         const startResponse = await this.startTransaction(connectorId);
         if (startResponse?.idTagInfo?.status === AuthorizationStatus.ACCEPTED) {
           // Wait until end of transaction
-          const waitTrxEnd =
+          const waitTrxEnd = secondsToMilliseconds(
             getRandomInteger(
               this.chargingStation.getAutomaticTransactionGeneratorConfiguration().maxDuration,
               this.chargingStation.getAutomaticTransactionGeneratorConfiguration().minDuration,
-            ) * 1000;
+            ),
+          );
           logger.info(
             `${this.logPrefix(connectorId)} transaction started with id ${this.chargingStation
               .getConnectorStatus(connectorId)
@@ -320,9 +324,9 @@ export class AutomaticTransactionGenerator extends AsyncResource {
     this.connectorsStatus.get(connectorId)!.startDate = new Date();
     this.connectorsStatus.get(connectorId)!.stopDate = new Date(
       this.connectorsStatus.get(connectorId)!.startDate!.getTime() +
-        this.chargingStation.getAutomaticTransactionGeneratorConfiguration().stopAfterHours *
-          3600 *
-          1000 -
+        hoursToMilliseconds(
+          this.chargingStation.getAutomaticTransactionGeneratorConfiguration().stopAfterHours,
+        ) -
         previousRunDuration,
     );
     this.connectorsStatus.get(connectorId)!.start = true;
index 7e444c5442a6db93d22d329308d22175464bec9e..70390b152dd9356219c9ba2d2ced00a9d9c1c002 100644 (file)
@@ -14,6 +14,7 @@ import { dirname, join } from 'node:path';
 import { URL } from 'node:url';
 import { parentPort } from 'node:worker_threads';
 
+import { millisecondsToSeconds, secondsToMilliseconds } from 'date-fns';
 import merge from 'just-merge';
 import { type RawData, WebSocket } from 'ws';
 
@@ -516,11 +517,11 @@ export class ChargingStation {
   public getHeartbeatInterval(): number {
     const HeartbeatInterval = getConfigurationKey(this, StandardParametersKey.HeartbeatInterval);
     if (HeartbeatInterval) {
-      return convertToInt(HeartbeatInterval.value) * 1000;
+      return secondsToMilliseconds(convertToInt(HeartbeatInterval.value));
     }
     const HeartBeatInterval = getConfigurationKey(this, StandardParametersKey.HeartBeatInterval);
     if (HeartBeatInterval) {
-      return convertToInt(HeartBeatInterval.value) * 1000;
+      return secondsToMilliseconds(convertToInt(HeartBeatInterval.value));
     }
     this.stationInfo?.autoRegister === false &&
       logger.warn(
@@ -777,7 +778,7 @@ export class ChargingStation {
       terminateOpened: false,
     },
   ): void {
-    options = { handshakeTimeout: this.getConnectionTimeout() * 1000, ...options };
+    options = { handshakeTimeout: secondsToMilliseconds(this.getConnectionTimeout()), ...options };
     params = { ...{ closeOpened: false, terminateOpened: false }, ...params };
     if (this.started === false && this.starting === false) {
       logger.warn(
@@ -1226,7 +1227,7 @@ export class ChargingStation {
       stationTemplate?.firmwareUpgrade ?? {},
     );
     stationInfo.resetTime = !isNullOrUndefined(stationTemplate?.resetTime)
-      ? stationTemplate.resetTime! * 1000
+      ? secondsToMilliseconds(stationTemplate.resetTime!)
       : Constants.CHARGING_STATION_DEFAULT_RESET_TIME;
     stationInfo.maximumAmperage = this.getMaximumAmperage(stationInfo);
     return stationInfo;
@@ -1340,7 +1341,7 @@ export class ChargingStation {
     if (this.stationInfo?.autoRegister === true) {
       this.bootNotificationResponse = {
         currentTime: new Date(),
-        interval: this.getHeartbeatInterval() / 1000,
+        interval: millisecondsToSeconds(this.getHeartbeatInterval()),
         status: RegistrationStatusEnumType.ACCEPTED,
       };
     }
@@ -1826,7 +1827,7 @@ export class ChargingStation {
             this.getRegistrationMaxRetries() !== -1 && ++registrationRetryCount;
             await sleep(
               this?.bootNotificationResponse?.interval
-                ? this.bootNotificationResponse.interval * 1000
+                ? secondsToMilliseconds(this.bootNotificationResponse.interval)
                 : Constants.DEFAULT_BOOT_NOTIFICATION_INTERVAL,
             );
           }
@@ -2280,7 +2281,7 @@ export class ChargingStation {
         if (this.isWebSocketConnectionOpened() === true) {
           this.wsConnection?.ping();
         }
-      }, webSocketPingInterval * 1000);
+      }, secondsToMilliseconds(webSocketPingInterval));
       logger.info(
         `${this.logPrefix()} WebSocket ping started every ${formatDurationSeconds(
           webSocketPingInterval,
@@ -2378,7 +2379,7 @@ export class ChargingStation {
       ++this.autoReconnectRetryCount;
       const reconnectDelay = this.getReconnectExponentialDelay()
         ? exponentialDelay(this.autoReconnectRetryCount)
-        : this.getConnectionTimeout() * 1000;
+        : secondsToMilliseconds(this.getConnectionTimeout());
       const reconnectDelayWithdraw = 1000;
       const reconnectTimeout =
         reconnectDelay && reconnectDelay - reconnectDelayWithdraw > 0
index 422c964bcbedd232d28f1a7005efc09c7e5e989b..7c1a28200008e5d97270052bce4968b203109165 100644 (file)
@@ -1,3 +1,5 @@
+import { secondsToMilliseconds } from 'date-fns';
+
 import { WorkerBroadcastChannel } from './WorkerBroadcastChannel';
 import { BaseError, type OCPPError } from '../../exception';
 import {
@@ -193,7 +195,7 @@ export class ChargingStationWorkerBroadcastChannel extends WorkerBroadcastChanne
                   this.chargingStation.getConnectorStatus(requestPayload!.connectorId!)!
                     .transactionId!,
                   configuredMeterValueSampleInterval
-                    ? convertToInt(configuredMeterValueSampleInterval.value) * 1000
+                    ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
                     : Constants.DEFAULT_METER_VALUES_INTERVAL,
                 ),
               ],
index c7337ab69033c6ce1aa7727642fd3b87f63d651d..921c8f041daf8bdc2ac178ea52eabc5b827ef085 100644 (file)
@@ -6,6 +6,7 @@ import { URL, fileURLToPath } from 'node:url';
 
 import type { JSONSchemaType } from 'ajv';
 import { Client, type FTPResponse } from 'basic-ftp';
+import { secondsToMilliseconds } from 'date-fns';
 import { create } from 'tar';
 
 import { OCPP16Constants } from './OCPP16Constants';
@@ -673,7 +674,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       return OCPP16Constants.OCPP_RESPONSE_REJECTED;
     }
     const startDate = new Date();
-    const endDate = new Date(startDate.getTime() + commandPayload.duration * 1000);
+    const endDate = new Date(startDate.getTime() + secondsToMilliseconds(commandPayload.duration));
     let compositeSchedule: OCPP16ChargingSchedule | undefined;
     for (const chargingProfile of chargingStation.getConnectorStatus(commandPayload.connectorId)!
       .chargingProfiles!) {
@@ -1156,7 +1157,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
       OCPP16FirmwareStatus.DownloadFailed
     ) {
-      await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
+      await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
       await chargingStation.ocppRequestService.requestHandler<
         OCPP16FirmwareStatusNotificationRequest,
         OCPP16FirmwareStatusNotificationResponse
@@ -1167,7 +1168,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         chargingStation.stationInfo?.firmwareUpgrade?.failureStatus;
       return;
     }
-    await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
+    await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
     await chargingStation.ocppRequestService.requestHandler<
       OCPP16FirmwareStatusNotificationRequest,
       OCPP16FirmwareStatusNotificationResponse
@@ -1180,12 +1181,12 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
     do {
       const runningTransactions = chargingStation.getNumberOfRunningTransactions();
       if (runningTransactions > 0) {
-        const waitTime = 15 * 1000;
+        const waitTime = secondsToMilliseconds(15);
         logger.debug(
           `${chargingStation.logPrefix()} ${moduleName}.updateFirmwareSimulation:
-            ${runningTransactions} transaction(s) in progress, waiting ${
-              waitTime / 1000
-            } seconds before continuing firmware update simulation`,
+            ${runningTransactions} transaction(s) in progress, waiting ${formatDurationMilliSeconds(
+              waitTime,
+            )} before continuing firmware update simulation`,
         );
         await sleep(waitTime);
         transactionsStarted = true;
@@ -1223,7 +1224,8 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
         transactionsStarted = false;
       }
     } while (transactionsStarted);
-    !wasTransactionsStarted && (await sleep(getRandomInteger(maxDelay, minDelay) * 1000));
+    !wasTransactionsStarted &&
+      (await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay))));
     if (checkChargingStation(chargingStation, chargingStation.logPrefix()) === false) {
       return;
     }
@@ -1238,7 +1240,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       chargingStation.stationInfo?.firmwareUpgrade?.failureStatus ===
       OCPP16FirmwareStatus.InstallationFailed
     ) {
-      await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
+      await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
       await chargingStation.ocppRequestService.requestHandler<
         OCPP16FirmwareStatusNotificationRequest,
         OCPP16FirmwareStatusNotificationResponse
@@ -1250,7 +1252,7 @@ export class OCPP16IncomingRequestService extends OCPPIncomingRequestService {
       return;
     }
     if (chargingStation.stationInfo?.firmwareUpgrade?.reset === true) {
-      await sleep(getRandomInteger(maxDelay, minDelay) * 1000);
+      await sleep(secondsToMilliseconds(getRandomInteger(maxDelay, minDelay)));
       await chargingStation.reset(OCPP16StopTransactionReason.REBOOT);
     }
   }
index 3e718285d9ce4eccbc890267c70bebb303761022..3816c54fd2f35a201508dc72172fda679e60ed25 100644 (file)
@@ -3,6 +3,7 @@
 import { parentPort } from 'node:worker_threads';
 
 import type { JSONSchemaType } from 'ajv';
+import { secondsToMilliseconds } from 'date-fns';
 
 import { OCPP16ServiceUtils } from './OCPP16ServiceUtils';
 import {
@@ -665,7 +666,7 @@ export class OCPP16ResponseService extends OCPPResponseService {
       chargingStation.startMeterValues(
         transactionConnectorId,
         configuredMeterValueSampleInterval
-          ? convertToInt(configuredMeterValueSampleInterval.value) * 1000
+          ? secondsToMilliseconds(convertToInt(configuredMeterValueSampleInterval.value))
           : Constants.DEFAULT_METER_VALUES_INTERVAL,
       );
     } else {
index 8451a19339c8e2dcfc8d639513c2c0dc4b5d70b8..c5748922bd3dbae68dc4a6c944d6ed7d6e7eb88c 100644 (file)
@@ -4,6 +4,8 @@ import { type PerformanceEntry, PerformanceObserver, performance } from 'node:pe
 import type { URL } from 'node:url';
 import { parentPort } from 'node:worker_threads';
 
+import { secondsToMilliseconds } from 'date-fns';
+
 import {
   ConfigurationSection,
   type IncomingRequestCommand,
@@ -185,7 +187,7 @@ export class PerformanceStatistics {
     if (logStatisticsInterval > 0 && !this.displayInterval) {
       this.displayInterval = setInterval(() => {
         this.logStatistics();
-      }, logStatisticsInterval * 1000);
+      }, secondsToMilliseconds(logStatisticsInterval));
       logger.info(
         `${this.logPrefix()} logged every ${formatDurationSeconds(logStatisticsInterval)}`,
       );
index 48d0587cbce16e9db798c48877795325e8088eff..dd6591f88bd2b27fe999a2e9c748a5029eb41383 100644 (file)
@@ -3,10 +3,13 @@ import { inspect } from 'node:util';
 
 import {
   formatDuration,
+  hoursToMinutes,
+  hoursToSeconds,
   isDate,
   millisecondsToHours,
   millisecondsToMinutes,
   millisecondsToSeconds,
+  minutesToSeconds,
   secondsToMilliseconds,
 } from 'date-fns';
 import clone from 'just-clone';
@@ -36,9 +39,14 @@ export const formatDurationMilliSeconds = (duration: number): string => {
   duration = convertToInt(duration);
   const days = Math.floor(duration / (24 * 3600 * 1000));
   const hours = Math.floor(millisecondsToHours(duration) - days * 24);
-  const minutes = Math.floor(millisecondsToMinutes(duration) - days * 24 * 60 - hours * 60);
+  const minutes = Math.floor(
+    millisecondsToMinutes(duration) - days * 24 * 60 - hoursToMinutes(hours),
+  );
   const seconds = Math.floor(
-    millisecondsToSeconds(duration) - days * 24 * 3600 - hours * 3600 - minutes * 60,
+    millisecondsToSeconds(duration) -
+      days * 24 * 3600 -
+      hoursToSeconds(hours) -
+      minutesToSeconds(minutes),
   );
   return formatDuration({
     days,
index 5b9f81515c9686bcbea50afe1b781b3d6c108036..2b6df881b921f3533b285c8f1c3df7a8f6dd1431 100644 (file)
@@ -1,3 +1,4 @@
+import { hoursToMilliseconds } from 'date-fns';
 import { expect } from 'expect';
 
 import { Constants } from '../../src/utils/Constants';
@@ -7,6 +8,7 @@ import {
   convertToDate,
   convertToFloat,
   convertToInt,
+  formatDurationMilliSeconds,
   generateUUID,
   getRandomFloat,
   getRandomInteger,
@@ -48,6 +50,12 @@ describe('Utils test suite', () => {
     expect(end - start).toBeGreaterThanOrEqual(1000);
   });
 
+  it('Verify formatDurationMilliSeconds()', () => {
+    expect(formatDurationMilliSeconds(0)).toBe('');
+    expect(formatDurationMilliSeconds(1000)).toBe('1 second');
+    expect(formatDurationMilliSeconds(hoursToMilliseconds(4380))).toBe('182 days 12 hours');
+  });
+
   it('Verify isValidDate()', () => {
     expect(isValidDate(undefined)).toBe(false);
     expect(isValidDate(null)).toBe(false);