Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix months parsing for stranger locales #2869

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/lib/locale/prototype.js
Expand Up @@ -30,14 +30,20 @@ proto.set = set;
import {
localeMonthsParse,
defaultLocaleMonths, localeMonths,
defaultLocaleMonthsShort, localeMonthsShort
defaultLocaleMonthsShort, localeMonthsShort,
defaultMonthsRegex, monthsRegex,
defaultMonthsShortRegex, monthsShortRegex
} from '../units/month';

proto.months = localeMonths;
proto._months = defaultLocaleMonths;
proto.monthsShort = localeMonthsShort;
proto._monthsShort = defaultLocaleMonthsShort;
proto.monthsParse = localeMonthsParse;
proto.months = localeMonths;
proto._months = defaultLocaleMonths;
proto.monthsShort = localeMonthsShort;
proto._monthsShort = defaultLocaleMonthsShort;
proto.monthsParse = localeMonthsParse;
proto._monthsRegex = defaultMonthsRegex;
proto.monthsRegex = monthsRegex;
proto._monthsShortRegex = defaultMonthsShortRegex;
proto.monthsShortRegex = monthsShortRegex;

// Week
import { localeWeek, defaultLocaleWeek, localeFirstDayOfYear, localeFirstDayOfWeek } from '../units/week';
Expand Down
12 changes: 8 additions & 4 deletions src/lib/parse/regex.js
Expand Up @@ -20,7 +20,7 @@ export var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123

// any word (or two) characters or numbers including two/three word month in arabic.
// includes scottish gaelic two word and hyphenated months
export var matchWord = /[0-9]*(a[mn]\s?)?['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF\-]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;
export var matchWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i;


import hasOwnProp from '../utils/has-own-prop';
Expand All @@ -29,7 +29,7 @@ import isFunction from '../utils/is-function';
var regexes = {};

export function addRegexToken (token, regex, strictRegex) {
regexes[token] = isFunction(regex) ? regex : function (isStrict) {
regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) {
return (isStrict && strictRegex) ? strictRegex : regex;
};
}
Expand All @@ -44,7 +44,11 @@ export function getParseRegexForToken (token, config) {

// Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
function unescapeFormat(s) {
return s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
return p1 || p2 || p3 || p4;
}).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}));
}

export function regexEscape(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
77 changes: 74 additions & 3 deletions src/lib/units/month.js
@@ -1,7 +1,8 @@
import { get } from '../moment/get-set';
import hasOwnProp from '../utils/has-own-prop';
import { addFormatToken } from '../format/format';
import { addUnitAlias } from './aliases';
import { addRegexToken, match1to2, match2, matchWord } from '../parse/regex';
import { addRegexToken, match1to2, match2, matchWord, regexEscape } from '../parse/regex';
import { addParseToken } from '../parse/token';
import { hooks } from '../utils/hooks';
import { MONTH } from './constants';
Expand Down Expand Up @@ -36,8 +37,12 @@ addUnitAlias('month', 'M');

addRegexToken('M', match1to2);
addRegexToken('MM', match1to2, match2);
addRegexToken('MMM', matchWord);
addRegexToken('MMMM', matchWord);
addRegexToken('MMM', function (isStrict, locale) {
return locale.monthsShortRegex(isStrict);
});
addRegexToken('MMMM', function (isStrict, locale) {
return locale.monthsRegex(isStrict);
});

addParseToken(['M', 'MM'], function (input, array) {
array[MONTH] = toInt(input) - 1;
Expand Down Expand Up @@ -136,3 +141,69 @@ export function getSetMonth (value) {
export function getDaysInMonth () {
return daysInMonth(this.year(), this.month());
}

export var defaultMonthsShortRegex = matchWord;
export function monthsShortRegex (isStrict) {
if (this._monthsParseExact) {
if (!hasOwnProp(this, '_monthsRegex')) {
computeMonthsParse.call(this);
}
if (isStrict) {
return this._monthsShortStrictRegex;
} else {
return this._monthsShortRegex;
}
} else {
return this._monthsShortStrictRegex && isStrict ?
this._monthsShortStrictRegex : this._monthsShortRegex;
}
}

export var defaultMonthsRegex = matchWord;
export function monthsRegex (isStrict) {
if (this._monthsParseExact) {
if (!hasOwnProp(this, '_monthsRegex')) {
computeMonthsParse.call(this);
}
if (isStrict) {
return this._monthsStrictRegex;
} else {
return this._monthsRegex;
}
} else {
return this._monthsStrictRegex && isStrict ?
this._monthsStrictRegex : this._monthsRegex;
}
}

function computeMonthsParse () {
function cmpLenRev(a, b) {
return b.length - a.length;
}

var shortPieces = [], longPieces = [], mixedPieces = [],
i, mom;
for (i = 0; i < 12; i++) {
// make the regex if we don't have it already
mom = createUTC([2000, i]);
shortPieces.push(this.monthsShort(mom, ''));
longPieces.push(this.months(mom, ''));
mixedPieces.push(this.months(mom, ''));
mixedPieces.push(this.monthsShort(mom, ''));
}
// Sorting makes sure if one month (or abbr) is a prefix of another it
// will match the longer piece.
shortPieces.sort(cmpLenRev);
longPieces.sort(cmpLenRev);
mixedPieces.sort(cmpLenRev);
for (i = 0; i < 12; i++) {
shortPieces[i] = regexEscape(shortPieces[i]);
longPieces[i] = regexEscape(longPieces[i]);
mixedPieces[i] = regexEscape(mixedPieces[i]);
}

this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
this._monthsShortRegex = this._monthsRegex;
this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')$', 'i');
this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')$', 'i');
}
14 changes: 2 additions & 12 deletions src/locale/gd.js
Expand Up @@ -5,18 +5,7 @@
import moment from '../moment';

var months = [
'Am Faoilleach',
'An Gearran',
'Am Màrt',
'An Giblean',
'An Cèitean',
'An t-Ògmhios',
'An t-Iuchar',
'An Lùnastal',
'An t-Sultain',
'An Dàmhair',
'An t-Samhain',
'An Dùbhlachd'
'Am Faoilleach', 'An Gearran', 'Am Màrt', 'An Giblean', 'An Cèitean', 'An t-Ògmhios', 'An t-Iuchar', 'An Lùnastal', 'An t-Sultain', 'An Dàmhair', 'An t-Samhain', 'An Dùbhlachd'
];

var monthsShort = ['Faoi', 'Gear', 'Màrt', 'Gibl', 'Cèit', 'Ògmh', 'Iuch', 'Lùn', 'Sult', 'Dàmh', 'Samh', 'Dùbh'];
Expand All @@ -30,6 +19,7 @@ var weekdaysMin = ['Dò', 'Lu', 'Mà', 'Ci', 'Ar', 'Ha', 'Sa'];
export default moment.defineLocale('gd', {
months : months,
monthsShort : monthsShort,
monthsParseExact : true,
weekdays : weekdays,
weekdaysShort : weekdaysShort,
weekdaysMin : weekdaysMin,
Expand Down
2 changes: 1 addition & 1 deletion src/test/locale/gd.js
Expand Up @@ -19,7 +19,7 @@ var months = [

test('parse', function (assert) {
function equalTest(monthName, monthFormat, monthNum) {
assert.equal(moment(monthName, monthFormat).month(), monthNum, monthName + ' should be month ' + monthNum + 1);
assert.equal(moment(monthName, monthFormat).month(), monthNum, monthName + ' should be month ' + (monthNum + 1));
}

for (var i = 0; i < 12; i++) {
Expand Down
32 changes: 32 additions & 0 deletions src/test/moment/locale.js
Expand Up @@ -445,3 +445,35 @@ test('moment().lang with missing key doesn\'t change locale', function (assert)
'preserve global locale in case of bad locale id');
});


// TODO: Enable this after fixing pl months parse hack hack
// test('monthsParseExact', function (assert) {
// var locale = 'test-months-parse-exact';

// moment.defineLocale(locale, {
// monthsParseExact: true,
// months: 'A_AA_AAA_B_B B_BB B_C_C-C_C,C2C_D_D+D_D`D*D'.split('_'),
// monthsShort: 'E_EE_EEE_F_FF_FFF_G_GG_GGG_H_HH_HHH'.split('_')
// });

// assert.equal(moment('A', 'MMMM', true).month(), 0, 'parse long month 0 with MMMM');
// assert.equal(moment('AA', 'MMMM', true).month(), 1, 'parse long month 1 with MMMM');
// assert.equal(moment('AAA', 'MMMM', true).month(), 2, 'parse long month 2 with MMMM');
// assert.equal(moment('B B', 'MMMM', true).month(), 4, 'parse long month 4 with MMMM');
// assert.equal(moment('BB B', 'MMMM', true).month(), 5, 'parse long month 5 with MMMM');
// assert.equal(moment('C-C', 'MMMM', true).month(), 7, 'parse long month 7 with MMMM');
// assert.equal(moment('C,C2C', 'MMMM', true).month(), 8, 'parse long month 8 with MMMM');
// assert.equal(moment('D+D', 'MMMM', true).month(), 10, 'parse long month 10 with MMMM');
// assert.equal(moment('D`D*D', 'MMMM', true).month(), 11, 'parse long month 11 with MMMM');

// assert.equal(moment('E', 'MMM', true).month(), 0, 'parse long month 0 with MMM');
// assert.equal(moment('EE', 'MMM', true).month(), 1, 'parse long month 1 with MMM');
// assert.equal(moment('EEE', 'MMM', true).month(), 2, 'parse long month 2 with MMM');

// assert.equal(moment('A', 'MMM').month(), 0, 'non-strict parse long month 0 with MMM');
// assert.equal(moment('AA', 'MMM').month(), 1, 'non-strict parse long month 1 with MMM');
// assert.equal(moment('AAA', 'MMM').month(), 2, 'non-strict parse long month 2 with MMM');
// assert.equal(moment('E', 'MMMM').month(), 0, 'non-strict parse short month 0 with MMMM');
// assert.equal(moment('EE', 'MMMM').month(), 1, 'non-strict parse short month 1 with MMMM');
// assert.equal(moment('EEE', 'MMMM').month(), 2, 'non-strict parse short month 2 with MMMM');
// });