Convert Unix Timestamp in R

In base R, Unix timestamps are typically handled with POSIXct and POSIXlt. R stores times as seconds since the epoch and prints them using your system timezone unless you specify tz.

Unix Timestamp to POSIXct / POSIXlt

R
# Seconds since epoch -> POSIXct (recommended)
ts <- 1704067200
dt_utc <- as.POSIXct(ts, origin = "1970-01-01", tz = "UTC")
print(dt_utc)  # 2024-01-01 00:00:00

# Milliseconds -> divide by 1000
ts_ms <- 1704067200000
dt_utc_ms <- as.POSIXct(ts_ms / 1000, origin = "1970-01-01", tz = "UTC")
print(dt_utc_ms)  # 2024-01-01 00:00:00

# POSIXlt (broken-down time, useful for components)
dt_lt <- as.POSIXlt(ts, origin = "1970-01-01", tz = "UTC")
dt_lt$year + 1900  # 2024
dt_lt$mon + 1      # 1 (January)
dt_lt$mday         # 1

POSIXct / POSIXlt to Unix Timestamp

R
# POSIXct -> seconds since epoch
dt <- as.POSIXct("2024-01-01 00:00:00", tz = "UTC")
as.numeric(dt)        # 1704067200
as.integer(dt)        # 1704067200

# POSIXct -> milliseconds since epoch
as.numeric(dt) * 1000 # 1704067200000

Current Timestamp with Sys.time()

R
# Current time as POSIXct
now <- Sys.time()
now

# Current Unix timestamp (seconds)
as.numeric(now)

# Integer seconds (drops fractional seconds)
as.integer(now)

# Milliseconds since epoch
as.integer(as.numeric(now) * 1000)

Using lubridate

R
# install.packages("lubridate")
library(lubridate)

ts <- 1704067200

# Unix timestamp -> POSIXct
as_datetime(ts, tz = "UTC")   # 2024-01-01 00:00:00 UTC

# Parse strings safely
ymd_hms("2024-01-01 00:00:00", tz = "UTC")

# Current time
now(tzone = "UTC")
today(tzone = "UTC")

Timezone Handling

R
ts <- 1704067200

# Convert a Unix timestamp (an instant) and choose how it prints
dt_utc <- as.POSIXct(ts, origin = "1970-01-01", tz = "UTC")
format(dt_utc, tz = "UTC")              # "2024-01-01 00:00:00"
format(dt_utc, tz = "America/New_York") # "2023-12-31 19:00:00"
format(dt_utc, tz = "Asia/Tokyo")       # "2024-01-01 09:00:00"

# IMPORTANT: tz changes how strings are interpreted
utc_midnight <- as.POSIXct("2024-01-01 00:00:00", tz = "UTC")
ny_midnight  <- as.POSIXct("2024-01-01 00:00:00", tz = "America/New_York")
as.numeric(utc_midnight)  # 1704067200
as.numeric(ny_midnight)   # 1704085200 (5 hours later)

# With lubridate: with_tz keeps the instant, force_tz changes the instant
library(lubridate)
x <- ymd_hms("2024-01-01 00:00:00", tz = "UTC")
with_tz(x, "America/New_York")   # same moment, different timezone display
force_tz(x, "America/New_York")  # re-interpret the clock time as NY time

Data Frame Operations

R
# Base R
df <- data.frame(
  id = 1:3,
  ts = c(1704067200, 1704153600, 1704240000) # daily at midnight UTC
)

df$dt_utc <- as.POSIXct(df$ts, origin = "1970-01-01", tz = "UTC")
df$date <- as.Date(df$dt_utc)

subset(df, dt_utc >= as.POSIXct("2024-01-02 00:00:00", tz = "UTC"))

# dplyr (optional)
# install.packages("dplyr")
library(dplyr)
df %>%
  mutate(
    dt_utc = as.POSIXct(ts, origin = "1970-01-01", tz = "UTC"),
    day = as.Date(dt_utc)
  ) %>%
  arrange(dt_utc)

Common Pitfalls

Seconds vs Milliseconds

Base R expects seconds since epoch. If you pass milliseconds, your dates will be far in the future.

# ❌ Wrong - milliseconds treated as seconds
as.POSIXct(1704067200000, origin = "1970-01-01", tz = "UTC")

# ✅ Correct - divide by 1000 first
as.POSIXct(1704067200000 / 1000, origin = "1970-01-01", tz = "UTC")

Timezone Is Part of the Interpretation

A timestamp is an instant, but parsing a string requires a timezone. Always set tz when converting strings.

# Same string, different instants
as.numeric(as.POSIXct("2024-01-01 00:00:00", tz = "UTC"))             # 1704067200
as.numeric(as.POSIXct("2024-01-01 00:00:00", tz = "America/New_York"))# 1704085200

Printing vs Storage

POSIXct stores seconds since epoch; the printed time depends on your timezone. Use format(x, tz = "UTC") (or set TZ=UTC) for stable output in scripts.

DST Ambiguity

During DST transitions, local clock times can be ambiguous or nonexistent. Prefer storing timestamps in UTC and convert only for display.

Frequently Asked Questions

Does R use seconds or milliseconds for Unix timestamps?

Base R uses seconds since the Unix epoch (1970-01-01 00:00:00 UTC). If your data is in milliseconds, divide by 1000 before converting to POSIXct.

What is the difference between POSIXct and POSIXlt?

POSIXct is a numeric (seconds since epoch) representation and is efficient for storage and vectorized operations. POSIXlt is a list-like “broken-down time” (year, month, day, etc.) that is convenient for extracting components but heavier.

Why does the same timestamp print differently on different machines?

The underlying instant is the same, but printing typically uses the system’s local timezone. Set tz = "UTC" (or your desired timezone) when converting/formatting to get consistent output.

How do I get the current Unix timestamp in R?

Use as.numeric(Sys.time()) for seconds since epoch (as a double). Use as.integer(Sys.time()) for integer seconds. For milliseconds, multiply by 1000.

How do I change the timezone without changing the instant?

Use lubridate::with_tz() (same moment, different display timezone). Use lubridate::force_tz() to reinterpret clock time in a new timezone (changes the underlying instant).