OPD2019
OPD2019

Reputation: 31

Adding one month to datetime64 with timedelta

When I try this:

>>> a = numpy.datetime64('1995-12-31')
>>> b = a + pandas.Timedelta(1, unit='M')
>>> print(b)

I expect to see

1996-01-31

but instead I get

1996-01-30 10:29:06.

Any idea why? Many thanks.

Upvotes: 3

Views: 17248

Answers (5)

kobalt
kobalt

Reputation: 406

As already mentioned @hpaulj in his answer:

There's an inherent ambiguity in adding a 'month' to a time, since months vary in length.

Moreover, as at version 0.25.0, Pandas dropped support for the use of the units M (months) and Y (years) in Timedelta functions.

But as stated in official pandas guide, you should use Timedelta for absolute time duration and DateOffset for relative time duration that respects calendar arithmetic, which is exactly what we need in your case:

The basic DateOffset acts similar to dateutil.relativedelta (relativedelta documentation) that shifts a date time by the corresponding calendar duration specified.

So, using your example:

In [7]: a = numpy.datetime64('1995-12-31')
      : b = pandas.Timestamp(a) + pandas.DateOffset(months=1)
      : b
Out[7]: Timestamp('1996-01-31 00:00:00')

NB: you could always use to_numpy method, if you need to convert pandas.Timestamp to numpy.datetime64.

Upvotes: 11

Paul Panzer
Paul Panzer

Reputation: 53029

A concise way of adding a month leaving the day unchanged if possible would be truncating to months, adding 1 and then readding what was truncated:

>>> a = np.datetime64('1995-12-31')                                                              
>>> am = a.astype('M8[M]')                                                    
>>> b = (am + 1) + (a - am)                                                        
>>> b                                                                                            
numpy.datetime64('1996-01-31')                                                                   

Obviously, that does not work if the original day does not exist in the next month:

>>> a = np.datetime64('1995-01-31')
>>> am = a.astype('M8[M]')
>>> b = (am + 1) + (a - am)
>>> b
numpy.datetime64('1995-03-03')

But it is unclear what should be the answer in this case, anyway.

One possibility would be to max out at that month's last day:

>>> b = np.minimum((am + 1) + (a - am), (am + 2) - np.timedelta64(1, 'D'))
>>> b
numpy.datetime64('1995-02-28')

Upvotes: 0

hpaulj
hpaulj

Reputation: 231395

There's an inherent ambiguity in adding a 'month' to a time, since months vary in length.

Make a date:

In [247]: a = np.array('1995-12-31','datetime64[D]')                            
In [248]: a                                                                     
Out[248]: array('1995-12-31', dtype='datetime64[D]')

adding days to that works fine:

In [249]: a + np.array(31, 'timedelta64[D]')                                    
Out[249]: numpy.datetime64('1996-01-31')

Adding a month raises an error:

In [250]: a + np.array(1, 'timedelta64[M]')                                     
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-250-a331f724d7e7> in <module>
----> 1 a + np.array(1, 'timedelta64[M]')

TypeError: Cannot get a common metadata divisor for NumPy datetime metadata [D] and [M] because they have incompatible nonlinear base time units

We could cast a as a month - then it works:

In [251]: a.astype('datetime64[M]')                                             
Out[251]: array('1995-12', dtype='datetime64[M]')
In [252]: a.astype('datetime64[M]') + np.array(1, 'timedelta64[M]')             
Out[252]: numpy.datetime64('1996-01')

Change the month in the corresponding datetime object may be cleanest way to work with this:

In [254]: b = a.item()                                                          
In [255]: b                                                                     
Out[255]: datetime.date(1995, 12, 31)

I haven't worked enough with datetime objects to make the change without looking at its docs.

Upvotes: 1

mad_
mad_

Reputation: 8273

You can replace the day part to mimic the requirement.

import numpy as np
import pandas as pd
a = np.datetime64('1995-12-31')
b = a + pd.Timedelta(1, unit='M')
print(b.replace(day=pd.to_datetime(a).day))

Use .date() if you are only interested in date part

Upvotes: 0

Prune
Prune

Reputation: 77847

A time delta of one month is the length of a year divided by 12.

You need to examine your date and add the appropriate quantity of days. Alternately, increment the month number (rolling over to the next year, if needed), and leave the day number unchanged.

Upvotes: 1

Related Questions