From a897a67cf1eee6f524dce91c5e0d31b1ad8601b3 Mon Sep 17 00:00:00 2001 From: Michael Prentice Date: Fri, 28 Aug 2020 01:01:10 -0400 Subject: [PATCH] fix(calendar, datepicker): fix issues with GMT+X timezones - simplify `$$mdDateUtil.removeLocalTzAndReparseDate()` Fixes #12000 --- src/components/datepicker/js/calendar.js | 12 +++++++++--- src/components/datepicker/js/dateUtil.js | 9 ++------- .../datepicker/js/datepickerDirective.js | 18 +++++++++++++++--- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/components/datepicker/js/calendar.js b/src/components/datepicker/js/calendar.js index e2423e4274b..004303a50ac 100644 --- a/src/components/datepicker/js/calendar.js +++ b/src/components/datepicker/js/calendar.js @@ -301,7 +301,7 @@ /** * Sets up the controller's reference to ngModelController. * @param {!ngModel.NgModelController} ngModelCtrl Instance of the ngModel controller. - * @param {Object} inputDirective Config for Angular's `input` directive. + * @param {Object} inputDirective Config for AngularJS's `input` directive. */ CalendarCtrl.prototype.configureNgModel = function(ngModelCtrl, inputDirective) { var self = this; @@ -326,7 +326,7 @@ // In the case where a conversion is needed, the $viewValue here will be a string like // "2020-05-10" instead of a Date object. if (!self.dateUtil.isValidDate(value)) { - convertedDate = self.dateUtil.removeLocalTzAndReparseDate(new Date(this.$viewValue)); + convertedDate = self.dateUtil.removeLocalTzAndReparseDate(new Date(value)); if (self.dateUtil.isValidDate(convertedDate)) { value = convertedDate; } @@ -360,7 +360,13 @@ var value = this.dateUtil.createDateAtMidnight(date); this.focusDate(value); this.$scope.$emit('md-calendar-change', value); - this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd', timezone), 'default'); + // Using the timezone when the offset is negative (GMT+X) causes the previous day to be + // selected here. This check avoids that. + if (timezone == null || value.getTimezoneOffset() < 0) { + this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd'), 'default'); + } else { + this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd', timezone), 'default'); + } this.ngModelCtrl.$render(); return value; }; diff --git a/src/components/datepicker/js/dateUtil.js b/src/components/datepicker/js/dateUtil.js index b848d1e0e2a..9836bbafb2d 100644 --- a/src/components/datepicker/js/dateUtil.js +++ b/src/components/datepicker/js/dateUtil.js @@ -311,15 +311,10 @@ /** * @param {Date} value date in local timezone - * @return {Date} date with local timezone removed + * @return {Date} date with local timezone offset removed */ function removeLocalTzAndReparseDate(value) { - var dateValue, formattedDate; - // Remove the local timezone offset before calling formatDate. - dateValue = new Date(value.getTime() + 60000 * value.getTimezoneOffset()); - formattedDate = $mdDateLocale.formatDate(dateValue); - // parseDate only works with a date formatted by formatDate when using Moment validation. - return $mdDateLocale.parseDate(formattedDate); + return $mdDateLocale.parseDate(value.getTime() + 60000 * value.getTimezoneOffset()); } }); })(); diff --git a/src/components/datepicker/js/datepickerDirective.js b/src/components/datepicker/js/datepickerDirective.js index 48dedf72d06..cf4903c14ac 100644 --- a/src/components/datepicker/js/datepickerDirective.js +++ b/src/components/datepicker/js/datepickerDirective.js @@ -990,7 +990,13 @@ */ DatePickerCtrl.prototype.setModelValue = function(value) { var timezone = this.$mdUtil.getModelOption(this.ngModelCtrl, 'timezone'); - this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd', timezone), 'default'); + // Using the timezone when the offset is negative (GMT+X) causes the previous day to be + // set as the model value here. This check avoids that. + if (timezone == null || value.getTimezoneOffset() < 0) { + this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd'), 'default'); + } else { + this.ngModelCtrl.$setViewValue(this.ngDateFilter(value, 'yyyy-MM-dd', timezone), 'default'); + } }; /** @@ -1001,12 +1007,18 @@ var self = this; var timezone = this.$mdUtil.getModelOption(this.ngModelCtrl, 'timezone'); - if (this.dateUtil.isValidDate(value) && timezone != null) { + if (this.dateUtil.isValidDate(value) && timezone != null && value.getTimezoneOffset() >= 0) { this.date = this.dateUtil.removeLocalTzAndReparseDate(value); } else { this.date = value; } - this.inputElement.value = this.locale.formatDate(value, timezone); + // Using the timezone when the offset is negative (GMT+X) causes the previous day to be + // used here. This check avoids that. + if (timezone == null || value.getTimezoneOffset() < 0) { + this.inputElement.value = this.locale.formatDate(value); + } else { + this.inputElement.value = this.locale.formatDate(value, timezone); + } this.mdInputContainer && this.mdInputContainer.setHasValue(!!value); this.resizeInputElement(); // This is often called from the $formatters section of the $validators pipeline.