I'm trying to add a month to a np.datetime64:
np.datetime64('2020-01-05') + np.timedelta64(1, "M")
but this error raises:
Cannot cast ufunc 'subtract' input 1 from dtype('<m8[M]') to dtype('<m8[us]') with casting rule 'same_kind'
If I try with days or weeks, it works:
np.datetime64('2020-01-05') + np.timedelta64(1, "D")
numpy.datetime64('2020-01-06')
I read from numpy documentation that adding days and months is different (https://numpy.org/doc/stable/reference/arrays.datetime.html#datetime-and-timedelta-arithmetic), but they didn't give a solution.
NB: I'm trying NOT to use pandas or datetime, so I would like to avoid any casting
The below code works but I'm uncertain how future proof it is. I suspect converting to datetime or using pandas may in the long run be easier and more robust.
Extracting the months is from Anon's answer to getting year and month
As @pawan-jain said there are problems defining adding or subtracting months from dates. What is 2021-05-31 plus one month? 2021-06-31 isn't a date.
The code below maps
2020-01-31 to 2020-03-02 Adds 31 days to a January date
2020-02-01 to 2020-03-01 Adds 29 days to a February 2020 date
Check this meets the definition of add month that is required.
import numpy as np
DAYS_IN_MONTH = np.array( [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ])
FEB = 1
def is_leap( yy ):
""" Returns boolean True for leap_years. """
accum = np.logical_and( ( yy % 4 == 0 ), ( yy % 100 != 0 )) # century starts aren't leap
return ( accum | (( yy % 400 ) == 0 )) # except every 400 years
def plus_day( yy, mm ):
""" Returns one for Februaries in leap years.
yy is an array of years as ints
mm is an array of months as ints, zero based, Jan = 0, Feb = 1 ..
"""
return ( mm == FEB ) * is_leap( yy ).astype( np.int8 )
def days_in_month( dates ):
""" Returns the days in the month for the np.datetime64 array dates."""
month = dates.astype('datetime64[M]').astype(int)
year, month = np.divmod( month, 12 ) # Month zero based, Jan = 0...
year += 1970
return DAYS_IN_MONTH[ month ] + plus_day( year, month )
dates = np.arange(np.datetime64('2020-01-01'), np.datetime64('2021-01-01'))
new_dates = dates + days_in_month( dates )
for date, ndate in zip( dates[ 20: 80 ], new_dates[ 20: 80 ]):
print( date, ndate, end = ' : ' )
# 2020-01-21 2020-02-21 : 2020-01-22 2020-02-22 : 2020-01-23 2020-02-23 :
# 2020-01-24 2020-02-24 : 2020-01-25 2020-02-25 : 2020-01-26 2020-02-26 :
# 2020-01-27 2020-02-27 : 2020-01-28 2020-02-28 : 2020-01-29 2020-02-29 :
# 2020-01-30 2020-03-01 : 2020-01-31 2020-03-02 : 2020-02-01 2020-03-01 :
# 2020-02-02 2020-03-02 : 2020-02-03 2020-03-03 : 2020-02-04 2020-03-04 :
# 2020-02-05 2020-03-05 : 2020-02-06 2020-03-06 : 2020-02-07 2020-03-07 :
# 2020-02-08 2020-03-08 : 2020-02-09 2020-03-09 : 2020-02-10 2020-03-10 :
# 2020-02-11 2020-03-11 : 2020-02-12 2020-03-12 : 2020-02-13 2020-03-13 :
# 2020-02-14 2020-03-14 : 2020-02-15 2020-03-15 : 2020-02-16 2020-03-16 :
# 2020-02-17 2020-03-17 : 2020-02-18 2020-03-18 : 2020-02-19 2020-03-19 :
# 2020-02-20 2020-03-20 : 2020-02-21 2020-03-21 : 2020-02-22 2020-03-22 :
# 2020-02-23 2020-03-23 : 2020-02-24 2020-03-24 : 2020-02-25 2020-03-25 :
# 2020-02-26 2020-03-26 : 2020-02-27 2020-03-27 : 2020-02-28 2020-03-28 :
# 2020-02-29 2020-03-29 : 2020-03-01 2020-04-01 : 2020-03-02 2020-04-02 :
# 2020-03-03 2020-04-03 : 2020-03-04 2020-04-04 : 2020-03-05 2020-04-05 :
# 2020-03-06 2020-04-06 : 2020-03-07 2020-04-07 : 2020-03-08 2020-04-08 :
# 2020-03-09 2020-04-09 : 2020-03-10 2020-04-10 : 2020-03-11 2020-04-11 :
# 2020-03-12 2020-04-12 : 2020-03-13 2020-04-13 : 2020-03-14 2020-04-14 :
# 2020-03-15 2020-04-15 : 2020-03-16 2020-04-16 : 2020-03-17 2020-04-17 :
# 2020-03-18 2020-04-18 : 2020-03-19 2020-04-19 : 2020-03-20 2020-04-20 :