Python difference between two dates - python

For some reason I'm really stumped with this (relativity) question.
How to calculate the difference between two dates. I want to do this without using modules. But for some reason my code isn't outputting the correct answer.
This is my thought process:
If asked calculate the # of days between Dec 10th 2014 and Feb 2nd 2015.
First find the number of days left in Dec from the 10th on (31 - 10)
= 21 days
Find the number of months between Dec and Feb ( aka Jan) add the
number days in that month = 31 days
Add the Days left in Dec (21) + the days in between the months (31) + the days in the last month (2) = 54 days.
Then check for anomalies ie Leap Year etc.
This is my function:
def Calculate_Date (year1, month1, day1, year2, month2, day2):
"""
This function takes to dates (year/month/day) and returned the
difference between the dates
"""
#Create a dict for the # of days in each month
month_days = {1:31,2:28,3:31,4:30,5:31,6:30,7:31,8:31,9:30,10:31,11:30,12:31}
days_left_in_month1 = month_days[month1] - day1
days_left_in_year1 =0
days_into_year2 =0
days_between_year1_and_year2= 0
difference_in_days = 0
# Find the number days left in year one
i = month1
days_left_in_year = []
while i <= 12:
days = month_days[i]
days_left_in_year.append(days)
i = i + 1
days_left_in_year1 = (sum(days_left_in_year)) - day1
# Find the number days into year two
i = 1
days_into_year = []
while i <= month2:
days = month_days[i]
days_into_year.append(days)
i = i + 1
days_into_year2 = sum(days_into_year) - day2
#find the differernce in years
days_between_year1_and_year2 = (year2 - year1) * 365
#Check if its a leap year
leap_year = False
while True:
if float(year1 % 4) == 0:
if float(year1 % 100) != 0:
leap_year = True
break
if float(year1 % 100) == 0:
if float(year1 % 400) ==0:
leap_year = True
break
else:
break
#test output
print "The number of days left in the year One are %r " % days_left_in_year1
print "The number of days into the year Two are %r " % days_into_year2
print "The number of days between the years are %r " % days_between_year1_and_year2
#add an increment if leap year was true
if leap_year == True:
difference_in_days = days_left_in_year1 + days_into_year2 + days_between_year1_and_year2 + 1
else:
difference_in_days = days_left_in_year1 + days_into_year2 + days_between_year1_and_year2
return difference_in_days
print Calculate_Date(2011,6,30,2012,06,30)

Instead of doing date2 - date1, you might find it simpler to do (date2 - x) - (date1 - x) where x is an easy-to-handle date, ie "Jan 0" of year1.
Let's define a couple of functions:
def days_in_month(year, month):
"""
Return number of days in the specified month (28, 29, 30, or 31)
"""
if month == 2: # February
if not (year % 400):
return 29
elif not (year % 100):
return 28
elif not (year % 4):
return 29
else:
return 28
elif month in {1, 3, 5, 7, 8, 10, 12}:
return 31
else:
return 30
def days_in_year(year):
"""
Return the number of days in the specified year (365 or 366)
"""
return 337 + days_in_month(year, 2)
def days_this_year(year, month, day):
"""
Return the number of days so far this year
"""
return sum(days_in_month(year, m) for m in range(1, month)) + day
def year_days_since(base_year, this_year):
"""
Return the number of days from the start of base_year to the start of this_year
"""
if base_year > this_year:
raise ValueError("base_year must be <= this_year")
elif base_year == this_year:
return 0
else:
return sum(days_in_year(y) for y in range(base_year, this_year))
then the difference between two dates becomes:
def date_diff(y1, m1, d1, y2, m2, d2):
x = min(y1, y2) # base date
days1 = year_days_since(x, y1) + days_this_year(y1, m1, d1)
days2 = year_days_since(x, y2) + days_this_year(y2, m2, d2)
return days2 - days1
and because of the symmetry in this answer it will also happily do negative differences:
date_diff(2001, 1, 3, 2002, 2, 5) # => 398 == 365 + 31 + 2
date_diff(2002, 2, 5, 2001, 1, 3) # => -398

In case this is a real code, and not a school assignment, this is the way I'd do it:
from datetime import date
def date_diff(y1, m1, d1, y2, m2, d2):
return (date(y2, m2, d2) - date(y1, m1, d1)).days

Related

Calculation of business working hour in python

I would like to write a function that calculate working business hours in python, to do that I don't like to define a class and use python ready function to calculate.
I tried with following code but the code is not working well. I need to modify the code and change it for the hour instead of minutes too.
Do you have any suggestion?
def getminutes(datetime1,datetime2,worktiming=[9, 17]):
day_hours = (worktiming[1]-worktiming[0])
day_minutes = day_hours * 60 # minutes in a work day
weekends=[6, 7]
# Set initial default variables
dt_start = datetime1.datetime # datetime of start
dt_end = datetime2.datetime # datetime of end
worktime_in_seconds = 0
if dt_start.date() == dt_end.date():
# starts and ends on same workday
full_days = 0
if dt_start in [6, 7]:
return 0
else:
if dt_start.hour < worktiming[0]:
# set start time to opening hour
dt_start = datetime.datetime(
year=dt_start.year,
month=dt_start.month,
day=dt_start.day,
hour=worktiming[0],
minute=0)
if dt_start.hour >= worktiming[1] or \
dt_end.hour < worktiming[0]:
return 0
if dt_end.hour >= worktiming[1]:
dt_end = datetime.datetime(
year=dt_end.year,
month=dt_end.month,
day=dt_end.day,
hour=worktiming[1],
minute=0)
worktime_in_seconds = (dt_end-dt_start).total_seconds()
elif (dt_end-dt_start).days < 0:
# ends before start
return 0
else:
# start and ends on different days
current_day = dt_start # marker for counting workdays
while not current_day.date() == dt_end.date():
if not is_weekend(current_day):
if current_day == dt_start:
# increment hours of first day
if current_day.hour < worktiming[0]:
# starts before the work day
worktime_in_seconds += day_minutes*60 # add 1 full work day
elif current_day.hour >= worktiming[1]:
pass # no time on first day
else:
# starts during the working day
dt_currentday_close = datetime.datetime(
year=dt_start.year,
month=dt_start.month,
day=dt_start.day,
hour= worktiming[1],
minute=0)
worktime_in_seconds += (dt_currentday_close
- dt_start).total_seconds()
else:
# increment one full day
worktime_in_seconds += day_minutes*60
current_day += datetime.timedelta(days=1) # next day
# Time on the last day
if not is_weekend(dt_end):
if dt_end.hour >= worktiming[1]: # finish after close
# Add a full day
worktime_in_seconds += day_minutes*60
elif dt_end.hour < worktiming[0]: # close before opening
pass # no time added
else:
# Add time since opening
dt_end_open = datetime.datetime(
year=dt_end.year,
month=dt_end.month,
day=dt_end.day,
hour=worktiming[0],
minute=0)
worktime_in_seconds += (dt_end-dt_end_open).total_seconds()
return int(worktime_in_seconds / 60)
How can I modify the code that works with the following input ?
getminutes(2019-12-02 09:30:00,2019-12-07 12:15:00,worktiming=[9, 17])
You can use pd.bdate_range(datetime1, datetime2) to compute the number of working days. When converting worktiming to a pandas datetime, it is easy to compute the difference (in seconds) between the two datetimes:
import pandas as pd
datetime1 = "2019-12-02 09:30:00"
datetime2 = "2019-12-07 12:15:00"
def getminutes(datetime1, datetime2, worktiming=[9, 17]):
d1 = pd.to_datetime(datetime1)
d2 = pd.to_datetime(datetime2)
wd = pd.bdate_range(d1, d2) # working days
day_hours = (worktiming[1] - worktiming[0])
day_minutes = day_hours * 60 # minutes in a work day
day_seconds = day_minutes * 60 # seconds in a work day
full_days = len(wd)
day1 = datetime1[:10]
day2 = datetime2[:10]
dt1 = pd.to_datetime(day1 + " " + str(worktiming[0]) + ":00")
dt2 = pd.to_datetime(day2 + " " + str(worktiming[1]) + ":00")
ex1, ex2 = 0, 0
if day1 in wd:
ex1 = max(pd.Timedelta(d1 - dt1).seconds, 0)
if day2 in wd:
ex2 = max(pd.Timedelta(dt2 - d2).seconds, 0)
total_seconds = full_days * day_seconds - ex1 - ex2
total_minutes = total_seconds / 60
total_hours = total_minutes / 60
return int(total_minutes)
print(getminutes(datetime1, datetime2))
Output: 2370

Years and months output not formatting properly

When I try to implement this function of no_payment() in my python script:
import math
import argparse
parse = argparse.ArgumentParser(usage='Differential calculator that calculates the per month payment on a decreasing'
'principal amount')
parse.add_argument('--type', '-t', type=str, required=True, help='diff = differential, annuity = standard, fixed payments') #error code
# cant compute with diff argument selected, as payment each month is different
parse.add_argument('--payment', '-p', type=float, required=False, help='monthly payment amount')
parse.add_argument('--principal', '-P', type=float, required=False, help='principal amount of the loan')
parse.add_argument('--periods', '-m', type=int, required=False, help='number of payments required to pay the loan')
parse.add_argument('--interest', '-i', type=float, required=True, help='interest rate (as integer, not converted)') #error code
args = parse.parse_args()
def no_payments():
i = args.interest / (12 * 100)
month_no = math.log(args.payment / (args.payment - i * args.principal), 1 + i)
overpayment = args.principal * args.interest
year_count = math.ceil(month_no // 12)
month_count = math.ceil(month_no % 12)
if 1 < month_no <= 12:
print(f'It will take {month_count} months to repay this loan!')
elif month_no == 1:
print(f'It will take 1 month to repay this loan!')
elif month_no == 12:
print(f'It will take 1 year to repay this loan!')
elif 12 < month_no < 24 and month_count == 1:
print(f'It will take {year_count} year and 1 month to repay this loan!')
elif 12 < month_no < 24 and month_count > 1:
print(f'It will take {year_count} year and {month_count} months to repay this loan!')
elif month_no >= 24 and month_count == 1:
print(f'It will take {year_count} years and {month_count} month to repay this loan!')
elif month_no >= 24 and month_count > 1:
print(f'It will take {year_count} years and {month_count} months to repay this loan!')
print(f'Overpayment = {overpayment}')
# error codes thrown if interest and type are not inputted
if args.interest is None:
print('Incorrect Parameters')
elif args.type is None:
print('Incorrect Parameters')
if args.type == 'annuity' and args.payment is None:
ann_calc()
elif args.type == 'diff' and args.payment is None:
diff_calc()
elif args.type == 'annuity' and args.principal is None:
princ()
elif args.type == 'annuity' and args.periods is None:
no_payments()
With a given argument of --type=annuity --principal=500000 --payment=23000 --interest=7.8.
The output should be 2 years but it comes out to be 1 year and 12 months. What do I have to change to make the output as 2 years?
on line 8m, your configuration states:
month_no = math.log(args.payment / (args.payment - i * args.principal), 1 + I)
using the arguments you stated in your question, this results in a value of 23.513122662562726
This is the reason why you are receiving 1 year and 12 months in your response, because technically it's 23 months, then converted to 24 months when rounded.
you have two options:
round up to 24 months, if the business logic states
remove the math.ceil, and allow the number of months to be shown as fractions.
This answers your immediate question.
For the long term solution, I would recommend a reducing classification method with your date calculations. For example:
month_no = 24 (for your example)
years = math.floor(month_no / 12). -> 2
months = (month_no - (years * 12)) -> 24 - (2 * 12) -> 0
another example:
month_no = 26 (for your example)
years = math.floor(month_no / 12). -> 2
months = (month_no - (years * 12)) -> 26 - (2 * 12) -> 2
from there you can use string injection to push your print statement
yr = '{} years'.format(years) if years > 1 else '1 year' if years = 1 else ''
mo = 'and {} months'.format(months) if months > 1 else 'and 1 month' if months = 1 else ''
print('It will take {years}{months} to repay this loan!'.format(years,months))
here's my example:
import math
import argparse
#--type=annuity --principal=500000 --payment=23000 --interest=7.8
def no_payments(args):
i = args['interest'] / (12 * 100)
month_no = round(math.log(args['payment'] / (args['payment'] - i * args['principal']), 1 + i))
overpayment = args['principal'] * args['interest']
year_count = math.ceil(month_no // 12)
month_count = math.ceil(month_no % 12)
print("month no")
print(month_no, month_count, math.ceil(24 % 12))
years = math.floor(month_no / 12) #-> 2
months = (month_no - (years * 12)) # -> 26 - (2 * 12) #-> 2
yr = '{} years'.format(years) if years > 1 else '1 year' if years == 1 else ''
mo = 'and {} months'.format(months) if months > 1 else 'and 1 month' if months == 1 else ''
print('It will take {}{} to repay this loan!'.format(yr,mo))
args = {
'type': 'annuity',
'principal': 500000,
'payment': 23000,
'interest': 7.8
}
no_payments(args)

Concatenating url pages as a single Data Frame

I'm trying to download historic weather data for a given Location.
I have altered an example given at flowingdata but I've stuck in the last step - how to concate multiple Data Frames
MWE:
import pandas as pd
frames = pd.DataFrame(columns=['TimeEET', 'TemperatureC', 'Dew PointC', 'Humidity','Sea Level PressurehPa',
'VisibilityKm', 'Wind Direction', 'Wind SpeedKm/h','Gust SpeedKm/h','Precipitationmm',
'Events','Conditions', 'WindDirDegrees', 'DateUTC<br />'])
# Iterate through year, month, and day
for y in range(2006, 2007):
for m in range(1, 13):
for d in range(1, 32):
# Check if leap year
if y%400 == 0:
leap = True
elif y%100 == 0:
leap = False
elif y%4 == 0:
leap = True
else:
leap = False
#Check if already gone through month
if (m == 2 and leap and d > 29):
continue
elif (m == 2 and d > 28):
continue
elif (m in [4, 6, 9, 10] and d > 30):
continue
# Open wunderground.com url
url = "http://www.wunderground.com/history/airport/EFHK/"+str(y)+ "/" + str(m) + "/" + str(d) + "/DailyHistory.html?req_city=Vantaa&req_state=&req_statename=Finlandia&reqdb.zip=00000&reqdb.magic=4&reqdb.wmo=02974&format=1"
df=pd.read_csv(url, sep=',',skiprows=2)
frames=pd.concat(df)
This gives an error:
first argument must be an iterable of pandas objects, you passed an object of type "DataFrame"
The desired output would be to have one Data Frame with all days,month and years.
You should declare a list outside your loop and append to this then outside the loop you want to concatenate all the dfs into a single df:
import pandas as pd
frames = pd.DataFrame(columns=['TimeEET', 'TemperatureC', 'Dew PointC', 'Humidity','Sea Level PressurehPa',
'VisibilityKm', 'Wind Direction', 'Wind SpeedKm/h','Gust SpeedKm/h','Precipitationmm',
'Events','Conditions', 'WindDirDegrees', 'DateUTC<br />'])
# Iterate through year, month, and day
df_list = []
for y in range(2006, 2007):
for m in range(1, 13):
for d in range(1, 32):
# Check if leap year
if y%400 == 0:
leap = True
elif y%100 == 0:
leap = False
elif y%4 == 0:
leap = True
else:
leap = False
#Check if already gone through month
if (m == 2 and leap and d > 29):
continue
elif (m == 2 and d > 28):
continue
elif (m in [4, 6, 9, 10] and d > 30):
continue
# Open wunderground.com url
url = "http://www.wunderground.com/history/airport/EFHK/"+str(y)+ "/" + str(m) + "/" + str(d) + "/DailyHistory.html?req_city=Vantaa&req_state=&req_statename=Finlandia&reqdb.zip=00000&reqdb.magic=4&reqdb.wmo=02974&format=1"
df=pd.read_csv(url, sep=',',skiprows=2)
df_list.append(df)
frames=pd.concat(df_list, ignore_index=True)

Converting fractional days to H:M:S.MS - Two examples

Of the two methods of calculating a fractional day to local time, which one would you consider the best way and why?
Edit: 'Fractional day' means here the decimal part of a Julian day jd: jd - (math.floor(jd - 0.5) + 0.5) (this is because 0:00:00 is at jd.5)
#classmethod
def fromfractional(cls, frac, **kwargs):
changed = False
f = lambda x: decimal.dec(floor(x))
if not isinstance(frac, decimal.Decimal):
frac = decimal.dec(frac)
hours = decimal.dec(D24 * (frac - f(frac)))
if hours < 1:
hours += 1 # Or else microseconds won't be calculated correctly
changed = True
minutes = decimal.dec(D60 * (hours - f(hours)))
seconds = decimal.dec(D60 * (minutes - f(minutes)))
ms = decimal.dec(DKS * (seconds - f(seconds)))
if changed:
hours -= 1
return int(hours), int(minutes), int(seconds), int(ms)
#classmethod
def fromfractional2(cls, x):
d = lambda x: decimal.Decimal(str(x))
total = d(x) * d(86400000000000)
hours = (total - (total % d(3600000000000))) / d(3600000000000)
total = total % d(3600000000000)
minutes = (total - (total % d(60000000000))) / d(60000000000)
total = total % d(60000000000)
seconds = (total - (total % d(1000000000))) / d(1000000000)
total = total % d(1000000000)
ms = (total - (total % d(1000000))) / d(1000000)
total = total % d(1000000)
mics = (total - (total % d(1000))) / d(1000)
return int(hours), int(minutes), int(seconds), int(ms)
D24 = decimal.Decimal('24')
DMS = decimal.Decimal('86400000.0')
D60 = decimal.Decimal('60')
D3600 = decimal.Decimal('3600')
D1440=decimal.Decimal('1400')
DKS=decimal.Decimal('1000')
DTS=decimal.Decimal('86400')
I think you are trying to get from something like:
1.2256 days
To:
1 day, 5 hours, 24 minutes, 51 seconds
but with microseconds, too?
Here's how I generated the above response:
def nice_repr(timedelta, display="long"):
"""
Turns a datetime.timedelta object into a nice string repr.
display can be "minimal", "short" or "long" [default].
>>> from datetime import timedelta as td
>>> nice_repr(td(days=1, hours=2, minutes=3, seconds=4))
'1 day, 2 hours, 3 minutes, 4 seconds'
>>> nice_repr(td(days=1, seconds=1), "minimal")
'1d, 1s'
"""
assert isinstance(timedelta, datetime.timedelta), "First argument must be a timedelta."
result = ""
weeks = timedelta.days / 7
days = timedelta.days % 7
hours = timedelta.seconds / 3600
minutes = (timedelta.seconds % 3600) / 60
seconds = timedelta.seconds % 60
if display == 'minimal':
words = ["w", "d", "h", "m", "s"]
elif display == 'short':
words = [" wks", " days", " hrs", " min", " sec"]
else:
words = [" weeks", " days", " hours", " minutes", " seconds"]
values = [weeks, days, hours, minutes, seconds]
for i in range(len(values)):
if values[i]:
if values[i] == 1 and len(words[i]) > 1:
result += "%i%s, " % (values[i], words[i].rstrip('s'))
else:
result += "%i%s, " % (values[i], words[i])
return result[:-2]

User-friendly time format in Python?

Python: I need to show file modification times in the "1 day ago", "two hours ago", format.
Is there something ready to do that? It should be in English.
The code was originally published on a blog post "Python Pretty Date function" (http://evaisse.com/post/93417709/python-pretty-date-function)
It is reproduced here as the blog account has been suspended and the page is no longer available.
def pretty_date(time=False):
"""
Get a datetime object or a int() Epoch timestamp and return a
pretty string like 'an hour ago', 'Yesterday', '3 months ago',
'just now', etc
"""
from datetime import datetime
now = datetime.now()
if type(time) is int:
diff = now - datetime.fromtimestamp(time)
elif isinstance(time, datetime):
diff = now - time
elif not time:
diff = 0
second_diff = diff.seconds
day_diff = diff.days
if day_diff < 0:
return ''
if day_diff == 0:
if second_diff < 10:
return "just now"
if second_diff < 60:
return str(second_diff) + " seconds ago"
if second_diff < 120:
return "a minute ago"
if second_diff < 3600:
return str(second_diff // 60) + " minutes ago"
if second_diff < 7200:
return "an hour ago"
if second_diff < 86400:
return str(second_diff // 3600) + " hours ago"
if day_diff == 1:
return "Yesterday"
if day_diff < 7:
return str(day_diff) + " days ago"
if day_diff < 31:
return str(day_diff // 7) + " weeks ago"
if day_diff < 365:
return str(day_diff // 30) + " months ago"
return str(day_diff // 365) + " years ago"
If you happen to be using Django, then new in version 1.4 is the naturaltime template filter.
To use it, first add 'django.contrib.humanize' to your INSTALLED_APPS setting in settings.py, and {% load humanize %} into the template you're using the filter in.
Then, in your template, if you have a datetime variable my_date, you can print its distance from the present by using {{ my_date|naturaltime }}, which will be rendered as something like 4 minutes ago.
Other new things in Django 1.4.
Documentation for naturaltime and other filters in the django.contrib.humanize set.
In looking for the same thing with the additional requirement that it handle future dates, I found this:
http://pypi.python.org/pypi/py-pretty/1
Example code (from site):
from datetime import datetime, timedelta
now = datetime.now()
hrago = now - timedelta(hours=1)
yesterday = now - timedelta(days=1)
tomorrow = now + timedelta(days=1)
dayafter = now + timedelta(days=2)
import pretty
print pretty.date(now) # 'now'
print pretty.date(hrago) # 'an hour ago'
print pretty.date(hrago, short=True) # '1h ago'
print pretty.date(hrago, asdays=True) # 'today'
print pretty.date(yesterday, short=True) # 'yest'
print pretty.date(tomorrow) # 'tomorrow'
You can also do that with arrow package
From github page:
>>> import arrow
>>> utc = arrow.utcnow()
>>> utc = utc.shift(hours=-1)
>>> utc.humanize()
'an hour ago'
There is humanize package:
>>> from datetime import datetime, timedelta
>>> import humanize # $ pip install humanize
>>> humanize.naturaltime(datetime.now() - timedelta(days=1))
'a day ago'
>>> humanize.naturaltime(datetime.now() - timedelta(hours=2))
'2 hours ago'
It supports localization l10n, internationalization i18n:
>>> _ = humanize.i18n.activate('ru_RU')
>>> print humanize.naturaltime(datetime.now() - timedelta(days=1))
день назад
>>> print humanize.naturaltime(datetime.now() - timedelta(hours=2))
2 часа назад
The answer Jed Smith linked to is good, and I used it for a year or so, but I think it could be improved in a few ways:
It's nice to be able to define each time unit in terms of the preceding unit, instead of having "magic" constants like 3600, 86400, etc. sprinkled throughout the code.
After much use, I find I don't want to go to the next unit quite so eagerly. Example: both 7 days and 13 days will show as "1 week"; I'd rather see "7 days" or "13 days" instead.
Here's what I came up with:
def PrettyRelativeTime(time_diff_secs):
# Each tuple in the sequence gives the name of a unit, and the number of
# previous units which go into it.
weeks_per_month = 365.242 / 12 / 7
intervals = [('minute', 60), ('hour', 60), ('day', 24), ('week', 7),
('month', weeks_per_month), ('year', 12)]
unit, number = 'second', abs(time_diff_secs)
for new_unit, ratio in intervals:
new_number = float(number) / ratio
# If the new number is too small, don't go to the next unit.
if new_number < 2:
break
unit, number = new_unit, new_number
shown_num = int(number)
return '{} {}'.format(shown_num, unit + ('' if shown_num == 1 else 's'))
Notice how every tuple in intervals is easy to interpret and check: a 'minute' is 60 seconds; an 'hour' is 60 minutes; etc. The only fudge is setting weeks_per_month to its average value; given the application, that should be fine. (And note that it's clear at a glance that the last three constants multiply out to 365.242, the number of days per year.)
One downside to my function is that it doesn't do anything outside the "## units" pattern: "Yesterday", "just now", etc. are right out. Then again, the original poster didn't ask for these fancy terms, so I prefer my function for its succinctness and the readability of its numerical constants. :)
The ago package provides this. Call human on a datetime object to get a human readable description of the difference.
from ago import human
from datetime import datetime
from datetime import timedelta
ts = datetime.now() - timedelta(days=1, hours=5)
print(human(ts))
# 1 day, 5 hours ago
print(human(ts, precision=1))
# 1 day ago
Using datetime objects with tzinfo:
def time_elapsed(etime):
# need to add tzinfo to datetime.utcnow
now = datetime.utcnow().replace(tzinfo=etime.tzinfo)
opened_for = (now - etime).total_seconds()
names = ["seconds","minutes","hours","days","weeks","months"]
modulos = [ 1,60,3600,3600*24,3600*24*7,3660*24*30]
values = []
for m in modulos[::-1]:
values.append(int(opened_for / m))
opened_for -= values[-1]*m
pretty = []
for i,nm in enumerate(names[::-1]):
if values[i]!=0:
pretty.append("%i %s" % (values[i],nm))
return " ".join(pretty)
I have written a detailed blog post for the solution on http://sunilarora.org/17329071
I am posting a quick snippet here as well.
from datetime import datetime
from dateutil.relativedelta import relativedelta
def get_fancy_time(d, display_full_version = False):
"""Returns a user friendly date format
d: some datetime instace in the past
display_second_unit: True/False
"""
#some helpers lambda's
plural = lambda x: 's' if x > 1 else ''
singular = lambda x: x[:-1]
#convert pluran (years) --> to singular (year)
display_unit = lambda unit, name: '%s %s%s'%(unit, name, plural(unit)) if unit > 0 else ''
#time units we are interested in descending order of significance
tm_units = ['years', 'months', 'days', 'hours', 'minutes', 'seconds']
rdelta = relativedelta(datetime.utcnow(), d) #capture the date difference
for idx, tm_unit in enumerate(tm_units):
first_unit_val = getattr(rdelta, tm_unit)
if first_unit_val > 0:
primary_unit = display_unit(first_unit_val, singular(tm_unit))
if display_full_version and idx < len(tm_units)-1:
next_unit = tm_units[idx + 1]
second_unit_val = getattr(rdelta, next_unit)
if second_unit_val > 0:
secondary_unit = display_unit(second_unit_val, singular(next_unit))
return primary_unit + ', ' + secondary_unit
return primary_unit
return None
DAY_INCREMENTS = [
[365, "year"],
[30, "month"],
[7, "week"],
[1, "day"],
]
SECOND_INCREMENTS = [
[3600, "hour"],
[60, "minute"],
[1, "second"],
]
def time_ago(dt):
diff = datetime.now() - dt # use timezone.now() or equivalent if `dt` is timezone aware
if diff.days < 0:
return "in the future?!?"
for increment, label in DAY_INCREMENTS:
if diff.days >= increment:
increment_diff = int(diff.days / increment)
return str(increment_diff) + " " + label + plural(increment_diff) + " ago"
for increment, label in SECOND_INCREMENTS:
if diff.seconds >= increment:
increment_diff = int(diff.seconds / increment)
return str(increment_diff) + " " + label + plural(increment_diff) + " ago"
return "just now"
def plural(num):
if num != 1:
return "s"
return ""
This is the gist of #sunil 's post
>>> from datetime import datetime
>>> from dateutil.relativedelta import relativedelta
>>> then = datetime(2003, 9, 17, 20, 54, 47, 282310)
>>> relativedelta(then, datetime.now())
relativedelta(years=-11, months=-3, days=-9, hours=-18, minutes=-17, seconds=-8, microseconds=+912664)
You can download and install from below link. It should be more helpful for you. It has been providing user friendly message from second to year.
It's well tested.
https://github.com/nareshchaudhary37/timestamp_content
Below steps to install into your virtual env.
git clone https://github.com/nareshchaudhary37/timestamp_content
cd timestamp-content
python setup.py
Here is an updated answer based on Jed Smith's implementation that properly hands both offset-naive and offset-aware datetimes. You can also give a default timezones. Python 3.5+.
import datetime
def pretty_date(time=None, default_timezone=datetime.timezone.utc):
"""
Get a datetime object or a int() Epoch timestamp and return a
pretty string like 'an hour ago', 'Yesterday', '3 months ago',
'just now', etc
"""
# Assumes all timezone naive dates are UTC
if time.tzinfo is None or time.tzinfo.utcoffset(time) is None:
if default_timezone:
time = time.replace(tzinfo=default_timezone)
now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
if type(time) is int:
diff = now - datetime.fromtimestamp(time)
elif isinstance(time, datetime.datetime):
diff = now - time
elif not time:
diff = now - now
second_diff = diff.seconds
day_diff = diff.days
if day_diff < 0:
return ''
if day_diff == 0:
if second_diff < 10:
return "just now"
if second_diff < 60:
return str(second_diff) + " seconds ago"
if second_diff < 120:
return "a minute ago"
if second_diff < 3600:
return str(second_diff / 60) + " minutes ago"
if second_diff < 7200:
return "an hour ago"
if second_diff < 86400:
return str(second_diff / 3600) + " hours ago"
if day_diff == 1:
return "Yesterday"
if day_diff < 7:
return str(day_diff) + " days ago"
if day_diff < 31:
return str(day_diff / 7) + " weeks ago"
if day_diff < 365:
return str(day_diff / 30) + " months ago"
return str(day_diff / 365) + " years ago"
I've been dragging and tweaking this code from programming language to programming language for so long, I don't remember where I originally got it from. It served me well in PHP, Java, and TypeScript, and now it's time for Python.
It handles past and future dates, as well as edge cases.
def unix_time() -> int:
return int(time.time())
def pretty_time(t: int, absolute=False) -> str:
if not type(t) is int:
return "N/A"
if t == 0:
return "Never"
now = unix_time()
if t == now:
return "Now"
periods = ["second", "minute", "hour", "day", "week", "month", "year", "decade"]
lengths = [60, 60, 24, 7, 4.35, 12, 10]
diff = now - t
if absolute:
suffix = ""
else:
if diff >= 0:
suffix = "ago"
else:
diff *= -1
suffix = "remaining"
i = 0
while diff >= lengths[i] and i < len(lengths) - 1:
diff /= lengths[i]
i += 1
diff = round(diff)
if diff > 1:
periods[i] += "s"
return "{0} {1} {2}".format(diff, periods[i], suffix)
def time_ago(self):
start_time = self.date # The start date
now_time = datetime.now()
difference = int((now_time - start_time).total_seconds())
second = [1, 'seconds']
minute = [60, 'minutes']
hour = [60 * minute[0], 'hours']
day = [24 * hour[0], 'days']
week = [7 * day[0], 'weeks']
month = [4 * week[0], 'months']
year = [12 * month[0], 'years']
times = [year, month, week, day, hour, minute, second]
for time in times:
if difference >= time[0]:
time_ago = int(difference / time[0])
if time_ago <= 1:
timeframe = time[1].rstrip('s')
else:
timeframe = time[1]
time_item = str(time_ago) + ' ' + timeframe
return time_item
return 'Date Error'

Categories

Resources