]> git.ipfire.org Git - thirdparty/moment.git/commitdiff
Fix #3985 fix Invalid date for RFC 2822 with some refactoring
authorPetr Potekhin <PAPotehin.SBT@sberbank.ru>
Wed, 19 Jul 2017 23:22:15 +0000 (02:22 +0300)
committerIskren Chernev <iskren.chernev@gmail.com>
Mon, 7 Aug 2017 01:09:46 +0000 (04:09 +0300)
src/lib/create/from-string.js
src/test/locale/ru.js

index 7cae76d70ef57c64b683ac9a97876ab5b684b922..6f7af0cc205beb69e2285293544d920fb65c7800 100644 (file)
@@ -1,7 +1,10 @@
 import { configFromStringAndFormat } from './from-string-and-format';
+import { configFromArray } from './from-array';
 import { hooks } from '../utils/hooks';
 import { deprecate } from '../utils/deprecate';
 import getParsingFlags from './parsing-flags';
+import {defaultLocaleMonthsShort} from '../units/month';
+import {defaultLocaleWeekdaysShort} from '../units/day-of-week';
 
 // iso 8601 regex
 // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
@@ -94,70 +97,91 @@ export function configFromISO(config) {
 }
 
 // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
-var basicRfcRegex = /^((?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d?\d\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(?:\d\d)?\d\d\s)(\d\d:\d\d)(\:\d\d)?(\s(?:UT|GMT|[ECMP][SD]T|[A-IK-Za-ik-z]|[+-]\d{4}))$/;
-
-// date and time from ref 2822 format
-export function configFromRFC2822(config) {
-    var string, match, dayFormat,
-        dateFormat, timeFormat, tzFormat;
-    var timezones = {
-        ' GMT': ' +0000',
-        ' EDT': ' -0400',
-        ' EST': ' -0500',
-        ' CDT': ' -0500',
-        ' CST': ' -0600',
-        ' MDT': ' -0600',
-        ' MST': ' -0700',
-        ' PDT': ' -0700',
-        ' PST': ' -0800'
+var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|(?:([+-]\d\d)(\d\d)))$/;
+
+function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) {
+    var result = {
+        year: yearStr.length === 2 ? untrucateYear(parse10(yearStr)) : parse10(yearStr),
+        month: defaultLocaleMonthsShort.indexOf(monthStr),
+        day: parse10(dayStr),
+        hour: parse10(hourStr),
+        minute: parse10(minuteStr)
     };
-    var military = 'YXWVUTSRQPONZABCDEFGHIKLM';
-    var timezone, timezoneIndex;
 
-    string = config._i
-        .replace(/\([^\)]*\)|[\n\t]/g, ' ') // Remove comments and folding whitespace
-        .replace(/(\s\s+)/g, ' ') // Replace multiple-spaces with a single space
-        .replace(/^\s|\s$/g, ''); // Remove leading and trailing spaces
-    match = basicRfcRegex.exec(string);
+    if (secondStr) {
+        result.second = parse10(secondStr);
+    }
 
-    if (match) {
-        dayFormat = match[1] ? 'ddd' + ((match[1].length === 5) ? ', ' : ' ') : '';
-        dateFormat = 'D MMM ' + ((match[2].length > 10) ? 'YYYY ' : 'YY ');
-        timeFormat = 'HH:mm' + (match[4] ? ':ss' : '');
+    return result;
+}
 
-        // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
-        if (match[1]) { // day of week given
-            var momentDate = new Date(match[2]);
-            var momentDay = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][momentDate.getDay()];
+function untrucateYear(year) {
+    return year > 60 ? 1900 + year : 2000 + year;
+}
 
-            if (match[1].substr(0,3) !== momentDay) {
-                getParsingFlags(config).weekdayMismatch = true;
-                config._isValid = false;
-                return;
-            }
+function preprocessRFC2822(s) {
+    // Remove comments and folding whitespace and replace multiple-spaces with a single space
+    return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').trim();
+}
+
+function signedOffset(offHourStr, offMinuteStr) {
+    var offHour = parse10(offHourStr) || 0,
+        offMin = parse10(offMinuteStr) || 0,
+        offMinSigned = offHour < 0 ? -offMin : offMin;
+    return offHour * 60 + offMinSigned;
+}
+
+function checkWeekday(weekdayStr, parsedInput, config) {
+    if (weekdayStr) {
+        // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check.
+        var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
+            weekdayActual = new Date(parsedInput.year, parsedInput.month, parsedInput.day).getDay();
+        if (weekdayProvided !== weekdayActual) {
+            getParsingFlags(config).weekdayMismatch = true;
+            config._isValid = false;
+            return false;
         }
+    }
+    return true;
+}
 
-        switch (match[5].length) {
-            case 2: // military
-                if (timezoneIndex === 0) {
-                    timezone = ' +0000';
-                } else {
-                    timezoneIndex = military.indexOf(match[5][1].toUpperCase()) - 12;
-                    timezone = ((timezoneIndex < 0) ? ' -' : ' +') +
-                        (('' + timezoneIndex).replace(/^-?/, '0')).match(/..$/)[0] + '00';
-                }
-                break;
-            case 4: // Zone
-                timezone = timezones[match[5]];
-                break;
-            default: // UT or +/-9999
-                timezone = timezones[' GMT'];
+var obsOffsets = {
+    GMT: 0,
+    EDT: -4 * 60,
+    EST: -5 * 60,
+    CDT: 5 * 60,
+    CST: 6 * 60,
+    MDT: 6 * 60,
+    MST: 7 * 60,
+    PDT: 7 * 60,
+    PST: 8 * 60
+};
+
+function parse10(inty) {
+    return parseInt(inty, 10);
+}
+
+function calculateOffset(obsOffset, milOffset, offHourStr, offMinuteStr) {
+    if (obsOffset) {
+        return obsOffsets[obsOffset];
+    } else {
+        return (milOffset) ? 0 : signedOffset(offHourStr, offMinuteStr);
+    }
+}
+
+// date and time from ref 2822 format
+export function configFromRFC2822(config) {
+    var match = rfc2822.exec(preprocessRFC2822(config._i));
+    if (match) {
+        var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]);
+        if (!checkWeekday(match[1], parsedArray, config)) {
+            return;
         }
-        match[5] = timezone;
-        config._i = match.splice(1).join('');
-        tzFormat = ' ZZ';
-        config._f = dayFormat + dateFormat + timeFormat + tzFormat;
-        configFromStringAndFormat(config);
+
+        config._a = [parsedArray.year, parsedArray.month, parsedArray.day, parsedArray.hour, parsedArray.minute, parsedArray.second];
+        config._tzm = calculateOffset(match[8], match[9], match[10], match[11]);
+
+        configFromArray(config);
         getParsingFlags(config).rfc2822 = true;
     } else {
         config._isValid = false;
index b7a13f691132f986922630eaa33ad8ea15d73807..0ea1d74ff324bf09a7c465f93119f4bc74400273 100644 (file)
@@ -353,3 +353,35 @@ test('weeks year starting monday formatted', function (assert) {
     assert.equal(moment([2012,  0,  9]).format('w ww wo'), '2 02 2-я', 'Jan  9 2012 should be week 2');
 });
 
+test('parsing RFC 2822', function (assert) {
+    var testCases = {
+        'clean RFC2822 datetime with all options': 'Tue, 01 Nov 2016 01:23:45 UT',
+        'clean RFC2822 datetime without comma': 'Tue 01 Nov 2016 02:23:45 GMT',
+        'clean RFC2822 datetime without seconds': 'Tue, 01 Nov 2016 03:23 +0000',
+        'clean RFC2822 datetime without century': 'Tue, 01 Nov 16 04:23:45 Z',
+        'clean RFC2822 datetime without day': '01 Nov 2016 05:23:45 z',
+        'clean RFC2822 datetime with single-digit day-of-month': 'Tue, 1 Nov 2016 06:23:45 GMT',
+        'RFC2822 datetime with CFWSs': '(Init Comment) Tue,\n 1 Nov              2016 (Split\n Comment)  07:23:45 +0000 (GMT)'
+    };
+    var testCase;
+
+    for (testCase in testCases) {
+        var testResult = moment(testCases[testCase], moment.RFC_2822, true);
+        assert.ok(testResult.isValid(), testResult);
+        assert.ok(testResult.parsingFlags().rfc2822, testResult + ' - rfc2822 parsingFlag');
+    }
+});
+
+test('non RFC 2822 strings', function (assert) {
+    var testCases = {
+        'RFC2822 datetime with all options but invalid day delimiter': 'Tue. 01 Nov 2016 01:23:45 GMT',
+        'RFC2822 datetime with mismatching Day (week v date)': 'Mon, 01 Nov 2016 01:23:45 GMT'
+    };
+    var testCase;
+
+    for (testCase in testCases) {
+        var testResult = moment(testCases[testCase], moment.RFC_2822, true);
+        assert.ok(!testResult.isValid(), testResult);
+        assert.ok(!testResult.parsingFlags().rfc2822, testResult + ' - rfc2822 parsingFlag');
+    }
+});