mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 00:36:23 +08:00
168 lines
3.8 KiB
TypeScript
168 lines
3.8 KiB
TypeScript
import { Cron } from 'croner';
|
|
|
|
import { AbsoluteTimeRange, TimeRange, durationToMilliseconds, parseDuration } from '@grafana/data';
|
|
|
|
export type TimeRegionMode = null | 'cron';
|
|
export interface TimeRegionConfig {
|
|
mode?: TimeRegionMode;
|
|
|
|
from?: string;
|
|
fromDayOfWeek?: number; // 1-7
|
|
|
|
to?: string;
|
|
toDayOfWeek?: number; // 1-7
|
|
|
|
timezone?: string;
|
|
|
|
cronExpr?: string; // 0 9 * * 1-5
|
|
duration?: string; // 8h
|
|
}
|
|
|
|
const secsInDay = 24 * 3600;
|
|
|
|
function getDurationSecs(
|
|
fromDay: number,
|
|
fromHour: number,
|
|
fromMin: number,
|
|
toDay: number,
|
|
toHour: number,
|
|
toMin: number
|
|
) {
|
|
let days = toDay - fromDay;
|
|
|
|
// account for rollover
|
|
if (days < 0) {
|
|
days += 7;
|
|
}
|
|
|
|
let fromSecs = fromHour * 3600 + fromMin * 60;
|
|
let toSecs = toHour * 3600 + toMin * 60;
|
|
|
|
let durSecs = 0;
|
|
|
|
// account for toTime < fromTime on same day
|
|
if (days === 0 && toSecs < fromSecs) {
|
|
durSecs = 7 * secsInDay - (fromSecs - toSecs);
|
|
} else {
|
|
let daysSecs = days * secsInDay;
|
|
durSecs = daysSecs - fromSecs + toSecs;
|
|
}
|
|
|
|
return durSecs;
|
|
}
|
|
|
|
export function convertToCron(
|
|
fromDay?: number | null,
|
|
from?: string | null,
|
|
toDay?: number | null,
|
|
to?: string | null
|
|
) {
|
|
// valid defs must have a "from"
|
|
if (fromDay == null && from == null) {
|
|
return undefined;
|
|
}
|
|
|
|
const cronCfg = {
|
|
cronExpr: '',
|
|
duration: 0,
|
|
};
|
|
|
|
const isEveryDay = fromDay == null && toDay == null;
|
|
// if the def contains only days of week, then they become end-day-inclusive
|
|
const toDayEnd = fromDay != null && from == null && to == null;
|
|
|
|
from ??= '00:00';
|
|
|
|
// 1. create cron (only requires froms)
|
|
let [fromHour, fromMin] = from.split(':').map((v) => +v);
|
|
|
|
cronCfg.cronExpr = `${fromMin} ${fromHour} * * ${fromDay ?? '*'}`;
|
|
|
|
// 2. determine duration
|
|
fromDay ??= 1;
|
|
toDay ??= fromDay;
|
|
|
|
// e.g. from Wed to Fri (implies inclusive Fri)
|
|
if (toDayEnd) {
|
|
to = '00:00';
|
|
toDay += toDay === 7 ? -6 : 1;
|
|
}
|
|
|
|
to ??= from;
|
|
|
|
let [toHour, toMin] = to.split(':').map((v) => +v);
|
|
|
|
let fromSecs = fromHour * 3600 + fromMin * 60;
|
|
let toSecs = toHour * 3600 + toMin * 60;
|
|
|
|
// e.g. every day from 22:00 to 02:00 (implied next day)
|
|
// NOTE: the odd wrap-around case of toSecs < fromSecs in same day is handled inside getDurationSecs()
|
|
if (isEveryDay && toSecs < fromSecs) {
|
|
toDay += toDay === 7 ? -6 : 1;
|
|
}
|
|
|
|
cronCfg.duration = getDurationSecs(fromDay, fromHour, fromMin, toDay, toHour, toMin);
|
|
|
|
return cronCfg;
|
|
}
|
|
|
|
export function calculateTimesWithin(cfg: TimeRegionConfig, tRange: TimeRange): AbsoluteTimeRange[] {
|
|
const ranges: AbsoluteTimeRange[] = [];
|
|
|
|
let cronExpr = '';
|
|
let durationMs = 0;
|
|
|
|
let { fromDayOfWeek, from, toDayOfWeek, to, duration = '' } = cfg;
|
|
|
|
if (cfg.mode === 'cron') {
|
|
cronExpr = cfg.cronExpr ?? '';
|
|
durationMs = durationToMilliseconds(parseDuration(duration));
|
|
} else {
|
|
// remove empty strings
|
|
from = from === '' ? undefined : from;
|
|
to = to === '' ? undefined : to;
|
|
|
|
const cron = convertToCron(fromDayOfWeek, from, toDayOfWeek, to);
|
|
|
|
if (cron != null) {
|
|
cronExpr = cron.cronExpr;
|
|
durationMs = cron.duration * 1e3;
|
|
}
|
|
}
|
|
|
|
if (cronExpr === '') {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
let tz = cfg.timezone === 'browser' ? undefined : cfg.timezone === 'utc' ? 'Etc/UTC' : cfg.timezone;
|
|
|
|
let job = new Cron(cronExpr, { timezone: tz });
|
|
|
|
// get previous run that may overlap with start of timerange
|
|
let fromDate: Date | null = new Date(tRange.from.valueOf() - durationMs);
|
|
|
|
let toMs = tRange.to.valueOf();
|
|
let nextDate = job.nextRun(fromDate);
|
|
|
|
while (nextDate != null) {
|
|
let nextMs = +nextDate;
|
|
|
|
if (nextMs > toMs) {
|
|
break;
|
|
} else {
|
|
ranges.push({
|
|
from: nextMs,
|
|
to: nextMs + durationMs,
|
|
});
|
|
nextDate = job.nextRun(nextDate);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// invalid expression
|
|
console.error(e);
|
|
}
|
|
|
|
return ranges;
|
|
}
|