Skip to content

Loading…

Fix parsing/formatting of first century dates. #367

Closed
wants to merge 3 commits into from

2 participants

@jasondavies

Updated version of #366 for the develop branch.

According to ECMA-262, new Date(y, …) will auto-convert y to 1900 + y if 0 ≤ y ≤ 99, hence setFullYear or setUTCFullYear is used instead.

This also zero-pads the year for the "YYYY" format, since the year may not necessarily have four digits.

Lastly, the pt-br test is adjusted to test for the first day of the month instead of the zeroth (which was causing it to fail).

@jasondavies jasondavies Fix parsing/formatting of first century dates.
According to ECMA-262, `new Date(y, …)` will auto-convert `y` to `1900 +
y` if `0 ≤ y ≤ 99`, hence setFullYear or setUTCFullYear is used instead.

This also zero-pads the year for the "YYYY" format, since the year may
not necessarily have four digits.

Lastly, the pt-br test is adjusted to test for the first day of the
month instead of the zeroth (which was causing it to fail).
f38f021
@timrwood
Moment.js member

Looks great! I didn't know you could use setFullYear and pass in months and days as well, and the same for setHours.

We should probably add regression tests to make sure this doesn't become undone later on. Probably test moment([0, 0, 1]) as well as some negative years and far future years. Maybe even as far back as 270,000 BC and as far forward as 270,000 AD just for the hell of it.

@jasondavies

Okay, tests added. I also added tests for ±270,000 as you suggested, but I also had to add a special format specifier for these, YYYYY, since they interfere with YYYYMMDD if YYYY tries to match 6 digits. I believe this is compatible with ISO-8601, which states that the calendar year should be limited to 4 digits unless otherwise agreed by the parties.

@jasondavies

Another minor update: support for negative four-digit years (only when YYYYY is in use).

@timrwood timrwood added a commit that referenced this pull request
@timrwood timrwood Parsing very early dates 30b60c8
@timrwood
Moment.js member

Thanks for all this!

I cherry picked your first commit in, but I feel like adding YYYYY should be a separate pull request and discussion. If you still think YYYYY should be in the parser, create a new PR and we can discuss it there.

I also changed the YYYY parser to accept 1-4 digits so that moment('Dec 25 1', 'MMM DD YYYY') will work.

@timrwood timrwood closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 18, 2012
  1. @jasondavies

    Fix parsing/formatting of first century dates.

    jasondavies committed
    According to ECMA-262, `new Date(y, …)` will auto-convert `y` to `1900 +
    y` if `0 ≤ y ≤ 99`, hence setFullYear or setUTCFullYear is used instead.
    
    This also zero-pads the year for the "YYYY" format, since the year may
    not necessarily have four digits.
    
    Lastly, the pt-br test is adjusted to test for the first day of the
    month instead of the zeroth (which was causing it to fail).
Commits on Jul 19, 2012
  1. @jasondavies
  2. @jasondavies
This page is out of date. Refresh to see the latest.
Showing with 44 additions and 10 deletions.
  1. +16 −5 moment.js
  2. +2 −2 test/lang/pt-br.js
  3. +26 −3 test/moment/create.js
View
21 moment.js
@@ -30,7 +30,7 @@
aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
// format tokens
- formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?)/g,
+ formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|YYYYY|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|zz?|ZZ?|-)/g,
localFormattingTokens = /(LT|LL?L?L?)/g,
formattingRemoveEscapes = /(^\[)|(\\)|\]$/g,
@@ -42,6 +42,7 @@
parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
parseTokenThreeDigits = /\d{3}/, // 000 - 999
parseTokenFourDigits = /\d{4}/, // 0000 - 9999
+ parseTokenFiveToSixDigits = /[+\-\d]?\d{4,6}/, // -999,999 - 999,999
parseTokenWord = /[0-9a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+/i, // any word characters or numbers
parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z
parseTokenT = /T/i, // T (ISO seperator)
@@ -103,7 +104,8 @@
dddd : 'v("weekdays",t.day())',
w : '(a=new Date(t.year(),t.month(),t.date()-t.day()+5),b=new Date(a.getFullYear(),0,4),a=~~((a-b)/864e5/7+1.5))',
YY : 'p(t.year()%100,2)',
- YYYY : 't.year()',
+ YYYY : 'p(t.year(),4)',
+ YYYYY: 'p(t.year(),5)',
a : 'm(t.hours(),t.minutes(),!0)',
A : 'm(t.hours(),t.minutes(),!1)',
H : 't.hours()',
@@ -274,8 +276,14 @@
}
// we store whether we used utc or not in the input array
input[7] = asUTC;
- date = asUTC ? new Date(Date.UTC.apply({}, input)) :
- new Date(input[0], input[1], input[2], input[3], input[4], input[5], input[6]);
+ date = new Date(0);
+ if (asUTC) {
+ date.setUTCFullYear(input[0], input[1], input[2]);
+ date.setUTCHours(input[3], input[4], input[5], input[6]);
+ } else {
+ date.setFullYear(input[0], input[1], input[2]);
+ date.setHours(input[3], input[4], input[5], input[6]);
+ }
date._a = input;
return date;
}
@@ -396,6 +404,8 @@
return parseTokenThreeDigits;
case 'YYYY':
return parseTokenFourDigits;
+ case 'YYYYY':
+ return parseTokenFiveToSixDigits;
case 'S':
case 'SS':
case 'SSS':
@@ -468,7 +478,8 @@
datePartArray[0] = input + (input > 70 ? 1900 : 2000);
break;
case 'YYYY' :
- datePartArray[0] = ~~Math.abs(input);
+ case 'YYYYY' :
+ datePartArray[0] = ~~input;
break;
// AM / PM
case 'a' : // fall through to A
View
4 test/lang/pt-br.js
@@ -105,7 +105,7 @@ exports["lang:pt-br"] = {
var expected = 'Janeiro Jan_Fevereiro Fev_Março Mar_Abril Abr_Maio Mai_Junho Jun_Julho Jul_Agosto Ago_Setembro Set_Outubro Out_Novembro Nov_Dezembro Dez'.split("_");
var i;
for (i = 0; i < expected.length; i++) {
- test.equal(moment([2011, i, 0]).format('MMMM MMM'), expected[i], expected[i]);
+ test.equal(moment([2011, i, 1]).format('MMMM MMM'), expected[i], expected[i]);
}
test.done();
},
@@ -238,4 +238,4 @@ exports["lang:pt-br"] = {
test.equal(weeksFromNow.calendar(), weeksFromNow.format('L'), "in 2 weeks");
test.done();
}
-};
+};
View
29 test/moment/create.js
@@ -92,9 +92,9 @@ exports.create = {
"empty string with formats" : function(test) {
test.expect(3);
- test.equal(moment(' ', 'MM').format('YYYY-MM-DD HH:mm:ss'), '1900-01-01 00:00:00', 'should not break if input is an empty string');
- test.equal(moment(' ', 'DD').format('YYYY-MM-DD HH:mm:ss'), '1900-01-01 00:00:00', 'should not break if input is an empty string');
- test.equal(moment(' ', ['MM', "DD"]).format('YYYY-MM-DD HH:mm:ss'), '1900-01-01 00:00:00', 'should not break if input is an empty string');
+ test.equal(moment(' ', 'MM').format('YYYY-MM-DD HH:mm:ss'), '0000-01-01 00:00:00', 'should not break if input is an empty string');
+ test.equal(moment(' ', 'DD').format('YYYY-MM-DD HH:mm:ss'), '0000-01-01 00:00:00', 'should not break if input is an empty string');
+ test.equal(moment(' ', ['MM', "DD"]).format('YYYY-MM-DD HH:mm:ss'), '0000-01-01 00:00:00', 'should not break if input is an empty string');
test.done();
},
@@ -294,5 +294,28 @@ exports.create = {
test.equal(moment(null), null, "Calling moment(null)");
test.equal(moment('', 'YYYY-MM-DD'), null, "Calling moment('', 'YYYY-MM-DD')");
test.done();
+ },
+
+ "first century" : function(test) {
+ test.expect(2);
+ test.equal(moment([0, 0, 1]).format("YYYY-MM-DD"), "0000-01-01", "Year AD 0");
+ test.equal(moment([99, 0, 1]).format("YYYY-MM-DD"), "0099-01-01", "Year AD 99");
+ test.done();
+ },
+
+ "six digit years" : function(test) {
+ test.expect(5);
+ test.equal(moment([-270000, 0, 1]).format("YYYYY-MM-DD"), "-270000-01-01", "format BC 270,001");
+ test.equal(moment([ 270000, 0, 1]).format("YYYYY-MM-DD"), "270000-01-01", "format AD 270,000");
+ test.equal(moment("-270000-01-01", "YYYYY-MM-DD").toDate().getUTCFullYear(), -270000, "parse BC 270,001");
+ test.equal(moment("270000-01-01", "YYYYY-MM-DD").toDate().getUTCFullYear(), 270000, "parse AD 270,000");
+ test.equal(moment("+270000-01-01", "YYYYY-MM-DD").toDate().getUTCFullYear(), 270000, "parse AD +270,000");
+ test.done();
+ },
+
+ "negative four digit years" : function(test) {
+ test.expect(1);
+ test.equal(moment("-1000-01-01", "YYYYY-MM-DD").toDate().getUTCFullYear(), -1000, "parse BC 1,001");
+ test.done();
}
};
Something went wrong with that request. Please try again.