ActiveState Code

Recipe 476197: First / Last Day of the Month


I often do reporting that requires the first and last day of the month as bounds. I ended up writing repetitive code until I wrote these utility functions. You might find them useful too.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/env python

import datetime
import time

def mkDateTime(dateString,strFormat="%Y-%m-%d"):
    # Expects "YYYY-MM-DD" string
    # returns a datetime object
    eSeconds = time.mktime(time.strptime(dateString,strFormat))
    return datetime.datetime.fromtimestamp(eSeconds)

def formatDate(dtDateTime,strFormat="%Y-%m-%d"):
    # format a datetime object as YYYY-MM-DD string and return
    return dtDateTime.strftime(strFormat)

def mkFirstOfMonth2(dtDateTime):
    #what is the first day of the current month
    ddays = int(dtDateTime.strftime("%d"))-1 #days to subtract to get to the 1st
    delta = datetime.timedelta(days= ddays)  #create a delta datetime object
    return dtDateTime - delta                #Subtract delta and return

def mkFirstOfMonth(dtDateTime):
    #what is the first day of the current month
    #format the year and month + 01 for the current datetime, then form it back
    #into a datetime object
    return mkDateTime(formatDate(dtDateTime,"%Y-%m-01"))

def mkLastOfMonth(dtDateTime):
    dYear = dtDateTime.strftime("%Y")        #get the year
    dMonth = str(int(dtDateTime.strftime("%m"))%12+1)#get next month, watch rollover
    dDay = "1"                               #first day of next month
    nextMonth = mkDateTime("%s-%s-%s"%(dYear,dMonth,dDay))#make a datetime obj for 1st of next month
    delta = datetime.timedelta(seconds=1)    #create a delta of 1 second
    return nextMonth - delta                 #subtract from nextMonth and return

if __name__=="__main__":
    for i in range(12):
        thisMonth = ("0%i"%(i+1,))[-2:]
        print thisMonth
        d = mkDateTime("2004-%s-02"%thisMonth)
        print formatDate(d)
        print formatDate(d,"%Y%m%d")
        print mkFirstOfMonth(d)
        print mkFirstOfMonth2(d)
        print mkLastOfMonth(d)
    

Discussion

Often I need to know the last day of the month and the first. It is a simple thing to calc the first of the month since it is fixed, however the last day of the month is a bit more interesting.

In this, I calc the first day of the next month by incrementing the current month by 1, account for rollover. Then I subtract 1 second and there you are.

You will note that mkFirstOfMonth and mkFirstOfMonth2 do the same thing yet in different ways. mkFirstDayOfMonth is a hack while mkFirstOfMonth2 is, in my opinion, more pythonic.

Comments

  1. 1. At 6:39 a.m. on 30 mar 2006, Graham Fawcett said:

    strftime? No need for all the strftime work, since date and datetime objects have attributes representing their days, months, years, etc.

    For comparison, here's another way to write first_day and last_day functions. It's a bit opaque and undocumented, but is likely faster.

    from datetime import date, timedelta
    
    def get_first_day(dt, d_years=0, d_months=0):
        # d_years, d_months are "deltas" to apply to dt
        y, m = dt.year + d_years, dt.month + d_months
        a, m = divmod(m-1, 12)
        return date(y+a, m+1, 1)
    
    def get_last_day(dt):
        return get_first_day(dt, 0, 1) + timedelta(-1)
    
    >>> d = date.today()
    >>> d
    datetime.date(2006, 3, 30)
    >>> get_first_day(d)
    datetime.date(2006, 3, 1)
    >>> get_last_day(d)
    datetime.date(2006, 3, 31)
    

    The get_first_day function is a bit more convoluted than is stricly necessary. But allowing for those delta_month and delta_year optional arguments allows for a simpler get_last_month function, as well as letting you, e.g., get the last day of this month in the previous year.

  2. 2. At 6:43 a.m. on 30 mar 2006, Graham Fawcett said:

    thisMonth = ("0%i"%(i+1,))[-2:]. There's an easier way to do this:

    thisMonth = '%02d' % (i+1)
    

    The '02' qualifier means, "make it two characters wide, and pad it with zeroes on the left if necessary".

    You might also use "range(1, 13)" instead of "range(12)", then you wouldn't have to add the one every time.

  3. 3. At 7:34 a.m. on 30 mar 2006, Wim Stolker said:

    Alternative: use dateutil from labix.org. This library offers a relativedelta type. Adding relativedelta(day=31) to a date allways gives you the last day of the month that date is in.

    >>> from datetime import *
    >>> from dateutil.relativedelta import *
    >>> datetime(2008,2,3) + relativedelta(day=31)
    datetime.datetime(2008, 2, 29, 0, 0)
    

    You can do way more with this library, if you work a lot with dates it's worth a look.

  4. 4. At 2:07 p.m. on 1 apr 2006, Joe Steffl said:

    Number of days in months for year 2006 --- calendar module. >>> [calendar.monthrange(year,month)[1] for year in [2006] for month in range(1,13)]

    Results in:

    [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

  5. 5. At 6:37 p.m. on 20 mar 2007, Z Liu said:

    it doesn't work. How about today is 2007-12-30? It will give next month as 2007-01-01.

  6. 6. At 5:02 p.m. on 22 jul 2007, Edvald Gislason said:

    A quick fix to that problem. thx for useful code, i added a little hax to make it work for e.g. 2004/12/01:

    def mkLastOfMonth(dtDateTime):
        dYear = dtDateTime.strftime("%Y")
        dMonth = str(int(dtDateTime.strftime("%m"))%12+1)
        dDay = "1"
        #FIX
        if dMonth == '12':
           dYear = str(int(dYear)+1)
        #/FIX
        nextMonth = mkDateTime("%s-%s-%s"%(dYear,dMonth,dDay))
        delta = datetime.timedelta(seconds=1)
        return nextMonth - delta
    
  7. 7. At 5:19 p.m. on 22 jul 2007, Edvald Gislason said:

    Sorry... I ment 1 instead of 12:

    if dMonth == '1':
       dYear = str(int(dYear)+1)
    
  8. 8. At 7:08 p.m. on 8 sep 2008, Noah Tye said:

    For the specific problem of finding the last day of a month, I use:

    def lastday_of_month(d):
        '''Takes a datetime.date and returns the date for the last day in the
        same month.'''
        return datetime.date(d.year, d.month+1, d.day) - datetime.timedelta(1)
    

    ... which is a bit shorter.

  9. 9. At 7:58 a.m. on 16 oct 2008, Denis Barmenkov said:

    All the presented solutions use some sort of hacks: incrementing month without incrementing year, check for month changing "12 -> 01" etc.

    I present slower but simpler solution: we sequentially increment date passed into function until month changes, then get day-of-month from previous value:

    def lastday_of_month(d):
        ''' Denis Barmenkov @ http://code.activestate.com/recipes/476197/ '''
        while 1:
            prev_day = d
            d = d + datetime.timedelta(1)
            #print 'prev_day(%s) next(%s)' % (prev_day, d) # debug print
            if prev_day.month <> d.month:
                return prev_day.day
    
  10. 10. At 8:23 p.m. on 13 dec 2008, John Boyd said:

    Hello ,

    Please forgive me if I have misunderstood the question/issue , I have just started learning Python and programming in general.

    If I wanted to find the last day of any month I would use the calender.monthrange(year,month) function ( as described in python docs):

    "calendar.monthrange(year, month)

    Returns weekday of first day of the month and number of days in month, for the specified year and month."

    This function returns a tuple , element zero [0] is the weekday of the first day , the example below uses February 2004 , the first day was a Saturday, therefore lastday1[0]=6.

    Element one [1] returns the number of days in month. therefore lastday1[1]=29. Note that 2004 was a leapyear.

    For demonstration sake I have used the result in a datetime function. Please see Python interactive session below .

    >>> import calendar,datetime
    >>> lastday1=calendar.monthrange(2004,2)
    >>> print lastday1
    (6, 29)
    >>> lastday2=datetime.date(2004,2,lastday1[1])
    >>> print lastday2
    2004-02-29
    >>>
    

    Link: calendar module

  11. 11. At 8:29 p.m. on 13 dec 2008, John Boyd said:

    Oops !

    Weekday numbers start from 0 with default first day as Monday , therefore a weekday of 6 refers to Sunday not Saturday as described above .

    The first day of February 2004 is a Sunday.

Sign in to comment