lubridate time_length() in R: Span Length in Any Unit
The time_length() function in lubridate converts a time span (Interval, Period, Duration, or difftime) into a numeric in the unit you request: seconds, minutes, hours, days, weeks, months, or years. For an Interval it walks the real calendar; for a Period or Duration it divides by the unit's constant.
time_length(span, "second") # default unit time_length(span, "day") # days in the span time_length(span, "week") # weeks as a double time_length(gap, "month") # calendar-aware months time_length(gap, "year") # exact years between dates time_length(birthday %--% today(), "year") # age in years time_length(as.duration(span), "hour") # hours from a Duration floor(time_length(gap, "year")) # integer age
Need explanation? Read on for examples and pitfalls.
What time_length() does in one sentence
time_length() returns the length of a time span as a numeric in the unit you name. You pass any span object plus a unit string ("second" through "year") and lubridate returns a double: the count of that unit fitting inside the span. It is the bridge between time-span objects and ordinary numbers you can sort, average, or pass to ggplot.
The behaviour splits at the input class. For an Interval, lubridate walks the real calendar between start and end. For a Period or Duration, lubridate divides by a fixed seconds-per-unit constant, since neither class is anchored to real dates.
Syntax
time_length(x, unit = "second") accepts any time span and a unit string. The first argument is an Interval, Period, Duration, difftime, or a numeric of seconds. The unit argument names the result unit and accepts: "second", "minute", "hour", "day", "week", "month", "year".
The default unit is "second", matching the underlying storage of every span class. Returning a double (not an integer) is deliberate: months and years rarely divide evenly, and rounding belongs in the caller.
time_length() whenever you need a span as a plain number. int_length() only returns seconds and only accepts Intervals. as.numeric(span, "unit") works but is less explicit. time_length() accepts every span class and every common unit, so it always reads correctly in code review.Six common patterns
1. Days between two dates
Building the Interval with %--% and asking for "day" returns the exact day count between the timestamps. Day is a fixed 86,400 seconds, so the answer is an integer-valued double when no daylight-saving transition falls inside the span.
2. Calendar-aware months and years
For an Interval, time_length() walks the real calendar: 2023-01-31 to 2024-02-29 is exactly 13 months even though February 2024 has 29 days. The "year" answer factors in the leap day. A fixed-average calculation will not match.
3. Compute age in whole years
Age is the canonical use case. Build an Interval from birth date to today, ask for "year", and floor the result. Leap years are counted correctly, so a person born on 2000-02-29 increments their age on March 1 in non-leap years.
4. Convert a Duration to hours or days
For a Duration, time_length() divides the stored seconds by the unit's fixed constant (3,600 for hour, 86,400 for day, 2,629,800 for month). The result is a clean numeric without the "X seconds (~Y weeks)" string Duration prints by default.
5. Convert a difftime without unit gymnastics
Subtracting two POSIXct values returns a difftime whose unit attribute depends on the magnitude (R picks the largest sensible unit). time_length() ignores that attribute and returns the count in whatever unit you ask for, so downstream code never has to check units(diff).
time_length() is calendar-aware only when the input is an Interval. Periods and Durations carry no anchor date, so time_length(months(3), "day") returns 91.31 (3 times the average month). time_length(start %--% (start + months(3)), "day") returns the actual day count for those three specific months. Pick the right input class first; the function then does the math correctly.6. Vectorise across a column of intervals
time_length() is vectorised end to end. Build a column of Intervals with %--%, call time_length() per unit, and you get clean numeric columns ready for ggplot or arithmetic without a loop.
time_length() vs int_length() vs as.numeric()
The three functions overlap but have different scope. Pick by what kinds of inputs you need to accept and what units you need to return.
| Function | Accepts | Units | Returns |
|---|---|---|---|
time_length() |
Interval, Period, Duration, difftime | any of 7 named units | numeric in named unit |
int_length() |
Interval only | seconds only | numeric in seconds |
as.numeric(x, "unit") |
Period, Duration, difftime | any of 7 named units | numeric in named unit |
int_length() is the fastest path for seconds from an Interval. as.numeric() covers non-Interval classes but not Intervals. time_length() accepts every span class, which is why it is the default recommendation in newer lubridate docs.
as.numeric(span, "unit"); both still work. lubridate added time_length() to give one consistent function across all span classes. Existing code with as.numeric() does not need migration, but new code reads more clearly with time_length() since the function name says what it returns.Common pitfalls
"month" on a Period or Duration uses an average month length. time_length(months(3), "day") returns 91.31 because lubridate divides by an average-day-per-month constant. For the real day count in three specific months, anchor to a start date: time_length(start %--% (start + months(3)), "day") returns the actual count.
The unit must be singular, not plural. time_length(gap, "days") errors with Invalid units. lubridate accepts "second", "minute", "hour", "day", "week", "month", "year" only. Stick to the singular form, lowercase, in quotes.
Negative spans return negative numbers silently. Reverse the Interval bounds (later %--% earlier) and time_length() returns a negative double rather than its absolute value. Use abs(time_length(...)) or wrap the input with int_standardize() when direction does not matter.
Try it yourself
Try it: Build an Interval from ymd("2020-03-15") to ymd("2026-05-15") and store it in ex_span. Then compute the length in whole years and store the integer result in ex_years.
Click to reveal solution
Explanation: %--% builds the Interval. time_length(ex_span, "year") returns 6.166... and floor() strips the fractional part to give the whole-year count, the same pattern used for age calculations.
Related lubridate functions
interval()and the%--%infix build the Interval objects thattime_length()measures (see lubridate interval() in R).period()builds calendar-unit spans without anchors (see lubridate period() in R).duration()builds exact-second spans (see lubridate duration() in R).int_length()returns the length of an Interval in seconds only, slightly faster thantime_length(..., "second").as.duration(),as.period(),as.numeric()convert between span representations and plain numbers.
For the full lubridate reference, see the tidyverse time_length() documentation.
FAQ
What does time_length() return in lubridate?
time_length() returns a numeric (double) representing the count of a named unit inside the span. The unit defaults to "second" and accepts "minute", "hour", "day", "week", "month", or "year". The result is fractional when the unit does not divide evenly, so months and years often come back as 3.16, 1.083, and similar values. Wrap the call in floor(), round(), or as.integer() when you need a whole number.
How do I calculate age in years using lubridate?
Build an Interval from the date of birth to today, then call time_length() with "year" and floor the result: floor(time_length(dob %--% today(), "year")). The Interval input makes the calculation calendar-aware, so leap years count correctly and a person born on February 29 in a leap year increments their age on March 1 in non-leap years. The pattern works on vectors, so the same line handles a column of birth dates.
What is the difference between time_length() and int_length()?
int_length() only accepts an Interval and only returns seconds. time_length() accepts an Interval, Period, Duration, or difftime and returns the result in any of 7 named units. They agree on time_length(gap, "second") equals int_length(gap), but time_length() covers every span class lubridate exposes. New code should prefer time_length() for the wider input surface; int_length() remains useful when you specifically have an Interval and only need seconds.
Can time_length() return negative values?
Yes. If you build the span with the later timestamp first (later %--% earlier), time_length() returns a negative number. The sign tells you the direction of the span, which is useful when comparing a column of events to a reference date and you want to know which fell before. Wrap the result with abs() when the direction is irrelevant, or call int_standardize() on the Interval to flip negative spans back to positive before measuring.
Why does time_length() give different answers for an Interval and a Period of the same nominal length?
An Interval stores a real start and end, so time_length(interval(ymd("2024-01-31"), ymd("2024-02-29")), "day") returns 29. A Period stores months(1) as an abstract unit and converts to days using an average (about 30.44), so time_length(months(1), "day") returns 30.44. The Interval answer uses actual calendar days; the Period answer uses a constant. Choose by whether your downstream calculation needs calendar truth or a single typical value.