What Is 3 Months Ago From Today
Three months sounds simple until you actually have to calculate it.
Try this: pick today's date. December only has 31 days, so December 30th exists. But what about May 31st? What if today is March 31st — three months back would be December 31st, right? Did you land on the same day number? But what if today is March 30th? Now count back three months. Three months back is February 31st — which doesn't exist.
This is where most people get tripped up. But the concept seems trivial. The execution is anything but.
What Is "Three Months Ago" Actually
At its core, "three months ago from today" means subtracting three calendar months from the current date while preserving the day of the month where possible. But "where possible" is doing a lot of heavy lifting there.
Calendar months vs. 90 days
This is the first distinction that matters. Three calendar months is not the same as 90 days. Not even close, sometimes.
If today is January 15th, three calendar months back is October 15th. That's 92 days (or 91 in a leap year). If today is March 1st, three months back is December 1st — that's 90 days exactly in a non-leap year, 91 in a leap year. But if today is March 31st, three calendar months back is December 31st — that's 90 days.
The day count varies because months have different lengths. But january has 31 days. February has 28 or 29. March has 31. April has 30. The math shifts depending on which months you're crossing.
The "same day number" rule
Most systems — spreadsheets, programming languages, business logic — follow a simple rule: keep the day number the same, and if that day doesn't exist in the target month, roll to the last day of that month.
So March 31st minus three months = December 31st (exists, fine). August 31st minus three months = May 31st (exists). May 31st minus three months = February 28th (or 29th in leap year). October 31st minus three months = July 31st (exists).
But January 31st minus three months = October 31st (exists). July 31st minus three months = April 30th (April only has 30 days).
This rollover behavior is consistent in Excel, Google Sheets, Python's dateutil, PostgreSQL, and most SQL dialects. But not all. Some systems throw an error. Some roll forward to the 1st of the next month. You have to know what your tool does.
Business months vs. calendar months
Here's where it gets messy. In finance, legal contracts, and some regulatory contexts, "three months" might mean something completely different.
A "quarter" in business is often defined as exactly 13 weeks (91 days) or as specific date ranges: Jan 1–Mar 31, Apr 1–Jun 30, etc. Consider this: a rolling "three months" for a rolling window calculation might be 90 days exactly. A lease might say "three calendar months" and mean something different than "90 days.
If you're writing code for a billing system, you need to know which definition the contract uses. I've seen lawsuits hinge on this exact ambiguity.
Why It Matters / Why People Care
You might wonder why anyone writes a whole article about this. Because date math errors are everywhere, and they're expensive.
Billing cycles
Subscription services bill monthly. If a customer signs up on January 31st, their "three months free" trial ends when? April 30th? Consider this: may 1st? In practice, may 31st? The answer determines whether you charge them on the right day — or whether you lose revenue, or worse, charge them early and trigger a dispute.
I've seen Stripe and Braintree handle this differently. Some custom billing code I've reviewed just adds 30 days three times. Stripe's subscription engine uses "calendar month" logic with end-of-month rollover. That's wrong, and it compounds.
Legal and regulatory deadlines
"Within three months of the incident" — courts interpret this differently by jurisdiction. Some use 90 days. The U.Some use "three months minus one day" (so an incident on Jan 15 gives you until Apr 14). Some use calendar months. S. Federal Rules of Civil Procedure use "90 days" explicitly for some deadlines but "three calendar months" for others.
If you're building compliance software, you can't guess. You need the statute.
Data analysis and reporting
Rolling three-month windows are standard in analytics. " If you write WHERE date >= CURRENT_DATE - INTERVAL '3 months' in PostgreSQL, you get calendar month subtraction. "Show me revenue for the last three months.If you write WHERE date >= CURRENT_DATE - 90, you get 90 days. The results differ by up to three days depending on the month.
That difference changes whether a borderline transaction falls in or out of the window. For a high-volume business, that's thousands of dollars in reported revenue difference.
Age verification
"Must be 18 years old" — but what about "must have held a license for three months"? May 1st? The DMV has a rule for this. If someone got their license on January 31st, are they eligible on April 30th? Plus, april 31st doesn't exist. Your code should match it.
How It Works (or How to Do It)
Let's get practical. Here's how to calculate three months ago correctly in the most common contexts.
In spreadsheets (Excel / Google Sheets)
The function you want is EDATE.
=EDATE(TODAY(), -3)
That's it. EDATE handles the end-of-month rollover automatically. Consider this: TODAY() gives you the current date. -3 means three months back.
Don't use TODAY() - 90. That's 90 days, not three months. Don't use DATE(YEAR(TODAY()), MONTH(TODAY())-3, DAY(TODAY())) — that breaks on month boundaries and doesn't handle year rollover (January minus 3 months = October of previous year, but MONTH()-3 gives you -2, which errors).
EDATE is the only reliable built-in. It's been in Excel since forever. Google Sheets has it too.
In SQL
PostgreSQL:
SELECT CURRENT_DATE - INTERVAL '3 months';
MySQL:
SELECT DATE_SUB(CURDATE(), INTERVAL 3 MONTH);
SQL Server:
SELECT DATEADD(month, -3, GETDATE());
BigQuery:
SELECT DATE_SUB(CURRENT_DATE(), INTERVAL 3 MONTH);
Snowflake:
SELECT DATEADD(month, -3, CURRENT_DATE());
All of these follow the "same day, roll to end of month" rule. But verify your specific version — older MySQL versions had bugs with this.
In Python
The standard library datetime module doesn
The standard library datetime module doesn’t ship with a “subtract N months” helper, but you can build a strong version yourself or, more simply, lean on a well‑tested third‑party package. Below are the most common approaches and the pitfalls to watch for.
Using dateutil.relativedelta
dateutil is the de‑facto standard for “human‑friendly” date arithmetic. It understands calendar months, end‑of‑month roll‑overs, and even leap‑year quirks.
# pip install python‑dateutil
from dateutil.relativedelta import relativedelta
from datetime import date, datetime, timezone
# naive date
today = date.today()
three_months_ago = today - relativedelta(months=3)
print(three_months_ago) # e.g. 2024‑09‑27
# aware datetime (recommended for production)
now = datetime.now(timezone.utc)
three_months_ago_utc = now - relativedelta(months=3)
print(three_months_ago_utc) # 2024‑09‑27 14:12:45+00:00
relativedelta automatically clamps the day component to the last day of the target month when the source day doesn’t exist (e.Think about it: g. , date(2025,1,31) - relativedelta(months=3) yields date(2024,10,31)).
Pure‑Python implementation (no extra dependencies)
If you must stay in the standard library, a small helper can replicate the same behavior:
For more on this topic, read our article on what time will it be in 16 hours or check out how many hours till 12 am.
import calendar
from datetime import date
def subtract_months(dt: date, months: int) -> date:
"""Return a date that is months* calendar months before *dt*.month - months
while month < 1:
year -= 1
month += 12
# Preserve the day, but cap it at the target month’s last day
day = min(dt.day, calendar.year
month = dt.Which means """
year = dt. monthrange(year, month)[1])
return dt.
# Example
d = date(2025, 1, 31)
print(subtract_months(d, 3)) # 2024‑10‑31
This routine is deterministic and works for both date and datetime objects (the latter will keep the original time component).
Pandas offsets
When you’re already using pandas for analytics, its date offsets integrate nicely:
import pandas as pd
ts = pd.Also, timestamp. now(tz='UTC')
three_months_ago = ts - pd.
When working with calendar‑aware arithmetic, it’s easy to overlook subtle corner cases that can bite you in production. Below are a few additional patterns and safeguards that complement the approaches shown earlier.
#### Handling Leap‑Year Edge Cases
Subtracting a month from February 29 should land on February 28 (or 29 if the target year is also a leap year). Both `dateutil.relativedelta` and the pure‑Python helper respect this rule automatically:
```python
from dateutil.relativedelta import relativedelta
from datetime import date
leap_day = date(2024, 2, 29)
print(leap_day - relativedelta(months=1)) # 2024‑01‑29
print(leap_day - relativedelta(months=12)) # 2023‑02‑28
If you roll your own logic, make sure to recompute the month‑range after adjusting the year/month, as demonstrated in the subtract_months function.
Time‑Zone Awareness
Naïve date or datetime objects hide the fact that a month boundary can shift when viewed in different zones. Here's one way to look at it: subtracting one month from 2025‑03‑01 00:30 UTC yields 2025‑02‑01 00:30 UTC, but the same instant in America/New_York is 2025‑02‑28 19:30 EST (because DST offset changed). The safest practice is:
- Store timestamps in UTC (or another fixed offset) whenever possible.
- Apply
relativedeltato the aware UTC datetime. - Convert to the desired local zone only for presentation.
from datetime import datetime, timezone, timedelta
import pytz
utc_now = datetime.now(timezone.Here's the thing — utc)
three_months_ago_utc = utc_now - relativedelta(months=3)
local_time = three_months_ago_utc. astimezone(pytz.
#### Performance Considerations
For tight loops (e.g., processing millions of rows), the overhead of creating `relativedelta` objects can add up. In such scenarios:
* Pre‑compute a lookup table of month offsets for a fixed reference year if the day‑of‑month is constant.
* Use NumPy’s `datetime64[M]` dtype for vectorized month shifts when working with arrays:
```python
import numpy as np
dates = np.array(['2025-01-31', '2025-02-28', '2025-03-15'], dtype='datetime64[D]')
shifted = dates.astype('datetime64[M]') - np.timedelta64(3, 'M')
# Convert back to day precision if needed
shifted = shifted.astype('datetime64[D]')
- When using Pandas,
DateOffsetis implemented in Cython and is usually faster than a pure‑Python loop for Series operations.
Testing Strategies
Unit tests should cover at least the following scenarios:
| Input date | Months to subtract | Expected result |
|---|---|---|
| 2023‑01‑31 | 1 | 2022‑12‑31 |
| 2024‑02‑29 | 1 | 2024‑01‑29 |
| 2024‑02‑29 | 12 | 2023‑02‑28 |
| 2025‑03‑01 12:00Z | 1 | 2025‑02‑01 12:00Z |
| 2025‑03‑01 12:00Z | 1 | 2025‑02‑28 17:00EST (if converting to NY) |
Parameterized test frameworks (e.Also, g. , pytest.But mark. parametrize) make it easy to assert that both the library‑based and custom implementations produce identical outputs across a matrix of dates.
Choosing the Right Tool
dateutil.relativedelta– best for readability and correctness when you need
Choosing the Right Tool (continued)
-
dateutil.relativedelta– best for readability and correctness when you need exact calendar semantics, such as “go back three months, preserving the day‑of‑month if possible”. It handles month‑end edge cases automatically and works with both naive and awaredatetimeobjects. -
pandas.DateOffset– the go‑to choice when your data already lives in aSeriesorDataFrame. Because the offset is implemented in Cython, it offers near‑native speed for bulk operations. The trade‑off is a tighter coupling to the Pandas ecosystem; if you’re outside that environment you’ll need to convert back to plain Python objects. -
arrow– provides a fluent, human‑readable API (arrow.get('2025-03-01').shift(months=-3)) and excels at parsing heterogeneous string formats. Its strength lies in simplifying timezone conversion, but it adds a small dependency and is slightly slower than the lower‑level libraries for extremely tight loops. -
pytz/zoneinfo– useful when you only need to translate between zones without altering the underlying date arithmetic. They are not a replacement for month‑aware arithmetic, but they pair nicely withrelativedeltawhen the final display time must respect historic offset changes. -
Manual arithmetic – occasionally the only viable path for ultra‑high‑throughput pipelines where even the overhead of creating a small object is unacceptable. You can pre‑compute a dictionary of “month deltas” for a fixed reference year and apply integer addition to the month field while adjusting the year and clipping the day to the last valid day of the resulting month. This approach is error‑prone and should be exhaustively tested, but it can shave microseconds off per‑row processing.
When to avoid a particular tool
| Situation | Avoid | Prefer |
|---|---|---|
| Large‑scale vectorised date shifts in a Pandas context | Looping with relativedelta in a Python for loop |
pd.DateOffset or dt.Also, shift |
| Need for sub‑second precision with timezone handling | Naïve timedelta arithmetic |
datetime with timezone + relativedelta |
| Minimal dependencies (e. Here's the thing — g. , embedded system) | arrow or pytz |
Built‑in datetime + manual month logic |
| Highly complex recurrence rules (e.This leads to g. , “the last Monday of each quarter”) | Manual loops | dateutil.rrule or `pandas. |
Interoperability tips
-
Convert early, shift late: store every instant as a UTC‑aware
datetime. Perform month arithmetic on that UTC representation, and only convert to a local zone for output or reporting. This isolates calendar logic from timezone quirks. -
Serialization: if you need to persist the “shifted” result, store the underlying UTC value and the target zone separately; recompute the local representation on read‑time to avoid stale offset information.
-
Testing across zones: run your test matrix not only with the UTC reference but also with a representative set of zones (
-
Testing across zones: run your test matrix not only with the UTC reference but also with a representative set of zones (e.g., those that observe daylight saving time, those that have changed their offset historically, and those with fixed offsets) to catch edge cases like ambiguous or non‑existent local times.
Conclusion
Navigating month‑aware date arithmetic in Python requires balancing precision, performance, and ecosystem fit. While pandas offers the most ergonomic solution for vectorised operations, dateutil provides a solid fallback for complex recurrences, and manual arithmetic can be justified in performance‑critical niches. Here's the thing — the key is to treat timezones as a separate concern: anchor your data in UTC, apply calendar shifts where the logic is clearest, and defer localisation until the point of display. By adhering to these principles and rigorously testing across a diverse set of timezones, you can build date‑sensitive pipelines that are both correct and maintainable.
Latest Posts
Just Went Live
-
What Is 3 Months Ago From Today
Aug 26, 2026
-
How Many More Days Till June 7th
Aug 26, 2026
-
How Many Days Till March 21st
Aug 26, 2026
-
What Ratio Is Equivalent To 5 4
Aug 26, 2026
-
How Old If Born In 1990
Aug 26, 2026