From fc9e67318ea511626cdafd2e8c14e49b2f4e9e85 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Wed, 18 Aug 2010 15:11:27 +0000 Subject: [PATCH] Adding OpenLayers.Date utility functions to parse and serialize dates. The date string format parsed complies with ECMA-262 (v5) section 15.9.1.15, derived from the profile of ISO 8601 for date and time on the Internet (http://tools.ietf.org/html/rfc3339). Where available (and functional) the native date.toISOString and Date.parse methods will be used. r=ahocevar (closes #2778) git-svn-id: http://svn.openlayers.org/trunk/openlayers@10630 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- lib/OpenLayers/BaseTypes.js | 120 ++++++++++++++++++++++++++++ tests/BaseTypes.html | 152 ++++++++++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+) diff --git a/lib/OpenLayers/BaseTypes.js b/lib/OpenLayers/BaseTypes.js index 61186fe689..2b436eab28 100644 --- a/lib/OpenLayers/BaseTypes.js +++ b/lib/OpenLayers/BaseTypes.js @@ -559,3 +559,123 @@ OpenLayers.Array = { } }; + +/** + * Namespace: OpenLayers.Date + * Contains implementations of Date.parse and date.toISOString that match the + * ECMAScript 5 specification for parsing RFC 3339 dates. + * http://tools.ietf.org/html/rfc3339 + */ +OpenLayers.Date = { + + /** + * APIMethod: toISOString + * Generates a string representing a date. The format of the string follows + * the profile of ISO 8601 for date and time on the Internet (see + * http://tools.ietf.org/html/rfc3339). If the toISOString method is + * available on the Date prototype, that is used. The toISOString + * method for Date instances is defined in ECMA-262. + * + * Parameters: + * date - {Date} A date object. + * + * Returns: + * {String} A string representing the date (e.g. + * "2010-08-07T16:58:23.123Z"). If the date does not have a valid time + * (i.e. isNaN(date.getTime())) this method returns the string "Invalid + * Date". The ECMA standard says the toISOString method should throw + * RangeError in this case, but Firefox returns a string instead. For + * best results, use isNaN(date.getTime()) to determine date validity + * before generating date strings. + */ + toISOString: (function() { + if ("toISOString" in Date.prototype) { + return function(date) { + return date.toISOString(); + } + } else { + function pad(num, len) { + var str = num + ""; + while (str.length < len) { + str = "0" + str; + } + return str; + } + return function(date) { + var str; + if (isNaN(date.getTime())) { + // ECMA-262 says throw RangeError, Firefox returns + // "Invalid Date" + str = "Invalid Date" + } else { + str = + date.getUTCFullYear() + "-" + + pad(date.getUTCMonth() + 1, 2) + "-" + + pad(date.getUTCDate(), 2) + "T" + + pad(date.getUTCHours(), 2) + ":" + + pad(date.getUTCMinutes(), 2) + ":" + + pad(date.getUTCSeconds(), 2) + "." + + pad(date.getUTCMilliseconds(), 3) + "Z"; + } + return str; + } + } + + })(), + + /** + * APIMethod: parse + * Generate a date object from a string. The format for the string follows + * the profile of ISO 8601 for date and time on the Internet (see + * http://tools.ietf.org/html/rfc3339). If the parse method on + * the Date constructor returns a valid date for the given string, + * that method is used. + * + * Parameters: + * str - {String} A string representing the date (e.g. + * "2010", "2010-08", "2010-08-07", "2010-08-07T16:58:23.123Z", + * "2010-08-07T11:58:23.123-06"). + * + * Returns: + * {Date} A date object. If the string could not be parsed, an invalid + * date is returned (i.e. isNaN(date.getTime())). + */ + parse: function(str) { + var date; + // first check if the native parse method can parse it + var elapsed = Date.parse(str); + if (!isNaN(elapsed)) { + date = new Date(elapsed); + } else { + var match = str.match(/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))?$/); + var date; + if (match && (match[1] || match[7])) { // must have at least year or time + var year = parseInt(match[1], 10) || 0; + var month = (parseInt(match[2], 10) - 1) || 0; + var day = parseInt(match[3], 10) || 1; + date = new Date(Date.UTC(year, month, day)); + // optional time + var type = match[7]; + if (type) { + var hours = parseInt(match[4], 10); + var minutes = parseInt(match[5], 10); + var secFrac = parseFloat(match[6]); + var seconds = secFrac | 0; + var milliseconds = Math.round(1000 * (secFrac - seconds)); + date.setUTCHours(hours, minutes, seconds, milliseconds); + // check offset + if (type !== "Z") { + var hoursOffset = parseInt(type, 10); + var minutesOffset = parseInt(match[8]) || 0; + var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60); + date = new Date(date.getTime() + offset); + } + } + } else { + date = new Date("invalid"); + } + } + return date; + } + +}; \ No newline at end of file diff --git a/tests/BaseTypes.html b/tests/BaseTypes.html index cbb9db7398..c17d686c9d 100644 --- a/tests/BaseTypes.html +++ b/tests/BaseTypes.html @@ -357,7 +357,159 @@ } + function test_Date_toISOString(t) { + t.plan(3); + + var date, str; + + // check valid date + date = new Date(Date.UTC(2010, 10, 27, 18, 19, 15, 123)); + str = OpenLayers.Date.toISOString(date); + t.eq(str, "2010-11-27T18:19:15.123Z", "valid date"); + // check zero padding + date = new Date(Date.UTC(2010, 7, 7, 18, 9, 5, 12)); + str = OpenLayers.Date.toISOString(date); + t.eq(str, "2010-08-07T18:09:05.012Z", "zero padding"); + + // check invalid date + date = new Date("foo"); + str = OpenLayers.Date.toISOString(date); + t.eq(str, "Invalid Date", "invalid date"); + + } + + function test_Date_parse(t) { + + t.plan(93); + + var cases = { + "2000": { + year: 2000, + month: 0, + date: 1 + }, + "2005-10": { + year: 2005, + month: 9, + date: 1 + }, + "1971-07-23": { + year: 1971, + month: 6, + date: 23 + }, + "1801-11-20T04:30:15Z": { + year: 1801, + month: 10, + date: 20, + hour: 4, + minutes: 30, + seconds: 15 + }, + "1989-06-15T18:30:15.91Z": { + year: 1989, + month: 5, + date: 15, + hour: 18, + minutes: 30, + seconds: 15, + milliseconds: 910 + }, + "1989-06-15T18:30:15.091Z": { + year: 1989, + month: 5, + date: 15, + hour: 18, + minutes: 30, + seconds: 15, + milliseconds: 91 + }, + "1989-06-15T13:30:15.091-05": { + year: 1989, + month: 5, + date: 15, + hour: 18, + minutes: 30, + seconds: 15, + milliseconds: 91 + }, + "2010-08-06T15:21:25-06": { // MDT + year: 2010, + month: 7, + date: 6, + hour: 21, + minutes: 21, + seconds: 25 + }, + "2010-08-07T06:21:25+9": { // JSP + year: 2010, + month: 7, + date: 6, + hour: 21, + minutes: 21, + seconds: 25 + }, + "2010-08-07T02:51:25+05:30": { // IST + year: 2010, + month: 7, + date: 6, + hour: 21, + minutes: 21, + seconds: 25 + }, + "T21:51:25Z": { + hour: 21, + minutes: 51, + seconds: 25 + }, + "T02:51:25+05:30": { // IST + hour: 21, + minutes: 21, + seconds: 25 + }, + "T2:51:25.1234-7": { // lenient + hour: 9, + minutes: 51, + seconds: 25, + milliseconds: 123 + } + }; + + var o, got, exp; + for (var str in cases) { + o = cases[str]; + got = OpenLayers.Date.parse(str); + exp = new Date(Date.UTC(o.year || 0, o.month || 0, o.date || 1, o.hour || 0, o.minutes || 0, o.seconds || 0, o.milliseconds || 0)); + if ("year" in o) { + t.eq(got.getUTCFullYear(), exp.getUTCFullYear(), str + ": correct UTCFullYear"); + t.eq(got.getUTCMonth(), exp.getUTCMonth(), str + ": correct UTCMonth"); + t.eq(got.getUTCDate(), exp.getUTCDate(), str + ": correct UTCDate"); + } else { + t.ok(true, str + ": ECMA doesn't specify how years are handled in time only strings"); + t.ok(true, str + ": ECMA doesn't specify how months are handled in time only strings"); + t.ok(true, str + ": ECMA doesn't specify how days are handled in time only strings"); + } + if ("hour" in o) { + t.eq(got.getUTCHours(), exp.getUTCHours(), str + ": correct UTCHours"); + t.eq(got.getUTCMinutes(), exp.getUTCMinutes(), str + ": correct UTCMinutes"); + t.eq(got.getUTCSeconds(), exp.getUTCSeconds(), str + ": correct UTCSeconds"); + t.eq(got.getUTCMilliseconds(), exp.getUTCMilliseconds(), str + ": correct UTCMilliseconds"); + } else { + t.ok(true, str + ": ECMA doesn't specify how hours are handled in date only strings"); + t.ok(true, str + ": ECMA doesn't specify how minutes are handled in date only strings"); + t.ok(true, str + ": ECMA doesn't specify how seconds are handled in date only strings"); + t.ok(true, str + ": ECMA doesn't specify how milliseconds are handled in date only strings"); + } + } + + // check invalid date parsing + var invalid = OpenLayers.Date.parse("foo"); + t.ok(invalid instanceof Date, "invalid is a date"); + t.ok(isNaN(invalid.getTime()), "invalid has no time"); + + + }