diff options
| -rw-r--r-- | src/filters.js | 104 | ||||
| -rw-r--r-- | test/FiltersSpec.js | 147 |
2 files changed, 151 insertions, 100 deletions
diff --git a/src/filters.js b/src/filters.js index e423f590..b3d4c437 100644 --- a/src/filters.js +++ b/src/filters.js @@ -68,9 +68,11 @@ </doc:example> */ angularFilter.currency = function(amount, currencySymbol){ + var formats = this.$service('$locale').NUMBER_FORMATS; this.$element.toggleClass('ng-format-negative', amount < 0); - if (isUndefined(currencySymbol)) currencySymbol = NUMBER_FORMATS.CURRENCY_SYM; - return formatNumber(amount, 2, 1).replace(/\u00A4/g, currencySymbol); + if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; + return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2) + .replace(/\u00A4/g, currencySymbol); }; /** @@ -113,35 +115,17 @@ angularFilter.currency = function(amount, currencySymbol){ </doc:example> */ -// PATTERNS[0] is an array for Decimal Pattern, PATTERNS[1] is an array Currency Pattern -// Following is the order in each pattern array: -// 0: minInteger, -// 1: minFraction, -// 2: maxFraction, -// 3: positivePrefix, -// 4: positiveSuffix, -// 5: negativePrefix, -// 6: negativeSuffix, -// 7: groupSize, -// 8: lastGroupSize -var NUMBER_FORMATS = { - DECIMAL_SEP: '.', - GROUP_SEP: ',', - PATTERNS: [[1, 0, 3, '', '', '-', '', 3, 3],[1, 2, 2, '\u00A4', '', '(\u00A4', ')', 3, 3]], - CURRENCY_SYM: '$' -}; var DECIMAL_SEP = '.'; angularFilter.number = function(number, fractionSize) { if (isNaN(number) || !isFinite(number)) return ''; - return formatNumber(number, fractionSize, 0); -}; + var formats = this.$service('$locale').NUMBER_FORMATS; + return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, + formats.DECIMAL_SEP, fractionSize); +} -function formatNumber(number, fractionSize, type) { +function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { var isNegative = number < 0, - type = type || 0, // 0 is decimal pattern, 1 is currency pattern - pattern = NUMBER_FORMATS.PATTERNS[type]; - number = Math.abs(number); var numStr = number + '', formatedText = '', @@ -154,7 +138,7 @@ function formatNumber(number, fractionSize, type) { //determine fractionSize if it is not specified if (isUndefined(fractionSize)) { - fractionSize = Math.min(Math.max(pattern[1], fractionLen), pattern[2]); + fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); } var pow = Math.pow(10, fractionSize); @@ -164,14 +148,14 @@ function formatNumber(number, fractionSize, type) { fraction = fraction[1] || ''; var pos = 0, - lgroup = pattern[8], - group = pattern[7]; + lgroup = pattern.lgSize, + group = pattern.gSize; if (whole.length >= (lgroup + group)) { pos = whole.length - lgroup; for (var i = 0; i < pos; i++) { if ((pos - i)%group === 0 && i !== 0) { - formatedText += NUMBER_FORMATS.GROUP_SEP; + formatedText += groupSep; } formatedText += whole.charAt(i); } @@ -179,7 +163,7 @@ function formatNumber(number, fractionSize, type) { for (i = pos; i < whole.length; i++) { if ((whole.length - i)%lgroup === 0 && i !== 0) { - formatedText += NUMBER_FORMATS.GROUP_SEP; + formatedText += groupSep; } formatedText += whole.charAt(i); } @@ -188,12 +172,13 @@ function formatNumber(number, fractionSize, type) { while(fraction.length < fractionSize) { fraction += '0'; } - if (fractionSize) formatedText += NUMBER_FORMATS.DECIMAL_SEP + fraction.substr(0, fractionSize); + + if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize); } - parts.push(isNegative ? pattern[5] : pattern[3]); + parts.push(isNegative ? pattern.negPre : pattern.posPre); parts.push(formatedText); - parts.push(isNegative ? pattern[6] : pattern[4]); + parts.push(isNegative ? pattern.negSuf : pattern.posSuf); return parts.join(''); } @@ -222,16 +207,11 @@ function dateGetter(name, size, offset, trim) { } function dateStrGetter(name, shortForm) { - return function(date) { + return function(date, formats) { var value = date['get' + name](); + var get = uppercase(shortForm ? ('SHORT' + name) : name); - if(name == 'Month') { - value = MONTH[value]; - } else { - value = DAY[value]; - } - - return shortForm ? value.substr(0,3) : value; + return formats[get][value]; }; } @@ -246,10 +226,9 @@ function timeZoneGetter(numFormat) { }; } -var DAY = 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','); - -var MONTH = 'January,February,March,April,May,June,July,August,September,October,November,December'. - split(','); +function ampmGetter(date, formats) { + return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; +} var DATE_FORMATS = { yyyy: dateGetter('FullYear', 4), @@ -271,26 +250,13 @@ var DATE_FORMATS = { s: dateGetter('Seconds', 1), EEEE: dateStrGetter('Day'), EEE: dateStrGetter('Day', true), - a: function(date){return date.getHours() < 12 ? 'am' : 'pm';}, + a: ampmGetter, z: timeZoneGetter(false), Z: timeZoneGetter(true) }; -var DEFAULT_DATETIME_FORMATS = { - long: 'MMMM d, y h:mm:ss a z', - medium: 'MMM d, y h:mm:ss a', - short: 'M/d/yy h:mm a', - fullDate: 'EEEE, MMMM d, y', - longDate: 'MMMM d, y', - mediumDate: 'MMM d, y', - shortDate: 'M/d/yy', - longTime: 'h:mm:ss a z', - mediumTime: 'h:mm:ss a', - shortTime: 'h:mm a' -}; - var GET_TIME_ZONE = /[A-Z]{3}(?![+\-])/; -var DATE_FORMATS_SPLIT = /([^yMdHhmsazZE]*)(E+|y+|M+|d+|H+|h+|m+|s+|a|Z|z)(.*)/; +var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/ var OPERA_TOSTRING_PATTERN = /^[\d].*Z$/; var NUMBER_STRING = /^\d+$/; @@ -329,8 +295,8 @@ var NUMBER_STRING = /^\d+$/; * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200) * * `'z'`: short form of current timezone name (e.g. PDT) * - * `format` string can also be the following default formats for `en_US` locale (support for other - * locales will be added in the future versions): + * `format` string can also be one of the following predefined + * {@link guide/dev_guide.i18n localizable formats}: * * * `'long'`: equivalent to `'MMMM d, y h:mm:ss a z'` for en_US locale * (e.g. September 3, 2010 12:05:08 pm PDT) @@ -346,6 +312,10 @@ var NUMBER_STRING = /^\d+$/; * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) * + * `format` string can contain literal values. These need to be quoted with single quotes (e.g. + * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence + * (e.g. `"h o''clock"`). + * * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or * number) or ISO 8601 extended datetime string (yyyy-MM-ddTHH:mm:ss.SSSZ). * @param {string=} format Formatting rules (see Description). If not specified, @@ -365,17 +335,18 @@ var NUMBER_STRING = /^\d+$/; <doc:scenario> it('should format date', function(){ expect(binding("1288323623006 | date:'medium'")). - toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (am|pm)/); + toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/); expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). - toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(am|pm)/); + toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); }); </doc:scenario> </doc:example> */ angularFilter.date = function(date, format) { - format = DEFAULT_DATETIME_FORMATS[format] || format; + var $locale = this.$service('$locale'); + format = $locale.DATETIME_FORMATS[format] || format; if (isString(date)) { if (NUMBER_STRING.test(date)) { date = parseInt(date, 10); @@ -408,7 +379,8 @@ angularFilter.date = function(date, format) { } forEach(parts, function(value){ fn = DATE_FORMATS[value]; - text += fn ? fn(date) : value; + text += fn ? fn(date, $locale.DATETIME_FORMATS) + : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); }); } return text; diff --git a/test/FiltersSpec.js b/test/FiltersSpec.js index f87d0317..a09ef481 100644 --- a/test/FiltersSpec.js +++ b/test/FiltersSpec.js @@ -27,10 +27,66 @@ describe('filter', function() { delete filter['fakeFilter']; }); + describe('formatNumber', function() { + var pattern; + + beforeEach(function() { + pattern = { minInt: 1, + minFrac: 0, + maxFrac: 3, + posPre: '', + posSuf: '', + negPre: '-', + negSuf: '', + gSize: 3, + lgSize: 3 }; + }); + + it('should format according to different patterns', function() { + pattern.gSize = 2; + var num = formatNumber(1234567.89, pattern, ',', '.'); + expect(num).toBe('12,34,567.89'); + num = formatNumber(1234.56, pattern, ',', '.'); + expect(num).toBe('1,234.56'); + + pattern.negPre = '('; + pattern.negSuf = '-)'; + num = formatNumber(-1234, pattern, ',', '.'); + expect(num).toBe('(1,234-)'); + pattern.posPre = '+'; + pattern.posSuf = '+'; + num = formatNumber(1234, pattern, ',', '.'); + expect(num).toBe('+1,234+'); + pattern.posPre = pattern.posSuf = ''; + + pattern.minFrac = 2; + num = formatNumber(1, pattern, ',', '.'); + expect(num).toBe('1.00'); + pattern.maxFrac = 4; + num = formatNumber(1.11119, pattern, ',', '.'); + expect(num).toBe('1.1112'); + }); + + it('should format according different seperators', function() { + var num = formatNumber(1234567.1, pattern, '.', ',', 2); + expect(num).toBe('1.234.567,10'); + }); + + it('should format with or without fractionSize', function() { + var num = formatNumber(123.1, pattern, ',', '.', 3); + expect(num).toBe('123.100'); + num = formatNumber(123.12, pattern, ',', '.'); + expect(num).toBe('123.12'); + var num = formatNumber(123.1116, pattern, ',', '.'); + expect(num).toBe('123.112'); + }); + }); + describe('currency', function() { it('should do basic currency filtering', function() { var html = jqLite('<span/>'); - var context = {$element:html}; + var context = createScope(); + context.$element = html; var currency = bind(context, filter.currency); expect(currency(0)).toEqual('$0.00'); @@ -39,13 +95,21 @@ describe('filter', function() { expect(html.hasClass('ng-format-negative')).toBeTruthy(); expect(currency(1234.5678, "USD$")).toEqual('USD$1,234.57'); expect(html.hasClass('ng-format-negative')).toBeFalsy(); + + dealoc(context); }); }); describe('number', function() { + var context, number; + + beforeEach(function() { + context = createScope(); + number = bind(context, filter.number); + }); + + it('should do basic filter', function() { - var context = {jqElement:jqLite('<span/>')}; - var number = bind(context, filter.number); expect(number(0, 0)).toEqual('0'); expect(number(-999)).toEqual('-999'); expect(number(123)).toEqual('123'); @@ -71,8 +135,6 @@ describe('filter', function() { }); it('should filter exponential numbers', function() { - var context = {jqElement:jqLite('<span/>')}; - var number = bind(context, filter.number); expect(number(1e50, 0)).toEqual('1e+50'); expect(number(-2e50, 2)).toEqual('-2e+50'); }); @@ -136,104 +198,121 @@ describe('filter', function() { var timZoneDate = new TzDate(+5, '2010-09-03T05:05:08.000Z', 'Mon Sep 3 2010 00:05:08 GMT+0500 (XYZ)'); //12am + var context, date; + + beforeEach(function() { + context = createScope(); + date = bind(context, filter.date); + }); + it('should ignore falsy inputs', function() { - expect(filter.date(null)).toBeNull(); - expect(filter.date('')).toEqual(''); + expect(date(null)).toBeNull(); + expect(date('')).toEqual(''); }); it('should do basic filter', function() { - expect(filter.date(noon)).toEqual(noon.toLocaleDateString()); - expect(filter.date(noon, '')).toEqual(noon.toLocaleDateString()); + expect(date(noon)).toEqual(noon.toLocaleDateString()); + expect(date(noon, '')).toEqual(noon.toLocaleDateString()); }); it('should accept number or number string representing milliseconds as input', function() { - expect(filter.date(noon.getTime())).toEqual(noon.toLocaleDateString()); - expect(filter.date(noon.getTime() + "")).toEqual(noon.toLocaleDateString()); + expect(date(noon.getTime())).toEqual(noon.toLocaleDateString()); + expect(date(noon.getTime() + "")).toEqual(noon.toLocaleDateString()); }); it('should accept various format strings', function() { - expect(filter.date(morning, "yy-MM-dd HH:mm:ss")). + expect(date(morning, "yy-MM-dd HH:mm:ss")). toEqual('10-09-03 07:05:08'); - expect(filter.date(midnight, "yyyy-M-d h=H:m:saZ")). + expect(date(midnight, "yyyy-M-d h=H:m:saZ")). toEqual('2010-9-3 12=0:5:8am0500'); - expect(filter.date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")). + expect(date(midnight, "yyyy-MM-dd hh=HH:mm:ssaZ")). toEqual('2010-09-03 12=00:05:08am0500'); - expect(filter.date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")). + expect(date(noon, "yyyy-MM-dd hh=HH:mm:ssaZ")). toEqual('2010-09-03 12=12:05:08pm0500'); - expect(filter.date(timZoneDate, "yyyy-MM-dd hh=HH:mm:ss a z")). + expect(date(timZoneDate, "yyyy-MM-dd hh=HH:mm:ss a z")). toEqual('2010-09-03 12=00:05:08 am XYZ'); - expect(filter.date(noon, "EEE, MMM d, yyyy")). + expect(date(noon, "EEE, MMM d, yyyy")). toEqual('Fri, Sep 3, 2010'); - expect(filter.date(noon, "EEEE, MMMM dd, yyyy")). + expect(date(noon, "EEEE, MMMM dd, yyyy")). toEqual('Friday, September 03, 2010'); - expect(filter.date(earlyDate, "MMMM dd, y")). + expect(date(earlyDate, "MMMM dd, y")). toEqual('September 03, 1'); }); + it('should treat single quoted strings as string literals', function() { + expect(date(midnight, "yyyy'de' 'a'x'dd' 'adZ' h=H:m:saZ")). + toEqual('2010de axdd adZ 12=0:5:8AM0500'); + }); + + it('should treat a sequence of two single quotes as a literal single quote', function() { + expect(date(midnight, "yyyy'de' 'a''dd' 'adZ' h=H:m:saZ")). + toEqual("2010de a'dd adZ 12=0:5:8AM0500"); + }); + it('should accept default formats', function() { - expect(filter.date(timZoneDate, "long")). + expect(date(timZoneDate, "long")). toEqual('September 3, 2010 12:05:08 am XYZ'); - expect(filter.date(noon, "medium")). + expect(date(noon, "medium")). toEqual('Sep 3, 2010 12:05:08 pm'); - expect(filter.date(noon, "short")). + expect(date(noon, "short")). toEqual('9/3/10 12:05 pm'); - expect(filter.date(noon, "fullDate")). + expect(date(noon, "fullDate")). toEqual('Friday, September 3, 2010'); - expect(filter.date(noon, "longDate")). + expect(date(noon, "longDate")). toEqual('September 3, 2010'); - expect(filter.date(noon, "mediumDate")). + expect(date(noon, "mediumDate")). toEqual('Sep 3, 2010'); - expect(filter.date(noon, "shortDate")). + expect(date(noon, "shortDate")). toEqual('9/3/10'); - expect(filter.date(timZoneDate, "longTime")). + expect(date(timZoneDate, "longTime")). toEqual('12:05:08 am XYZ'); - expect(filter.date(noon, "mediumTime")). + expect(date(noon, "mediumTime")). toEqual('12:05:08 pm'); - expect(filter.date(noon, "shortTime")). + expect(date(noon, "shortTime")). toEqual('12:05 pm'); }); it('should parse timezone identifier from various toString values', function() { //chrome and firefox format - expect(filter.date(new TzDate(+5, '2010-09-03T17:05:08.000Z', + expect(date(new TzDate(+5, '2010-09-03T17:05:08.000Z', 'Mon Sep 3 2010 17:05:08 GMT+0500 (XYZ)'), "z")).toBe('XYZ'); //opera format - expect(filter.date(new TzDate(+5, '2010-09-03T17:05:08.000Z', + expect(date(new TzDate(+5, '2010-09-03T17:05:08.000Z', '2010-09-03T17:05:08Z'), "z")).toBe('0500'); //ie 8 format - expect(filter.date(new TzDate(+5, '2010-09-03T17:05:08.000Z', + expect(date(new TzDate(+5, '2010-09-03T17:05:08.000Z', 'Mon Sep 3 17:05:08 XYZ 2010'), "z")).toBe('XYZ'); }); it('should be able to parse ISO 8601 dates/times using', function() { var isoString = '2010-09-03T05:05:08.872Z'; - expect(filter.date(isoString)). + expect(date(isoString)). toEqual(angular.String.toDate(isoString).toLocaleDateString()); }); it('should parse format ending with non-replaced string', function() { - expect(filter.date(morning, 'yy/xxx')).toEqual('10/xxx'); + expect(date(morning, 'yy/xxx')).toEqual('10/xxx'); }); }); }); |
