Luxon: Incorrect year when adding time for years before 100

Created on 31 Jan 2019  Â·  7Comments  Â·  Source: moment/luxon

Given a DateTime with a year that is before 100, trying to add time that crosses to the next year results in the same year.

For example,

const a = DateTime.local(99, 12, 1) // 0099-12-01
const b = a.plus({ month: 1 })      // 0099-01-01 (should be 0100-01-01)
a.year === b.year // true

I dug into the code and it turns out there's a special case here that sets the UTC full year using the JavaScript Date object, because it would result in the year 2000 instead of 100.

However, it uses the "original" year (in this case 99), because it does not take into account the month, which is 12 (January of the next year).

This is also incorrect since that same code doesn't take into account days greater than the last day of the month.

DateTime.local(99, 12, 31).plus({ day: 1 }) // 0099-01-01
bug

All 7 comments

Hmm, yeah, thanks. Definitely a bug.

This is proving difficult to fix. We're relying on Date.UTC() to handle overflows (e.g. in this case, understanding what "99-12-32" means), which means that we can't second-guess it. Luxon would need to figure out that the outputted year was wrong, but knowing that would require doing the exact computation we just delegated to Date.UTC(). I think the only way to really fix this is to implement a Gregorian calendar in Luxon, something I've hoping not to do. Blech

What about instead of doing d.setUTCFullYear(obj.year) it does d.setUTCFullYear(d.getUTCFullYear() - 1900)?

The trouble is that you need to know whether to do it or not. Checking obj.year is wrong because it ignores the months and you can’t check the date obj because maybe it really is 1900. Imagine, for example, that you had added tens of thousands of days to 0099.

You would still check if obj.year is in between [0, 100) in the if-condition, but offset the resulting year by 1900 inside the block. So that block becomes:

if (obj.year < 100 && obj.year >= 0) {
  d = new Date(d);
  d.setUTCFullYear(d.getUTCFullYear() - 1900);
}

Let's assume we add 657438 days (the number of days between the end of 0099 and 1900) to 0099-12-31, so obj.day would be 31 + 657438, resulting in an obj like this:

{ year: 99, month: 11, day: 657469, hour: 0, minute: 0, second: 0, millisecond: 0 }

Date.UTC(...) will see obj.year as 1999 and obj.day will cause an overflow resulting in the year 3800 (657438 ≈ 1800 years). Thus, d.getUTCFullYear() - 1900 would equal 1900, which is the expected year.

Yeah, I guess you're right. Thanks

Fixed in c16d7d1

Was this page helpful?
0 / 5 - 0 ratings