trunc / add_months
Truncate dates to the month or year and shift them by whole months, including month-end rules.
On this page
Show code in
Every code block on the page follows this.
You will learn
- How to truncate dates to the month, quarter or year
- The difference between trunc and date_trunc
- How add_months handles month ends
- How to build month-over-month comparisons
F.trunc(date, "month") returns the first day of the month as a date; F.date_trunc("month", ts) does the same for timestamps. F.add_months(date, n) shifts by whole months, clamping to the last valid day.What they do
| Function | Input | Example result |
|---|---|---|
trunc(d, "month") | date | 2025-03-31 → 2025-03-01 |
trunc(d, "quarter") | date | 2025-05-20 → 2025-04-01 |
trunc(d, "year") | date | 2025-05-20 → 2025-01-01 |
date_trunc("hour", ts) | timestamp | 10:47:12 → 10:00:00 |
add_months(d, 1) | date | 2025-01-31 → 2025-02-28 |
last_day(d) | date | 2025-02-10 → 2025-02-28 |
Note the argument order: trunc(column, unit) but date_trunc(unit, column). trunc returns a date and only knows date units; date_trunc returns a timestamp and also handles hours, minutes and weeks.
How add_months handles month ends
Adding a month to January 31 cannot give February 31. Spark returns the last day of the target month instead: February 28 (or 29 in a leap year). Going back is not symmetric: add_months("2025-02-28", 1) is March 28, not March 31.
Step by step: monthly revenue
Input
| order_date | amount |
|---|---|
| 2025-01-15 | 100 |
| 2025-01-31 | 40 |
| 2025-02-10 | 70 |
| 2025-03-30 | 90 |
| 2025-03-31 | 60 |
Output
| month | revenue |
|---|---|
| 2025-01-01 | 140 |
| 2025-02-01 | 70 |
| 2025-03-01 | 150 |
Truncating to the month gives every order in a month the same key, ready for groupBy.
Run the example
from pyspark.sql import functions as F result = (orders .withColumn("month", F.trunc("order_date", "month")) .groupBy("month").agg(F.sum("amount").alias("revenue")) .withColumn("next_month", F.add_months("month", 1)) .orderBy("month"))
SELECT trunc(order_date, 'month') AS month, SUM(amount) AS revenue, add_months(trunc(order_date, 'month'), 1) AS next_month FROM orders GROUP BY trunc(order_date, 'month') ORDER BY month
Switch to PySpark to edit and run this example in your browser.
Month over month
Two common ways to compare each month with the previous one: lag(revenue) over months ordered in time (fast, but skips months with no data), or join the monthly table to itself on a.month = add_months(b.month, 1) (correct even when months are missing, and clearer about which month is compared). For guaranteed rows for every month, generate a calendar with sequence(start, end, interval 1 month) and left join to it.
Parsing first
These functions need real dates. A string in ISO format (yyyy-MM-dd) is cast automatically; anything else must be parsed with F.to_date(col, "dd/MM/yyyy"). In Spark 3, an unparseable string returns null; with ANSI mode in Spark 4.0, to_date raises an error unless you use try_to_date.
Common mistakes
Swapping the arguments of trunc and date_trunc
trunc(col, unit) vs date_trunc(unit, col). Swapped arguments return null or fail.Grouping by month(date) alone
month() returns 1 to 12, so January 2024 and January 2025 merge. Truncate instead.Using lag for month over month with gaps
Key takeaways
- trunc(date, unit) gives the start of the month, quarter or year.
- date_trunc(unit, ts) does the same for timestamps, with finer units.
- add_months clamps to the last valid day; it is not symmetric.
- Group by the truncated date, not month(), to keep years apart.
Check yourself
3 questions1. What is add_months('2025-01-31', 1)?
Show the answer
2025-02-28. February 31 does not exist, so Spark clamps to the last day of February.
2. Why is grouping by F.month("order_date") risky?
Show the answer
It returns 1 to 12, merging the same month of different years. month() drops the year. Truncate to the month to keep a full date key.
3. Which call is correct for a timestamp column ts?
Show the answer
F.date_trunc("hour", "ts"). date_trunc takes the unit first and supports hours. trunc takes the column first and only date units.
Practice it
Interview problems that use trunc / add_months: write the PySpark, run it, and get graded on hidden tests.
Go deeper
Primary sources: functions.trunc · functions.add_months · Datetime patterns