# Ressourcen, Ereignisse und Intervalle

> Das Datenmodell: IDs von Ressourcen und Ereignissen, halboffene Intervalle, ISO-Zeiten mit Sekunden, Ortszeiten ohne Zeitzone und typisierte Felder mit EventData<T>.

Source: https://superscheduler.org/de/docs/resources-events-intervals/
Reviewed: 2026-10-07

Zeilen sind ResourceData-Objekte mit einer id und einem name; Balken sind EventData-Objekte mit id, text, start, end und der ID der Ressource, zu der sie gehören. IDs sind Strings oder Zahlen und werden strikt verglichen, 1 und '1' sind also verschieden. Intervalle sind halboffen, [start, end), und Datumsangaben sind bürgerliche Zeitwerte (lokale Datum-Uhrzeit), geschrieben als ISO-Strings mit Sekunden; die Bibliothek rechnet nie Zeitzonen um. Eigene Felder ergänzen Sie mit EventData<YourFields> und grenzen sie ein, wenn sie vom Control zurückkommen.

SuperScheduler zeichnet zwei Arrays: **Ressourcen**, die Zeilen, und **Ereignisse**, die Balken in diesen Zeilen. Beides sind einfache Objekte, die Sie aus Ihren eigenen Daten erzeugen. Wer vier Regeln beachtet, vermeidet fast jedes Problem der Art „mein Ereignis erscheint nicht“: IDs sind strikt typisiert, Intervalle schließen ihr Ende aus, Datumsstrings enthalten Sekunden, und Zeiten sind lokale Uhrzeiten ohne Zeitzone.

Die Regeln auf dieser Seite gelten für beide Editionen, sofern ein Abschnitt nichts anderes sagt. Lite akzeptiert eine Teilmenge der Felder; siehe [Schnellstart mit Lite](https://superscheduler.org/de/docs/quick-start-lite/#event-fields).

## Ressourcen
Eine Ressource braucht eine `id` und einen `name`. In Pro ist `ResourceData` offen: Sie können eigene Felder (`floor`, `kind`, `capacity`) im selben Objekt behalten und sie in Callbacks und Render-Hooks wieder auslesen.

Die Felder, die Sie in Pro am häufigsten verwenden:

| Feld | Zweck |
|---|---|
| `id` | String oder Zahl; eindeutig unter den Ressourcen |
| `name` | Text im Zeilenkopf |
| `backColor`, `cssClass`, `html`, `toolTip` | Aussehen des Zeilenkopfs (`html` ist vertrauenswürdiges Markup) |
| `minHeight`, `eventHeight` | Geometrie nur dieser Zeile |
| `cellsDisabled` | Jede Zelle der Zeile lehnt Ablegen und Auswahl ab |
| `columns` | Zellen für zusätzliche Spalten im Zeilenkopf (mit `rowHeaderColumns`) |
| `children`, `expanded` | Ein Ressourcenbaum (mit `treeEnabled`) |
| `frozen` | `'top'` oder `'bottom'`: Die Zeile bleibt beim Scrollen sichtbar |

### Ressourcenbäume
Um Zeilen zu gruppieren, verschachteln Sie Ressourcen in `children` und setzen `treeEnabled` am Planer. Ohne `treeEnabled` werden Kinder ignoriert, und die Liste bleibt flach. Ein Elternknoten startet eingeklappt, sofern sein Feld `expanded` nicht `true` ist. Elternzeilen können wie jede Zeile Ereignisse enthalten; setzen Sie `treePreventParentUsage`, damit sie reine Gruppenüberschriften bleiben. Bäume, Zeilenspalten und Zeilenauswahl behandelt [Bäume, Spalten und Auswahl](https://superscheduler.org/de/docs/trees-columns-selection/). Lite akzeptiert nur flache Listen.

## Ereignisse
Ein Ereignis braucht `id`, `text`, `start`, `end` und, um in einer Zeile zu erscheinen, `resource`. Optionale Felder ändern Aussehen und Verhalten:

| Feld | Zweck |
|---|---|
| `backColor`, `fontColor`, `borderColor`, `barColor` | Farben des Balkens, seines Texts, seines Rahmens und des Dauerbalkens |
| `cssClass` | Klassen für Ihr eigenes CSS |
| `html` | Inhalt als vertrauenswürdiges HTML (Nutzerdaten mit `SuperScheduler.Util.escapeHtml` escapen) |
| `toolTip`, `bubbleHtml` | Nativer Tooltip oder Inhalt der Hover-Blase |
| `moveDisabled`, `resizeDisabled` | Dieses Ereignis gegen Verschieben oder Dauer ändern sperren |
| `moveHDisabled`, `moveVDisabled` | Verschieben nur zwischen Zeilen oder nur in der Zeit erlauben |
| `clickDisabled`, `deleteDisabled` | Dieses Ereignis von Klicks oder Löschen ausnehmen |
| `tags` | Ein beliebiger Wert für Ihre eigene Verwendung |

Alle Felder dieser Tabelle gibt es nur in Pro, außer `backColor`, `fontColor`, `cssClass`, `toolTip` und `tags`, die auch Lite akzeptiert.

## IDs sind Strings oder Zahlen und werden strikt verglichen
`ResourceId` und `EventId` sind `string | number`, und Vergleiche berücksichtigen Wert und Typ. Die Zahl `101` und der String `'101'` sind verschiedene IDs. Ein Ereignis mit `resource: '101'` wird nicht in einer Zeile mit der ID `101` gezeichnet, und `control.events.find('7')` findet das Ereignis mit der ID `7` nicht.

Das ist vor allem wichtig, wenn Daten aus mehreren Quellen kommen: Ein Datenbanktreiber liefert vielleicht numerische Zimmer-IDs, während ein Formular oder eine URL Strings liefert. Normalisieren Sie IDs an der Stelle, an der Daten in Ihre Anwendung gelangen, und verwenden Sie pro Art von ID einen einzigen Typ.

> **Behavior:**
> In Pro wirft `control.events.add()` eine `SuperScheduler.Exception`, wenn die ID bereits existiert, und `control.events.update()` mit einer unbekannten ID tut nichts (es fügt nichts hinzu). Verwenden Sie `add` für neue Ereignisse und `update` für bestehende.

## Intervalle sind halboffen
Ein Ereignis belegt `[start, end)`: Der Anfangszeitpunkt gehört dazu, der Endzeitpunkt nicht. Drei Folgen:

- **Direkt aufeinanderfolgende Ereignisse überlappen sich nicht.** Ein Aufenthalt, der um 11:00 Uhr endet, und der nächste, der um 11:00 Uhr im selben Zimmer beginnt, sind vereinbar, auch wenn Überlappungen abgelehnt werden.
- **Ein reines Datum als Ende ist der erste freie Tag.** `start: '2026-10-02'`, `end: '2026-10-05'` umfasst den 2., 3. und 4. Oktober. Soll auch der 5. erscheinen, ist das Ende `'2026-10-06'`.
- **Dauern sind einfache Differenzen.** `end - start` ist die Dauer, ohne Korrekturen um „plus einen Tag“.

Ihr Backend sollte dieselbe Regel verwenden. Zwei Intervalle überlappen sich, wenn `a.start < b.end && b.start < a.end`; eine Abfrage nach dem, was zwischen `from` und `to` sichtbar ist, lautet `start < to AND end > from`.

In Pro schaltet `eventEndSpec: 'Date'` für tagesbasierte Planungen auf inklusive Enden ohne Uhrzeit um: Ein Ereignis, das auf `'2026-10-05'` endet, umfasst dann den 5. Die Bibliothek rechnet den Wert intern um und gibt ihn in derselben Konvention zurück. Verwenden Sie pro Planer eine einzige Konvention.

## Datumsstrings
`start`, `end` und jede Datumsoption akzeptieren ein `SuperScheduler.Date` oder einen ISO-8601-String:

| Eingabe | Akzeptiert | Gelesen als |
|---|---|---|
| `'2026-10-02'` | ja | Mitternacht zu Beginn des 2. Oktober |
| `'2026-10-02T14:00:00'` | ja | 14:00 Uhr |
| `'2026-10-02 14:00:00'` | ja | 14:00 Uhr (ein Leerzeichen statt `T`) |
| `'2026-10-02T14:00:00.250'` | ja | Mit Millisekunden |
| `'2026-10-02T14:00'` | **nein, wirft einen Fehler** | Sekunden sind Pflicht |
| `'2026-10-02T14:00:00+02:00'` | ja, mit Vorsicht | 12:00 Uhr: Ein Offset rechnet den Wert in die UTC-Uhrzeit um |
| `new Date()` | nein | Ein natives `Date` besteht die Typprüfung als `start` oder `end` nicht |

Zwei Regeln verhindern die meisten Überraschungen: Geben Sie immer Sekunden an, und senden Sie keine Offsets oder `Z`, es sei denn, Sie wollen die UTC-Uhrzeit. Formatieren Sie Werte als `yyyy-MM-ddTHH:mm:ss` in der Zeitzone des Ortes, der geplant wird.

## Bürgerliche Zeit, keine Zeitzonen
SuperScheduler arbeitet mit bürgerlicher Zeit (lokale Datum-Uhrzeit): `2026-10-25T02:30:00` ist „halb drei am 25.“, genau so, wie es dasteht, ohne Zeitzone und ohne Sommerzeitverschiebungen. Der Planer zeigt an, was Sie ihm geben, und gibt Werte in derselben Form zurück.

Genau das braucht eine Planungstafel: Ein Hotel in Madrid zeigt den Check-in für alle Nutzer um 14:00 Uhr Ortszeit an, egal wo deren Browser steht. Es bedeutet aber auch, dass die Umrechnungen Ihre Sache sind:

- Speichert Ihr Backend Zeitpunkte (UTC-Zeitstempel), rechnen Sie sie vor der Übergabe in die Ortszeit des Standorts der Ressource um und beim Speichern zurück in Zeitpunkte.
- Liegen verschiedene Ressourcen in verschiedenen Zeitzonen, entscheiden Sie, welche Ortszeit die Ansicht zeigt; der Planer hat eine einzige Zeitachse.
- Wiederkehrende Ereignisse (jeden Montag um 9:00 Uhr) müssen den Planer als konkrete Vorkommen erreichen, die Ihre Anwendung expandiert hat.

> **Limitation:**
> Die Bibliothek rechnet keine Zeitzonen um, wendet keine Sommerzeitregeln an und expandiert keine Wiederholungsregeln. Strategien dafür finden Sie unter [Locales, Datumsangaben und Zeitzonen](https://superscheduler.org/de/docs/locales-dates-timezones/).

## Eigene Felder mit EventData&lt;T&gt;
Ihre Ereignisse tragen meist mehr als eine Beschriftung: einen Gastcode, einen Status, einen Preis. In Pro hat `EventData` keine Indexsignatur, daher scheitert ein Objektliteral mit zusätzlichen Eigenschaften an der Excess-Property-Prüfung von TypeScript. Deklarieren Sie Ihre Felder einmal und verwenden Sie den generischen Typ `SuperScheduler.EventData<YourFields>`:

```ts
// src/bookings.ts
import type { SuperScheduler } from 'super-scheduler'

/** Fields your application adds to every booking. */
export interface BookingFields {
  guestCode: string
  status: 'tentative' | 'confirmed' | 'checkedIn'
  adults: number
}

export type BookingEvent = SuperScheduler.EventData<BookingFields>

// ResourceData accepts extra properties: keep your own row fields next to id and name.
export const rooms: SuperScheduler.ResourceData[] = [
  { id: 101, name: 'Room 101', floor: 1, kind: 'double' },
  { id: 102, name: 'Room 102', floor: 1, kind: 'suite' },
]

// EventData has no index signature: type the array so literals may carry custom fields.
export const bookings: BookingEvent[] = [
  {
    id: 'bk-1042',
    resource: 101, // the same type as the room id: 101 and '101' are different ids
    start: '2026-10-02T14:00:00',
    end: '2026-10-05T11:00:00',
    text: 'Booking 1042',
    guestCode: 'G-1042',
    status: 'confirmed',
    adults: 2,
  },
]

const STATUSES: ReadonlySet<string> = new Set(['tentative', 'confirmed', 'checkedIn'])

/**
 * Objects that come back from the control (handler arguments, onEventsChange) are typed as plain
 * EventData. Narrow them instead of casting, so a malformed object is caught where it appears.
 */
export function isBooking(data: SuperScheduler.EventData): data is BookingEvent {
  return (
    'guestCode' in data &&
    typeof data.guestCode === 'string' &&
    'status' in data &&
    typeof data.status === 'string' &&
    STATUSES.has(data.status) &&
    'adults' in data &&
    typeof data.adults === 'number'
  )
}
```
Daten, die vom Control zurückkommen, sind als einfaches `EventData` typisiert: Handler-Argumente, `onEventsChange`, `control.events.list`. Die Bibliothek behält Ihre Felder, aber TypeScript kann nicht wissen, dass sie da sind. Grenzen Sie mit einem Type Guard wie `isBooking` ein, statt zu casten; ein Guard fängt auch Objekte ab, die Ihr eigener Code fehlerhaft gebaut hat. Render-Hooks erhalten eine offene Kopie (`args.data` in `onBeforeEventRender`), dort können Sie Felder also direkt lesen, aber ein Guard hält die Typen exakt:

```tsx
// src/TypedPlanning.tsx
import { useMemo } from 'react'
import { SuperScheduler, SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerProps } from 'super-scheduler'
import { bookings, isBooking, rooms } from './typed-data'

export function TypedPlanning({ onOpen }: { onOpen: (guestCode: string) => void }) {
  const owned = useMemo(() => bookings.slice(), [])

  const config = useMemo<SchedulerProps>(
    () => ({
      startDate: '2026-10-01',
      days: 31,
      scale: 'Day',
      onBeforeEventRender: (args) => {
        // args.data is a per-render copy: start and end are always SuperScheduler.Date here.
        if (!isBooking(args.data)) return
        const nights = Math.round(
          (args.data.end.getTime() - args.data.start.getTime()) / 86_400_000,
        )
        // html is trusted markup: escape every value that came from users.
        const code = SuperScheduler.Util.escapeHtml(args.data.guestCode)
        args.data.html = `${code} · ${nights} night${nights === 1 ? '' : 's'}`
        args.data.cssClass = `booking booking--${args.data.status}`
      },
      onEventClick: (args) => {
        // args.e is a wrapper: id(), start(), text() are methods; data is the raw object.
        const data = args.e.data
        if (isBooking(data)) onOpen(data.guestCode)
      },
    }),
    [onOpen],
  )

  return <SuperSchedulerComponent {...config} resources={rooms} events={owned} />
}
```
Sie sollten jede Buchung mit ihrem Gastcode und der Anzahl der Nächte beschriftet sehen, und beim Klick wird `onOpen` mit dem Gastcode aufgerufen.

Das `EventData` von Lite ist nicht generisch und akzeptiert in TypeScript keine zusätzlichen Felder: Legen Sie Anwendungsdaten in `tags` ab, die `onEventClick` in `e.data.tags` zurückgibt.

## Werte nach dem Ziehen oder Ändern der Dauer
Wenn ein Nutzer in Pro ein Ereignis verschiebt oder seine Dauer ändert, bearbeitet die Bibliothek Ihr Objekt nicht. Sie ersetzt es durch ein neues Objekt, `{ ...old, start, end, resource }`, in dem `start` und `end` Instanzen von `SuperScheduler.Date` sind. Ereignisse, die niemand angefasst hat, behalten die Strings, die Sie übergeben haben. Code, der Ereignisse liest, muss daher beide Formen akzeptieren:

```ts
// src/event-dates.ts
import { SuperScheduler } from 'super-scheduler'

/**
 * After a drag or a resize, the committed event holds SuperScheduler.Date values in start and end;
 * events nobody touched keep the strings you passed. Normalize both to one canonical string.
 */
export function toIso(value: SuperScheduler.DateInput): string {
  // `value` is `yyyy-MM-ddTHH:mm:ss` (plus `.fff` when milliseconds are not zero).
  return typeof value === 'string' ? new SuperScheduler.Date(value).value : value.value
}

/** The part of an event your backend stores. */
export function toSavePayload(event: SuperScheduler.EventData) {
  if (event.resource === undefined) throw new Error(`Event ${String(event.id)} has no resource`)
  return {
    id: String(event.id),
    resource: String(event.resource),
    start: toIso(event.start),
    end: toIso(event.end),
  }
}

/** Half-open overlap, the rule the scheduler applies: touching intervals do not overlap. */
export function overlaps(a: SuperScheduler.EventData, b: SuperScheduler.EventData): boolean {
  const date = (value: SuperScheduler.DateInput) => new SuperScheduler.Date(value)
  return SuperScheduler.Util.overlaps(date(a.start), date(a.end), date(b.start), date(b.end))
}
```
Einige Details helfen, wenn Sie diese Werte speichern oder vergleichen:

- `JSON.stringify` schreibt ein `SuperScheduler.Date` als `yyyy-MM-ddTHH:mm:ss`, ein ganzes Ereignisobjekt wird also zu gültigen ISO-Strings serialisiert.
- `String(date)` und `date.value` liefern denselben Text; `date.toString('d MMM HH:mm', 'en-us')` formatiert mit einem Muster und einer Locale.
- Vergleichen Sie Datumsangaben mit `a.equals(b)` oder über `getTime()`. `===` vergleicht die Objektidentität.
- `getDay()` ist der Tag im Monat (1 bis 31), anders als beim nativen `Date`. Den Wochentag liefern `getDayOfWeek()` (0 ist Sonntag) oder `dayOfWeekISO()` (1 ist Montag).
- Prüfen Sie den Typ mit `value instanceof SuperScheduler.Date`, nie über den Namen des Konstruktors. Verwenden Sie in Lite das eigene `SuperScheduler.Date` von Lite; sind beide Editionen installiert, tauschen Sie ISO-Strings zwischen ihnen aus.

→ https://superscheduler.org/de/examples/fleet-rentals/
→ https://superscheduler.org/de/examples/lab-instruments/
## Nächste Schritte
- Ereignisse im React-State halten und Änderungen speichern: [Kontrollierte Ereignisse und Callbacks](https://superscheduler.org/de/docs/controlled-state/).
- Stunden und Minuten statt Tagen anzeigen: [Stunden, Minuten, Tage und Zoom](https://superscheduler.org/de/docs/time-scales-zoom/).
- Die Balken mit Ihren Feldern anpassen: [React-Render-Slots](https://superscheduler.org/de/docs/react-render-slots/) und [Themes](https://superscheduler.org/de/docs/theming/).
