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

fix(input): allow overriding timezone for date input types #16336

Merged
merged 3 commits into from
Apr 6, 2018

Conversation

Narretz
Copy link
Contributor

@Narretz Narretz commented Nov 21, 2017

Fixes #13382
Closes #16181

What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
fix

What is the current behavior? (You can also link to an open issue here)
See #16181

What is the new behavior (if this is a feature change)?
overrideModelOptions can be used to change the timezone.

Does this PR introduce a breaking change?
No

Please check if the PR fulfills these requirements

  • The commit message follows our guidelines
  • Fix/Feature: Docs have been added/updated
  • Fix/Feature: Tests have been added; existing tests pass

Other information:

Copy link
Contributor

@petebacondarwin petebacondarwin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice work @Narretz

Copy link
Member

@gkalpak gkalpak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should document that changing the value timezone will have no effect until the next parse/format.

var previousDate;

ctrl.$$parserName = type;
ctrl.$parsers.push(function(value) {
if (ctrl.$isEmpty(value)) return null;
if (ctrl.$isEmpty(value)) {
previousDate = null;
Copy link
Member

@gkalpak gkalpak Nov 21, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this change is valid (in fact I think it is not).
AFAICT the idea of previousDate is to store the last valid date set (to use it for feeling missing date field that cannot be derived from the input value).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not valid, then why are there no test failures? :D
The formatter is doing the same thing afaict - it sets previousDate = null if the model is not a valid date (empty is not a valid date, too).
The specific test at https://github.com/angular/angular.js/pull/16336/files/d172e8ecb650ad3220e00e46b08325ad3672e9f1#diff-ce6d0f141f60d25eede7abf044d04b93R1968 shows that when clearing out the input, previousDate is still set and this leads the next input to use an incorrect value for the hour part.

We'd need a test case to show that this is a regression.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's not valid, then why are there no test failures? :D

Valid point...nooot 😛

The formatter is doing the same thing afaict - it sets previousDate = null if the model is not a valid date (empty is not a valid date, too).

That is the point. The formatter processes the model value and previousDate keeps track of the last valid (date) model value.

The specific test at https://github.com/angular/angular.js/pull/16336/files/d172e8ecb650ad3220e00e46b08325ad3672e9f1#diff-ce6d0f141f60d25eede7abf044d04b93R1968 shows that when clearing out the input, previousDate is still set and this leads the next input to use an incorrect value for the hour part.

"use an incorrect value for the hour part" is subjective. I'd say currently it uses "the correct value for the hour part".
The idea is to be able to set the hour for a date field. AFAICT, the only way to achieve this today is by setting the model to a date with the desired date. Then, as the user edits the field (including emptying and refilling it) the hour/minute/second part is preserved. I think this is a desired property (I am pretty sure I've used it in my apps 😃).
In any case, I believe this change is breaking not related to this PR. It should go into a separate PR (where it can be debated further 😁).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to be able to set the hour for a date field.

Ah, that's right. Did you get this from the code or did you search for the commit? Here's the commit: 1a1ef62

As you can see, there are not tests for type=date ... :<

This is at least confusing, because as you can see in the test, the date is re-using the hour part that was set for -0500, even though the timezone is set to UTC again. But maybe this is a contrived example.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't search for the commit (I didn't expect there to be a commit specific for that - I assumed it was part of the initial implementation) 😃

So, I am not sure what the correct behavior is here (wrt to changing the timezone) 😕
Maybe keep track of the previous timezone and re-adjust? Or accept the fact that changing the timezone may change the time part?
Timezones are tricky 😁

@@ -1456,6 +1459,7 @@ function createDateInputType(type, regexp, parseDate, format) {
}
if (isValidDate(value)) {
previousDate = value;
var timezone = ctrl.$options.getOption('timezone');
if (previousDate && timezone) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (and unrelated to this PR): previousDate will always be truthy here (else isValidDate() would be false).

inputElm.controller('ngModel').$overrideModelOptions({timezone: '-0500'});

$rootScope.$apply(function() {
$rootScope.value = new Date(Date.UTC(2014, 6, 1));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the year change (here and below)? I would stick to the minimum necessary changes to avoid confusion.

@Narretz
Copy link
Contributor Author

Narretz commented Nov 21, 2017

Regarding the docs - any property changes in ngModelOptions only have an effect after the next update. We could add this info in overrideModelOptions generally.

@Narretz
Copy link
Contributor Author

Narretz commented Nov 23, 2017

I've removed the date reset and added a different fix that should account for changing timezones. I'm not 100% happy with the implementation, but I also don't want to spend too much time on this.

@Narretz Narretz modified the milestones: 1.6.x, 1.6.8 Nov 30, 2017
@Narretz
Copy link
Contributor Author

Narretz commented Dec 1, 2017

@gkalpak could you give this another look?

helper.changeInputValueTo('2013-W03');
expect(+$rootScope.value).toBe(Date.UTC(2013, 0, 17));

inputElm.controller('ngModel').$overrideModelOptions({timezone: '+5000'});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really supposed to be +5000?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revised the test and added more comments.

inputElm.controller('ngModel').$overrideModelOptions({timezone: '+5000'});

$rootScope.$apply(function() {
// the 17. with an offset of +5000 moves the date into next week
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you expand a bit on this comment? It mentions 17. (which is in German format 😛), but the code uses 18. Also, how does it move to the next week?


inputElm.controller('ngModel').$overrideModelOptions({timezone: 'UTC'});
helper.changeInputValueTo('23:02:00');
// The year is still set from the previous date
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@@ -1879,6 +1966,30 @@ describe('input', function() {
dealoc(formElm);
});

it('should not reuse the hour part of a previous date object after changing the timezone', function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how this test checks that the hour part of the previous date object isn't used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also Revised this test and added more comments.

// Note: We cannot read ctrl.$modelValue, as there might be a different
// parser/formatter in the processing chain so that the model
// contains some different data format!
if (timezone && previousTimezone && previousTimezone !== timezone) {
// If the timezone has changed, adjust the previousDate to the default timzeone
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

timzeone --> timezone

// Note: We cannot read ctrl.$modelValue, as there might be a different
// parser/formatter in the processing chain so that the model
// contains some different data format!
if (timezone && previousTimezone && previousTimezone !== timezone) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need timezone or previousTimezone to be truthy? Don't we always need to adjust as long as previousTimezone !== timezone?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do need previousTimezone to be defined, since we pass it to addDateMinutes. timezone must not be defined, though.

Copy link
Contributor Author

@Narretz Narretz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've revised the tests in hope that they are clearer now, or at least it's easier to understand them. Let me know if I have failed :D

// Note: We cannot read ctrl.$modelValue, as there might be a different
// parser/formatter in the processing chain so that the model
// contains some different data format!
if (timezone && previousTimezone && previousTimezone !== timezone) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do need previousTimezone to be defined, since we pass it to addDateMinutes. timezone must not be defined, though.

helper.changeInputValueTo('2013-W03');
expect(+$rootScope.value).toBe(Date.UTC(2013, 0, 17));

inputElm.controller('ngModel').$overrideModelOptions({timezone: '+5000'});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revised the test and added more comments.

@@ -1879,6 +1966,30 @@ describe('input', function() {
dealoc(formElm);
});

it('should not reuse the hour part of a previous date object after changing the timezone', function() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also Revised this test and added more comments.


//To check that the timezone overwrite works, apply an offset of +24 hours.
//Since January 19 is a Saturday, +24 will turn the formatted Date into January 20 - Sunday -
//which is in calendar week 4 instead of 3.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing whitspace after // 😇


// Emptying the input should clear the cached date object
helper.changeInputValueTo('');
// At this point, the cached Date has its hours set to to 20 (00:00 - 05:00 = 19:00)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am confused 😕
Are you saying that 19 === 20?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, just a result of trying to understand timezones. If you log a date, you always get it with the local timezone offset which is +1 for me, which means 20. But 19 is correct.

@Narretz Narretz force-pushed the fix-input-timezone branch from e843205 to 347a1aa Compare December 5, 2017 13:16
@Narretz Narretz modified the milestones: 1.6.8, 1.6.9 Jan 4, 2018
@Narretz Narretz modified the milestones: 1.6.9, 1.6.10 Feb 2, 2018
Narretz added 3 commits April 6, 2018 13:07
This commit also fixes a bug where part of the Date object
was re-used even after the input was emptied.

Fixes angular#16181
Closes angular#13382
@Narretz Narretz force-pushed the fix-input-timezone branch from 7a9447c to 6e618ef Compare April 6, 2018 11:19
@Narretz Narretz merged commit 3a2aea7 into angular:master Apr 6, 2018
Narretz added a commit that referenced this pull request Apr 6, 2018
This commit also fixes a bug where part of the Date object
was re-used even after the input was emptied.

Fixes #16181
Closes #13382
Closes #16336
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
4 participants