Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Old dates have weird minute offsets in utc #1996

Open
irgipaulius opened this issue Jul 21, 2022 · 14 comments
Open

Old dates have weird minute offsets in utc #1996

irgipaulius opened this issue Jul 21, 2022 · 14 comments

Comments

@irgipaulius
Copy link

irgipaulius commented Jul 21, 2022

Describe the bug
Take this as an example:

// loop same day every 11 years for the past 200 years
for (let i = 2022; i > 1800; i -= 11) {
  console.log(dayjs(`12/31/${i}`).utc(true).toDate());
}

Expected behavior

2022-12-31T00:00:00.000Z
2011-12-31T00:00:00.000Z
2000-12-31T00:00:00.000Z
1989-12-31T00:00:00.000Z
1978-12-31T00:00:00.000Z
1967-12-31T00:00:00.000Z
1956-12-31T00:00:00.000Z
1945-12-31T00:00:00.000Z
1934-12-31T00:00:00.000Z
1923-12-31T00:00:00.000Z
1912-12-31T00:00:00.000Z
1901-12-31T00:00:00.000Z
1890-12-31T00:00:00.000Z
1879-12-31T00:00:00.000Z
1868-12-31T00:00:00.000Z
1857-12-31T00:00:00.000Z
1846-12-31T00:00:00.000Z
1835-12-31T00:00:00.000Z
1824-12-31T00:00:00.000Z
1813-12-31T00:00:00.000Z
1802-12-31T00:00:00.000Z

Actual behavior

2022-12-31T00:00:00.000Z
2011-12-31T00:00:00.000Z
2000-12-31T00:00:00.000Z
1989-12-31T00:00:00.000Z
1978-12-31T00:00:00.000Z
1967-12-31T00:00:00.000Z
1956-12-31T00:00:00.000Z
1945-12-31T00:00:00.000Z
1934-12-31T00:00:00.000Z
1923-12-31T00:00:00.000Z
1912-12-31T00:06:00.000Z   /// what???
1901-12-31T00:06:00.000Z
1890-12-31T00:06:00.000Z
1879-12-31T00:03:44.000Z   /// huh??!
1868-12-31T00:03:44.000Z
1857-12-31T00:03:44.000Z
1846-12-31T00:03:44.000Z
1835-12-31T00:03:44.000Z
1824-12-31T00:03:44.000Z
1813-12-31T00:03:44.000Z
1802-12-31T00:03:44.000Z   /// it goes on like this until 100 AC

Information

  • Day.js Version ^1.11.4
  • OS: MacOs Big Sur 11.6
  • NodeJS
  • Time zone: [e.g. GMT+03:00 DST]
@Bykiev
Copy link

Bykiev commented Jul 21, 2022

Hi, what is your timezone? I can't reproduce the issue with Europe/Moscow timezone

UPD: reproduced with this demo

Here is a demo with moment.js and same results.

@irgipaulius
Copy link
Author

irgipaulius commented Jul 21, 2022

My timezone is Europe/Vilnius.

This is curious.
running with moment ^2.29.3 I get a different result. I don't know how to make such cool demos, so I'll just put it here:

code:

const moment = require("moment");
const dayjs = require("dayjs");
const utc = require("dayjs/plugin/utc");
dayjs.extend(utc);

const f = "YYYY-MM-DD HH:mm:ss Z";

// loop same day every 11 years for the past 200 years
for (let i = 2022; i > 1800; i -= 11) {
  const dayjsDate = dayjs(`12/31/${i}`).utc(true).format(f);
  const momentDate = moment.utc(`12/31/${i}`).format(f).toString();
  console.log(`${dayjsDate} === ${momentDate}`);
}

result:

2022-12-31 00:00:00 +00:00 === 2022-12-31 00:00:00 +00:00
2011-12-31 00:00:00 +00:00 === 2011-12-31 00:00:00 +00:00
2000-12-31 00:00:00 +00:00 === 2000-12-31 00:00:00 +00:00
1989-12-31 00:00:00 +00:00 === 1989-12-31 00:00:00 +00:00
1978-12-31 00:00:00 +00:00 === 1978-12-31 00:00:00 +00:00
1967-12-31 00:00:00 +00:00 === 1967-12-31 00:00:00 +00:00
1956-12-31 00:00:00 +00:00 === 1956-12-31 00:00:00 +00:00
1945-12-31 00:00:00 +00:00 === 1945-12-31 00:00:00 +00:00
1934-12-31 00:00:00 +00:00 === 1934-12-31 00:00:00 +00:00
1923-12-31 00:00:00 +00:00 === 1923-12-31 00:00:00 +00:00
1912-12-31 00:06:00 +00:00 === 1912-12-31 00:00:00 +00:00
1901-12-31 00:06:00 +00:00 === 1901-12-31 00:00:00 +00:00
1890-12-31 00:06:00 +00:00 === 1890-12-31 00:00:00 +00:00
1879-12-31 00:03:44 +00:00 === 1879-12-31 00:00:00 +00:00
1868-12-31 00:03:44 +00:00 === 1868-12-31 00:00:00 +00:00
1857-12-31 00:03:44 +00:00 === 1857-12-31 00:00:00 +00:00
1846-12-31 00:03:44 +00:00 === 1846-12-31 00:00:00 +00:00
1835-12-31 00:03:44 +00:00 === 1835-12-31 00:00:00 +00:00
1824-12-31 00:03:44 +00:00 === 1824-12-31 00:00:00 +00:00
1813-12-31 00:03:44 +00:00 === 1813-12-31 00:00:00 +00:00
1802-12-31 00:03:44 +00:00 === 1802-12-31 00:00:00 +00:00

the results differ very much depending on the location of the machine. Shouldn't utc be idempotent?

Edit: formatted the output

@Bykiev
Copy link

Bykiev commented Jul 21, 2022

Indeed, depends on localtion, but I'm getting the same result for both libs, again small demo and my results:

2022-12-31 00:00:00 +00:00 === 2022-12-31 00:00:00 +00:00
2011-12-31 00:00:00 +00:00 === 2011-12-31 00:00:00 +00:00
2000-12-31 00:00:00 +00:00 === 2000-12-31 00:00:00 +00:00
1989-12-31 00:00:00 +00:00 === 1989-12-31 00:00:00 +00:00
1978-12-31 00:00:00 +00:00 === 1978-12-31 00:00:00 +00:00
1967-12-31 00:00:00 +00:00 === 1967-12-31 00:00:00 +00:00
1956-12-31 00:00:00 +00:00 === 1956-12-31 00:00:00 +00:00
1945-12-31 00:00:00 +00:00 === 1945-12-31 00:00:00 +00:00
1934-12-31 00:00:00 +00:00 === 1934-12-31 00:00:00 +00:00
1923-12-31 00:00:00 +00:00 === 1923-12-31 00:00:00 +00:00
1912-12-30 23:59:43 +00:00 === 1912-12-30 23:59:43 +00:00
1901-12-30 23:59:43 +00:00 === 1901-12-30 23:59:43 +00:00
1890-12-30 23:59:43 +00:00 === 1890-12-30 23:59:43 +00:00
1879-12-30 23:59:43 +00:00 === 1879-12-30 23:59:43 +00:00
1868-12-30 23:59:43 +00:00 === 1868-12-30 23:59:43 +00:00
1857-12-30 23:59:43 +00:00 === 1857-12-30 23:59:43 +00:00
1846-12-30 23:59:43 +00:00 === 1846-12-30 23:59:43 +00:00
1835-12-30 23:59:43 +00:00 === 1835-12-30 23:59:43 +00:00
1824-12-30 23:59:43 +00:00 === 1824-12-30 23:59:43 +00:00
1813-12-30 23:59:43 +00:00 === 1813-12-30 23:59:43 +00:00
1802-12-30 23:59:43 +00:00 === 1802-12-30 23:59:43 +00:00

@Bykiev
Copy link

Bykiev commented Jul 21, 2022

Seems to be a bug with utc function. With utcOffset the result is correct:
dayjs(12/31/${i}).utcOffset(0,true).format("YYYY-MM-DD HH:mm:ss:SSS Z")

@BePo65
Copy link
Contributor

BePo65 commented Jul 24, 2022

This is not a bug, but the correct implementation of time zones. So for example for CET the definition of the time zone ("UTC offset") changed several times over the years, as can be seen in the timeanddate web page).

The time zone definitions can be found on the iana Time Zone Database. This database is implemented for example in the Internationalization API of javascript, the basis for time zones in dayjs.

@irgipaulius
Copy link
Author

@BePo65 interesting. This causes a few issues for my needs though.

  • utc gives different results on machines in different timezones. I have no way of testing/verifying that to be absolutely sure.
  • my application potentially works with old dates. I don't want to have to round dates every time.

are there any other ways to set utc mode without this "feature"?

currently I use moment and it works as I expect it to.

@Bykiev
Copy link

Bykiev commented Jul 24, 2022

This is not a bug, but the correct implementation of time zones. So for example for CET the definition of the time zone ("UTC offset") changed several times over the years, as can be seen in the timeanddate web page).

The time zone definitions can be found on the iana Time Zone Database. This database is implemented for example in the Internationalization API of javascript, the basis for time zones in dayjs.

Hi, sorry, I'm far from timezones, but why does .utcOffset(0,true) has another results?

@BePo65
Copy link
Contributor

BePo65 commented Jul 25, 2022

why does .utcOffset(0,true) has another results

Using utcOffset with 1 parameter, sets the utcOffset of a given dayjs object.

Using utcOffset with 2 parameters, sets the utcOffset of a given dayjs object while keeping the time value of that object. And therefore using .utcOffset(0, true) gets other values (this fragment avoids the critical branch in the code).

Regarding that point, the documentation is not really complete (at least I didn't find it in the documentation, only in the source code). @Bykiev : perhaps you want to make a PR for the documentation on the corresponding project?

Back to time zones / utcOffset:
sorry that it took me so long to understand the issue. If I get it right, your point is not about correct definition of timezones. The point is that .utcOffset(true) does not keep the local time. And that is really an error in dayjs.

I'm working on a pr for this issue; give me a few days.

@BePo65
Copy link
Contributor

BePo65 commented Jul 25, 2022

in the meantime: how about using

dayjs.utc(`12/31/${i}`).format(f)

instead of

dayjs(`12/31/${i}`).utc(true).format(f)

In a quick test, this solves the point on my machine (I believe 😄 ).

Nevertheless I will try to fix the utcOffset issue, as I am working on the dayjs 2.0 utc plugin and this could / should be fixed there too.

@Bykiev
Copy link

Bykiev commented Jul 25, 2022

why does .utcOffset(0,true) has another results

Using utcOffset with 1 parameter, sets the utcOffset of a given dayjs object.

Using utcOffset with 2 parameters, sets the utcOffset of a given dayjs object while keeping the time value of that object. And therefore using .utcOffset(0, true) gets other values (this fragment avoids the critical branch in the code).

Regarding that point, the documentation is not really complete (at least I didn't find it in the documentation, only in the source code). @Bykiev : perhaps you want to make a PR for the documentation on the corresponding project?

Back to time zones / utcOffset: sorry that it took me so long to understand the issue. If I get it right, your point is not about correct definition of timezones. The point is that .utcOffset(true) does not keep the local time. And that is really an error in dayjs.

I'm working on a pr for this issue; give me a few days.

Thank you for your investigation, indeed, utcOffset(true) doesn't preserve time, but it's undocumented feature and should be avoided.
I can't understand, why does time converted from 1912-12-30 to 1912-12-30 23:59:43 +00:00 while using utc(true) and converted to 1912-12-31 00:00:0 +00:00 while using utcOffset(0,true)? My timezone is Europe/Moscow

in console:

$y: 1912…}
$L: "en"
$u: true
$d: Tue Dec 31 1912 02:30:00 GMT+0230 (Москва, стандартное время)
$x: Object
$y: 1912
$M: 11
$D: 30
$W: 1
$H: 23
$m: 59
$s: 43
$ms: 0

@Bykiev
Copy link

Bykiev commented Jul 25, 2022

in the meantime: how about using

dayjs.utc(`12/31/${i}`).format(f)

instead of

dayjs(`12/31/${i}`).utc(true).format(f)

In a quick test, this solves the point on my machine (I believe 😄 ).

Nevertheless I will try to fix the utcOffset issue, as I am working on the dayjs 2.0 utc plugin and this could / should be fixed there too.

With such format it returns the same 1912-12-30 23:59:43 +00:00, but if the input string format is RFC2822/ISO (${i}-12-31) it'll return 1912-12-31 00:00:0 +00:00.

@Bykiev
Copy link

Bykiev commented Jul 25, 2022

I did some more research and it seems the issue is not related to dayjs, here is small demo with date:

Ouput:

Sat Dec 31 2022 03:00:00 GMT+0300 (Москва, стандартное время)
Sat Dec 31 2011 04:00:00 GMT+0400 (Москва, стандартное время)
Sun Dec 31 2000 03:00:00 GMT+0300 (Москва, стандартное время)
Sun Dec 31 1989 03:00:00 GMT+0300 (Москва, стандартное время)
Sun Dec 31 1978 03:00:00 GMT+0300 (Москва, стандартное время)
Sun Dec 31 1967 03:00:00 GMT+0300 (Москва, стандартное время)
Mon Dec 31 1956 03:00:00 GMT+0300 (Москва, стандартное время)
Mon Dec 31 1945 03:00:00 GMT+0300 (Москва, стандартное время)
Mon Dec 31 1934 03:00:00 GMT+0300 (Москва, стандартное время)
Mon Dec 31 1923 02:00:00 GMT+0200 (Москва, стандартное время)
Tue Dec 31 1912 02:30:17 GMT+0230 (Москва, стандартное время)
Tue Dec 31 1901 02:30:17 GMT+0230 (Москва, стандартное время)
Wed Dec 31 1890 02:30:17 GMT+0230 (Москва, стандартное время)
Wed Dec 31 1879 02:30:17 GMT+0230 (Москва, стандартное время)
Thu Dec 31 1868 02:30:17 GMT+0230 (Москва, стандартное время)
Thu Dec 31 1857 02:30:17 GMT+0230 (Москва, стандартное время)
Thu Dec 31 1846 02:30:17 GMT+0230 (Москва, стандартное время)
Thu Dec 31 1835 02:30:17 GMT+0230 (Москва, стандартное время)
Fri Dec 31 1824 02:30:17 GMT+0230 (Москва, стандартное время)
Fri Dec 31 1813 02:30:17 GMT+0230 (Москва, стандартное время)
Fri Dec 31 1802 02:30:17 GMT+0230 (Москва, стандартное время)

Still can't understand how did it working as UTC offset in 1912 is +02:30... Where 17 seconds are from?

@BePo65, thank you for your links above, all is correct, the timezone shift in 1912 was +02:30:17

My misunderstood between utc(true) and utcOffset(0, true) is utc(true) converting time from timezone to UTC, but utcOffset(0, true) just preserve specified time with specified time shift.

@BePo65
Copy link
Contributor

BePo65 commented Jul 25, 2022

One of the problems of offset is the handling of seconds (see issue #1905).

@BePo65
Copy link
Contributor

BePo65 commented Aug 6, 2022

After more tests and investigation here my current interpretation of the original problem of this issue:

The code from @irgipaulius compares dayjs'12/31/2000').utc(true).format(f)
with moment.utc('12/31/2000').format(f) and gets different results for the 2 values; which is ok, because

  • .utc(true) takes the original date (2000-12-31T00:00:00.000) and converts it to an utc date keeping the local time resulting in 2000-12-31T00:00:00.000.
  • .utc('12/31/2000') simply parses the string and creates an utc date from it (2000-12-31T00:00:00.000)..

Both results look the same at the first view, but things get complicated, if the timezone offset contains seconds. Using utc(dateString) simply ignores the offset, as it parses directly to an utc date, while date(datestring).utc(true) subtracts the utcOffset (which includes the seconds) from the date (which does not contain the seconds). As a result the time part gets "wrong". And that's true for moment and dayjs.

I created an example for 'Europe/Kiev":

element value comment
dateString '1879-12-31'
utcOffset 122 to be exactly: "02:02:04"
date.utc().format(f) "1879-12-30 21:57:56 +00:00" .utc() just ignores the time
date.utc(true).format(f) "1879-12-30 23:59:56 +00:00" here are the 4s from the exact utcOffset
dayjs.utc(dateString).format(f) "1879-12-31 00:00:00 +00:00" parses the string directly to utc

So if you would use the same function for moment and dayjs, the results would be s´the same in both cases - even moment(string).utc(true) shows the 'mysterious' seconds 😄

Besides of this, there remains the problem of dayjs with seconds in the timezone offset (see also issue #1905). I created a PR (#2016) for this, hoping that it helps solve the points with historical dates.

BePo65 added a commit to BePo65/dayjs that referenced this issue Sep 2, 2022
Example for offset not full or halve hours is '1879-12-31' for
timezone 'Europe/Berlin', where utcOffset is '00:53'.
Solves issue iamkun#1996.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants