7

I am getting a date time value from asp.net mvc controller as "2014-08-31T00:00:00Z". When I bind this value to my angular-ui datepicker control it's state is showing as ng-invalid ng-invalid-date.

I am getting the date-format as well from the mvc controller so I am binding the date-format as well in my html.

When I am debugging the ui-bootstrap-tpls.js (latest version) file at line 1807

enter image description here

It's always coming as undefined. I have tried so many alternatives but I am unable to succeed. :(

javascript does't convert angular ui datepicker date to UTC correctly

So please give some thoughts and suggest me how can I solve this problem.

Thanks & Regards, N.Murali Krishna.

6
  • Please can you share your code at least the Angular one. Commented Oct 8, 2015 at 13:52
  • Please see my answer posted below as the one posted by Chet is very heavy-handed and not the right way to go if you intend on adhering to proper Angular design. Commented Oct 8, 2015 at 18:34
  • Please don't post screenshots of code. Commented Oct 8, 2015 at 18:41
  • Hi Joel,Since this is ui-bootstrap-tpls.js code and it is open source code so I don't think no body will object if we post open source code. Commented Oct 9, 2015 at 5:13
  • 1
    Yeah, I wasn't referring to copyright at all. Screenshots are harder to work with for those who want to help you. We need you to post the text of the code, rather than an image. Commented Nov 20, 2015 at 15:01

7 Answers 7

11

I had the same problem. The issue is that Angular is expecting an actual date object, not a string representation of the date. After doing a bunch of research I ended up adding a transformReponse to the $httpProvider which checks all string objects to see if they can be converted to a date, and if so actually converting them.

angular.module('test')
.config(['$httpProvider', function ($httpProvider) {

    // ISO 8601 Date Pattern: YYYY-mm-ddThh:MM:ss
    var dateMatchPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;

   var convertDates = function (obj) {
      for (var key in obj) {
         if (!obj.hasOwnProperty(key)) continue;

         var value = obj[key];
         var typeofValue = typeof (value);

         if (typeofValue === 'object') {
            // If it is an object, check within the object for dates.
            convertDates(value);
         } else if (typeofValue === 'string') {
            if (dateMatchPattern.test(value)) {
               obj[key] = new Date(value);
            }
         }
      }
   }

   $httpProvider.defaults.transformResponse.push(function (data) {
      if (typeof (data) === 'object') {
         convertDates(data);
      }

      return data;
   });
}])
Sign up to request clarification or add additional context in comments.

2 Comments

Excellent. Jumping out of joy. I was struggling to fix this issue on my own since 2 days. Your solution solved the problem for me. Now we need to see when Angular-UI is going to fix this problem. For the moment I will use your code.
This solution is VERY heavy-handed, is not flexible, and is not easily testable. It should not be the right solution for anyone interested in developing a system that follows good Angular coding practices. Please see my answer for more info, including how to rectify.
6

A couple of notes here:

  1. First, the datepicker directive requires that the ng-model be a Date object. This is documented here.
  2. Second, the solution posted (and accepted) by Chet above is VERY heavy-handed as it takes EVERY date string received in an HTTP response and converts it to a Date object if it matches a hard-coded pattern. This is not flexible nor is it easily testable. It will not be the right solution for most people.

If, in your system, global date string conversion is the right behavior, the proper Angular design would be to create a service that does this for you. This leads me to...

We've (Angular UI Bootstrap) provided a mechanism for converting date strings into Date objects via the dateParser service. You can see the source code here. NB: this service name becomes deprecated and changed to uibDateParser with the 0.14.0 release.

10 Comments

Hi icfantv, Can you please let me know is there any possibility for me to convert a model value to Date object directly, if we change any thing in JSONSerailization setting do we get the date as java date object instead of string. If possible can you please provide us a plnkr sample how to use the uibDateParser.
I have done the changes in my code as mentioned by you, then I am getting the following error in console. "[ngModel:datefmt] Expected 2014-08-31T00:00:00Z to be a date". I have modified the input type from text to date as well in my html code.
I think you may have misunderstood and I apologize if I didn't explain it well enough. The value of the model object referred to by the ng-model directive needs to be a JS Date object. You don't need to touch the input type. And in fact, some browsers still do not support <input type="date"/>.
@icfantv I understand you guys did this design choice to close a lot of bugs (just read the bug reports), but it defeated the transparent two-way data binding with json that was the reason I used the library in the first place.
Gotcha. Unfortunately, I don't have a better solution for you here other than the one I already suggested. We have decided to expose the $dateParser service (i.e., document it and support it) for use by devs. The only other thing I can think of is wrapping our uib-datepicker directive with a custom one and passing in the date string, leaving it to the directive to convert to the Date object necessary for the uib-datepicker directive. This is probably a better (and significantly faster) solution than an HTTP interceptor. HTH.
|
6

This is best solution i found so far:

.directive('uibDatepickerPopup', function (dateFilter, uibDateParser, uibDatepickerPopupConfig) {
    return {
        restrict: 'A',
        priority: 1,
        require: 'ngModel',
        link: function (scope, element, attr, ngModel) {
            var dateFormat = attr.uibDatepickerPopup || uibDatepickerPopupConfig.datepickerPopup;
            ngModel.$validators.date = function(modelValue, viewValue) {
                var value = viewValue || modelValue;

                if (!attr.ngRequired && !value) {
                    return true;
                }

                if (angular.isNumber(value)) {
                    value = new Date(value);
                }
                if (!value) {
                    return true;
                } else if (angular.isDate(value) && !isNaN(value)) {
                    return true;
                } else if (angular.isString(value)) {
                    var date = uibDateParser.parse(value, dateFormat);
                    return !isNaN(date);
                } else {
                    return false;
                }
            };
        }
    };
});

Comments

2

I don't have enough reputation to comment under Chet's answer but thanks so much this solution worked for me as well! I don't know your Github handle to tag you in the issue but I submitted a Github issue under Angular UI to help others find the solution and hopefully get Angular UI guys to look into it. Thanks again!

https://github.com/angular-ui/bootstrap/issues/4554

1 Comment

This is not a bug in Angular UI Bootstrap as the datepicker directive is functioning as documented and designed. It requires that the ng-model be a Date object. Please see my answer in this SO article for the proper way to solve your issue.
2

To use formatted date string instead of Date object as ng-model for Angular Datepicker, you need to create a wrapper directive. The wrapper directive will parse your string into Date object and pass it to the datepicker. When you select a date, it is converted from Date back into string. Here is an example (Plunker):

(function () {
    'use strict';

    angular
        .module('myExample', ['ngAnimate', 'ngSanitize', 'ui.bootstrap'])
        .controller('MyController', MyController)
        .directive('myDatepicker', myDatepickerDirective);

    MyController.$inject = ['$scope'];

    function MyController ($scope) {
      $scope.dateFormat = 'dd MMMM yyyy';
      $scope.myDate = '30 Jun 2017';
    }

    myDatepickerDirective.$inject = ['uibDateParser', '$filter'];

    function myDatepickerDirective (uibDateParser, $filter) {
        return {
            restrict: 'E',
            scope: {
                name: '@',
                dateFormat: '@',
                ngModel: '='
            },
            required: 'ngModel',
            link: function (scope) {

                var isString = angular.isString(scope.ngModel) && scope.dateFormat;

                if (isString) {
                    scope.internalModel = uibDateParser.parse(scope.ngModel, scope.dateFormat);
                } else {
                    scope.internalModel = scope.ngModel;
                }

                scope.open = function (event) {
                    event.preventDefault();
                    event.stopPropagation();
                    scope.isOpen = true;
                };

                scope.change = function () {
                    if (isString) {
                        scope.ngModel = $filter('date')(scope.internalModel, scope.dateFormat);
                    } else {
                        scope.ngModel = scope.internalModel;
                    }
                };

            },
            template: [
                '<div class="input-group">',
                    '<input type="text" readonly="true" style="background:#fff" name="{{name}}" class="form-control" uib-datepicker-popup="{{dateFormat}}" ng-model="internalModel" is-open="isOpen" ng-click="open($event)" ng-change="change()">',
                    '<span class="input-group-btn">',
                        '<button class="btn btn-default" ng-click="open($event)">&nbsp;<i class="glyphicon glyphicon-calendar"></i>&nbsp;</button>',
                    '</span>',
                '</div>'
            ].join('')
        }
    }

})();
<!DOCTYPE html>
<html>

  <head>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-animate.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-sanitize.js"></script>
    <script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-2.5.0.js"></script>
    <script src="example.js"></script>
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  </head>

  <body ng-app="myExample">
    <div ng-controller="MyController">
      <p>
        Date format: {{dateFormat}}
      </p>
      <p>
        Value: {{myDate}}
      </p>
      <p>
        <my-datepicker ng-model="myDate" date-format="{{dateFormat}}"></my-datepicker>
      </p>
    </div>
  </body>

</html>

Comments

1

Just wanted to add my solution to this stack.

app.directive('formatDate', function (dateFilter, uibDateParser, uibDatepickerPopupConfig) {
    return {
        restrict: 'A',
        priority: 1,
        require: 'ngModel',
        link: function (scope, element, attr, ngModel) {
            var dateFormat = attr.uibDatepickerPopup || uibDatepickerPopupConfig.datepickerPopup;
            ngModel.$validators.date = function(modelValue, viewValue) {
                var value = viewValue || modelValue;

                if (!attr.ngRequired && !value) {
                    return true;
                }

                if (angular.isNumber(value)) {
                    value = new Date(value);
                }
                if (!value) {
                    return true;
                } else if (angular.isDate(value) && !isNaN(value)) {
                    return true;
                } else if (angular.isString(value)) {
                    var date = new Date(value);
                    return !isNaN(date);
                } else {
                    return false;
                }
            }
            ngModel.$formatters.push(function(value) {
                return new Date(value);
            });
        }
    };
});

1 Comment

Just added formatter to @user1839114. It took me sometime to figure out why the value was not showing after adding the validator.
1

I solved the problem by converting string to valid javascript formt in directive:

see here: Datepicker-popup formatting not working when value set initially in scope

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.