ggplot2 scale_x_datetime() in R: Format POSIXct Axis

The scale_x_datetime() function in ggplot2 customizes the X axis when x is a POSIXct datetime. It is the datetime sister of scale_x_date(), supporting time components (hours, minutes).

⚡ Quick Answer
+ scale_x_datetime(date_breaks = "1 hour", date_labels = "%H:%M")
+ scale_x_datetime(date_labels = "%Y-%m-%d %H:%M")
+ scale_x_datetime(date_breaks = "1 day")
+ scale_x_datetime(timezone = "America/New_York")
+ scale_x_date()                            # for date-only

Need explanation? Read on for examples and pitfalls.

📊 Is scale_x_datetime() the right tool?
STARTx is POSIXctscale_x_datetime()x is Date (no time)scale_x_date()x is hms (time only)scale_x_time()numeric xscale_x_continuous()timezone-awarepass timezone arg

What scale_x_datetime() does in one sentence

scale_x_datetime() formats a POSIXct X axis using strftime codes; supports time components (hours, minutes, seconds) and timezones.

Syntax

scale_x_datetime(date_breaks = NULL, date_labels = NULL, timezone = NULL, ...).

Run live
Run live, no install needed. Every R block on this page runs in your browser. Click Run, edit the code, re-run instantly. No setup.
RHourly breaks for time-of-day plot
library(ggplot2) df <- data.frame( ts = seq(as.POSIXct("2024-01-15 00:00"), as.POSIXct("2024-01-15 23:00"), by = "hour"), value = sin(0:23 * pi / 12) ) ggplot(df, aes(ts, value)) + geom_line() + scale_x_datetime(date_breaks = "4 hours", date_labels = "%H:%M")

  
Tip
For datetime data, use scale_x_datetime; for date-only, scale_x_date. Mixing them produces wrong-looking axes.

Five common patterns

1. Hourly axis

RTime within a day
+ scale_x_datetime(date_breaks = "1 hour", date_labels = "%H:%M")

  

2. Daily axis with date labels

RMulti-day data
+ scale_x_datetime(date_breaks = "1 day", date_labels = "%b %d")

  

3. Combined date + time

RFull timestamp
+ scale_x_datetime(date_labels = "%Y-%m-%d %H:%M")

  

4. Specific timezone

RDisplay in EST
+ scale_x_datetime(timezone = "America/New_York")

  

5. Limits

RZoom to a window
+ scale_x_datetime( limits = as.POSIXct(c("2024-01-01 09:00","2024-01-01 17:00")) )

  
Key Insight
Datetime axes need careful tick placement. Default may produce too many or too few. Set date_breaks explicitly to match your data's resolution (1 hour, 15 min, etc.).

scale_x_datetime() vs scale_x_date() vs scale_x_time

Function x class Best for
scale_x_datetime() POSIXct Date + time
scale_x_date() Date Date only
scale_x_time() hms Time of day, no date
scale_x_continuous() numeric When x is seconds-since-epoch

A practical workflow

For server logs or sensor data, datetime axes need tight breaks.

RInteractive R
logs |> ggplot(aes(timestamp, count)) + geom_line() + scale_x_datetime( date_breaks = "15 min", date_labels = "%H:%M", timezone = "UTC" )

  

15-minute ticks; UTC display.

Common pitfalls

Pitfall 1: timezone confusion. ggplot uses the data's timezone unless overridden. Set timezone explicitly for displays in a specific zone.

Pitfall 2: x is Date not POSIXct. Use scale_x_date instead. POSIXct adds time component which Date lacks.

Warning
scale_x_datetime requires POSIXct x, NOT Date or character. Convert first if needed: mutate(ts = as.POSIXct(ts_str)).

Try it yourself

Try it: Plot a simulated 24-hour signal with hourly tick labels. Save to ex_plot.

RYour turn: hourly axis
df <- data.frame( ts = seq(as.POSIXct("2024-01-15 00:00"), as.POSIXct("2024-01-15 23:00"), by = "hour"), v = rnorm(24) ) ex_plot <- df |> ggplot(aes(ts, v)) + geom_line() + # your code here

  
Click to reveal solution
RSolution
ex_plot <- ggplot(df, aes(ts, v)) + geom_line() + scale_x_datetime(date_breaks = "2 hours", date_labels = "%H:%M")

  

Explanation: 2-hour ticks with HH:MM labels.

After mastering scale_x_datetime, look at:

  • scale_x_date(): Date-only
  • scale_x_time(): hms time-of-day
  • scale_x_continuous(): numeric x
  • scales::date_breaks(): helper

FAQ

What does scale_x_datetime do in ggplot2?

scale_x_datetime() formats a POSIXct X axis with strftime labels and date-interval breaks. Supports timezones.

What is the difference between scale_x_datetime and scale_x_date?

datetime supports time-of-day (POSIXct). date is for date-only (Date class). Pick by your x's class.

How do I display a specific timezone?

Pass timezone = "America/New_York" (or other Olson name).

How do I format the labels?

Use strftime codes: %H:%M for time, %Y-%m-%d %H:%M for full timestamp.

What if my x is numeric (seconds-since-epoch)?

Convert to POSIXct first: mutate(ts = as.POSIXct(seconds, origin = "1970-01-01")).