1 |
torben |
2125 |
/*! |
2 |
|
|
* Globalize |
3 |
|
|
* |
4 |
|
|
* http://github.com/jquery/globalize |
5 |
|
|
* |
6 |
|
|
* Copyright Software Freedom Conservancy, Inc. |
7 |
|
|
* Dual licensed under the MIT or GPL Version 2 licenses. |
8 |
|
|
* http://jquery.org/license |
9 |
|
|
*/ |
10 |
|
|
|
11 |
|
|
(function( window, undefined ) { |
12 |
|
|
|
13 |
|
|
var Globalize, |
14 |
|
|
// private variables |
15 |
|
|
regexHex, |
16 |
|
|
regexInfinity, |
17 |
|
|
regexParseFloat, |
18 |
|
|
regexTrim, |
19 |
|
|
// private JavaScript utility functions |
20 |
|
|
arrayIndexOf, |
21 |
|
|
endsWith, |
22 |
|
|
extend, |
23 |
|
|
isArray, |
24 |
|
|
isFunction, |
25 |
|
|
isObject, |
26 |
|
|
startsWith, |
27 |
|
|
trim, |
28 |
|
|
truncate, |
29 |
|
|
zeroPad, |
30 |
|
|
// private Globalization utility functions |
31 |
|
|
appendPreOrPostMatch, |
32 |
|
|
expandFormat, |
33 |
|
|
formatDate, |
34 |
|
|
formatNumber, |
35 |
|
|
getTokenRegExp, |
36 |
|
|
getEra, |
37 |
|
|
getEraYear, |
38 |
|
|
parseExact, |
39 |
|
|
parseNegativePattern; |
40 |
|
|
|
41 |
|
|
// Global variable (Globalize) or CommonJS module (globalize) |
42 |
|
|
Globalize = function( cultureSelector ) { |
43 |
|
|
return new Globalize.prototype.init( cultureSelector ); |
44 |
|
|
}; |
45 |
|
|
|
46 |
|
|
if ( typeof require !== "undefined" |
47 |
|
|
&& typeof exports !== "undefined" |
48 |
|
|
&& typeof module !== "undefined" ) { |
49 |
|
|
// Assume CommonJS |
50 |
|
|
module.exports = Globalize; |
51 |
|
|
} else { |
52 |
|
|
// Export as global variable |
53 |
|
|
window.Globalize = Globalize; |
54 |
|
|
} |
55 |
|
|
|
56 |
|
|
Globalize.cultures = {}; |
57 |
|
|
|
58 |
|
|
Globalize.prototype = { |
59 |
|
|
constructor: Globalize, |
60 |
|
|
init: function( cultureSelector ) { |
61 |
|
|
this.cultures = Globalize.cultures; |
62 |
|
|
this.cultureSelector = cultureSelector; |
63 |
|
|
|
64 |
|
|
return this; |
65 |
|
|
} |
66 |
|
|
}; |
67 |
|
|
Globalize.prototype.init.prototype = Globalize.prototype; |
68 |
|
|
|
69 |
|
|
// 1. When defining a culture, all fields are required except the ones stated as optional. |
70 |
|
|
// 2. Each culture should have a ".calendars" object with at least one calendar named "standard" |
71 |
|
|
// which serves as the default calendar in use by that culture. |
72 |
|
|
// 3. Each culture should have a ".calendar" object which is the current calendar being used, |
73 |
|
|
// it may be dynamically changed at any time to one of the calendars in ".calendars". |
74 |
|
|
Globalize.cultures[ "default" ] = { |
75 |
|
|
// A unique name for the culture in the form <language code>-<country/region code> |
76 |
|
|
name: "en", |
77 |
|
|
// the name of the culture in the english language |
78 |
|
|
englishName: "English", |
79 |
|
|
// the name of the culture in its own language |
80 |
|
|
nativeName: "English", |
81 |
|
|
// whether the culture uses right-to-left text |
82 |
|
|
isRTL: false, |
83 |
|
|
// "language" is used for so-called "specific" cultures. |
84 |
|
|
// For example, the culture "es-CL" means "Spanish, in Chili". |
85 |
|
|
// It represents the Spanish-speaking culture as it is in Chili, |
86 |
|
|
// which might have different formatting rules or even translations |
87 |
|
|
// than Spanish in Spain. A "neutral" culture is one that is not |
88 |
|
|
// specific to a region. For example, the culture "es" is the generic |
89 |
|
|
// Spanish culture, which may be a more generalized version of the language |
90 |
|
|
// that may or may not be what a specific culture expects. |
91 |
|
|
// For a specific culture like "es-CL", the "language" field refers to the |
92 |
|
|
// neutral, generic culture information for the language it is using. |
93 |
|
|
// This is not always a simple matter of the string before the dash. |
94 |
|
|
// For example, the "zh-Hans" culture is netural (Simplified Chinese). |
95 |
|
|
// And the "zh-SG" culture is Simplified Chinese in Singapore, whose lanugage |
96 |
|
|
// field is "zh-CHS", not "zh". |
97 |
|
|
// This field should be used to navigate from a specific culture to it's |
98 |
|
|
// more general, neutral culture. If a culture is already as general as it |
99 |
|
|
// can get, the language may refer to itself. |
100 |
|
|
language: "en", |
101 |
|
|
// numberFormat defines general number formatting rules, like the digits in |
102 |
|
|
// each grouping, the group separator, and how negative numbers are displayed. |
103 |
|
|
numberFormat: { |
104 |
|
|
// [negativePattern] |
105 |
|
|
// Note, numberFormat.pattern has no "positivePattern" unlike percent and currency, |
106 |
|
|
// but is still defined as an array for consistency with them. |
107 |
|
|
// negativePattern: one of "(n)|-n|- n|n-|n -" |
108 |
|
|
pattern: [ "-n" ], |
109 |
|
|
// number of decimal places normally shown |
110 |
|
|
decimals: 2, |
111 |
|
|
// string that separates number groups, as in 1,000,000 |
112 |
|
|
",": ",", |
113 |
|
|
// string that separates a number from the fractional portion, as in 1.99 |
114 |
|
|
".": ".", |
115 |
|
|
// array of numbers indicating the size of each number group. |
116 |
|
|
// TODO: more detailed description and example |
117 |
|
|
groupSizes: [ 3 ], |
118 |
|
|
// symbol used for positive numbers |
119 |
|
|
"+": "+", |
120 |
|
|
// symbol used for negative numbers |
121 |
|
|
"-": "-", |
122 |
|
|
// symbol used for NaN (Not-A-Number) |
123 |
|
|
NaN: "NaN", |
124 |
|
|
// symbol used for Negative Infinity |
125 |
|
|
negativeInfinity: "-Infinity", |
126 |
|
|
// symbol used for Positive Infinity |
127 |
|
|
positiveInfinity: "Infinity", |
128 |
|
|
percent: { |
129 |
|
|
// [negativePattern, positivePattern] |
130 |
|
|
// negativePattern: one of "-n %|-n%|-%n|%-n|%n-|n-%|n%-|-% n|n %-|% n-|% -n|n- %" |
131 |
|
|
// positivePattern: one of "n %|n%|%n|% n" |
132 |
|
|
pattern: [ "-n %", "n %" ], |
133 |
|
|
// number of decimal places normally shown |
134 |
|
|
decimals: 2, |
135 |
|
|
// array of numbers indicating the size of each number group. |
136 |
|
|
// TODO: more detailed description and example |
137 |
|
|
groupSizes: [ 3 ], |
138 |
|
|
// string that separates number groups, as in 1,000,000 |
139 |
|
|
",": ",", |
140 |
|
|
// string that separates a number from the fractional portion, as in 1.99 |
141 |
|
|
".": ".", |
142 |
|
|
// symbol used to represent a percentage |
143 |
|
|
symbol: "%" |
144 |
|
|
}, |
145 |
|
|
currency: { |
146 |
|
|
// [negativePattern, positivePattern] |
147 |
|
|
// negativePattern: one of "($n)|-$n|$-n|$n-|(n$)|-n$|n-$|n$-|-n $|-$ n|n $-|$ n-|$ -n|n- $|($ n)|(n $)" |
148 |
|
|
// positivePattern: one of "$n|n$|$ n|n $" |
149 |
|
|
pattern: [ "($n)", "$n" ], |
150 |
|
|
// number of decimal places normally shown |
151 |
|
|
decimals: 2, |
152 |
|
|
// array of numbers indicating the size of each number group. |
153 |
|
|
// TODO: more detailed description and example |
154 |
|
|
groupSizes: [ 3 ], |
155 |
|
|
// string that separates number groups, as in 1,000,000 |
156 |
|
|
",": ",", |
157 |
|
|
// string that separates a number from the fractional portion, as in 1.99 |
158 |
|
|
".": ".", |
159 |
|
|
// symbol used to represent currency |
160 |
|
|
symbol: "$" |
161 |
|
|
} |
162 |
|
|
}, |
163 |
|
|
// calendars defines all the possible calendars used by this culture. |
164 |
|
|
// There should be at least one defined with name "standard", and is the default |
165 |
|
|
// calendar used by the culture. |
166 |
|
|
// A calendar contains information about how dates are formatted, information about |
167 |
|
|
// the calendar's eras, a standard set of the date formats, |
168 |
|
|
// translations for day and month names, and if the calendar is not based on the Gregorian |
169 |
|
|
// calendar, conversion functions to and from the Gregorian calendar. |
170 |
|
|
calendars: { |
171 |
|
|
standard: { |
172 |
|
|
// name that identifies the type of calendar this is |
173 |
|
|
name: "Gregorian_USEnglish", |
174 |
|
|
// separator of parts of a date (e.g. "/" in 11/05/1955) |
175 |
|
|
"/": "/", |
176 |
|
|
// separator of parts of a time (e.g. ":" in 05:44 PM) |
177 |
|
|
":": ":", |
178 |
|
|
// the first day of the week (0 = Sunday, 1 = Monday, etc) |
179 |
|
|
firstDay: 0, |
180 |
|
|
days: { |
181 |
|
|
// full day names |
182 |
|
|
names: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], |
183 |
|
|
// abbreviated day names |
184 |
|
|
namesAbbr: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], |
185 |
|
|
// shortest day names |
186 |
|
|
namesShort: [ "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" ] |
187 |
|
|
}, |
188 |
|
|
months: { |
189 |
|
|
// full month names (13 months for lunar calendards -- 13th month should be "" if not lunar) |
190 |
|
|
names: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", "" ], |
191 |
|
|
// abbreviated month names |
192 |
|
|
namesAbbr: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "" ] |
193 |
|
|
}, |
194 |
|
|
// AM and PM designators in one of these forms: |
195 |
|
|
// The usual view, and the upper and lower case versions |
196 |
|
|
// [ standard, lowercase, uppercase ] |
197 |
|
|
// The culture does not use AM or PM (likely all standard date formats use 24 hour time) |
198 |
|
|
// null |
199 |
|
|
AM: [ "AM", "am", "AM" ], |
200 |
|
|
PM: [ "PM", "pm", "PM" ], |
201 |
|
|
eras: [ |
202 |
|
|
// eras in reverse chronological order. |
203 |
|
|
// name: the name of the era in this culture (e.g. A.D., C.E.) |
204 |
|
|
// start: when the era starts in ticks (gregorian, gmt), null if it is the earliest supported era. |
205 |
|
|
// offset: offset in years from gregorian calendar |
206 |
|
|
{ |
207 |
|
|
"name": "A.D.", |
208 |
|
|
"start": null, |
209 |
|
|
"offset": 0 |
210 |
|
|
} |
211 |
|
|
], |
212 |
|
|
// when a two digit year is given, it will never be parsed as a four digit |
213 |
|
|
// year greater than this year (in the appropriate era for the culture) |
214 |
|
|
// Set it as a full year (e.g. 2029) or use an offset format starting from |
215 |
|
|
// the current year: "+19" would correspond to 2029 if the current year 2010. |
216 |
|
|
twoDigitYearMax: 2029, |
217 |
|
|
// set of predefined date and time patterns used by the culture |
218 |
|
|
// these represent the format someone in this culture would expect |
219 |
|
|
// to see given the portions of the date that are shown. |
220 |
|
|
patterns: { |
221 |
|
|
// short date pattern |
222 |
|
|
d: "M/d/yyyy", |
223 |
|
|
// long date pattern |
224 |
|
|
D: "dddd, MMMM dd, yyyy", |
225 |
|
|
// short time pattern |
226 |
|
|
t: "h:mm tt", |
227 |
|
|
// long time pattern |
228 |
|
|
T: "h:mm:ss tt", |
229 |
|
|
// long date, short time pattern |
230 |
|
|
f: "dddd, MMMM dd, yyyy h:mm tt", |
231 |
|
|
// long date, long time pattern |
232 |
|
|
F: "dddd, MMMM dd, yyyy h:mm:ss tt", |
233 |
|
|
// month/day pattern |
234 |
|
|
M: "MMMM dd", |
235 |
|
|
// month/year pattern |
236 |
|
|
Y: "yyyy MMMM", |
237 |
|
|
// S is a sortable format that does not vary by culture |
238 |
|
|
S: "yyyy\u0027-\u0027MM\u0027-\u0027dd\u0027T\u0027HH\u0027:\u0027mm\u0027:\u0027ss" |
239 |
|
|
} |
240 |
|
|
// optional fields for each calendar: |
241 |
|
|
/* |
242 |
|
|
monthsGenitive: |
243 |
|
|
Same as months but used when the day preceeds the month. |
244 |
|
|
Omit if the culture has no genitive distinction in month names. |
245 |
|
|
For an explaination of genitive months, see http://blogs.msdn.com/michkap/archive/2004/12/25/332259.aspx |
246 |
|
|
convert: |
247 |
|
|
Allows for the support of non-gregorian based calendars. This convert object is used to |
248 |
|
|
to convert a date to and from a gregorian calendar date to handle parsing and formatting. |
249 |
|
|
The two functions: |
250 |
|
|
fromGregorian( date ) |
251 |
|
|
Given the date as a parameter, return an array with parts [ year, month, day ] |
252 |
|
|
corresponding to the non-gregorian based year, month, and day for the calendar. |
253 |
|
|
toGregorian( year, month, day ) |
254 |
|
|
Given the non-gregorian year, month, and day, return a new Date() object |
255 |
|
|
set to the corresponding date in the gregorian calendar. |
256 |
|
|
*/ |
257 |
|
|
} |
258 |
|
|
}, |
259 |
|
|
// For localized strings |
260 |
|
|
messages: {} |
261 |
|
|
}; |
262 |
|
|
|
263 |
|
|
Globalize.cultures[ "default" ].calendar = Globalize.cultures[ "default" ].calendars.standard; |
264 |
|
|
|
265 |
|
|
Globalize.cultures[ "en" ] = Globalize.cultures[ "default" ]; |
266 |
|
|
|
267 |
|
|
Globalize.cultureSelector = "en"; |
268 |
|
|
|
269 |
|
|
// |
270 |
|
|
// private variables |
271 |
|
|
// |
272 |
|
|
|
273 |
|
|
regexHex = /^0x[a-f0-9]+$/i; |
274 |
|
|
regexInfinity = /^[+-]?infinity$/i; |
275 |
|
|
regexParseFloat = /^[+-]?\d*\.?\d*(e[+-]?\d+)?$/; |
276 |
|
|
regexTrim = /^\s+|\s+$/g; |
277 |
|
|
|
278 |
|
|
// |
279 |
|
|
// private JavaScript utility functions |
280 |
|
|
// |
281 |
|
|
|
282 |
|
|
arrayIndexOf = function( array, item ) { |
283 |
|
|
if ( array.indexOf ) { |
284 |
|
|
return array.indexOf( item ); |
285 |
|
|
} |
286 |
|
|
for ( var i = 0, length = array.length; i < length; i++ ) { |
287 |
|
|
if ( array[i] === item ) { |
288 |
|
|
return i; |
289 |
|
|
} |
290 |
|
|
} |
291 |
|
|
return -1; |
292 |
|
|
}; |
293 |
|
|
|
294 |
|
|
endsWith = function( value, pattern ) { |
295 |
|
|
return value.substr( value.length - pattern.length ) === pattern; |
296 |
|
|
}; |
297 |
|
|
|
298 |
|
|
extend = function( deep ) { |
299 |
|
|
var options, name, src, copy, copyIsArray, clone, |
300 |
|
|
target = arguments[0] || {}, |
301 |
|
|
i = 1, |
302 |
|
|
length = arguments.length, |
303 |
|
|
deep = false; |
304 |
|
|
|
305 |
|
|
// Handle a deep copy situation |
306 |
|
|
if ( typeof target === "boolean" ) { |
307 |
|
|
deep = target; |
308 |
|
|
target = arguments[1] || {}; |
309 |
|
|
// skip the boolean and the target |
310 |
|
|
i = 2; |
311 |
|
|
} |
312 |
|
|
|
313 |
|
|
// Handle case when target is a string or something (possible in deep copy) |
314 |
|
|
if ( typeof target !== "object" && !isFunction(target) ) { |
315 |
|
|
target = {}; |
316 |
|
|
} |
317 |
|
|
|
318 |
|
|
for ( ; i < length; i++ ) { |
319 |
|
|
// Only deal with non-null/undefined values |
320 |
|
|
if ( (options = arguments[ i ]) != null ) { |
321 |
|
|
// Extend the base object |
322 |
|
|
for ( name in options ) { |
323 |
|
|
src = target[ name ]; |
324 |
|
|
copy = options[ name ]; |
325 |
|
|
|
326 |
|
|
// Prevent never-ending loop |
327 |
|
|
if ( target === copy ) { |
328 |
|
|
continue; |
329 |
|
|
} |
330 |
|
|
|
331 |
|
|
// Recurse if we're merging plain objects or arrays |
332 |
|
|
if ( deep && copy && ( isObject(copy) || (copyIsArray = isArray(copy)) ) ) { |
333 |
|
|
if ( copyIsArray ) { |
334 |
|
|
copyIsArray = false; |
335 |
|
|
clone = src && isArray(src) ? src : []; |
336 |
|
|
|
337 |
|
|
} else { |
338 |
|
|
clone = src && isObject(src) ? src : {}; |
339 |
|
|
} |
340 |
|
|
|
341 |
|
|
// Never move original objects, clone them |
342 |
|
|
target[ name ] = extend( deep, clone, copy ); |
343 |
|
|
|
344 |
|
|
// Don't bring in undefined values |
345 |
|
|
} else if ( copy !== undefined ) { |
346 |
|
|
target[ name ] = copy; |
347 |
|
|
} |
348 |
|
|
} |
349 |
|
|
} |
350 |
|
|
} |
351 |
|
|
|
352 |
|
|
// Return the modified object |
353 |
|
|
return target; |
354 |
|
|
}; |
355 |
|
|
|
356 |
|
|
isArray = Array.isArray || function( obj ) { |
357 |
|
|
return Object.prototype.toString.call( obj ) === "[object Array]"; |
358 |
|
|
}; |
359 |
|
|
|
360 |
|
|
isFunction = function( obj ) { |
361 |
|
|
return Object.prototype.toString.call( obj ) === "[object Function]" |
362 |
|
|
} |
363 |
|
|
|
364 |
|
|
isObject = function( obj ) { |
365 |
|
|
return Object.prototype.toString.call( obj ) === "[object Object]"; |
366 |
|
|
}; |
367 |
|
|
|
368 |
|
|
startsWith = function( value, pattern ) { |
369 |
|
|
return value.indexOf( pattern ) === 0; |
370 |
|
|
}; |
371 |
|
|
|
372 |
|
|
trim = function( value ) { |
373 |
|
|
return ( value + "" ).replace( regexTrim, "" ); |
374 |
|
|
}; |
375 |
|
|
|
376 |
|
|
truncate = function( value ) { |
377 |
|
|
return value | 0; |
378 |
|
|
}; |
379 |
|
|
|
380 |
|
|
zeroPad = function( str, count, left ) { |
381 |
|
|
var l; |
382 |
|
|
for ( l = str.length; l < count; l += 1 ) { |
383 |
|
|
str = ( left ? ("0" + str) : (str + "0") ); |
384 |
|
|
} |
385 |
|
|
return str; |
386 |
|
|
}; |
387 |
|
|
|
388 |
|
|
// |
389 |
|
|
// private Globalization utility functions |
390 |
|
|
// |
391 |
|
|
|
392 |
|
|
appendPreOrPostMatch = function( preMatch, strings ) { |
393 |
|
|
// appends pre- and post- token match strings while removing escaped characters. |
394 |
|
|
// Returns a single quote count which is used to determine if the token occurs |
395 |
|
|
// in a string literal. |
396 |
|
|
var quoteCount = 0, |
397 |
|
|
escaped = false; |
398 |
|
|
for ( var i = 0, il = preMatch.length; i < il; i++ ) { |
399 |
|
|
var c = preMatch.charAt( i ); |
400 |
|
|
switch ( c ) { |
401 |
|
|
case "\'": |
402 |
|
|
if ( escaped ) { |
403 |
|
|
strings.push( "\'" ); |
404 |
|
|
} |
405 |
|
|
else { |
406 |
|
|
quoteCount++; |
407 |
|
|
} |
408 |
|
|
escaped = false; |
409 |
|
|
break; |
410 |
|
|
case "\\": |
411 |
|
|
if ( escaped ) { |
412 |
|
|
strings.push( "\\" ); |
413 |
|
|
} |
414 |
|
|
escaped = !escaped; |
415 |
|
|
break; |
416 |
|
|
default: |
417 |
|
|
strings.push( c ); |
418 |
|
|
escaped = false; |
419 |
|
|
break; |
420 |
|
|
} |
421 |
|
|
} |
422 |
|
|
return quoteCount; |
423 |
|
|
}; |
424 |
|
|
|
425 |
|
|
expandFormat = function( cal, format ) { |
426 |
|
|
// expands unspecified or single character date formats into the full pattern. |
427 |
|
|
format = format || "F"; |
428 |
|
|
var pattern, |
429 |
|
|
patterns = cal.patterns, |
430 |
|
|
len = format.length; |
431 |
|
|
if ( len === 1 ) { |
432 |
|
|
pattern = patterns[ format ]; |
433 |
|
|
if ( !pattern ) { |
434 |
|
|
throw "Invalid date format string \'" + format + "\'."; |
435 |
|
|
} |
436 |
|
|
format = pattern; |
437 |
|
|
} |
438 |
|
|
else if ( len === 2 && format.charAt(0) === "%" ) { |
439 |
|
|
// %X escape format -- intended as a custom format string that is only one character, not a built-in format. |
440 |
|
|
format = format.charAt( 1 ); |
441 |
|
|
} |
442 |
|
|
return format; |
443 |
|
|
}; |
444 |
|
|
|
445 |
|
|
formatDate = function( value, format, culture ) { |
446 |
|
|
var cal = culture.calendar, |
447 |
|
|
convert = cal.convert; |
448 |
|
|
|
449 |
|
|
if ( !format || !format.length || format === "i" ) { |
450 |
|
|
var ret; |
451 |
|
|
if ( culture && culture.name.length ) { |
452 |
|
|
if ( convert ) { |
453 |
|
|
// non-gregorian calendar, so we cannot use built-in toLocaleString() |
454 |
|
|
ret = formatDate( value, cal.patterns.F, culture ); |
455 |
|
|
} |
456 |
|
|
else { |
457 |
|
|
var eraDate = new Date( value.getTime() ), |
458 |
|
|
era = getEra( value, cal.eras ); |
459 |
|
|
eraDate.setFullYear( getEraYear(value, cal, era) ); |
460 |
|
|
ret = eraDate.toLocaleString(); |
461 |
|
|
} |
462 |
|
|
} |
463 |
|
|
else { |
464 |
|
|
ret = value.toString(); |
465 |
|
|
} |
466 |
|
|
return ret; |
467 |
|
|
} |
468 |
|
|
|
469 |
|
|
var eras = cal.eras, |
470 |
|
|
sortable = format === "s"; |
471 |
|
|
format = expandFormat( cal, format ); |
472 |
|
|
|
473 |
|
|
// Start with an empty string |
474 |
|
|
ret = []; |
475 |
|
|
var hour, |
476 |
|
|
zeros = [ "0", "00", "000" ], |
477 |
|
|
foundDay, |
478 |
|
|
checkedDay, |
479 |
|
|
dayPartRegExp = /([^d]|^)(d|dd)([^d]|$)/g, |
480 |
|
|
quoteCount = 0, |
481 |
|
|
tokenRegExp = getTokenRegExp(), |
482 |
|
|
converted; |
483 |
|
|
|
484 |
|
|
function padZeros( num, c ) { |
485 |
|
|
var r, s = num + ""; |
486 |
|
|
if ( c > 1 && s.length < c ) { |
487 |
|
|
r = ( zeros[c - 2] + s); |
488 |
|
|
return r.substr( r.length - c, c ); |
489 |
|
|
} |
490 |
|
|
else { |
491 |
|
|
r = s; |
492 |
|
|
} |
493 |
|
|
return r; |
494 |
|
|
} |
495 |
|
|
|
496 |
|
|
function hasDay() { |
497 |
|
|
if ( foundDay || checkedDay ) { |
498 |
|
|
return foundDay; |
499 |
|
|
} |
500 |
|
|
foundDay = dayPartRegExp.test( format ); |
501 |
|
|
checkedDay = true; |
502 |
|
|
return foundDay; |
503 |
|
|
} |
504 |
|
|
|
505 |
|
|
function getPart( date, part ) { |
506 |
|
|
if ( converted ) { |
507 |
|
|
return converted[ part ]; |
508 |
|
|
} |
509 |
|
|
switch ( part ) { |
510 |
|
|
case 0: return date.getFullYear(); |
511 |
|
|
case 1: return date.getMonth(); |
512 |
|
|
case 2: return date.getDate(); |
513 |
|
|
} |
514 |
|
|
} |
515 |
|
|
|
516 |
|
|
if ( !sortable && convert ) { |
517 |
|
|
converted = convert.fromGregorian( value ); |
518 |
|
|
} |
519 |
|
|
|
520 |
|
|
for ( ; ; ) { |
521 |
|
|
// Save the current index |
522 |
|
|
var index = tokenRegExp.lastIndex, |
523 |
|
|
// Look for the next pattern |
524 |
|
|
ar = tokenRegExp.exec( format ); |
525 |
|
|
|
526 |
|
|
// Append the text before the pattern (or the end of the string if not found) |
527 |
|
|
var preMatch = format.slice( index, ar ? ar.index : format.length ); |
528 |
|
|
quoteCount += appendPreOrPostMatch( preMatch, ret ); |
529 |
|
|
|
530 |
|
|
if ( !ar ) { |
531 |
|
|
break; |
532 |
|
|
} |
533 |
|
|
|
534 |
|
|
// do not replace any matches that occur inside a string literal. |
535 |
|
|
if ( quoteCount % 2 ) { |
536 |
|
|
ret.push( ar[0] ); |
537 |
|
|
continue; |
538 |
|
|
} |
539 |
|
|
|
540 |
|
|
var current = ar[ 0 ], |
541 |
|
|
clength = current.length; |
542 |
|
|
|
543 |
|
|
switch ( current ) { |
544 |
|
|
case "ddd": |
545 |
|
|
//Day of the week, as a three-letter abbreviation |
546 |
|
|
case "dddd": |
547 |
|
|
// Day of the week, using the full name |
548 |
|
|
var names = ( clength === 3 ) ? cal.days.namesAbbr : cal.days.names; |
549 |
|
|
ret.push( names[value.getDay()] ); |
550 |
|
|
break; |
551 |
|
|
case "d": |
552 |
|
|
// Day of month, without leading zero for single-digit days |
553 |
|
|
case "dd": |
554 |
|
|
// Day of month, with leading zero for single-digit days |
555 |
|
|
foundDay = true; |
556 |
|
|
ret.push( |
557 |
|
|
padZeros( getPart(value, 2), clength ) |
558 |
|
|
); |
559 |
|
|
break; |
560 |
|
|
case "MMM": |
561 |
|
|
// Month, as a three-letter abbreviation |
562 |
|
|
case "MMMM": |
563 |
|
|
// Month, using the full name |
564 |
|
|
var part = getPart( value, 1 ); |
565 |
|
|
ret.push( |
566 |
|
|
( cal.monthsGenitive && hasDay() ) |
567 |
|
|
? |
568 |
|
|
cal.monthsGenitive[ clength === 3 ? "namesAbbr" : "names" ][ part ] |
569 |
|
|
: |
570 |
|
|
cal.months[ clength === 3 ? "namesAbbr" : "names" ][ part ] |
571 |
|
|
); |
572 |
|
|
break; |
573 |
|
|
case "M": |
574 |
|
|
// Month, as digits, with no leading zero for single-digit months |
575 |
|
|
case "MM": |
576 |
|
|
// Month, as digits, with leading zero for single-digit months |
577 |
|
|
ret.push( |
578 |
|
|
padZeros( getPart(value, 1) + 1, clength ) |
579 |
|
|
); |
580 |
|
|
break; |
581 |
|
|
case "y": |
582 |
|
|
// Year, as two digits, but with no leading zero for years less than 10 |
583 |
|
|
case "yy": |
584 |
|
|
// Year, as two digits, with leading zero for years less than 10 |
585 |
|
|
case "yyyy": |
586 |
|
|
// Year represented by four full digits |
587 |
|
|
part = converted ? converted[ 0 ] : getEraYear( value, cal, getEra(value, eras), sortable ); |
588 |
|
|
if ( clength < 4 ) { |
589 |
|
|
part = part % 100; |
590 |
|
|
} |
591 |
|
|
ret.push( |
592 |
|
|
padZeros( part, clength ) |
593 |
|
|
); |
594 |
|
|
break; |
595 |
|
|
case "h": |
596 |
|
|
// Hours with no leading zero for single-digit hours, using 12-hour clock |
597 |
|
|
case "hh": |
598 |
|
|
// Hours with leading zero for single-digit hours, using 12-hour clock |
599 |
|
|
hour = value.getHours() % 12; |
600 |
|
|
if ( hour === 0 ) hour = 12; |
601 |
|
|
ret.push( |
602 |
|
|
padZeros( hour, clength ) |
603 |
|
|
); |
604 |
|
|
break; |
605 |
|
|
case "H": |
606 |
|
|
// Hours with no leading zero for single-digit hours, using 24-hour clock |
607 |
|
|
case "HH": |
608 |
|
|
// Hours with leading zero for single-digit hours, using 24-hour clock |
609 |
|
|
ret.push( |
610 |
|
|
padZeros( value.getHours(), clength ) |
611 |
|
|
); |
612 |
|
|
break; |
613 |
|
|
case "m": |
614 |
|
|
// Minutes with no leading zero for single-digit minutes |
615 |
|
|
case "mm": |
616 |
|
|
// Minutes with leading zero for single-digit minutes |
617 |
|
|
ret.push( |
618 |
|
|
padZeros( value.getMinutes(), clength ) |
619 |
|
|
); |
620 |
|
|
break; |
621 |
|
|
case "s": |
622 |
|
|
// Seconds with no leading zero for single-digit seconds |
623 |
|
|
case "ss": |
624 |
|
|
// Seconds with leading zero for single-digit seconds |
625 |
|
|
ret.push( |
626 |
|
|
padZeros( value.getSeconds(), clength ) |
627 |
|
|
); |
628 |
|
|
break; |
629 |
|
|
case "t": |
630 |
|
|
// One character am/pm indicator ("a" or "p") |
631 |
|
|
case "tt": |
632 |
|
|
// Multicharacter am/pm indicator |
633 |
|
|
part = value.getHours() < 12 ? ( cal.AM ? cal.AM[0] : " " ) : ( cal.PM ? cal.PM[0] : " " ); |
634 |
|
|
ret.push( clength === 1 ? part.charAt(0) : part ); |
635 |
|
|
break; |
636 |
|
|
case "f": |
637 |
|
|
// Deciseconds |
638 |
|
|
case "ff": |
639 |
|
|
// Centiseconds |
640 |
|
|
case "fff": |
641 |
|
|
// Milliseconds |
642 |
|
|
ret.push( |
643 |
|
|
padZeros( value.getMilliseconds(), 3 ).substr( 0, clength ) |
644 |
|
|
); |
645 |
|
|
break; |
646 |
|
|
case "z": |
647 |
|
|
// Time zone offset, no leading zero |
648 |
|
|
case "zz": |
649 |
|
|
// Time zone offset with leading zero |
650 |
|
|
hour = value.getTimezoneOffset() / 60; |
651 |
|
|
ret.push( |
652 |
|
|
( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), clength ) |
653 |
|
|
); |
654 |
|
|
break; |
655 |
|
|
case "zzz": |
656 |
|
|
// Time zone offset with leading zero |
657 |
|
|
hour = value.getTimezoneOffset() / 60; |
658 |
|
|
ret.push( |
659 |
|
|
( hour <= 0 ? "+" : "-" ) + padZeros( Math.floor(Math.abs(hour)), 2 ) |
660 |
|
|
// Hard coded ":" separator, rather than using cal.TimeSeparator |
661 |
|
|
// Repeated here for consistency, plus ":" was already assumed in date parsing. |
662 |
|
|
+ ":" + padZeros( Math.abs(value.getTimezoneOffset() % 60), 2 ) |
663 |
|
|
); |
664 |
|
|
break; |
665 |
|
|
case "g": |
666 |
|
|
case "gg": |
667 |
|
|
if ( cal.eras ) { |
668 |
|
|
ret.push( |
669 |
|
|
cal.eras[ getEra(value, eras) ].name |
670 |
|
|
); |
671 |
|
|
} |
672 |
|
|
break; |
673 |
|
|
case "/": |
674 |
|
|
ret.push( cal["/"] ); |
675 |
|
|
break; |
676 |
|
|
default: |
677 |
|
|
throw "Invalid date format pattern \'" + current + "\'."; |
678 |
|
|
break; |
679 |
|
|
} |
680 |
|
|
} |
681 |
|
|
return ret.join( "" ); |
682 |
|
|
}; |
683 |
|
|
|
684 |
|
|
// formatNumber |
685 |
|
|
(function() { |
686 |
|
|
var expandNumber; |
687 |
|
|
|
688 |
|
|
expandNumber = function( number, precision, formatInfo ) { |
689 |
|
|
var groupSizes = formatInfo.groupSizes, |
690 |
|
|
curSize = groupSizes[ 0 ], |
691 |
|
|
curGroupIndex = 1, |
692 |
|
|
factor = Math.pow( 10, precision ), |
693 |
|
|
rounded = Math.round( number * factor ) / factor; |
694 |
|
|
|
695 |
|
|
if ( !isFinite(rounded) ) { |
696 |
|
|
rounded = number; |
697 |
|
|
} |
698 |
|
|
number = rounded; |
699 |
|
|
|
700 |
|
|
var numberString = number+"", |
701 |
|
|
right = "", |
702 |
|
|
split = numberString.split( /e/i ), |
703 |
|
|
exponent = split.length > 1 ? parseInt( split[1], 10 ) : 0; |
704 |
|
|
numberString = split[ 0 ]; |
705 |
|
|
split = numberString.split( "." ); |
706 |
|
|
numberString = split[ 0 ]; |
707 |
|
|
right = split.length > 1 ? split[ 1 ] : ""; |
708 |
|
|
|
709 |
|
|
var l; |
710 |
|
|
if ( exponent > 0 ) { |
711 |
|
|
right = zeroPad( right, exponent, false ); |
712 |
|
|
numberString += right.slice( 0, exponent ); |
713 |
|
|
right = right.substr( exponent ); |
714 |
|
|
} |
715 |
|
|
else if ( exponent < 0 ) { |
716 |
|
|
exponent = -exponent; |
717 |
|
|
numberString = zeroPad( numberString, exponent + 1 ); |
718 |
|
|
right = numberString.slice( -exponent, numberString.length ) + right; |
719 |
|
|
numberString = numberString.slice( 0, -exponent ); |
720 |
|
|
} |
721 |
|
|
|
722 |
|
|
if ( precision > 0 ) { |
723 |
|
|
right = formatInfo[ "." ] + |
724 |
|
|
( (right.length > precision) ? right.slice(0, precision) : zeroPad(right, precision) ); |
725 |
|
|
} |
726 |
|
|
else { |
727 |
|
|
right = ""; |
728 |
|
|
} |
729 |
|
|
|
730 |
|
|
var stringIndex = numberString.length - 1, |
731 |
|
|
sep = formatInfo[ "," ], |
732 |
|
|
ret = ""; |
733 |
|
|
|
734 |
|
|
while ( stringIndex >= 0 ) { |
735 |
|
|
if ( curSize === 0 || curSize > stringIndex ) { |
736 |
|
|
return numberString.slice( 0, stringIndex + 1 ) + ( ret.length ? (sep + ret + right) : right ); |
737 |
|
|
} |
738 |
|
|
ret = numberString.slice( stringIndex - curSize + 1, stringIndex + 1 ) + ( ret.length ? (sep + ret) : "" ); |
739 |
|
|
|
740 |
|
|
stringIndex -= curSize; |
741 |
|
|
|
742 |
|
|
if ( curGroupIndex < groupSizes.length ) { |
743 |
|
|
curSize = groupSizes[ curGroupIndex ]; |
744 |
|
|
curGroupIndex++; |
745 |
|
|
} |
746 |
|
|
} |
747 |
|
|
|
748 |
|
|
return numberString.slice( 0, stringIndex + 1 ) + sep + ret + right; |
749 |
|
|
}; |
750 |
|
|
|
751 |
|
|
formatNumber = function( value, format, culture ) { |
752 |
|
|
if ( !isFinite(value) ) { |
753 |
|
|
if ( value === Infinity ) { |
754 |
|
|
return culture.numberFormat.positiveInfinity; |
755 |
|
|
} |
756 |
|
|
if ( value === -Infinity ) { |
757 |
|
|
return culture.numberFormat.negativeInfinity; |
758 |
|
|
} |
759 |
|
|
return culture.numberFormat.NaN; |
760 |
|
|
} |
761 |
|
|
if ( !format || format === "i" ) { |
762 |
|
|
return culture.name.length ? value.toLocaleString() : value.toString(); |
763 |
|
|
} |
764 |
|
|
format = format || "D"; |
765 |
|
|
|
766 |
|
|
var nf = culture.numberFormat, |
767 |
|
|
number = Math.abs( value ), |
768 |
|
|
precision = -1, |
769 |
|
|
pattern; |
770 |
|
|
if ( format.length > 1 ) precision = parseInt( format.slice(1), 10 ); |
771 |
|
|
|
772 |
|
|
var current = format.charAt( 0 ).toUpperCase(), |
773 |
|
|
formatInfo; |
774 |
|
|
|
775 |
|
|
switch ( current ) { |
776 |
|
|
case "D": |
777 |
|
|
pattern = "n"; |
778 |
|
|
number = truncate( number ); |
779 |
|
|
if ( precision !== -1 ) { |
780 |
|
|
number = zeroPad( "" + number, precision, true ); |
781 |
|
|
} |
782 |
|
|
if ( value < 0 ) number = "-" + number; |
783 |
|
|
break; |
784 |
|
|
case "N": |
785 |
|
|
formatInfo = nf; |
786 |
|
|
// fall through |
787 |
|
|
case "C": |
788 |
|
|
formatInfo = formatInfo || nf.currency; |
789 |
|
|
// fall through |
790 |
|
|
case "P": |
791 |
|
|
formatInfo = formatInfo || nf.percent; |
792 |
|
|
pattern = value < 0 ? formatInfo.pattern[ 0 ] : ( formatInfo.pattern[1] || "n" ); |
793 |
|
|
if ( precision === -1 ) precision = formatInfo.decimals; |
794 |
|
|
number = expandNumber( number * (current === "P" ? 100 : 1), precision, formatInfo ); |
795 |
|
|
break; |
796 |
|
|
default: |
797 |
|
|
throw "Bad number format specifier: " + current; |
798 |
|
|
} |
799 |
|
|
|
800 |
|
|
var patternParts = /n|\$|-|%/g, |
801 |
|
|
ret = ""; |
802 |
|
|
for ( ; ; ) { |
803 |
|
|
var index = patternParts.lastIndex, |
804 |
|
|
ar = patternParts.exec( pattern ); |
805 |
|
|
|
806 |
|
|
ret += pattern.slice( index, ar ? ar.index : pattern.length ); |
807 |
|
|
|
808 |
|
|
if ( !ar ) { |
809 |
|
|
break; |
810 |
|
|
} |
811 |
|
|
|
812 |
|
|
switch ( ar[0] ) { |
813 |
|
|
case "n": |
814 |
|
|
ret += number; |
815 |
|
|
break; |
816 |
|
|
case "$": |
817 |
|
|
ret += nf.currency.symbol; |
818 |
|
|
break; |
819 |
|
|
case "-": |
820 |
|
|
// don't make 0 negative |
821 |
|
|
if ( /[1-9]/.test(number) ) { |
822 |
|
|
ret += nf[ "-" ]; |
823 |
|
|
} |
824 |
|
|
break; |
825 |
|
|
case "%": |
826 |
|
|
ret += nf.percent.symbol; |
827 |
|
|
break; |
828 |
|
|
} |
829 |
|
|
} |
830 |
|
|
|
831 |
|
|
return ret; |
832 |
|
|
}; |
833 |
|
|
|
834 |
|
|
}()); |
835 |
|
|
|
836 |
|
|
getTokenRegExp = function() { |
837 |
|
|
// regular expression for matching date and time tokens in format strings. |
838 |
|
|
return /\/|dddd|ddd|dd|d|MMMM|MMM|MM|M|yyyy|yy|y|hh|h|HH|H|mm|m|ss|s|tt|t|fff|ff|f|zzz|zz|z|gg|g/g; |
839 |
|
|
}; |
840 |
|
|
|
841 |
|
|
getEra = function( date, eras ) { |
842 |
|
|
if ( !eras ) return 0; |
843 |
|
|
var start, ticks = date.getTime(); |
844 |
|
|
for ( var i = 0, l = eras.length; i < l; i++ ) { |
845 |
|
|
start = eras[ i ].start; |
846 |
|
|
if ( start === null || ticks >= start ) { |
847 |
|
|
return i; |
848 |
|
|
} |
849 |
|
|
} |
850 |
|
|
return 0; |
851 |
|
|
}; |
852 |
|
|
|
853 |
|
|
getEraYear = function( date, cal, era, sortable ) { |
854 |
|
|
var year = date.getFullYear(); |
855 |
|
|
if ( !sortable && cal.eras ) { |
856 |
|
|
// convert normal gregorian year to era-shifted gregorian |
857 |
|
|
// year by subtracting the era offset |
858 |
|
|
year -= cal.eras[ era ].offset; |
859 |
|
|
} |
860 |
|
|
return year; |
861 |
|
|
}; |
862 |
|
|
|
863 |
|
|
// parseExact |
864 |
|
|
(function() { |
865 |
|
|
var expandYear, |
866 |
|
|
getDayIndex, |
867 |
|
|
getMonthIndex, |
868 |
|
|
getParseRegExp, |
869 |
|
|
outOfRange, |
870 |
|
|
toUpper, |
871 |
|
|
toUpperArray; |
872 |
|
|
|
873 |
|
|
expandYear = function( cal, year ) { |
874 |
|
|
// expands 2-digit year into 4 digits. |
875 |
|
|
var now = new Date(), |
876 |
|
|
era = getEra( now ); |
877 |
|
|
if ( year < 100 ) { |
878 |
|
|
var twoDigitYearMax = cal.twoDigitYearMax; |
879 |
|
|
twoDigitYearMax = typeof twoDigitYearMax === "string" ? new Date().getFullYear() % 100 + parseInt( twoDigitYearMax, 10 ) : twoDigitYearMax; |
880 |
|
|
var curr = getEraYear( now, cal, era ); |
881 |
|
|
year += curr - ( curr % 100 ); |
882 |
|
|
if ( year > twoDigitYearMax ) { |
883 |
|
|
year -= 100; |
884 |
|
|
} |
885 |
|
|
} |
886 |
|
|
return year; |
887 |
|
|
}; |
888 |
|
|
|
889 |
|
|
getDayIndex = function ( cal, value, abbr ) { |
890 |
|
|
var ret, |
891 |
|
|
days = cal.days, |
892 |
|
|
upperDays = cal._upperDays; |
893 |
|
|
if ( !upperDays ) { |
894 |
|
|
cal._upperDays = upperDays = [ |
895 |
|
|
toUpperArray( days.names ), |
896 |
|
|
toUpperArray( days.namesAbbr ), |
897 |
|
|
toUpperArray( days.namesShort ) |
898 |
|
|
]; |
899 |
|
|
} |
900 |
|
|
value = toUpper( value ); |
901 |
|
|
if ( abbr ) { |
902 |
|
|
ret = arrayIndexOf( upperDays[1], value ); |
903 |
|
|
if ( ret === -1 ) { |
904 |
|
|
ret = arrayIndexOf( upperDays[2], value ); |
905 |
|
|
} |
906 |
|
|
} |
907 |
|
|
else { |
908 |
|
|
ret = arrayIndexOf( upperDays[0], value ); |
909 |
|
|
} |
910 |
|
|
return ret; |
911 |
|
|
}; |
912 |
|
|
|
913 |
|
|
getMonthIndex = function( cal, value, abbr ) { |
914 |
|
|
var months = cal.months, |
915 |
|
|
monthsGen = cal.monthsGenitive || cal.months, |
916 |
|
|
upperMonths = cal._upperMonths, |
917 |
|
|
upperMonthsGen = cal._upperMonthsGen; |
918 |
|
|
if ( !upperMonths ) { |
919 |
|
|
cal._upperMonths = upperMonths = [ |
920 |
|
|
toUpperArray( months.names ), |
921 |
|
|
toUpperArray( months.namesAbbr ) |
922 |
|
|
]; |
923 |
|
|
cal._upperMonthsGen = upperMonthsGen = [ |
924 |
|
|
toUpperArray( monthsGen.names ), |
925 |
|
|
toUpperArray( monthsGen.namesAbbr ) |
926 |
|
|
]; |
927 |
|
|
} |
928 |
|
|
value = toUpper( value ); |
929 |
|
|
var i = arrayIndexOf( abbr ? upperMonths[1] : upperMonths[0], value ); |
930 |
|
|
if ( i < 0 ) { |
931 |
|
|
i = arrayIndexOf( abbr ? upperMonthsGen[1] : upperMonthsGen[0], value ); |
932 |
|
|
} |
933 |
|
|
return i; |
934 |
|
|
}; |
935 |
|
|
|
936 |
|
|
getParseRegExp = function( cal, format ) { |
937 |
|
|
// converts a format string into a regular expression with groups that |
938 |
|
|
// can be used to extract date fields from a date string. |
939 |
|
|
// check for a cached parse regex. |
940 |
|
|
var re = cal._parseRegExp; |
941 |
|
|
if ( !re ) { |
942 |
|
|
cal._parseRegExp = re = {}; |
943 |
|
|
} |
944 |
|
|
else { |
945 |
|
|
var reFormat = re[ format ]; |
946 |
|
|
if ( reFormat ) { |
947 |
|
|
return reFormat; |
948 |
|
|
} |
949 |
|
|
} |
950 |
|
|
|
951 |
|
|
// expand single digit formats, then escape regular expression characters. |
952 |
|
|
var expFormat = expandFormat( cal, format ).replace( /([\^\$\.\*\+\?\|\[\]\(\)\{\}])/g, "\\\\$1" ), |
953 |
|
|
regexp = [ "^" ], |
954 |
|
|
groups = [], |
955 |
|
|
index = 0, |
956 |
|
|
quoteCount = 0, |
957 |
|
|
tokenRegExp = getTokenRegExp(), |
958 |
|
|
match; |
959 |
|
|
|
960 |
|
|
// iterate through each date token found. |
961 |
|
|
while ( (match = tokenRegExp.exec(expFormat)) !== null ) { |
962 |
|
|
var preMatch = expFormat.slice( index, match.index ); |
963 |
|
|
index = tokenRegExp.lastIndex; |
964 |
|
|
|
965 |
|
|
// don't replace any matches that occur inside a string literal. |
966 |
|
|
quoteCount += appendPreOrPostMatch( preMatch, regexp ); |
967 |
|
|
if ( quoteCount % 2 ) { |
968 |
|
|
regexp.push( match[0] ); |
969 |
|
|
continue; |
970 |
|
|
} |
971 |
|
|
|
972 |
|
|
// add a regex group for the token. |
973 |
|
|
var m = match[ 0 ], |
974 |
|
|
len = m.length, |
975 |
|
|
add; |
976 |
|
|
switch ( m ) { |
977 |
|
|
case "dddd": case "ddd": |
978 |
|
|
case "MMMM": case "MMM": |
979 |
|
|
case "gg": case "g": |
980 |
|
|
add = "(\\D+)"; |
981 |
|
|
break; |
982 |
|
|
case "tt": case "t": |
983 |
|
|
add = "(\\D*)"; |
984 |
|
|
break; |
985 |
|
|
case "yyyy": |
986 |
|
|
case "fff": |
987 |
|
|
case "ff": |
988 |
|
|
case "f": |
989 |
|
|
add = "(\\d{" + len + "})"; |
990 |
|
|
break; |
991 |
|
|
case "dd": case "d": |
992 |
|
|
case "MM": case "M": |
993 |
|
|
case "yy": case "y": |
994 |
|
|
case "HH": case "H": |
995 |
|
|
case "hh": case "h": |
996 |
|
|
case "mm": case "m": |
997 |
|
|
case "ss": case "s": |
998 |
|
|
add = "(\\d\\d?)"; |
999 |
|
|
break; |
1000 |
|
|
case "zzz": |
1001 |
|
|
add = "([+-]?\\d\\d?:\\d{2})"; |
1002 |
|
|
break; |
1003 |
|
|
case "zz": case "z": |
1004 |
|
|
add = "([+-]?\\d\\d?)"; |
1005 |
|
|
break; |
1006 |
|
|
case "/": |
1007 |
|
|
add = "(\\" + cal[ "/" ] + ")"; |
1008 |
|
|
break; |
1009 |
|
|
default: |
1010 |
|
|
throw "Invalid date format pattern \'" + m + "\'."; |
1011 |
|
|
break; |
1012 |
|
|
} |
1013 |
|
|
if ( add ) { |
1014 |
|
|
regexp.push( add ); |
1015 |
|
|
} |
1016 |
|
|
groups.push( match[0] ); |
1017 |
|
|
} |
1018 |
|
|
appendPreOrPostMatch( expFormat.slice(index), regexp ); |
1019 |
|
|
regexp.push( "$" ); |
1020 |
|
|
|
1021 |
|
|
// allow whitespace to differ when matching formats. |
1022 |
|
|
var regexpStr = regexp.join( "" ).replace( /\s+/g, "\\s+" ), |
1023 |
|
|
parseRegExp = { "regExp": regexpStr, "groups": groups }; |
1024 |
|
|
|
1025 |
|
|
// cache the regex for this format. |
1026 |
|
|
return re[ format ] = parseRegExp; |
1027 |
|
|
}; |
1028 |
|
|
|
1029 |
|
|
outOfRange = function( value, low, high ) { |
1030 |
|
|
return value < low || value > high; |
1031 |
|
|
}; |
1032 |
|
|
|
1033 |
|
|
toUpper = function( value ) { |
1034 |
|
|
// "he-IL" has non-breaking space in weekday names. |
1035 |
|
|
return value.split( "\u00A0" ).join( " " ).toUpperCase(); |
1036 |
|
|
}; |
1037 |
|
|
|
1038 |
|
|
toUpperArray = function( arr ) { |
1039 |
|
|
var results = []; |
1040 |
|
|
for ( var i = 0, l = arr.length; i < l; i++ ) { |
1041 |
|
|
results[ i ] = toUpper( arr[i] ); |
1042 |
|
|
} |
1043 |
|
|
return results; |
1044 |
|
|
}; |
1045 |
|
|
|
1046 |
|
|
parseExact = function( value, format, culture ) { |
1047 |
|
|
// try to parse the date string by matching against the format string |
1048 |
|
|
// while using the specified culture for date field names. |
1049 |
|
|
value = trim( value ); |
1050 |
|
|
var cal = culture.calendar, |
1051 |
|
|
// convert date formats into regular expressions with groupings. |
1052 |
|
|
// use the regexp to determine the input format and extract the date fields. |
1053 |
|
|
parseInfo = getParseRegExp( cal, format ), |
1054 |
|
|
match = new RegExp( parseInfo.regExp ).exec( value ); |
1055 |
|
|
if ( match === null ) { |
1056 |
|
|
return null; |
1057 |
|
|
} |
1058 |
|
|
// found a date format that matches the input. |
1059 |
|
|
var groups = parseInfo.groups, |
1060 |
|
|
era = null, year = null, month = null, date = null, weekDay = null, |
1061 |
|
|
hour = 0, hourOffset, min = 0, sec = 0, msec = 0, tzMinOffset = null, |
1062 |
|
|
pmHour = false; |
1063 |
|
|
// iterate the format groups to extract and set the date fields. |
1064 |
|
|
for ( var j = 0, jl = groups.length; j < jl; j++ ) { |
1065 |
|
|
var matchGroup = match[ j + 1 ]; |
1066 |
|
|
if ( matchGroup ) { |
1067 |
|
|
var current = groups[ j ], |
1068 |
|
|
clength = current.length, |
1069 |
|
|
matchInt = parseInt( matchGroup, 10 ); |
1070 |
|
|
switch ( current ) { |
1071 |
|
|
case "dd": case "d": |
1072 |
|
|
// Day of month. |
1073 |
|
|
date = matchInt; |
1074 |
|
|
// check that date is generally in valid range, also checking overflow below. |
1075 |
|
|
if ( outOfRange(date, 1, 31) ) return null; |
1076 |
|
|
break; |
1077 |
|
|
case "MMM": case "MMMM": |
1078 |
|
|
month = getMonthIndex( cal, matchGroup, clength === 3 ); |
1079 |
|
|
if ( outOfRange(month, 0, 11) ) return null; |
1080 |
|
|
break; |
1081 |
|
|
case "M": case "MM": |
1082 |
|
|
// Month. |
1083 |
|
|
month = matchInt - 1; |
1084 |
|
|
if ( outOfRange(month, 0, 11) ) return null; |
1085 |
|
|
break; |
1086 |
|
|
case "y": case "yy": |
1087 |
|
|
case "yyyy": |
1088 |
|
|
year = clength < 4 ? expandYear( cal, matchInt ) : matchInt; |
1089 |
|
|
if ( outOfRange(year, 0, 9999) ) return null; |
1090 |
|
|
break; |
1091 |
|
|
case "h": case "hh": |
1092 |
|
|
// Hours (12-hour clock). |
1093 |
|
|
hour = matchInt; |
1094 |
|
|
if ( hour === 12 ) hour = 0; |
1095 |
|
|
if ( outOfRange(hour, 0, 11) ) return null; |
1096 |
|
|
break; |
1097 |
|
|
case "H": case "HH": |
1098 |
|
|
// Hours (24-hour clock). |
1099 |
|
|
hour = matchInt; |
1100 |
|
|
if ( outOfRange(hour, 0, 23) ) return null; |
1101 |
|
|
break; |
1102 |
|
|
case "m": case "mm": |
1103 |
|
|
// Minutes. |
1104 |
|
|
min = matchInt; |
1105 |
|
|
if ( outOfRange(min, 0, 59) ) return null; |
1106 |
|
|
break; |
1107 |
|
|
case "s": case "ss": |
1108 |
|
|
// Seconds. |
1109 |
|
|
sec = matchInt; |
1110 |
|
|
if ( outOfRange(sec, 0, 59) ) return null; |
1111 |
|
|
break; |
1112 |
|
|
case "tt": case "t": |
1113 |
|
|
// AM/PM designator. |
1114 |
|
|
// see if it is standard, upper, or lower case PM. If not, ensure it is at least one of |
1115 |
|
|
// the AM tokens. If not, fail the parse for this format. |
1116 |
|
|
pmHour = cal.PM && ( matchGroup === cal.PM[0] || matchGroup === cal.PM[1] || matchGroup === cal.PM[2] ); |
1117 |
|
|
if ( |
1118 |
|
|
!pmHour && ( |
1119 |
|
|
!cal.AM || ( matchGroup !== cal.AM[0] && matchGroup !== cal.AM[1] && matchGroup !== cal.AM[2] ) |
1120 |
|
|
) |
1121 |
|
|
) return null; |
1122 |
|
|
break; |
1123 |
|
|
case "f": |
1124 |
|
|
// Deciseconds. |
1125 |
|
|
case "ff": |
1126 |
|
|
// Centiseconds. |
1127 |
|
|
case "fff": |
1128 |
|
|
// Milliseconds. |
1129 |
|
|
msec = matchInt * Math.pow( 10, 3 - clength ); |
1130 |
|
|
if ( outOfRange(msec, 0, 999) ) return null; |
1131 |
|
|
break; |
1132 |
|
|
case "ddd": |
1133 |
|
|
// Day of week. |
1134 |
|
|
case "dddd": |
1135 |
|
|
// Day of week. |
1136 |
|
|
weekDay = getDayIndex( cal, matchGroup, clength === 3 ); |
1137 |
|
|
if ( outOfRange(weekDay, 0, 6) ) return null; |
1138 |
|
|
break; |
1139 |
|
|
case "zzz": |
1140 |
|
|
// Time zone offset in +/- hours:min. |
1141 |
|
|
var offsets = matchGroup.split( /:/ ); |
1142 |
|
|
if ( offsets.length !== 2 ) return null; |
1143 |
|
|
hourOffset = parseInt( offsets[0], 10 ); |
1144 |
|
|
if ( outOfRange(hourOffset, -12, 13) ) return null; |
1145 |
|
|
var minOffset = parseInt( offsets[1], 10 ); |
1146 |
|
|
if ( outOfRange(minOffset, 0, 59) ) return null; |
1147 |
|
|
tzMinOffset = ( hourOffset * 60 ) + ( startsWith(matchGroup, "-") ? -minOffset : minOffset ); |
1148 |
|
|
break; |
1149 |
|
|
case "z": case "zz": |
1150 |
|
|
// Time zone offset in +/- hours. |
1151 |
|
|
hourOffset = matchInt; |
1152 |
|
|
if ( outOfRange(hourOffset, -12, 13) ) return null; |
1153 |
|
|
tzMinOffset = hourOffset * 60; |
1154 |
|
|
break; |
1155 |
|
|
case "g": case "gg": |
1156 |
|
|
var eraName = matchGroup; |
1157 |
|
|
if ( !eraName || !cal.eras ) return null; |
1158 |
|
|
eraName = trim( eraName.toLowerCase() ); |
1159 |
|
|
for ( var i = 0, l = cal.eras.length; i < l; i++ ) { |
1160 |
|
|
if ( eraName === cal.eras[i].name.toLowerCase() ) { |
1161 |
|
|
era = i; |
1162 |
|
|
break; |
1163 |
|
|
} |
1164 |
|
|
} |
1165 |
|
|
// could not find an era with that name |
1166 |
|
|
if ( era === null ) return null; |
1167 |
|
|
break; |
1168 |
|
|
} |
1169 |
|
|
} |
1170 |
|
|
} |
1171 |
|
|
var result = new Date(), defaultYear, convert = cal.convert; |
1172 |
|
|
defaultYear = convert ? convert.fromGregorian( result )[ 0 ] : result.getFullYear(); |
1173 |
|
|
if ( year === null ) { |
1174 |
|
|
year = defaultYear; |
1175 |
|
|
} |
1176 |
|
|
else if ( cal.eras ) { |
1177 |
|
|
// year must be shifted to normal gregorian year |
1178 |
|
|
// but not if year was not specified, its already normal gregorian |
1179 |
|
|
// per the main if clause above. |
1180 |
|
|
year += cal.eras[( era || 0 )].offset; |
1181 |
|
|
} |
1182 |
|
|
// set default day and month to 1 and January, so if unspecified, these are the defaults |
1183 |
|
|
// instead of the current day/month. |
1184 |
|
|
if ( month === null ) { |
1185 |
|
|
month = 0; |
1186 |
|
|
} |
1187 |
|
|
if ( date === null ) { |
1188 |
|
|
date = 1; |
1189 |
|
|
} |
1190 |
|
|
// now have year, month, and date, but in the culture's calendar. |
1191 |
|
|
// convert to gregorian if necessary |
1192 |
|
|
if ( convert ) { |
1193 |
|
|
result = convert.toGregorian( year, month, date ); |
1194 |
|
|
// conversion failed, must be an invalid match |
1195 |
|
|
if ( result === null ) return null; |
1196 |
|
|
} |
1197 |
|
|
else { |
1198 |
|
|
// have to set year, month and date together to avoid overflow based on current date. |
1199 |
|
|
result.setFullYear( year, month, date ); |
1200 |
|
|
// check to see if date overflowed for specified month (only checked 1-31 above). |
1201 |
|
|
if ( result.getDate() !== date ) return null; |
1202 |
|
|
// invalid day of week. |
1203 |
|
|
if ( weekDay !== null && result.getDay() !== weekDay ) { |
1204 |
|
|
return null; |
1205 |
|
|
} |
1206 |
|
|
} |
1207 |
|
|
// if pm designator token was found make sure the hours fit the 24-hour clock. |
1208 |
|
|
if ( pmHour && hour < 12 ) { |
1209 |
|
|
hour += 12; |
1210 |
|
|
} |
1211 |
|
|
result.setHours( hour, min, sec, msec ); |
1212 |
|
|
if ( tzMinOffset !== null ) { |
1213 |
|
|
// adjust timezone to utc before applying local offset. |
1214 |
|
|
var adjustedMin = result.getMinutes() - ( tzMinOffset + result.getTimezoneOffset() ); |
1215 |
|
|
// Safari limits hours and minutes to the range of -127 to 127. We need to use setHours |
1216 |
|
|
// to ensure both these fields will not exceed this range. adjustedMin will range |
1217 |
|
|
// somewhere between -1440 and 1500, so we only need to split this into hours. |
1218 |
|
|
result.setHours( result.getHours() + parseInt(adjustedMin / 60, 10), adjustedMin % 60 ); |
1219 |
|
|
} |
1220 |
|
|
return result; |
1221 |
|
|
}; |
1222 |
|
|
}()); |
1223 |
|
|
|
1224 |
|
|
parseNegativePattern = function( value, nf, negativePattern ) { |
1225 |
|
|
var neg = nf[ "-" ], |
1226 |
|
|
pos = nf[ "+" ], |
1227 |
|
|
ret; |
1228 |
|
|
switch ( negativePattern ) { |
1229 |
|
|
case "n -": |
1230 |
|
|
neg = " " + neg; |
1231 |
|
|
pos = " " + pos; |
1232 |
|
|
// fall through |
1233 |
|
|
case "n-": |
1234 |
|
|
if ( endsWith(value, neg) ) { |
1235 |
|
|
ret = [ "-", value.substr(0, value.length - neg.length) ]; |
1236 |
|
|
} |
1237 |
|
|
else if ( endsWith(value, pos) ) { |
1238 |
|
|
ret = [ "+", value.substr(0, value.length - pos.length) ]; |
1239 |
|
|
} |
1240 |
|
|
break; |
1241 |
|
|
case "- n": |
1242 |
|
|
neg += " "; |
1243 |
|
|
pos += " "; |
1244 |
|
|
// fall through |
1245 |
|
|
case "-n": |
1246 |
|
|
if ( startsWith(value, neg) ) { |
1247 |
|
|
ret = [ "-", value.substr(neg.length) ]; |
1248 |
|
|
} |
1249 |
|
|
else if ( startsWith(value, pos) ) { |
1250 |
|
|
ret = [ "+", value.substr(pos.length) ]; |
1251 |
|
|
} |
1252 |
|
|
break; |
1253 |
|
|
case "(n)": |
1254 |
|
|
if ( startsWith(value, "(") && endsWith(value, ")") ) { |
1255 |
|
|
ret = [ "-", value.substr(1, value.length - 2) ]; |
1256 |
|
|
} |
1257 |
|
|
break; |
1258 |
|
|
} |
1259 |
|
|
return ret || [ "", value ]; |
1260 |
|
|
}; |
1261 |
|
|
|
1262 |
|
|
// |
1263 |
|
|
// public instance functions |
1264 |
|
|
// |
1265 |
|
|
|
1266 |
|
|
Globalize.prototype.findClosestCulture = function( cultureSelector ) { |
1267 |
|
|
return Globalize.findClosestCulture.call( this, cultureSelector ); |
1268 |
|
|
}; |
1269 |
|
|
|
1270 |
|
|
Globalize.prototype.format = function( value, format, cultureSelector ) { |
1271 |
|
|
return Globalize.format.call( this, value, format, cultureSelector ); |
1272 |
|
|
}; |
1273 |
|
|
|
1274 |
|
|
Globalize.prototype.localize = function( key, cultureSelector ) { |
1275 |
|
|
return Globalize.localize.call( this, key, cultureSelector ); |
1276 |
|
|
}; |
1277 |
|
|
|
1278 |
|
|
Globalize.prototype.parseInt = function( value, radix, cultureSelector ) { |
1279 |
|
|
return Globalize.parseInt.call( this, value, radix, cultureSelector ); |
1280 |
|
|
}; |
1281 |
|
|
|
1282 |
|
|
Globalize.prototype.parseFloat = function( value, radix, cultureSelector ) { |
1283 |
|
|
return Globalize.parseFloat.call( this, value, radix, cultureSelector ); |
1284 |
|
|
}; |
1285 |
|
|
|
1286 |
|
|
Globalize.prototype.culture = function( cultureSelector ) { |
1287 |
|
|
return Globalize.culture.call( this, cultureSelector ); |
1288 |
|
|
}; |
1289 |
|
|
|
1290 |
|
|
// |
1291 |
|
|
// public singleton functions |
1292 |
|
|
// |
1293 |
|
|
|
1294 |
|
|
Globalize.addCultureInfo = function( cultureName, baseCultureName, info ) { |
1295 |
|
|
|
1296 |
|
|
var base = {}, |
1297 |
|
|
isNew = false; |
1298 |
|
|
|
1299 |
|
|
if ( typeof cultureName !== "string" ) { |
1300 |
|
|
// cultureName argument is optional string. If not specified, assume info is first |
1301 |
|
|
// and only argument. Specified info deep-extends current culture. |
1302 |
|
|
info = cultureName; |
1303 |
|
|
cultureName = this.culture().name; |
1304 |
|
|
base = this.cultures[ cultureName ]; |
1305 |
|
|
} else if ( typeof baseCultureName !== "string" ) { |
1306 |
|
|
// baseCultureName argument is optional string. If not specified, assume info is second |
1307 |
|
|
// argument. Specified info deep-extends specified culture. |
1308 |
|
|
// If specified culture does not exist, create by deep-extending default |
1309 |
|
|
info = baseCultureName; |
1310 |
|
|
isNew = ( this.cultures[ cultureName ] == null ); |
1311 |
|
|
base = this.cultures[ cultureName ] || this.cultures[ "default" ]; |
1312 |
|
|
} else { |
1313 |
|
|
// cultureName and baseCultureName specified. Assume a new culture is being created |
1314 |
|
|
// by deep-extending an specified base culture |
1315 |
|
|
isNew = true; |
1316 |
|
|
base = this.cultures[ baseCultureName ]; |
1317 |
|
|
} |
1318 |
|
|
|
1319 |
|
|
this.cultures[ cultureName ] = extend(true, {}, |
1320 |
|
|
base, |
1321 |
|
|
info |
1322 |
|
|
); |
1323 |
|
|
// Make the standard calendar the current culture if it's a new culture |
1324 |
|
|
if ( isNew ) { |
1325 |
|
|
this.cultures[ cultureName ].calendar = this.cultures[ cultureName ].calendars.standard; |
1326 |
|
|
} |
1327 |
|
|
}; |
1328 |
|
|
|
1329 |
|
|
Globalize.findClosestCulture = function( name ) { |
1330 |
|
|
var match; |
1331 |
|
|
if ( !name ) { |
1332 |
|
|
return this.cultures[ this.cultureSelector ] || this.cultures[ "default" ]; |
1333 |
|
|
} |
1334 |
|
|
if ( typeof name === "string" ) { |
1335 |
|
|
name = name.split( "," ); |
1336 |
|
|
} |
1337 |
|
|
if ( isArray(name) ) { |
1338 |
|
|
var lang, |
1339 |
|
|
cultures = this.cultures, |
1340 |
|
|
list = name, |
1341 |
|
|
i, l = list.length, |
1342 |
|
|
prioritized = []; |
1343 |
|
|
for ( i = 0; i < l; i++ ) { |
1344 |
|
|
name = trim( list[i] ); |
1345 |
|
|
var pri, parts = name.split( ";" ); |
1346 |
|
|
lang = trim( parts[0] ); |
1347 |
|
|
if ( parts.length === 1 ) { |
1348 |
|
|
pri = 1; |
1349 |
|
|
} |
1350 |
|
|
else { |
1351 |
|
|
name = trim( parts[1] ); |
1352 |
|
|
if ( name.indexOf("q=") === 0 ) { |
1353 |
|
|
name = name.substr( 2 ); |
1354 |
|
|
pri = parseFloat( name ); |
1355 |
|
|
pri = isNaN( pri ) ? 0 : pri; |
1356 |
|
|
} |
1357 |
|
|
else { |
1358 |
|
|
pri = 1; |
1359 |
|
|
} |
1360 |
|
|
} |
1361 |
|
|
prioritized.push({ lang: lang, pri: pri }); |
1362 |
|
|
} |
1363 |
|
|
prioritized.sort(function( a, b ) { |
1364 |
|
|
return a.pri < b.pri ? 1 : -1; |
1365 |
|
|
}); |
1366 |
|
|
|
1367 |
|
|
// exact match |
1368 |
|
|
for ( i = 0; i < l; i++ ) { |
1369 |
|
|
lang = prioritized[ i ].lang; |
1370 |
|
|
match = cultures[ lang ]; |
1371 |
|
|
if ( match ) { |
1372 |
|
|
return match; |
1373 |
|
|
} |
1374 |
|
|
} |
1375 |
|
|
|
1376 |
|
|
// neutral language match |
1377 |
|
|
for ( i = 0; i < l; i++ ) { |
1378 |
|
|
lang = prioritized[ i ].lang; |
1379 |
|
|
do { |
1380 |
|
|
var index = lang.lastIndexOf( "-" ); |
1381 |
|
|
if ( index === -1 ) { |
1382 |
|
|
break; |
1383 |
|
|
} |
1384 |
|
|
// strip off the last part. e.g. en-US => en |
1385 |
|
|
lang = lang.substr( 0, index ); |
1386 |
|
|
match = cultures[ lang ]; |
1387 |
|
|
if ( match ) { |
1388 |
|
|
return match; |
1389 |
|
|
} |
1390 |
|
|
} |
1391 |
|
|
while ( 1 ); |
1392 |
|
|
} |
1393 |
|
|
|
1394 |
|
|
// last resort: match first culture using that language |
1395 |
|
|
for ( i = 0; i < l; i++ ) { |
1396 |
|
|
lang = prioritized[ i ].lang; |
1397 |
|
|
for ( var cultureKey in cultures ) { |
1398 |
|
|
var culture = cultures[ cultureKey ]; |
1399 |
|
|
if ( culture.language == lang ) { |
1400 |
|
|
return culture; |
1401 |
|
|
} |
1402 |
|
|
} |
1403 |
|
|
} |
1404 |
|
|
} |
1405 |
|
|
else if ( typeof name === "object" ) { |
1406 |
|
|
return name; |
1407 |
|
|
} |
1408 |
|
|
return match || null; |
1409 |
|
|
}; |
1410 |
|
|
|
1411 |
|
|
Globalize.format = function( value, format, cultureSelector ) { |
1412 |
|
|
culture = this.findClosestCulture( cultureSelector ); |
1413 |
|
|
if ( value instanceof Date ) { |
1414 |
|
|
value = formatDate( value, format, culture ); |
1415 |
|
|
} |
1416 |
|
|
else if ( typeof value === "number" ) { |
1417 |
|
|
value = formatNumber( value, format, culture ); |
1418 |
|
|
} |
1419 |
|
|
return value; |
1420 |
|
|
}; |
1421 |
|
|
|
1422 |
|
|
Globalize.localize = function( key, cultureSelector ) { |
1423 |
|
|
return this.findClosestCulture( cultureSelector ).messages[ key ] || |
1424 |
|
|
this.cultures[ "default" ].messages[ key ]; |
1425 |
|
|
}; |
1426 |
|
|
|
1427 |
|
|
Globalize.parseDate = function( value, formats, culture ) { |
1428 |
|
|
culture = this.findClosestCulture( culture ); |
1429 |
|
|
|
1430 |
|
|
var date, prop, patterns; |
1431 |
|
|
if ( formats ) { |
1432 |
|
|
if ( typeof formats === "string" ) { |
1433 |
|
|
formats = [ formats ]; |
1434 |
|
|
} |
1435 |
|
|
if ( formats.length ) { |
1436 |
|
|
for ( var i = 0, l = formats.length; i < l; i++ ) { |
1437 |
|
|
var format = formats[ i ]; |
1438 |
|
|
if ( format ) { |
1439 |
|
|
date = parseExact( value, format, culture ); |
1440 |
|
|
if ( date ) { |
1441 |
|
|
break; |
1442 |
|
|
} |
1443 |
|
|
} |
1444 |
|
|
} |
1445 |
|
|
} |
1446 |
|
|
} else { |
1447 |
|
|
patterns = culture.calendar.patterns; |
1448 |
|
|
for ( prop in patterns ) { |
1449 |
|
|
date = parseExact( value, patterns[prop], culture ); |
1450 |
|
|
if ( date ) { |
1451 |
|
|
break; |
1452 |
|
|
} |
1453 |
|
|
} |
1454 |
|
|
} |
1455 |
|
|
|
1456 |
|
|
return date || null; |
1457 |
|
|
}; |
1458 |
|
|
|
1459 |
|
|
Globalize.parseInt = function( value, radix, cultureSelector ) { |
1460 |
|
|
return truncate( Globalize.parseFloat(value, radix, cultureSelector) ); |
1461 |
|
|
}; |
1462 |
|
|
|
1463 |
|
|
Globalize.parseFloat = function( value, radix, cultureSelector ) { |
1464 |
|
|
// radix argument is optional |
1465 |
|
|
if ( typeof radix !== "number" ) { |
1466 |
|
|
cultureSelector = radix; |
1467 |
|
|
radix = 10; |
1468 |
|
|
} |
1469 |
|
|
|
1470 |
|
|
var culture = this.findClosestCulture( cultureSelector ); |
1471 |
|
|
var ret = NaN, |
1472 |
|
|
nf = culture.numberFormat; |
1473 |
|
|
|
1474 |
|
|
if ( value.indexOf(culture.numberFormat.currency.symbol) > -1 ) { |
1475 |
|
|
// remove currency symbol |
1476 |
|
|
value = value.replace( culture.numberFormat.currency.symbol, "" ); |
1477 |
|
|
// replace decimal seperator |
1478 |
|
|
value = value.replace( culture.numberFormat.currency["."], culture.numberFormat["."] ); |
1479 |
|
|
} |
1480 |
|
|
|
1481 |
|
|
// trim leading and trailing whitespace |
1482 |
|
|
value = trim( value ); |
1483 |
|
|
|
1484 |
|
|
// allow infinity or hexidecimal |
1485 |
|
|
if ( regexInfinity.test(value) ) { |
1486 |
|
|
ret = parseFloat( value ); |
1487 |
|
|
} |
1488 |
|
|
else if ( !radix && regexHex.test(value) ) { |
1489 |
|
|
ret = parseInt( value, 16 ); |
1490 |
|
|
} |
1491 |
|
|
else { |
1492 |
|
|
|
1493 |
|
|
// determine sign and number |
1494 |
|
|
var signInfo = parseNegativePattern( value, nf, nf.pattern[0] ), |
1495 |
|
|
sign = signInfo[ 0 ], |
1496 |
|
|
num = signInfo[ 1 ]; |
1497 |
|
|
|
1498 |
|
|
// #44 - try parsing as "(n)" |
1499 |
|
|
if ( sign === "" && nf.pattern[0] !== "(n)" ) { |
1500 |
|
|
signInfo = parseNegativePattern( value, nf, "(n)" ); |
1501 |
|
|
sign = signInfo[ 0 ]; |
1502 |
|
|
num = signInfo[ 1 ]; |
1503 |
|
|
} |
1504 |
|
|
|
1505 |
|
|
// try parsing as "-n" |
1506 |
|
|
if ( sign === "" && nf.pattern[0] !== "-n" ) { |
1507 |
|
|
signInfo = parseNegativePattern( value, nf, "-n" ); |
1508 |
|
|
sign = signInfo[ 0 ]; |
1509 |
|
|
num = signInfo[ 1 ]; |
1510 |
|
|
} |
1511 |
|
|
|
1512 |
|
|
sign = sign || "+"; |
1513 |
|
|
|
1514 |
|
|
// determine exponent and number |
1515 |
|
|
var exponent, |
1516 |
|
|
intAndFraction, |
1517 |
|
|
exponentPos = num.indexOf( "e" ); |
1518 |
|
|
if ( exponentPos < 0 ) exponentPos = num.indexOf( "E" ); |
1519 |
|
|
if ( exponentPos < 0 ) { |
1520 |
|
|
intAndFraction = num; |
1521 |
|
|
exponent = null; |
1522 |
|
|
} |
1523 |
|
|
else { |
1524 |
|
|
intAndFraction = num.substr( 0, exponentPos ); |
1525 |
|
|
exponent = num.substr( exponentPos + 1 ); |
1526 |
|
|
} |
1527 |
|
|
// determine decimal position |
1528 |
|
|
var integer, |
1529 |
|
|
fraction, |
1530 |
|
|
decSep = nf[ "." ], |
1531 |
|
|
decimalPos = intAndFraction.indexOf( decSep ); |
1532 |
|
|
if ( decimalPos < 0 ) { |
1533 |
|
|
integer = intAndFraction; |
1534 |
|
|
fraction = null; |
1535 |
|
|
} |
1536 |
|
|
else { |
1537 |
|
|
integer = intAndFraction.substr( 0, decimalPos ); |
1538 |
|
|
fraction = intAndFraction.substr( decimalPos + decSep.length ); |
1539 |
|
|
} |
1540 |
|
|
// handle groups (e.g. 1,000,000) |
1541 |
|
|
var groupSep = nf[ "," ]; |
1542 |
|
|
integer = integer.split( groupSep ).join( "" ); |
1543 |
|
|
var altGroupSep = groupSep.replace( /\u00A0/g, " " ); |
1544 |
|
|
if ( groupSep !== altGroupSep ) { |
1545 |
|
|
integer = integer.split( altGroupSep ).join( "" ); |
1546 |
|
|
} |
1547 |
|
|
// build a natively parsable number string |
1548 |
|
|
var p = sign + integer; |
1549 |
|
|
if ( fraction !== null ) { |
1550 |
|
|
p += "." + fraction; |
1551 |
|
|
} |
1552 |
|
|
if ( exponent !== null ) { |
1553 |
|
|
// exponent itself may have a number patternd |
1554 |
|
|
var expSignInfo = parseNegativePattern( exponent, nf, "-n" ); |
1555 |
|
|
p += "e" + ( expSignInfo[0] || "+" ) + expSignInfo[ 1 ]; |
1556 |
|
|
} |
1557 |
|
|
if ( regexParseFloat.test(p) ) { |
1558 |
|
|
ret = parseFloat( p ); |
1559 |
|
|
} |
1560 |
|
|
} |
1561 |
|
|
return ret; |
1562 |
|
|
}; |
1563 |
|
|
|
1564 |
|
|
Globalize.culture = function( cultureSelector ) { |
1565 |
|
|
// setter |
1566 |
|
|
if ( typeof cultureSelector !== "undefined" ) { |
1567 |
|
|
this.cultureSelector = cultureSelector; |
1568 |
|
|
} |
1569 |
|
|
// getter |
1570 |
|
|
return this.findClosestCulture( cultureSelector ) || this.culture[ "default" ]; |
1571 |
|
|
}; |
1572 |
|
|
|
1573 |
|
|
}( this )); |