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

Commit

Permalink
Accept ISO8601-formatted dates using the "±YYYYYY-MM-DDTHH:mm:ss.sss"…
Browse files Browse the repository at this point in the history
… format. Also ensure we pad all stringified dates to either 4 unsigned or 6 signed digits.

Fixes #1142

RELNOTES: Dates now implement ISO8601 more accurately for very large or negative years.
PiperOrigin-RevId: 408386206
Change-Id: I9a67f50812c678da4dc678c722e4799f14550c51
  • Loading branch information
shicks authored and copybara-github committed Nov 8, 2021
1 parent 2a21f12 commit 2dd8283
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 22 deletions.
29 changes: 24 additions & 5 deletions closure/goog/date/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ goog.date.month = {
* @private
*/
goog.date.splitDateStringRegex_ = new RegExp(
'^(\\d{4})(?:(?:-?(\\d{2})(?:-?(\\d{2}))?)|' +
'^((?:[-+]\\d*)?\\d{4})(?:(?:-?(\\d{2})(?:-?(\\d{2}))?)|' +
'(?:-?(\\d{3}))|(?:-?W(\\d{2})(?:-?([1-7]))?))?$');


Expand Down Expand Up @@ -526,6 +526,16 @@ goog.date.setIso8601TimeOnly_ = function(d, formatted) {
};


/**
* Pads the year to 4 unsigned digits, or 6 digits with a sign.
* @param {number} year
* @return {string}
*/
goog.date.padYear_ = function(year) {
const sign = year < 0 ? '-' : year >= 10000 ? '+' : '';
return sign + goog.string.padNumber(Math.abs(year), sign ? 6 : 4);
};


/**
* Class representing a date/time interval. Used for date calculations.
Expand Down Expand Up @@ -1352,18 +1362,23 @@ goog.date.Date.prototype.add = function(interval) {


/**
* Returns ISO 8601 string representation of date.
* Returns ISO 8601 string representation of date. Consistent with the
* standard built-in Date#toISOString method, the year is either four digits
* (YYYY) or six with a sign prefix (±YYYYYY), since ISO 8601 requires the
* number of digits in the year to be agreed upon in advance.
*
* @param {boolean=} opt_verbose Whether the verbose format should be used
* instead of the default compact one.
* @param {boolean=} opt_tz Whether the timezone offset should be included
* in the string.
* @return {string} ISO 8601 string representation of date.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
*/
goog.date.Date.prototype.toIsoString = function(opt_verbose, opt_tz) {
'use strict';
var str = [
this.getFullYear(), goog.string.padNumber(this.getMonth() + 1, 2),
goog.date.padYear_(this.getFullYear()),
goog.string.padNumber(this.getMonth() + 1, 2),
goog.string.padNumber(this.getDate(), 2)
];

Expand All @@ -1385,7 +1400,7 @@ goog.date.Date.prototype.toIsoString = function(opt_verbose, opt_tz) {
goog.date.Date.prototype.toUTCIsoString = function(opt_verbose, opt_tz) {
'use strict';
var str = [
goog.string.padNumber(this.getUTCFullYear(), 4),
goog.date.padYear_(this.getUTCFullYear()),
goog.string.padNumber(this.getUTCMonth() + 1, 2),
goog.string.padNumber(this.getUTCDate(), 2)
];
Expand Down Expand Up @@ -1758,14 +1773,18 @@ goog.date.DateTime.prototype.add = function(interval) {


/**
* Returns ISO 8601 string representation of date/time.
* Returns ISO 8601 string representation of date/time. Consistent with the
* standard built-in Date#toISOString method, the year is either four digits
* (YYYY) or six with a sign prefix (±YYYYYY), since ISO 8601 requires the
* number of digits in the year to be agreed upon in advance.
*
* @param {boolean=} opt_verbose Whether the verbose format should be used
* instead of the default compact one.
* @param {boolean=} opt_tz Whether the timezone offset should be included
* in the string.
* @return {string} ISO 8601 string representation of date/time.
* @override
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
*/
goog.date.DateTime.prototype.toIsoString = function(opt_verbose, opt_tz) {
'use strict';
Expand Down
88 changes: 71 additions & 17 deletions closure/goog/date/date_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ testSuite({
assertEquals('20130101', date.toIsoString());

date = new DateDate(12, 12, 1);
assertEquals('130101', date.toIsoString());
assertEquals('00130101', date.toIsoString());
},

testDateToIsoString() {
Expand Down Expand Up @@ -664,6 +664,66 @@ testSuite({
assertEquals(`Got 22 minutes from ${iso}`, 22, date.getUTCMinutes());
assertEquals(`Got 33 seconds from ${iso}`, 33, date.getUTCSeconds());

// +YYYYYY-MM-DD
iso = '+102005-02-22';
assertEquals(
`From ${iso}`, '+102005-02-22T00:00:00',
DateTime.fromIsoString(iso).toIsoString(true));

// +YYYYYY-MM-DDTHH:MM:SS
iso = '+102005-02-22T11:22:33';
assertEquals(
`From ${iso}`, '+102005-02-22T11:22:33',
DateTime.fromIsoString(iso).toIsoString(true));

// +YYYYYY-MM-DDTHH:MM:SS+03:00
iso = '+102005-02-22T11:22:33+03:00';
assertEquals(
`From ${iso}`, '+102005-02-22T08:22:33',
DateTime.fromIsoString(iso).toUTCIsoString(true));

// +YYYY-MM-DD
iso = '+2005-02-22';
assertEquals(
`From ${iso}`, '2005-02-22T00:00:00',
DateTime.fromIsoString(iso).toIsoString(true));

// +YYYYY-MM-DD
iso = '+12005-02-22';
assertEquals(
`From ${iso}`, '+012005-02-22T00:00:00',
DateTime.fromIsoString(iso).toIsoString(true));

// -YYYYYY-MM-DD
iso = '-000100-02-22';
assertEquals(
`From ${iso}`, '-000100-02-22T00:00:00',
DateTime.fromIsoString(iso).toIsoString(true));

// -YYYYYY-MM-DDTHH:MM:SS
iso = '-000100-02-22T11:22:33';
assertEquals(
`From ${iso}`, '-000100-02-22T11:22:33',
DateTime.fromIsoString(iso).toIsoString(true));

// -YYYYYY-MM-DDTHH:MM:SS+03:00
iso = '-000100-02-22T11:22:33+03:00';
assertEquals(
`From ${iso}`, '-000100-02-22T08:22:33',
DateTime.fromIsoString(iso).toUTCIsoString(true));

// -YYYY-MM-DD
iso = '-0100-02-22';
assertEquals(
`From ${iso}`, '-000100-02-22T00:00:00',
DateTime.fromIsoString(iso).toIsoString(true));

// -YYYYY-MM-DD
iso = '-00100-02-22';
assertEquals(
`From ${iso}`, '-000100-02-22T00:00:00',
DateTime.fromIsoString(iso).toIsoString(true));

// On a DST boundary, using a UTC timestamp
iso = '2019-03-10T11:22:33Z';
date = DateTime.fromIsoString(iso);
Expand Down Expand Up @@ -962,60 +1022,54 @@ testSuite({
// Javascript Date objects have special behavior for years 0-99
d = new DateDate(0, month.MAR, 3);
d.add(new Interval(Interval.DAYS, -1));
// Note that ISO strings allow dropping leading zeros on the year.
assertEquals('0000-3-3 - 1d = 0000-03-02', '00302', d.toIsoString());
assertEquals('0000-3-3 - 1d = 0000-03-02', '00000302', d.toIsoString());

// Javascript Date objects have special behavior for years 0-99
d = new DateDate(99, month.OCT, 31);
d.add(new Interval(Interval.DAYS, -1));
// Note that ISO strings allow dropping leading zeros on the year.
assertEquals('0099-10-31 - 1d = 0099-10-30', '991030', d.toIsoString());
assertEquals('0099-10-31 - 1d = 0099-10-30', '00991030', d.toIsoString());

// Javascript Date objects have special behavior for years 0-99; -1 is just
// outside that range.
d = new DateDate(-1, month.JUN, 10);
d.add(new Interval(Interval.DAYS, 1));
// Note that ISO strings allow dropping leading zeros on the year.
assertEquals('-0001-06-10 + 1d = -0001-06-11', '-10611', d.toIsoString());
assertEquals(
'-0001-06-10 + 1d = -0001-06-11', '-0000010611', d.toIsoString());

// Javascript Date objects have special behavior for years 0-99; 100 is just
// outside that range.
d = new DateDate(100, month.JUN, 10);
d.add(new Interval(Interval.DAYS, 1));
// Note that ISO strings allow dropping leading zeros on the year.
assertEquals('0100-06-10 + 1d = 0100-06-11', '1000611', d.toIsoString());
assertEquals('0100-06-10 + 1d = 0100-06-11', '01000611', d.toIsoString());

// Javascript Date objects have special behavior for years 0-99; add an
// interval that pushes the original date into the range from across the
// bottom boundary.
d = new DateDate(-1, month.DEC, 20);
d.add(new Interval(Interval.DAYS, 12));
// Note that ISO strings allow dropping leading zeros on the year.
assertEquals('-0001-12-20 + 12d = 0000-01-01', '00101', d.toIsoString());
assertEquals('-0001-12-20 + 12d = 0000-01-01', '00000101', d.toIsoString());

// Javascript Date objects have special behavior for years 0-99; add an
// interval that pushes the original date into the range from across the top
// boundary.
d = new DateDate(100, month.JAN, 3);
d.add(new Interval(Interval.DAYS, -3));
// Note that ISO strings allow dropping leading zeros on the year.
assertEquals('0100-01-03 - 3d = 0099-12-31', '991231', d.toIsoString());
assertEquals('0100-01-03 - 3d = 0099-12-31', '00991231', d.toIsoString());

// Javascript Date objects have special behavior for years 0-99; add an
// interval that pushes the original, special date outside the range across
// the bottom boundary.
d = new DateDate(0, month.JAN, 2);
d.add(new Interval(Interval.DAYS, -2));
// Note that ISO strings allow dropping leading zeros on the year.
assertEquals('0000-01-02 - 2d = -0001-12-31', '-11231', d.toIsoString());
assertEquals(
'0000-01-02 - 2d = -000001-12-31', '-0000011231', d.toIsoString());

// Javascript Date objects have special behavior for years 0-99; add an
// interval that pushes the original, special date outside the range across
// the top boundary.
d = new DateDate(99, month.DEC, 30);
d.add(new Interval(Interval.DAYS, 2));
// Note that ISO strings allow dropping leading zeros on the year.
assertEquals('0099-12-30 + 2d = 0100-01-01', '1000101', d.toIsoString());
assertEquals('0099-12-30 + 2d = 0100-01-01', '01000101', d.toIsoString());
},

/** @suppress {checkTypes} suppression added to enable type checking */
Expand Down

0 comments on commit 2dd8283

Please # to comment.