Hacker News new | ask | show | jobs
by ehsankia 2305 days ago
It's not only about writing your own library though. For example, it's often not reading the documentation properly.

In Python, if you take a datetime, and call .replace(year=X) on a datetime for Feb29, it'll throw a ValueError.

3 comments

I think .replace() is a mistake, and it shouldn't exist in the first place. The way dates work, replacing a single component is almost always going to create problems in specific cases.
And then you have to implement business directives with “the same date in 2025” in documents already signed by all parties (and no one got confused, except math guys). Replace is not a mistake, it just should state what it does, so that a programmer could test and use it.
It does state what it does, but people rarely read documentations, and unlike static languages, Python doesn't force you to deal with the exception. And since it's a very rare problem, most people don't catch the bug.
What if you take March 31st and add one month to it? (I assume you can do that somehow)
Not with the standard library. (The "timedelta" class only expresses in units up to days, as it is ambiguous how long a month is.

There's a nice package called "python-dateutil" that includes a "relativedelta" class; adding a month to March 31 results in April 30:

  In [7]: datetime.datetime(2020, 3, 31) + dateutil.relativedelta.relativedelta(months=1)                                
  Out[7]: datetime.datetime(2020, 4, 30, 0, 0)
Adding a year to a leap day:

  In [8]: datetime.datetime(2020, 2, 29) + dateutil.relativedelta.relativedelta(years=1)                                 
  Out[8]: datetime.datetime(2021, 2, 28, 0, 0)
The exact duration that relativedelta adds depends on what you add it to. (Hence the name.) But the results tend to match up with human expectations.
You get April 30. Idk about all libraries, but date-fns and few others I worked with do it right.

Also, if you add one month to Apr 30 and two months to Mar 31, you’ll see that month addition is not commutative-y and one should operate on distances from a base date, not in an incremental way.

Even if you replace it with another leap year?
No another leap year would be fine. But yeah a very common (wrong) pattern is, if you want to find the same day a year in advance, is to do `d.replace(year=d.year + 1)`, and that would break on Feb 29 only, so one day every 4 years. It's a very common pattern unfortunately.
It returns a new datetime object which includes its own validity check and raises an exception for a bad leap day the same way it would for March 32nd or Blurnsuary 12th. (However, year must be an integer in [1, 9999].)

Edit: So, another leap year would be fine.