Spark Scala last_day and next_day
last_day returns the last day of the month that a given date falls in, and next_day returns the first date after a given date that lands on a particular weekday. Both are handy for building reporting periods, billing cycles, and scheduling logic.
last_day
def last_day(e: Column): Column
Takes a date (or anything castable to a date — including a string like "2026-01-15") and returns the last day of that month. The function takes care of leap years and short months automatically.
val df = Seq(
"2026-01-15",
"2026-02-10",
"2026-02-28",
"2024-02-15",
"2026-12-01",
).toDF("event_date")
val df2 = df
.withColumn("month_end", last_day(col("event_date")))
df2.show(false)
// +----------+----------+
// |event_date|month_end |
// +----------+----------+
// |2026-01-15|2026-01-31|
// |2026-02-10|2026-02-28|
// |2026-02-28|2026-02-28|
// |2024-02-15|2024-02-29|
// |2026-12-01|2026-12-31|
// +----------+----------+
Notice that 2024-02-15 returns 2024-02-29 — last_day correctly handles leap years. Passing a date that already sits on the last day of the month (2026-02-28) just returns the same date.
next_day
def next_day(date: Column, dayOfWeek: String): Column
def next_day(date: Column, dayOfWeek: Column): Column
next_day returns the first date strictly after the input date that falls on the specified weekday. The day name can be passed as a string literal or as a column, and Spark accepts a few different forms — full names ("Monday"), abbreviations ("Mon"), and even shorter forms ("Mo") all work.
val df = Seq(
"2026-03-10",
"2026-03-11",
"2026-03-12",
"2026-03-13",
"2026-03-14",
).toDF("event_date")
val df2 = df
.withColumn("next_friday", next_day(col("event_date"), "Friday"))
df2.show(false)
// +----------+-----------+
// |event_date|next_friday|
// +----------+-----------+
// |2026-03-10|2026-03-13 |
// |2026-03-11|2026-03-13 |
// |2026-03-12|2026-03-13 |
// |2026-03-13|2026-03-20 |
// |2026-03-14|2026-03-20 |
// +----------+-----------+
2026-03-13 is itself a Friday, but next_day returns the following Friday (2026-03-20) — the result is always strictly after the input date, never equal to it.
Passing the Day Name as a Column
If different rows need different target weekdays, pass a column instead of a string literal:
val df = Seq(
("2026-05-04", "Mon"),
("2026-05-04", "Wed"),
("2026-05-04", "Sun"),
("2026-05-04", "Tuesday"),
("2026-05-04", "FR"),
).toDF("event_date", "target_day")
val df2 = df
.withColumn("next_target", next_day(col("event_date"), col("target_day")))
df2.show(false)
// +----------+----------+-----------+
// |event_date|target_day|next_target|
// +----------+----------+-----------+
// |2026-05-04|Mon |2026-05-11 |
// |2026-05-04|Wed |2026-05-06 |
// |2026-05-04|Sun |2026-05-10 |
// |2026-05-04|Tuesday |2026-05-05 |
// |2026-05-04|FR |2026-05-08 |
// +----------+----------+-----------+
2026-05-04 is a Monday — and asking for the next Monday returns 2026-05-11, again confirming the "strictly after" behavior. All the day-name spellings (Mon, Wed, Sun, Tuesday, FR) resolve correctly.
Handling Nulls and Invalid Dates
Both functions return null when the input is null or cannot be parsed as a valid date. This includes string values that aren't dates at all, and date strings with out-of-range months or days:
val df = Seq(
"2026-04-15",
null,
"not-a-date",
"2026-13-40",
).toDF("event_date")
val df2 = df
.withColumn("month_end", last_day(col("event_date")))
.withColumn("next_monday", next_day(col("event_date"), "Mon"))
df2.show(false)
// +----------+----------+-----------+
// |event_date|month_end |next_monday|
// +----------+----------+-----------+
// |2026-04-15|2026-04-30|2026-04-20 |
// |null |null |null |
// |not-a-date|null |null |
// |2026-13-40|null |null |
// +----------+----------+-----------+
Garbage input silently becomes null rather than raising an error, so it's worth running a sanity check (e.g., counting nulls) on the output if you're not sure your source data is clean. If you need stricter parsing, see to_date and to_timestamp for explicit format handling.
Related Functions
For shifting a date forward or backward by a fixed number of days or months, see date_add and date_sub. For truncating a date to the start of its month or quarter (the natural counterpart to last_day), see date_trunc. For extracting individual fields like year, month, or day-of-week from a date, see year, month, and dayofmonth.