Aitor Martin
Aitor Martin

Reputation: 734

Python get days/weeks/months ahead

I've been searching through stackoverflow for the answer but I haven't been able to find what I'm looking for in Python and in a Pythonic way.

I'm trying to get the number of days, weeks or months ahead based on two dates. Here is a little script I created which does what I want to do but I'm concerned about it.

import datetime
from dateutil.relativedelta import relativedelta


now = datetime.datetime.now()
days_ahead = datetime.datetime.now() + relativedelta(days=3)
weeks_ahead = datetime.datetime.now() + relativedelta(weeks=2)
month_ahead = datetime.datetime.now() + relativedelta(months=1)
months_ahead = datetime.datetime.now() + relativedelta(months=3)


def get_relative_date(dt):

    ahead = (dt - now).days

    if ahead < 7:
        return "Due in " + str(ahead) + " days"

    elif ahead < 31:
        return "Due in " + str(ahead/7) + " weeks"

    else:
        return "Due in " + str(ahead/30) + " months"

print get_relative_date(days_ahead)
print get_relative_date(weeks_ahead)
print get_relative_date(month_ahead)
print get_relative_date(months_ahead)

The result is the following:

Due in 3 days
Due in 2 weeks
Due in 1 months
Due in 3 months

Despite being a good answer my concerns are related to:

Thanks in advance. If the question was answered please link me to the post and I'll read it carefully. I'm willing to give more information if required.

Edit

I include here my complete updated code for anyone who required too this functionality in Python. It also takes care of negative day values and Today.

def relative_date(dt):

    if dt is not None and len(dt) > 0:

        now = datetime.now()
        then = arrow.get(dt).naive

        rd = relativedelta(then, now)
        if rd.years or rd.months:
            months = 12 * rd.years + rd.months

            if months < 0:
                if months == -1:
                    return "Due 1 month ago"

                return "Due %i months ago" % -months

            if months == 1:
                return "Due in 1 month"
            return "Due in %d months" % months

        elif rd.days > 7 or rd.days < -7:
            weeks = rd.days / 7

            if weeks < 0:
                if weeks == -1:
                    return "Due 1 week ago"
                return "Due %i weeks ago" % -weeks

            if weeks == 1:
                return "Due in 1 week"
            return "Due in %d weeks" % weeks

        else:

            if rd.days == 0:
                return "Due Today"

            elif rd.days < 0:
                if rd.days == -1:
                    return "Due 1 day ago"
                return "Due %i days ago" % -rd.days

            elif rd.days == 1:
                return "Due in 1 day"

            return "Due in %d days" % rd.days

    else:
        return ""

Upvotes: 4

Views: 3384

Answers (1)

Alasdair
Alasdair

Reputation: 308899

Yes, your current code an issue because not all months have 31 days. In practice, you might decide that it's not too important if it says "Due in 2 months" when it's actually due in 1 month and 28 days. After all, the rounding down means you are showing "Due in 2 months" when it's due in 2 months and 28 days.

Since you are using the dateutil module already, note that you can use relativedelta the other way around as well (see the examples page).

If you instantiate a relativedelta with two date objects, it returns a relativedelta object with year, month and day attributes.

>>> relativedelta(date(2015, 7, 20), date(2014, 6, 10))
relativedelta(years=+1, months=+1, days=+10)

You could use this in your method as follows:

from dateutil.relativedelta import relativedelta

def get_relative_date(dt):

    rd = relativedelta(dt, now)
    if rd.years or rd.months:
        months = 12 * rd.years + rd.months
        return "Due in %d months" % months
    elif rd.days > 7:
        weeks = rd.days / 7
        return "Due in %d weeks" % weeks
    else:
        return "Due in %d days" % rd.days

Upvotes: 6

Related Questions