Skip to content
Harness Engineering2026-09-108 min readRev. 2026-10-02

A Contamination Cutoff Needs a Clock, Not Just a Date

A training cutoff of March 31 is a date without a clock. One post can land on either side of it depending on the time zone. How to pick one clock, size the ambiguous boundary day and keep it out of a held-out split.

Key takeaways

  • A post made at 11:30 p.m. in Los Angeles on March 31 is on March 31 by Pacific time and April 1 by UTC: inside or outside a date cutoff depending on the clock.
  • If you do not know which clock a source used, the boundary day is 26 hours wide, because time zones run from UTC-12 to UTC+14.
  • Pick one clock, convert every source timestamp to it once, record the choice in the eval's metadata, and exclude or separately report items on the boundary day.
  • Store the full timestamp with its offset; a date computed in an unknown zone cannot be converted back.
  • Daylight-saving transitions change the offset, so derive it from zoneinfo and test both halves of the year.

Rod Rivera

Author

A Contamination Cutoff Needs a Clock, Not Just a Date

Rod's note: read with a pencil; the margins are for you.

One post, two sides of the cutoff

To test a model on material it cannot have seen in training, you keep only items published after its training data ends. That is a contamination cutoff, and it is usually stated as a date: "trained on data through March 31." A date is not an instant. It hides a clock, and the clock decides which side of the cutoff an item lands on.

python
from datetime import datetime, date
from zoneinfo import ZoneInfo

CUTOFF = date(2026, 3, 31)  # "the model was trained on data through March 31"
posted = datetime(2026, 3, 31, 23, 30, tzinfo=ZoneInfo("America/Los_Angeles"))

for zone in ["America/Los_Angeles", "UTC", "Asia/Tokyo"]:
    local_day = posted.astimezone(ZoneInfo(zone)).date()
    verdict = "after the cutoff" if local_day > CUTOFF else "not after the cutoff"
    print(f"{zone:20} {local_day}  {verdict}")
text
America/Los_Angeles  2026-03-31  not after the cutoff
UTC                  2026-04-01  after the cutoff
Asia/Tokyo           2026-04-01  after the cutoff

One post, made at 23:30 Pacific time, is inside the held-out split by one clock and outside it by another. On a large set, the items near the boundary are a small share, but they are exactly the ones whose status you cannot defend.

How wide the boundary day is

Suppose a source gives you dates with no clock at all. Which instants could it have called "after March 31"? Time zones in use run from UTC-12 to UTC+14, so the first zone reaches April 1 a full 26 hours before the last one does.

python
from datetime import date, datetime, time, timedelta, timezone
from zoneinfo import ZoneInfo

CUTOFF = date(2026, 3, 31)
next_day = CUTOFF + timedelta(days=1)

def start_of(day: date, zone: str) -> datetime:
    return datetime.combine(day, time(0), tzinfo=ZoneInfo(zone)).astimezone(timezone.utc)

first = start_of(next_day, "Pacific/Kiritimati")  # UTC+14: the first zone to reach April 1
last = start_of(next_day, "Etc/GMT+12")           # UTC-12: the last (POSIX names flip the sign)
print("some zone says April 1 from:", first)
print("every zone says April 1 from:", last)
print("ambiguous window:", last - first)

Run on Python 3.12, it prints:

text
some zone says April 1 from: 2026-03-31 10:00:00+00:00
every zone says April 1 from: 2026-04-01 12:00:00+00:00
ambiguous window: 1 day, 2:00:00

Before 10:00 UTC on March 31, no zone calls the item "after the cutoff." From 12:00 UTC on April 1, every zone does. In between, the answer depends on a clock you were not told. A stated training cutoff is itself a date without a clock, which is one more reason to leave a margin.

Pick one clock and convert once

The fix is a rule, written down before you split anything:

  • Pick one clock. UTC instants are the usual choice.
  • Convert every source timestamp to it once, at ingestion, and reject timestamps with no offset. Python can read a naive datetime in the host's local zone, which puts an undeclared machine setting into the split.
  • Record the clock and the cutoff instant in the evaluation's metadata.
  • Treat items in the ambiguous window as their own group: exclude them, or report the metric with and without them.
Keep the instant, not only the date

Store the full timestamp with its offset for every item, and derive dates from it when you need them. A date column that was computed in some unknown zone cannot be converted back.

Daylight saving moves the offset

Offsets are not fixed. Pacific time is UTC-7 in summer and UTC-8 in winter; London runs one hour ahead of UTC from late March to late October and matches it the rest of the year. A test that hardcodes "23:00 UTC is midnight in London" breaks when the calendar crosses the October transition. Call .astimezone(zone) and let zoneinfo, which carries the transition rules, handle both halves of the year.

Write the boundary as a pure function first

Define it against zoneinfo.ZoneInfo, not a hand-computed offset, so daylight-saving transitions are handled by the library.

Test both sides of the boundary and both halves of the year

Check an instant just before and just after the boundary in a summer month, then repeat in a winter month, so you test the offset derivation rather than a memorized constant.

Record the clock with the data

Write the clock, the cutoff instant and the size of the excluded window into the split's metadata, so a reader can recompute which items were held out.

Report the boundary group separately

Give the count of items in the ambiguous window and the metric with and without them.

Case: the clock behind this site's publishing

This site applies the same rule to its own release dates: an article is available on its publishedAt calendar date, starting at midnight in the Europe/London time zone, and every request checks that one clock.

python
from datetime import datetime, date
from zoneinfo import ZoneInfo

LONDON = ZoneInfo("Europe/London")
TARGET_DATE = date(2026, 9, 11)

def london_date(iso_utc: str) -> date:
    instant = datetime.fromisoformat(iso_utc)
    if instant.tzinfo is None or instant.utcoffset() is None:
        raise ValueError("An explicit UTC offset is required")
    return instant.astimezone(LONDON).date()

def is_eligible(iso_utc: str) -> bool:
    return london_date(iso_utc) >= TARGET_DATE

for stamp in ["2026-09-10T22:59:59+00:00", "2026-09-10T23:00:00+00:00"]:
    print(stamp, "->", london_date(stamp), is_eligible(stamp))

Run on Python 3.12, it prints:

text
2026-09-10T22:59:59+00:00 -> 2026-09-10 False
2026-09-10T23:00:00+00:00 -> 2026-09-11 True

The site's boundary tests use a summer pair and a winter pair, both checked with the same london_date function:

Release date in LondonUTC instant immediately beforeUTC instant at release
2026-09-112026-09-10T22:59:59Z2026-09-10T23:00:00Z
2026-11-012026-10-31T23:59:59Z2026-11-01T00:00:00Z
A correct date function is only half the job

A cached or pre-built response can bypass the function entirely, so the page a reader gets can disagree with a correct boundary. GitHub's documentation for scheduled workflows also says scheduled runs can be delayed under load, so a cron job does not guarantee exact timing. Checking that the result actually exists, for a page or a training job, is the subject of a companion essay in this series, out October 8.

Deployment acknowledgment is followed by build completion and reader-facing article and discovery probes before availability is recorded.. Deployment hook accepts request; Build and deployment complete; Probe reader-facing article and discovery URL; Check expected revision; Record observed availability
Deployment acknowledgment is followed by build completion and reader-facing article and discovery probes before availability is recorded.

Checkpoint

Quick check — An eval keeps items posted after March 31. One item's timestamp is 2026-03-31T23:30-07:00. Which treatment can you defend in a write-up?

Check the decision

Why this matters in a lab

Date boundaries decide what counts as held-out data, which checkpoint a report describes, and which runs belong to "this week's" comparison. Logs from machines in different zones, a laptop's local time in a notebook, and a scheduler that records UTC can all disagree about the same event. Name one clock, convert once, store instants, and test the boundary on both sides and in both halves of the year. This is everyday work in evals and research engineering.

An interviewer could ask: you are building an evaluation split of problems published after a model's training cutoff. What can go wrong with the dates? A good answer mentions the clock each source used, the conversion, the ambiguous boundary window, and a margin around a cutoff that was itself stated without a clock.

See the upcoming Research Engineering and Evaluation Science course

It covers building an evaluation set, splitting it and checking it for contamination before any system sees it.

Learn the mechanism. Test the claim.

Join the Prof Rod newsletter for one educational lesson a week, with worked examples attached. It is free to register for and separate from the Zero Employee community.