US Market Hours in German Time (CET/CEST)
US market hours in German time: the NYSE session in CET and CEST, the Xetra overlap window, and the few weeks each year when the clock gap shrinks.
US stock market hours in German time run from 15:January to 22:January, and those two clock times hold all year. Germany sits six hours ahead of New York for roughly 48 weeks of the year, and both regions move their clocks in spring and autumn, which keeps the German start time steady while the label changes from CET to CEST. The exception is a short mismatch each spring and autumn, when the US open arrives an hour earlier on a German clock than the rest of the year.
US stock market hours in German time (CET and CEST)
Every time on this page starts from a single anchor: the New York exchange clock, Eastern Time. Eastern Time runs at UTC minus 5 in the US winter (EST) and UTC minus 4 in the US summer (EDT). German local time runs at UTC plus 1 in winter (CET) and UTC plus 2 in summer (CEST). The panel below converts the anchor through those offsets and recomputes on every rebuild of this page, so nothing in it can go stale as the switch dates move around the calendar.
| milestone | et_clock | utc_us_winter | german_cet | utc_us_summer | german_cest | et_minutes |
|---|---|---|---|---|---|---|
| Premarket opens | 04:00 | 09:January | 10:January | 08:July | 10:July | 240 |
| Regular session opens | 09:30 | 14:January | 15:January | 13:July | 15:July | 570 |
| Half-day early close | 13:00 | 18:January | 19:January | 17:July | 19:July | 780 |
| Regular session closes | 16:00 | 21:January | 22:January | 20:July | 22:July | 960 |
| After-hours ends | 20:00 | 01:January | 02:January | 00:July | 02:July | 1200 |
The exact SQL behind every number
SELECT
tupleElement(m, 1) AS milestone,
tupleElement(m, 2) AS et_clock,
formatDateTime(toTimeZone(et_winter, 'UTC'), '%H:%M') AS utc_us_winter,
formatDateTime(toTimeZone(et_winter, 'Europe/Berlin'), '%H:%M') AS german_cet,
formatDateTime(toTimeZone(et_summer, 'UTC'), '%H:%M') AS utc_us_summer,
formatDateTime(toTimeZone(et_summer, 'Europe/Berlin'), '%H:%M') AS german_cest,
tupleElement(m, 3) AS et_minutes
FROM
(
SELECT
m,
toDateTime(concat(toString(toYear(today())), '-01-15 ', tupleElement(m, 2), ':00'), 'America/New_York') AS et_winter,
toDateTime(concat(toString(toYear(today())), '-07-15 ', tupleElement(m, 2), ':00'), 'America/New_York') AS et_summer
FROM
(
SELECT arrayJoin([
('Premarket opens', '04:00', 240),
('Regular session opens', '09:30', 570),
('Half-day early close', '13:00', 780),
('Regular session closes', '16:00', 960),
('After-hours ends', '20:00', 1200)
]) AS m
)
)
ORDER BY et_minutesThe regular session, the stretch almost every quote, chart and closing price refers to, opens at 09:30 ET and ends at 16:00 ET. In Germany that is 15:January to 22:January in winter and 15:July to 22:July in summer: the same clock times, sitting on different UTC offsets underneath. Premarket trading opens at 04:00 ET, which is 10:January in Germany, and the after-hours session runs to 20:00 ET, or 02:January the following morning German time. The mechanics of those two thin windows sit in after-hours and premarket trading, and the full session-by-session clock in stock market hours.
When are Xetra and the NYSE open at the same time?
Xetra, the main German electronic order book, runs continuous trading from 09:00 to 17:30 German time on an ordinary day, with auctions at the edges. Its own closures and its extended-hours arrangements sit in Xetra trading hours and holidays. New York opens at 15:January German time, which leaves a two-hour overlap, 15:January to 17:30, when both books quote at once. That is the window where a US company's German listing and its home listing are priced side by side. After 17:30 the US tape keeps running alone for another four and a half hours, up to 22:January.
The weeks each year when US hours move in German time
The two regions do not switch to summer time on the same dates. US clocks change on the second Sunday in March and the first Sunday in November. EU clocks change on the last Sunday in March and the last Sunday in October. Two gaps open up:
- From the second Sunday in March until the last Sunday in March, the US is already on summer time while Germany is still on winter time.
- From the last Sunday in October until the first Sunday in November, Germany is back on winter time while the US is still on summer time.
In both windows the distance between the two clocks narrows from six hours to five, and the whole US session lands an hour earlier in Germany: the open at 14:March instead of 15:January, the close at 21:March instead of 22:January. The March gap is two or three weeks wide, depending on where the second and last Sundays fall. The autumn gap is always exactly one week. The panel below takes the 09:30 ET open for every US session of the past year and converts it to German local time, week by week.
| week | german_open | german_close | berlin_minus_et_hours |
|---|---|---|---|
| 2025-08-18 | 15:August | 22:August | 6 |
| 2025-08-25 | 15:August | 22:August | 6 |
| 2025-09-01 | 15:September | 22:September | 6 |
| 2025-09-08 | 15:September | 22:September | 6 |
| 2025-09-15 | 15:September | 22:September | 6 |
| 2025-09-22 | 15:September | 22:September | 6 |
| 2025-09-29 | 15:September | 22:September | 6 |
| 2025-10-06 | 15:October | 22:October | 6 |
| 2025-10-13 | 15:October | 22:October | 6 |
| 2025-10-20 | 15:October | 22:October | 6 |
| 2025-10-27 | 14:October | 21:October | 5 |
| 2025-11-03 | 15:November | 22:November | 6 |
| 2025-11-10 | 15:November | 22:November | 6 |
| 2025-11-17 | 15:November | 22:November | 6 |
| 2025-11-24 | 15:November | 22:November | 6 |
| 2025-12-01 | 15:December | 22:December | 6 |
| 2025-12-08 | 15:December | 22:December | 6 |
| 2025-12-15 | 15:December | 22:December | 6 |
| 2025-12-22 | 15:December | 22:December | 6 |
| 2025-12-29 | 15:January | 22:January | 6 |
The exact SQL behind every number
SELECT
toString(week) AS week,
any(formatDateTime(toTimeZone(us_open_et, 'Europe/Berlin'), '%H:%M')) AS german_open,
any(formatDateTime(toTimeZone(us_close_et, 'Europe/Berlin'), '%H:%M')) AS german_close,
any(toHour(toTimeZone(us_open_et, 'Europe/Berlin')) - toHour(us_open_et)) AS berlin_minus_et_hours
FROM
(
SELECT
toMonday(date) AS week,
toDateTime(concat(toString(date), ' 09:30:00'), 'America/New_York') AS us_open_et,
toDateTime(concat(toString(date), ' 16:00:00'), 'America/New_York') AS us_close_et
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= today() - 400
)
GROUP BY week
ORDER BY weekThe line sits flat at six hours across 58 weeks of real trading sessions and drops to five inside the two mismatch windows. Counting the affected sessions rather than the weeks makes the size of it concrete.
| year | mismatch_sessions | mismatch_open_german | mismatch_close_german | aligned_open_german | aligned_close_german |
|---|---|---|---|---|---|
| 2021 | 15 | 14:March | 21:March | 15:January | 22:January |
| 2022 | 15 | 14:March | 21:March | 15:January | 22:January |
| 2023 | 15 | 14:March | 21:March | 15:January | 22:January |
| 2024 | 19 | 14:March | 21:March | 15:January | 22:January |
| 2025 | 20 | 14:March | 21:March | 15:January | 22:January |
The exact SQL behind every number
SELECT
toString(toYear(session_date)) AS year,
countIf(gap_hours = 5) AS mismatch_sessions,
anyIf(german_open, gap_hours = 5) AS mismatch_open_german,
anyIf(german_close, gap_hours = 5) AS mismatch_close_german,
anyIf(german_open, gap_hours = 6) AS aligned_open_german,
anyIf(german_close, gap_hours = 6) AS aligned_close_german
FROM
(
SELECT
date AS session_date,
formatDateTime(toTimeZone(toDateTime(concat(toString(date), ' 09:30:00'), 'America/New_York'), 'Europe/Berlin'), '%H:%M') AS german_open,
formatDateTime(toTimeZone(toDateTime(concat(toString(date), ' 16:00:00'), 'America/New_York'), 'Europe/Berlin'), '%H:%M') AS german_close,
toHour(toTimeZone(toDateTime(concat(toString(date), ' 09:30:00'), 'America/New_York'), 'Europe/Berlin')) - 9 AS gap_hours
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
AND date >= toStartOfYear(today() - INTERVAL 5 YEAR)
AND date < toStartOfYear(today())
)
GROUP BY year
ORDER BY yearAcross the last 5 complete calendar years the mismatch covered 15 US trading sessions in 2021 and 20 in 2025. A standing alarm set for 15:January German time misses the first half hour of trading on every one of those days. The switch rules themselves, on both sides of the Atlantic, are laid out in US market hours and daylight saving time, and the same conversion done for other regions in US stock market hours around the world.
US holidays, German holidays, and the 13:00 half days
The two calendars disagree far more often than the clocks do. The exchange calendar below lists what is still ahead, with the half-day close converted to German time.
| closure_date | closure_label | weekday | holiday | day_status | et_close | german_close | days_away |
|---|---|---|---|---|---|---|---|
| 2026-11-26 | 26 November | Thu | Thanksgiving | closed | 61 | ||
| 2026-11-27 | 27 November | Fri | Thanksgiving | early-close | 13:November | 19:November | 62 |
| 2026-12-24 | 24 December | Thu | Christmas | early-close | 13:December | 19:December | 89 |
| 2026-12-25 | 25 December | Fri | Christmas | closed | 90 | ||
| 2027-01-01 | 1 January | Fri | New Years Day | closed | 97 | ||
| 2027-01-18 | 18 January | Mon | Martin Luther King, Jr. Day | closed | 114 | ||
| 2027-02-15 | 15 February | Mon | Washington's Birthday | closed | 142 | ||
| 2027-03-26 | 26 March | Fri | Good Friday | closed | 181 | ||
| 2027-05-31 | 31 May | Mon | Memorial Day | closed | 247 | ||
| 2027-06-18 | 18 June | Fri | Juneteenth | closed | 265 | ||
| 2027-07-05 | 5 July | Mon | Independence Day | closed | 282 | ||
| 2027-09-06 | 6 September | Mon | Labor Day | closed | 345 |
The exact SQL behind every number
SELECT
toString(date) AS closure_date,
concat(toString(toDayOfMonth(date)), ' ', monthName(date)) AS closure_label,
formatDateTime(date, '%a') AS weekday,
any(name) AS holiday,
any(status) AS day_status,
any(if(status = 'early-close',
formatDateTime(toTimeZone(close, 'America/New_York'), '%H:%M'),
'')) AS et_close,
any(if(status = 'early-close',
formatDateTime(toTimeZone(close, 'Europe/Berlin'), '%H:%M'),
'')) AS german_close,
dateDiff('day', today(), date) AS days_away
FROM global_markets.stocks_market_holidays
WHERE date >= today()
GROUP BY date
ORDER BY dateThe forward calendar carries 12 dated closures. The next one is Thanksgiving on 26 November, 61 days out from today. Most of the rows are days New York sits dark while Xetra holds a perfectly normal session: Thanksgiving, Independence Day, Memorial Day, Labor Day and the two February and January Monday holidays all belong to the US calendar alone. The traffic runs the other way too. German public holidays such as Tag der Deutschen Einheit on 3 October, Labour Day on 1 May and Whit Monday close Xetra while US markets trade the full session, which means a German investor can have a working US market and no local order book on the same afternoon. Only a few dates close both, among them New Year's Day, Good Friday and Christmas Day.
The day_status column marks the third case. On a half day the US regular session ends at 13:00 ET instead of 16:00 ET, which is 19:January in Germany rather than 22:January. Those fall around Thanksgiving and Christmas Eve, and the closing auction still prints, three hours earlier than usual on a German clock. The recurring pattern behind them is covered in US stock market holidays and early closes.
Why the first and last half hour trade differently
The practical question behind all of this is when in a German evening the US market is actually liquid. The panel splits the recent SPY tape into 30-minute buckets by New York clock time and labels each one with the German equivalent at the offset in force at build time (six hours, as of late September 2026). SPY is the S&P 500 exchange-traded fund and the most heavily traded US listing, which makes its profile a clean read on the shape of the day.
| et_time | german_time | avg_volume_millions | avg_range_pct |
|---|---|---|---|
| 09:00 | 15:00 | 0.23 | 0.019 |
| 09:30 | 15:30 | 4.06 | 0.055 |
| 10:00 | 16:00 | 2.94 | 0.044 |
| 10:30 | 16:30 | 2.51 | 0.039 |
| 11:00 | 17:00 | 2.66 | 0.035 |
| 11:30 | 17:30 | 2.03 | 0.032 |
| 12:00 | 18:00 | 1.84 | 0.028 |
| 12:30 | 18:30 | 1.61 | 0.026 |
| 13:00 | 19:00 | 1.65 | 0.025 |
| 13:30 | 19:30 | 1.46 | 0.022 |
| 14:00 | 20:00 | 1.88 | 0.025 |
| 14:30 | 20:30 | 2.28 | 0.025 |
| 15:00 | 21:00 | 2.64 | 0.026 |
| 15:30 | 21:30 | 7.7 | 0.032 |
| 16:00 | 22:00 | 5.06 | 0.021 |
| 16:30 | 22:30 | 0.29 | 0.01 |
The exact SQL behind every number
WITH
(
SELECT toHour(toTimeZone(toDateTime(concat(toString(max(date)), ' 12:00:00'), 'America/New_York'), 'Europe/Berlin')) - 12
FROM global_markets.stocks_daily_aggs
WHERE ticker = 'SPY'
) AS german_offset_hours
SELECT
formatDateTime(toDateTime(bucket_min * 60, 'UTC'), '%H:%i') AS et_time,
formatDateTime(toDateTime(((bucket_min + german_offset_hours * 60) % 1440) * 60, 'UTC'), '%H:%i') AS german_time,
round(sum(volume) / countDistinct(session_date) / 1e6, 2) AS avg_volume_millions,
round(avg(bar_range_pct), 3) AS avg_range_pct
FROM
(
SELECT
toDate(et) AS session_date,
intDiv(toHour(et) * 60 + toMinute(et), 30) * 30 AS bucket_min,
volume,
toFloat64(high - low) / toFloat64(close) * 100 AS bar_range_pct
FROM
(
SELECT
toTimeZone(window_start, 'America/New_York') AS et,
volume,
high,
low,
close
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= today() - 60
AND window_start < today() - 1
)
WHERE (toHour(et) * 60 + toMinute(et)) >= 540
AND (toHour(et) * 60 + toMinute(et)) < 1020
)
GROUP BY bucket_min
ORDER BY bucket_minRead the panel bucket by bucket rather than as one headline. The opening half hour, 15:30 German time, averaged 4.06 million shares per session across this sample of the tape. The 12:00 ET bucket averaged 1.84 million, and the half hour before the close averaged 7.7 million. The 16:00 bucket carries the closing auction print, a single large trade struck at the official closing price, which makes it a different kind of bar from the continuous ones around it. One bucket later, at 22:30 in Germany, the regular session has ended and the figure is 0.29 million.
The panel measures one more thing: how far price travels inside a single minute. The average high-to-low range of a one-minute bar came out at 0.055% in the opening half hour and 0.028% in the 12:00 ET bucket. For a German reader the point of the clock is structural. The opening half hour works through orders that piled up overnight and the imbalance left by the opening auction. The final half hour runs into the closing auction that sets the official price, just before 22:January in Germany. Once 22:30 has passed, the regular session is finished and quoting moves to the thin after-hours book, where a market order can fill a long way from the last price it showed.
FAQ
What time does the US stock market open in German time?
The New York open, 09:30 ET, lands at 15:January on a German clock for most of the year, with the close at 22:January. During the two stretches when the EU and US summer-time dates do not line up, the open moves to 14:March and the close to 21:March.
Do US market hours change when Germany switches to summer time?
On a German clock they stay put, since both regions shift. The label moves from CET to CEST and the UTC offset from plus 1 to plus 2, while the US open stays at 15:July German time. Only the mismatch weeks in March and late October break that.
Is Xetra open when the NYSE opens?
Yes, for about two hours. Xetra's continuous trading runs to 17:30 German time and the US regular session starts at 15:January, so the two books overlap from the US open until the German close.
What time do US half days end in German time?
On an early-close day the US regular session ends at 13:00 ET, which is 19:January in Germany. The calendar panel above flags each one in its day_status column.
Can I trade US stocks late in the German evening?
US after-hours trading runs to 20:00 ET, or 02:January the following morning in Germany, and access depends on what your broker supports. Volume in that window is a small fraction of the regular session.
Every panel on this page ships with the exact SQL beneath it. To convert a specific session, half day or holiday into your own local time, ask the question in plain English on the Strasmore terminal.