Weekly Maintenance Windows Across Time Zones: Find Usable Slots
StripeSoftware Engineer · OnsiteLast updated
Combine recurring 'open' maintenance windows and 'blackout' periods, declared in different local time zones, into the next usable UTC slots after a notice period; tests interval merging, subtraction and week wrap-around.
A database platform team runs maintenance on a schedule that repeats every week. Time is measured in minutes on a cycle of W = 10080 minutes (7 x 24 x 60). Regional teams publish rules:
"open"rules: maintenance may run during this window."blackout"rules: maintenance must not run (blackout always wins over open).
A minute is usable if it is covered by at least one open rule and by no blackout rule. A slot is a maximal run of consecutive usable minutes, written as a half-open interval [start, end).
Part 1 - Single week, UTC only
Each rule is (start, end, kind) with 0 <= start < end <= W, in UTC. Return all slots in [0, W) sorted by start.
rules = [(540, 1020, "open"), (1980, 2460, "open"),
(600, 660, "blackout"), (2400, 2520, "blackout")]
-> [[540, 600], [660, 1020], [1980, 2400]]
Part 2 - Local time zones and wrap-around
Each rule becomes (start, end, kind, utc_offset). Times are in the rule's local week; utc_offset is local minus UTC (UTC+2 -> 120, UTC-1 -> -60). In UTC the rule covers [start - utc_offset, end - utc_offset), and it repeats every W minutes in both directions, so a converted window may spill into the previous or next week.
Part 3 - Planning from now
Implement:
def maintenance_slots(now_utc: int, notice: int, min_length: int,
limit: int, rules: list[tuple[int, int, str, int]]) -> list[list[int]]
now_utcis the current minute of the current UTC week (0 <= now_utc < W). Work cannot start beforelo = now_utc + notice.- Only the horizon
[lo, lo + W)is considered; slots are clipped to it. - Drop slots shorter than
min_length, then return the firstlimitslots in time order. - Output times are on one continuous UTC line: minute
Wis the start of next week, so values may exceedW.
Part 1 is now_utc = 0, notice = 0, min_length = 1, all offsets 0, large limit.
rules = [(9900, 10080, "open", 0), # Sun 21:00-24:00 UTC
(0, 120, "open", 0), # Mon 00:00-02:00 UTC
(480, 600, "open", 120), # Mon 08:00-10:00 at UTC+2
(10020, 10080, "blackout", -60)] # Sun 23:00-24:00 at UTC-1
maintenance_slots(0, 0, 1, 99, rules)
-> [[60, 120], [360, 480], [9900, 10080]]
maintenance_slots(9950, 30, 45, 2, rules)
-> [[9980, 10080], [10140, 10200]]
In the second call lo = 9980. The Sunday-night open window and next Monday's 00:00-02:00 window touch at 10080, but the blackout (Monday 00:00-01:00 UTC) cuts out [10080, 10140).
Constraints
1 <= len(rules) <= 10^4,0 <= notice <= 10^5,|utc_offset| <= 1440,min_length >= 1,limit >= 1.
Hints
Hint 1
Convert every rule to UTC and generate only the weekly copies that overlap the horizon [lo, lo + W).
Hint 2
Clip copies to the horizon, then merge the open list and the blackout list separately.
Hint 3
Subtract merged blackouts from merged opens with a two-pointer sweep.
Hint 4
Apply the min_length filter after clipping and subtraction - a slot cut by the horizon edge may become too short.
Follow-up questions
- How would you answer many queries with different now_utc values against the same rules without recomputing everything?
- Real time zones have daylight-saving changes. How would you model rules defined in a named zone like Europe/Berlin?
- How would you support a priority system where some blackouts can be overridden by emergency open windows?
- Return the slot that maximizes overlap with the low-traffic hours of all regions instead of the earliest slots.
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.