sifting/io
US Equities
6 min readSiftingIO Team

Pre-market, regular hours, and after-hours: what US stock data exists in each session

Pre-market, regular hours, and after-hours US stock sessions with times in ET and UTC, what data exists in each, and how an app should handle the boundaries.

Pre-market, regular hours, and after-hours: what US stock data exists in each session

Do US stocks trade before the market opens? Yes. Pre-market trading starts at 4:00 AM Eastern Time and after-hours trading runs until 8:00 PM ET, so the US stock data day is 16 hours long even though the regular session lasts six and a half. Every price printed in an extended session is a real trade, and an app that displays US stock quotes has to decide what to do with those prints: which number is "the" price at 7:00 PM, why the daily bar disagrees with the last trade, and what happens to hardcoded session times twice a year when the UTC offset shifts.

The three US stock market sessions in ET and UTC#

SessionEastern TimeUTC in summer (EDT, UTC-4)UTC in winter (EST, UTC-5)
Pre-market4:00 AM to 9:30 AM08:00 to 13:3009:00 to 14:30
Regular hours9:30 AM to 4:00 PM13:30 to 20:0014:30 to 21:00
After-hours4:00 PM to 8:00 PM20:00 to 00:0021:00 to 01:00 (next day)

Two details in that table cause real bugs. The UTC columns swap twice a year because Eastern Time moves between UTC-4 and UTC-5, and the US changes its clocks on different dates than Europe, so for a few weeks each spring and autumn the offset a Europe-based developer assumes is wrong. And in winter the after-hours session crosses midnight UTC: a trade at 7:30 PM ET on a January Tuesday carries a Wednesday UTC date.

Holidays and half-days modify the schedule further. On scheduled half-days the regular session ends at 1:00 PM ET and the extended sessions compress around it. Rather than hardcoding any of this, pull it from the market-hours endpoints, where US equities is the us_equities market:

curl -H "X-API-Key: $SIFTING_KEY" \
  "https://api.sifting.io/v1/fnd/markets/us_equities/status"

curl -H "X-API-Key: $SIFTING_KEY" \
  "https://api.sifting.io/v1/fnd/markets/us_equities/hours"

curl -H "X-API-Key: $SIFTING_KEY" \
  "https://api.sifting.io/v1/fnd/markets/us_equities/calendar?from=2026-08-01&to=2026-12-31"

status answers "is it open right now", hours returns the weekly schedule in venue-local time, and calendar lists holidays and half-days up to 730 days ahead. The market hours API guide walks through these endpoints in detail.

What data exists in each session#

DataPre-marketRegular hoursAfter-hours
Trades (last price)Yes, sparse in many symbolsYes, continuous in liquid namesYes, clustered near the close and around earnings releases
Bid/ask quotesYes, with wider spreadsYes, the tightest of the dayYes, spreads widen as the evening goes on
VolumeA small fraction of the daily totalThe large majority of daily volumeSmall, spiky around news
Official opening and closing pricesNoYesNo

The liquidity caveats are worth stating plainly. Extended-session volume is a small fraction of regular-session volume, and it concentrates in large, widely held names. Many mid-cap and small-cap symbols print few or no trades before 8:00 AM ET. With fewer participants quoting, spreads run wider, and a single modest order can move the printed price further than it would at 11:00 AM. Minutes can pass between consecutive trades, so a pre-market "last price" may already be several minutes old when you read it. None of this makes the data wrong. It means the data needs a label when displayed.

The official opening and closing prices belong to the regular session, and daily bars follow that convention: a 1d OHLCV bar covers regular hours, so its open is the regular-session opening print rather than the first 4:00 AM trade, and an after-hours spike won't show up in that day's high.

curl -H "X-API-Key: $SIFTING_KEY" --compressed \
  "https://api.sifting.io/v1/hist/stocks/AAPL/bars?interval=1d&limit=10"

Historical bar endpoints require gzip, hence --compressed; without it the API returns 406 gzip_required. The live snapshot endpoints (/v1/last/trade/stocks/AAPL and /v1/last/quote/stocks/AAPL) return the latest print regardless of session, with a Unix epoch millisecond timestamp in t. That combination explains the classic mismatch: the chart says a stock closed at one price, the quote box says another, and both are right. The chart shows the daily bar's regular-session close while the quote box shows an after-hours trade.

How an app should handle session boundaries#

Build the day's boundaries once from the hours and calendar endpoints, convert them to epoch milliseconds, and classify every price by comparing its timestamp against them:

function sessionAt(tMs, b) {
  if (tMs >= b.preOpen && tMs < b.regOpen) return "pre-market";
  if (tMs >= b.regOpen && tMs < b.regClose) return "regular";
  if (tMs >= b.regClose && tMs < b.extClose) return "after-hours";
  return "closed";
}

With that label in hand, the display rules most quote UIs converge on are straightforward. During regular hours, show the live last trade and compute change against the prior regular close. After 4:00 PM ET, freeze the official close as its own line and show the extended-hours last separately, labeled, with its own change figure. Don't overwrite the close with an after-hours print. Before the open, apply the same rule in reverse: yesterday's close stays the anchor and the pre-market last is a separate, labeled number. At 9:30 AM the baseline doesn't change, only the prints do, which is why a stock can gap on the daily chart while the quote page saw a continuous path through pre-market.

For gating decisions (whether to poll, whether to show a live badge), prefer the status endpoint over local clock math, since it already accounts for holidays and half-days. The US stocks product page lists the full coverage this sits on: live prices, historical OHLCV bars, fundamentals, and SEC filings under one API key.

Common pitfalls#

Storing session times as fixed UTC offsets. A config that records the open as 13:30 UTC is correct from March to November and wrong the rest of the year, and the failure is silent: prices keep flowing while every session label sits an hour off. Derive boundaries from the hours endpoint or an IANA timezone library keyed to America/New_York, never from a stored offset.

Bucketing trades by UTC calendar date. In winter, after-hours trades from 7:00 PM ET onward land on the next UTC date, so a daily aggregation keyed to UTC dates splits one evening's activity across two rows, and a Friday evening's prints show up under Saturday. Bucket by the trading day from the calendar endpoint instead.

Retry-looping on 503 stale_snapshot. Outside the 16-hour data day, a live snapshot can age past the freshness threshold, and the API responds with 503 stale_snapshot plus last_t and server_now in the body. Overnight and on weekends this is the expected state for stocks. Treat it as "closed, last print at last_t" and back off until the next session opens.

Session boundaries are data. Fetch them, cache them for the day, and refresh them after midnight ET, and the pre-market, regular, and after-hours distinctions your users expect fall out of a single timestamp comparison. Read the docs

Keep reading

Related posts