> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Working with time dimensions and scheduling for monitors

export const PartitionTimeline = ({currentTime = new Date(), granularity = "hourly", partitionShift = 0, skipLastPartitions = 0, scheduleDelayMinutes = 0, timezone = "UTC", partitionsToShow = 4}) => {
  const [now, setNow] = React.useState(currentTime);
  React.useEffect(() => {
    const interval = setInterval(() => {
      setNow(new Date());
    }, 1000);
    return () => clearInterval(interval);
  }, []);
  const getGranularityMinutes = gran => {
    switch (gran) {
      case "fivemin":
        return 5;
      case "tenmin":
        return 10;
      case "fifteenmin":
        return 15;
      case "thirtymin":
        return 30;
      case "hourly":
        return 60;
      case "daily":
        return 24 * 60;
      default:
        return 60;
    }
  };
  const addMinutes = (date, minutes) => {
    return new Date(date.getTime() + minutes * 60 * 1000);
  };
  const addDays = (date, days) => {
    return new Date(date.getTime() + days * 24 * 60 * 60 * 1000);
  };
  const addHours = (date, hours) => {
    return new Date(date.getTime() + hours * 60 * 60 * 1000);
  };
  const startOfDay = date => {
    const d = new Date(date);
    d.setHours(0, 0, 0, 0);
    return d;
  };
  const startOfHour = date => {
    const d = new Date(date);
    d.setMinutes(0, 0, 0);
    return d;
  };
  const floorToGranularity = (date, gran) => {
    const minutes = getGranularityMinutes(gran);
    if (gran === "daily") {
      return startOfDay(date);
    }
    if (gran === "hourly") {
      return startOfHour(date);
    }
    const startOfHourDate = startOfHour(date);
    const minutesIntoHour = date.getMinutes();
    const flooredMinutes = Math.floor(minutesIntoHour / minutes) * minutes;
    return addMinutes(startOfHourDate, flooredMinutes);
  };
  const addPartition = (date, gran) => {
    const minutes = getGranularityMinutes(gran);
    if (gran === "daily") {
      return addDays(date, 1);
    }
    if (gran === "hourly") {
      return addHours(date, 1);
    }
    return addMinutes(date, minutes);
  };
  const formatInTimeZone = (date, tz, format) => {
    const options = {
      timeZone: tz,
      month: 'short',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: format.includes('ss') ? '2-digit' : undefined,
      hour12: false
    };
    const formatter = new Intl.DateTimeFormat('en-US', options);
    const parts = formatter.formatToParts(date);
    const month = parts.find(p => p.type === 'month')?.value;
    const day = parts.find(p => p.type === 'day')?.value;
    const hour = parts.find(p => p.type === 'hour')?.value;
    const minute = parts.find(p => p.type === 'minute')?.value;
    const second = parts.find(p => p.type === 'second')?.value;
    if (format === "MMM dd, HH:mm:ss") {
      return `${month} ${day}, ${hour}:${minute}:${second || '00'}`;
    } else if (format === "MMM dd HH:mm") {
      return `${month} ${day} ${hour}:${minute}`;
    } else if (format === "HH:mm") {
      return `${hour}:${minute}`;
    }
    return formatter.format(date);
  };
  const partitions = React.useMemo(() => {
    const result = [];
    const currentPartitionStart = floorToGranularity(now, granularity);
    const shiftedCurrentPartitionStart = addMinutes(currentPartitionStart, partitionShift);
    const shiftedCurrentPartitionEnd = addPartition(shiftedCurrentPartitionStart, granularity);
    const nextPartitionStart = addPartition(shiftedCurrentPartitionStart, granularity);
    const startOffset = (partitionsToShow - 1) * getGranularityMinutes(granularity);
    let partitionStart = addMinutes(nextPartitionStart, -startOffset);
    for (let i = 0; i < partitionsToShow; i++) {
      const partitionEnd = addPartition(partitionStart, granularity);
      let state;
      let waitUntil;
      if (now < partitionStart) {
        state = "future";
      } else if (now >= partitionStart && now < partitionEnd) {
        state = "in-progress";
        const additionalWaitMinutes = skipLastPartitions * getGranularityMinutes(granularity);
        waitUntil = addMinutes(partitionEnd, scheduleDelayMinutes + additionalWaitMinutes);
      } else {
        const minutesSincePartitionEnd = (now.getTime() - partitionEnd.getTime()) / (60 * 1000);
        if (minutesSincePartitionEnd < scheduleDelayMinutes) {
          state = "waiting";
          waitUntil = addMinutes(partitionEnd, scheduleDelayMinutes);
        } else {
          const partitionsAgo = Math.floor((shiftedCurrentPartitionStart.getTime() - partitionStart.getTime()) / (getGranularityMinutes(granularity) * 60 * 1000));
          if (partitionsAgo < skipLastPartitions) {
            state = "waiting";
            const partitionsToAdvance = skipLastPartitions - partitionsAgo;
            const futurePartitionEnd = addMinutes(shiftedCurrentPartitionEnd, partitionsToAdvance * getGranularityMinutes(granularity));
            waitUntil = addMinutes(futurePartitionEnd, scheduleDelayMinutes);
          } else {
            state = "fetched";
          }
        }
      }
      const startDay = formatInTimeZone(partitionStart, timezone, "MMM dd HH:mm");
      const endDay = formatInTimeZone(partitionEnd, timezone, "MMM dd HH:mm");
      const startDayOnly = startDay.split(' ').slice(0, 2).join(' ');
      const endDayOnly = endDay.split(' ').slice(0, 2).join(' ');
      const daysDiffer = startDayOnly !== endDayOnly;
      let label;
      if (granularity === "daily") {
        label = `${formatInTimeZone(partitionStart, timezone, "MMM dd HH:mm")}-${formatInTimeZone(partitionEnd, timezone, daysDiffer ? "MMM dd HH:mm" : "HH:mm")}`;
      } else {
        label = `${formatInTimeZone(partitionStart, timezone, "HH:mm")}-${formatInTimeZone(partitionEnd, timezone, daysDiffer ? "MMM dd HH:mm" : "HH:mm")}`;
      }
      result.push({
        start: partitionStart,
        end: partitionEnd,
        state,
        label,
        waitUntil
      });
      partitionStart = addPartition(partitionStart, granularity);
    }
    return result;
  }, [now, granularity, partitionShift, skipLastPartitions, scheduleDelayMinutes, partitionsToShow, timezone]);
  const firstPartition = partitions[0];
  const lastPartition = partitions[partitions.length - 1];
  if (!firstPartition || !lastPartition) {
    return null;
  }
  const timelineStart = firstPartition.start.getTime();
  const timelineEnd = lastPartition.end.getTime();
  const timelineWidth = timelineEnd - timelineStart;
  const nowPosition = (now.getTime() - timelineStart) / timelineWidth * 100;
  const delayStartTime = now.getTime() - scheduleDelayMinutes * 60 * 1000;
  const delayStartPosition = Math.max(0, (delayStartTime - timelineStart) / timelineWidth * 100);
  const delayWidth = Math.max(0, nowPosition - delayStartPosition);
  const stateLabels = {
    fetched: "Will be fetched",
    "in-progress": "In progress",
    waiting: "Delayed",
    future: "Future"
  };
  return <div className="font-sans text-sm mt-8 mb-8">
      {}
      <div className="flex justify-between mb-6 text-sm">
        <div>
          <span className="font-semibold">Current time:</span>
          <div className="mt-1">
            {timezone !== "UTC" && <div className="text-gray-600 dark:text-gray-400">
                <span className="font-medium">{timezone}:</span>{" "}
                {formatInTimeZone(now, timezone, "MMM dd, HH:mm:ss")}
              </div>}
            <div className="text-gray-500 dark:text-gray-500">
              <span className="font-medium">UTC:</span>{" "}
              {formatInTimeZone(now, "UTC", "MMM dd, HH:mm:ss")}
            </div>
          </div>
        </div>
        <div className="text-right">
          <div className="text-gray-500 dark:text-gray-500">
            <span className="font-semibold">Granularity:</span> {granularity}
          </div>
          <div className="text-gray-500 dark:text-gray-500">
            <span className="font-semibold">Shift:</span> {partitionShift} min
          </div>
          <div className="text-gray-500 dark:text-gray-500">
            <span className="font-semibold">Skip last partitions:</span> {skipLastPartitions}
          </div>
          <div className="text-gray-500 dark:text-gray-500">
            <span className="font-semibold">Delay minutes:</span> {scheduleDelayMinutes}
          </div>
        </div>
      </div>

      {}
      <div className="relative mb-6">
        <div className="flex gap-2">
          {partitions.map((partition, index) => <div key={index} className="flex-1 min-w-0">
              <div className={`relative h-24 border-2 rounded-md transition-transform ${partition.state === "fetched" ? "bg-green-100 dark:bg-green-900/30 border-green-600 dark:border-green-500" : partition.state === "in-progress" ? "bg-orange-100 dark:bg-orange-900/30 border-orange-500 dark:border-orange-400" : partition.state === "waiting" ? "bg-orange-200 dark:bg-orange-800/40 border-orange-500 dark:border-orange-400" : "bg-gray-100 dark:bg-gray-800 border-gray-400 dark:border-gray-600"}`}>
                <div className="p-2 h-full flex flex-col justify-between">
                  <div className="text-xs font-semibold text-gray-900 dark:text-gray-100">
                    {partition.label}
                  </div>
                  <div className="text-xs text-gray-900 dark:text-gray-100 opacity-90">
                    {stateLabels[partition.state]}
                    {partition.waitUntil && <div className="mt-1 text-[10px] leading-tight">
                        {timezone !== "UTC" && <div>
                            Until: {formatInTimeZone(partition.waitUntil, timezone, "MMM dd HH:mm")}
                          </div>}
                        <div>
                          {timezone === "UTC" ? "Until: " : ""}
                          {formatInTimeZone(partition.waitUntil, "UTC", "MMM dd HH:mm")} UTC
                        </div>
                      </div>}
                  </div>
                </div>
              </div>

              {}
              <div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
                {timezone !== "UTC" && <div>{formatInTimeZone(partition.start, timezone, "MMM dd HH:mm")}</div>}
                <div className="text-gray-400 dark:text-gray-500">
                  {formatInTimeZone(partition.start, "UTC", "MMM dd HH:mm")} UTC
                </div>
              </div>
            </div>)}

          {}
          {scheduleDelayMinutes > 0 && delayWidth > 0 && <div className="absolute top-0 h-24 bg-blue-500 opacity-20 pointer-events-none z-10" style={{
    left: `${delayStartPosition}%`,
    width: `${delayWidth}%`
  }} />}
        </div>

        {}
        <div className="absolute top-0 h-24 w-0.5 bg-blue-500 pointer-events-none z-50 -translate-x-1/2" style={{
    left: `${nowPosition}%`
  }}>
          <div className="absolute -top-6 left-1/2 -translate-x-1/2 bg-blue-500 text-white px-2 py-1 rounded text-xs font-bold whitespace-nowrap shadow-md">
            NOW
          </div>
        </div>
      </div>

      {}
      <div className="flex flex-wrap gap-4 text-sm mb-6">
        <div className="flex items-center gap-2">
          <div className="w-4 h-4 border-2 border-green-600 dark:border-green-500 rounded bg-green-100 dark:bg-green-900/30"></div>
          <span>Will be fetched</span>
        </div>
        <div className="flex items-center gap-2">
          <div className="w-4 h-4 border-2 border-orange-500 dark:border-orange-400 rounded bg-orange-100 dark:bg-orange-900/30"></div>
          <span>In progress</span>
        </div>
        <div className="flex items-center gap-2">
          <div className="w-4 h-4 border-2 border-orange-500 dark:border-orange-400 rounded bg-orange-200 dark:bg-orange-800/40"></div>
          <span>Delayed</span>
        </div>
        <div className="flex items-center gap-2">
          <div className="w-4 h-4 border-2 border-gray-400 dark:border-gray-600 rounded bg-gray-100 dark:bg-gray-800"></div>
          <span>Future</span>
        </div>
      </div>
    </div>;
};

When defining scheduled monitors, you can control **when** and **how** your data is evaluated through *time partitioning* and *scheduling*. These settings determine both the time window a monitor measures (e.g. “yesterday’s data”) and when the measurement occurs (e.g. “4 AM New York time”).

This guide explains how scheduling and time partitioning interact, the available configurations, and how to visualize them using the [Partition Timeline Tool](https://app.synq.io/partition-timeline-test).

## Understanding time partitioning

Every monitor requires a **time partition field**, such as `created_at` or `date`. This field defines how data is grouped over time and is used to determine which records belong to a given measurement window.

You can then choose how frequently to measure the data — daily, hourly, or down to minutes — and optionally define a delay to account for late-arriving data.

## Scheduling options

Each monitor run is defined by three parameters:

1. **Frequency** – how often the monitor runs (e.g. daily, hourly).
2. **Partition time** – the timestamp field used to group data.
3. **Time zone and offset** – when in the day the monitor executes.

By default, all monitors execute internally in **UTC** for consistency and easier debugging. You can still define schedules in your local time zone — the platform automatically converts this to the correct UTC schedule under the hood.

### Common scheduling examples

* **Daily at midnight UTC**
  Measures data for the previous calendar day.
* **Daily at 2 AM UTC**
  Measures data for the previous calendar day at 2AM (useful for delayed ETL jobs).
* **Daily at midnight New York**
  Runs at 5 AM UTC but measures midnight → midnight in the New York time zone.

These combinations allow teams to align monitoring with pipeline completion times and business-day boundaries.

## Handling data delays

Many teams have ETL pipelines that complete several hours after midnight. In these cases, you can configure monitors to **delay execution** (for example, run at 4 AM but still measure the previous day’s partition).

This ensures freshness or volume checks evaluate complete data instead of partially loaded partitions.

## Advanced configurations

The platform supports advanced scheduling behaviors to cover all data-readiness scenarios:

* **Ignore last partition** – Skip the most recent day/hour if data isn’t yet ready (useful for delayed ingestion).

These options allow you to model both CRON-style schedules and dynamic, event-driven monitoring.

## Visualizing your configuration

You can preview how your scheduling and partitioning settings interact using the interactive partition timeline tool below. It helps you confirm exactly what data window each run will measure and when it will execute.

export const InteractiveTimeline = () => {
  const [granularity, setGranularity] = React.useState("hourly");
  const [ignoreLastPartition, setIgnoreLastPartition] = React.useState(0);
  const [delayMinutes, setDelayMinutes] = React.useState(0);
  const [timezone, setTimezone] = React.useState("UTC");
  const [partitionsToShow, setPartitionsToShow] = React.useState(4);
  return <div className="font-sans p-4 mt-4 mb-4">
      <h3 className="mt-0 mb-6 text-xl font-semibold">
        Interactive Partition Timeline
      </h3>

      <div className="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-4 mb-8 p-4 bg-gray-50 dark:bg-gray-900 rounded-md">
        <div>
          <label className="block mb-2 font-medium text-sm text-gray-700 dark:text-gray-300">
            Granularity
          </label>
          <select value={granularity} onChange={e => setGranularity(e.target.value)} className="w-full p-2 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm">
            <option value="fivemin">5 minutes</option>
            <option value="tenmin">10 minutes</option>
            <option value="fifteenmin">15 minutes</option>
            <option value="thirtymin">30 minutes</option>
            <option value="hourly">Hourly</option>
            <option value="daily">Daily</option>
          </select>
        </div>

        <div>
          <label className="block mb-2 font-medium text-sm text-gray-700 dark:text-gray-300">
            Ignore Last Partition
          </label>
          <input type="number" value={ignoreLastPartition} onChange={e => setIgnoreLastPartition(parseInt(e.target.value) || 0)} min="0" max="10" className="w-full p-2 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm" />
        </div>

        <div>
          <label className="block mb-2 font-medium text-sm text-gray-700 dark:text-gray-300">
            Delay Minutes
          </label>
          <input type="number" value={delayMinutes} onChange={e => setDelayMinutes(parseInt(e.target.value) || 0)} min="0" max="1440" step="1" className="w-full p-2 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm" />
        </div>

        <div>
          <label className="block mb-2 font-medium text-sm text-gray-700 dark:text-gray-300">
            Timezone
          </label>
          <select value={timezone} onChange={e => setTimezone(e.target.value)} className="w-full p-2 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm">
            <option value="UTC">UTC</option>
            <option value="America/New_York">America/New_York</option>
            <option value="America/Los_Angeles">America/Los_Angeles</option>
            <option value="America/Chicago">America/Chicago</option>
            <option value="Europe/London">Europe/London</option>
            <option value="Europe/Paris">Europe/Paris</option>
            <option value="Asia/Tokyo">Asia/Tokyo</option>
            <option value="Australia/Sydney">Australia/Sydney</option>
          </select>
        </div>

        <div>
          <label className="block mb-2 font-medium text-sm text-gray-700 dark:text-gray-300">
            Partitions to Show
          </label>
          <input type="number" value={partitionsToShow} onChange={e => setPartitionsToShow(parseInt(e.target.value) || 4)} min="2" max="8" className="w-full p-2 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm" />
        </div>
      </div>

      <div className="mb-6">
        <p className="font-medium mb-2 text-sm text-gray-700 dark:text-gray-300">Quick Presets:</p>
        <div className="flex flex-wrap gap-2">
          <button onClick={() => {
    setGranularity("daily");
    setIgnoreLastPartition(0);
    setDelayMinutes(0);
    setTimezone("UTC");
  }} className="group flex items-center pr-3 py-1.5 cursor-pointer gap-x-3 text-left rounded-xl outline-offset-[-1px] hover:bg-gray-600/5 dark:hover:bg-gray-200/5 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 text-sm transition-colors px-4">
            Daily at midnight UTC
          </button>
          <button onClick={() => {
    setGranularity("daily");
    setIgnoreLastPartition(0);
    setDelayMinutes(120);
    setTimezone("UTC");
  }} className="group flex items-center pr-3 py-1.5 cursor-pointer gap-x-3 text-left rounded-xl outline-offset-[-1px] hover:bg-gray-600/5 dark:hover:bg-gray-200/5 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 text-sm transition-colors px-4">
            Daily at 2 AM UTC
          </button>
          <button onClick={() => {
    setGranularity("daily");
    setIgnoreLastPartition(0);
    setDelayMinutes(0);
    setTimezone("America/New_York");
  }} className="group flex items-center pr-3 py-1.5 cursor-pointer gap-x-3 text-left rounded-xl outline-offset-[-1px] hover:bg-gray-600/5 dark:hover:bg-gray-200/5 text-gray-700 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-300 text-sm transition-colors px-4">
            Daily at midnight New York
          </button>
        </div>
      </div>

      <PartitionTimeline currentTime={new Date()} granularity={granularity} partitionShift={0} skipLastPartitions={ignoreLastPartition} scheduleDelayMinutes={delayMinutes} timezone={timezone} partitionsToShow={partitionsToShow} />
    </div>;
};

<InteractiveTimeline />
