Katkat

Weekly Maintenance Windows Across Time Zones: Find Usable Slots

StripeSoftware Engineer · Onsite

Last 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_utc is the current minute of the current UTC week (0 <= now_utc < W). Work cannot start before lo = 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 first limit slots in time order.
  • Output times are on one continuous UTC line: minute W is the start of next week, so values may exceed W.

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.