Sitemap
Analytics Vidhya

Analytics Vidhya is a community of Generative AI and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

Time (and Date) Freedom in Python

6 min readDec 11, 2020

--

It always seems to be a question of time. When did that happen? How much has happened since then? When will this happen?

But when it comes to reading a python “how to”, dates and time seem like second-class citizens. In books, handling dates and times are buried in the last few chapters. It’s a brief mention like it is something you will never need. By now I am sure you have realised you do need it. Let me take you on a journey so that you can start to experience real time-freedom the python way.

Getting started right away — in case you are wondering what time it is:

import datetime

datetime.datetime.today()

with result: datetime.datetime(2020, 12, 3, 17, 55, 3, 361780)

Which is today 2020–12–03 at 17:55:03. You get the idea.

Don’t worry this isn’t going to be a dry crawl through the subject, method by method. I have a story to lighten the mood.

Let say you are creating a system that produces a monthly financial report and that it does this by calling an external API (maybe it’s AWS‘s Cost Explorer). You have to create a class to produce the required time inputs based on a specified year and month. I am thinking you might need to do the following:

  • determine the start and end date of the specified month
  • determine the fiscal year of the specified month
  • determine the start and end date of the fiscal year
  • determine the number of months between the specified month and the start of the fiscal year (if you want to create some monthly averages)

Let’s tackle the first use case.

Start and End Date of Specified Month

You are going to want to create a class that returns the start and end date for a specified month. From a test perspective, I am going to assume the “datetime” type is return as it is more flexible if we want to add additional functionality to the class. You can see the “datetime” format above. So the test could look like this:

test_value1=YearMonth('202011').start_monthdate
test_value2=YearMonth('202011').end_monthdate

assert test_value1==datetime.datetime(2020,11,1,0,0,0,0),\
'FAIL create start date'
assert test_value2==datetime.datetime(2020,11,30,0,0,0,0),\
'FAIL create end date'

print(f'PASSED start and end date specified month\
{test_value1, test_value2}')

I am using Jupyter Notebooks so print is more convenient than logging the result. I have assumed the month is identified with a string in a “YYYYMM” format.

To get the end date of the month we can use the “monthrange” method in the calendar module. If provided with the year and month, it returns the first working day of the month and the number of days in a month. The second value is obviously what we need. Also, I am in the habit of using the “parse” method from the dateutil package ( $ pip install dateutil ) to parse the start date. The day of the start date is obviously known (“01”). The parse method is more forgiving than some other approaches you could take.

Here’s the class:

import calendar
from dateutil import parser as p

class YearMonth:
def __init__(self,year_month):

self.year_month=year_month

year=year_month[0:4]
month=year_month[4:]
# convert year, month to date for first day of month
self.start_monthdate=p.parse(year_month+'01')
# convert year, month to date for last day of month
self.end_monthdate=p.parse(year_month+str(calendar.\
monthrange(2020,int(month))[1]))

return

You could play around with a “monthrange” method a bit just to get to know it. As mentioned the first value it returns is the first working day of the month, with Monday being 0.

Fiscal year, It’s Start Date and It’s End Date

Moving swiftly on — not time to waste. There are three things to test but you might want to check out the data types before you write the tests. There is a package that is going to come in very handy — “fiscalyear” ( $ pip install fiscalyear). It has a class “FiscalDate” with an attribute “fiscal_year” which is going to be helpful. There is also a class “FiscalYear” with attributes “.start” and “.end”. Full marks for clear naming to the creators of this package.

To explore these classes a little bit before writing a test:

# explore date types required to create tests
import datetime
import fiscalyear

fiscalyear.START_MONTH = 7

# create exploratory datetime based on today
td=datetime.datetime.today()

# create exploratory fiscal year
fy=fiscalyear.FiscalDate(td.year,td.month,td.day).fiscal_year
# fiscal year and type
print(fy, type(fy))

gives us back: 2021 <class ‘int’>

It is necessary to define the month the fiscal year starts in by setting “START_MONTH”, in our case 7 for July.

If you want to explore the start date of the fiscal year you could try adding to the above code with:

# create exploratory start date for determined fiscal year
fy_startdate=fiscalyear.FiscalYear(fy).start.date()
# start date of fiscal year and type
print(fy_startdate, type(fy_startdate))

which will give you back: 2020–07–01 <class ‘datetime.date’>

Now you know which types to use in your tests. So your tests could look something like this:

test_value1=YearMonth2('202011').fiscal_year
test_value2=YearMonth2('202011').fiscal_startdate
test_value3=YearMonth2('202011').fiscal_enddate
assert test_value1==2021, 'FAIL return fiscal year'
assert test_value2==datetime.date(2020,7,1)
assert test_value3==datetime.date(2021,6,30)
print(f'PASSED fiscal year, start date, end date \
{test_value1, test_value2, test_value3}')

So we have pretty much got most of what we need from the exploratory process. The class could look like this:

# class
import calendar
from dateutil import parser as p
import fiscalyear
fiscalyear.START_MONTH = 7class YearMonth2:
def __init__(self,year_month):

self.year_month=year_month

year=year_month[0:4]
month=year_month[4:]
# convert year, month to date for first day of month
self.start_monthdate=p.parse(year_month+'01')
# convert year, month to date for last day of month
self.end_monthdate=p.parse(year_month+\
str(calendar.monthrange(2020,int(month))[1]))

# determine fiscal year
self.fiscal_year=fiscalyear.FiscalDate\
(self.start_monthdate.year,
self.start_monthdate.month,
self.start_monthdate.day).fiscal_year
# determine fiscal year start date
self.fiscal_startdate=fiscalyear.FiscalYear\
(self.fiscal_year).start.date()
self.fiscal_enddate=fiscalyear.FiscalYear\
(self.fiscal_year).end.date()
return

I called it YearMonth2 so that you don’t overwrite the first version of the class. You can see that I define the fiscal year for the “FiscalDate” class as a “datetime”. This is something we discover in the exploratory phase.

Months Between the Current Month and the Start or the Financial Year

There is a class in the “dateutil” package called “relativedelta”. It accepts two dates and gives you the difference between them in it’s own data type “relativedelta”. To explore this a little, I suggest something like this:

#explore
from dateutil.relativedelta import relativedelta
diff=relativedelta(datetime.date(2020,11,30),\
datetime.date(2020,7,1))
print(diff, type(diff))

This will return:

relativedelta(months=+4, days=+29)

<class ‘dateutil.relativedelta.relativedelta’>

Not exactly what we are looking for since the answer we want is five months. But we have enough to write a test at this stage, so here is what I suggest:

# test
test_value=YearMonth3('202011').months_between
assert test_value==5,\
'FAIL MonthsBetween specified month and start of financial year'
print(f'PASSED fiscal year, start date, end date {test_value}')

I have named the class YearMonth3 so we don’t have to overwrite previous ones.

With this class, we can take one of two approaches to deal with the fact we don’t get five months back. We can use the start date of the specified month and add one month or we can add one day to the end date of the specified month i.e. the first day of the next month. Just to give us some extra practice I have opted for the latter so the class could look like this:

import calendar
from dateutil import parser as p
from dateutil.relativedelta import relativedelta
class YearMonth3:
def __init__(self,year_month):
self.year_month=year_month
year=year_month[0:4]
month=year_month[4:]
# convert year, month to date for first day of month
self.start_monthdate=p.parse(year_month+'01')
# convert year, month to date for last day of month
self.end_monthdate=p.parse(year_month+str(\
calendar.monthrange(2020,int(month))[1]))
# determine fiscal year
self.fiscal_year=fiscalyear.FiscalDate(\
self.start_monthdate.year,
self.start_monthdate.month,
self.start_monthdate.day).fiscal_year
# determine fiscal year start date
self.fiscal_startdate=fiscalyear.FiscalYear(\
self.fiscal_year).start.date()
self.fiscal_enddate=fiscalyear.FiscalYear(\
self.fiscal_year).end.date()
#convert year, month to first day of the following month
self.end_p1_monthdate=self.end_monthdate+\
relativedelta(days=1)
# months between spefcified month and start of fiscal year
self.months_between = relativedelta(\
self.end_p1_monthdate,
self.fiscal_startdate).months
return

You can see from this that “relativedelta” can be used to add an increment of time to a date, in this case, 1 day to our already defined date.

Conclusion

I hope you enjoyed our journey through time. I really do think examples of working with time-based data deserve more prominence and hope my contribution is seen as meaningful.

--

--

Analytics Vidhya
Analytics Vidhya

Published in Analytics Vidhya

Analytics Vidhya is a community of Generative AI and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

No responses yet