Aller au contenu
SuperScheduler

ProductionS’applique àLite et Pro

Langues, dates civiles et fuseaux horaires

Définissez `locale` avec n’importe quel identifiant de locale Intl, comme `fr-fr` : les noms des mois et des jours, l’horloge sur 12 ou 24 heures et le premier jour de la semaine en découlent, et `timeFormat` et `weekStarts` remplacent ces deux derniers. Les dates sont des valeurs civiles d’horloge murale : le planificateur ne convertit jamais de fuseau horaire et ne développe jamais les événements récurrents. Convertissez les instants dans le fuseau horaire de l’activité avant de passer les événements, reconvertissez-les à l’enregistrement, et développez les séries en occurrences dans votre application.

Vérifié avec la v0.1.0 · relu le 7 octobre 2026.md

Un planificateur montre des dates à des personnes : deux préoccupations distinctes se rencontrent donc ici. La localisation décide de la façon d’écrire une date : noms, ordre, horloge et premier jour de la semaine. La sémantique du temps décide de quelle date il s’agit : la bibliothèque travaille avec des valeurs civiles d’horloge murale et laisse les fuseaux horaires et la récurrence à votre application. Ce guide couvre les deux, pour Pro et, quand c’est indiqué, pour Lite.

Définir la locale

locale accepte n’importe quel identifiant de locale que Intl comprend, écrit à la manière de SuperScheduler, en minuscules : en-us (par défaut), en-gb, fr-fr, de-de, es-es, pt-br, nl-nl, ja-jp, etc. Il n’y a aucune liste à enregistrer. en_US est normalisé en en-us, et un identifiant que Intl ne sait pas résoudre se rabat sur en-us.

Dans Pro, la locale détermine :

  • les noms des mois et des jours dans les en-têtes de temps par défaut et dans chaque motif format d’en-tête ;
  • l’horloge par défaut quand timeFormat vaut 'Auto' (12 heures pour en-us, 24 heures pour la plupart des locales européennes) ;
  • le premier jour de la semaine quand weekStarts vaut 'Auto' (dimanche pour en-us et pt-br, lundi pour la plupart des locales européennes) ;
  • les motifs de date par défaut des en-têtes de jour et les dates de la carte de glissement ;
  • la langue des annonces clavier et de certains libellés intégrés (voir Chaînes intégrées).

Lite accepte les mêmes identifiants locale pour ses en-têtes de jour.

src/FrenchPlanning.tsxtsx
import { useCallback, useState } from 'react'
import { SuperScheduler, SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerEventClickArgs } from 'super-scheduler'

// One constant for the scheduler and for every date you format yourself.
const LOCALE = 'fr-fr'
const WEEK_START = SuperScheduler.Date.today().firstDayOfWeek(1)

const TIME_HEADERS: SuperScheduler.TimeHeaderData[] = [
  { groupBy: 'Day', format: 'dddd d MMMM' }, // "lundi 5 octobre"
  { groupBy: 'Hour' }, // default labels follow timeFormat: "0" to "23" here, "2 PM" in 12-hour mode
]

interface Props {
  readonly rooms: SuperScheduler.ResourceData[]
  readonly events: SuperScheduler.EventData[]
}

export function FrenchPlanning({ rooms, events }: Props) {
  const [summary, setSummary] = useState('')

  // SuperScheduler.Date#toString does not read the scheduler's locale: pass it explicitly.
  const onEventClick = useCallback((args: SchedulerEventClickArgs) => {
    const start = args.e.start().toString('dddd d MMMM, HH:mm', LOCALE)
    const end = args.e.end().toString('HH:mm', LOCALE)
    setSummary(`${args.e.text()} : ${start} – ${end}`)
  }, [])

  return (
    <>
      <p aria-live="polite">{summary}</p>
      <SuperSchedulerComponent
        locale={LOCALE}
        // 'Auto' already gives Monday and a 24-hour clock for fr-fr; explicit values ignore the locale.
        weekStarts={1}
        timeFormat="Clock24Hours"
        startDate={WEEK_START}
        days={7}
        scale="Hour"
        cellWidth={48}
        timeHeaders={TIME_HEADERS}
        resources={rooms}
        events={events}
        onEventClick={onEventClick}
      />
    </>
  )
}

Vous devriez voir des en-têtes comme « lundi 5 octobre » au-dessus de colonnes d’heures numérotées de 0 à 23, une semaine qui commence le lundi, et un résumé en français quand vous cliquez sur un événement.

Format d’horloge et premier jour de la semaine

timeFormat accepte 'Auto', 'Clock12Hours' ou 'Clock24Hours'. Il ne modifie que les libellés d’heures par défaut ; un format explicite dans timeHeaders l’emporte toujours ('HH:mm' pour 24 heures, 'h:mm tt' pour 12 heures). C’est un réglage d’affichage, pas une conversion horaire : le changer ne déplace jamais un événement.

weekStarts accepte 'Auto' ou un numéro de jour de 0 (dimanche) à 6 (samedi). Il agit sur les cellules et groupes d’en-tête Week, sur les lignes de semaine tracées en vue dézoomée et sur les numéros de semaine par défaut : numéros ISO quand les semaines commencent le lundi, numéros américains sinon. Dans votre propre code, date.firstDayOfWeek() prend le dimanche par défaut : passez donc la même valeur (firstDayOfWeek(1)) ou l’identifiant de locale (firstDayOfWeek('fr-fr')).

Ce que donne 'Auto' pour quelques identifiants :

Localeddd d MMMHorlogeDébut de semaine
en-usMo 5 Oct12 heuresdimanche
en-gbMo 5 Oct24 heureslundi
es-esL 5 oct24 heureslundi
de-deMo 5 Okt24 heureslundi
fr-frlu 5 oct.24 heureslundi
pt-brse 5 out.24 heuresdimanche

Formater les dates dans votre propre interface

SuperScheduler.Date formate avec les motifs de SuperScheduler : yyyy, yy, MMMM, MMM, MM, M, dddd, ddd, dd, d, HH, H, hh, h, mm, m, ss, s et tt (AM/PM). Tout le reste est du texte littéral.

ddd est le nom court du jour fourni par Intl, qui fait une ou deux lettres dans plusieurs locales : « Mo » en anglais, « L » en espagnol, « lu » en français, « dl » en catalan. Utilisez dddd pour le nom complet, enregistrez vos propres noms courts (voir plus bas), ou formatez directement avec Intl. date.toDate() renvoie une Date native avec les mêmes ticks, à lire en UTC : formatez-la donc avec timeZone: 'UTC' pour afficher exactement la valeur civile sur n’importe quel appareil :

src/format.tsts
import type { SuperScheduler } from 'super-scheduler'

// toDate() returns a native Date with the same ticks, meant to be read in UTC.
// Formatting it with timeZone 'UTC' shows exactly the civil value, on any device.
const dayFormat = new Intl.DateTimeFormat('fr-FR', {
  weekday: 'short',
  day: 'numeric',
  month: 'short',
  timeZone: 'UTC',
})
const stayFormat = new Intl.DateTimeFormat('en-GB', {
  day: 'numeric',
  month: 'short',
  hour: '2-digit',
  minute: '2-digit',
  timeZone: 'UTC',
})

export function formatDay(date: SuperScheduler.Date): string {
  return dayFormat.format(date.toDate()) // "lun. 5 oct."
}

export function formatStay(start: SuperScheduler.Date, end: SuperScheduler.Date): string {
  return stayFormat.formatRange(start.toDate(), end.toDate())
}

Noms et motifs personnalisés

SuperScheduler.Locale.register() remplace les noms et les motifs d’un identifiant pour tous les planificateurs et tous les appels de toString qui l’utilisent. Un SuperScheduler.Locale passé directement dans l’option locale est enregistré automatiquement.

src/locale.tsts
import { SuperScheduler } from 'super-scheduler'

// Start from the Intl data for the id: fields you leave out fall back to US English, not to French.
const base = SuperScheduler.Locale.find('fr-fr')

// Run once at startup, before the first scheduler mounts.
SuperScheduler.Locale.register(
  new SuperScheduler.Locale('fr-fr', {
    ...base,
    // `ddd` gives two-letter Intl abbreviations ("lu"); these read "lun.".
    dayNamesShort: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'],
  }),
)

// Every scheduler with locale="fr-fr" and every toString(pattern, 'fr-fr') now uses these names.
export const sample = new SuperScheduler.Date('2026-10-05').toString('ddd d MMM', 'fr-fr') // "lun. 5 oct."

Chaînes intégrées et leurs langues

La bibliothèque écrit elle-même quelques chaînes. La langue est la première partie de l’identifiant de locale (ca-es donne ca).

ChaînesLanguesPersonnalisation
Annonces clavier et libellés de focusanglais, espagnol, catalan, basque, galicien, allemand, français, italien, portugaisNon configurable
Durées et refus de la carte de glissement (« 2 nights », « Overlaps », « Not allowed »)Les neuf mêmes languesdragCard={{ labels: { ... } }}
Nom de la grille, textes de chargement, d’état vide et d’erreuranglais, ou espagnol pour les locales esemptyState, errorState et loadingLabelText ; le nom accessible de la grille est fixe
Libellés des entrées d’historique (« Move », « Resize »)anglais, ou espagnol pour les locales escreateHistory({ labels })
Libellés de la minimapanglais, ou espagnol pour les locales escreateMinimap(control, element, { labels }) ou la prop labels de SchedulerMinimap
Badge du niveau de détailanglais, ou espagnol pour les locales estroisième argument de createLodBadge
Lite : libellé de la grille et texte d’état videanglaisariaLabel, emptyState

Toute autre langue affiche l’anglais : une application en français ou en allemand doit donc passer ses propres chaînes pour l’historique, la minimap, le badge et les textes d’état. Le texte des événements, les noms des ressources et tout HTML que vous affichez sont à traduire par vous.

Dates civiles

Dans SuperScheduler, chaque date est une valeur civile d’horloge murale, sans fuseau horaire. '2026-10-01T10:00:00' signifie dix heures sur le planning, où que la page soit ouverte. Les conséquences :

  • Les chaînes exigent les secondes. '2026-10-01' et '2026-10-01T10:00:00' sont valides ; '2026-10-01T10:00' lève l’erreur « is not an ISO 8601 date ». Les objets Date natifs ne passent pas la vérification de types pour start ou end.
  • Les fuseaux sont convertis en UTC. Une chaîne avec Z ou un décalage est convertie en heure murale UTC : '2026-10-01T10:00:00+02:00' devient 08:00:00. Ne retirez les fuseaux qu’après avoir vous-même converti dans le fuseau horaire de l’activité.
  • Pas de surprise au changement d’heure. 2026-03-29T02:30:00 existe, et lui ajouter une heure donne 03:30, quel que soit le fuseau du navigateur. Les durées sont de simples différences d’horloge murale.
  • Les intervalles sont semi-ouverts. Un événement de 14:00 à 16:00 se termine avant une réservation qui commence à 16:00.
  • Dates natives. new SuperScheduler.Date(date) lit les champs UTC d’une Date native ; new SuperScheduler.Date(date, true) lit ses champs locaux. toDate() renvoie une Date à lire en UTC ; toDateLocal() en renvoie une dont les champs locaux affichent l’heure murale.
  • « Aujourd’hui » est celui de l’appareil du visiteur. SuperScheduler.Date.today(), le startDate par défaut, la mise en évidence du jour et la ligne de l’heure actuelle utilisent l’horloge de l’appareil. Une personne à New York qui consulte le planning d’un hôtel à Madrid voit la date du jour à New York. Si cela compte, calculez vous-même la date du jour de l’activité et passez-la dans startDate ou à scrollTo.

Les fuseaux horaires relèvent de votre application

Si votre backend stocke des instants (horodatages UTC), choisissez le fuseau horaire que représente chaque planificateur, en général celui du site ou du responsable de la ressource, et convertissez aux frontières. Intl.DateTimeFormat avec un timeZone vous donne l’heure murale de n’importe quel instant, sans dépendance supplémentaire :

src/time-zones.tsts
import { SuperScheduler } from 'super-scheduler'

const formatters = new Map<string, Intl.DateTimeFormat>()

function partsFormatter(timeZone: string): Intl.DateTimeFormat {
  let formatter = formatters.get(timeZone)
  if (formatter === undefined) {
    formatter = new Intl.DateTimeFormat('en-US', {
      timeZone,
      hourCycle: 'h23',
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
    })
    formatters.set(timeZone, formatter)
  }
  return formatter
}

/** What a wall clock in `timeZone` shows at `instant`, as a civil ISO string: "2026-10-01T10:00:00". */
export function toWallClock(instant: Date, timeZone: string): string {
  const part: Record<string, string> = {}
  for (const { type, value } of partsFormatter(timeZone).formatToParts(instant)) part[type] = value
  return `${part.year}-${part.month}-${part.day}T${part.hour}:${part.minute}:${part.second}`
}

function offsetAt(ms: number, timeZone: string): number {
  return Date.parse(`${toWallClock(new Date(ms), timeZone)}Z`) - ms
}

/**
 * The instant at which a wall clock in `timeZone` shows `wall`. Times skipped or repeated by a
 * daylight-saving change have no single answer: this picks a neighbouring instant, so validate
 * them in your application if they matter.
 */
export function fromWallClock(wall: SuperScheduler.DateInput, timeZone: string): Date {
  const asUtc = Date.parse(`${new SuperScheduler.Date(wall).value}Z`)
  const guess = asUtc - offsetAt(asUtc, timeZone)
  return new Date(asUtc - offsetAt(guess, timeZone))
}

/** API instants to scheduler events on the property's wall clock. */
export async function loadBookings(fromUtc: string, toUtc: string, timeZone: string) {
  const rows = await fetchBookingInstants(fromUtc, toUtc)
  return rows.map((row): SuperScheduler.EventData => ({
    id: row.id,
    resource: row.roomId,
    text: row.guest,
    start: toWallClock(new Date(row.startUtc), timeZone),
    end: toWallClock(new Date(row.endUtc), timeZone),
  }))
}

// Saving goes the other way: fromWallClock(event.start, 'Europe/Madrid').toISOString()

Convertissez à l’entrée (toWallClock quand vous transformez les lignes de l’API en événements) et à la sortie (fromWallClock quand vous enregistrez start et end, qui sont des objets SuperScheduler.Date après un glissement). Pour le chargement par plages, convertissez de la même façon le start et le end civils de la tranche avant d’interroger une API en UTC.

Les heures sautées ou répétées lors des changements d’heure sont une règle métier, pas un détail de formatage. Une réservation à 02:30 la nuit du passage à l’heure d’été n’existe pas à Madrid ; décidez s’il faut la refuser, la déplacer ou la stocker autrement.

Événements récurrents

Le planificateur n’a pas de moteur de récurrence. Les champs recurrent et recurrentMasterId sont typés, mais control.events.findRecurrent() est réservé et renvoie null. Stockez les séries dans votre application et développez-les en événements ordinaires pour les dates affichées. Donnez à chaque occurrence un id stable d’une requête à l’autre, par exemple l’id de la série suivi de sa date, pour que le chargement par plages puisse la fusionner :

src/recurrence.tsts
import { SuperScheduler } from 'super-scheduler'

/** A weekly series as your application stores it. */
export interface WeeklySeries {
  readonly id: string
  readonly resource: string
  readonly text: string
  /** First occurrence, civil date-time with seconds. */
  readonly start: string
  readonly durationMinutes: number
  /** 0 = Sunday ... 6 = Saturday. */
  readonly weekdays: readonly number[]
  /** Last day of the series, inclusive, as yyyy-MM-dd. */
  readonly until: string
  /** Days removed from the series, as yyyy-MM-dd. */
  readonly exceptions: readonly string[]
}

/** The occurrences that overlap [from, to), each with an id that is stable across requests. */
export function expandWeekly(
  series: WeeklySeries,
  from: SuperScheduler.Date,
  to: SuperScheduler.Date,
) {
  const first = new SuperScheduler.Date(series.start)
  const firstDay = first.getDatePart().ticks
  const afterLastDay = new SuperScheduler.Date(series.until).addDays(1).ticks
  // Look back far enough to catch an occurrence that started earlier and is still running.
  const lookBack = Math.ceil(series.durationMinutes / 1440)
  const events: SuperScheduler.EventData[] = []
  for (
    let day = from.getDatePart().addDays(-lookBack);
    day.ticks < to.ticks;
    day = day.addDays(1)
  ) {
    if (day.ticks < firstDay || day.ticks >= afterLastDay) continue
    if (!series.weekdays.includes(day.getDayOfWeek())) continue
    const key = day.toString('yyyy-MM-dd')
    if (series.exceptions.includes(key)) continue
    const start = day.addTime(first.getTimePart())
    const end = start.addMinutes(series.durationMinutes)
    if (end.ticks <= from.ticks) continue
    events.push({
      id: `${series.id}:${key}`,
      resource: series.resource,
      text: series.text,
      start,
      end,
    })
  }
  return events
}

La modification vous revient aussi. « Cette occurrence uniquement » signifie en général ajouter la date aux exceptions de la série et créer un événement indépendant ; « Cette occurrence et les suivantes » scinde la série ; « Toutes les occurrences » modifie la série et la développe à nouveau. Faites correspondre les modifications de onEventsChange à ces opérations grâce à l’id de l’occurrence. Développer les séries dans la fonction load d’un chargeur de plages garde les longues séries peu coûteuses : seules les tranches visibles sont développées.

Liste de vérification

  • Une seule constante LOCALE, passée au composant et à chaque appel de toString.
  • weekStarts et firstDayOfWeek() concordent.
  • Vos propres chaînes pour l’historique, la minimap, le badge et les textes d’état dans toute langue autre que l’anglais et l’espagnol.
  • Des dates d’événements sous forme de chaînes civiles avec secondes, converties dans le fuseau horaire de l’activité avant d’arriver au planificateur.
  • L’enregistrement reconvertit en instants si votre backend les stocke ainsi.
  • Des séries développées par plage visible, avec des id d’occurrence stables.

Planification de production audiovisuelleUn tournage déborde. Déplacez le montage qui en dépendait, comprenez pourquoi, puis revenez en arrière. Guides associés : Ressources, événements et intervalles, Échelles de temps et zoom, Clavier, accessibilité et tactile et Chargement des données par plage de dates.