moment / luxon

⏱ A library for working with dates and times in JS
https://moment.github.io/luxon
MIT License
15.26k stars 731 forks source link

Argentina timezone is wrong before the day 1920/05/01 #1399

Open augustogalan7 opened 1 year ago

augustogalan7 commented 1 year ago

On 1 May 1920 Argentina's time zone changed from UTC-4:16 to UTC-4, when you apply the toLocaleString method on that date, the result is one day earlier.

When you use the browser's native functions you get new Date(1920,04,02).toLocaleTimeString() ---> '0:00:00'. new Date(1920,04,01).toLocaleTimeString() ---> '0:16:48'.

Can be reproduced in code pen: var DateTime = luxon.DateTime; console.log("Date is incorrect expected on 1/5/1920 but found on 30/4/1920") console.log(DateTime.local(1920, 05, 01).toLocaleString(DateTime.DATE_SHORT))

link: https://codepen.io/augustogalan/pen/QWVBwej

icambron commented 1 year ago

There are a couple things going on here.

The first is that the offset isn't just -4:16; it's -4:16:48. The problem for Luxon is that JS doesn't report the seconds:

new Date(1920, 3, 30).getTimezoneOffset()   //=> 256 

This throws off Luxon's computations by exactly those seconds. To take an arbitrary date before the switch:

DateTime.local(1920, 2, 1).toLocaleString(DateTime.DATETIME_FULL_WITH_SECONDS) //=> 'January 31, 1920 at 11:59:12 PM GMT-4:16:48'  

Notice Luxon is short those exact 48 seconds, which unfortunately puts the date into the previous day. For more, see #401.

There's more going on here though, because 1920-05-01 is after the switch. I think what's suppose to happen is that at midnight, the clocks advanced from midnight to 00:16:48. This means that local times like 00:00:00 or, say, 00:10:15 never actually happened. In theory, if you try to build a local time with those "hole times" you should get '0:16:48' for them, but V8 doesn't seem to handle it that way:

> new Date(1920, 3, 30, 23, 59, 59).toLocaleString()                                                                                                                                
'4/30/1920, 11:59:59 PM'                                                                                                                                                                                                                                                                                                                  
> new Date(1920, 4, 1, 0, 0).toLocaleString()                                                                                                                                       
'5/1/1920, 12:16:48 AM'                                                                                                                                                             
> new Date(1920, 4, 1, 0, 15).toLocaleString()                                                                                                                                      
'5/1/1920, 12:31:48 AM'                                                                                                                                                             
> new Date(1920, 4, 1, 0, 16, 48).toLocaleString()                                                                                                                                  
'5/1/1920, 12:16:48 AM'   

That's all exceedingly strange, but since those local times are essentially undefined, I guess they can do what they want.

Luxon seems to inherit this behavior, plus the weirdness with the seconds. We can't fix the seconds thing, but I'd have thought we'd have gotten the hole time stuff correct. I'd like to look into that. Anyway, the two issues are conspiring to throw off your date.

A couple work arounds:

  1. use a time in the middle of the day instead of midnight so that the offset doesn't affect you.
  2. build the JS date and pass it into Luxon via fromJSDate
marc-mabe commented 1 year ago

I had a similar issue with getTimezoneOffset for dates around 1900-01-01 (excel epoch). I noticed it to be returned in minutes but chromium based browsers return minutes only where Firefox returns the fractions as well. Solved it with

// date-getTimezoneOffset.ts
// In Chrome Date.getTimezoneOffset is in minute precision only
// but France (and other) countries where still using solar time in 1899/1900
// where the Excel epoch started which gives inaccurate results.
// This monkey patches Date.prototype.getTimezoneOffset to return more accurate results
// @see https://github.com/SheetJS/sheetjs/issues/1212
export default function getTimezoneOffset(this: Date) {
    const utcTime = Date.UTC(
        this.getFullYear(),
        this.getMonth(),
        this.getDate(),
        this.getHours(),
        this.getMinutes(),
        this.getSeconds(),
        this.getMilliseconds(),
    );

    return (this.getTime() - utcTime) / 60000;
}

// index.ts
import getTimezoneOffset from './date-getTimezoneOffset';

// eslint-disable-next-line no-extend-native
Date.prototype.getTimezoneOffset = getTimezoneOffset;