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

Add date timezone (z, Z, O, x, X) support #202

Closed
wants to merge 5 commits into from

Conversation

rxaviers
Copy link
Member

Fixes #196.

Format

Native Date Object

  • Zone z - fall back to O
  • Zone Z
  • Zone O
  • (skip) Zone v
  • (skip) Zone V
  • Zone X
  • Zone x

Extended Olson-timezone-supported Date Object see #340

Parse

  • Zone z
  • Zone Z
  • Zone O
  • Zone X
  • Zone x

Extended Olson-timezone-supported Date Object see #340

Fix issues:


Docs:

@rxaviers
Copy link
Member Author

The commits above partially implement the timezone support. It's still a work in progress... It happens that timezones in JavaScript are so messed up, and ECMA-402 provides no better landscape, that this work has been halted until we brainstorm something (or, I/we learn more about what exists) to figure out what to do about it.

Timezone rules vs. zones

CLDR follows the Olson time zone database and naming conventions (with slightly modifications). See UTS TR35.

Olson database has two definitions: rules and zones. Rules represent transitions between standard and daylight saving time (or other stuff like war time, or peace time). Zones represent transitions between rules, eg. Asia/Gaza follows Zion, EgyptAsia, or Palestine rules depending of the date. Zones define the offset from GMT, and the abbreviated name to use (eg. EST).

Formatting vs. Native Date Object

CLDR timezone patterns Z, O, x, X can be (and have mostly been) implemented just fine, although z, v, V do not have trivial implementation using native JavaScript Date object due to the following.

Date object has a method called .getTimezoneOffset() that returns the timezone offset. But, it lacks a method to properly identify the timezone name (also called metazone by CLDR), eg. America/New_York. It's deducible. But, it's not a trivial operation. See below.

Sidenote: User envorinment's timezone (not date's timezone) name can be obtained on Chrome using Intl.DateTimeFormat().resolvedOptions().timeZone. But, AFAIK, this is not in the specs, nor it has been implemented in any other browser but Chrome (eg. Firefox, Safari, or IE).

Some libraries (eg. Dojo) use the timezone string excerpt provided by .toString() or .toLocaleString(), which provides the abbreviated timezone name, to guess the long timezone name. Although, it's not a unique name, eg. CST could stand for "Central Standard Time", or "China Standard Time"; or EST could stand for North American "Eastern Standard Time", or "Australian Eastern Standard Time". See a full list on https://gist.github.com/rxaviers/8481876.

Some libraries (eg. jsTimezoneDetect) use the same string above plus the timezone offset to better guess its identification. Although, it's still not sufficient, eg. America/Sao_Paulo, and America/Bahia have the same abbreviated timezone BRT during parts of the year, and they have the same offset 180. But, they follow different timezone rules.

It's possible to overcome the previous issue by also analyzing the tuple (abbreviated timezone, offset) in different periods of time. But, the analysis requires different attempts depending of the case. Follow an example to illustrate. Let's consider the following excerpt of the BRT (-3:00) timezone from Olson database for this example:

Zone America/Maceio -2:22:52 -  LMT 1914
            -3:00   Brazil  BR%sT   1990 Sep 17
            -3:00   -   BRT 1995 Oct 13
            -3:00   Brazil  BR%sT   1996 Sep  4
            -3:00   -   BRT 1999 Sep 30
            -3:00   Brazil  BR%sT   2000 Oct 22
            -3:00   -   BRT 2001 Sep 13
            -3:00   Brazil  BR%sT   2002 Oct  1
            -3:00   -   BRT

Zone America/Bahia  -2:34:04 -  LMT 1914
            -3:00   Brazil  BR%sT   2003 Sep 24
            -3:00   -   BRT 2011 Oct 16
            -3:00   Brazil  BR%sT   2012 Oct 21
            -3:00   -   BRT

Zone America/Sao_Paulo  -3:06:28 -  LMT 1914
            -3:00   Brazil  BR%sT   1963 Oct 23 00:00
            -3:00   1:00    BRST    1964
            -3:00   Brazil  BR%sT

If new Date(2014,0,1) is Wed Jan 01 2014 00:00:00 GMT-0200 (BRST), it's America/Sao_Paulo, because it's the only one of all above that follows Brazil daylight rules on 2014.

If new Date(2014,0,1) is Wed Jan 01 2014 00:00:00 GMT-0300 (BRT), it could be America/Bahia, or America/Maceio. Because, both are following no daylight saving rules on 2014. Although, each of these zones has different rules background. So, it's possible to deduce the correct zone trying some more attempts:

$ TZ=:/usr/share/zoneinfo/America/Maceio node
> new Date(2003,0,1)
Wed Jan 01 2003 00:00:00 GMT-0300 (BRT)
> new Date(2002,0,1)
Tue Jan 01 2002 00:00:00 GMT-0200 (BRT)
> new Date(2001,0,1)
Mon Jan 01 2001 00:00:00 GMT-0300 (BRT)
> new Date(2000,0,1)
Sat Jan 01 2000 00:00:00 GMT-0200 (BRT)

$ TZ=:/usr/share/zoneinfo/America/Bahia node
> new Date(2003,0,1)
Wed Jan 01 2003 00:00:00 GMT-0200 (BRT)
> new Date(2002,0,1)
Tue Jan 01 2002 00:00:00 GMT-0200 (BRT)
> new Date(2001,0,1)
Mon Jan 01 2001 00:00:00 GMT-0200 (BRT)
> new Date(2000,0,1)
Sat Jan 01 2000 00:00:00 GMT-0200 (BRT)

One last challenge relates to cross-compatibility of .toString() and .toLocaleString() on different environments (different browsers, different browser versions, different operating systems).

Formatting vs. Extended Olson-timezone-supported Date Object

The CLDR timezone patterns z, v, V (that can't be implemented using the native JavaScript Date object as pointed out above, or at least, not as easily) can have its implementation straightforward if it relies on top of an extended Date object that returns the Olson timezone name and handles (and abstracts) all the complexities above.

Parsing vs. Native Date Object

It's not possible to set a timezone on a native Date object other than the automatically set timezone from user/browser environment. So, it turns out that the only goal parsing date timezones into a native Date instance is: to normalize the date according to the parsed timezone.

Parsing vs. Extended Olson-timezone-supported Date Object

Considering we can rely on top of an extended Date object that fully supports Olson timezone, it doesn't make sense to change date according to parsed timezone (as done above). But, the goal here should be to properly set the date instance's timezone the best way possible.

For difficulties similar to the ones presented above on "Format vs. Native Date Object", it won't be possible to precisely parse some timezone strings into it's corresponding zone.

@rxaviers
Copy link
Member Author

Making long story short: right now, I think the best approach for Globalize is NOT to try to overcome the issues. But, have two modules: (a) the normal date module, which will provide a simple support for date timezone format/parse (skipping some patterns), and (b) enhanced date timezone module, which will rely on a third-party library that supports Olson timezones, and therefore support all CLDR timezone patterns.

@scottgonzalez
Copy link
Contributor

+1

@rxaviers
Copy link
Member Author

I want to bring in the talk I'm having with @ichernev about the basic timezone support ( (a) module above). So, we can have more eyeballs into it, and possibly more ideas.

This is about defining the behavior of the basic timezone formatting and parsing.

parsing

Our conception so far of the basic timezone parsing support is to convert the given date input (String) into a native date object by normalizing the given timezone. For example, "2014-03-24T12:15:41+06:00" is converted into Mon Mar 24 2014 03:15:41 GMT-0300 (BRT) object if my local environment is GMT-0300 (BRT). Note this is exactly what's done natively, ie. new Date( "2014-03-24T12:15:41+06:00" ) produces such date object.

The thing is, user loses trace of the original timezone offset. But, @ichernev says it should be preserved in his view (and considering what moment.js users have asked him in the past).

formatting

Our conception so far of the basic timezone formatting support is to allow Z, O, x, X patterns only (the numeric ones), and to use the object's .getTimezoneOffset() as the input for the zone offset.

The thing is, this approach doesn't allow formatting any time with a zone offset other than the user's environment one.

@ichernev says that moment.js users have asked him in the past the ability to do so, even if its not DST aware.

API?

The question is: how this should happen?

At a very high level perspective, we are clearly dealing with two problems: (a) I18n (formatting & parsing timezones), and (b) Fix dates in javascript (allowing arbitrary zones). My personal feeling is that mixing them both together won't help. I definitely think it's important to address @ichernev statements above, and I believe that a good solution would address them separately.

@rxaviers
Copy link
Member Author

A proposal.

Extended Olson-timezone-supported Date Object should implement:

  • ExtendedDate.prototype.setTimezoneOffset( offset )
  • ExtendedDate.prototype.getTimezone() -> zoneName
  • ExtendedDate.prototype.setTimezone( zoneName )

Globalize will set Globalize.Date = Date as default. User can override this by setting Globalize.Date = ExtendedDate.

parsing

date = new Globalize.Date(), and:

  1. If no timezone info is found on the string, set time, ie. local time.
  2. If a zone offset is found (or it's an ISO UTC time):
    2.1. If date.setTimezoneOffset() exists, set zone, then set time with no adjustments made by Globalize;
    2.2. Otherwise, set time with offset adjustments made by Globalize;
  3. If a zone name is found:
    3.1. If date.setTimezone( zoneName ) exists, set the zone, then set time with no adjustments by Globalize;
    3.2. Otherwise, throw an error.

formatting

date is passed in the first function argument.

  1. On Z, O, x, X (the numeric zone) patterns, use date.getTimezoneOffset() as input offset.
  2. On z, v, V (the named zone) patterns, use:
    2.1. If date.getTimezone() exists, use it as the input zone.
    2.2. Otherwise, throw an error.

conclusion

Using this "simple" scheme, we're able to attend everyone in a more flexible way. I mean, we could roll back into the idea of having one only date module, which will attend all worlds: (a) users not caring about timezone, which can use native Date, ie. do nothing (b) users caring about setting arbitrary time offsets, but not caring about named (DST aware) Olson zones, which can use a partially extended Date (c) users really caring about Olson zones, which can use the extended Date with Olson support.

@ichernev, I see moment-cldr as a b user, and moment-timezone as a c user. What do you think of all that? Thanks

@ichernev
Copy link

A few notes.

  • moment by default does not set the offset found in the string, but has the ability to do so. Most people are ok with the time adjusting in local zone, but a small percentage want to keep the zone. More importantly moment has the ability to be in a different (non local) timezone.
  • how would you determine that you see a timezone string, if you don't have Oslon data (3.2 case). I guess it can be a short named one (BRT, which means -3, but also Europe/London which is a Zone Set (in moment-timezone terminology)). Having all those strings hard coded, without the rules seems stupid :)

I like the proposed solution. We can include a b) level implementation for Date, that just stores the set offset, but actually adjusts the time (so you basically get a Date + an offset).

@rxaviers
Copy link
Member Author

how would you determine that you see a timezone string, if you don't have Oslon data (3.2 case). I guess it can be a short named one (BRT, which means -3, but also Europe/London which is a Zone Set (in moment-timezone terminology)). Having all those strings hard coded, without the rules seems stupid :)

Excellent question and I agree with you that it's stupid to hard code that information into the source code.

My idea is to deduce that from the CLDR data (note that zone names are kept in a separate file main/<lang>/timeZoneNames.json). So, it's up to the user whether or not to include that. If there's data, parser will be able to identify the zone names (by following spec: items 4..8), and it would eventually enter into 3.x case above. If there are no data, parser won't be able to identify any zone name anyway, therefore it will never enter into the 3.x case.

I like the proposed solution. We can include a b) level implementation for Date, that just stores the set offset, but actually adjusts the time (so you basically get a Date + an offset).

That's awesome.

rxaviers added a commit that referenced this pull request Oct 10, 2014
@rxaviers rxaviers closed this in 9dd3cd0 Oct 10, 2014
@rxaviers rxaviers changed the title Add date timezone (z, Z, O, v, V, x, X) support Add date timezone (z, Z, O, x, X) support Oct 10, 2014
This was referenced Dec 11, 2014
@rxaviers rxaviers mentioned this pull request Feb 15, 2015
ashensis pushed a commit to ashensis/globalize that referenced this pull request Mar 17, 2016
…tribute_delegation

Formtastic inputs for translated attributes don't get type information
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Date module - handle timezone
3 participants