lubridate seconds() in R: Create Second-Based Time Periods
The seconds() function in lubridate builds a second-based Period object you can add to or subtract from a datetime. It is vectorised, plays nicely with POSIXct arithmetic, and lets you shift timestamps by N seconds without manual unit conversion.
ymd_hms("2024-07-15 09:00:00") + seconds(45) # add 45 seconds
ymd_hms("2024-07-15 09:00:00") - seconds(30) # subtract 30 seconds
now() + seconds(c(15, 30, 60)) # vectorised shifts
minutes(1) + seconds(30) # combine components
class(seconds(10)) # "Period" object
as.numeric(seconds(120), "minutes") # 2 minutes
ymd_hms("2024-07-15 09:00:45") + seconds(20) # cross-minute shift
df %>% mutate(stop = start + seconds(elapsed)) # use in dplyrNeed explanation? Read on for examples and pitfalls.
What seconds() does in one sentence
seconds(n) returns a Period object representing n seconds of clock time. Pass an integer or numeric vector and you get a period whose second slot equals the input, ready to add to a POSIXct or Date value.
This is a constructor, not an accessor. It builds time spans you can do arithmetic with. If you want to read the second component out of an existing datetime, you want second() (singular), covered in lubridate second() in R.
Syntax
seconds(x = 1) takes a numeric vector and returns a Period of the same length. There is one argument and one return type. The output prints as "45S" and behaves like a clock offset in datetime arithmetic.
The period stores the input value in its seconds slot without normalising into minutes. You can mix it with other constructors like minutes() and hours() to build richer spans that print with each component.
Period and Duration agree at the second level. A Period from seconds() adds wall-clock seconds, whereas a Duration from dseconds() adds physical seconds. The two are numerically identical for seconds; the divergence appears at coarser units like days() vs ddays() where DST transitions matter.Six common patterns with seconds()
1. Add seconds to a single datetime
Use + seconds(n) to push a timestamp forward by n seconds. The result is a POSIXct value with the same timezone as the input.
Negative inputs work too: seconds(-10) is equivalent to -seconds(10). The arithmetic is exact because Period carries seconds as a separate slot rather than rolling them through other units.
2. Vectorised second arithmetic
Both sides of the + operator can be vectors. This makes batch event-offset calculation a single expression.
The result is the same length as the longer of the two operands. R recycles the shorter vector when their lengths are compatible.
3. Cross-minute and cross-hour shifts
seconds() rolls into the next minute (and hour) cleanly. You do not need a separate minutes() call for shifts of 60 or more seconds.
Large second counts (seconds(3600)) work as well, though hours(1) reads more clearly for human consumers. Pick the unit that matches how the input is stored.
4. Build multi-component periods
Add period constructors together to express compound spans. minutes(1) + seconds(30) reads like English and prints as "1M 30S".
This composes well when the offset comes from a stopwatch reading or log entry that supplies hours, minutes, and seconds separately.
5. Use seconds() inside dplyr pipelines
seconds() plugs into mutate() for row-wise event arithmetic. Pair it with a numeric duration column to compute end timestamps.
Integer inputs are preferred. Lubridate accepts fractional seconds, but the printed form depends on options(digits.secs), so for sub-second precision either set that option or scale to milliseconds and divide back later.
6. Convert seconds() to other units
Coerce a period to minutes or hours with as.numeric(period, "minutes") when downstream code expects a different unit. This is the standard bridge between lubridate periods and numeric offsets.
period_to_seconds() is the explicit helper if you prefer named verbs over as.numeric() coercion. Since the unit is already seconds, the result is the input value unchanged.
seconds() vs dseconds(): period vs duration
seconds() returns a Period; dseconds() returns a Duration. At the second level the two are numerically identical, but their classes differ and that propagates through the rest of your pipeline.
The values match because seconds are atomic; DST flips happen on hour boundaries, never inside a second. The class divergence matters when you mix them with larger units (minutes(), hours(), days()) where wall-clock and elapsed semantics start to diverge.
| Aspect | seconds() (Period) |
dseconds() (Duration) |
|---|---|---|
| Unit | Clock seconds | Physical seconds |
| Result at second level | Identical to dseconds | Identical to seconds |
| Print form | "45S" |
"45s (~45 seconds)" |
| Use when | Composing with periods (minutes(), hours()) |
Composing with durations or stopwatch math |
| Class | Period |
Duration |
seconds() to compose with other period constructors, dseconds() to compose with durations. The choice is about which family the rest of your expression lives in. Mixing families forces a coercion that tends to surprise readers; pick one and stay consistent.seconds() vs second(): constructor vs accessor
seconds() builds a span; second() reads a component. They are independent functions and a frequent source of confusion for newcomers.
If you write ts + second(15) (singular) you almost certainly meant ts + seconds(15). The singular form returns a numeric, not a period, and the addition silently treats it as raw seconds anyway, so the bug hides until you swap in a different unit's accessor.
Common pitfalls
Three patterns trip up most users. Knowing them up front saves debugging later.
start + 30 and start + seconds(30) both advance by 30 seconds, but the bare integer skips the Period machinery. The instant you swap units (e.g., to minutes), the bare-integer code keeps adding seconds while the wrapped form fails loudly so you notice.The second pitfall is mixing periods and durations in one expression. seconds(1) + dseconds(1) works at the second level but coerces silently. Stick to one family per calculation to avoid surprises when you scale up to larger units.
The third pitfall is fractional seconds. seconds(2.5) is accepted, but the printed form depends on options(digits.secs). Set the option or work in integer milliseconds if sub-second precision matters for your output.
Try it yourself
Try it: Build a tibble of three race start times. Add an elapsed_sec column with values 12, 45, and 90. Compute the finish timestamp for each runner using seconds(). Save the tibble to ex_finishes.
Click to reveal solution
Explanation: mutate() evaluates start + seconds(elapsed_sec) row-wise, building a Period from each numeric elapsed time and adding it to the matching start timestamp.
Related lubridate functions
A handful of neighbouring constructors round out second arithmetic. Reach for these when seconds() is not the right shape.
- minutes() and hours(): larger period constructors. Combine with
seconds()for compound spans. days(),weeks(),months(),years(): calendar-grain period constructors. Same arithmetic model.dseconds(),dminutes(),dhours(): duration siblings. Use when the rest of the expression is a duration.- second(): the accessor that reads the second-of-minute from a datetime.
hms(),hm(), andms(): parse"HH:MM:SS","HH:MM", and"MM:SS"strings intoPeriodobjects.period(): generic period constructor with named arguments, e.g.,period(45, "seconds").
For the full lubridate reference, see the official lubridate docs.
FAQ
What does seconds() do in R?
seconds(n) from the lubridate package returns a Period object representing n seconds of clock time. You add or subtract it from a POSIXct or Date value to shift the timestamp by that many seconds. The result is a normal POSIXct you can pass to plotting, formatting, or further arithmetic without unwrapping anything.
What is the difference between second() and seconds() in lubridate?
second() is an accessor: it reads the second-of-minute (0 to 59) out of a datetime and returns a numeric. seconds() is a constructor: it builds a Period you can add to a datetime to shift it forward or backward. The plural form is for arithmetic; the singular form is for inspection.
How do I add seconds to a datetime in R?
Use your_datetime + seconds(n). For example, ymd_hms("2024-07-15 09:00:00") + seconds(45) returns "2024-07-15 09:00:45 UTC". The + operator is overloaded to recognise lubridate Period objects, so the arithmetic reads like plain math without manual unit conversion.
What is the difference between seconds() and dseconds()?
seconds() returns a Period and dseconds() returns a Duration. At the second level the two add the same number of physical seconds, so the resulting timestamps are identical. The classes differ, which matters when you compose with larger units like minutes() vs dminutes() where wall-clock and elapsed semantics diverge.
Can I use fractional seconds with seconds()?
Fractional inputs like seconds(2.5) are accepted, but the printed form depends on options(digits.secs). For reliable sub-second precision, set options(digits.secs = 3) or scale to integer milliseconds and divide back later. For most use cases integer seconds are simpler and avoid floating-point surprises.