Operating with arbitrary timezones

# Juan Dopazo (8 years ago)

Hello,

I’ve been working on Yahoo Mail and we’ve been running into multiple issues with timezones. Currently EcmaScript supports only creating Date objects with the User Agent’s timezone. However, we need to be able to operate with whatever timezone our users selected in their settings page. In most cases we just need to render dates with the correct timezone and this is currently available using the Ecma 402 APIs. However, in other cases we need to make certain computations taking into account an arbitrary timezone and this is not covered by either EcmaScript or Ecma 402.

In particular, we need to answer the following questions:

  • Is a certain date in the current day/month/year? This means given a Date object, is it in the current day for an arbitrary timezone?

  • When is the start of the day/month for this timezone?

Currently the only way to answer these questions is by calculating the timezone offset for the timezone around the date we’re studying. And the way to do that is to render a date using Intl.DateTimeFormat and recreate a date object: gist.github.com/juandopazo/b52820e368739ed19cb206e3f3893166.

Ideally, EcmaScript would allow developers to operate on a Date object for an arbitrary timezone. Would date.setTimezone(/* IANA string identifier */) work? I know it’s a problem that is in the middle between EcmaScript and Ecma 402.

Thank you,

Juan Dopazo

# Jordan Harband (8 years ago)

This message was in my Gmail spam folder due to "It has a from address in yahoo-inc.com but has failed yahoo-inc.com's required tests for authentication." - so it's possible nobody saw it.

Replying all to bump.

# Alexander Jones (8 years ago)

I would object to your suggestion on the grounds that, as they stand, Date objects represent exact points in time, with no ambiguities, and in many cases that's exactly what you need. Once you endow them with a timezone offset they cease to represent points in time, but rather more points in space, which adds complexity and liability to any code handling Date objects - similar to how any code expecting integral Numbers has to be prepared for non-integral input.

It's unfortunate that the boat has sailed on Date, because it's actually a very inappropriate name for what it is. But what I think is really needed is another distinct class for Date+Time components. You would project a Date to its components (incl a tz offset, to avoid ambiguity around DST shifts) for a given timezone, and with those components, answer the kinds of questions you have. No arithmetic should be possible with such a class, short of extracting a Date back from it and working on that.

# Kris Siegel (8 years ago)

Once you endow them with a timezone offset they cease to represent points in time, but rather more points in space, which adds complexity and liability to any code handling Date objects

I would disagree. Time isn't useful without a point in space (space and time should be looked at as one thing, not two) and since the Date object tracks it internally as UTC it has a default space.

It's unfortunate that the boat has sailed on Date, because it's actually a

very inappropriate name for what it is. But what I think is really needed is another distinct class for Date+Time components.

Completely agree. Other than adding functions nothing can really change with the existing Date object. However this could be a real start to a new set of standard libraries for JavaScript that require the usage of import (so like Python, C++, C#, etc ECMAScript could have a set of libraries that can be included in the exact same fashion as including external libraries).

I'm not really sure about not having arithmetic on this hypothetical new set of components (arithmetic is one of the most useful things about date time libraries) but that's details to be explored later and I guess this is getting off track from the original proposal here.

# Tab Atkins Jr. (8 years ago)

On Fri, Aug 5, 2016 at 1:59 PM, Kris Siegel <krissiegel at gmail.com> wrote:

Once you endow them with a timezone offset they cease to represent points in time, but rather more points in space, which adds complexity and liability to any code handling Date objects

I would disagree. Time isn't useful without a point in space (space and time should be looked at as one thing, not two) and since the Date object tracks it internally as UTC it has a default space.

This is incorrect. It tracks a UTC timestamp; the current time as I write this is 1470432591121 milliseconds since the epoch, and that's true regardless of where you are on the planet.

Timezones are a display concept - they affect how you parse human-readable strings into timestamps, and how you display a timestamp as a human-readable string. This is similar to the distinction between Unicode values and strings encoded in UTF8.

JS Date objects input/output using the user's local timezone. Luckily, since Yahoo Mail apparently knows the user's preferred timezone (from their settings), they can adjust things as appropriate themselves. The important concept, tho, is that this adjustment is in how you parse/display datetimes, not how you store them - storing is done without any timezone information whatsoever. It's not trivial to do so (thus the success of libraries like moment.js), but it's at least easier when you realize that Date objects represent exact points in time, and timezone (and related things like "start of day") are display-related only.

# Jon Zeppieri (8 years ago)

On Fri, Aug 5, 2016 at 5:30 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Fri, Aug 5, 2016 at 1:59 PM, Kris Siegel <krissiegel at gmail.com> wrote:

Once you endow them with a timezone offset they cease to represent points

in time, but rather more points in space, which adds complexity and liability to any code handling Date objects

I would disagree. Time isn't useful without a point in space (space and time should be looked at as one thing, not two) and since the Date object tracks it internally as UTC it has a default space.

This is incorrect. It tracks a UTC timestamp; the current time as I write this is 1470432591121 milliseconds since the epoch, and that's true regardless of where you are on the planet.

Yep.

Timezones are a display concept - they affect how you parse human-readable strings into timestamps, and how you display a timestamp as a human-readable string. This is similar to the distinction between Unicode values and strings encoded in UTF8.

Time zones are not just for display. They're are also necessary for doing date arithmetic on a local timeline (assuming the most common and, IMO, useful sort of date arithmetic). For example: if I ask you, "What date and time is it exactly one day after March 12, 2016 at 3pm in the America/New_York time zone," how do you answer? If you take "one day" to mean 24 hours, then the answer is March 13, 2016 at 4pm in America/New_York (because of daylight saving time), but most libraries that implement date arithmetic will give you March 13, 2016 at 3pm, etc., etc. -- because that's what users tend to expect out of date arithmetic (as opposed to time arithmetic).

# Alexander Jones (8 years ago)

What I meant by Date+Time components was actually { year, month, day, hours, minutes, seconds, offset }. I think it makes sense to formalise this as an expression of a time point (aka Date) in a particular timezone. For example this can be used to decide e.g. "are these two time points in the same month in this specific timezone", where that month may be e.g. 31 days

  • 1 hour. It could also be formatted for display.

Conflating Date projections into a timezone, with formatting, is a bit of a conceptual error IMO. Convenient, but incorrect, as per Single Responsibility Principle.

Alex

# Jon Zeppieri (8 years ago)

On Fri, Aug 5, 2016 at 5:58 PM, Alexander Jones <alex at weej.com> wrote:

What I meant by Date+Time components was actually { year, month, day, hours, minutes, seconds, offset }. I think it makes sense to formalise this as an expression of a time point (aka Date) in a particular timezone. For example this can be used to decide e.g. "are these two time points in the same month in this specific timezone", where that month may be e.g. 31 days

  • 1 hour. It could also be formatted for display.

Conflating Date projections into a timezone, with formatting, is a bit of a conceptual error IMO. Convenient, but incorrect, as per Single Responsibility Principle.

Alex

UTC offsets are terrible representations of time zones, because they don't acknowledge that many actual local timelines are discontinuous.

When you don't need time zones, it's best to avoid them (and UTC offsets) altogether, but when you really need them, UTC offsets just aren't enough.

# Alexander Jones (8 years ago)

Don't confuse timezones with timezone offsets. They are different concepts.

# Jon Zeppieri (8 years ago)

On Fri, Aug 5, 2016 at 6:21 PM, Alexander Jones <alex at weej.com> wrote:

Don't confuse timezones with timezone offsets. They are different concepts.

How am I confusing them? You wrote:

What I meant by Date+Time components was actually { year, month, day,

hours, minutes, seconds, offset }. I think it makes sense to formalise this as an expression of a time point (aka Date) in a particular timezone. For example this can be used to decide e.g. "are these two time points in the same month in this specific timezone", where that month may be e.g. 31 days

  • 1 hour. It could also be formatted for display.

And I'm saying, in response, that if you want to formalize the concept of a time point in a particular time zone, this isn't a good way to do it. If instead you want to formalize the concept of a particular point in time at a particular UTC offset, then it's fine, of course, but at that point, I'd just reiterate Tab's response.

# Jon Zeppieri (8 years ago)

On Fri, Aug 5, 2016 at 6:32 PM, Jon Zeppieri <zeppieri at gmail.com> wrote:

On Fri, Aug 5, 2016 at 6:21 PM, Alexander Jones <alex at weej.com> wrote:

Don't confuse timezones with timezone offsets. They are different concepts.

How am I confusing them? You wrote:

What I meant by Date+Time components was actually { year, month, day,

hours, minutes, seconds, offset }. I think it makes sense to formalise this as an expression of a time point (aka Date) in a particular timezone. For example this can be used to decide e.g. "are these two time points in the same month in this specific timezone", where that month may be e.g. 31 days

  • 1 hour. It could also be formatted for display.

And I'm saying, in response, that if you want to formalize the concept of a time point in a particular time zone, this isn't a good way to do it. If instead you want to formalize the concept of a particular point in time at a particular UTC offset, then it's fine, of course, but at that point, I'd just reiterate Tab's response.

  • Jon

Actually, let me try to clarify this a bit more. It sounds like you do not want a DateTime type to contain (what I would call) a robust concept of a time zone, because you think that date "projections" (by which I think you're referring to the kind of date arithmetic that I mentioned in an earlier post) are a separate concern.

(I'm fine with that, by the way. Off the top of my head, I can't think of any date/time libraries that use a time zone as a separate input to date arithmetic functions, but it's a reasonable design.)

However, you say that it would be useful to have the offset as part of a DateTime object, so that you could answer questions like: "Are these two points in time in the same month in this specific time zone?" Well, first question: which specific time zone (offset, actually, since that's what you want in the objects, right)? We're talking about either a two argument predicate function or a binary method, I assume. You might say that it doesn't matter which offset you use -- coerce in either direction you want; they're either in the same month or not. True (under certain assumptions, which I'll get back to in a moment) -- but if you're not interested in which month (and which offset), then you don't need the offset in the representation at all, because if you could coerce both to UTC to answer the question, then... well, you could just represent them in UTC.

And all of this assumes that the question is a meaningful one to start with. I'll happily elide the question of what calendar we're talking about, but assuming we only care about the proleptic Gregorian calendar: when would you ever be interested in whether two points in time fall within the same month at a given UTC offset -- as opposed to a more robust notion of time zone? I might, at some point, care if two points in time are in the same month in America/New_York, but I can't imagine why I'd ever care if they are thus in UTC-5:00 -- precisely because UTC-5:00 doesn't really designate an area that uses the calendar in such-and-such a way, while America/New_York does.

I suppose this is my long-winded way of saying that the very projections that you'd (understandably) like to avoid are implicit in almost any time zone-related question you'd want to ask.

# Alexander Jones (8 years ago)

I'm not sure I properly conveyed myself above given what you're saying. Let me back up and explain more clearly now I have an actual keyboard in front of me.

Currently we have Date.prototype.toLocaleString(), which conflates two concerns:

  1. Taking an unambiguous time point to a { year, month, day, hours, minutes, seconds, milliseconds, timezoneOffset } for the selected timezone (which is the subject of this thread).
  2. Formatting those "local datetime components" in a way appropriate to a specific locale.

Example:

d.toLocaleString("en-GB", {timeZone: "Europe/London"})
"06/08/2016, 09:02:13"
d.toLocaleString("en-US", {timeZone: "America/New_York"})
"8/6/2016, 4:02:13 AM"

What I'm suggesting is that these two concerns could be better separated. Hypothetically, think of a new class with the awful and purely illustrative name LocalDatetimeComponents:

const now = new Date();

const ldc = now.toLocalDatetimeComponents({timeZone: "Europe/London"});

assert(ldc instanceof LocalDatetimeComponents);

assert(ldc.month === AUGUST);

assert(ldc.timeZoneOffset === -1 * 60);
// UTC+1 for this specific time point, UTC+0 in winter

assert(ldc.timeZoneName === "BST");
// or GMT in winter

assert(ldc.toLocaleString("en-GB", {timeZoneName: "short"}) ===
"06/08/2016, 09:02:13 BST");

Note that the vast majority of the many possible LocalDatetimeComponents values would not normally constitute real datetimes. But one benefit of this separation is that it can express and format things like the time 2015-06-30T23:59:60.123Z (an actual time point within the most recent "leap second") in spite of the fact that Date itself has no possible representation of it. Perhaps some class or function other than Date can produce LocalDatetimeComponents objects, maybe one that supports leap seconds in applications that actually need it.

Alex

# Jon Zeppieri (8 years ago)

On Sat, Aug 6, 2016 at 4:27 AM, Alexander Jones <alex at weej.com> wrote:

I'm not sure I properly conveyed myself above given what you're saying. Let me back up and explain more clearly now I have an actual keyboard in front of me.

Currently we have Date.prototype.toLocaleString(), which conflates two concerns:

  1. Taking an unambiguous time point to a { year, month, day, hours, minutes, seconds, milliseconds, timezoneOffset } for the selected timezone (which is the subject of this thread).
  2. Formatting those "local datetime components" in a way appropriate to a specific locale.

Ah -- you're right. I didn't understand what you were getting at before. This was very helpful. Thanks.

# peter miller (8 years ago)

On Fri, 05 Aug 2016 22:30:28 +0100, Tab Atkins Jr. <jackalmage at gmail.com>

wrote:

Much of what follows is wanton pedantry, for which I apologise. ;)

I would disagree. Time isn't useful without a point in space (space and
time should be looked at as one thing, not two) and since the Date object
tracks it internally as UTC it has a default space.

This is incorrect. It tracks a UTC timestamp; the current time as I write this is 1470432591121 milliseconds since the epoch, and that's true regardless of where you are on the planet.

I suspect you've dropped 36s from that count or used 1st January 1970 0h
0m 36s as your epoch. I might be wrong. But if you have, it's because
Universal Time is a "rotational timescale": it measures the angle of a
fictitious point in the sky (U) relative to the prime meridian and not a
linear count of seconds. The IERS (International Earth Rotation and
Reference Systems Service) measures the angle of U and issue "Universal
Time" (UT1). With UTC being kept within one second of UT1 via leap seconds
(next one at the end of year).

The value an observer measures for U, called UT0, really does depend on
their exact position on the surface of the earth. That has to be adjusted
for. That said, UT1/UTC are notionally the same for us all. And I know
that's what you were saying. Like I said, I'm showing off my very hard won
knowledge.

And I might as well point out UT is defined on the earth's surface. It's
affected by gravity.

Timezones are a display concept - they affect how you parse human-readable strings into timestamps, and how you display a timestamp as a human-readable string. This is similar to the distinction between Unicode values and strings encoded in UTF8.

Sure, calendar dates (year, month and day) are a human-friendly way of
displaying the number rotations of U over a meridian.

But I think timezones represent a change in meridian. I believe in the
olden days people would set their watches to a local meridian. (I'm
reading Verne's Mysterious_Island at the moment and it contains a reference to a chronometer set to the Washington
meridian.) And while the "timezones" we use in modern civil life are a
political construct, I suspect a timezone is still saying display the
transits of U over such-and-such a meridian; it's a shift in longitude,
not directly a shift in time.

As a final unrelated comment, I wonder whether 20.3.1.1 should explicitly
define javascript time values to be:

d * 86400000 + ms

Where d is the number of whole UTC days elapsed since midnight 1 January
1970 and ms is the number of milliseconds elapsed since the start of
current UTC day, with the value of ms being implementation defined close
to a leap second - and possibly diverging from thousandths of an SI second.

I think that's what the existing standard is trying to say but the current
phrasing feels wrong, and the above makes clear that timevalues are a
construct used to represent UTC not a count of millseconds since epoch
(which javascript dates can never be because they exclude the 36---soon to
be 37---leap seconds).

Peter

PS: Can we not add underscores to numbers? Would allowing 86_400_000 break
the web?

# Claude Pache (8 years ago)

Le 8 août 2016 à 20:51, peter miller <fuchsia.groan at virgin.net> a écrit :

On Fri, 05 Aug 2016 22:30:28 +0100, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

Much of what follows is wanton pedantry, for which I apologise. ;)

I would disagree. Time isn't useful without a point in space (space and time should be looked at as one thing, not two) and since the Date object tracks it internally as UTC it has a default space.

This is incorrect. It tracks a UTC timestamp; the current time as I write this is 1470432591121 milliseconds since the epoch, and that's true regardless of where you are on the planet.

I suspect you've dropped 36s from that count or used 1st January 1970 0h 0m 36s as your epoch. I might be wrong. But if you have, it's because Universal Time is a "rotational timescale": it measures the angle of a fictitious point in the sky (U) relative to the prime meridian and not a linear count of seconds. The IERS (International Earth Rotation and Reference Systems Service) measures the angle of U and issue "Universal Time" (UT1). With UTC being kept within one second of UT1 via leap seconds (next one at the end of year).

The value an observer measures for U, called UT0, really does depend on their exact position on the surface of the earth. That has to be adjusted for. That said, UT1/UTC are notionally the same for us all. And I know that's what you were saying. Like I said, I'm showing off my very hard won knowledge.

And I might as well point out UT is defined on the earth's surface. It's affected by gravity.

Timezones are a display concept - they affect how you parse human-readable strings into timestamps, and how you display a timestamp as a human-readable string. This is similar to the distinction between Unicode values and strings encoded in UTF8.

Sure, calendar dates (year, month and day) are a human-friendly way of displaying the number rotations of U over a meridian.

But I think timezones represent a change in meridian. I believe in the olden days people would set their watches to a local meridian. (I'm reading Verne's Mysterious_Island at the moment and it contains a reference to a chronometer set to the Washington meridian.) And while the "timezones" we use in modern civil life are a political construct, I suspect a timezone is still saying display the transits of U over such-and-such a meridian; it's a shift in longitude, not directly a shift in time.

As a final unrelated comment, I wonder whether 20.3.1.1 should explicitly define javascript time values to be:

d * 86400000 + ms

Where d is the number of whole UTC days elapsed since midnight 1 January 1970 and ms is the number of milliseconds elapsed since the start of current UTC day, with the value of ms being implementation defined close to a leap second - and possibly diverging from thousandths of an SI second.

I think that's what the existing standard is trying to say but the current phrasing feels wrong, and the above makes clear that timevalues are a construct used to represent UTC not a count of millseconds since epoch (which javascript dates can never be because they exclude the 36---soon to be 37---leap seconds).

Peter

PS: Can we not add underscores to numbers? Would allowing 86_400_000 break the web?

Well, if you want to be pedantic...

You should disregard the non-normative introduction text found in 20.3.1.1, which might be slightly imprecise:

Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC. In time values leap seconds are ignored. It is assumed that there are exactly 86,400,000 milliseconds per day.

and pay more attention to the normative definitions given in 20.3.1.2:

A given time value t belongs to day number

Day(t) = floor(t / msPerDay)

where the number of milliseconds per day is

msPerDay = 86400000

The remainder is called the time within the day:

TimeWithinDay(t) = t modulo msPerDay

# Juan Ignacio Dopazo (8 years ago)

@Jordan, thanks a lot! Switching to a personal account then.

@Tab other people in this thread have explained this throughly, but in essence answering certain questions about dates require knowing thetimezone offset. For example knowing when "yesterday" was in an arbitrary timezone is not just a matter of taking a date and removing 24 hours from it. This is mostly a "presentation" issue, but not necessarily a "display" issue, which is why I'm saying it's not just something that Ecma 402 covers.

Moment-js and Intl.DateTimeFormat do a good job at displaying dates, but in order to do arithmetic on local dates we end up having to use them and parse their return values.

It should be straightforward to implement a library with a TimeZone class, same as what the ICU library has (icu-project.org apiref/icu4j/com/ibm/icu/util/TimeZone.html), based on the Unicode CLDR data. But this does look like something EcmaScript should provide. The only reason I haven't done it and published it is because we want to avoid having to ship all that historical CLDR data to the browser.