fix: properly handling moving recurring charging profiles
[e-mobility-charging-stations-simulator.git] / src / charging-station / ChargingStationUtils.ts
index d0d99c0b38a89c9ddc100d4407790cbd44ca599f..9da406c282d448b8b713dd116658433c4e291994 100644 (file)
@@ -4,7 +4,17 @@ import { basename, dirname, join } from 'node:path';
 import { fileURLToPath } from 'node:url';
 
 import chalk from 'chalk';
-import moment from 'moment';
+import {
+  addDays,
+  addSeconds,
+  addWeeks,
+  differenceInDays,
+  differenceInWeeks,
+  isAfter,
+  isBefore,
+  isWithinInterval,
+  toDate,
+} from 'date-fns';
 
 import type { ChargingStation } from './ChargingStation';
 import { BaseError } from '../exception';
@@ -36,6 +46,7 @@ import {
   Constants,
   DCElectricUtils,
   cloneObject,
+  convertToDate,
   convertToInt,
   isEmptyObject,
   isEmptyString,
@@ -43,6 +54,7 @@ import {
   isNotEmptyString,
   isNullOrUndefined,
   isUndefined,
+  isValidDate,
   logger,
   secureRandom,
 } from '../utils';
@@ -51,7 +63,7 @@ const moduleName = 'ChargingStationUtils';
 
 export const getChargingStationId = (
   index: number,
-  stationTemplate: ChargingStationTemplate
+  stationTemplate: ChargingStationTemplate,
 ): string => {
   // In case of multiple instances: add instance index to charging station id
   const instanceIndex = process.env.CF_INSTANCE_INDEX ?? 0;
@@ -60,7 +72,7 @@ export const getChargingStationId = (
   return stationTemplate?.fixedName
     ? stationTemplate.baseName
     : `${stationTemplate.baseName}-${instanceIndex.toString()}${idStr.substring(
-        idStr.length - 4
+        idStr.length - 4,
       )}${idSuffix}`;
 };
 
@@ -101,7 +113,7 @@ export const getHashId = (index: number, stationTemplate: ChargingStationTemplat
 
 export const checkChargingStation = (
   chargingStation: ChargingStation,
-  logPrefix: string
+  logPrefix: string,
 ): boolean => {
   if (chargingStation.started === false && chargingStation.starting === false) {
     logger.warn(`${logPrefix} charging station is stopped, cannot proceed`);
@@ -112,7 +124,7 @@ export const checkChargingStation = (
 
 export const getPhaseRotationValue = (
   connectorId: number,
-  numberOfPhases: number
+  numberOfPhases: number,
 ): string | undefined => {
   // AC/DC
   if (connectorId === 0 && numberOfPhases === 0) {
@@ -144,7 +156,7 @@ const getMaxNumberOfConnectors = (connectors: Record<string, ConnectorStatus>):
 export const getBootConnectorStatus = (
   chargingStation: ChargingStation,
   connectorId: number,
-  connectorStatus: ConnectorStatus
+  connectorStatus: ConnectorStatus,
 ): ConnectorStatusEnum => {
   let connectorBootStatus: ConnectorStatusEnum;
   if (
@@ -169,7 +181,7 @@ export const getBootConnectorStatus = (
 export const checkTemplate = (
   stationTemplate: ChargingStationTemplate,
   logPrefix: string,
-  templateFile: string
+  templateFile: string,
 ): void => {
   if (isNullOrUndefined(stationTemplate)) {
     const errorMsg = `Failed to read charging station template file ${templateFile}`;
@@ -181,16 +193,16 @@ export const checkTemplate = (
     logger.error(`${logPrefix} ${errorMsg}`);
     throw new BaseError(errorMsg);
   }
-  if (isEmptyObject(stationTemplate.AutomaticTransactionGenerator)) {
+  if (isEmptyObject(stationTemplate.AutomaticTransactionGenerator!)) {
     stationTemplate.AutomaticTransactionGenerator = Constants.DEFAULT_ATG_CONFIGURATION;
     logger.warn(
       `${logPrefix} Empty automatic transaction generator configuration from template file ${templateFile}, set to default: %j`,
-      Constants.DEFAULT_ATG_CONFIGURATION
+      Constants.DEFAULT_ATG_CONFIGURATION,
     );
   }
   if (isNullOrUndefined(stationTemplate.idTagsFile) || isEmptyString(stationTemplate.idTagsFile)) {
     logger.warn(
-      `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`
+      `${logPrefix} Missing id tags file in template file ${templateFile}. That can lead to issues with the Automatic Transaction Generator`,
     );
   }
 };
@@ -198,7 +210,7 @@ export const checkTemplate = (
 export const checkConnectorsConfiguration = (
   stationTemplate: ChargingStationTemplate,
   logPrefix: string,
-  templateFile: string
+  templateFile: string,
 ): {
   configuredMaxConnectors: number;
   templateMaxConnectors: number;
@@ -206,9 +218,9 @@ export const checkConnectorsConfiguration = (
 } => {
   const configuredMaxConnectors = getConfiguredNumberOfConnectors(stationTemplate);
   checkConfiguredMaxConnectors(configuredMaxConnectors, logPrefix, templateFile);
-  const templateMaxConnectors = getMaxNumberOfConnectors(stationTemplate.Connectors);
+  const templateMaxConnectors = getMaxNumberOfConnectors(stationTemplate.Connectors!);
   checkTemplateMaxConnectors(templateMaxConnectors, logPrefix, templateFile);
-  const templateMaxAvailableConnectors = stationTemplate?.Connectors[0]
+  const templateMaxAvailableConnectors = stationTemplate.Connectors![0]
     ? templateMaxConnectors - 1
     : templateMaxConnectors;
   if (
@@ -216,7 +228,7 @@ export const checkConnectorsConfiguration = (
     !stationTemplate?.randomConnectors
   ) {
     logger.warn(
-      `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`
+      `${logPrefix} Number of connectors exceeds the number of connector configurations in template ${templateFile}, forcing random connector configurations affectation`,
     );
     stationTemplate.randomConnectors = true;
   }
@@ -227,11 +239,11 @@ export const checkStationInfoConnectorStatus = (
   connectorId: number,
   connectorStatus: ConnectorStatus,
   logPrefix: string,
-  templateFile: string
+  templateFile: string,
 ): void => {
   if (!isNullOrUndefined(connectorStatus?.status)) {
     logger.warn(
-      `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`
+      `${logPrefix} Charging station information from template ${templateFile} with connector id ${connectorId} status configuration defined, undefine it`,
     );
     delete connectorStatus.status;
   }
@@ -240,7 +252,7 @@ export const checkStationInfoConnectorStatus = (
 export const buildConnectorsMap = (
   connectors: Record<string, ConnectorStatus>,
   logPrefix: string,
-  templateFile: string
+  templateFile: string,
 ): Map<number, ConnectorStatus> => {
   const connectorsMap = new Map<number, ConnectorStatus>();
   if (getMaxNumberOfConnectors(connectors) > 0) {
@@ -252,7 +264,7 @@ export const buildConnectorsMap = (
     }
   } else {
     logger.warn(
-      `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`
+      `${logPrefix} Charging station information from template ${templateFile} with no connectors, cannot build connectors map`,
     );
   }
   return connectorsMap;
@@ -260,26 +272,26 @@ export const buildConnectorsMap = (
 
 export const initializeConnectorsMapStatus = (
   connectors: Map<number, ConnectorStatus>,
-  logPrefix: string
+  logPrefix: string,
 ): void => {
   for (const connectorId of connectors.keys()) {
     if (connectorId > 0 && connectors.get(connectorId)?.transactionStarted === true) {
       logger.warn(
-        `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${
-          connectors.get(connectorId)?.transactionId
-        }`
+        `${logPrefix} Connector id ${connectorId} at initialization has a transaction started with id ${connectors.get(
+          connectorId,
+        )?.transactionId}`,
       );
     }
     if (connectorId === 0) {
-      connectors.get(connectorId).availability = AvailabilityType.Operative;
+      connectors.get(connectorId)!.availability = AvailabilityType.Operative;
       if (isUndefined(connectors.get(connectorId)?.chargingProfiles)) {
-        connectors.get(connectorId).chargingProfiles = [];
+        connectors.get(connectorId)!.chargingProfiles = [];
       }
     } else if (
       connectorId > 0 &&
       isNullOrUndefined(connectors.get(connectorId)?.transactionStarted)
     ) {
-      initializeConnectorStatus(connectors.get(connectorId));
+      initializeConnectorStatus(connectors.get(connectorId)!);
     }
   }
 };
@@ -289,9 +301,10 @@ export const resetConnectorStatus = (connectorStatus: ConnectorStatus): void =>
   connectorStatus.idTagAuthorized = false;
   connectorStatus.transactionRemoteStarted = false;
   connectorStatus.transactionStarted = false;
+  delete connectorStatus?.transactionStart;
+  delete connectorStatus?.transactionId;
   delete connectorStatus?.localAuthorizeIdTag;
   delete connectorStatus?.authorizeIdTag;
-  delete connectorStatus?.transactionId;
   delete connectorStatus?.transactionIdTag;
   connectorStatus.transactionEnergyActiveImportRegisterValue = 0;
   delete connectorStatus?.transactionBeginMeterValue;
@@ -299,7 +312,7 @@ export const resetConnectorStatus = (connectorStatus: ConnectorStatus): void =>
 
 export const createBootNotificationRequest = (
   stationInfo: ChargingStationInfo,
-  bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp
+  bootReason: BootReasonEnumType = BootReasonEnumType.PowerUp,
 ): BootNotificationRequest => {
   const ocppVersion = stationInfo.ocppVersion ?? OCPPVersion.VERSION_16;
   switch (ocppVersion) {
@@ -352,11 +365,12 @@ export const createBootNotificationRequest = (
 export const warnTemplateKeysDeprecation = (
   stationTemplate: ChargingStationTemplate,
   logPrefix: string,
-  templateFile: string
+  templateFile: string,
 ) => {
-  const templateKeys: { key: string; deprecatedKey: string }[] = [
-    { key: 'supervisionUrls', deprecatedKey: 'supervisionUrl' },
-    { key: 'idTagsFile', deprecatedKey: 'authorizationFile' },
+  const templateKeys: { deprecatedKey: string; key?: string }[] = [
+    { deprecatedKey: 'supervisionUrl', key: 'supervisionUrls' },
+    { deprecatedKey: 'authorizationFile', key: 'idTagsFile' },
+    { deprecatedKey: 'payloadSchemaValidation', key: 'ocppStrictCompliance' },
   ];
   for (const templateKey of templateKeys) {
     warnDeprecatedTemplateKey(
@@ -364,20 +378,20 @@ export const warnTemplateKeysDeprecation = (
       templateKey.deprecatedKey,
       logPrefix,
       templateFile,
-      `Use '${templateKey.key}' instead`
+      !isUndefined(templateKey.key) ? `Use '${templateKey.key}' instead` : undefined,
     );
     convertDeprecatedTemplateKey(stationTemplate, templateKey.deprecatedKey, templateKey.key);
   }
 };
 
 export const stationTemplateToStationInfo = (
-  stationTemplate: ChargingStationTemplate
+  stationTemplate: ChargingStationTemplate,
 ): ChargingStationInfo => {
   stationTemplate = cloneObject<ChargingStationTemplate>(stationTemplate);
   delete stationTemplate.power;
   delete stationTemplate.powerUnit;
-  delete stationTemplate?.Connectors;
-  delete stationTemplate?.Evses;
+  delete stationTemplate.Connectors;
+  delete stationTemplate.Evses;
   delete stationTemplate.Configuration;
   delete stationTemplate.AutomaticTransactionGenerator;
   delete stationTemplate.chargeBoxSerialNumberPrefix;
@@ -395,7 +409,7 @@ export const createSerialNumber = (
   } = {
     randomSerialNumberUpperCase: true,
     randomSerialNumber: true,
-  }
+  },
 ): void => {
   params = { ...{ randomSerialNumberUpperCase: true, randomSerialNumber: true }, ...params };
   const serialNumberSuffix = params?.randomSerialNumber
@@ -414,11 +428,11 @@ export const createSerialNumber = (
 export const propagateSerialNumber = (
   stationTemplate: ChargingStationTemplate,
   stationInfoSrc: ChargingStationInfo,
-  stationInfoDst: ChargingStationInfo
+  stationInfoDst: ChargingStationInfo,
 ) => {
   if (!stationInfoSrc || !stationTemplate) {
     throw new BaseError(
-      'Missing charging station template or existing configuration to propagate serial number'
+      'Missing charging station template or existing configuration to propagate serial number',
     );
   }
   stationTemplate?.chargePointSerialNumberPrefix && stationInfoSrc?.chargePointSerialNumber
@@ -450,52 +464,57 @@ export const getAmperageLimitationUnitDivider = (stationInfo: ChargingStationInf
 
 export const getChargingStationConnectorChargingProfilesPowerLimit = (
   chargingStation: ChargingStation,
-  connectorId: number
+  connectorId: number,
 ): number | undefined => {
-  let limit: number, matchingChargingProfile: ChargingProfile;
-  // Get charging profiles for connector and sort by stack level
+  let limit: number | undefined, matchingChargingProfile: ChargingProfile | undefined;
+  // Get charging profiles for connector id and sort by stack level
   const chargingProfiles =
     cloneObject<ChargingProfile[]>(
-      chargingStation.getConnectorStatus(connectorId)?.chargingProfiles
+      chargingStation.getConnectorStatus(connectorId)!.chargingProfiles!,
     )?.sort((a, b) => b.stackLevel - a.stackLevel) ?? [];
-  // Get profiles on connector 0
+  // Get charging profiles on connector 0 and sort by stack level
   if (chargingStation.getConnectorStatus(0)?.chargingProfiles) {
     chargingProfiles.push(
       ...cloneObject<ChargingProfile[]>(
-        chargingStation.getConnectorStatus(0).chargingProfiles
-      ).sort((a, b) => b.stackLevel - a.stackLevel)
+        chargingStation.getConnectorStatus(0)!.chargingProfiles!,
+      ).sort((a, b) => b.stackLevel - a.stackLevel),
     );
   }
   if (isNotEmptyArray(chargingProfiles)) {
-    const result = getLimitFromChargingProfiles(chargingProfiles, chargingStation.logPrefix());
+    const result = getLimitFromChargingProfiles(
+      chargingStation,
+      connectorId,
+      chargingProfiles,
+      chargingStation.logPrefix(),
+    );
     if (!isNullOrUndefined(result)) {
       limit = result?.limit;
       matchingChargingProfile = result?.matchingChargingProfile;
       switch (chargingStation.getCurrentOutType()) {
         case CurrentType.AC:
           limit =
-            matchingChargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+            matchingChargingProfile?.chargingSchedule?.chargingRateUnit ===
+            ChargingRateUnitType.WATT
               ? limit
               : ACElectricUtils.powerTotal(
                   chargingStation.getNumberOfPhases(),
                   chargingStation.getVoltageOut(),
-                  limit
+                  limit!,
                 );
           break;
         case CurrentType.DC:
           limit =
-            matchingChargingProfile.chargingSchedule.chargingRateUnit === ChargingRateUnitType.WATT
+            matchingChargingProfile?.chargingSchedule?.chargingRateUnit ===
+            ChargingRateUnitType.WATT
               ? limit
-              : DCElectricUtils.power(chargingStation.getVoltageOut(), limit);
+              : DCElectricUtils.power(chargingStation.getVoltageOut(), limit!);
       }
       const connectorMaximumPower =
         chargingStation.getMaximumPower() / chargingStation.powerDivider;
-      if (limit > connectorMaximumPower) {
+      if (limit! > connectorMaximumPower) {
         logger.error(
-          `${chargingStation.logPrefix()} Charging profile id ${
-            matchingChargingProfile.chargingProfileId
-          } limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
-          result
+          `${chargingStation.logPrefix()} Charging profile id ${matchingChargingProfile?.chargingProfileId} limit ${limit} is greater than connector id ${connectorId} maximum ${connectorMaximumPower}: %j`,
+          result,
         );
         limit = connectorMaximumPower;
       }
@@ -507,7 +526,7 @@ export const getChargingStationConnectorChargingProfilesPowerLimit = (
 export const getDefaultVoltageOut = (
   currentType: CurrentType,
   logPrefix: string,
-  templateFile: string
+  templateFile: string,
 ): Voltage => {
   const errorMsg = `Unknown ${currentType} currentOutType in template file ${templateFile}, cannot define default voltage out`;
   let defaultVoltageOut: number;
@@ -535,9 +554,9 @@ export const getIdTagsFile = (stationInfo: ChargingStationInfo): string | undefi
 export const waitChargingStationEvents = async (
   emitter: EventEmitter,
   event: ChargingStationWorkerMessageEvents,
-  eventsToWait: number
+  eventsToWait: number,
 ): Promise<number> => {
-  return new Promise((resolve) => {
+  return new Promise<number>((resolve) => {
     let events = 0;
     if (eventsToWait === 0) {
       resolve(events);
@@ -552,7 +571,7 @@ export const waitChargingStationEvents = async (
 };
 
 const getConfiguredNumberOfConnectors = (stationTemplate: ChargingStationTemplate): number => {
-  let configuredMaxConnectors: number;
+  let configuredMaxConnectors = 0;
   if (isNotEmptyArray(stationTemplate.numberOfConnectors) === true) {
     const numberOfConnectors = stationTemplate.numberOfConnectors as number[];
     configuredMaxConnectors =
@@ -560,11 +579,10 @@ const getConfiguredNumberOfConnectors = (stationTemplate: ChargingStationTemplat
   } else if (isUndefined(stationTemplate.numberOfConnectors) === false) {
     configuredMaxConnectors = stationTemplate.numberOfConnectors as number;
   } else if (stationTemplate.Connectors && !stationTemplate.Evses) {
-    configuredMaxConnectors = stationTemplate?.Connectors[0]
+    configuredMaxConnectors = stationTemplate.Connectors[0]
       ? getMaxNumberOfConnectors(stationTemplate.Connectors) - 1
       : getMaxNumberOfConnectors(stationTemplate.Connectors);
   } else if (stationTemplate.Evses && !stationTemplate.Connectors) {
-    configuredMaxConnectors = 0;
     for (const evse in stationTemplate.Evses) {
       if (evse === '0') {
         continue;
@@ -578,11 +596,11 @@ const getConfiguredNumberOfConnectors = (stationTemplate: ChargingStationTemplat
 const checkConfiguredMaxConnectors = (
   configuredMaxConnectors: number,
   logPrefix: string,
-  templateFile: string
+  templateFile: string,
 ): void => {
   if (configuredMaxConnectors <= 0) {
     logger.warn(
-      `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`
+      `${logPrefix} Charging station information from template ${templateFile} with ${configuredMaxConnectors} connectors`,
     );
   }
 };
@@ -590,15 +608,15 @@ const checkConfiguredMaxConnectors = (
 const checkTemplateMaxConnectors = (
   templateMaxConnectors: number,
   logPrefix: string,
-  templateFile: string
+  templateFile: string,
 ): void => {
   if (templateMaxConnectors === 0) {
     logger.warn(
-      `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`
+      `${logPrefix} Charging station information from template ${templateFile} with empty connectors configuration`,
     );
   } else if (templateMaxConnectors < 0) {
     logger.error(
-      `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`
+      `${logPrefix} Charging station information from template ${templateFile} with no connectors configuration defined`,
     );
   }
 };
@@ -621,9 +639,9 @@ const warnDeprecatedTemplateKey = (
   key: string,
   logPrefix: string,
   templateFile: string,
-  logMsgToAppend = ''
+  logMsgToAppend = '',
 ): void => {
-  if (!isUndefined(template[key])) {
+  if (!isUndefined(template[key as keyof ChargingStationTemplate])) {
     const logMsg = `Deprecated template key '${key}' usage in file '${templateFile}'${
       isNotEmptyString(logMsgToAppend) ? `. ${logMsgToAppend}` : ''
     }`;
@@ -635,119 +653,208 @@ const warnDeprecatedTemplateKey = (
 const convertDeprecatedTemplateKey = (
   template: ChargingStationTemplate,
   deprecatedKey: string,
-  key: string
+  key?: string,
 ): void => {
-  if (!isUndefined(template[deprecatedKey])) {
-    template[key] = template[deprecatedKey] as unknown;
-    delete template[deprecatedKey];
+  if (!isUndefined(template[deprecatedKey as keyof ChargingStationTemplate])) {
+    if (!isUndefined(key)) {
+      (template as unknown as Record<string, unknown>)[key!] =
+        template[deprecatedKey as keyof ChargingStationTemplate];
+    }
+    delete template[deprecatedKey as keyof ChargingStationTemplate];
   }
 };
 
+interface ChargingProfilesLimit {
+  limit: number;
+  matchingChargingProfile: ChargingProfile;
+}
+
 /**
  * Charging profiles should already be sorted by connector id and stack level (highest stack level has priority)
  *
  * @param chargingProfiles -
  * @param logPrefix -
- * @returns
+ * @returns ChargingProfilesLimit
  */
 const getLimitFromChargingProfiles = (
+  chargingStation: ChargingStation,
+  connectorId: number,
   chargingProfiles: ChargingProfile[],
-  logPrefix: string
-): {
-  limit: number;
-  matchingChargingProfile: ChargingProfile;
-} | null => {
+  logPrefix: string,
+): ChargingProfilesLimit | undefined => {
   const debugLogMsg = `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Matching charging profile found for power limitation: %j`;
-  const currentMoment = moment();
   const currentDate = new Date();
+  const connectorStatus = chargingStation.getConnectorStatus(connectorId);
   for (const chargingProfile of chargingProfiles) {
-    // Set helpers
+    if (
+      isValidDate(chargingProfile.validFrom) &&
+      isValidDate(chargingProfile.validTo) &&
+      !isWithinInterval(currentDate, {
+        start: chargingProfile.validFrom!,
+        end: chargingProfile.validTo!,
+      })
+    ) {
+      logger.debug(
+        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Charging profile id ${
+          chargingProfile.chargingProfileId
+        } is not valid for the current date ${currentDate.toISOString()}`,
+      );
+      continue;
+    }
     const chargingSchedule = chargingProfile.chargingSchedule;
-    if (!chargingSchedule?.startSchedule) {
+    if (connectorStatus?.transactionStarted && !chargingSchedule?.startSchedule) {
+      logger.debug(
+        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}. Trying to set it to the connector transaction start date`,
+      );
+      // OCPP specifies that if startSchedule is not defined, it should be relative to start of the connector transaction
+      chargingSchedule.startSchedule = connectorStatus?.transactionStart;
+    }
+    if (!(chargingSchedule?.startSchedule instanceof Date)) {
       logger.warn(
-        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not defined in charging profile id ${chargingProfile.chargingProfileId}`
+        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`,
       );
+      chargingSchedule.startSchedule = convertToDate(chargingSchedule.startSchedule)!;
     }
-    // Check type (recurring) and if it is already active
-    // Adjust the daily recurring schedule to today
     if (
       chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING &&
-      chargingProfile.recurrencyKind === RecurrencyKindType.DAILY &&
-      currentMoment.isAfter(chargingSchedule.startSchedule)
+      isNullOrUndefined(chargingProfile.recurrencyKind)
     ) {
-      if (!(chargingSchedule?.startSchedule instanceof Date)) {
-        logger.warn(
-          `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: startSchedule is not a Date object in charging profile id ${chargingProfile.chargingProfileId}. Trying to convert it to a Date object`
-        );
-        chargingSchedule.startSchedule = new Date(chargingSchedule.startSchedule);
-      }
-      chargingSchedule.startSchedule.setFullYear(
-        currentDate.getFullYear(),
-        currentDate.getMonth(),
-        currentDate.getDate()
+      logger.error(
+        `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring charging profile id ${chargingProfile.chargingProfileId} has no recurrencyKind defined`,
       );
-      // Check if the start of the schedule is yesterday
-      if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
-        chargingSchedule.startSchedule.setDate(currentDate.getDate() - 1);
-      }
-    } else if (moment(chargingSchedule.startSchedule).isAfter(currentMoment)) {
-      return null;
+      continue;
+    }
+    if (chargingProfile.chargingProfileKind === ChargingProfileKindType.RECURRING) {
+      prepareRecurringChargingProfile(chargingProfile, currentDate, logPrefix);
+    } else if (
+      chargingProfile.chargingProfileKind === ChargingProfileKindType.RELATIVE &&
+      connectorStatus?.transactionStarted
+    ) {
+      chargingSchedule.startSchedule = connectorStatus?.transactionStart;
     }
     // Check if the charging profile is active
     if (
-      moment(chargingSchedule.startSchedule)
-        .add(chargingSchedule.duration, 's')
-        .isAfter(currentMoment)
+      isValidDate(chargingSchedule.startSchedule) &&
+      isAfter(addSeconds(chargingSchedule.startSchedule!, chargingSchedule.duration!), currentDate)
     ) {
-      let lastButOneSchedule: ChargingSchedulePeriod;
-      // Search the right schedule period
-      for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
-        // Handling of only one period
+      if (isNotEmptyArray(chargingSchedule.chargingSchedulePeriod)) {
+        // Handling of only one schedule period
         if (
           chargingSchedule.chargingSchedulePeriod.length === 1 &&
-          schedulePeriod.startPeriod === 0
+          chargingSchedule.chargingSchedulePeriod[0].startPeriod === 0
         ) {
-          const result = {
-            limit: schedulePeriod.limit,
+          const result: ChargingProfilesLimit = {
+            limit: chargingSchedule.chargingSchedulePeriod[0].limit,
             matchingChargingProfile: chargingProfile,
           };
           logger.debug(debugLogMsg, result);
           return result;
         }
-        // Find the right schedule period
-        if (
-          moment(chargingSchedule.startSchedule)
-            .add(schedulePeriod.startPeriod, 's')
-            .isAfter(currentMoment)
-        ) {
-          // Found the schedule: last but one is the correct one
-          const result = {
-            limit: lastButOneSchedule.limit,
-            matchingChargingProfile: chargingProfile,
-          };
-          logger.debug(debugLogMsg, result);
-          return result;
-        }
-        // Keep it
-        lastButOneSchedule = schedulePeriod;
-        // Handle the last schedule period
-        if (
-          schedulePeriod.startPeriod ===
-          chargingSchedule.chargingSchedulePeriod[
-            chargingSchedule.chargingSchedulePeriod.length - 1
-          ].startPeriod
-        ) {
-          const result = {
-            limit: lastButOneSchedule.limit,
-            matchingChargingProfile: chargingProfile,
-          };
-          logger.debug(debugLogMsg, result);
-          return result;
+        let lastButOneSchedule: ChargingSchedulePeriod | undefined;
+        // Search for the right schedule period
+        for (const schedulePeriod of chargingSchedule.chargingSchedulePeriod) {
+          // Find the right schedule period
+          if (
+            isAfter(
+              addSeconds(chargingSchedule.startSchedule!, schedulePeriod.startPeriod),
+              currentDate,
+            )
+          ) {
+            // Found the schedule period: last but one is the correct one
+            const result: ChargingProfilesLimit = {
+              limit: lastButOneSchedule!.limit,
+              matchingChargingProfile: chargingProfile,
+            };
+            logger.debug(debugLogMsg, result);
+            return result;
+          }
+          // Keep it
+          lastButOneSchedule = schedulePeriod;
+          // Handle the last schedule period
+          if (
+            schedulePeriod.startPeriod ===
+            chargingSchedule.chargingSchedulePeriod[
+              chargingSchedule.chargingSchedulePeriod.length - 1
+            ].startPeriod
+          ) {
+            const result: ChargingProfilesLimit = {
+              limit: lastButOneSchedule.limit,
+              matchingChargingProfile: chargingProfile,
+            };
+            logger.debug(debugLogMsg, result);
+            return result;
+          }
         }
       }
     }
   }
-  return null;
+};
+
+/**
+ *  Adjust recurring charging profile startSchedule to the current recurrency time interval if needed
+ *
+ * @param chargingProfile -
+ * @param currentDate -
+ * @param logPrefix -
+ */
+const prepareRecurringChargingProfile = (
+  chargingProfile: ChargingProfile,
+  currentDate: Date,
+  logPrefix: string,
+) => {
+  const chargingSchedule = chargingProfile.chargingSchedule;
+  let recurringInterval: Interval;
+  switch (chargingProfile.recurrencyKind) {
+    case RecurrencyKindType.DAILY:
+      recurringInterval = {
+        start: chargingSchedule.startSchedule!,
+        end: addDays(chargingSchedule.startSchedule!, 1),
+      };
+      if (
+        !isWithinInterval(currentDate, recurringInterval) &&
+        isBefore(chargingSchedule.startSchedule!, currentDate)
+      ) {
+        chargingSchedule.startSchedule = addDays(
+          chargingSchedule.startSchedule!,
+          differenceInDays(chargingSchedule.startSchedule!, recurringInterval.end),
+        );
+        recurringInterval = {
+          start: chargingSchedule.startSchedule,
+          end: addDays(chargingSchedule.startSchedule, 1),
+        };
+      }
+      break;
+    case RecurrencyKindType.WEEKLY:
+      recurringInterval = {
+        start: chargingSchedule.startSchedule!,
+        end: addWeeks(chargingSchedule.startSchedule!, 1),
+      };
+      if (
+        !isWithinInterval(currentDate, recurringInterval) &&
+        isBefore(chargingSchedule.startSchedule!, currentDate)
+      ) {
+        chargingSchedule.startSchedule = addWeeks(
+          chargingSchedule.startSchedule!,
+          differenceInWeeks(chargingSchedule.startSchedule!, recurringInterval.end),
+        );
+        recurringInterval = {
+          start: chargingSchedule.startSchedule,
+          end: addWeeks(chargingSchedule.startSchedule, 1),
+        };
+      }
+      break;
+  }
+  if (!isWithinInterval(currentDate, recurringInterval!)) {
+    logger.error(
+      `${logPrefix} ${moduleName}.getLimitFromChargingProfiles: Recurring ${
+        chargingProfile.recurrencyKind
+      } charging profile id ${
+        chargingProfile.chargingProfileId
+      } startSchedule ${chargingSchedule.startSchedule!.toISOString()} is not properly translated to current recurrency time interval [${toDate(
+        recurringInterval!.start,
+      ).toISOString()}, ${toDate(recurringInterval!.end).toISOString()}]`,
+    );
+  }
 };
 
 const getRandomSerialNumberSuffix = (params?: {