Python get first and last day of current calendar quarter

Question:

I’ve built a function to get the first and last day of the current quarter but it’s a bit long winded. I was wondering, is there a more succinct way of accomplishing this?

I understand that pandas has a QuarterBegin() function, but I couldn’t implement it in a more concise way.

import datetime as dt
from dateutil.relativedelta import relativedelta     

def get_q(first=None,last=None):

    today = dt.date.today()

    qmonth = [1, 4, 7, 10]

    if first:
        for i,v in enumerate(qmonth):
            if (today.month-1)//3 == i:
                return dt.date(today.year,qmonth[i],1).strftime("%Y-%m-%d")

    if last:
        firstday = dt.datetime.strptime(get_q(first=True),"%Y-%m-%d") 
        lastday = firstday + relativedelta(months=3, days=-1)
        return lastday.strftime("%Y-%m-%d")

EDIT: Please let me know if this would be better suited to Code Review

Asked By: Charon

||

Answers:

You can do it this way:

import bisect
import datetime as dt

def get_quarter_begin():
    today = dt.date.today()

    qbegins = [dt.date(today.year, month, 1) for month in (1,4,7,10)]

    idx = bisect.bisect(qbegins, today)
    return str(qbegins[idx-1])

This solves the “first” case; I’m leaving the “last” case as an exercise but I suggest keeping it as an independent function for clarity (with your original version it’s pretty strange what happens if no arguments are passed!).

Answered By: John Zwinck
  • avoid Yo-Yo code. Don’t convert a date object to a string only to parse it back into a date object again immediately like if last branch does in the question. Instead, return a date object and convert to string only when necessary (it is easier to access object attributes such as .year, .month than to parse its string representation to extract the same info)
  • avoid mutually exclusive boolean keywords arguments (first, last). It is an error-prone interface and it breeds code duplication. It is easy to return both results here and access corresponding attributes such as .first_day later. Or if there are (unlikely) performance issues; you could create two functions such as get_first_day_of_the_quarter() instead
  • to simplify algorithm, you could add a little redundancy to the input data e.g., see quarter_first_days in the code below (1 is mentioned twice in the list of months)β€”it allow to use i+1 unconditionally:
#!/usr/bin/env python
from collections import namedtuple
from datetime import MINYEAR, date, timedelta

DAY = timedelta(1)
quarter_first_days = [date(MINYEAR+1, month, 1) for month in [1, 4, 7, 10, 1]]    
Quarter = namedtuple('Quarter', 'first_day last_day')

def get_current_quarter():
    today = date.today()
    i = (today.month - 1) // 3 # get quarter index
    days = quarter_first_days[i], quarter_first_days[i+1] - DAY
    return Quarter(*[day.replace(year=today.year) for day in days])

MINYEAR+1 is used to accommodate - DAY expression (it assumes MINYEAR < MAXYEAR). The quarter index formula is from Is there a Python function to determine which quarter of the year a date is in?

Example:

>>> get_current_quarter()
Quarter(first_day=datetime.date(2016, 4, 1), last_day=datetime.date(2016, 6, 30))
>>> str(get_current_quarter().last_day)
'2016-06-30'
Answered By: jfs

Why so complicated πŸ™‚

from datetime import date
from calendar import monthrange

quarter = 2
year = 2016
first_month_of_quarter = 3 * quarter - 2
last_month_of_quarter = 3 * quarter
date_of_first_day_of_quarter = date(year, first_month_of_quarter, 1)
date_of_last_day_of_quarter = date(year, last_month_of_quarter, monthrange(year, last_month_of_quarter)[1])
Answered By: oxidworks

Why roll your own?

import pandas as pd

quarter_start = pd.to_datetime(pd.datetime.today() - pd.tseries.offsets.QuarterBegin(startingMonth=1)).date()
Answered By: John Redford

You shouldn’t need to use unnecessary loops or large libraries like pandas to do this. You can do it with simple integer division / arithmetic and just the datetime library (although using dateutil results in cleaner code).

import datetime

def getQuarterStart(dt=datetime.date.today()):
    return datetime.date(dt.year, (dt.month - 1) // 3 * 3 + 1, 1)

# using just datetime
def getQuarterEnd1(dt=datetime.date.today()):
    nextQtYr = dt.year + (1 if dt.month>9 else 0)
    nextQtFirstMo = (dt.month - 1) // 3 * 3 + 4
    nextQtFirstMo = 1 if nextQtFirstMo==13 else nextQtFirstMo
    nextQtFirstDy = datetime.date(nextQtYr, nextQtFirstMo, 1)
    return nextQtFirstDy - datetime.timedelta(days=1)

# using dateutil
from dateutil.relativedelta import relativedelta

def getQuarterEnd2(dt=datetime.date.today()):
    quarterStart = getQuarterStart(dt)
    return quarterStart + relativedelta(months=3, days=-1)

Output:

>>> d1=datetime.date(2017,2,15)
>>> d2=datetime.date(2017,1,1)
>>> d3=datetime.date(2017,10,1)
>>> d4=datetime.date(2017,12,31)
>>> 
>>> getQuarterStart(d1)
datetime.date(2017, 1, 1)
>>> getQuarterStart(d2)
datetime.date(2017, 1, 1)
>>> getQuarterStart(d3)
datetime.date(2017, 10, 1)
>>> getQuarterStart(d4)
datetime.date(2017, 10, 1)
>>> getQuarterEnd1(d1)
datetime.date(2017, 3, 31)
>>> getQuarterEnd1(d2)
datetime.date(2017, 3, 31)
>>> getQuarterEnd1(d3)
datetime.date(2017, 12, 31)
>>> getQuarterEnd1(d4)
datetime.date(2017, 12, 31)
>>> getQuarterEnd2(d1)
datetime.date(2017, 3, 31)
>>> getQuarterEnd2(d2)
datetime.date(2017, 3, 31)
>>> getQuarterEnd2(d3)
datetime.date(2017, 12, 31)
>>> getQuarterEnd2(d4)
datetime.date(2017, 12, 31)
Answered By: Matthew Strax-Haber

Use Pandas:

import pandas as pd
# current quarter start
pd.date_range(end=pd.Timestamp.now(), periods=1, freq='QS')

# current quarter end
pd.date_range(start=pd.Timestamp.now(), periods=1, freq='Q')
Answered By: Weiwei
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.