Here’s how to check if a date is less than today in TypeScript: set a copy of today to midnight, then compare with <. Anything earlier than that midnight falls on a previous day, so a time earlier today won’t count:
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const isPastDay = date < startOfToday;
Every snippet below ran on TypeScript 7.0.2 and Node.js v22.22.2 with --strict, on a machine set to Chicago time. The outputs are pasted straight from those runs.
One catch with “today” demos: the answer changes every day. So I pin today to October 17, 2026, 2:30 PM by passing a now argument. In your app you leave it out, and it falls back to the real clock.
Check if a date is less than today in TypeScript
Wrap the midnight trick in a function. The optional now parameter keeps it testable, and the default makes everyday calls short:
function startOfDay(d: Date): Date {
return new Date(d.getFullYear(), d.getMonth(), d.getDate()); // local midnight
}
function isBeforeToday(date: Date, now: Date = new Date()): boolean {
return date < startOfDay(now);
}
// Pinned "today" so this output never changes: Oct 17, 2026, 2:30 PM
const now = new Date(2026, 9, 17, 14, 30);
const checks: [string, Date][] = [
["Oct 16, 2026 11:59 PM", new Date(2026, 9, 16, 23, 59)],
["Oct 17, 2026 08:00 AM", new Date(2026, 9, 17, 8, 0)],
["Oct 18, 2026 12:00 AM", new Date(2026, 9, 18)],
];
for (const [label, date] of checks) {
console.log(label, "-> before today?", isBeforeToday(date, now));
}
// Without the second argument it uses the real clock
console.log("Jan 1, 2020 vs the real today:", isBeforeToday(new Date(2020, 0, 1)));
Output:
Oct 16, 2026 11:59 PM -> before today? true
Oct 17, 2026 08:00 AM -> before today? false
Oct 18, 2026 12:00 AM -> before today? false
Jan 1, 2020 vs the real today: true

The new Date(year, month, day) constructor builds local midnight; months start at 0, so 9 is October. Comparing Dates with < works because each one becomes its millisecond timestamp. For the wider picture, see my guide on how to compare dates in TypeScript.
I’d avoid calling setHours(0, 0, 0, 0) on the date you were given, because it mutates that object. Building a fresh Date keeps the caller’s value safe. For other formats, here’s how to get the current date in TypeScript.
Less than today vs less than now
These checks sound alike but differ. “Before now” includes earlier hours of today, while “before today” ignores them:
const now = new Date(2026, 9, 17, 14, 30); // pinned: Oct 17, 2:30 PM
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const appointment = new Date(2026, 9, 17, 9, 15); // 9:15 AM the same day
console.log("appointment < now: ", appointment < now);
console.log("appointment < startOfToday:", appointment < startOfToday);
const a = new Date(2026, 9, 17);
const b = new Date(2026, 9, 17);
console.log("a === b: ", a === b);
console.log("a.getTime() === b.getTime():", a.getTime() === b.getTime());
Output:
appointment < now: true
appointment < startOfToday: false
a === b: false
a.getTime() === b.getTime(): true
A 9:15 AM appointment is before 2:30 PM, yet it isn’t before today. Pick the one your feature needs: expired tokens care about now, while overdue bills care about the calendar day.
Notice a === b is false for two identical dates. Equality operators compare object references, so compare getTime() values for equality instead. The same idea powers calculating the date difference in days in TypeScript.
Is the date less than or equal to today?
For “on or before today”, don’t compare against now, because 11:45 PM tonight would fail. Compare against the start of tomorrow instead:
function isOnOrBeforeToday(date: Date, now: Date = new Date()): boolean {
const startOfTomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
return date < startOfTomorrow;
}
const now = new Date(2026, 9, 17, 14, 30); // pinned: Oct 17, 2:30 PM
console.log("Oct 17, 11:45 PM:", isOnOrBeforeToday(new Date(2026, 9, 17, 23, 45), now));
console.log("Oct 18, 12:00 AM:", isOnOrBeforeToday(new Date(2026, 9, 18), now));
// getDate() + 1 rolls over into the next month and year
const newYearsEve = new Date(2026, 11, 31, 20, 0);
console.log("Dec 31, 9 AM (today Dec 31):", isOnOrBeforeToday(new Date(2026, 11, 31, 9), newYearsEve));
console.log("Jan 1 (today Dec 31): ", isOnOrBeforeToday(new Date(2027, 0, 1), newYearsEve));
Output:
Oct 17, 11:45 PM: true
Oct 18, 12:00 AM: false
Dec 31, 9 AM (today Dec 31): true
Jan 1 (today Dec 31): false
Adding 1 to getDate() is safe at month and year ends, as the December 31 run shows. The constructor rolls day 32 into January of the next year.
Why does “2026-10-17” count as before today?
This bug catches most people. A date-only string like "2026-10-17" is parsed as midnight UTC, and in Chicago that’s 7 PM the evening before:
const now = new Date(2026, 9, 17, 14, 30); // pinned: Oct 17, 2:30 PM
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
console.log("time zone:", Intl.DateTimeFormat().resolvedOptions().timeZone);
const raw: string = "2026-10-17"; // from <input type="date"> or an API
const parsed = new Date(raw);
console.log("new Date(raw):", parsed.toString());
console.log("before today? ", parsed < startOfToday, " <- wrong, it IS today");
function parseLocalDate(s: string): Date {
const [y, m, d] = s.split("-").map(Number);
return new Date(y, m - 1, d);
}
const local = parseLocalDate(raw);
console.log("parseLocalDate:", local.toString());
console.log("before today? ", local < startOfToday);
function toDateKey(d: Date): string {
const mm = String(d.getMonth() + 1).padStart(2, "0");
const dd = String(d.getDate()).padStart(2, "0");
return `${d.getFullYear()}-${mm}-${dd}`;
}
const todayKey = toDateKey(now);
console.log(`"${raw}" < "${todayKey}":`, raw < todayKey);
Output:
time zone: America/Chicago
new Date(raw): Fri Oct 16 2026 19:00:00 GMT-0500 (Central Daylight Time)
before today? true <- wrong, it IS today
parseLocalDate: Sat Oct 17 2026 00:00:00 GMT-0500 (Central Daylight Time)
before today? false
"2026-10-17" < "2026-10-17": false

Every US time zone is behind UTC, so this hits all American users. A string with a time but no offset, like "2026-10-17T00:00", parses as local time.
You have two clean fixes. Split the string and build a local date, or compare YYYY-MM-DD keys directly, since they sort correctly as text, just like any string comparison in TypeScript. Read more in converting a string to a date in TypeScript.
Keep the padStart(2, "0") calls: without them, October 9 becomes "2026-10-9" and sorts after October 17. I cover that in adding leading zeros in TypeScript.
TypeScript errors when comparing dates
Strict mode blocks two common mistakes: comparing a Date to a string, and comparing an optional date that might be missing. Both come up often when data arrives from JSON, as covered in working with dates in TypeScript:
interface Order {
id: string;
shippedAt?: Date;
}
const today = new Date(2026, 9, 17);
const due = new Date(2026, 9, 15);
if (due < "2026-10-17") console.log("past due");
const order: Order = { id: "A-1001" };
if (order.shippedAt < today) console.log("shipped before today");
Output:
compare_errors.ts(8,5): error TS2365: Operator '<' cannot be applied to types 'Date' and 'string'.
compare_errors.ts(11,5): error TS18048: 'order.shippedAt' is possibly 'undefined'.

Date < string, and TS18048 flags the optional shippedAt field.The fixes are short. Parse the string into a Date first, and guard the optional field with order.shippedAt !== undefined && order.shippedAt < today. Inside that check, TypeScript narrows the type to Date.
Filter overdue invoices due before today
Here’s a real use case: an accounts-receivable report that lists unpaid invoices due before today. It also skips a due date that can’t be parsed:
interface Invoice {
customer: string;
city: string;
due: string; // YYYY-MM-DD from the API
amount: number;
paid: boolean;
}
function parseLocalDate(s: string): Date {
const [y, m, d] = s.split("-").map(Number);
const date = new Date(y, m - 1, d);
return date.getMonth() === m - 1 ? date : new Date(NaN); // rejects 2026-13-01
}
function isBeforeToday(date: Date, now: Date = new Date()): boolean {
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
return date < startOfToday;
}
const invoices: Invoice[] = [
{ customer: "Lone Star Roofing", city: "Austin, TX", due: "2026-10-09", amount: 1840.5, paid: false },
{ customer: "Bayou Bakery", city: "New Orleans, LA", due: "2026-10-16", amount: 312.75, paid: false },
{ customer: "Prairie Dental", city: "Omaha, NE", due: "2026-10-17", amount: 960.0, paid: false },
{ customer: "Lakeside Gym", city: "Chicago, IL", due: "2026-10-02", amount: 499.99, paid: true },
{ customer: "Desert Pools", city: "Phoenix, AZ", due: "2026-13-01", amount: 725.25, paid: false },
];
const now = new Date(2026, 9, 17, 14, 30); // pinned: Oct 17, 2:30 PM
console.log("Invalid Date < now:", parseLocalDate("2026-13-01") < now);
const overdue = invoices.filter(
(inv) => !inv.paid && isBeforeToday(parseLocalDate(inv.due), now),
);
for (const inv of overdue) {
console.log(`OVERDUE ${inv.customer.padEnd(18)} ${inv.city.padEnd(16)} due ${inv.due} $${inv.amount.toFixed(2)}`);
}
const total = overdue.reduce((sum, inv) => sum + inv.amount, 0);
console.log(`${overdue.length} overdue, $${total.toFixed(2)} outstanding`);
Output:
Invalid Date < now: false
OVERDUE Lone Star Roofing Austin, TX due 2026-10-09 $1840.50
OVERDUE Bayou Bakery New Orleans, LA due 2026-10-16 $312.75
2 overdue, $2153.25 outstanding

The month check matters because new Date(2026, 12, 1) quietly becomes January 1, 2027. An Invalid Date holds NaN, so every comparison with it returns false. Test Number.isNaN(date.getTime()) if bad input should be reported.
The filtering itself is ordinary array work, explained in filtering arrays in TypeScript. You could sort the overdue list by date too, as shown in sorting arrays in TypeScript.
Check a date before today with date-fns
Already using date-fns? isBefore() and startOfDay() say the same thing in words, in local time:
import { isBefore, isPast, startOfDay } from "date-fns";
const now = new Date(2026, 9, 17, 14, 30); // pinned: Oct 17, 2:30 PM
const dueYesterday = new Date(2026, 9, 16, 17, 0); // Oct 16, 5:00 PM
const thisMorning = new Date(2026, 9, 17, 9, 0); // Oct 17, 9:00 AM
console.log("yesterday before today:", isBefore(dueYesterday, startOfDay(now)));
console.log("this morning before today:", isBefore(thisMorning, startOfDay(now)));
console.log("this morning before now:", isBefore(thisMorning, now));
console.log("isPast(Jan 1, 2020):", isPast(new Date(2020, 0, 1))); // real clock
Output:
yesterday before today: true
this morning before today: false
this morning before now: true
isPast(Jan 1, 2020): true
Be careful with isPast(): it compares against this exact moment, not today’s midnight. That’s the “before now” check from earlier, and it always reads the real clock, so you can’t pin it in tests.
For displaying these dates in a specific time zone, see how to format a date with a time zone in TypeScript.
Which check should you use?
| Goal | Code | Watch out for |
|---|---|---|
| Before today (calendar day) | date < startOfDay(now) | Build midnight with the local constructor |
| Before this moment | date < now | Earlier today counts as past |
| On or before today | date < startOfTomorrow | Don’t use <= now |
| Date-only strings | Parse parts or compare YYYY-MM-DD keys | new Date("2026-10-17") is UTC |
These guides pair well with today’s checks:
- Format dates in TypeScript
- Count the days between two dates in TypeScript
- Get the week number from a date in TypeScript
Frequently asked questions
How do I check if a date is less than today in TypeScript?
Build today’s midnight with new Date(now.getFullYear(), now.getMonth(), now.getDate()) and test date < startOfToday. Earlier times today won’t count as past.
Can I compare Date objects with < and > in TypeScript?
Yes. Relational operators convert both dates to millisecond timestamps. Don’t use === though, because it compares object references; compare getTime() values instead.
Why is today’s date from an input field treated as yesterday?
new Date("2026-10-17") parses a date-only string as UTC midnight, which is the previous evening in US time zones. Split the string and build a local date instead.
How do I check if a date is today or in the past?
Compare against the start of tomorrow: date < new Date(y, m, d + 1). That includes every time today, up to 11:59 PM.
How do I test code that depends on today’s date?
Pass the current time as a parameter with a default, like now: Date = new Date(). Tests then supply a fixed date, so results don’t change day to day.
Where are the Date parsing rules documented?
See Date.parse() on MDN, including why date-only strings are treated as UTC.

Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn