---
title: "Clock and sessions kit"
description: "The ./sdk/clock module turns the one number bar.time() gives a wrun indicator, the bar's open time in UTC epoch seconds, into the questions a trader asks of a…"
order: 53
section: "functions"
---

<!-- source: docs/indicators/functions/time-and-sessions-kit.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Clock and sessions kit

The `./sdk/clock` module turns the one number `bar.time()` gives a
wrun indicator, the bar's open time in UTC epoch seconds, into the questions a
trader asks of a bar: what hour is it in New York, is this the first bar
of the day, is the cash session open, which bar is this, how long is a
bar, when does it close. A `Clock` answers the calendar questions in a
named time zone with its daylight rule applied; a `Session` answers
whether the bar sits inside an `0930-1600` window on the days you list,
when a session instance starts and when it has just closed. Both are
fed once per bar with `update(t)` and read as often as you like, and
neither allocates per bar. A `MarketSession` asks the market instead of a
zone: it reads the bar's trade date and session phase from the `time`
source (Market sessions, below). A higher timeframe built from the chart's
own bars or from a `candles` stream is the
[Higher-timeframe kit](higher-timeframe-kit.md).
The hand-rolled integer math they replace is still on
[Time and sessions](../core-concepts/time-and-sessions.md), the manual way,
if you want to see what the modules do for you.

Construct both classes in `onStart()`, never at module start: a bad zone or
a bad session spec aborts in the constructor, and an abort raised in
`onStart()` shows its message in the Console, while an abort at module start
loses it.

## The clock

| Export | Signature | Answers |
| --- | --- | --- |
| `Clock` | `new Clock(zone: string = "UTC")` | a bar clock in one zone; an unknown zone aborts with `Clock: unknown zone <x>` |
| `update` | `clock.update(t: f64): void` | folds the bar's open time (`bar.time()`, UTC epoch seconds); call once per bar |
| `offsetSec` | `clock.offsetSec(): i32` | the zone's offset from UTC for this bar in seconds, daylight time applied (New York reads `-18000` in winter, `-14400` in summer) |
| `hour`, `minute`, `second` | `clock.hour(): i32` | the local time of the bar's open: `0`..`23`, `0`..`59`, `0`..`59` (`second` is `0` on every candle grid) |
| `weekday` | `clock.weekday(): i32` | `0` Sunday .. `6` Saturday |
| `dayOfMonth`, `month`, `year`, `dayOfYear` | `clock.month(): i32` | `1`..`31`, `1`..`12`, the four-digit year, `1`..`366` |
| `dayKey` | `clock.dayKey(): i32` | local days since 1970-01-01: the same number on every bar of one local day |
| `weekKey` | `clock.weekKey(): i32` | local weeks since Monday 1970-01-05, a monotonic count (weeks start on Monday) |
| `monthKey` | `clock.monthKey(): i32` | `year * 12 + (month - 1)` |
| `quarterKey` | `clock.quarterKey(): i32` | `year * 4 + (month - 1) / 3` in integer division: the same number on every bar of one local quarter (the year's own key is `year()`) |
| `isNewDay`, `isNewWeek`, `isNewMonth`, `isNewQuarter`, `isNewYear` | `clock.isNewDay(): bool` | `true` on the first bar of the period and on the very first update; a quarter opens on January, April, July or October 1st |
| `index` | `clock.index(): i32` | updates seen so far, 0-based: `0` on the first bar, `-1` before any |
| `intervalSec` | `clock.intervalSec(): f64` | the smallest positive gap between consecutive bar opens seen so far (a weekend gap never widens it); `NaN` until two bars |
| `barCloseSec` | `clock.barCloseSec(): f64` | `t + intervalSec()`: where this bar closes, `NaN` while the interval is unknown |
| `dayOfWeek` | `clock.dayOfWeek(): i32` | Pine's `dayofweek`: `1` Sunday .. `7` Saturday (`weekday() + 1`), the scale of the `dayofweek.*` constants |
| `weekOfYear` | `clock.weekOfYear(): i32` | Pine's `weekofyear`, the ISO 8601 week: weeks run Monday to Sunday and belong to the year their Thursday is in, so week 1 holds January 4th, January 1st to 3rd can still be week 52 or 53 of the year before, and a late-December Monday can already be week 1; `0` before any update |
| `epochSec` | `clock.epochSec(year, month, day, hour = 0, minute = 0, second = 0): f64` | Pine's `timestamp(...)` in the clock's zone, in seconds: the UTC instant of a wall-clock time with the daylight rule applied; the parts normalise as Pine's do (month `13`, day `0`, `dayOfMonth + 1` past a month end, hour `24`, negative parts) |
| `reset` | `clock.reset(): void` | back to the freshly constructed state; the zone stays |

Two more names come with the clock, for a wall-clock time that needs no
zone and for Pine's weekday constants:

| Export | Signature | Answers |
| --- | --- | --- |
| `epochSec` | `epochSec(year, month, day, hour = 0, minute = 0, second = 0): f64` | the same instant read in UTC, with no clock: Pine's `timestamp("UTC", ...)`, in seconds, the parts normalised the same way |
| `dayofweek` | `dayofweek.SUNDAY` .. `dayofweek.SATURDAY` (`i32`, `1`..`7`) | Pine's `dayofweek.*` constants, the scale `dayOfWeek()` answers on |

`epochSec` answers seconds because every time in a wrun indicator is
seconds (`bar.time()`, a `param.time` default, a drawing's x); Pine's
`timestamp()` answers milliseconds, so a value a script holds in Pine's
unit is this times `1000.0`. A wall time inside a daylight gap (02:30 on
the morning the clocks go forward) moves forward by the gap, and one in
the repeated autumn hour reads as its first, daylight, occurrence: the
answers the Java calendar gives TradingView. Neither call allocates, so
`clock.epochSec(clock.year(), clock.month(), clock.dayOfMonth(), 9, 30)`
on every bar is fine.

Dates from 1970-01-01 to 2099-12-31 are valid, and the calendar is
integer math (days from civil and civil from days, no floating-point
days), so every field is exact. A non-finite `t` does not move the
clock: the fields keep their last values, the new-period flags read
`false`, the interval is not fed; only `index()` advances.

### Zones and their daylight rules

| Zone | Offset (standard / daylight) | Daylight rule |
| --- | --- | --- |
| `"UTC"`, `"GMT"`, `"Etc/UTC"`, `"Etc/GMT"` | `+00:00` | none |
| `"America/New_York"` | `-05:00` / `-04:00` | US: from the second Sunday of March 02:00 local standard time to the first Sunday of November 02:00 local daylight time |
| `"America/Toronto"` | `-05:00` / `-04:00` | US |
| `"America/Chicago"` | `-06:00` / `-05:00` | US |
| `"America/Denver"` | `-07:00` / `-06:00` | US |
| `"America/Phoenix"` | `-07:00` | none |
| `"America/Los_Angeles"` | `-08:00` / `-07:00` | US |
| `"America/Mexico_City"` | `-06:00` | none since 2023 |
| `"America/Bogota"` | `-05:00` | none |
| `"America/Lima"` | `-05:00` | none |
| `"America/Sao_Paulo"` | `-03:00` | none since 2020 |
| `"America/Argentina/Buenos_Aires"`, `"America/Buenos_Aires"` | `-03:00` | none since 2010 |
| `"Europe/London"` | `+00:00` / `+01:00` | EU: from the last Sunday of March 01:00 UTC to the last Sunday of October 01:00 UTC |
| `"Europe/Dublin"` | `+00:00` / `+01:00` | EU |
| `"Europe/Lisbon"` | `+00:00` / `+01:00` | EU |
| `"Atlantic/Reykjavik"` | `+00:00` | none |
| `"Europe/Paris"` | `+01:00` / `+02:00` | EU |
| `"Europe/Brussels"` | `+01:00` / `+02:00` | EU |
| `"Europe/Amsterdam"` | `+01:00` / `+02:00` | EU |
| `"Europe/Berlin"` | `+01:00` / `+02:00` | EU |
| `"Europe/Zurich"` | `+01:00` / `+02:00` | EU |
| `"Europe/Madrid"` | `+01:00` / `+02:00` | EU |
| `"Europe/Rome"` | `+01:00` / `+02:00` | EU |
| `"Europe/Vienna"` | `+01:00` / `+02:00` | EU |
| `"Europe/Stockholm"` | `+01:00` / `+02:00` | EU |
| `"Europe/Oslo"` | `+01:00` / `+02:00` | EU |
| `"Europe/Copenhagen"` | `+01:00` / `+02:00` | EU |
| `"Europe/Prague"` | `+01:00` / `+02:00` | EU |
| `"Europe/Warsaw"` | `+01:00` / `+02:00` | EU |
| `"Europe/Budapest"` | `+01:00` / `+02:00` | EU |
| `"Europe/Luxembourg"` | `+01:00` / `+02:00` | EU |
| `"Europe/Malta"` | `+01:00` / `+02:00` | EU |
| `"Europe/Monaco"` | `+01:00` / `+02:00` | EU |
| `"Europe/Zagreb"` | `+01:00` / `+02:00` | EU |
| `"Europe/Ljubljana"` | `+01:00` / `+02:00` | EU |
| `"Europe/Bratislava"` | `+01:00` / `+02:00` | EU |
| `"Europe/Belgrade"` | `+01:00` / `+02:00` | EU |
| `"Europe/Sarajevo"` | `+01:00` / `+02:00` | EU |
| `"Europe/Podgorica"` | `+01:00` / `+02:00` | EU |
| `"Europe/Skopje"` | `+01:00` / `+02:00` | EU |
| `"Europe/Tirane"` | `+01:00` / `+02:00` | EU |
| `"Europe/Vaduz"` | `+01:00` / `+02:00` | EU |
| `"Europe/Andorra"` | `+01:00` / `+02:00` | EU |
| `"Europe/Gibraltar"` | `+01:00` / `+02:00` | EU |
| `"Europe/San_Marino"` | `+01:00` / `+02:00` | EU |
| `"Europe/Vatican"` | `+01:00` / `+02:00` | EU |
| `"Europe/Helsinki"` | `+02:00` / `+03:00` | EU |
| `"Europe/Athens"` | `+02:00` / `+03:00` | EU |
| `"Europe/Bucharest"` | `+02:00` / `+03:00` | EU |
| `"Europe/Sofia"` | `+02:00` / `+03:00` | EU |
| `"Europe/Riga"` | `+02:00` / `+03:00` | EU |
| `"Europe/Tallinn"` | `+02:00` / `+03:00` | EU |
| `"Europe/Vilnius"` | `+02:00` / `+03:00` | EU |
| `"Europe/Kyiv"`, `"Europe/Kiev"` | `+02:00` / `+03:00` | EU |
| `"Europe/Chisinau"` | `+02:00` / `+03:00` | EU (since 2022; Moldova switched at 00:00 UTC before) |
| `"Asia/Nicosia"`, `"Europe/Nicosia"` | `+02:00` / `+03:00` | EU |
| `"Europe/Istanbul"` | `+03:00` | none since 2017 |
| `"Europe/Moscow"` | `+03:00` | none since 2015 |
| `"Europe/Minsk"` | `+03:00` | none since 2012 |
| `"Africa/Johannesburg"` | `+02:00` | none |
| `"Africa/Cairo"` | `+02:00` / `+03:00` | Egypt: from the last Friday of April 00:00 local standard time to the last Thursday of October 24:00 local daylight time (since 2023) |
| `"Africa/Lagos"` | `+01:00` | none |
| `"Africa/Nairobi"` | `+03:00` | none |
| `"Asia/Jerusalem"`, `"Asia/Tel_Aviv"` | `+02:00` / `+03:00` | Israel: from the Friday before the last Sunday of March 02:00 local standard time to the last Sunday of October 02:00 local daylight time (since 2013) |
| `"Asia/Riyadh"` | `+03:00` | none |
| `"Asia/Dubai"` | `+04:00` | none |
| `"Asia/Tehran"` | `+03:30` | none since 2023 |
| `"Asia/Karachi"` | `+05:00` | none since 2010 |
| `"Asia/Kolkata"`, `"Asia/Calcutta"` | `+05:30` | none |
| `"Asia/Kathmandu"`, `"Asia/Katmandu"` | `+05:45` | none |
| `"Asia/Dhaka"` | `+06:00` | none since 2010 |
| `"Asia/Yangon"`, `"Asia/Rangoon"` | `+06:30` | none |
| `"Asia/Bangkok"` | `+07:00` | none |
| `"Asia/Jakarta"` | `+07:00` | none |
| `"Asia/Ho_Chi_Minh"`, `"Asia/Saigon"` | `+07:00` | none |
| `"Asia/Singapore"` | `+08:00` | none |
| `"Asia/Kuala_Lumpur"` | `+08:00` | none |
| `"Asia/Manila"` | `+08:00` | none |
| `"Asia/Hong_Kong"` | `+08:00` | none |
| `"Asia/Shanghai"` | `+08:00` | none |
| `"Asia/Taipei"` | `+08:00` | none |
| `"Asia/Seoul"` | `+09:00` | none |
| `"Asia/Tokyo"` | `+09:00` | none |
| `"Australia/Perth"` | `+08:00` | none since 2010 |
| `"Australia/Darwin"` | `+09:30` | none |
| `"Australia/Adelaide"` | `+09:30` / `+10:30` | AU: from the first Sunday of October 02:00 local standard time to the first Sunday of April 03:00 local daylight time (it spans the new year; since 2008) |
| `"Australia/Brisbane"` | `+10:00` | none |
| `"Australia/Sydney"` | `+10:00` / `+11:00` | AU (since 2008) |
| `"Australia/Melbourne"` | `+10:00` / `+11:00` | AU (since 2008) |
| `"Australia/Hobart"` | `+10:00` / `+11:00` | AU (since 2008) |
| `"Pacific/Auckland"` | `+12:00` / `+13:00` | NZ: from the last Sunday of September 02:00 local standard time to the first Sunday of April 03:00 local daylight time (it spans the new year; since 2008) |
| `"+HH:MM"`, `"-HH:MM"`, `"+H:MM"` | as written | none: a fixed offset written with a colon, such as `"+05:30"` or `"+5:30"`, hours `00`..`23` (the bound the first `Clock` had, so `"+15:00"` still builds) |
| `"+H"`, `"+HHMM"` | as written | none: the same without a colon, such as `"-3"` or `"+0530"`, at most `14:00` either way so a typo cannot pass as an offset |
| `"UTC+5:30"`, `"GMT-4"`, `"UTC+0530"` | as written | none: the same offsets after a `UTC` or `GMT` prefix, the way Pine scripts spell them, with the same bounds (`"UTC+15:30"` builds, `"UTC+15"` aborts) |
| `"Etc/GMT+N"`, `"Etc/GMT-N"` | the sign inverted | none: the tz database's `Etc` zones, whole hours up to `14` with the sign inverted (`"Etc/GMT+5"` is `-05:00`) |

Every year in the valid range follows today's rule for its zone; where a
rule changed after 2007 the row says since when (Mexico City kept
daylight time until 2022, so a 2019 summer bar reads one hour off here),
and a zone's history before that year is not modelled. The table holds
every European capital with a zone of its own in the tz database,
Reykjavik to Nicosia and Chisinau (Pristina has none: Kosovo keeps
Belgrade's time, so `"Europe/Belgrade"` serves it). Anything else passed
as a zone aborts at construction with `Clock: unknown zone <x>`.

## Sessions

| Export | Signature | Answers |
| --- | --- | --- |
| `Session` | `new Session(spec: string, zone: string = "UTC", days: string = "0123456")` | an `"HHMM-HHMM"` window in the zone's local time on the weekdays listed; a spec that is not `HHMM-HHMM` aborts with `Session: bad spec <x>`, a day character outside `0`..`6` with `Session: bad days <x>` |
| `update` | `session.update(t: f64): void` | folds the bar's open time; call once per bar |
| `isIn` | `session.isIn(): bool` | the bar's open sits inside the window on a listed day |
| `isFirst` | `session.isFirst(): bool` | this is the first in-session bar of an instance |
| `closed` | `session.closed(): bool` | an instance ended before this bar: `true` on the first update after it, whether this bar is outside or already inside the next instance |
| `key` | `session.key(): i32` | the `dayKey` of the instance's opening day while in, `-1` outside |
| `reset` | `session.reset(): void` | forgets the running instance; the spec, zone and days stay |

The rules, written out:

- **Half-open.** A bar is in when `open <= local time of day < close`,
  tested on the bar's open time. `"0930-1600"` includes the 09:30 bar and
  excludes the 16:00 bar. `open == close` is never in. `"2400"` is
  accepted as a close, so `"0000-2400"` is the whole day.
- **Crossing midnight.** A close before its open wraps: `"2200-0400"`
  runs from 22:00 to 04:00 the next morning. Every bar of that window
  belongs to the day it opened on, so a Friday-night session keyed to
  Friday runs into Saturday morning.
- **The day digits.** `days` lists the weekdays the session may OPEN on,
  as the same digits `weekday()` uses: `0` Sunday .. `6` Saturday.
  `"12345"` is Monday to Friday; a `"2200-0400"` session on `"12345"`
  never opens on Saturday or Sunday night. Pine's session day string
  counts Sunday as `1`, so its `"23456"` is `"12345"` here.
- **One instance per opening day.** `key()` is the local `dayKey` of the
  opening day; it stays the same across a midnight crossing and changes
  when the next instance opens. `isFirst()` marks the first bar of an
  instance. `closed()` marks the first update after an instance ended:
  on a daily chart, where every bar is a new instance, `closed()` and
  `isFirst()` are both `true` on every bar after the first.

## Market sessions

`Session` is a window you define; `MarketSession` is the market's own
calendar, read from the two market facts the chart fills on every bar:
`time.trade_date` (the exchange trade date, epoch seconds at 00:00 UTC)
and `time.session` (`1` regular, `2` pre-market, `3` after-hours, `0`
closed). A CME evening bar already belongs to the next trade date, a US
stock bar is pre-market, regular or after-hours by the New York clock,
and a market without sessions is regular on every bar with the UTC date
as its trade date ([Data sources](../core-concepts/data-sources.md)).

| Export | Signature | Answers |
| --- | --- | --- |
| `MarketSession` | `new MarketSession()` | the market's trade date and session phase, bar by bar |
| `update` | `ms.update(tradeDateSec: f64, session: f64): void` | folds `time.trade_date` and `time.session`; call once per bar |
| `isRegular`, `isPremarket`, `isAfterHours` | `ms.isRegular(): bool` | the bar's phase: regular, pre-market, after-hours |
| `isExtended` | `ms.isExtended(): bool` | pre-market or after-hours |
| `isClosed` | `ms.isClosed(): bool` | the market is closed on this bar (any session value other than `1`, `2`, `3`) |
| `isNewTradingDay` | `ms.isNewTradingDay(): bool` | the first bar of a trade date, and the very first update |
| `isFirstRegularBar` | `ms.isFirstRegularBar(): bool` | the first regular bar of a trade date, after its pre-market bars |
| `tradingDayKey` | `ms.tradingDayKey(): i32` | the trade date as days since 1970-01-01 |
| `tradingWeekKey` | `ms.tradingWeekKey(): i32` | weeks since Monday 1970-01-05 of the trade date: one number from a Monday trade date through its Sunday |
| `tradingMonthKey` | `ms.tradingMonthKey(): i32` | `year * 12 + (month - 1)` of the trade date |
| `reset` | `ms.reset(): void` | back to the freshly constructed state |

The warm-up rule is the `Clock`'s: the very first update is a new trading
day, and the first regular bar seen is a first regular bar even when the
loaded history starts inside a session. A non-finite trade date or
session moves nothing (the phase and keys keep their values, both flags
read `false`); before the first update every check reads `false` and the
keys read `0`. The checks map one to one onto Pine's `session.*`
variables ([Pine equivalents](../core-concepts/time-and-sessions.md#pine-equivalents)).

## A New York session filter

The close, drawn only while the cash session is open, with a data-only
flag for the session and one for its first bar. The chart's bars are in
UTC; the session is in New York time, daylight rule included, so the
window moves with the clocks in March and November without any change
to the file.

```typescript sample=fn-clock-kit-session-filter
output("cash_close", line, overlay, { color: "#2563eb", width: 2, description: "The close while the New York cash session is open, NaN outside" });
output("session_open", none, overlay, { description: "1 from 09:30 to 16:00 New York time on a weekday" });
output("session_start", none, overlay, { description: "1 on the first bar of each cash session" });

let cash = new Session("0930-1600");

function onStart(): void {
  // Constructed here so a bad spec or zone reports in the Console.
  cash = new Session("0930-1600", "America/New_York", "12345");
}

function onBar(): void {
  const close = bar.close();
  cash.update(bar.time());
  const inSession = cash.isIn();
  const first = cash.isFirst();
  out_cash_close(inSession ? close : NaN);
  out_session_open(inSession ? 1.0 : 0.0);
  out_session_start(first ? 1.0 : 0.0);
}
```

`session_open` is the gate a mark or an alert reads; `session_start` is
where an anchored VWAP or a session high-low box begins.

## A new-day reset of a running sum

Volume accumulated since the New York day started, and since the week
started, with the bar's position inside the day. `isNewDay()` is `true`
on the first bar after local midnight and on the very first bar, so the
sums start clean at both; `isNewWeek()` flips on the first bar of
Monday.

```typescript sample=fn-clock-kit-daily-reset
output("day_volume", histogram, lower, { color: "#2563eb", description: "Volume since the New York day started" });
output("week_volume", line, lower, { color: "#f59e0b", description: "Volume since the week started (Monday, New York time)" });
output("bars_today", none, lower, { description: "Bars since the day started, 0 on its first bar" });

let clock = new Clock();
let dayVolume: f64 = 0.0;
let weekVolume: f64 = 0.0;
let barsToday: i32 = 0;

function onStart(): void {
  clock = new Clock("America/New_York");
}

function onBar(): void {
  clock.update(bar.time());
  if (clock.isNewDay()) {
    dayVolume = 0.0;
    barsToday = 0;
  } else {
    barsToday += 1;
  }
  if (clock.isNewWeek()) weekVolume = 0.0;
  const volume = bar.volume();
  dayVolume += volume;
  weekVolume += volume;
  if (isNaN(bar.close())) return;
  out_day_volume(dayVolume);
  out_week_volume(weekVolume);
  out_bars_today(f64(barsToday));
}
```

The same shape resets on `isNewMonth()`, `isNewQuarter()` or
`isNewYear()`, or compares `dayKey()` with a stored value when the
period is yours to define.

## An interval-aware bar-close estimate

The module never sees a wall clock, but it sees the gaps between bar
opens: `intervalSec()` is the smallest positive gap so far, so after two
bars it is the chart interval, and a weekend gap never stretches it.
`barCloseSec()` adds that interval to the open. A second `Clock` fed the
close time answers what hour the bar closes in, which is how a file
knows that the 15:30 bar on a 1-hour chart runs past the cash close.

```typescript sample=fn-clock-kit-bar-close
output("bar_close", none, overlay, { description: "The bar's close time in epoch seconds, NaN until the interval is known" });
output("bar_minutes", none, lower, { description: "The chart interval in minutes, from the smallest gap seen" });
output("close_hour", none, lower, { description: "The New York hour the bar closes in, NaN on the first bar" });

let opens = new Clock();
let closes = new Clock();

function onStart(): void {
  opens = new Clock("America/New_York");
  closes = new Clock("America/New_York");
}

function onBar(): void {
  opens.update(bar.time());
  // NaN on the first bar: a non-finite time does not move a clock.
  closes.update(opens.barCloseSec());
  if (isNaN(bar.close())) return;
  const interval = opens.intervalSec();
  out_bar_close(opens.barCloseSec());
  out_bar_minutes(interval / 60.0);
  out_close_hour(isNaN(interval) ? NaN : f64(closes.hour()));
}
```

`index()` on the same clock is the bar index, 0 on the first bar the
chart loaded; it counts every update, so it restarts after `reset()`.

## Calendar questions

Pine's calendar calls answer on the same clock: a wall-clock time as a
timestamp, the quarter, the ISO week and the weekday. The price at the New
York cash open held through the day, the open of the running quarter, and
two data-only readings:

```typescript sample=fn-clock-kit-calendar
// The price at the New York cash open, held through the day; the open of the running quarter; the ISO week and a Monday flag.
output("cash_open", line, overlay, { color: "#2563eb", width: 2, description: "The open of the bar that holds 09:30 New York time, held for the rest of the day" });
output("quarter_open", line, overlay, { color: "#94a3b8", line_style: "dashed", description: "The open of the running quarter, New York time" });
output("iso_week", none, lower, { description: "The ISO 8601 week of the bar, 1 to 53" });
output("monday", none, lower, { description: "1 on a Monday in New York, else 0" });

let clock = new Clock();
let cashOpen: f64 = NaN;
let quarterOpen: f64 = NaN;

function onStart(): void {
  clock = new Clock("America/New_York");
}

function onBar(): void {
  const t = bar.time();
  clock.update(t);
  if (clock.isNewDay()) cashOpen = NaN;
  if (clock.isNewQuarter()) quarterOpen = bar.open();
  // Today's 09:30 in New York as UTC epoch seconds, daylight time applied.
  const cashOpenSec = clock.epochSec(clock.year(), clock.month(), clock.dayOfMonth(), 9, 30);
  // The bar that holds 09:30 sets the level. The interval is NaN on the first bar, so nothing is set there.
  if (t <= cashOpenSec && cashOpenSec < t + clock.intervalSec()) cashOpen = bar.open();
  out_cash_open(cashOpen);
  out_quarter_open(quarterOpen);
  out_iso_week(f64(clock.weekOfYear()));
  out_monday(clock.dayOfWeek() == dayofweek.MONDAY ? 1.0 : 0.0);
}
```

- **A wall-clock time.** `clock.epochSec(year, month, day, 9, 30)` is
  that day's 09:30 in New York as UTC epoch seconds: 14:30 UTC in winter,
  13:30 UTC in summer. The bar that holds it sets the level, so on a 1-hour
  chart the line starts at the 09:00 New York bar.
- **The quarter.** `isNewQuarter()` is true on the first bar of January,
  April, July and October, so the open is taken there. The first quarter on
  the chart starts at the first loaded bar. `quarterKey()` holds one number
  for the whole quarter, for a reset you compare yourself.
- **The week.** `weekOfYear()` is Pine's `weekofyear`, the ISO week: Monday
  to Sunday, and week 1 holds January 4th.
- **The weekday.** `dayOfWeek()` counts 1 Sunday to 7 Saturday, as Pine
  does, so it compares with `dayofweek.MONDAY`. `weekday()` counts from 0.

## From Pine

One-to-one where the meaning is the same; the zone is the difference,
since Pine reads the chart's exchange time and a `Clock` reads the zone
you name.

| Pine | Here |
| --- | --- |
| `hour`, `minute`, `second` | `clock.hour()`, `clock.minute()`, `clock.second()` |
| `dayofweek` (`1` Sunday .. `7` Saturday), `dayofweek.monday` | `clock.dayOfWeek()`, `dayofweek.MONDAY` (`clock.weekday()` counts the same day from `0`) |
| `weekofyear` | `clock.weekOfYear()`: the ISO 8601 week, Monday to Sunday (TradingView's own week-of-month example shows a Sunday-evening open still in the week before, and PineTS implements the ISO number) |
| `timestamp(year, month, dayofmonth, 9, 30)` | `clock.epochSec(clock.year(), clock.month(), clock.dayOfMonth(), 9, 30)`, in seconds (`* 1000.0` where a script holds Pine's milliseconds) |
| `timestamp("America/New_York", 2024, 1, 15, 9, 30)` | `ny.epochSec(2024, 1, 15, 9, 30)` on a `Clock` built in that zone |
| `timestamp("UTC", 2024, 1, 15, 9, 30)`, or `timestamp(2024, 1, 15, 9, 30)` on a 24/7 venue (its exchange zone is UTC) | `epochSec(2024, 1, 15, 9, 30)`; a literal date the user may change is a `param.time` default |
| `dayofmonth`, `month`, `year` | `clock.dayOfMonth()`, `clock.month()`, `clock.year()` |
| `timeframe.change("D")`, `("W")`, `("M")`, `("3M")`, `("12M")` | `clock.isNewDay()`, `isNewWeek()`, `isNewMonth()`, `isNewQuarter()`, `isNewYear()` in the zone you name; a `PeriodLevels` built from the same word answers the same with `isNew()` ([Levels kit](levels-kit.md)) |
| `bar_index` | `clock.index()` |
| `not na(time(timeframe.period, "0930-1600:23456"))` | `session.isIn()` with `new Session("0930-1600", zone, "12345")` |
| `time_close` | `clock.barCloseSec()` (in seconds, from the observed interval) |
| `session.ismarket`, `session.isfirstbar_regular`, `time_tradingday` | `MarketSession` over `time.session` and `time.trade_date` (Market sessions, above) |
