Skip to content
This repository has been archived by the owner on May 28, 2019. It is now read-only.

Commit

Permalink
Add a workaround for buggy Date#getUTC{FullYear, Month, Date} imple…
Browse files Browse the repository at this point in the history
…mentations in Opera > 9.64. Closes #4.
  • Loading branch information
Kit Cambridge committed Apr 14, 2012
1 parent e63e560 commit 9dfa56b
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 16 deletions.
80 changes: 72 additions & 8 deletions lib/json3.js
Expand Up @@ -9,14 +9,20 @@
}
})(this, function (exports) {
// Convenience aliases.
var getClass = {}.toString, hasOwnProperty = {}.hasOwnProperty, forEach, stringifySupported, parseSupported;
var getClass = {}.toString, hasOwnProperty = {}.hasOwnProperty, getUTCSupported = false, stringifySupported, parseSupported, forEach;

// Feature tests to determine whether the native `JSON.stringify` and `parse`
// implementations are spec-compliant. Based on work by Ken Snyder.
stringifySupported = typeof exports.stringify == "function";
parseSupported = typeof exports.parse == "function";
(function () {
var serialized = '{"result":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}', toJSON, original, value;
var serialized = '{"result":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}', value = new Date(-3509827334573292), toJSON, original;
// The `Date#getUTC{FullYear, Month, Date}` methods return nonsensical
// results for certain dates in Opera > 9.64.
try {
getUTCSupported = value.getUTCFullYear() == -109252 && value.getUTCMonth() === 0 && value.getUTCDate() == 1;
} catch (exception) {}
value = null;
// Test `JSON.stringify`.
if (stringifySupported) {
// An object with a custom `toJSON` method.
Expand Down Expand Up @@ -245,7 +251,7 @@
"\n": "\\n",
"\r": "\\r",
"\t": "\\t"
};
}, Months, getYearOffset, getLeapOffset;

// Converts `value` into a zero-padded string such that its length is at
// least equal to `width`. The `width` must be <= 6.
Expand All @@ -272,21 +278,79 @@
}
return result + '"';
}

// Conditionally define additional helper methods for calculating dates
// if the native `Date#getUTC*` methods are buggy.
if (!getUTCSupported) {
Months = [31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];

// Internal: Calculates the number of days between the Unix epoch and
// January 1st of the given `year`. For example, if `year` is `1971`,
// `getYearOffset` returns `365`; if `year` is 1969, `-365` is returned.
// This is an implementation of the `DayFromYear` routine described in
// section 15.9.1.3 of the ES 5.1 spec.
getYearOffset = function getYearOffset(year) {
return 365 * (year - 1970) + Math.floor((year - 1969) / 4) - Math.floor((year - 1901) / 100) + Math.floor((year - 1601) / 400);
};

// Internal: returns `1` if the given `year` is a leap year; `0`
// otherwise. Leap years are either divisible by 4 and not by 100, or
// divisible by 400. This is similar to the `InLeapYear` routine
// described in 15.9.1.3, but differs in that it accepts a year, rather
// than a millisecond time value.
getLeapOffset = function getLeapOffset(year) {
// Coerce the parenthesized expression to a boolean and convert it to
// a number using the unary `+` operator.
return +!!(!(year % 4) && year % 100 || !(year % 400));
};
}

// Recursively serializes an object. Implements the `Str(key, holder)`,
// `JO(value)`, and `JA(value)` operations.
function serialize(property, object, callback, properties, whitespace, indentation, stack) {
var value = object[property], className, year, results, element, index, length, prefix, any;
var value = object[property], className, year, month, day, date, results, element, index, length, prefix, any;
if (typeof value == "object" && value) {
if (getClass.call(value) == "[object Date]" && !hasOwnProperty.call(value, "toJSON")) {
if (value > -1 / 0 && value < 1 / 0) {
// Dates are serialized according to the `Date#toJSON` method
// specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15 for
// the ISO 8601 date time string format.
year = value.getUTCFullYear();
// specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
// for the ISO 8601 date time string format. If the `getUTC*`
// methods are buggy, the year, month, and date are computed
// manually.
if (getUTCSupported) {
year = value.getUTCFullYear();
month = value.getUTCMonth();
date = value.getUTCDate();
} else {
// Compute the year from the millisecond time value of the
// given date. This is a modified implementation of the
// `YearFromTime` routine described in 15.9.1.3.
// `floor(date / 864e5)` calculates the number of days between
// the Unix epoch and the current time. Credits: @Yaffle.
for (year = Math.floor((date = Math.floor(value / 864e5)) / 365.2425) + 1970 - 1; getYearOffset(year + 1) <= date;) {
year += 1;
}
// Compute the number of days elapsed since the beginning of
// the year. This value is used internally for calculating the
// month and date.
day = Math.floor(value / 864e5) - getYearOffset(year);
// Compute the current month. This is an implementation of the
// `MonthFromTime` routine described in 15.9.1.4.
for (date = day, month = 0; month < 12; month += 1) {
// Subtract the leap day if the `year` is a leap year (month
// 1 is February).
if ((month == 1 ? (date -= getLeapOffset(year)) : date) < Months[month]) {
break;
}
}
// Compute the day of the month ("date"). This is an
// implementation of the `DateFromTime` routine described in
// 15.9.1.5.
date = month ? day - (Months[month - 1] - 1) - (month < 2 ? 0 : getLeapOffset(year)) : (day + 1);
}
// Serialize extended years correctly.
value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
"-" + toPaddedString(2, value.getUTCMonth() + 1) + "-" + toPaddedString(2, value.getUTCDate()) +
"-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
// Months, dates, hours, minutes, and seconds should have two
// digits; milliseconds should have three.
"T" + toPaddedString(2, value.getUTCHours()) + ":" + toPaddedString(2, value.getUTCMinutes()) + ":" + toPaddedString(2, value.getUTCSeconds()) +
Expand Down
10 changes: 2 additions & 8 deletions test/test_json3.js
Expand Up @@ -217,7 +217,7 @@
});

testSuite.addTest("`stringify`", function () {
var expected = 23, value, pattern;
var expected = 24, value, pattern;

// Special values.
this.serializes("null", null, "`null` is represented literally");
Expand Down Expand Up @@ -258,13 +258,7 @@
this.serializes('"1969-12-31T23:59:59.999Z"', new Date(-1), "Millisecond values < 1000 should be serialized correctly");
this.serializes('"-000001-01-01T00:00:00.000Z"', new Date(-621987552e5), "Years prior to 0 should be serialized as extended years");
this.serializes('"+010000-01-01T00:00:00.000Z"', new Date(2534023008e5), "Years after 9999 should be serialized as extended years");

value = new Date(-3509827334573292);
if (value.getUTCFullYear() == -109252) {
// This test will fail in Opera >= 10.53. See issue #4.
expected += 1;
this.serializes('"-109252-01-01T10:37:06.708Z"', value, "Opera <= 9.64 should correctly serialize a date with a year of `-109252`");
}
this.serializes('"-109252-01-01T10:37:06.708Z"', new Date(-3509827334573292), "Issue #4: Opera > 9.64 should correctly serialize a date with a year of `-109252`");

// Safari 2 restricts date time values to the range `[(-2 ** 31),
// (2 ** 31) - 1]`, which respectively correspond to the minimum and
Expand Down

0 comments on commit 9dfa56b

Please sign in to comment.