dayjs icon indicating copy to clipboard operation
dayjs copied to clipboard

dayjs.tz parse time string incorrect

Open shaul-xu opened this issue 3 years ago • 14 comments

Describe the bug dayjs.tz parse time incorrect

Information image

  • Day.js Version [e.g. v1.0.0]
  • OS: MacOS

shaul-xu avatar May 27 '22 08:05 shaul-xu

another case

image

shaul-xu avatar May 27 '22 08:05 shaul-xu

It's correct if I don't use dayjs.tz method

image

shaul-xu avatar May 27 '22 08:05 shaul-xu

As you don't define a time zone when parsing a date with dayjs.tz(timestring, timezone), dayjs uses the time zone of the machine the program is running on.

So to understand the results from parsing the date string, you must know, what time zone you are in - from the screen shot I suppose it is GMT. So here a view variations to show the effect of the different ways of parsing the string:

const isoString = '2022-05-22T09:30:00+08:00'

// current time zone offset is +02:00 just to show some differences
const date1 = dayjs(isoString)
console.log(date1.format())  // '2022-05-22T03:30:00+02:00'

// without time zone: convert isoString to GMT and
// set offset to current offset without changing the time
const date2 = dayjs.tz(isoString)
console.log(date2.format())  // '2022-05-22T01:30:00+02:00'

// with time zone: convert isoString to GMT and
// set offset to the given time zone (GMT) offset without changing the time
const date3 = dayjs.tz(isoString, 'GMT')
console.log(date3.format())  // '2022-05-22T01:30:00+00:00'

Hope it helps understand, what dayjs does.

BePo65 avatar Jun 08 '22 17:06 BePo65

As you don't define a time zone when parsing a date with dayjs.tz(timestring, timezone), dayjs uses the time zone of the machine the program is running on.

So to understand the results from parsing the date string, you must know, what time zone you are in - from the screen shot I suppose it is GMT. So here a view variations to show the effect of the different ways of parsing the string:

const isoString = '2022-05-22T09:30:00+08:00'

// current time zone offset is +02:00 just to show some differences
const date1 = dayjs(isoString)
console.log(date1.format())  // '2022-05-22T03:30:00+02:00'

// without time zone: convert isoString to GMT and
// set offset to current offset without changing the time
const date2 = dayjs.tz(isoString)
console.log(date2.format())  // '2022-05-22T01:30:00+02:00'

// with time zone: convert isoString to GMT and
// set offset to the given time zone (GMT) offset without changing the time
const date3 = dayjs.tz(isoString, 'GMT')
console.log(date3.format())  // '2022-05-22T01:30:00+00:00'

Hope it helps understand, what dayjs does.

image

But, how to understand the above code

shaul-xu avatar Jun 09 '22 02:06 shaul-xu

  • If .tz is used with javascript Date object as only parameter, the information of that object are taken to set the corresponding values of the dayjs object (e.g. hours, time zone, offset).

  • If .tz is used with a date sting as only parameter, then as a first step this string is parsed to a dayjs object, subtracting the given offset (in your case the result will be '2022-05-22T01:30:00').
    In the second step, this time string ('2022-05-22T01:30:00') will be parsed respecting the given time zone (the second parameter) or if there is no second parameter, then using the current time zone (in your case I suppose this is CST). The result is '2022-05-21T17:30:00'.
    As a final step the time zone (offset) of this date is set to the given / the default time zone (in your case '+08:00') without changing any other values of the generated dayjs object.
    Using the dayjs.format() method this date gets formatted as '2022-05-22T01:30:00+08:00';
    using the dayjs.toString() method, this date is formatted as UTC as '2022-05-21T17:30:00 GMT'.

So the problem is using dayjs.tz() without a time zone parameter to parse a date string.

BePo65 avatar Jun 09 '22 12:06 BePo65

  • If .tz is used with javascript Date object as only parameter, the information of that object are taken to set the corresponding values of the dayjs object (e.g. hours, time zone, offset).
  • If .tz is used with a date sting as only parameter, then as a first step this string is parsed to a dayjs object, subtracting the given offset (in your case the result will be '2022-05-22T01:30:00'). In the second step, this time string ('2022-05-22T01:30:00') will be parsed respecting the given time zone (the second parameter) or if there is no second parameter, then using the current time zone (in your case I suppose this is CST). The result is '2022-05-21T17:30:00'. As a final step the time zone (offset) of this date is set to the given / the default time zone (in your case '+08:00') without changing any other values of the generated dayjs object. Using the dayjs.format() method this date gets formatted as '2022-05-22T01:30:00+08:00'; using the dayjs.toString() method, this date is formatted as UTC as '2022-05-21T17:30:00 GMT'.

So the problem is using dayjs.tz() without a time zone parameter to parse a date string.

But, I think it's a Bug. Because moment.tz doesn't have the problem I mentioned. Many people will use Dayjs to replace Momentjs, resulting in inconsistent behavior of the above code, resulting in Bugs.

shaul-xu avatar Jun 10 '22 02:06 shaul-xu

I scanned the documentation for moment.tz and I did not find the section describing the use of 'tz' with a date string, but without a time zone. Can you help me out with the place, where this feature is documented?
dayjs tries to be a 1:1 replacement for moment, but there are of course a few differences (see my comment in pr #1914).

BePo65 avatar Jun 10 '22 04:06 BePo65

It is precisely because dayjs.tz and moment.tz have different performances, so I created this issue, I think it's a bug, I hope the maintainer of dayjs can explain this phenomenon

shaul-xu avatar Jun 10 '22 05:06 shaul-xu

May I resume the bug:

  • you are using moment.tz in a way that is not covered by the documentation of moment (using moment.tz(dateString))
    and expect
  • dayjs.tz(dateString) to behave the same way, although this signature is also not documented in dayjs?

BePo65 avatar Jun 11 '22 05:06 BePo65

Related: #1687


I have found a related bug: dayjs.tz with timezone other that UTC/Etc/UTC parses UTC strings as if the UTC strings didn’t have timezone (they have Z which stands for Zulu which is UTC):

const dayjs = require('dayjs')
const utc = require('dayjs/plugin/utc')
const timezone = require('dayjs/plugin/timezone')

const dayjs.extend(utc)
const dayjs.extend(timezone)

const oldDateDayjsTest1 = dayjs.tz('2022-10-29T10:00:00.000Z', 'Europe/Bratislava')

console.log(oldDateDayjsTest1)
// {
//   '$L': 'en',
//   '$d': 2022-10-29T08:00:00.000Z,
//   '$x': { '$localOffset': -120, '$timezone': 'Europe/Bratislava' },
//   '$y': 2022,
//   '$M': 9,
//   '$D': 29,
//   '$W': 6,
//   '$H': 10,
//   '$m': 0,
//   '$s': 0,
//   '$ms': 0,
//   '$offset': 120
// }

The UTC time should stay the same, regardless of timezone used.

tukusejssirs avatar Oct 29 '22 08:10 tukusejssirs

oh, it does (forget about looking at the internal variable $d :-)

console.log(oldDateDayjsTest1.format()) // => '2022-10-29T12:00:00+02:00'
console.log(oldDateDayjsTest1.toISOString()) // => '2022-10-29T10:00:00.000Z'
console.log(oldDateDayjsTest1.toString()) // => 'Sat, 29 Oct 2022 10:00:00 GMT'
console.log(oldDateDayjsTest1.valueOf()) // => 1667037600000

BePo65 avatar Nov 05 '22 18:11 BePo65

export function tzDayjs(date?: ConfigType, timezone?: string)
{
	if (typeof date === 'string' && date.endsWith('.000Z'))
	{
		date = dayjs.utc(date)
	}

	return dayjs.tz(date ?? void 0, timezone ?? TZ_TIME_ZONE)
}

bluelovers avatar Apr 30 '23 00:04 bluelovers

dayjs is not able to work with timezone information, adds and removes hours left and right. don't use it.

dayjs.utc('2022-11-28T14:30:00.000Z').tz('America/Chicago').format();  // 2022-11-28T01:30:00-06:00

Changed time and offset. The documentation says, pass an additional parameter.

dayjs.utc('2022-11-28T14:30:00.000Z').tz('America/Chicago', true).format();  // 2022-11-28T07:30:00-06:00

Changed offset "only". But removed one hour. Counting from the result backwards, 7:30 + 06:00 is 13:30, the source is however 14:30.

The expect result is 2022-11-28T08:30:00-06:00. The same as other time services tell us.

Maybe a different print function reveals something more,

dayjs.utc('2022-11-28T14:30:00.000Z').tz('America/Chicago', true).toISOString()  // 2022-11-28T13:30:00.000Z

It is trying to say what? "14:30 UTC" expressed in "America/Chicago" timezone prints as "13:30 UTC". Looks like dayjs is not made to work with timezones.

Let's try some basic prints,

dayjs.utc('2022-11-28T14:30:00.000Z').format()       // 2022-11-28T14:30:00+00:00
dayjs.utc('2022-11-28T14:30:00.000Z').toISOString()  // 2022-11-28T14:30:00.000Z
dayjs.utc('2022-11-28T14:30:00.000Z')                // Mon, 28 Nov 2022 14:30:00 GMT

As expected, let's try date in DST,

dayjs.utc('2022-10-26T13:30:00.000Z').format()      // 2022-10-26T14:30:00+00:00
dayjs.utc('2022-10-26T13:30:00.000Z').toISOString() // 2022-10-26T13:30:00.000Z
dayjs.utc('2022-10-26T13:30:00.000Z')               // Wed, 26 Oct 2022 13:30:00 GMT

Tries to say what? 13:30:00 UTC is equal to 14:30:00+00:00? It has just made up one additional hour.

Is there any random combination of functions and parameters that can achieve the result. Nope, one hour gets lost in every combination.

dayjs('2022-11-28T14:30:00.000Z').tz('America/Chicago', true).toISOString()  // 2022-11-28T13:30:00.000Z
dayjs.tz('2022-11-28T14:30:00', 'UTC').tz('America/Chicago', true).toISOString()  // 2022-11-28T13:30:00.000Z
dayjs.tz('2022-11-28T14:30:00.000Z', 'UTC').tz('America/Chicago', true).toISOString()  // 2022-11-28T13:30:00.000Z

thomasnal avatar Nov 06 '23 17:11 thomasnal