Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

1.3.0 input[date] model is a Date object in local time zone instead of UTC #8447

Closed
estekhin opened this issue Aug 1, 2014 · 43 comments · Fixed by #8629
Closed

1.3.0 input[date] model is a Date object in local time zone instead of UTC #8447

estekhin opened this issue Aug 1, 2014 · 43 comments · Fixed by #8629

Comments

@estekhin
Copy link

estekhin commented Aug 1, 2014

Please check this plunker for a demonstration.

Assuming the computer time zone is not UTC and the browser supports input[date], select a date from the input[date] dropdown.

The input field will display a date in the localized format, for example "01.08.2014" in my case. My current timezone is GMT+4.

value is

"2014-07-31T20:00:00.000Z"

but expected is either

"2014-07-31T20:00:00.000-0400"

or

"2014-08-01T00:00:00.000Z"

It seems that the code that converts input[date].value into a Date model object first obtains the correct value "2014-07-31T20:00:00.000-0400" but then replaces the correct "-0400" suffix with incorrect "Z", or it is a matter of formatting when the date is formatted as localized but the timezone is not part of the format and instead the "Z" suffix is hardcoded..

The code that converts the string value of the input[date] into a Date model should follow the HTML5 input[type=date] guidelines and convert the value string as if it is the start of the day in the UTC regardless of the current timezone.

@petebacondarwin
Copy link
Contributor

@estekhin - Thanks for reporting this issue. Has this ever worked in a previous version of AngularJS?

@petebacondarwin
Copy link
Contributor

I believe this is a valid issue and can be fixed by changing the following line: https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L1048

return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm);

to

return new Date(Date.UTC(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm));

But I am not sure how this would affect the parsing of other date/time inputs. Some investigation is needed.

@petebacondarwin petebacondarwin added this to the 1.3.0 milestone Aug 2, 2014
@estekhin
Copy link
Author

estekhin commented Aug 2, 2014

I think that angular 1.2 does not provide special handling for input[date], so in 1.2 ngModel returns the original string value.

@caitp
Copy link
Contributor

caitp commented Aug 2, 2014

the date inputs are not implemented in 1.2 at all, so there is no use backporting any fixes there.

@estekhin
Copy link
Author

estekhin commented Aug 4, 2014

@petebacondarwin why simple new Date(string value) can't be used? It correctly parses the string returned from input[date].value and returns a date object.

Check http://plnkr.co/edit/tkAyCBoILXQWvC84U2SV?p=preview for a demo - plain input value and a Date object created from it have correct value, while angular model mis-adjusts time-zone and returns a value which can be off-by-one day.

@estekhin
Copy link
Author

estekhin commented Aug 4, 2014

Related to this issue - if i am fine with input[date] "as is" with the string model, will i be able to disable special input[date] handling in angular 1.3? Is this being discussed somewhere or should i raise a new issue/feature request for this?

@petebacondarwin
Copy link
Contributor

@estekhin - It would be better if we fixed this but you could create an additional input directive that cleared out the $parsers and adds your own. Something like this:

angular.directive('input', function() {
  return function postLink(scope, element, attrs, ngModel) {
    if ( attrs.type !== 'date' || !ngModel ) { return; }
    ngModel.$parsers = [
      function myDateParser(val) { ... }
    ];
  });
});

@petebacondarwin
Copy link
Contributor

Actually from looking at the createDateParser function it appears that if it already has a Date object then it just returns it. So you can even just add your own date parser to the front of the $parsers array and it will supercede the parsing of the built in input directive.

@petebacondarwin
Copy link
Contributor

@tbosch was involved in the original implementation, perhaps he can have a view on this?

@estekhin
Copy link
Author

estekhin commented Aug 4, 2014

Playing with the http://plnkr.co/edit/tkAyCBoILXQWvC84U2SV?p=preview in IE11 which allows entering arbitrary text into input[date].

Entering invalid "2014-00-00" as input.
The input[date].value is the same invalid string "2014-00-00", and new Date(2014-00-00") returns a object that represents "invalid date".
But angular model returns a normal Date object that represents "2013-11-29T20:00:00.000Z". From some point of view it is "correct" if treating "2014-00-00" as "2014-01-01" minus one month and one day, but it is really unexpected when moving from string model to Date model.

Entering invalid "2014-33-98" results in similar results, with input[date].value = "2014-33-98", invalid Date object, and angular model reporting date as "2016-12-06".

I see that input[date] and input[time] was discussed #5864 and issues linked from it but it is already closed. What would be a proper way to propose changes to handling of all time- and date-related inputs in 1.3?

@estekhin
Copy link
Author

estekhin commented Aug 5, 2014

Tried to use 1.3 snapshot in the real code and, as expected, got unspoken number of problems due to Date values instead of strings and due to actual date values being off-by-one =).

Please consider these requests/requirements:

  1. fix string-to-date conversion so that the resulting Date correlates with algorithms defined in the html5 spec
  • if the string value is valid, then Date is exact and in UTC
  • if the string value is invalid (including out of range values for month or day), then Date is NaN (or whatever is used to report invalid date).
  1. provide a way to disable special handling (especially converting from string to some object) provided by new input[date] and input[time] directives, so that the ngModel value will remain a string. The mechanism to disable probably can be based on ngModelOptions.

  2. consider using hash/object as a model for input elements, with model.value being the plain string value from the input element, and model.data being the parsed object. This can be considered for any kind of input[type] elements.

At the moment i am using the following hack to force the input[date] ngModel to remain a string:

app.directive( 'input', function() {
    return {
        restrict : 'E',
        require : ['?ngModel'],
        priority : 100,
        link : function( scope, element, attr, ngModel ) {
            if ( attr.type === "date" ) {
                var model = ngModel[0];
                model.$parsers.length = 0;
                model.$parsers.push( ng.identity );
                model.$formatters.length = 0;
                model.$formatters.push( ng.identity );
            }
        }
    };
} );

With that hack in place my code seems to work with 1.3 snapshot without any other problems.

@ilplotkin
Copy link

+1 for requests from previous comment.
We develop/maintain several applications based on string ng-model of date/time inputs.
To migrate to Angular 1.3 we had to introduce the hack similar to the one from previous comment.

@estekhin
Copy link
Author

Possible solution:

there are a dozen of places inside Angular code where the Date constructor with two or more parameters (new Date(year,month,,,)) is used.

replacing all new Date(year,month,,,) with new Date(Date.UTC(year,month,,,)) results in the correct UTC-based value.

@estekhin
Copy link
Author

Seems like I am a bit slow as new Date(Date.UTC()) trick was already suggested by @petebacondarwin

Two additional demos:

1.2.21-based: http://plnkr.co/edit/WdNVxblrBPQPiSNTOIPA?p=preview
the model value corresponds to the actual input both for input[date] and input[time]

1.3-based: http://plnkr.co/edit/ja9UjP9rtJanYrJCVxMo?p=preview
the model value for input[date] is off by one when the computer timezone is in -HH:MM category.
the model value for input[time] is off by timezone offset (and the date part is wrong, should be 1970-01-01)

after some reflection i think that i want two things:

  1. fixed input[date] and input[time] values
  2. an ability to completely disable ALL special handling for ALL text-based input.type values - if HTML spec says the input value is text, i want it as text inside ngModel. In extreme case i would question the utility of special handling at all and even would call it feature creep and suggest to remove all special support for input[date/time/datetime/month/week]

@tbosch
Copy link
Contributor

tbosch commented Aug 15, 2014

Hi @estekhin
I agree, we should fix the off-by-one problem in the Date parsing as suggested.

And in the case where the browser does not support native input[type=date] we should use the date format of the locale for parsing the date (ngLocale already contains the date format, see e.g. https://github.com/angular/angular.js/blob/master/src/ngLocale/angular-locale_de-be.js#L75).

@estekhin Would that also solve your problem? If this is fixed, would you still want the raw string that the user entered? If yes, when are you then parsing the Date?

@tbosch
Copy link
Contributor

tbosch commented Aug 15, 2014

@Blesh You said that you started on implementing a generic date parser / serializer based on ngLocale. Do you still have this somewhere?

@estekhin
Copy link
Author

@tbosch hi,

in my particular case I am using plain string input[date] value as is as one of the parameters in the GET/POST requests. It works very good in modern browsers which always return a yyyy-MM-dd string (and not so good in old browsers which treat input[date] as input[text] and return whatever the user entered).

assuming that ngModel will be a Date with the proper value, converting it into a string is not a problem.

the off-by-one is the real pain because one has to either replace the parser/formatter to "identity" as in the hack above or to correct the Date using the getTimezoneOffset(). Of course it would be better to fix it in inside angular instead of everyone inventing their own ways to deal with it.

tbosch added a commit to tbosch/angular.js that referenced this issue Aug 15, 2014
Angular used to use the `new Date(month, date, year)` constructor to
create `Date` objects in `input[type=date]` et al, which uses the 
current timezone of the browser. However, the HTML5 spec 
defines the timezone for for `input[type=date]` to be UTC.
This commit changes the parsing to always use the 
UTC timezone.

Closes angular#8447.
@benlesh
Copy link
Contributor

benlesh commented Aug 15, 2014

@tbosch - I can look, but I seriously doubt I still have that sitting around. It was a standalone module I made in a plnkr almost a year ago. The basics of it was it was examining the locale strings and creating Regexps from them, which were then used to test and parse the date strings.

@benlesh
Copy link
Contributor

benlesh commented Aug 15, 2014

Wow! I found it... Here's a Gist I pulled from the original Plunker:

https://gist.github.com/blesh/accff2313c7998b65727

@benlesh
Copy link
Contributor

benlesh commented Aug 15, 2014

I have no idea what state it's in, but as I recall I had it mostly or partially working at some point. There are definitely better ways to do this if you were to build something into Angular core. Also, it could be leveraged in those date/time related directives I added a while back.

@pkozlowski-opensource
Copy link
Member

@Blesh @tbosch we've got a similar parser as part of https://github.com/angular-ui/bootstrap here although it handles dates only.

I think that the whole AngularJS ecosystem would benefit from a standalone / published / maintained parsing routine and I could spend some time collaborating on this. Anyone interested in joining efforts?

@shahata
Copy link
Contributor

shahata commented Aug 16, 2014

I might be totally wrong here, but it seems to me that using element.valueAsDate will save a whole lot of effort here (assuming it works correctly in all supported browsers). The problem is that ngModel is really tightly coupled with element.value, so I don't know if it is worth the effort...

@shahata
Copy link
Contributor

shahata commented Aug 16, 2014

Hmmm, on second thought it might be easier than I thought. I'll try to look into that.

@estekhin
Copy link
Author

@shahata input[type].valueAsDate:
Chrome 36 - supported, returns proper value
FireFox 31 - returns "undefined"
IE 11 - returns "undefined"

@shahata
Copy link
Contributor

shahata commented Aug 16, 2014

Those browsers do not support date/time inputs afaik, so I guess that's okay...

@btford btford removed the gh: issue label Aug 20, 2014
tbosch added a commit to tbosch/angular.js that referenced this issue Aug 25, 2014
Angular used to always use the browser timezone for
`dateFilter`. An additional parameter was added to allow to use
`UTC` timezone instead.

Related to angular#8447.
tbosch added a commit to tbosch/angular.js that referenced this issue Aug 25, 2014
Angular used to always use the browser timezone when parsing
`input[date]`, `input[time]`, … The timezone can now be changed
to `UTC` via `ngModelOptions`.

Closes angular#8447.
@tbosch
Copy link
Contributor

tbosch commented Aug 25, 2014

Hi,
ok, thought about this for a while with the following conclusion: Lets parameterize the timezone!

Via that, people can continue to get a Date instance in the browser timezone, but they can change it to use UTC via ngModelOptions. I also added a timezone parameter to the dateFilter that allows to specify UTC as timezone.

Making this configurable is good as I can see usecases for both timezones... Updates the attached PR as well.

tbosch added a commit to tbosch/angular.js that referenced this issue Aug 25, 2014
Angular used to always use the browser timezone when parsing
`input[date]`, `input[time]`, … The timezone can now be changed
to `UTC` via `ngModelOptions`.

Closes angular#8447.
@tbosch
Copy link
Contributor

tbosch commented Aug 25, 2014

Ah, and decided not to include the generic date parser into Angular core...

@benlesh
Copy link
Contributor

benlesh commented Aug 25, 2014

The generic date parser is a separate issue for another time. I agree it shouldn't be in core. Most people won't need it. For those that do, though, perhaps a separate module would do? Although at that point, arguably you could just use Moment.

@tbosch
Copy link
Contributor

tbosch commented Aug 26, 2014

Regarding why we use the browser timezone as default:

  • input[time] does no timezone conversion on Chrome and iOS, the major browsers that support this feature (see http://caniuse.com/#feat=input-datetime). I.e. if a user enters 11:10 am in the UI, then input.value will be 11:10. This means that those browsers do not convert the time to UTC, so using the browser timezone to create the Date object is correct for them. Same is true for input[date].
  • input[datetime] is not supported in Chrome and neither on iOS, only input[datetime-local] is.

So apparently browsers report the date and time for input.value in the browser timezone, and not in UTC. However, they do assume UTC timezone when reading input.valueAsDate.

Thinking about building an app where a user enters a date and time, it feels more natural to assume that he enters that time and date in his local date and time.

@estekhin
Copy link
Author

Please reconsider using UTC for both input[date] and input[time] for consistency.

Using UTC for input[date] and local timezone for input[time] will be surprising:

  • one input type returns a value in UTC, the other in random timezone.
  • one input type follows HTML spec, the other does not.
  • if or when browsers will support .valueAsDate and .valueAsNumber properties, the angular model for one input will correspond to these values, the other will not.

tbosch added a commit to tbosch/angular.js that referenced this issue Aug 26, 2014
Angular used to always use the browser timezone for
`dateFilter`. An additional parameter was added to allow to use
`UTC` timezone instead.

Related to angular#8447.
tbosch added a commit to tbosch/angular.js that referenced this issue Aug 26, 2014
Angular used to always use the browser timezone when parsing
`input[date]`, `input[time]`, … The timezone can now be changed
to `UTC` via `ngModelOptions`.

Closes angular#8447.
@tbosch
Copy link
Contributor

tbosch commented Aug 26, 2014

Sorry, I did not make this clear: We are using local timezone for all date/time related widgets now, but it can be overridden using ngModelOptions.timezone.

tbosch added a commit to tbosch/angular.js that referenced this issue Aug 26, 2014
Angular used to always use the browser timezone when parsing
`input[date]`, `input[time]`, … The timezone can now be changed
to `UTC` via `ngModelOptions`.

Closes angular#8447.
tbosch added a commit to tbosch/angular.js that referenced this issue Aug 26, 2014
Angular used to always use the browser timezone for
`dateFilter`. An additional parameter was added to allow to use
`UTC` timezone instead.

Related to angular#8447.
tbosch added a commit to tbosch/angular.js that referenced this issue Aug 26, 2014
BREAKING CHANGE:

According to the HTML5 spec `input[time]` should create dates
based on the year 1970 (used to be based on the year 1900).

Related to angular#8447.
tbosch added a commit to tbosch/angular.js that referenced this issue Aug 26, 2014
Angular used to always use the browser timezone when parsing
`input[date]`, `input[time]`, … The timezone can now be changed
to `UTC` via `ngModelOptions`.

Closes angular#8447.
tbosch added a commit to tbosch/angular.js that referenced this issue Aug 26, 2014
…-local]`

The HTML5 spec allows to use seconds for `input[time]` and `input[datetime-local]`,
even though they are not displayed by all browsers.

Related to angular#8447.
@fhofer
Copy link

fhofer commented Mar 4, 2015

I'm not sure if I don't understand or if it doesn't work right for me on Galaxy S3 but for me the string and also Android's input dialogs ar incorrect, even though the browser's locale is correct (alert(new Date()) shows "...GMT+0100 (CET)").
But the unformatted date output still says ".....000Z" and the HTML input shows 2015-03-04 instead of 04.03.2015 with ng-model-options="{timezone: 'UTC'}" (tried GMT+0100 too). In the Android's date selector dialog the date is correctly formatted but in both date and time selector dialogs, the "Clear" button is in english instead of german and in the time selector dialog, the time selection goes from 1 to 12 with AM/PM instead of 0 to 23 without AM/PM.

BTW: In another place I have a Date() that stands for a duration and which I build by new Date(dateB-dateA) which then for example outputs "1970-01-01T01:00:00.000Z" which is "correct". But when I use formating "HH:mm" the output is "02:00" instead of "01:00" :/

thx

@konsumer
Copy link

konsumer commented Jun 2, 2016

I think it's still incorrect that date & time don't have the same format (correct Date Object.) I made a codepen to illustrate.

If I set it to today, at 1AM I get different timezones for the 2 inputs, both of which are not UTC.

Wed Jun 01 2016 00:00:00 GMT-0700 (Pacific Daylight Time) 
Thu Jan 01 1970 01:00:00 GMT-0800 (Pacific Standard Time) 

I can adjust for this by subtracting the timezone offset of each, then adding the local timezone offset when I add the 2 together, but this is incorrect behavior. They should both agree on the timezone.

@gkalpak
Copy link
Member

gkalpak commented Jun 2, 2016

@konsumer, in both it is your local timezone with and without daylight saving. This is because the dates are different. We can't really fix this.

@konsumer
Copy link

konsumer commented Jun 2, 2016

These are just 2 elements in the same page. As you can see in the valFixed code, if I strip the zone offset from both, then add it from new Date().getTimezoneOffset() * 60000 it works fine, but I shouldn't have to do this. The date objects attached to the ng-model of date and time inputs shouldn't default to different timezones.

@konsumer
Copy link

konsumer commented Jun 2, 2016

As you can see, outFixed is the correct value, but only after I strip the timzone from both, then add the local timezone:
screen shot 2016-06-02 at 3 20 52 am

Time input defaults to PST, when everything else defaults to PDT. This seems like an obvious bug.

@petebacondarwin
Copy link
Contributor

@konsumer if you feel this is still a problem. Please open up a new issue. If you could provide a broken unit test that needs fixing that would be even better.

@konsumer
Copy link

konsumer commented Jun 2, 2016

Upon more thought, I'm not sure what it should do instead. I think I initially misunderstood what @gkalpak was saying. The reason the timezone is different is that pacific changes based on the time of the year (not just location) so, January 1st (1970) is a different timezone. If I want to use it as a simple addition of milliseconds, I can set the timezone of the time-input to UTC, or adjust it after (as I did in codepen,) I guess I expected the default to be UTC for time, so it would add correctly, but I think I understand why it's not. I will just remember this gotcha, in the future, and set my time-input zones to UTC or process them after.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.