Carbon::isPast

Returns true when the instance is strictly earlier than the current moment.

Signature
public function isPast(): bool
  • Added in Laravel Carbon 1.0.0

Return value

booltrue when the instance is strictly less than "now" evaluated in the instance's own timezone, false when it is equal to or after now.

Changelog

  • PHP Carbon 3.0Native bool return type added; the comparison now runs through nowWithSameTz(), which builds "now" in the instance's own timezone.

isPast() is a strict less-than comparison against the current moment: internally it is $this->lessThan($this->nowWithSameTz()). The comparison happens at microsecond precision, so two instances that print the same Y-m-d H:i:s can still disagree.

When to reach for it

Reach for it wherever a stored deadline decides behaviour: has a subscription lapsed, is a signed URL stale, is an invoice overdue, should a scheduled post now be visible. It reads better than $date->lt(now()) and it cannot drift out of sync with the clock the rest of Carbon uses.

What trips people up

Three things. First, it is strict — an instance exactly equal to now is neither past nor future; use isNowOrPast() if the boundary should count. Second, in tests it obeys Carbon::setTestNow(); if your assertions flip when run at midnight, freeze the clock rather than widening the tolerance. Third, a date-only value such as Carbon::parse('2026-03-14') is midnight, so it becomes "past" at 00:00:00.000001 on that same day — usually not what "expires on the 14th" means. Compare against endOfDay() when the whole day should count.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Decide whether a subscription's expires_at has lapsed before granting access.
  • Hide scheduled posts whose publish_at has not yet arrived by negating it.
  • Reject a password reset token whose expiry timestamp has gone by.
  • Flag invoices as overdue in a listing without a second database query.

Frequently asked questions

How do I check if a date has already passed in Laravel?

Call isPast() on the Carbon instance — $date->isPast() returns true when the moment is strictly before now. Its counterpart isFuture() covers the other direction, and isNowOrPast() includes the exact boundary. For a value cast with 'datetime' on a model you can call it directly on the attribute.

Why does isPast() return false for a date that looks like it already passed?

Almost always because the comparison is strict and the two moments are equal, or because a frozen test clock is still in effect. Carbon::setTestNow() pins "now" for the rest of the process, so an unmatched call in an earlier test leaks into later ones — always reset it with Carbon::setTestNow() and no arguments.

Does isPast() account for timezones?

Yes, and it does not change the answer. Carbon builds "now" in the instance's own timezone before comparing, but since both sides refer to the same instant the result is timezone-independent. Timezone only matters for the day-based checks such as isToday(), which compare formatted date strings rather than instants.

More examples

The boundary is exact to the microsecond

The grace period ends at 2026-06-01 00:00:00. At exactly that instant isPast() is false — the comparison is strictly less-than — and one second later it is true. Any "off by one customer" bug report about expiry almost always lands on this line.

Example · php
<?php

use Illuminate\Support\Carbon;

date_default_timezone_set('UTC');

$grace = Carbon::parse('2026-05-25 00:00:00')->addDays(7);

echo 'grace ends: ', $grace->toDateTimeString(), PHP_EOL;

Carbon::setTestNow('2026-06-01 00:00:00');
var_dump($grace->isPast());

Carbon::setTestNow('2026-06-01 00:00:01');
var_dump($grace->isPast());

Carbon::setTestNow();

A date-only value expires at midnight, not at the end of the day

Parsing a bare date gives midnight, so at 09:00 on the same day isPast() already reports true — the card looks expired on the day it should still work. Comparing against endOfDay() (2026-03-14 23:59:59.999999) reports false and matches what "valid through the 14th" means to a user.

Example · php
<?php

use Illuminate\Support\Carbon;

date_default_timezone_set('UTC');

Carbon::setTestNow('2026-03-14 09:00:00');

$expiresOn = Carbon::parse('2026-03-14'); // 2026-03-14 00:00:00

echo 'stored value: ', $expiresOn->toDateTimeString(), PHP_EOL;
var_dump($expiresOn->isPast());
var_dump($expiresOn->copy()->endOfDay()->isPast());

Carbon::setTestNow();

On an Eloquent model

The datetime cast turns the raw column into an Illuminate\Support\Carbon instance built in the application timezone, so isPast() can be called straight on the attribute. The null guard matters: a nullable expires_at column means "never expires", and calling isPast() on null is a fatal error. This snippet needs a booted application with a database connection.

Example · php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Subscription extends Model
{
    protected function casts(): array
    {
        return ['expires_at' => 'datetime'];
    }

    public function hasExpired(): bool
    {
        return $this->expires_at !== null && $this->expires_at->isPast();
    }
}

// Elsewhere, inside a booted application:
// $subscription = Subscription::find(1);
// echo $subscription->hasExpired() ? 'expired' : 'active', PHP_EOL;

Discussion

  • Be the first to comment on this lesson.