# Time, sessions and resolutions

> Time zones, trading sessions, session-anchored intraday bars and aggregation.

Section: Data. Source: https://ortexcharts.com/docs/time

## Timestamps Are Milliseconds, UTC

Every timestamp in the library — bars, ticks, events, drawings, alerts, ranges — is
milliseconds since the Unix epoch in UTC. There is no other convention anywhere and no
per-series exception.

A time zone never changes a timestamp. It decides two things: where bar boundaries fall when
the library builds or aggregates bars itself, and how the axis, crosshair and legend labels
read.

```ts
const chart = createFinancialChart(el, {
  timeZone: "America/New_York",   // an IANA zone name
  locale: "en-GB",                // formatting locale for dates and numbers
  resolution: "1D",
});
```

Zone handling goes through `Intl`, so any IANA zone the browser knows works, daylight saving
transitions are handled by the platform rather than by an offset table, and there is no time
zone database in the bundle.

If your feed sends seconds, multiply by 1,000 before handing the rows over. If it sends
`YYYY-MM-DD` for daily bars, the [adapter layer](https://ortexcharts.com/docs/data) has a `date` time unit that
parses it as midnight in a named zone, which is almost always what you want for daily data.

## Resolutions

A resolution is written in datafeed notation: a number of minutes, or a number with a `D`,
`W` or `M` suffix.

| Text | Meaning |
|---|---|
| `1`, `5`, `15`, `30` | Minutes. |
| `60`, `240` | Minutes that happen to be whole hours; parsed as hours. |
| `1H`, `4H` | Hours, normalized to the minute form. |
| `1D`, `3D` | Days. |
| `1W` | Weeks. |
| `1M`, `3M` | Months. |

```ts
import { parseResolution, floorTimeToBar, nextBarTime, sameBar } from "@ortex-charts/math";

const res = parseResolution("15");
// { text: "15", unit: "minute", multiple: 15, isIntraday: true, durationMs: 900000 }

floorTimeToBar(Date.now(), res, "America/New_York");   // start of the current bar
nextBarTime(Date.now(), res, "America/New_York");      // start of the next one
sameBar(a, b, res, "America/New_York");                // do two times share a bar
```

`durationMs` is nominal: calendar units use one day, seven days and 30 days respectively,
because a month is not a fixed length. Bar boundaries themselves are computed with calendar
arithmetic in the chart time zone, not by dividing by `durationMs`.

The chart resolution matters in three places: it is what live ticks are folded into, it is
what the axis labels are chosen for, and it is what a datafeed binding requests. Set it in
options, or let the [datafeed binding](https://ortexcharts.com/docs/data) set it when the user changes it.

## Sessions

A session string describes trading hours as local wall-clock minutes, with commas between
segments and an optional day list after a colon.

```text
0930-1600                       regular US equity hours
0400-0930,0930-1600,1600-2000   with pre-market and after-hours
24x7                            continuous, for crypto
0930-1600:12345                 Monday to Friday only (the default anyway)
1700-1600                       an overnight session that wraps midnight
```

```ts
import { parseSession, isInSession, sessionBoundsForDay, sessionMinuteAt } from "@ortex-charts/math";

const session = parseSession("0400-0930,0930-1600,1600-2000");
isInSession(Date.now(), session, "America/New_York");             // boolean
sessionBoundsForDay(Date.now(), session, "America/New_York");     // [[start, end], …] in ms
sessionMinuteAt(Date.now(), session, "America/New_York");         // minutes elapsed in the session, NaN when closed
```

Days are 0 for Sunday through 6 for Saturday, and the default is Monday to Friday. Adjacent
segments are merged when parsed, so `0930-1200,1200-1600` becomes one segment.

The chart takes the session as an option, and the [datafeed binding](https://ortexcharts.com/docs/data) sets it for
you from `SymbolInfo.session` when a symbol resolves.

```ts
createFinancialChart(el, { timeZone: "America/New_York", resolution: "60", session: "0930-1600" });
```

## Shading Extended Hours

An intraday chart of a stock with pre-market and after-hours data draws 04:00 exactly like
10:00, and a reader cannot tell thin extended-hours prints from the real session. Two options
fix that, and both are off by default:

```ts
createFinancialChart(el, {
  timeZone: "America/New_York",
  resolution: "60",
  session: "0400-0930,0930-1600,1600-2000",
  sessions: {
    shadeExtendedHours: true,
    dayBreaks: true,
    // Regular hours. Without this the first segment is used, which for the
    // session above is the pre-market, so a symbol with extended hours wants
    // it stated.
    regular: "0930-1600",
  },
  data: bars,
});
```

| Option | Effect |
|---|---|
| `sessions.shadeExtendedHours` | Fills the columns outside `regular` with the theme's `sessionShade` |
| `sessions.dayBreaks` | Draws a line at the first column of each trading day, in the theme's `sessionBreak` |
| `sessions.regular` | Regular hours, as a session string. Defaults to the first segment of `session` |

The day boundary is the **exchange's**, not the reader's: two bars an hour apart across UTC
midnight are one New York day and two London days, and the break is drawn where the exchange
says the day turned. Both colours are theme tokens, so a custom theme sets them like any
other.

Neither option costs anything on a chart that does not use it: a daily chart, or a market
with no extended hours, produces no bands.

## Session-Anchored Intraday Bars

This is the part that is different from most libraries, and it fixes a long-standing
complaint about hourly candles.

If hourly bars are floored to the hour, a US equity session produces a first candle covering
only 09:30 to 10:00 and then a run of clean hours. The half-hour stub is an artifact of the
arithmetic, not of the market.

When the chart knows the session, intraday resolutions are **phased from the session open**
instead. With `0930-1600`, 60-minute bars run 09:30–10:30, 10:30–11:30 and so on, and
pre-market bars keep the same phase going backwards, so 08:30–09:30 lines up with the rest.

```ts
import { anchoredResolution, parseResolution } from "@ortex-charts/math";

const res = anchoredResolution(parseResolution("60"), "0930-1600");
// { …, anchorMinute: 570 }   // 09:30 expressed in minutes from midnight
```

Four things worth being precise about:

- The anchor applies to **everything the library builds or aggregates itself**: ticks folded
  into bars by `streamTo`, `aggregateBars`, and the higher-timeframe followers in
  [bar replay](https://ortexcharts.com/docs/replay).
- Bars delivered ready-made by a feed are shown exactly as given. The library does not
  re-cut somebody else's candles.
- Daily and longer resolutions, and `24x7` sessions, are unaffected.
- With several segments, the regular session is the one that starts latest before noon, so
  `0400-0930,0930-1600,1600-2000` anchors at 09:30 rather than at 04:00.

_Live example: [intraday-session](https://ortexcharts.com/examples/intraday-session)_

## The Time Axis

The axis is indexed by bar position, so closed periods do not exist on it. There is no gap
to hide, no `breaks` configuration and no special case for holidays: a day the market did
not trade simply has no bars.

Tick labels are chosen by weight rather than by a fixed interval, so a year label beats a
month label, a month beats a day and a day beats an hour. When there is not room for
everything, the more significant labels survive, which is why a two-year daily chart reads
as years and months rather than as a row of collided dates.

```ts
chart.applyOptions({
  timeScale: {
    barSpacing: 8,          // pixels per bar
    minBarSpacing: 0.5,
    maxBarSpacing: 60,
    rightOffsetPx: 60,      // empty space right of the last bar
    fixLeftEdge: false,     // stop scrolling past the first bar
    fixRightEdge: false,
    followRealtime: true,   // follow new bars while the view is at the right edge
    borderVisible: true,
    visible: true,
  },
  timeLabelGap: 72,         // minimum pixels between time labels
});
```

`followRealtime` is what makes a live chart scroll with new bars only while the user is
already at the right edge. Scrolling back into history stops the follow until
`scrollToRealtime()` is called or the user pans back.

## Working With Ranges

```ts
chart.timeScale.setVisibleRange(fromIndex, toIndex);
chart.timeScale.setVisibleTimeRange(fromTime, toTime);
chart.timeScale.fitContent();
chart.timeScale.scrollToRealtime();
chart.timeScale.setBarSpacing(12);

chart.timeScale.timeToCoordinate(time);
chart.timeScale.coordinateToTime(px);
chart.timeScale.timeToIndex(time);
chart.timeScale.indexToTime(index);

const off = chart.timeScale.subscribeVisibleRangeChange((r) => {
  console.log(r.fromIndex, r.toIndex, r.fromTime, r.toTime, r.barSpacing);
});
```

Index ranges are exact and time ranges are converted to them, so `setVisibleRange` is the
right call when you know how many bars you want on screen and `setVisibleTimeRange` is the
right call when you are synchronizing with something that thinks in dates.

Range presets — 1D, 5D, 1M, 3M, 6M, YTD, 1Y, 5Y, All — are not built into the toolbar. They
are a few lines on top of `setVisibleTimeRange`, and adding them as
[toolbar actions](https://ortexcharts.com/docs/toolbar) is the usual approach:

```ts
const RANGES = { "1M": 30, "3M": 91, "6M": 182, "1Y": 365, "5Y": 1826 };

function applyRange(days: number) {
  const to = chart.main.rawTimes[chart.main.rawLength - 1];
  chart.chart.timeScale.setVisibleTimeRange(to - days * 86_400_000, to);
}
```

## Aggregating Bars Yourself

When you have fine bars and want coarse ones without another request:

```ts
import { aggregateBars, anchoredResolution, barSeriesFromRows, barSeriesToRows, parseResolution } from "@ortex-charts/math";

const minute = barSeriesFromRows(minuteBars);
const hourly = aggregateBars(minute, anchoredResolution(parseResolution("60"), "0930-1600"), "America/New_York");

chart.setData(barSeriesToRows(hourly));
```

Aggregation carries `buyVolume` and `sellVolume` through when the source has them, so an
[order-flow](https://ortexcharts.com/docs/order-flow) chart keeps exact delta after aggregation.

## Formatting Time Yourself

```ts
import { formatBarTime, formatClock, formatDate } from "@ortex-charts/math";

formatBarTime(time, { timeZone: "America/New_York", resolution: parseResolution("15"), locale: "en-US" });
formatClock(time, { timeZone: "America/New_York" });
formatDate(time, { timeZone: "America/New_York" });
```

`formatBarTime` picks the shape from the resolution, which is what the crosshair label uses:
a date for daily bars, a date and a time for intraday ones. All three go through `Intl`, so
`locale` controls the ordering and the month names.

There is no string table for the rest of the interface. The toolbar, menus and dialogs in
`@ortex-charts/ui` are English only today; dates and numbers localize, labels do not.
