Material: calendar: doesn't handle timezone from ng-model-options

Created on 22 Feb 2017  ·  9Comments  ·  Source: angular/material

Actual Behavior:

  • What is the issue? * The timezone is not considered: date is always returned with current timezone at midnight
  • What is the expected behavior?The return date is with selected timezone

CodePen (or steps to reproduce the issue): *

Angular Versions: *

  • Angular Version: 1.6.2
  • Angular Material Version: 1.1.3

Additional Information:

  • Browser Type: * Indifferent
  • Browser Version: *
  • OS: *
  • Stack Traces:
required sync Pull Request localization enhancement integration

Most helpful comment

Nice! I ended up writing a component (that doesn't rely on moment, although I think moment is probably unnecessary with your approach as well)

(function () {
  'use strict';

  angular.module('app.widgets').component('datePicker', {
    templateUrl: 'path/to/template.html',
    controller: DatePickerController,
    bindings: {
      minDate: '<?',
      maxDate: '<?',
      required: '<?',
      placeholder: '<?',
      currentView: '<?',
      name: '<?',
      initialDate: '<?',
      onChange: '&',
      ariaLabel: '<?'
    }
  });

  function DatePickerController() {
    var vm = this;

    if (!vm.initialDate) {
      vm.date = null;
    } else {
      // outer date is UTC
      // convert to local tim
      vm.date = new Date(vm.initialDate.getTime() + 60000 * vm.initialDate.getTimezoneOffset());
    }
    vm.update = update;

    function update(date) {
      // inner date is local time
      // convert to UTC to pass back up
      var utcDate = new Date(date.getTime() - 60000 * date.getTimezoneOffset());
      vm.onChange({ date: utcDate });
    }
  }
}());
<md-datepicker
  name="{{$ctrl.name}}"
  ng-required="$ctrl.required"
  md-min-date="$ctrl.minDate"
  md-max-date="$ctrl.maxDate"
  md-current-view="{{$ctrl.currentView}}"
  ng-model="$ctrl.date"
  layout
  md-placeholder="{{$ctrl.placeholder}}"
  ng-change="$ctrl.update($ctrl.date)"
  aria-label="$ctrl.ariaLabel"
></md-datepicker>
          <date-picker name="'someDate'" required="true" min-date="ctrl.minDate"
                         initial-date="ctrl.date"
                         on-change="ctrl.date = date"
                         placeholder="'Placeholder'"
                         ></date-picker>

I'm a React fan so I reached for the component when I saw this problem.

All 9 comments

This is a huge issue for our application, and I don't know any workaround besides "don't use angular-md". I don't expect it to get fixed, though, considering the bug is probably considered something of a "feature" at this point. Would break old applications that rely on the bug...

I ended up writing a custom directive amending the returned date, to use within md-calendar. It's a good workaround (imho :smile: ), but I still think the bug has to be fixed.

myApp.directive('tzDate', function($parse) {
      return {
        restrict: 'A',
        require: 'ngModel', 
        link: function (scope, element, attrs, ngModelCtrl) {
            ngModelCtrl.$viewChangeListeners.push(function(){ 
                if (ngModelCtrl.$modelValue && angular.isDate(ngModelCtrl.$modelValue)) {
                    var offset = attrs.tzDate;
                    offset && $parse(attrs.ngModel).assign(scope, moment(ngModelCtrl.$modelValue).utcOffset(offset, true).toDate());
                }
            });
        }
      };
});

html <md-calendar ng-model="date" tz-date="+02:00"></md-calendar>

Nice! I ended up writing a component (that doesn't rely on moment, although I think moment is probably unnecessary with your approach as well)

(function () {
  'use strict';

  angular.module('app.widgets').component('datePicker', {
    templateUrl: 'path/to/template.html',
    controller: DatePickerController,
    bindings: {
      minDate: '<?',
      maxDate: '<?',
      required: '<?',
      placeholder: '<?',
      currentView: '<?',
      name: '<?',
      initialDate: '<?',
      onChange: '&',
      ariaLabel: '<?'
    }
  });

  function DatePickerController() {
    var vm = this;

    if (!vm.initialDate) {
      vm.date = null;
    } else {
      // outer date is UTC
      // convert to local tim
      vm.date = new Date(vm.initialDate.getTime() + 60000 * vm.initialDate.getTimezoneOffset());
    }
    vm.update = update;

    function update(date) {
      // inner date is local time
      // convert to UTC to pass back up
      var utcDate = new Date(date.getTime() - 60000 * date.getTimezoneOffset());
      vm.onChange({ date: utcDate });
    }
  }
}());
<md-datepicker
  name="{{$ctrl.name}}"
  ng-required="$ctrl.required"
  md-min-date="$ctrl.minDate"
  md-max-date="$ctrl.maxDate"
  md-current-view="{{$ctrl.currentView}}"
  ng-model="$ctrl.date"
  layout
  md-placeholder="{{$ctrl.placeholder}}"
  ng-change="$ctrl.update($ctrl.date)"
  aria-label="$ctrl.ariaLabel"
></md-datepicker>
          <date-picker name="'someDate'" required="true" min-date="ctrl.minDate"
                         initial-date="ctrl.date"
                         on-change="ctrl.date = date"
                         placeholder="'Placeholder'"
                         ></date-picker>

I'm a React fan so I reached for the component when I saw this problem.

I can see the difference in behavior and it needs to be fixed. I've updated the CodePen for 1.1.5 and the issue is still there.

This is needed so that the md-datepicker's calendar panel can be in sync with the md-datepicker's input. That issue is tracked in https://github.com/angular/material/issues/10598.

A similar fix was made for the md-datepicker, it may be helpful to review when making this fix.

any progress?

@Splaktar

DatePickerCtrl.prototype.onExternalChange = function(value) {
    var timezone = this.$mdUtil.getModelOption(this.ngModelCtrl, 'timezone');

    this.date = !value ? value : new Date(this.locale.formatDate(value, timezone)); //changed part

    this.inputElement.value = this.locale.formatDate(value, timezone);
    this.mdInputContainer && this.mdInputContainer.setHasValue(!!value);
    this.resizeInputElement();
    this.updateErrorState();
  };

works for me

@gaby64 I haven't had a chance to get back to this. I've been focused on P1 issues. If you want to submit a PR to fix this, I would be happy to review it and try to get it merged.

Opened PR 🧙‍♂️ https://github.com/angular/material/pull/11906 ✨ for this issue.

Was this page helpful?
0 / 5 - 0 ratings