2

There are many posts about finding the difference between two dates but the values involved start and end with different formatting than those I am using eg:

a = 01/01/10 # dd/mm/yy format
b = 01/01/05 # dd/mm/yy format

So I am after the difference in years, months and days between a and b where the output required is in the format x years, x months, x days (if required) format.

I'm reading the datetime documentation and have had a crack at figuring it out (note: admittedly newbie code ahead, i was trying to piece together all the demo's there so had to make a few modifications):

from datetime import datetime as dt

# calculate duration between two dates

# later date
later_date = '01/01/10'.replace('/', '')
# reverse the order
later_date = "".join(reversed([later_datet[i:i+2] for i in range(0, len(later_date), 2)]))
# separate with commas every two numbers
later_date = ','.join(later_date[i:i+2] for i in range(0, len(later_date), 2))
# convert to datetime object
later_date = dt.strptime(later_date, "%y,%m,%d")

# earlier date
earlier_date = '01/01/05'.replace('/','')
# reverse the order
earlier_date = "".join(reversed([earlier_date[i:i+2] for i in range(0, len(earlier_date), 2)]))
# separate with commas every two numbers
earlier_date = ','.join(earlier_date[i:i+2] for i in range(0, len(earlier_date), 2))
# convert to datetime object
earlier_date = dt.strptime(earlier_date, "%y,%m,%d")

duration = later date - earlier_date

print duration
print type(duration)

is outputting:

1826 days, 0:00:00
<type 'datetime.timedelta'>

So I think i am somewhat close to getting the correct data, but now i need to convert it into the x years, x months, x days (if required) format.

Edit/Solution:

I have put some code together and am testing now, I think it is working for all date combinations but if anyone notices a bug please let me know:

"""

this code calculates the duration between two dates (a later and earlier date)
in the format dd/mm/yy and returns the duration in years, months and days with
correct formatting in regards to the plurality of the year/s, month/s, and day/s
and the punctuation required dependent on whether one or more values are returned
ie multiple values are separated by ',' whereas a singular value is terminated by '.'.

"""

# imported libraries
from datetime import datetime as dt
from dateutil import relativedelta
import sys

# initial date objects
later_date = '01/01/10'
earlier_date = '01/01/05'

# convert dates to required format 
a_date = dt.strptime(later_date, '%d/%m/%y')
b_date = dt.strptime(earlier_date, '%d/%m/%y')

# get duration using dateutil
duration = relativedelta.relativedelta(a_date, b_date)

# check if number of years is not false ie != 0
if duration.years != 0:
    years = duration.years
else:
    years = False

# check if number of months is not false ie != 0
if duration.months != 0:
    months = duration.months
else:
    months = False

# check if number of days is not false ie != 0
if duration.days != 0:
    days = duration.days
else:
    days = False

# add values to a list
date_list = [years,months,days]

# count instances of False in the list
false_count = date_list.count(False)

# iterate over list with enumeration performing value and
# boolean checking to predicate plurality and punctuality
# requirements.

for n, _ in enumerate(date_list):
    # year/s - single or plural, lone value or more
    if _ != False and n == 0:
        single_year = date_list[0] == 1
        # if single and not lone
        if single_year == True and false_count != 2:
            sys.stdout.write(str(_)+' year, ')
        # if single and lone
        elif single_year == True and false_count == 2:
            sys.stdout.write(str(_)+' year.')
        # if not single and not lone
        elif single_year == False and false_count != 2:
            sys.stdout.write(str(_)+' years, ')
        # if not single but lone
        elif single_year == False and false_count == 2:
            sys.stdout.write(str(_)+' years.')
    # if there are no years, still provide value for possible later concatenation
    if _ == False and n == 0:
        datasetduration_y = ''
    # month/s - single or plural, lone value or more
    if _ != False and n == 1:
        single_month = date_list[1] == 1
        # if single and not lone
        if single_month == True and false_count != 2:
            sys.stdout.write(str(_)+' month, ')
        # if single and lone
        elif single_month == True and false_count == 2:
            sys.stdout.write(str(_)+' month.')
        # if not single and not lone and there are days
        elif single_month == False and false_count != 2 and date_list[2] != False:
            sys.stdout.write(str(_)+' months, ')
        # if not single and not lone and there are no days
        elif single_month == False and false_count != 2 and date_list[2] == False:
            sys.stdout.write(str(_)+' months.')
        # if not single but lone
        elif single_month == False and false_count == 2:
            sys.stdout.write(str(_)+' months.')
    # if there are no months, still provide value for possible later concatenation
    if _ == False and n == 1:
        datasetduration_m = ''
    # day/s - single or plural, lone value or more
    if _ != False and n == 2:
        single_day = date_list[2] == 1
        # if single and not lone
        if single_day == True and false_count != 2:
            sys.stdout.write(str(_)+' day.')
        # if single and lone
        elif single_day == True and false_count == 2:
            sys.stdout.write(str(_)+' day.')
        # if not single and not lone
        elif single_day == False and false_count != 2:
            sys.stdout.write(str(_)+' days.')
        # if not single but lone
        elif single_day == False and false_count == 2:
            sys.stdout.write(str(_)+' days.')
    # if there are no days, still provide value for possible later concatenation
    if _ == False and n == 2:
        datasetduration_d = ''
user1063287
  • 10,265
  • 25
  • 122
  • 218
  • Why not parse them directly from their source formats? – Ignacio Vazquez-Abrams May 26 '13 at 10:34
  • And you are going *way* overboard with the parsing. Use `dt.strptime(later_date, %y/%m/%d').date()` without the replacing and splitting. – Martijn Pieters May 26 '13 at 10:38
  • the linked post above outputs to month and years, i was after years, months and days (if required). – user1063287 May 26 '13 at 10:47
  • @user1063287: The solution is still the same; the only thing left is calculating the remaining days. For my answer, that just means using another `divmod()` call instead of ``\\`` to get months and days, for the relative delta accepted answer, just use the `.days` attribute. Extrapolating that extra step is not that hard, is it? – Martijn Pieters May 26 '13 at 10:50

3 Answers3

0

Well, here we go. This is not a datetime-related solution. But, I think this should still get you what you are you asking...

Number of years: 1826/365. See how many whole years have passed.

Number of months: (1826%365)/30. Of the remaining days, how many months. (Here, we are ignoring the specific month lengths (Jan=31, Feb=28, etc) and just using 30days/month).

Number of days: (1826%365)%30. Of the remaining days, how many days.

0

This should do the trick

>>> from datetime import datetime as dt
>>> a = '01/01/2010'
>>> b = '01/01/2005'
>>> a_date = dt.strptime(a, '%d/%m/%Y') # Use capital Y for year with century
>>> b_date = dt.strptime(b, '%d/%m/%Y')
>>> td = a_date - b_date
>>> td
datetime.timedelta(1826)

You now have a timedelta object that contains the number of days between a and b.

It's not quite clear what it means to express this in years, months and days, since none of "year" or "month" or "day" are a standardized unit of time - years and months have a variable number of days, and days have a variable number of seconds. In this case I think it is best to leave the difference expressed in days, which you can get by doing

>>> td.days
1826

If you really want to express the number of days, years and months, then you could operate as follows

>>> nyears = a_date.year - b_date.year
>>> nmonths = a_date.month - b_data.month
>>> ndays = a_date.day - b_data.day

Which gives you the difference in years, days and months, subject to the proviso that they can be negative. There are a few options for dealing with that. One way is to allow negative differences in number of days, months and years (which makes perfect mathematical sense). Another option is to adjust them:

>>> if ndays < 0:
        ndays = ndays + 30
        nmonths = nmonths - 1
>>> if nmonths < 0:
        nmonths = nmonths + 12
        nyears = nyears - 1
Chris Taylor
  • 46,912
  • 15
  • 110
  • 154
  • This doesn't give the answer the OP is looking for; display the delta as years - months - days. – Martijn Pieters May 26 '13 at 10:49
  • This returns the same result as I had originally (although greatly cutting down on original code for the first part). – user1063287 May 26 '13 at 10:52
  • See the duplicate vote link; we make a pretty good attempt there to show how to at least approximate years, months and days there. And the number of seconds in a day is irrelevant here, the OP starts with dates, not datetimes. – Martijn Pieters May 26 '13 at 10:52
0

A trivial answer:

years, remainder = divmod(duration.days, 365)
months, days = divmod(remainder, 30)
print "{} years, {} months {} days".format(years,months,days)
Burhan Khalid
  • 169,990
  • 18
  • 245
  • 284