Web:Component:Cron Added a method for getting the time of the next synchronization

This commit is contained in:
Akmal Isomadinov 2023-08-17 13:11:45 +05:00
parent 9b1f7d3f8e
commit 3e4a1a5ae1
4 changed files with 120 additions and 10 deletions

View File

@ -1,6 +1,8 @@
import React, { useEffect, useState } from "react";
import i18n from "i18next";
import React, { useEffect, useMemo, useState } from "react";
import { initReactI18next } from "react-i18next";
import i18n from "i18next";
import Cron, { getNextSynchronization } from ".";
import type { Meta, StoryObj } from "@storybook/react";
@ -53,8 +55,6 @@ i18n.use(initReactI18next).init({
},
});
import Cron from ".";
type CronType = typeof Cron;
type Story = StoryObj<CronType>;
@ -88,9 +88,14 @@ export const Default: Story = {
setValue(defaultValue);
}, [defaultValue]);
const date = useMemo(() => cron && getNextSynchronization(cron), [cron]);
return (
<div>
<input
style={{
borderColor: error ? "red" : "black",
}}
value={input}
onChange={(e) => setInput(e.target.value)}
onBlur={(e) => setCron(e.target.value)}
@ -102,6 +107,12 @@ export const Default: Story = {
<p>
<strong>Error message: </strong> {error?.message ?? "undefined"}
</p>
{date && (
<p>
<strong>Next synchronization: </strong>{" "}
{date.toUTC().setLocale("en-GB").toFormat("DDDD tt")}
</p>
)}
</div>
);
},

View File

@ -1 +1,2 @@
export { default } from "./Cron";
export { getNextSynchronization } from "./part";

View File

@ -1,8 +1,14 @@
import { DateTime } from "luxon";
import { Options, PeriodType } from "./types";
import { defaultOptions, units } from "./constants";
import { arrayToStringPart, assertValidArray, stringToArrayPart } from "./util";
import {
arrayToStringPart,
assertValidArray,
findDate,
stringToArrayPart,
} from "./util";
export const stringToArray = (str: string) => {
export const stringToArray = (str: string, full = false) => {
if (typeof str !== "string") {
throw new Error("Invalid cron string");
}
@ -10,7 +16,7 @@ export const stringToArray = (str: string) => {
if (parts.length !== 5) {
throw new Error("Invalid cron string format");
} else {
return parts.map((str, idx) => stringToArrayPart(str, units[idx]));
return parts.map((str, idx) => stringToArrayPart(str, units[idx], full));
}
};
@ -51,3 +57,25 @@ export function getCronStringFromValues(
return parsedArray;
}
export const getNextSynchronization = (
cronString: string,
timezone?: string
) => {
const cron = stringToArray(cronString, true);
assertValidArray(cron);
let date = DateTime.now();
if (timezone) date = date.setZone(timezone);
if (!date.isValid) {
throw new Error("Invalid timezone provided");
}
if (date.second > 0) {
// plus a minute to the date to prevent returning dates in the past
date = date.plus({ minute: 1 });
}
return findDate(cron, date);
};

View File

@ -1,3 +1,4 @@
import { DateTime } from "luxon";
import { Options, PeriodType, Unit } from "./types";
export const parseNumber = (value: unknown) => {
@ -17,7 +18,7 @@ export const parseNumber = (value: unknown) => {
return undefined;
};
export const assertValidArray = (arr: unknown) => {
export const assertValidArray = (arr: unknown): void | never => {
if (
arr === undefined ||
!Array.isArray(arr) ||
@ -100,8 +101,8 @@ export const arrayToStringPart = (
return toString(values, unit, options);
};
export const stringToArrayPart = (str: string, unit: Unit) => {
if (str === "*" || str === "*/1") {
export const stringToArrayPart = (str: string, unit: Unit, full = false) => {
if ((str === "*" || str === "*/1") && !full) {
return [];
}
@ -332,3 +333,72 @@ const getStep = (values: number[]) => {
const isFull = (values: number[], unit: Unit) => {
return values.length === unit.max - unit.min + 1;
};
const shiftMonth = (arr: number[][], date: DateTime) => {
while (arr[3].indexOf(date.month) === -1) {
date = date.plus({ months: 1 }).startOf("month");
}
return date;
};
const shiftDay = (arr: number[][], date: DateTime): [DateTime, boolean] => {
const currentMonth = date.month;
while (
arr[2].indexOf(date.day) === -1 ||
// luxon uses 1-7 for weekdays, but we use 0-6
arr[4].indexOf(date.weekday === 7 ? 0 : date.weekday) === -1
) {
date = date.plus({ days: 1 }).startOf("day");
if (currentMonth !== date.month) {
return [date, true];
}
}
return [date, false];
};
const shiftHour = (arr: number[][], date: DateTime): [DateTime, boolean] => {
const currentDay = date.day;
while (arr[1].indexOf(date.hour) === -1) {
date = date.plus({ hours: 1 }).startOf("hour");
if (currentDay !== date.day) {
return [date, true];
}
}
return [date, false];
};
const shiftMinute = (arr: number[][], date: DateTime): [DateTime, boolean] => {
const currentHour = date.hour;
while (arr[0].indexOf(date.minute) === -1) {
date = date.plus({ minutes: 1 }).startOf("minute");
if (currentHour !== date.hour) {
return [date, true];
}
}
return [date, false];
};
export const findDate = (arr: number[][], date: DateTime) => {
let retry = 24;
let monthChanged: boolean;
let dayChanged: boolean;
let hourChanged: boolean;
while (--retry) {
date = shiftMonth(arr, date);
[date, monthChanged] = shiftDay(arr, date);
if (!monthChanged) {
[date, dayChanged] = shiftHour(arr, date);
if (!dayChanged) {
[date, hourChanged] = shiftMinute(arr, date);
if (!hourChanged) {
break;
}
}
}
}
if (!retry) {
throw new Error("Unable to find execution time for schedule");
}
return date.set({ second: 0, millisecond: 0 });
};