So I'm attempting to deal with dates with mongodb / mongoose / angular.
I was trying to use the as a date picker. But it requires yyyy-MM-dd format. Where the dates generated in by a mongoose schema:
created: {
type: Date,
default: Date.now
},
Those dates are this format: 2014-12-13T22:23:20.633Z
So I looked around for how people are handling binding to the data model when there is a conversion required.
I came up with the following directive.
'use strict';
angular.module('clients').directive('mongooseDateFormat', ['$filter',
function ($filter) {
return {
require: 'ngModel',
link: function (scope, element, attrs, ngModelController) {
ngModelController.$parsers.push(function (data) {
console.log(data);
data = $filter('date')(data, 'yyyy-MM-dd');
console.log(data);
return data; //converted
});
ngModelController.$formatters.push(function (data) {
console.log(data); // gets 2015-01-12T00:00:00.000Z
data = $filter('date')(data, 'yyyy-MM-dd');
console.log(data); // converts to 2015-01-11
return data; //converted
});
}
};
}
]);
So I included the console.log functions where I'm testing the values and show above in the code some sample dates in the comments.
You can see that 2015-01-12T00:00:00.000Z becomes 2015-01-11.
So the value that the sends into the filter is with the 0 time stamp and the $format(date)(data, "yyyy-MM-dd") command removes the timestamp but changes the day.
(..sigh.. expletives removed )
Being new to having to care about date formats this is just mind blowing. Is the problem that I'm not using a timezone? By that I mean that mongodb and mongoose are not generating a timezone? Why would a date with a zero time round down to the previous date when you attempt to format it?
I could continue complaining about how odd this is and make myself sound stupid when someone tells me the easy answer. I'll just post and see if anyone knows.
<input type="text" data-mongoose-date-format data-ng-model="client.mydate">
or
<input type="date" data-mongoose-date-format data-ng-model="client.mydate">
They both bind after you enter the date and it is converted and return a date one day less.
It's because Z at the end of your time string tells that this is UTC and date filter converts date to your local timezone. If you use Angularjs 1.3.* you can add timezone parameter:
$filter('date')(data, 'yyyy-MM-dd', 'UTC');
I'm using the meanjs.org boiler plate as well, and I've been trying to work through this same problem for several hours now. I was storing it as a Date, but couldn't use it with ng-model because of the above mentioned formatting error. So I created the directive above, only to find that I need angular 1.3 to get the time zone param for $filter if I don't want it to automatically subtract 8 hours. But I found it's a real pain to upgrade angular to 1.3 because lots other stuff breaks, so I decided to revert back to 1.2 instead of following that rabbit hole.
But, I kid you not, I was able to circumvent the entire problem by simply changing the mongoose datatype from Date to String in my server model. This eliminates the automatic time zone conversion, but still allows you to use the date filter in your data bindings.
{{show.date | date: 'mediumDate'}}
Despite being stored in mongo as a string, it still formats perfectly, without the pesky automatic timezone conversion.
Also, when the date is stored as a string, it surprisingly needs no conversion at all when using in the edit form.
<input type="date" data-ng-model="show.date" id="date" class="form-control" placeholder="Show date" required>
Works just fine, you don't need a directive.
I realize from a data schema point of view, it's always preferable to store dates as type Date. But, to me, this is a much simpler solution until the boilerplate comes out of the box with angular 1.3, which will make it much easier to avoid the automatic time zone conversion of stored dates.
Related
I'm improving and automating certain things in an old web app. One of them is the date format. We used to handle these with jquery functions and now we are doing it with angularjs.
For the input we use a directive and it works perfect. The problem occurs when it is not used, the directive is not executed and the value of the model is left without the proper value.
Directive:
app.directive('formatDate', function($filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, modelCtrl) {
// format text (model to view)
modelCtrl.$formatters.push(function(value) {
if(value !== "" && typeof value !== "undefined" && value !== null){
return value.split("-").reverse().join("/");
}
});
// format text (view to model)
modelCtrl.$parsers.push(function(value) {
if(value !== "" && typeof value !== "undefined" && value !== null){
var date = new Date(value.split("/").reverse().join("-"));
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
return date;
}
});
}
};
});
Issue:
When you load the value from webservice, for example: "invoice.date" comes from the database with the format "yyyy-mm-dd". It is loaded in the input with format "dd/mm/yyyy" and if the input is edited the model value is a "Date object" thanks to the directive. But if no field is edited, the value remains "yyyy-mm-dd" and that string causes errors. (Note: they use webservices with jpa and I can not change anything in backend)
How to format the value before sending it without doing a manual verification and analyzing the value in each function? Can I use $watch over each of the variables without causing a conflict with the directive or an infinite loop? Angular has some other way to automate this?
Thanks for any help.
I have seen this many times, it is quite common and you are using the time because jpa or the database changes the date because Timezone, right?
ok, then it comes from a webserice so it probably comes in json format. You can simply convert to a date object before assigning it to the model value and thus always use this format. Another way is to convert it before sending it and you've already said that's what you want to avoid.
There are many things that can be done. To sum up:
(choose one)
Convert the format upon receipt and before assigning it. either
manual, by simple javascript or any other method.
On the reverse, before sending, make the format change.
To avoid doing it manually, look for it with functions or events, if
$ watch can be used to detect in which format it comes and change it
correctly. So if it is assigned programmatically, it will also work.
There are other methods to filter and elaborate the response but I
think it would be too much for this case. As Jorge said, there are some plugins, tools and more that can be added. But I always try to avoid overloading with so many things.
The problem is to try to automate with angularjs, because there are many ways and finding the "right" way is difficult since each project is different and each person has a different way of thinking.
Background Story:
My Application is timezone aware. Users can select a specific timezone and all date inputs expect values according to that timezone. E.g. if I chose the timezone New York City, every input field expects and displays UTC-5 dates.
With Angular 1.2 I managed that with moment.js. Any input[date] had a string generated by moment as model. Then I upgraded to Angular 1.3.
The Issue:
Angular 1.3 requires a native Date object as model for all date related input fields. Native date objects only support the browsers timezone or UTC.
So how to manage timezones with Angular 1.3? Can I remove the validator from the modelController somehow? Any idea how to solve that?
I have found a way that is not very clean and could have side effects.
I am using a directive to remove validators on specific input fields:
OMD.app.directive('clearValidators', function ($window) {
return {
require:'^ngModel',
restrict:'A',
link:function (scope, elm, attrs, ctrl) {
ctrl.$formatters = [];
ctrl.$parsers = [];
}
};
});
This is how to apply it to the input. It seems like it adds the required validator after I cleared the default validators.
<input clear-validators type="datetime-local" ng-model="zoneTime" ng-required="required"/>
It works as with Angular 1.2 I don't like this approach though.
I am using Angular UI datepicker in my project.
The control has an option "datepicker-popup" which allows me to set up te format I want to display the date in. However the date is bound to my model as a date object and not as a formatted string.
The rest of my code simply requires the date as a string in the correct (yyyy-MM-dd) format.
At the moment wehenever I need to use the date, I format it to the correct string before passing it along.
This works for now since The code base is pretty small but is there a better way to somehow bind the date to my model as a string so that someone forgetting to format the date before using it doesn't break the system.
A plunker for the sample code can be found here.
I was thinking maybe I would need to set up a watch or something but was not too sure what the correct solution would be.
I think that I found better solution for this. You can use your own parser for datepickerPopup directive. Example which works for me (you have to add this directive to the application):
angular.module('myApp')
.directive('datepickerPopup', function (){
return {
restrict: 'EAC',
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
ngModel.$parsers.push(function toModel(date) {
return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
});
}
}
});
Each when you will select the date on date picker you will have the String object with formatted date: 'yyyy-MM-dd'. Hope it helps.
No, currently AngularUI and many other frameworks use the Date object to bind information. You need to format the date to a string each time you want it as a string. The way to do this is to create a function like
$scope.getMyDateAsString = function(){
return myDate.toString(); // or however you format your string.
};
Then anytime you want to get the string you can call this function. You CAN create a watcher
$scope.$watch($scope.myDateModel, function(newVal, oldVal){
$scope.myDateAsString = $scope.getMyDateAsString();
});
This way, anytime the datepicker changes value, you change the value of the string.
You can format your dates after picking, using Cordova Plugin Datepicker and Moment.js, this solution works for me:
$scope.pickSinceDate = function(){
pickDate($scope.filter.since).then(function(date){
$scope.since = moment(date).format('YYYY-MM-DD');
});
});
Hope it helps.
So we have a model with a 'time' in it, formatted like '14:00:00'. We are using angular-boostrap's timepicker, but it expects a JS date object or an ISO datetime string.
My first thought was to do a directive that would add $parser/$formatter to ngModel controller that would convert it on the way in and way out to the format we need. This works smashingly for output, but not for input.
I've made a fiddle here http://jsfiddle.net/HB7LU/1820/
myApp.directive('timespan',function($filter){
var dateFilter = $filter('date');
function parser(data){
return dateFilter(data,'HH:mm:ss');
};
function formatter(data){
var converted = '2000-01-01T' + data + '-06:00';
return converted;
};
return {
require: 'ngModel',
link:function(scope,element,attrs,ctrl){
ctrl.$parsers.unshift(parser);
ctrl.$formatters.unshift(formatter);
}
};
});
As you cans when you open the fiddle the 12:00:00 isn't reflected by the timepicker. If you look at the console its complaining that ngModel is in the wrong format. If you change the time with the picker you'll see that $scope.myModel.time is in the correct format.
How do I get the ngModel to the correct format before it gets to timepicker?
timepicker in angular-bootstrap expects the $modelValue to be a date (it passes it to the Date() constructor and then parses hours and minutes from the object). Since it writes its own $render() function, based on $modelValue, your formatters aren't being used.
I'm afraid you're not going to get what you want without doing some surgery on the angular-bootstrap code. It would likely be easier to write a controller with two input fields (for hours and minutes).
A possible solution is to create directive for your Model Value input. This directive would
receive date in the same format as timepicker - timestamp or Date object and display it any way you want.
I think timepicker must use $viewValue in render method. Therefore the best solution is this surgery on the angular-bootstrap code.
Change $modelValue to $viewValue in render function.
How would I get a localized version of every month in angularjs in an array? I know how to localize a single date using the format options, but in this case I want all of the months. Basically, this is going to be bound to a select that the user can than choose a month.
I m guessing that your page will be entirely localized in a given language and that you will add the proper angular-locale file as such :
<script src="http://.../angular-locale_xx-xx.js"></script> // ex: angular-locale_fr-fr.js
If this is the case you can simply access all the months as an array like that :
function Controller($scope, $locale) { //inject the $locale service
var datetime = $locale.DATETIME_FORMATS; //get date and time formats
$scope.months = datetime.MONTH; //access localized months
}
You can see a working plunker here