lubridate week() in R: Week-of-Year From Dates
The week() function in lubridate returns the week-of-year number (1 to 53) for a Date or POSIXct value, computed as the number of complete seven-day periods since January 1 plus one. It is the calendar-week counterpart to yday(), designed for weekly aggregation, week-over-week comparisons, and time-bucketed plotting.
week(ymd("2024-02-14")) # simple week of year
week(ymd(c("2024-01-01", "2024-12-31"))) # vectorised, returns 1 and 53
isoweek(ymd("2024-12-30")) # ISO 8601 week (1 of 2025)
epiweek(ymd("2024-02-14")) # CDC epi week (Sunday-start)
df |> mutate(wk = week(date)) # weekly bucket column
df |> group_by(year(date), week(date)) |> summarise(...) # weekly rollup
floor_date(x, "week") # snap to week start
format(x, "%V") # ISO week as characterNeed explanation? Read on for examples and pitfalls.
What week() does in one sentence
week() returns the week-of-year as an integer between 1 and 53. The formula is (yday(x) - 1) %/% 7 + 1. January 1 to January 7 are week 1, January 8 to January 14 are week 2, and so on through December. Pass any Date, POSIXct, or POSIXlt vector and you get a vector of the same length.
This is lubridate's "simple" week function. It does not implement the ISO 8601 rule, does not honour week_start, and does not align to Sunday. For those behaviours use isoweek() or epiweek() instead.
Syntax
week(x) takes one argument: a date or date-time vector. No label, no week_start, no abbr. The output is always integer. The function is fully vectorised and returns NA for NA input.
February 14, 2024 lands in week 7 because it is the 45th day of the year, and (45 - 1) %/% 7 + 1 equals 7. The last day of the year sits in week 53.
week(x) returns 1 through 53 with no year context, so week 7 of 2023 and week 7 of 2024 collide. Group by both: group_by(yr = year(date), wk = week(date)) produces a unique row per calendar week without sticky-tape work.Five common patterns
1. Get the week number
The output is plain integer, ready for %in%, comparison, and arithmetic. New Year's Day is week 1, Independence Day is week 27, Christmas is week 52. No locale, no Sunday-versus-Monday switch.
2. Group and summarise by week
Each row collapses seven consecutive days into one bucket. Including year() in the grouping prevents week 1 of two different years from being merged. The n column confirms each week holds seven days except the last partial one.
3. Compare week() with isoweek() and epiweek()
The three functions diverge at year boundaries. week() resets on January 1 of each calendar year. isoweek() follows ISO 8601: the week containing the year's first Thursday is week 1, so December 30, 2024 is already ISO week 1 of 2025. epiweek() is the US CDC convention used in epidemiology: Sunday-start, with week 1 containing January 4, so December 31, 2024 falls in epi week 1.
4. Build a year-week label
format(x, "%Y-W%V") builds the ISO 8601 year-week label using the strftime engine, which is correct for ISO compliance. The lubridate-only route paste0(year(d), "-W", sprintf("%02d", week(d))) builds a calendar-year label, which matches week() semantics but disagrees with ISO at year boundaries. Pick the route that matches the function you used.
5. Snap dates to the start of the week
week() returns an integer; floor_date(x, "week") returns the Date at the start of the week. Use the integer for grouping when sorting matters; use the Date for joining against a weekly calendar table or for plotting on a time axis. Together they cover both shapes of weekly logic.
week() when you want the simplest weekly bucket inside one calendar year, isoweek() when reports must follow ISO 8601, and epiweek() when the audience is public-health analysts. Mixing them silently shifts a week boundary and changes year-end totals; always confirm which one your downstream consumers expect.week() vs the base R alternatives
week() competes with three base R routes for week extraction. Each returns the same idea (which week is this date in?) with a different convention.
| Style | Example | Returns | Reads best when |
|---|---|---|---|
week(x) |
mutate(wk = week(date)) |
Integer 1 to 53 | Simple calendar weeks, fast aggregation |
isoweek(x) |
isoweek(date) |
Integer 1 to 53 | ISO 8601 reports, Monday-start weeks |
format(x, "%V") |
format(date, "%V") |
Character "01" to "53" | ISO weeks without lubridate, sorted strings |
format(x, "%U") |
format(date, "%U") |
Character "00" to "53" | Sunday-start weeks with leading zero |
Confirm the relationship:
isoweek() and format(d, "%V") always agree. week() differs at the year boundary because it resets on January 1 instead of following the Thursday rule. The base route is dependency-free; lubridate returns integer directly.
Common pitfalls
Pitfall 1: collapsing week 53 with next year's week 1. week() resets every January 1, so group_by(week(date)) merges late-December and early-January rows from two different years. Always group by year(date) alongside week(date).
Pitfall 2: assuming ISO compliance. week() is not ISO 8601. December 30, 2024 returns 53 but isoweek() returns 1 (of 2025). External reports citing "week 1" almost always mean ISO weeks; pick isoweek() unless confirmed otherwise.
Pitfall 3: passing a character. week("2024-02-14") errors. Wrap with ymd() or as.Date() first.
Pitfall 4: expecting week 53 only in leap years. Any year whose January 1 lands late in the week can produce a week 53, leap year or not.
week() has no week_start argument. Unlike wday() and floor_date(), week() does not honour a Sunday-versus-Monday switch and ignores getOption("lubridate.week.start"). If your team needs Monday-aligned weeks, use isoweek() (Monday-start by design) or floor_date(x, "week", week_start = 1) for the snap-to-week-start version. Code that mixes week() with wday(x, week_start = 1) will see weeks and weekdays disagree on where Sunday belongs.week(x) is s.dt.isocalendar().week from pandas 1.1 plus (ISO week, matching isoweek()). For the lubridate week() semantics (count of seven-day periods since January 1), use (s.dt.dayofyear - 1) // 7 + 1. Older pandas s.dt.week is deprecated and behaves like the ISO version.A practical workflow with week()
Week-of-year appears in three places: weekly rollups, week-over-week growth, and seasonality plots.
- Weekly rollups.
group_by(yr = year(date), wk = week(date))returns one row per calendar week without cross-year collisions. - Week-over-week growth. Compute the weekly average, then
lag()it:mutate(wow = (avg / lag(avg) - 1) * 100). - Seasonality plots. Plot
week()on the x-axis withfacet_wrap(~ year)to compare 52 weeks across multiple years.
Five lines turn a daily series into a weekly trend table. The wow_pct column carries the percent change versus the prior week; lag() naturally returns NA for week 1 because there is no prior. Plot this directly or feed it into an alerting rule.
Try it yourself
Try it: Build a tibble with 21 daily sales rows starting on 2024-06-01, then compute the weekly sum using week(). Save the result to ex_weekly_sum.
Click to reveal solution
Explanation: group_by(year(date), week(date)) produces one row per calendar week. June 1, 2024 lands in week 22, so the first bucket only holds two days; the next two buckets are full seven-day weeks.
Related lubridate functions
After mastering week(), look at:
isoweek(),epiweek(): ISO 8601 and CDC week conventionsyear(),month(),quarter(): other calendar-part extractorsyday(),mday(),wday(): day-prefix familyfloor_date(),ceiling_date(): snap to week start or endmake_date(),weeks(): rebuild dates and shift by weekly periodswith_tz(): when the week boundary changes because of time zone
See the lubridate week documentation for the official reference.
FAQ
What does week() return in R?
week() returns the week-of-year as an integer between 1 and 53. The formula is (yday(x) - 1) %/% 7 + 1, so January 1 to January 7 are week 1, January 8 to January 14 are week 2, and the count continues through December. The function is fully vectorised and returns NA for NA input, matching the rest of the lubridate extractor family.
What is the difference between week() and isoweek() in R?
week() resets every January 1 and counts seven-day periods from there. isoweek() follows ISO 8601: the week containing the year's first Thursday is week 1, which means late December dates can already be week 1 of the next year. For example, December 30, 2024 returns week(d) = 53 but isoweek(d) = 1. Pick isoweek() for ISO-compliant reporting; pick week() for simple calendar buckets.
How do I get the week number from a date in R?
The lubridate route is week(date), which returns integer 1 through 53. The base R route is as.integer(format(date, "%V")) for ISO weeks or as.integer(format(date, "%U")) for Sunday-start weeks. The lubridate version returns integer directly, while the base version returns character and needs casting. Both are vectorised.
Why does week() return 53 for December 30 but isoweek() returns 1?
week() resets on January 1 and counts forward, so any date close to the year end lands in week 53 of that calendar year. isoweek() shifts the week 1 boundary to the week containing the year's first Thursday, which often means the last week of December belongs to the next ISO year. Both conventions are valid; they answer different questions.