18

I have strings that show a date in the following format:

x minutes/hours/days/months/years ago

I need to parse that to a datetime using python.

It seems dateutil can't do it.

Is there a way to do it?

applechief
  • 6,615
  • 12
  • 50
  • 70
  • 1
    It looks like `dateutil` can handle it in the format that Lattyware and I prescribed, just substituting `dateutil.relativedelta` for `datetime.timedelta`. (see my update and link). – mgilson Sep 24 '12 at 13:56

9 Answers9

15

Sure you can do it. You just need a timedelta.

s = "3 days ago"
parsed_s = [s.split()[:2]]
time_dict = dict((fmt,float(amount)) for amount,fmt in parsed_s)
dt = datetime.timedelta(**time_dict)
past_time = datetime.datetime.now() - dt

As an aside, it looks like dateutil has a relativedelta which acts like a timedelta, but the constructor also accepts months and years in the arguments (and apparently the arguments need to be integers).

mgilson
  • 300,191
  • 65
  • 633
  • 696
  • relativedelta works better, it doesn't work with a float though, it needs `amount` to be an int. – applechief Sep 24 '12 at 14:06
  • @chaft -- Thanks. I suppose that makes sense. While it's concievable that you can come up with a definition for 3 months ago, it's probably not concievable to say 3.5 months ago and have a unique date. – mgilson Sep 24 '12 at 14:09
13

Easiest way is to use dateparser

import dateparser
date_ago = '4 months ago'
date     = dateparser.parse(date_ago)
date     = date.strftime("%Y-%m-%d")

Output:

'2020-01-08'

you may need install dateparser pip package

pip install dateparser
Nam G VU
  • 33,193
  • 69
  • 233
  • 372
Youcef
  • 1,103
  • 2
  • 11
  • 26
11

Since your arguments are something like 2 days ago, 3 months ago, 2 years ago. The function below could be of help in getting the exact date for the arguments. You first need to import the following date utils

import datetime
from dateutil.relativedelta import relativedelta

Then implement the function below

def get_past_date(str_days_ago):
    TODAY = datetime.date.today()
    splitted = str_days_ago.split()
    if len(splitted) == 1 and splitted[0].lower() == 'today':
        return str(TODAY.isoformat())
    elif len(splitted) == 1 and splitted[0].lower() == 'yesterday':
        date = TODAY - relativedelta(days=1)
        return str(date.isoformat())
    elif splitted[1].lower() in ['hour', 'hours', 'hr', 'hrs', 'h']:
        date = datetime.datetime.now() - relativedelta(hours=int(splitted[0]))
        return str(date.date().isoformat())
    elif splitted[1].lower() in ['day', 'days', 'd']:
        date = TODAY - relativedelta(days=int(splitted[0]))
        return str(date.isoformat())
    elif splitted[1].lower() in ['wk', 'wks', 'week', 'weeks', 'w']:
        date = TODAY - relativedelta(weeks=int(splitted[0]))
        return str(date.isoformat())
    elif splitted[1].lower() in ['mon', 'mons', 'month', 'months', 'm']:
        date = TODAY - relativedelta(months=int(splitted[0]))
        return str(date.isoformat())
    elif splitted[1].lower() in ['yrs', 'yr', 'years', 'year', 'y']:
        date = TODAY - relativedelta(years=int(splitted[0]))
        return str(date.isoformat())
    else:
        return "Wrong Argument format"

You can then call the function like this:

print get_past_date('5 hours ago')
print get_past_date('yesterday')
print get_past_date('3 days ago')
print get_past_date('4 months ago')
print get_past_date('2 years ago')
print get_past_date('today')
9

This can be done easily with timedeltas:

import datetime

def string_to_delta(string_delta):
    value, unit, _ = string_delta.split()
    return datetime.timedelta(**{unit: float(value)})

Producing:

>>> string_to_delta("20 hours ago")
datetime.timedelta(0, 72000)

Although this will require some extra work to deal with months/years - as adding a month to a date is an ambiguous operation, but it should be a simple addition if you know what you want it to mean.

To get an actual time, simply take the delta away from datetime.datetime.now().

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
  • Your splitting is a bit more elegant than mine. +1. (although I would use `float(n)` -- timedelta seems to handle it OK and it would allow use of `3.5 days ago`.). – mgilson Sep 24 '12 at 13:42
  • @mgilson: Thanks for the suggestion - didn't know ``timedelta``s handled floats. – Gareth Latty Sep 24 '12 at 13:43
  • @chaft Afraid not - ``timedelta``s don't handle months or years as it's relatively ambiguous as to what adding a month to a date means. You will need to handle those as a special case. – Gareth Latty Sep 24 '12 at 13:49
  • 1
    Thanks, it works for minutes hours and days but fails on months and years. – applechief Sep 24 '12 at 13:49
  • As I said, what does adding a month mean? Going to the same date on the next month? If so, what if you are on a day that isn't in the next month? If you are happy with four weeks as a month, then you can just convert a month to that (and a year by extension). – Gareth Latty Sep 24 '12 at 13:51
  • @Lattyware Even without months and years this is great. Thanks. I'll take care of the months and years manually. – applechief Sep 24 '12 at 13:51
  • FYI, this will not handle singular units, like `1 day ago` – Renaud Jul 26 '20 at 19:56
  • 1
    @Renaud You could add special case handling to deal with the singular, although that that point you'd probably want to start using a real parser rather than relying on something a little hack-y like this. – Gareth Latty Jul 26 '20 at 23:20
1

completely exaggerated solution but I needed something more flexible:

def string_to_delta(relative):
    #using simplistic year (no leap months are 30 days long.
    #WARNING: 12 months != 1 year
    unit_mapping = [('mic', 'microseconds', 1),
                    ('millis', 'microseconds', 1000),
                    ('sec', 'seconds', 1),
                    ('day', 'days', 1),
                    ('week', 'days', 7),
                    ('mon', 'days', 30),
                    ('year', 'days', 365)]
    try:
        tokens = relative.lower().split(' ')
        past = False
        if tokens[-1] == 'ago':
            past = True
            tokens =  tokens[:-1]
        elif tokens[0] == 'in':
            tokens = tokens[1:]


        units = dict(days = 0, seconds = 0, microseconds = 0)
        #we should always get pairs, if not we let this die and throw an exception
        while len(tokens) > 0:
            value = tokens.pop(0)
            if value == 'and':    #just skip this token
                continue
            else:
                value = float(value)

            unit = tokens.pop(0)
            for match, time_unit, time_constant in unit_mapping:
                if unit.startswith(match):
                    units[time_unit] += value * time_constant
        return datetime.timedelta(**units), past

    except Exception as e:
        raise ValueError("Don't know how to parse %s: %s" % (relative, e))

This can parse things like:

  • 2 days ago
  • in 60 seconds
  • 2 DAY and 4 Secs
  • in 1 year, 1 Month, 2 days and 4 MICRO
  • 2 Weeks 4 secs ago
  • 7 millis ago

A huge but: It simplifies month and year to 30 and 365 days respectively. Not always what you want, though it's enough for some cases.

estani
  • 24,254
  • 2
  • 93
  • 76
1

@mgilson answer didn't work for me where dateutil.relativedelta did the job:

Edited following @mosc9575 advise

import datetime
from dateutil.relativedelta import relativedelta

time_ago = "1 month ago"
val, unit = time_ago.split()[:2]
past_time = datetime.datetime.now() - relativedelta(**{unit:int(val)})
asiera
  • 492
  • 5
  • 12
  • This works fine but I think your if statement is unnecessary (as far as I was testing). Maybe you can edit this to a function like Gareth Latty did. – mosc9575 Jan 22 '21 at 18:28
  • That's correct! I didn't realise relativedelta accept both argument month=3 & months=3 – asiera Mar 24 '21 at 10:26
0

Custom function to convert x hours ago to datetime, x hour, y mins ago to datetime, etc in Python.

Function takes single parameter of type string which is parsed using RegExp. RegExp can be customized to match function input.

For usage see examples below.

import re
from datetime import datetime, timedelta


def convert_datetime(datetime_ago):
    matches = re.search(r"(\d+ weeks?,? )?(\d+ days?,? )?(\d+ hours?,? )?(\d+ mins?,? )?(\d+ secs? )?ago", datetime_ago)

    if not matches:
        return None

    date_pieces = {'week': 0, 'day': 0, 'hour': 0, 'min': 0, 'sec': 0}

    for i in range(1, len(date_pieces) + 1):
        if matches.group(i):
            value_unit = matches.group(i).rstrip(', ')
            if len(value_unit.split()) == 2:
                value, unit = value_unit.split()
                date_pieces[unit.rstrip('s')] = int(value)

    d = datetime.today() - timedelta(
        weeks=date_pieces['week'],
        days=date_pieces['day'],
        hours=date_pieces['hour'],
        minutes=date_pieces['min'],
        seconds=date_pieces['sec']
    )

    return d

Example usage:

dates = [
    '1 week, 6 days, 11 hours, 20 mins, 13 secs ago',
    '1 week, 10 hours ago',
    '1 week, 1 day ago',
    '6 days, 11 hours, 20 mins ago',
    '1 hour ago',
    '11 hours, 20 mins ago',
    '20 mins 10 secs ago',
    '10 secs ago',
    '1 sec ago',
]    

for date in dates:
    print(convert_datetime(date))

Output:

2019-05-10 06:26:40.937027
2019-05-16 07:46:53.937027
2019-05-15 17:46:53.937027
2019-05-17 06:26:53.937027
2019-05-23 16:46:53.937027
2019-05-23 06:26:53.937027
2019-05-23 17:26:43.937027
2019-05-23 17:46:43.937027
2019-05-23 17:46:52.937027
Hamza Rashid
  • 1,329
  • 15
  • 22
0

make sure to install dependencies using pip3

from datetime import date
from dateutil.relativedelta import relativedelta
import re

baseDate = date.today() #date(2020, 4, 29)

hoursPattern = re.compile(r'(\d\d?\d?) hours? ago')
daysPattern = re.compile(r'(\d\d?\d?) days? ago')
weeksPattern = re.compile(r'(\d\d?\d?) weeks? ago')
monthsPattern = re.compile(r'(\d\d?\d?) months? ago')
yearsPattern = re.compile(r'(\d\d?\d?) years? ago')


days = 0
daysMatch = daysPattern.search(ago)
if daysMatch:
    days += int(daysMatch.group(1))

hours = 0
hoursMatch = hoursPattern.search(ago)
if hoursMatch:
    hours += int(hoursMatch.group(1))

weeks = 0
weeksMatch = weeksPattern.search(ago)
if weeksMatch:
    weeks += int(weeksMatch.group(1))

months = 0
monthsMatch = monthsPattern.search(ago)
if monthsMatch:
    months += int(monthsMatch.group(1))

years = 0
yearsMatch = yearsPattern.search(ago)
if yearsMatch:
    years += int(yearsMatch.group(1))

yourDate = baseDate - relativedelta(hours=hours, days=days, weeks=weeks, months=months, years=years)
kamayd
  • 420
  • 6
  • 10
0

Any chance for datetime to work with other languages (italian) 'x-days ago to date'?

import locale\
locale.setlocale(locale.LC_ALL,'it_IT.UTF-8') 

doesn't make any difference.

Gab
  • 51
  • 1
  • 5