Support overnight blocks in schedule validation
This commit is contained in:
@@ -49,14 +49,60 @@ export function snapMinutes(minutes: number): number {
|
||||
return Math.max(0, Math.min(MINUTES_PER_DAY, snapped));
|
||||
}
|
||||
|
||||
/** First sibling whose half-open range [start, end) intersects the input. */
|
||||
/** A Block whose end clock time is at or before its start wraps past midnight. */
|
||||
export function isOvernight(block: { startMinutes: number; endMinutes: number }): boolean {
|
||||
return block.endMinutes <= block.startMinutes;
|
||||
}
|
||||
|
||||
/** Block length in minutes, counting a wrap past midnight as the next day. */
|
||||
export function blockDuration(block: { startMinutes: number; endMinutes: number }): number {
|
||||
if (block.endMinutes > block.startMinutes) {
|
||||
return block.endMinutes - block.startMinutes;
|
||||
}
|
||||
return block.endMinutes - block.startMinutes + MINUTES_PER_DAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a Block into the linear half-open `[start, end)` segments it occupies
|
||||
* within the 24h day. An overnight block produces two segments (start→midnight
|
||||
* and midnight→end); a zero-length block produces none.
|
||||
*/
|
||||
export function blockSegments(block: {
|
||||
startMinutes: number;
|
||||
endMinutes: number;
|
||||
}): [number, number][] {
|
||||
if (block.startMinutes < block.endMinutes) {
|
||||
return [[block.startMinutes, block.endMinutes]];
|
||||
}
|
||||
if (block.startMinutes > block.endMinutes) {
|
||||
return [
|
||||
[block.startMinutes, MINUTES_PER_DAY],
|
||||
[0, block.endMinutes]
|
||||
];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function segmentsIntersect(a: [number, number], b: [number, number]): boolean {
|
||||
return a[0] < b[1] && b[0] < a[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* First sibling whose occupied time intersects the input. Works for overnight
|
||||
* (midnight-crossing) blocks by comparing their linear segments; ranges that
|
||||
* only touch at an endpoint (e.g. 9PM–9AM vs 9AM–9PM) do not overlap.
|
||||
*/
|
||||
export function findOverlap(
|
||||
input: { startMinutes: number; endMinutes: number },
|
||||
siblings: BlockSibling[]
|
||||
): BlockSibling | null {
|
||||
const inputSegments = blockSegments(input);
|
||||
for (const sibling of siblings) {
|
||||
if (input.startMinutes < sibling.endMinutes && sibling.startMinutes < input.endMinutes) {
|
||||
return sibling;
|
||||
const siblingSegments = blockSegments(sibling);
|
||||
for (const segment of inputSegments) {
|
||||
for (const other of siblingSegments) {
|
||||
if (segmentsIntersect(segment, other)) return sibling;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -78,13 +124,17 @@ export function validateBlock(
|
||||
!Number.isInteger(startMinutes) ||
|
||||
!Number.isInteger(endMinutes) ||
|
||||
startMinutes < 0 ||
|
||||
endMinutes > MINUTES_PER_DAY ||
|
||||
startMinutes >= endMinutes
|
||||
startMinutes > MINUTES_PER_DAY ||
|
||||
endMinutes < 0 ||
|
||||
endMinutes > MINUTES_PER_DAY
|
||||
) {
|
||||
return {
|
||||
ok: false,
|
||||
error: "Start time must be before end time. Overnight blocks aren't supported."
|
||||
};
|
||||
return { ok: false, error: 'Enter a valid start and end time.' };
|
||||
}
|
||||
|
||||
// Equal clock times would be a zero- or full-length block; both are
|
||||
// ambiguous, so reject them. Overnight ranges (end before start) are valid.
|
||||
if (startMinutes === endMinutes) {
|
||||
return { ok: false, error: "Start and end time can't be the same." };
|
||||
}
|
||||
|
||||
if (!Array.isArray(proclamationIds) || proclamationIds.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user