Reputation: 2364
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
Upvotes: 18
Views: 32770
Reputation: 41
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')
Upvotes: 3
Reputation: 489
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)
Upvotes: 6
Reputation: 497
Why roll your own?
import pandas as pd
quarter_start = pd.to_datetime(pd.datetime.today() - pd.tseries.offsets.QuarterBegin(startingMonth=1)).date()
Upvotes: 22
Reputation: 1642
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])
Upvotes: 11
Reputation: 414585
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)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()
insteadquarter_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'
Upvotes: 4
Reputation: 249394
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!).
Upvotes: 4