0
\$\begingroup\$

I am provided a bunch of dates in the format (YYYYMMDD) such as:

date='20170503'

My goal is to convert that date into the number of days in the given year. For example, it would convert:

  • 20170130 to 30
  • 20170201 to 32
  • 20151231 to 365

Here is my solution:

import pandas as pd
my_date = pd.to_datetime(date, format='%Y%m%d')
my_ordinal = 736329 - 365*(2017 - my_date.year)
print(datetime.toordinal(my_date) - my_ordinal)

Is there a better way to do this without making it much more complex? Note that pandas is already in the program, so there is no extra burden in using it.

\$\endgroup\$
  • 4
    \$\begingroup\$ I don't think that you can claim that the code works correctly as intended, as required by the help center, if the magic constant for 2017 is hard-coded. \$\endgroup\$ – 200_success Feb 1 '17 at 5:58
  • \$\begingroup\$ My bad, I fixed it \$\endgroup\$ – jss367 Feb 1 '17 at 6:01
  • 4
    \$\begingroup\$ But hard-coding 365 makes it fail after the next leap year. \$\endgroup\$ – 200_success Feb 1 '17 at 6:02
  • 4
    \$\begingroup\$ Also 736329 is a very magic number. \$\endgroup\$ – Der Kommissar Feb 1 '17 at 6:54
  • 2
    \$\begingroup\$ This fails to convert 20151231 to 365, it instead returns 364. \$\endgroup\$ – Peilonrayz Feb 1 '17 at 10:12
6
\$\begingroup\$

As you already use pandas, you are right that there is no overhead on using Timestamps (the kind of objects returned by pd.to_datetime). But, as said in the documentation (help(pd.Timestamp)):

Help on class Timestamp in module pandas.tslib:

class Timestamp(_Timestamp)
 |  TimeStamp is the pandas equivalent of python's Datetime
 |  and is interchangable with it in most cases. It's the type used
 |  for the entries that make up a DatetimeIndex, and other timeseries
 |  oriented data structures in pandas.

So you may choose pd.Timestamp or Python's datetime.datetime interchangeably.

The simplest way to count days from January 1st of the same year is to build both objects and then substract each other to get the count of days separating them. Take care of adding one day to actually count January 1st as well:

date = pd.to_datetime('20170201', format='%Y%m%d')
new_year_day = pd.Timestamp(year=date.year, month=1, day=1)
day_of_the_year = (date - new_year_day).days + 1

Now, having this piece of code hanging around is not really of any use, so you should wrap it into a function for better reusability:

import pandas as pd


def date_to_nth_day(date, format='%Y%m%d'):
    date = pd.to_datetime(date, format=format)
    new_year_day = pd.Timestamp(year=date.year, month=1, day=1)
    return (date - new_year_day).days + 1


if __name__ == '__main__':
    print(date_to_nth_day('20170201'))

Using Python's datetime module, the function would look like:

def date_to_nth_day(date, format='%Y%m%d'):
    date = datetime.datetime.strptime(date, format=format)
    new_year_day = datetime.datetime(year=date.year, month=1, day=1)
    return (date - new_year_day).days + 1
\$\endgroup\$
  • \$\begingroup\$ Thanks for this but the line pd.Timestamp(year=date.year, month=1, day=1) gives me "TypeError: __new__() takes at least 2 positional arguments (1 given)". I am able to give it a single argument as a string, e.g. pd.Timestamp('20170404'), and it works. This is with the latest Python 3 Anaconda distro. \$\endgroup\$ – jss367 Feb 2 '17 at 8:34
  • \$\begingroup\$ In the documentation I see four parameters listed: ts_input : datetime-like, str, int, float | Value to be converted to Timestamp | offset : str, DateOffset | Offset which Timestamp will have | tz : string, pytz.timezone, dateutil.tz.tzfile or None | Time zone for time which Timestamp will have. | unit : string | numpy unit used for conversion, if ts_input is int or float But as I said it accepts a single string, so I'm definitely not understanding the documentation correctly. \$\endgroup\$ – jss367 Feb 2 '17 at 8:37
  • \$\begingroup\$ @jss367 Both pd.Timestamp(year=date.year, month=1, day=1) and pd.Timestamp(date.year, 1, 1) work fine on my machine. Using Python 3.5.2 and pandas 0.19.1. \$\endgroup\$ – 409_Conflict Feb 2 '17 at 9:40
  • \$\begingroup\$ Hmmm, I just copied and pasted it into a Python 3.5.2 console on python.org/shell and got the same error. \$\endgroup\$ – jss367 Feb 2 '17 at 12:24
  • \$\begingroup\$ @jss367 On python.org, pandas' version is 0.18.1, sounds like the datetime-like constructor is pretty new in pandas. You can use pd.to_datetime('{}0101'.format(date.year), format=format) instead; or your pd.Timestamp('{}0101'.format(date.year)) \$\endgroup\$ – 409_Conflict Feb 2 '17 at 16:19
4
\$\begingroup\$

You may want to take a look at

which comes with Python:

import datetime as dt
year = 2017
month = 5
day = 3
days_in_the_year = (dt.date(year, month, day) - dt.date(year,1,1)).days + 1

This gives 123 days.

\$\endgroup\$
  • \$\begingroup\$ Nice answer, but this isn't taking data in the same way that OPs is. Keeping datetime you could use something like the following instead. (datetime.datetime.strptime('20170101', '%Y%m%d') - datetime.datetime(2017, 1, 1)).days + 1 (I also just noticed Mathias has this at the bottom of their answer) \$\endgroup\$ – Peilonrayz Feb 1 '17 at 9:55
  • 1
    \$\begingroup\$ @Peilonrayz I though the part where it trims the string to date is easy, To me, looks like OP is stuck at the date to days in year algorithm(which is already there, in Python). \$\endgroup\$ – Gordon Feb 1 '17 at 10:36

Not the answer you're looking for? Browse other questions tagged or ask your own question.