Question

Find the Last Day of a Month in PHP

phpdate

Question

How can I find the last calendar day of the month for a date in PHP?

For example:

$a_date = "2009-11-23";

The result should be 2009-11-30.

And for:

$a_date = "2009-12-23";

The result should be 2009-12-31.

Short Answer

You will learn how to parse a date and move it to the last day of its month in PHP. You will also see why DateTimeImmutable is a reliable choice, how leap years are handled automatically, and how to format the result.

Concept

PHP's date and time classes understand the calendar, including months with 28, 29, 30, or 31 days. Instead of manually listing the number of days in each month, create a date object and ask PHP to modify it to the last day of this month.

DateTimeImmutable is especially useful because operations return a new object instead of changing the original date. This makes code easier to reason about and reduces accidental changes to shared values.

The expression last day of this month is a relative date-time instruction recognized by PHP's date parser. It works correctly for:

  • February in regular years: 28 days
  • February in leap years: 29 days
  • Months with 30 days
  • Months with 31 days

For example, PHP knows that the last day of February 2024 is February 29, while the last day of February 2023 is February 28.

Mental Model

Think of a date as a page on a calendar. Starting from any page in November, the instruction last day of this month means: “Stay in this month, then turn forward until you reach its final page.”

You do not need to know how many pages the month has. PHP checks the calendar rules for you.

Take Quiz

Syntax and Examples

Create a DateTimeImmutable object from the input, call modify(), then format the result:

<?php
$aDate = "2009-11-23";

$date = new DateTimeImmutable($aDate);
$lastDay = $date->modify('last day of this month');

echo $lastDay->format('Y-m-d'); // 2009-11-30

format('Y-m-d') produces a date in ISO-style form:

  • Y — four-digit year, such as 2009
  • m — two-digit month, such as 11
  • d — two-digit day, such as 30

The same code works for December:

<?php
$aDate = "2009-12-23";

$lastDay = (new DateTimeImmutable($aDate))
    ->modify('last day of this month');

echo $lastDay->format('Y-m-d'); // 2009-12-31

For known Y-m-d input, createFromFormat() makes the expected format explicit:

<?php
$date = DateTimeImmutable::createFromFormat('!Y-m-d', '2024-02-10');
$lastDay = $date->modify('last day of this month');

echo $lastDay->format('Y-m-d'); // 2024-02-29

The ! resets unspecified time fields to midnight.

Step by Step Execution

Consider this code:

<?php
$input = '2024-02-10';
$date = DateTimeImmutable::createFromFormat('!Y-m-d', $input);
$lastDay = $date->modify('last day of this month');

$result = $lastDay->format('Y-m-d');
echo $result;

Step by step:

  1. $input stores the text date 2024-02-10.
  2. createFromFormat('!Y-m-d', $input) parses that text as a year-month-day date at 00:00:00.
  3. modify('last day of this month') uses February 2024 from the parsed date and finds that month's final day.
  4. Since 2024 is a leap year, the final day is February 29.
  5. format('Y-m-d') converts the date object back to the string 2024-02-29.
  6. echo prints 2024-02-29.

Real World Use Cases

Finding month-end dates is common in applications that work with periods and reports:

  • Billing systems: calculate the end of a billing month.
  • Financial reports: define a report range from the first to the last day of a selected month.
  • Subscriptions: determine when a monthly plan period ends.
  • Dashboards: query data such as signups or sales for the current month.
  • Scheduled jobs: run a month-end reconciliation task on the correct date.
  • Form validation: ensure a selected date falls within a month-based reporting period.
Take Quiz

Real Codebase Usage

In production code, developers usually put date calculations in a small function so that parsing, validation, formatting, and time-zone choices are consistent.

A reusable function can return null for invalid input instead of silently continuing:

<?php
function lastDayOfMonth(string $input, DateTimeZone $timezone): ?string
{
    $date = DateTimeImmutable::createFromFormat('!Y-m-d', $input, $timezone);
    $errors = DateTimeImmutable::getLastErrors();

    if ($date === false || ($errors !== false && ($errors['warning_count'] > 0 || $errors['error_count'] > 0))) {
        return null;
    }

    return $date
        ->modify('last day of this month')
        ->format('Y-m-d');
}

Typical project patterns include:

  • Validation first: reject malformed dates before querying a database.
  • Immutable values: prefer DateTimeImmutable when a calculation should not alter its starting date.
  • Explicit time zones: pass an application time zone when date boundaries affect users in different regions.
  • Database ranges: calculate a start and end date before building a monthly query.

For a date-only value such as Y-m-d, using a consistent application time zone prevents server configuration from unexpectedly affecting calculations.

Common Mistakes

Manually hard-coding month lengths

This approach fails for February and is easy to maintain incorrectly:

// Fragile: February does not always have 28 days.
$daysInMonth = [1 => 31, 2 => 28, 3 => 31];

Use PHP's calendar-aware date handling instead:

$lastDay = (new DateTimeImmutable('2024-02-10'))
    ->modify('last day of this month');

Assuming February always has 28 days

This is incorrect in leap years:

// Incorrect for 2024.
$lastDay = '2024-02-28';

PHP correctly calculates 2024-02-29 when using modify().

Using an ambiguous input format

A string such as 03/04/2024 can mean different dates depending on regional convention. Prefer Y-m-d for data interchange:

$date = DateTimeImmutable::createFromFormat('!Y-m-d', '2024-04-03');

Ignoring parse errors

createFromFormat() can return false for invalid input. Check it before calling methods:

$date = DateTimeImmutable::createFromFormat('!Y-m-d', $input);

if ($date === false) {
    throw new InvalidArgumentException('Expected a valid YYYY-MM-DD date.');
}

Unexpected mutation with DateTime

DateTime::modify() changes the existing object:

$date = new DateTime('2024-01-10');
$date->modify('last day of this month');
// $date is now 2024-01-31

Use DateTimeImmutable if you need to keep the original value unchanged:

$date = new DateTimeImmutable('2024-01-10');
$lastDay = $date->modify('last day of this month');
// $date is still 2024-01-10

Comparisons

ApproachBest useImportant behavior
DateTimeImmutable->modify('last day of this month')Most application codeReturns a new date object; original stays unchanged.
DateTime->modify('last day of this month')Code intentionally mutating one objectChanges the existing object.
cal_days_in_month()You only need the number of daysReturns an integer, not a date.
Manual month-length arrayAvoidEasy to mishandle February and leap years.

If you only need a day count, cal_days_in_month() can be appropriate:

<?php
$days = cal_days_in_month(CAL_GREGORIAN, 2, 2024);
echo $days; // 29

If you need the actual final date for an input date, use DateTimeImmutable and modify().

Take Quiz

Cheat Sheet

// Recommended: input date to last date of its month
$lastDay = (new DateTimeImmutable('2024-11-23'))
    ->modify('last day of this month')
    ->format('Y-m-d');
// 2024-11-30
// Parse a known YYYY-MM-DD format explicitly
$date = DateTimeImmutable::createFromFormat('!Y-m-d', '2024-02-10');
$lastDay = $date->modify('last day of this month');
NeedUse
Last calendar datemodify('last day of this month')
First calendar datemodify('first day of this month')
Number of dayscal_days_in_month(CAL_GREGORIAN, $month, $year)
Keep the original objectDateTimeImmutable
Print as YYYY-MM-DDformat('Y-m-d')
  • Use Y-m-d for clear, sortable date strings.
  • Leap years are handled automatically.
  • Validate external input before depending on its result.
  • Supply a DateTimeZone when your application has a defined business time zone.

FAQ

How do I get the last day of the current month in PHP?

$lastDay = (new DateTimeImmutable('now'))
    ->modify('last day of this month')
    ->format('Y-m-d');

Does PHP handle leap years when finding the last day of February?

Yes. modify('last day of this month') uses PHP's calendar rules, so February 2024 ends on 2024-02-29 and February 2023 ends on 2023-02-28.

What is the difference between DateTime and DateTimeImmutable?

DateTime changes its own value when modified. DateTimeImmutable returns a new object and keeps the original unchanged.

Can I get the number of days in a month instead of a date?

Yes:

$days = cal_days_in_month(CAL_GREGORIAN, 11, 2009);
// 30

Why should I use Y-m-d dates?

Y-m-d is unambiguous, easy to sort, commonly used by APIs and databases, and directly supported by PHP date formatting.

How do I set a time zone for this calculation?

Pass a DateTimeZone object when creating the date:

$timezone = new DateTimeZone('UTC');
$date = new DateTimeImmutable('2024-11-23', $timezone);

What happens if the input date is invalid?

Parsing may fail or produce warnings depending on the parser and input. For external input, use createFromFormat() and check its result and parsing errors.

Take Quiz

Mini Project

Description

Build a small PHP function that accepts a YYYY-MM-DD date and returns the inclusive start and end dates of that month. This is useful when filtering monthly reports, invoices, or database records.

Goal

Create a validated monthly date-range helper that correctly handles every month, including leap-year February.

Requirements

Create a function that accepts a date string in YYYY-MM-DD format. Validate the input before performing date calculations. Return the first and last day of the input date's month. Format both returned dates as YYYY-MM-DD. Test the function with February 2024 and November 2009.

Take Quiz

Keep learning

Related questions

View more