Welcome, guest | Sign In | My Account | Store | Cart

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, 45 lines
 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)
    

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.

12 comments

Graham Fawcett 18 years ago  # | flag

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.

Graham Fawcett 18 years ago  # | flag

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.

Wim Stolker 18 years ago  # | flag

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.

Joe Steffl 18 years ago  # | flag

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]

Z Liu 17 years ago  # | flag

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

Edvald Gislason 16 years, 8 months ago  # | flag

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
Edvald Gislason 16 years, 8 months ago  # | flag

Sorry... I ment 1 instead of 12:

if dMonth == '1':
   dYear = str(int(dYear)+1)
Noah Tye 15 years, 6 months ago  # | flag

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.

Denis Barmenkov 15 years, 5 months ago  # | flag

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
John Boyd 15 years, 3 months ago  # | flag

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

John Boyd 15 years, 3 months ago  # | flag

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.

Bjorn Madsen 11 years, 8 months ago  # | flag

Let's update this one a little: monthrange(year, month) Returns weekday of first day of the month and number of days in month, for the specified year and month.

>>> import calendar
>>> calendar.monthrange(2000,3)
(2, 31)
>>> calendar.monthrange(2000,3)[1]
31