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

Commit

Permalink
refactor(fromJson/date filter): move date string logic to date filter
Browse files Browse the repository at this point in the history
Breaks angular.fromJson which doesn't deserialize date strings into date objects.

This was done to make fromJson compatible with JSON.parse.

If you do require the old behavior - if at all neeeded then because of
json deserialization of XHR responses - then please create a custom
$http transform:

$httpProvider.defaults.transformResponse.push(function(data) {
  // recursively parse dates from data object here
  // see code removed in this diff for hints
});

Closes #202
  • Loading branch information
IgorMinar committed Mar 28, 2012
1 parent bb2fa6f commit ac4318a
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 216 deletions.
43 changes: 3 additions & 40 deletions src/JSON.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict';

var array = [].constructor;

/**
* @ngdoc function
* @name angular.toJson
Expand Down Expand Up @@ -35,46 +33,11 @@ function toJson(obj, pretty) {
function fromJson(json, useNative) {
if (!isString(json)) return json;

var obj;

if (useNative && window.JSON && window.JSON.parse) {
obj = JSON.parse(json);
} else {
obj = parseJson(json, true)();
}
return transformDates(obj);

// TODO make forEach optionally recursive and remove this function
// TODO(misko): remove this once the $http service is checked in.
function transformDates(obj) {
if (isString(obj) && 15 <= obj.length && obj.length <= 24) {
return jsonStringToDate(obj);
} else if (isArray(obj) || isObject(obj)) {
forEach(obj, function(val, name) {
obj[name] = transformDates(val);
});
}
return obj;
}
return (useNative && window.JSON && window.JSON.parse)
? JSON.parse(json)
: parseJson(json, true)();
}

var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
function jsonStringToDate(string){
var match;
if (match = string.match(R_ISO8061_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
}
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
return date;
}
return string;
}

function jsonDateToString(date){
if (!date) return date;
Expand Down
24 changes: 23 additions & 1 deletion src/ng/filter/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
* (e.g. `"h o''clock"`).
*
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
* number) or ISO 8601 extended datetime string (yyyy-MM-ddTHH:mm:ss.SSSZ).
* number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
* shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
* @param {string=} format Formatting rules (see Description). If not specified,
* `mediumDate` is used.
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
Expand Down Expand Up @@ -317,6 +318,27 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
*/
dateFilter.$inject = ['$locale'];
function dateFilter($locale) {


var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
function jsonStringToDate(string){
var match;
if (match = string.match(R_ISO8601_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
}
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
return date;
}
return string;
}


return function(date, format) {
var text = '',
parts = [],
Expand Down
266 changes: 148 additions & 118 deletions src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,146 +373,176 @@ angular.mock.$LogProvider = function() {
};


/**
* @ngdoc object
* @name angular.mock.TzDate
* @description
*
* *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
*
* Mock of the Date type which has its timezone specified via constroctor arg.
*
* The main purpose is to create Date-like instances with timezone fixed to the specified timezone
* offset, so that we can test code that depends on local timezone settings without dependency on
* the time zone settings of the machine where the code is running.
*
* @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
*
* @example
* !!!! WARNING !!!!!
* This is not a complete Date object so only methods that were implemented can be called safely.
* To make matters worse, TzDate instances inherit stuff from Date via a prototype.
*
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
* incomplete we might be missing some non-standard methods. This can result in errors like:
* "Date.prototype.foo called on incompatible Object".
*
* <pre>
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
* newYearInBratislava.getTimezoneOffset() => -60;
* newYearInBratislava.getFullYear() => 2010;
* newYearInBratislava.getMonth() => 0;
* newYearInBratislava.getDate() => 1;
* newYearInBratislava.getHours() => 0;
* newYearInBratislava.getMinutes() => 0;
* </pre>
*
*/
angular.mock.TzDate = function (offset, timestamp) {
var self = new Date(0);
if (angular.isString(timestamp)) {
var tsStr = timestamp;

self.origDate = angular.fromJson(angular.toJson({date:timestamp})).date;

timestamp = self.origDate.getTime();
if (isNaN(timestamp))
throw {
name: "Illegal Argument",
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
};
} else {
self.origDate = new Date(timestamp);
(function() {
var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;

function jsonStringToDate(string){
var match;
if (match = string.match(R_ISO8061_STR)) {
var date = new Date(0),
tzHour = 0,
tzMin = 0;
if (match[9]) {
tzHour = int(match[9] + match[10]);
tzMin = int(match[9] + match[11]);
}
date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
return date;
}
return string;
}

var localOffset = new Date(timestamp).getTimezoneOffset();
self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
self.date = new Date(timestamp + self.offsetDiff);
function int(str) {
return parseInt(str, 10);
}

self.getTime = function() {
return self.date.getTime() - self.offsetDiff;
};

self.toLocaleDateString = function() {
return self.date.toLocaleDateString();
};
/**
* @ngdoc object
* @name angular.mock.TzDate
* @description
*
* *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
*
* Mock of the Date type which has its timezone specified via constroctor arg.
*
* The main purpose is to create Date-like instances with timezone fixed to the specified timezone
* offset, so that we can test code that depends on local timezone settings without dependency on
* the time zone settings of the machine where the code is running.
*
* @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
* @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
*
* @example
* !!!! WARNING !!!!!
* This is not a complete Date object so only methods that were implemented can be called safely.
* To make matters worse, TzDate instances inherit stuff from Date via a prototype.
*
* We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
* incomplete we might be missing some non-standard methods. This can result in errors like:
* "Date.prototype.foo called on incompatible Object".
*
* <pre>
* var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
* newYearInBratislava.getTimezoneOffset() => -60;
* newYearInBratislava.getFullYear() => 2010;
* newYearInBratislava.getMonth() => 0;
* newYearInBratislava.getDate() => 1;
* newYearInBratislava.getHours() => 0;
* newYearInBratislava.getMinutes() => 0;
* </pre>
*
*/
angular.mock.TzDate = function (offset, timestamp) {
var self = new Date(0);
if (angular.isString(timestamp)) {
var tsStr = timestamp;

self.origDate = jsonStringToDate(timestamp)

timestamp = self.origDate.getTime();
if (isNaN(timestamp))
throw {
name: "Illegal Argument",
message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
};
} else {
self.origDate = new Date(timestamp);
}

self.getFullYear = function() {
return self.date.getFullYear();
};
var localOffset = new Date(timestamp).getTimezoneOffset();
self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
self.date = new Date(timestamp + self.offsetDiff);

self.getMonth = function() {
return self.date.getMonth();
};
self.getTime = function() {
return self.date.getTime() - self.offsetDiff;
};

self.getDate = function() {
return self.date.getDate();
};
self.toLocaleDateString = function() {
return self.date.toLocaleDateString();
};

self.getHours = function() {
return self.date.getHours();
};
self.getFullYear = function() {
return self.date.getFullYear();
};

self.getMinutes = function() {
return self.date.getMinutes();
};
self.getMonth = function() {
return self.date.getMonth();
};

self.getSeconds = function() {
return self.date.getSeconds();
};
self.getDate = function() {
return self.date.getDate();
};

self.getTimezoneOffset = function() {
return offset * 60;
};
self.getHours = function() {
return self.date.getHours();
};

self.getUTCFullYear = function() {
return self.origDate.getUTCFullYear();
};
self.getMinutes = function() {
return self.date.getMinutes();
};

self.getUTCMonth = function() {
return self.origDate.getUTCMonth();
};
self.getSeconds = function() {
return self.date.getSeconds();
};

self.getUTCDate = function() {
return self.origDate.getUTCDate();
};
self.getTimezoneOffset = function() {
return offset * 60;
};

self.getUTCHours = function() {
return self.origDate.getUTCHours();
};
self.getUTCFullYear = function() {
return self.origDate.getUTCFullYear();
};

self.getUTCMinutes = function() {
return self.origDate.getUTCMinutes();
};
self.getUTCMonth = function() {
return self.origDate.getUTCMonth();
};

self.getUTCSeconds = function() {
return self.origDate.getUTCSeconds();
};
self.getUTCDate = function() {
return self.origDate.getUTCDate();
};

self.getDay = function() {
return self.date.getDay();
};
self.getUTCHours = function() {
return self.origDate.getUTCHours();
};

//hide all methods not implemented in this mock that the Date prototype exposes
var unimplementedMethods = ['getMilliseconds', 'getUTCDay',
'getUTCMilliseconds', 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString',
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];

angular.forEach(unimplementedMethods, function(methodName) {
self[methodName] = function() {
throw Error("Method '" + methodName + "' is not implemented in the TzDate mock");
self.getUTCMinutes = function() {
return self.origDate.getUTCMinutes();
};
});

return self;
};
self.getUTCSeconds = function() {
return self.origDate.getUTCSeconds();
};

self.getUTCMilliseconds = function() {
return self.origDate.getUTCMilliseconds();
};

self.getDay = function() {
return self.date.getDay();
};

//hide all methods not implemented in this mock that the Date prototype exposes
var unimplementedMethods = ['getMilliseconds', 'getUTCDay',
'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
'setYear', 'toDateString', 'toJSON', 'toGMTString', 'toLocaleFormat', 'toLocaleString',
'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];

angular.forEach(unimplementedMethods, function(methodName) {
self[methodName] = function() {
throw Error("Method '" + methodName + "' is not implemented in the TzDate mock");
};
});

return self;
};

//make "tzDateInstance instanceof Date" return true
angular.mock.TzDate.prototype = Date.prototype;
//make "tzDateInstance instanceof Date" return true
angular.mock.TzDate.prototype = Date.prototype;
})();


/**
Expand Down
Loading

0 comments on commit ac4318a

Please sign in to comment.