# Schedule Data JSON Schema Reference

This document defines version 3 of the `schedule-data.json` format used by [`index.html`](./index.html) and generated by [`schedule-builder.html`](./schedule-builder.html). It is intended to be complete enough for a person, spreadsheet, script, watch application, or other client to create and consume a compatible file without inspecting the HTML source.

Schema version 3 stores each day type's calendar dates directly inside that day type. There is no separate top-level `dates` object and no day-type ID or date-list reference.

## File location and loading

The production filename is `schedule-data.json`. It belongs in the same directory as `index.html`:

```text
index.html
schedule-data.json
```

The page requests the file without browser caching. Consequently:

- Both files must be published together.
- The filename is case-sensitive on GitHub Pages.
- The page must be accessed through HTTP or HTTPS. Direct `file://` use is generally blocked by browser security rules.
- The server must return a successful response for both files.
- The JSON must be syntactically valid before any schedule information is displayed.

For local testing only, the HTML accepts a simple JSON filename through the `data` query parameter:

```text
http://localhost:8000/index.html?data=tempv3.json
```

This override works only on `localhost` or `127.0.0.1`, accepts a filename ending in `.json` without path separators, and has no effect on the published GitHub Pages site.

## Conceptual model

Version 3 contains two principal collections:

```text
day type
├── displayed label
├── color
├── calendar dates
└── schedule reference OR all-day message
        │
        └── references a reusable bell schedule
```

- `dayTypes` defines every kind of calendar day and owns its dates directly.
- `schedules` contains reusable sequences of timed blocks.
- A timed day type references one named schedule.
- An all-day type contains an `allDay` message instead of a schedule reference.
- Several day types may reuse the same schedule.

Dates are nested because sharing a date list between day types has no useful meaning: a calendar date must resolve to exactly one day type. Schedules remain separate because schedule reuse is useful.

## Complete structural example

```json
{
  "schemaVersion": 3,
  "dataVersion": "2026-09-03.1",
  "schoolYear": "2026-27",
  "timezone": "America/Chicago",
  "site": {
    "schoolName": "Example School",
    "title": "Example School Current Schedule",
    "description": "Current schedule information for Example School.",
    "defaultColor": "#123456",
    "disclaimer": "Calendar information may change.",
    "contact": "Questions or corrections? Contact the schedule administrator."
  },
  "revisionNote": "Revised 2026/09/03.",
  "dayTypes": [
    {
      "label": "Regular Day",
      "color": "#123456",
      "dates": [
        "2026-08-24",
        "2026-08-25"
      ],
      "schedule": "regular"
    },
    {
      "label": "No School",
      "color": "#555555",
      "dates": [
        "2026-09-07"
      ],
      "allDay": "No School"
    }
  ],
  "schedules": {
    "regular": {
      "blocks": [
        { "start": "08:00", "label": "First Period" },
        { "start": "09:00", "label": "Passing Period" },
        { "start": "09:05", "label": "Second Period" },
        { "start": "15:00", "label": "End of School" }
      ]
    }
  }
}
```

## Formal type model

These TypeScript-style definitions describe the contract. They are documentation, not code required by the website.

```ts
type ScheduleDataV3 = {
  schemaVersion: 3;
  dataVersion?: string;
  schoolYear?: string;
  timezone: string;
  site: SiteInformation;
  revisionNote?: string;
  dayTypes: DayType[];
  schedules: Record<string, BellSchedule>;
};

type SiteInformation = {
  schoolName: string;
  title: string;
  description?: string;
  defaultColor: string;
  disclaimer?: string;
  contact?: string;
};

type DayType = TimedDayType | AllDayType;

type TimedDayType = {
  label: string;
  color: string;
  dates: string[];
  schedule: string;
};

type AllDayType = {
  label: string;
  color: string;
  dates: string[];
  allDay: string;
};

type BellSchedule = {
  blocks: ScheduleBlock[];
};

type ScheduleBlock = {
  start: string;
  label: string;
};
```

## Top-level properties

| Property | JSON type | Required by viewer | Meaning |
| --- | --- | --- | --- |
| `schemaVersion` | number | Yes | Structural contract version. Version 3 files must contain the number `3`. |
| `dataVersion` | string | No | Identifier for a particular content revision; displayed in the footer. |
| `schoolYear` | string | No | Maintainer-facing description of the covered school year. |
| `timezone` | string | Yes | IANA timezone used for the clock, date selection, and block calculations. |
| `site` | object | Yes | Organization-specific titles, metadata, default color, and footer text. |
| `revisionNote` | string | No | Revision message displayed in the footer. |
| `dayTypes` | array | Yes | Calendar day definitions with nested dates. |
| `schedules` | object | Yes | Named, reusable bell schedules. |

Unknown top-level properties are ignored by the current viewer. Producers should not rely on unknown properties controlling behavior.

## `schemaVersion`

```json
"schemaVersion": 3
```

Rules:

- It must be the JSON number `3`, not the string `"3"`.
- It describes the structure, not the freshness of the calendar.
- Do not change it for ordinary date, label, color, or bell-time corrections.
- Increment it only when an incompatible structural change is introduced.
- The viewer and builder accept only schema version 3.

## `dataVersion`

```json
"dataVersion": "2026-09-03.1"
```

The viewer displays this value as `Data version: <value>` in the footer but otherwise treats it as an opaque string. Recommended formats include:

```text
2026-09-03.1
2026-27-r3
2026-09-03T14:30:00-05:00
```

Change `dataVersion` whenever dates, times, labels, colors, site fields, or footer content change. If it is absent or empty, its footer line is empty.

## `schoolYear`

```json
"schoolYear": "2026-27"
```

The viewer does not display or validate this value. It documents the intended coverage for maintainers, generators, cached applications, and future integrations. Use one consistent string convention.

## `timezone`

```json
"timezone": "America/Chicago"
```

Requirements:

- Use an IANA timezone identifier such as `America/Chicago`, `America/New_York`, or `America/Los_Angeles`.
- Do not use abbreviations such as `CST`, `EST`, or `PST`.
- The browser must recognize the identifier.
- The timezone applies even when the viewer's device is elsewhere.
- Daylight-saving transitions are handled by the browser's timezone implementation.

An invalid timezone prevents the schedule from starting.

## `site`

```json
"site": {
  "schoolName": "Lindblom",
  "title": "Lindblom Current Schedule",
  "description": "Today's schedule, current class, time remaining, and what comes next.",
  "defaultColor": "#66151c",
  "disclaimer": "Calendar information may change.",
  "contact": "Questions or corrections? Please contact Mr. Sidarous."
}
```

### `site.schoolName`

- Type: string
- Required: yes
- Must be non-empty.
- Identifies the organization represented by the data.
- Validated by the viewer but not displayed separately from `site.title`.

### `site.title`

- Type: string
- Required: yes
- Must be non-empty.
- Sets the large page heading and browser-tab title.

### `site.description`

- Type: string
- Required: no
- Sets the page metadata description.
- Falls back to `site.title` when empty or omitted.

### `site.defaultColor`

- Type: string
- Required: yes
- Must be non-empty.
- Used before or when no day type is selected.
- Also sets the browser theme-color metadata.
- A six-digit hexadecimal CSS color is recommended.

The viewer checks only that this value is non-empty. The builder additionally checks that the browser recognizes it as a CSS color.

### `site.disclaimer`

- Type: string
- Required: no
- Displayed as the first footer line.

### `site.contact`

- Type: string
- Required: no
- Displayed as the second footer line.

## `revisionNote`

```json
"revisionNote": "Revised 2026/09/03 to update calendar dates."
```

This optional plain-text message is displayed after `dataVersion` in the footer. It is not parsed as a date. An empty or omitted value leaves the line empty.

## `dayTypes`

`dayTypes` is an ordered array. Each entry owns its dates and chooses exactly one behavior: a timed schedule or an all-day message.

Timed example:

```json
{
  "label": "Gold 1–4 Day",
  "color": "#9a6700",
  "dates": [
    "2026-09-03",
    "2026-09-08"
  ],
  "schedule": "gold"
}
```

All-day example:

```json
{
  "label": "Weekend",
  "color": "#333333",
  "dates": [
    "2026-09-05",
    "2026-09-06"
  ],
  "allDay": "Weekend"
}
```

### `dayTypes[].label`

- Type: string
- Required: yes
- Must be non-empty.
- Displayed below the site title as the current kind of day.
- Also identifies the day type in validation messages.

Labels do not have to be unique, but distinct labels are strongly recommended.

### `dayTypes[].color`

- Type: string
- Required: yes
- Must be non-empty.
- Sets the heading color, progress bar, highlighted schedule row, and page tint.
- Any valid CSS color works, but six-digit hex colors are most portable.

### `dayTypes[].dates`

- Type: array of strings
- Required: yes
- May be empty for a planned day type.
- Owned directly by the containing day type.
- Should be sorted chronologically for reviewability.

The canonical date representation is:

```text
YYYY-MM-DD
```

Examples:

```json
["2026-09-03", "2027-01-05"]
```

Important rules:

- Months and days must be zero-padded.
- A date may appear only once in the entire `dayTypes` array.
- A duplicate within one day type is also invalid.
- Every value should represent a real Gregorian calendar date.
- Dates should normally fall inside `schoolYear`.
- Version-3 producers must use ISO dates. Slash-formatted dates such as `9/3/2026` are rejected.

### `dayTypes[].schedule`

- Type: string
- Required for timed day types
- Must exactly match a property name in `schedules`.
- Matching is case-sensitive.
- Several day types may reference the same schedule.
- Must not be combined with `allDay`.

### `dayTypes[].allDay`

- Type: string
- Required for all-day types
- Must be non-empty.
- Contains the large status message displayed for the entire date.
- Suppresses countdown and upcoming-block information.
- Produces one highlighted `All day` row in the schedule list.
- Must not be combined with `schedule`.

Use exactly one of `schedule` or `allDay`.

### Day-type ordering

Array order is retained in the generated file. Because duplicate dates are invalid, only one day type should match any date. If invalid duplicate protection were bypassed, the first matching entry would win; never rely on that fallback.

## `schedules`

`schedules` is an object containing named bell schedules:

```json
"schedules": {
  "regular": {
    "blocks": [
      { "start": "08:00", "label": "First Period" },
      { "start": "09:00", "label": "Passing Period" },
      { "start": "09:05", "label": "Second Period" },
      { "start": "15:00", "label": "End of School" }
    ]
  }
}
```

Schedule names are user-defined identifiers.

Rules:

- A timed day type's `schedule` must match one of these names exactly.
- Names are case-sensitive.
- Referenced schedules require a `blocks` array containing at least two entries.
- Unreferenced schedules are permitted but never displayed.
- Multiple day types may reuse one schedule.
- Renaming a schedule requires updating every referencing day type.

The builder updates references automatically when a schedule is renamed.

## Schedule blocks

Each schedule block contains `start` and `label`.

### `start`

- Type: string
- Required: yes
- Exact format: `HH:mm`
- Clock: 24 hour
- Range: `00:00` through `23:59`
- Hours and minutes must contain two digits.

Valid:

```json
{ "start": "08:05", "label": "First Period" }
{ "start": "13:24", "label": "Fourth Period" }
```

Invalid:

```json
{ "start": "8:05", "label": "First Period" }
{ "start": "1:24 PM", "label": "Fourth Period" }
{ "start": "24:00", "label": "End of Day" }
{ "start": 805, "label": "First Period" }
```

Start times must be strictly increasing within each schedule. Equal, duplicated, or decreasing times invalidate the complete file.

### `label`

- Type: string
- Required: yes
- Must be non-empty.
- Displayed as the current block, upcoming block, and schedule-row text.
- The final label should describe the state after scheduled activity, such as `End of School`.

## Terminal-block semantics

Blocks specify start times but not explicit end times. A block ends when the following block begins. The final entry is therefore a terminal marker rather than a normal interval.

Given:

```json
"blocks": [
  { "start": "08:00", "label": "First Period" },
  { "start": "09:00", "label": "Passing Period" },
  { "start": "09:05", "label": "Second Period" },
  { "start": "10:00", "label": "End of School" }
]
```

the viewer interprets:

| Start | End | State |
| --- | --- | --- |
| 8:00 AM | 9:00 AM | First Period |
| 9:00 AM | 9:05 AM | Passing Period |
| 9:05 AM | 10:00 AM | Second Period |
| 10:00 AM onward | — | End of School |

Without the terminal entry, the viewer cannot calculate the final real block's duration or countdown.

## Runtime behavior

After loading and validation, the viewer:

1. Gets the current instant from the viewer's device.
2. Converts it into the configured `timezone`.
3. Constructs the school date in `YYYY-MM-DD` format.
4. Finds the day type whose `dates` contains that date.
5. For a timed day, converts block starts to minutes after midnight.
6. Identifies the interval containing the current school time.
7. Displays the current block, range, countdown, next block, progress, and complete schedule.
8. Recalculates once per second.

Timed intervals are start-inclusive and end-exclusive:

```text
block start <= current time < next block start
```

Before the first block, the page counts down to the first block. At or after the terminal block, it displays the terminal label with no countdown or next block. On an all-day type, it displays the `allDay` message with no countdown. If no date matches, it displays `Schedule unavailable`.

Weekends and holidays are not inferred. Include them explicitly if they should have an all-day display.

## Viewer validation

The current viewer rejects the complete file when it detects any of the following:

- `schemaVersion` is not exactly the number `3`.
- `site`, `dayTypes`, or `schedules` is missing.
- `dayTypes` is not an array.
- `site.title`, `site.schoolName`, `site.defaultColor`, or `timezone` is empty.
- A day type lacks a label, color, or date array.
- A normalized date occurs more than once.
- A timed day type references a missing schedule.
- A referenced schedule has fewer than two blocks.
- A block label is empty.
- A start time does not use valid `HH:mm` syntax.
- Start times are not strictly increasing.
- The timezone is not recognized when the formatter initializes.

When loading or validation fails, the viewer shows a generic error instead of using partial data.

## Builder validation

The builder applies stricter generation checks:

- It outputs only schema version 3.
- Dates must be real, ISO-formatted Gregorian dates.
- Dates must be unique across all day types.
- Colors must be recognized CSS colors.
- Each day type must use exactly one of `schedule` or `allDay`.
- Every schedule, including an unreferenced one, must have valid ordered blocks.
- Unused schedules produce warnings rather than errors.
- Downloads and clipboard copying are blocked while errors remain.

All editing and file generation occurs locally in the browser. The builder does not transmit uploaded JSON to a server.

## JSON syntax requirements

- Property names and strings use double quotation marks.
- Commas separate entries.
- The final entry in an object or array has no trailing comma.
- Comments are not permitted.
- Literal line breaks inside strings must be escaped.
- The file should be saved as UTF-8.

Valid:

```json
{
  "schemaVersion": 3,
  "dayTypes": []
}
```

Invalid JSON:

```js
{
  schemaVersion: 3,
  "dayTypes": [],
}
```

The invalid example has an unquoted property and trailing comma.

## Adding a timed day type

1. Add the day type to `dayTypes`.
2. Put its calendar dates directly in `dates`.
3. Create its named schedule under `schedules`, unless an existing one can be reused.
4. Set `schedule` to the exact schedule name.
5. Confirm none of its dates occur in another day type.
6. Update `dataVersion` and `revisionNote`.

```json
{
  "label": "Early Release Day",
  "color": "#7848a8",
  "dates": [
    "2026-10-16",
    "2027-02-12"
  ],
  "schedule": "earlyRelease"
}
```

## Adding an all-day type

An all-day type does not require a schedule:

```json
{
  "label": "Professional Development Day",
  "color": "#555555",
  "dates": [
    "2026-10-12"
  ],
  "allDay": "No Classes"
}
```

Do not add a `schedule` property to the same object.

## Reusing a schedule

Schedule sharing avoids duplicated times:

```json
{
  "dayTypes": [
    {
      "label": "Regular Day",
      "color": "#123456",
      "dates": ["2026-09-01"],
      "schedule": "regular"
    },
    {
      "label": "Spirit Day",
      "color": "#8a2be2",
      "dates": ["2026-10-30"],
      "schedule": "regular"
    }
  ]
}
```

Both day types retain distinct labels, colors, and dates while sharing the `regular` bell times.

## Spreadsheet-generation guidance

A spreadsheet generator can use these conceptual tables.

### Site settings

| Field | Example |
| --- | --- |
| `schemaVersion` | `3` |
| `dataVersion` | `2026-09-03.1` |
| `schoolYear` | `2026-27` |
| `timezone` | `America/Chicago` |
| `site.title` | `Example School Current Schedule` |

### Calendar assignments

Use one row per assigned date:

| Day-type label | Date |
| --- | --- |
| Gold Day | `2026-09-03` |
| Gold Day | `2026-09-08` |
| No School | `2026-09-07` |

The formula or script groups rows by day-type label and emits each group's nested `dates` array.

### Day types

| Label | Color | Behavior | Schedule or message |
| --- | --- | --- | --- |
| Gold Day | `#9a6700` | timed | `gold` |
| No School | `#555555` | all-day | `No School` |

### Schedule blocks

Use one row per block:

| Schedule | Start | Label |
| --- | --- | --- |
| `gold` | `08:00` | First Period |
| `gold` | `09:40` | Passing Period |
| `gold` | `15:05` | End of School |

Sort by schedule and start time before producing each `blocks` array.

The builder is also suitable as a non-spreadsheet generator: import an existing file, edit it visually, and download a complete replacement.

## Versioning policy

| Change | `schemaVersion` | `dataVersion` |
| --- | --- | --- |
| Add or correct a date | Keep `3` | Change |
| Correct a bell time | Keep `3` | Change |
| Change a label or color | Keep `3` | Change |
| Change footer text | Keep `3` | Change |
| Add a compatible optional field | Usually keep `3` | Change if content changed |
| Remove or reinterpret a required field | Increment | Change |
| Change date or time representation incompatibly | Increment | Change |

`schemaVersion` answers “How should this file be parsed?” `dataVersion` answers “Which content revision is this?”

## Failure behavior and troubleshooting

### Calendar could not be loaded

Check that:

- The file is named `schedule-data.json` in production.
- The HTML and JSON are in the same directory.
- Capitalization matches exactly.
- The page is served over HTTP or HTTPS.
- The response is successful rather than 404.

### Unsupported schedule data

Confirm `schemaVersion` is the number `3`, and that `site`, `dayTypes`, and `schedules` exist with the required container types.

### Invalid day type

Confirm every day type contains non-empty `label` and `color` strings plus a `dates` array.

### Duplicate date

Search every day type, including the same one, for the reported date. Each calendar date may occur only once.

### Missing schedule

Confirm the day type's `schedule` value exactly matches a property name under `schedules`.

### Invalid or unordered time

Confirm all starts use zero-padded `HH:mm`, fall between `00:00` and `23:59`, and increase strictly.

## Publication checklist

- [ ] JSON parses successfully.
- [ ] `schemaVersion` is the number `3`.
- [ ] `dataVersion` reflects the intended content revision.
- [ ] `schoolYear` and `timezone` are correct.
- [ ] Site and footer text are current.
- [ ] Every day type has a label, color, and `dates` array.
- [ ] All dates use real `YYYY-MM-DD` values.
- [ ] Date arrays are sorted.
- [ ] No date occurs more than once.
- [ ] Every day type uses exactly one of `schedule` or `allDay`.
- [ ] Every referenced schedule exists.
- [ ] Every schedule has at least two ordered blocks.
- [ ] Every timed schedule ends with an intentional terminal block.
- [ ] Representative timed, all-day, before-school, after-school, and unlisted dates have been tested.
- [ ] The revision note remains accurate.

## Current implementation boundary

This document defines schema version 3. The viewer and builder accept only version 3. Other clients may consume the same data as long as they follow these meanings and constraints. Any incompatible evolution should introduce a new `schemaVersion` and corresponding documentation.
