With the module swisseph (pip install pyswisseph; import swisseph as swe), you can even handle the Julian calendar and dates outside the 1-9999AD range, while in datetime you can only work with the Gregorian calendar and at most four-digit years.
This is slower than datetime though, so if you don't need the Julian calendar or dates outside said range, it's advisable to use datetime. However, the speed is only about five times slower, compared to pandas, which is ~160 times slower! Actually, the below "plain" function, which does not verify that the dates are valid and expects year, month, day to already be in int format, can sometimes be less than 2 times slower than the datetime module.
import swisseph as swe # install with: pip install pyswisseph
CALENDARS = {b'g': swe.GREG_CAL, b'j': swe.JUL_CAL, swe.GREG_CAL: b'g', swe.JUL_CAL: b'j'}
def add_days(year, month, day, delta, calendar=b'g'): # do not verify if date is invalid
cal = CALENDARS[calendar]
start_julday = swe.julday(year, month, day, cal=cal)
end_julday = start_julday + delta
return swe.revjul(end_julday, cal=cal)[:3]
def add_days_verify(year, month, day, delta, calendar=b'g'): # raise error if date is not valid
valid, start_julday, correct_parameters = swe.date_conversion(year, month, day, 12, cal=calendar)
if not valid:
raise ValueError('Invalid date')
end_julday = start_julday + delta
cal = CALENDARS[calendar]
return swe.revjul(end_julday, cal=cal)[:3]
def add_days_amdate(datestr, delta, calendar=b'g', verify=True):
month, day, year = datestr.split('/')
if verify:
nyear, nmonth, nday = add_days_verify(int(year), int(month), int(day), delta, calendar)
else:
nyear, nmonth, nday = add_days(int(year), int(month), int(day), delta, calendar)
#return '{}/{}/{}'.format(nmonth, nday, nyear) # Python < 3.6
return f'{nmonth}/{nday}/{nyear}'
def add_days_plain(year, month, day, delta, cal=swe.GREG_CAL):
start_julday = swe.julday(year, month, day, cal=cal)
end_julday = start_julday + delta
return swe.revjul(end_julday)[:3]
# pass calendar=b'j' for Julian calendar (swe.JUL_CAL for add_days_plain)
# Examples
##>>> add_days_amdate('2/28/1900', 1, b'g')
##'3/1/1900'
##>>> add_days_amdate('2/28/1900', 1, b'j')
##'2/29/1900'
##>>> add_days_amdate('2/29/1900', 1, b'g')
##Traceback (most recent call last): ... Error
##>>> add_days_amdate('2/29/1900', 1, b'g', verify=False)
##'3/2/1900'
##>>> add_days_verify(1900, 2, 28, 1)
##(1900, 3, 1)
##>>> add_days_verify(1900, 2, 28, 1, b'j')
##(1900, 2, 29)
##>>> add_days_verify(1900, 2, 29, 1)
##Traceback (most recent call last): ... Error
##ValueError: Invalid date
##>>> add_days(1900, 2, 29, 1)
##(1900, 3, 2)
##>>> add_days(1900, 2, 29, 1, b'j')
##(1900, 3, 1)
##>>> add_days_verify(30202, 10, 28, 5) # Date after 9999
##(30202, 11, 2)
##>>> add_days_verify(-1, 12, 31, 1) # -1 is 2 BC, 0 is 1 BC, 1 is 1 AD etc.
##(0, 1, 1)
# Testing speed like AlixaProDev
import timeit
def using_swisseph_verify():
return add_days_amdate("10/10/2022", 5)
def using_swisseph():
return add_days_amdate("10/10/2022", 5, verify=False)
def using_swisseph_plain():
return add_days_plain(2022, 10, 10, 5)
for func in [using_swisseph_verify, using_swisseph, using_swisseph_plain]:
print(f"{func.__name__} Time Took: ", timeit.timeit(stmt=func, number=10000))
A last note on speed. When I add this:
def using_swisseph():
start_julday = swe.julday(2022, 10, 10)
end_julday = start_julday + 5
return swe.revjul(end_julday)[:3]
to the end of AlixaProDev's testing script and change the for loop to for func in [using_datetime, using_pd, using_swisseph]:, somethimes swisseph is even faster than datetime, but it varies a lot.
name 'timedelta' is not defined, that means that you haven't definedtimedeltaanywhere. Python is usually pretty informative about its error messages.