Idea: date().end_of_month() (original) (raw)

September 16, 2025, 9:30pm 1

Currently to get the last day of the month, the most straightforward method is:

from calendar import monthrange
from datetime import date

mydate = date.today()

mydate.replace(day=monthrange(mydate.year, mydate.month)[1])  # date(2025, 9, 30)

monthrange() returns a tuple (dayofweek, numberofdaysinmonth), therefore taking the second element we have the number of last day of month.

Could we extend allowed input for day argument in the replace method for negative integers to be able to type:

from datetime import date

mydate = date.today()

mydate.replace(day=-1)  # date(2025, 9, 30)

to get the last day of a given month?

By analogy to list indexing, this should be perfectly readable (and maybe even expected?) for a Python programmer.

Sample implementation (patch on CPython’s main branch):

Index: Lib/_pydatetime.py
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/Lib/_pydatetime.py b/Lib/_pydatetime.py
--- a/Lib/_pydatetime.py	(revision cf9ef73121cd2b96dd514e09b3ad253d841d91dd)
+++ b/Lib/_pydatetime.py	(date 1758057891128)
@@ -1178,6 +1178,8 @@
             month = self._month
         if day is None:
             day = self._day
+        elif day < 0:
+            day = _days_in_month(year, month) + 1 + day
         return type(self)(year, month, day)
 
     __replace__ = replace
@@ -2090,6 +2092,8 @@
             month = self.month
         if day is None:
             day = self.day
+        elif day < 0:
+            day = _days_in_month(year, month) + 1 + day
         if hour is None:
             hour = self.hour
         if minute is None:

Alternatively we could extend the behaviour of date/datetime constructors in this matter.

Update: I’ve edited the title to express the discussion direction (LAST sentinel).

Update: I’ve edited the title again (end_of_month).

jorenham (Joren Hammudoglu) September 17, 2025, 2:50pm 2

I like it!

Yea that would also be pretty cool. For example, date(year=-6, month=2, day=-1) would then be the last day of February from five (not six) years ago.

loic-simon (Loïc Simon) September 17, 2025, 6:31pm 3

I would find negative year numbers pretty ambiguous, without context I would except it to mean “year 6 BC” rather than “6 years from now”.

I don’t see much interest to allow negative months either, but I would definitively use negative days, having used the calendar.monthrange()[-1] trick numerous times!

kapinga (Jon Harding) September 17, 2025, 7:13pm 4

Months are 1-indexed, not 0-indexed, so using -1 to signal the end of the month seems incoherent. Moreover, days=-1 could just as easily be read as the last day of the previous month.

That said, I agree creating a date for the last day of the month is harder than it should be. Perhaps we could add a (sentinel?) constant LAST to the datetime module that explicitly signals the last day in the current month should be used?

There is no need for any other field to accept this special argument, since the last month, hour, minute, second, and microsecond are all well known (and there’s no “final” year that I’m aware of :-))

storchaka (Serhiy Storchaka) September 17, 2025, 7:27pm 5

Negative day is ambiguous. Based on continuity, I would interpret day=0 as the last day of the previous month and day=-1 as the penultimate day of the previous month. So, that data(year, month, day-d) == data(year, month, day) - timedelta(days=d).

This will actually lead to bugs. If you calculate the year, month, and day, or get them from the user, you will silently get wrong date instead of an error.

If you need this, write a helper function that handles negative days in the way expected in your program.

xitop (Xitop) September 19, 2025, 6:06am 6

Yes, pretty ambiguous as a negative number, but perhaps quite useful as a symbolic constant.

import datetime as dt

mydate = dt.date.today()
mydate.replace(day=dt.LAST_DAY_IN_MONTH) 

maciek (Maciej Olko) September 19, 2025, 9:00am 7

Thank you for all replies, I am now convinced that a sentinel would be better than negative numbers support. Also, for consistency, when adding this, it would be worth to support this sentinel in both constructors and the replace() methods.

As @xitop noted, LAST_DAY_IN_MONTH sentinel would more precisely describe it.

Though maybe for consistency we could create short-named LAST sentinel and let it work for majority of arguments of a datetime/date constructor?

datetime(
    2025, month=LAST, day=LAST, hour=LAST, minute=LAST,
    second=LAST, microsecond=LAST
) == datetime(2025, 12, 31, 23, 59, 59, 59, 999999)

date(2025, month=LAST, day=LAST) == date(2025, 12, 31)

May I propose a poll?

What’s better?

blhsing (Ben Hsing) September 19, 2025, 9:12am 8

But how does one express the second to last day of a month? Maybe the sentinel can support subtraction so one can write LAST_DAY_IN_MONTH - 1?

The upper bounds of the other arguments do not vary so they don’t need a sentinel.

AndersMunch (Anders Munch) September 19, 2025, 9:21am 9

Alternative option: Stop trying to be clever with .replace arguments, and just make a new method.

mydate = date.today()
mydate.last_day_of_month() # date(2025, 9, 30)

pf_moore (Paul Moore) September 19, 2025, 9:26am 10

Why would you need this? I don’t think it’s common enough that anything more than date(y, m, day=LAST_DAY_IN_MONTH) - timedelta(day=1) is needed.

I can see the advantage of date(y, m, day=LAST_DAY_IN_MONTH) over date(y, m+1, 1) - timedelta(day=1) in terms of clarity, but I don’t think further generalisation is needed.

Although I voted for LAST_DAY_IN_MONTH, when typing the above I found it to be annoyingly verbose. So my vote is for the functionality, but I’d like to see a different name (something shorter, but not as vague as LAST).

blhsing (Ben Hsing) September 19, 2025, 9:34am 11

I have payments that are due on the first day of each month but I want to set the send date of my automatic payments to be 3 days before the last day of each month in order to account for the processing time.

Agreed that it may not be common enough a scenario to warrant the additional complexity over the timedelta operation.

pf_moore (Paul Moore) September 19, 2025, 9:43am 12

For that I’d actually find date(yy, mm, 1) - timedelta(days=3) clearer, because it isolates the two aspects of the logic. But I agree it’s all very much personal preference at that point.

As a side note, for anything more complex, the python-dateutil library is awesome.

blhsing (Ben Hsing) September 19, 2025, 9:52am 13

FWIW this python-dateutil package that you find awesome uses -1 to denote the last day of a month, -2 for the second-to-last day of a month, and so on, and so does the popular fintech Plaid API.

So maybe datetime can just follow this established convention, but also define the constant datetime.LAST_DAY_OF_MONTH as -1 so it can be subtracted with very little complexity?

We currently have yday for the day number of the year in the documentation, so we can probably name the new constant LAST_MDAY for short.

peterc (peter) September 19, 2025, 11:38am 14

I’m used to libraries where a common pattern is to use literal strings rather than sentinals, eg as

date(2025, 8, "last")

date(year: int, month: int, day: int | Literal("last"))

or in practical use

for month in range(1, 13):
  my_date = date(2025, month, "last") - timedelta(days=3)
  do_stuff(my_date)

I find it a convenient pattern. It works well with the type-hinters and autocomplete, and you don’t need to import the sentinals.

Just want to put it out there as an option.

kapinga (Jon Harding) September 19, 2025, 12:12pm 15

As @storchaka noted above, it’s common to do math on date inputs and/or to rely on user inputs. A (non-string, non-number) constant ensures that any attempt to manipulate the use of “last” errors immediately and correctly, and also makes sure that user input cannot unintentionally use the “last” value.

As to bikeshedding, I’d vote for LAST_DAY. The day field in date and datetime always refers to the day of a month, so its meaning would be clear.

Dutcho (Dutcho) September 19, 2025, 4:43pm 16

In finance, End of Month or EOM is common (as is EOY for End of Year).

storchaka (Serhiy Storchaka) September 19, 2025, 5:39pm 17

Passing a non-integer singleton will complicate the C implementation. And if use a special integer value as a sentinel, it can accidentally match the calculated value. I think that it may be simpler to add a special function or method that return the date of the last day of the month, if this is a common problem. It can be just a function that returns the number of days in the month.

Other non-trivial problems are finding the date of the first Tuesday of the month, the last Thursday of the month, the first Monday of the year, the last Saturday of the year. These are practical problems, for example, the US presidential election is held on the first Tuesday after the first Monday in November.

tim.one (Tim Peters) September 19, 2025, 5:54pm 18

I sympathize with the desire, but do not want the dirt-simple .replace() to become “smart” in any way. It has one job to do, and does it in a wholly obvious way now.

So I think what you really want is a (at least one) new method to compute minor variations of the current date. Like, say, date.eom() to return a new date set to the last day of the month.

But then the problem is that people will pile on so many increasingly expansive ideas of “minor variations” that the proposal will collapse under its own increasing weight <0l.3 wink>.

Appealing to, and following, prior art, can help in such cases. Things like “third Thursday of the month” have been tackled by lots of other software before.

pf_moore (Paul Moore) September 19, 2025, 7:17pm 19

Agreed. The very basic “create a date object referring to the last date of a given month” is a plausible extension to the datetime API. As soon as we go beyond that, the possibilities expand rapidly, and they are basically all practical requirements[1]. We already have good 3rd party solutions here - python-datetime is one I’ve already mentioned which works with stdlib date objects, and there’s others such as moment, arrow, and pendulum.

Let’s keep to the simple case. I like Tim’s idea of a simple method on the date class to get the end of the given month (note: just on the date class - IMO it doesn’t make sense on a datetime value).


  1. There’s no end to the number of esoteric rules people have for picking dates. ↩︎

maciek (Maciej Olko) September 21, 2025, 4:44am 20

Thank you for the discussion so far. It looks like a new method wins over a sentinel.

For comparison, APIs from other languages and libraries:

import java.time.LocalDate;
import java.time.YearMonth;

YearMonth ym = YearMonth.of(2025, 2);
LocalDate lastDay = ym.atEndOfMonth();  // 2025-02-28
new Date(2025, 11, 0).getTime() == new Date(2025, 10, 31).getTime()
SELECT LAST_DAY('2025-02-15');
import { lastDayOfMonth } from "date-fns";
lastDayOfMonth(new Date(2025, 1, 15)); // 2025-02-28
moment("2025-02-15").endOf("month").date(); // 28
import pendulum

d = pendulum.date(2025, 9, 1)
last_date = d.end_of("month")
print(last_date)  # 2025-09-30
// February 2025
year_month ym{year{2025}, February};
    
// last day of month
year_month_day last = ym / last;
use DateTime;

my $dt = DateTime->new(year => 2025, month => 2, day => 15);
my <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>l</mi><mi>a</mi><mi>s</mi><mi>t</mi><mo>=</mo></mrow><annotation encoding="application/x-tex">last = </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">a</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span></span></span>dt->clone->set_day( $dt->last_day_of_month );
print $last->ymd, "\n";   # 2025-02-28
$dt = new DateTime("2025-02-15"); <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>l</mi><mi>a</mi><mi>s</mi><mi>t</mi><mo>=</mo></mrow><annotation encoding="application/x-tex">last = </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">a</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span></span></span></span>dt->format("Y-m-t");  // 2025-02-28

My suggestion would be to make the new date class method accept year and month arguments if called on the class, and without arguments when called on an instance:

from datetime import date

date.end_of_month(2025, 10)  # 2025-10-31
date.today().end_of_month()  # 2025-09-30

May I ask you for your opinion?

Method name