{
  "name": "Booking time test cases for a voice agent",
  "version": "1.0",
  "generated": "2026-09-01",
  "purpose": "Run these through whatever converts a spoken time into a calendar event. Every case has bitten a production booking agent somewhere.",
  "howToUse": [
    "For each case, feed spokenTime and callerTimezone into your booking path.",
    "Compare what your system stores against expectUtc.",
    "A case is a pass only when the stored UTC instant matches exactly.",
    "The two marked as gap or ambiguous have no single right answer: what matters is that your system notices and asks, rather than guessing silently."
  ],
  "cases": [
    {
      "id": "us-spring-forward-gap",
      "why": "02:30 does not exist in US Eastern on this date: the clock goes 01:59:59 to 03:00:00",
      "callerTimezone": "America/New_York",
      "spokenTime": "2:30 on 2026-03-08",
      "expectLocal": null,
      "expectUtc": null,
      "expectAbbrev": null,
      "note": "There is no correct UTC instant. Python's zoneinfo silently returns 2026-03-08T07:30:00Z and most date libraries do something equally quiet. A booking agent must detect the gap and offer a real time instead."
    },
    {
      "id": "us-spring-forward-day",
      "why": "The day the clocks jump: an appointment booked the evening before must not drift",
      "callerTimezone": "America/New_York",
      "spokenTime": "14:00 on 2026-03-08",
      "expectLocal": "2026-03-08T14:00:00-04:00",
      "expectUtc": "2026-03-08T18:00:00Z",
      "expectAbbrev": "EDT",
      "note": "14:00 EDT, UTC-4, not UTC-5"
    },
    {
      "id": "us-fall-back-ambiguous",
      "why": "1:30am happens twice in US Eastern on this date",
      "callerTimezone": "America/New_York",
      "spokenTime": "1:00 on 2026-11-01",
      "expectLocal": "2026-11-01T01:00:00-04:00",
      "expectUtc": "2026-11-01T05:00:00Z",
      "expectAbbrev": "EDT",
      "note": "Ambiguous local time: the system must pick a fold explicitly, not accept the default"
    },
    {
      "id": "us-day-before-fall-back",
      "why": "Booked before the change, occurring after it",
      "callerTimezone": "America/New_York",
      "spokenTime": "15:00 on 2026-10-31",
      "expectLocal": "2026-10-31T15:00:00-04:00",
      "expectUtc": "2026-10-31T19:00:00Z",
      "expectAbbrev": "EDT",
      "note": "15:00 EDT, UTC-4"
    },
    {
      "id": "us-day-after-fall-back",
      "why": "Same clock time, one hour different in UTC",
      "callerTimezone": "America/New_York",
      "spokenTime": "15:00 on 2026-11-02",
      "expectLocal": "2026-11-02T15:00:00-05:00",
      "expectUtc": "2026-11-02T20:00:00Z",
      "expectAbbrev": "EST",
      "note": "15:00 EST, UTC-5"
    },
    {
      "id": "uk-bst-start",
      "why": "UK clocks go forward; a booking made in GMT for a BST date",
      "callerTimezone": "Europe/London",
      "spokenTime": "11:00 on 2026-03-29",
      "expectLocal": "2026-03-29T11:00:00+01:00",
      "expectUtc": "2026-03-29T10:00:00Z",
      "expectAbbrev": "BST",
      "note": "11:00 BST, UTC+1"
    },
    {
      "id": "uk-bst-end",
      "why": "UK clocks go back",
      "callerTimezone": "Europe/London",
      "spokenTime": "11:00 on 2026-10-25",
      "expectLocal": "2026-10-25T11:00:00+00:00",
      "expectUtc": "2026-10-25T11:00:00Z",
      "expectAbbrev": "GMT",
      "note": "11:00 GMT, UTC+0"
    },
    {
      "id": "us-eu-offset-window",
      "why": "The two-week window where US and EU offsets are out of step",
      "callerTimezone": "Europe/London",
      "spokenTime": "16:00 on 2026-03-11",
      "expectLocal": "2026-03-11T16:00:00+00:00",
      "expectUtc": "2026-03-11T16:00:00Z",
      "expectAbbrev": "GMT",
      "note": "16:00 GMT while New York is already on EDT: the usual 5-hour gap is 4"
    },
    {
      "id": "au-southern-hemisphere",
      "why": "Australian DST runs the other way round",
      "callerTimezone": "Australia/Sydney",
      "spokenTime": "9:00 on 2026-10-04",
      "expectLocal": "2026-10-04T09:00:00+11:00",
      "expectUtc": "2026-10-03T22:00:00Z",
      "expectAbbrev": "AEDT",
      "note": "09:00 AEDT, UTC+11"
    },
    {
      "id": "india-half-hour",
      "why": "A half-hour offset breaks integer-hour arithmetic",
      "callerTimezone": "Asia/Kolkata",
      "spokenTime": "10:00 on 2026-06-15",
      "expectLocal": "2026-06-15T10:00:00+05:30",
      "expectUtc": "2026-06-15T04:30:00Z",
      "expectAbbrev": "IST",
      "note": "10:00 IST, UTC+5:30"
    },
    {
      "id": "nepal-quarter-hour",
      "why": "A 45-minute offset breaks it further",
      "callerTimezone": "Asia/Kathmandu",
      "spokenTime": "10:00 on 2026-06-15",
      "expectLocal": "2026-06-15T10:00:00+05:45",
      "expectUtc": "2026-06-15T04:15:00Z",
      "expectAbbrev": "+0545",
      "note": "10:00 +0545"
    },
    {
      "id": "us-next-year-boundary",
      "why": "DST rules must come from a live database, not a cached year",
      "callerTimezone": "America/New_York",
      "spokenTime": "14:00 on 2027-03-14",
      "expectLocal": "2027-03-14T14:00:00-04:00",
      "expectUtc": "2027-03-14T18:00:00Z",
      "expectAbbrev": "EDT",
      "note": "14:00 EDT in 2027, on a date that differs from 2026"
    }
  ]
}
