Years and months output not formatting properly - python

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)

Related

Netezza In-Built AGE function as UDF in Redshift

I'm trying to Implement Netezza AGE function in Redshift as a UDF. I can able to get the correct answer in Python (Spyder IDE - Py 3.6) but when I execute it in Redshift as UDF, it gives me incorrect output.
I've tried to execute as select AGE_UDF('1994-04-04 20:10:52','2018-09-24 11:31:05'); in Redshift.
Here is the code used in RS UDF.
CREATE OR REPLACE FUNCTION AGE_UDF (START_DATE TIMESTAMP, END_DATE TIMESTAMP)
RETURNS varchar(100)
stable
AS $$
from datetime import datetime
from dateutil import relativedelta
START_DATE = datetime.strptime(START_DATE, '%Y-%m-%d %H:%M:%S')
END_DATE = datetime.strptime(END_DATE, '%Y-%m-%d %H:%M:%S')
difference = relativedelta.relativedelta(END_DATE, START_DATE)
years = difference.years
months = difference.months
days = difference.days
hours = difference.hours
minutes = difference.minutes
seconds = difference.seconds
age=''
if years == 0:
age=''
elif years == 1:
age+=str(years)+' year '
else:
age+=str(years)+' years '
if months == 0:
age+=''
elif months == 1:
age+=str(months)+' mon '
else:
age+=str(months)+' mons '
if days == 0:
age+=''
elif days == 1:
age+=str(days)+' day '
else:
age+=str(days)+' days '
age+=str(hours)+':'+str(minutes)+':'+str(seconds)
return age
$$ language plpythonu;
Output in RS: -8809.15:20:13
Here is the Code used in Python (3.6).
from datetime import datetime
from dateutil import relativedelta
START_DATE = '1994-04-04 20:10:52'
START_DATE = datetime.strptime(START_DATE, '%Y-%m-%d %H:%M:%S')
END_DATE = '2018-09-24 11:31:05'
END_DATE = datetime.strptime(END_DATE, '%Y-%m-%d %H:%M:%S')
difference = relativedelta.relativedelta(END_DATE, START_DATE)
years = difference.years
months = difference.months
days = difference.days
hours = difference.hours
minutes = difference.minutes
seconds = difference.seconds
age=''
if years == 0:
age=''
elif years == 1:
age+=str(years)+' year '
else:
age+=str(years)+' years '
if months == 0:
age+=''
elif months == 1:
age+=str(months)+' mon '
else:
age+=str(months)+' mons '
if days == 0:
age+=''
elif days == 1:
age+=str(days)+' day '
else:
age+=str(days)+' days '
age+=str(hours)+':'+str(minutes)+':'+str(seconds)
print(age)
Output in Python: 24 years 5 mons 19 days 15:20:13
EDIT:
I found the way to achieve the Netezza functionality and I've pasted here.
Still I'm Expecting an another efficient way !!! Cheers !!!
Thanks for the Support and Suggestions !!!
No Python needed. Here's a SQL UDF that encapsulates the logic. You will need to extend it if the units plural is important to you (mons vs mon).
/*
Postgres AGE() Function
*/
CREATE OR REPLACE FUNCTION f_postgres_age(TIMESTAMP, TIMESTAMP)
RETURNS VARCHAR(64)
STABLE AS $$
-- Input: '1994-04-04 20:10:52', '2018-09-24 11:31:05'
-- Output: 24 years 5 mons 19 days 15:20:13
-- Input: '1994-10-04 20:10:52', '2019-06-12 11:31:05'
-- Output: 24 years 8 mons 7 days 15:20:13
-- Check: SELECT '1994-10-04 20:10:52'::TIMESTAMP
-- + INTERVAL '24 years' + INTERVAL '8 months' + INTERVAL '7 days'
-- + INTERVAL '15 hours' + INTERVAL '20 minutes' + INTERVAL '13 seconds';
-- Result: 2019-06-12 11:31:05
SELECT CASE WHEN DATEDIFF(year, DATE_TRUNC('year', $1)
, DATE_TRUNC('year', CASE WHEN DATEPART(month, $1) > DATEPART(month, $2)
THEN $2 - INTERVAL '1 Year' ELSE $2 END)) > 0
THEN DATEDIFF(year, DATE_TRUNC('year', $1)
, DATE_TRUNC('year', CASE WHEN DATEPART(month, $1) > DATEPART(month, $2)
THEN $2 - INTERVAL '1 Year' ELSE $2 END)) || ' years '
ELSE '' END
|| CASE WHEN ABS( DATEDIFF(month, DATE_TRUNC('month', $1), DATE_TRUNC('month', $2))
- DATEDIFF(month, DATE_TRUNC('year', $1)
, DATE_TRUNC('year', CASE WHEN DATEPART(month, $1) > DATEPART(month, $2)
THEN $2 - INTERVAL '1 Year' ELSE $2 END))) > 0
THEN DATEDIFF(month, DATE_TRUNC('month', $1), DATE_TRUNC('month', $2))
- DATEDIFF(month, DATE_TRUNC('year', $1)
, DATE_TRUNC('year', CASE WHEN DATEPART(month, $1) > DATEPART(month, $2)
THEN $2 - INTERVAL '1 Year' ELSE $2 END)) || ' mons '
ELSE '' END
|| CASE WHEN ABS( DATEDIFF(day, DATE_TRUNC('day', $1)+1, DATE_TRUNC('day', $2))
- DATEDIFF(day, DATE_TRUNC('month', $1), DATE_TRUNC('month', $2))) > 0
THEN DATEDIFF(day, DATE_TRUNC('day', $1)+1, DATE_TRUNC('day', $2))
- DATEDIFF(day, DATE_TRUNC('month', $1), DATE_TRUNC('month', $2)) || ' days '
ELSE '' END
|| TO_CHAR((TIMESTAMP 'epoch'
+ ( DATEDIFF(second, $1, DATE_TRUNC('day', $1)+1 )
+ DATEDIFF(second, DATE_TRUNC('day', $2), $2) )
* INTERVAL '1 Second '),'HH24:MI:SS') age
$$ LANGUAGE SQL
;
I Found the way to get the output as same as Netezza ! And we need to create 4 Different UDF with different Inputs ! Here I've added the UDF for (TIMESTAMP, TIMESTAMP)
create or replace function AGE_UDF_V2 (START_DATE TIMESTAMP, END_DATE TIMESTAMP)
returns VARCHAR
stable
as $$
# -*- coding: utf-8 -*-
"""
Created on Wed Sep 26 12:59:24 2018
#author: pnataraj
"""
from dateutil import relativedelta
from dateutil.parser import parse
if (START_DATE is None or END_DATE is None):
return None
else:
START_DATE = str(START_DATE).strip()
END_DATE = str(END_DATE).strip()
START_DATE = parse(START_DATE)
END_DATE = parse(END_DATE)
difference = relativedelta.relativedelta(START_DATE, END_DATE)
years = difference.years
months = difference.months
days = difference.days
hours = difference.hours
minutes = difference.minutes
seconds = difference.seconds
age=''
if years != 0:
if years == 1 or years == -1:
age+=str(years)+' year '
else:
age+=str(years)+' years '
if months != 0:
if months == 1 or months == -1:
age+=str(months)+' mon '
else:
age+=str(months)+' mons '
if days != 0:
if days == 1 or days == -1:
age+=str(days)+' day '
else:
age+=str(days)+' days '
if (hours !=0 or minutes !=0 or seconds != 0):
if (hours < 0 or minutes < 0 or seconds < 0):
age+=str("-"+format(abs(hours),"02")+":"+format(abs(minutes),"02")+":"+format(abs(seconds),"02"))
else:
age+=str(format(hours,"02")+":"+format(minutes,"02")+":"+format(seconds,"02"))
elif(hours == 0 and minutes ==0 and seconds == 0):
if len(age)>0:
age = age
else:
age = "00:00:00"
return age.strip()
$$ language plpythonu;
Thanks for all the suggestions and Helps ! Hope It'll be helpful for those who're doing Nz to AWS RS Migration !

Python difference between two dates

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

how to calculate the fixed monthly payment of credit card

How do I continue the following loop until I find mmp = 310? So far, with my code the furthest I can go is mmp = 240. Do you think I should have one more if statement?
balance = 4213
annualInterestRate = 0.2
mir = annualInterestRate/12
monthlyPaymentRate = 0.04
rb = balance
mmp = 0
Month = 1
while Month <= 12:
print('Month:' + str(Month))
mmp = mmp + 10
print('Minimum monthly payment:' + str(mmp))
ub = rb - mmp
rb = round(ub + (annualInterestRate/12 * ub), 2)
Month = Month + 1
print('Remaining balance:' + str(rb))
if rb > 0:
rb = balance
Month = 1
while Month <= 12:
print('Month:' + str(Month))
mmp = mmp + 10
print('Minimum monthly payment:' + str(mmp))
ub = rb - mmp
rb = round(ub + (annualInterestRate/12 * ub), 2)
Month = Month + 1
print('Remaining balance:' + str(rb))
else:
print('Lowest Payment:' + str(mmp)
Instead of while Month <= 12:, you could use while mmp < 310: if you just want to reach that number. Or while rb > 0: if you want to continue looping until everything is paid.
If looping over the months is a requirement (btw, it's generally appreciated here if you mention that your question is homework), you could add an outer loop for years:
year = 1
while rb > 0:
month = 1
while month <= 12:
# do stuff
month = month + 1
year = year + 1

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