1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 // NOTE: The API for the classes in this file are inspired by the Java text
 26 //		 formatting classes but the implementation was NOT copied or ported
 27 //		 from the Java sources.
 28 
 29 //
 30 // Format class
 31 //
 32 
 33 /** 
 34  * Base class for all formats. To format an object, instantiate the
 35  * format of your choice and call the <code>format</code> method which
 36  * returns the formatted string.
 37  * 
 38  * @private
 39  */
 40 AjxFormat = function(pattern) {
 41     if (arguments.length == 0) { return; }
 42 	this._pattern = pattern;
 43 	this._segments = [];
 44 }
 45 
 46 /**
 47  * Returns a string representation of this object.
 48  * 
 49  * @return	{string}	a string representation of this object
 50  */
 51 AjxFormat.prototype.toString = function() { 
 52 	var s = [];
 53 	s.push("pattern=\"",this._pattern,'"');
 54 	if (this._segments.length > 0) {
 55 		s.push(", segments={ ");
 56 		for (var i = 0; i < this._segments.length; i++) {
 57 			if (i > 0) { s.push(", "); }
 58 			s.push(String(this._segments[i]));
 59 		}
 60 		s.push(" }");
 61 	}
 62 	return s.join("");
 63 };
 64 
 65 // Static functions
 66 
 67 AjxFormat.initialize = function() {
 68 	AjxDateFormat.initialize();
 69 	AjxNumberFormat.initialize();
 70 };
 71 
 72 // Public methods
 73 
 74 /** 
 75  * This method does <em>not</em> need to be overridden unless
 76  * the subclass doesn't use format segments and takes complete 
 77  * responsibility for formatting.
 78  */
 79 AjxFormat.prototype.format = function(object) { 
 80 	var s = [];
 81 	for (var i = 0; i < this._segments.length; i++) {
 82 		s.push(this._segments[i].format(object));
 83 	}
 84 	return s.join("");
 85 };
 86 
 87 /** 
 88  * Parses the given string according to this format's pattern and returns
 89  * an object.
 90  * <p>
 91  * <strong>Note:</strong>
 92  * The default implementation of this method assumes that the sub-class
 93  * has implemented the <code>_createParseObject</code> method.
 94  */
 95 AjxFormat.prototype.parse = function(s) {
 96 	var object = this._createParseObject();
 97 	var index = 0;
 98 	for (var i = 0; i < this._segments.length; i++) {
 99 		var segment = this._segments[i];
100 		index = segment.parse(object, s, index);
101 	}
102 	// REVISIT: Should this return null instead?
103 	if (index < s.length) {
104 		throw new AjxFormat.ParsingException(this, null, "input too long"); // I18n
105 	}
106 	return object;
107 };
108 
109 /** 
110  * Returns an array of segments that comprise this format. 
111  * <p>
112  * <strong>Note:</strong>
113  * This method is specific to this implementation and does not follow
114  * the format classes found in the <code>java.text</code> package.
115  */
116 AjxFormat.prototype.getSegments = function() {
117 	return this._segments;
118 };
119 
120 /** Returns a string pattern for this format. */
121 AjxFormat.prototype.toPattern = function() {
122 	return this._pattern;
123 };
124 
125 /** Returns a copy of this format. */
126 AjxFormat.prototype.clone = function() {
127 	return new this.constructor(this._pattern);
128 };
129 
130 // Protected methods
131 
132 /**
133  * Creates the object that is initialized by parsing
134  * <p>
135  * <strong>Note:</strong>
136  * This must be implemented by sub-classes.
137  */
138 AjxFormat.prototype._createParseObject = function(s) {
139 	throw new AjxFormat.ParsingException(this, null, "not implemented"); // I18n
140 };
141 
142 // Protected static methods
143 
144 AjxFormat._zeroPad = function(s, length, zeroChar, rightSide) {
145 	s = typeof s == "string" ? s : String(s);
146 
147 	if (s.length >= length) return s;
148 
149 	zeroChar = zeroChar || '0';
150 	
151 	var a = [];
152 	for (var i = s.length; i < length; i++) {
153 		a.push(zeroChar);
154 	}
155 	a[rightSide ? "unshift" : "push"](s);
156 
157 	return a.join("");
158 };
159 
160 //
161 // Format exception base class
162 //
163 
164 AjxFormat.FormatException = function(format, message) {
165 	this._format = format;
166 	this._message = message;
167 };
168 AjxFormat.FormatException.prototype.toString = function() { 
169 	return this._message; 
170 };
171 
172 
173 //
174 // Formatting exception class
175 //
176 
177 AjxFormat.FormattingException = function(format, segment, message) {
178 	AjxFormat.FormatException.call(this, format, message);
179 	this._segment = segment;
180 };
181 AjxFormat.FormattingException.prototype = new AjxFormat.FormatException;
182 AjxFormat.FormattingException.prototype.constructor = AjxFormat.FormattingException;
183 
184 
185 //
186 // Parsing exception class
187 //
188 
189 AjxFormat.ParsingException = function(format, segment, message) {
190 	AjxFormat.FormatException.call(this, format, message);
191 	this._segment = segment;
192 };
193 AjxFormat.ParsingException.prototype = new AjxFormat.FormatException;
194 AjxFormat.ParsingException.prototype.constructor = AjxFormat.ParsingException;
195 
196 //
197 // Segment class
198 //
199 
200 AjxFormat.Segment = function(format, s) {
201     if (arguments.length == 0) return;
202 	this._parent = format;
203 	this._s = s;
204 };
205 
206 AjxFormat.Segment.prototype.toString = function() { 
207 	return "segment: \""+this._s+'"'; 
208 };
209 
210 // Public methods
211 
212 AjxFormat.Segment.prototype.format = function(o) { 
213 	return this._s; 
214 };
215 
216 /**
217  * Parses the string at the given index, initializes the parse object
218  * (as appropriate), and returns the new index within the string for
219  * the next parsing step.
220  * <p>
221  * <strong>Note:</strong>
222  * This method must be implemented by sub-classes.
223  *
224  * @param o     [object] The parse object to be initialized.
225  * @param s     [string] The input string to be parsed.
226  * @param index [number] The index within the string to start parsing.
227  */
228 AjxFormat.Segment.prototype.parse = function(o, s, i) {
229 	throw new AjxFormat.ParsingException(this._parent, this, "not implemented"); // I18n
230 };
231 
232 AjxFormat.Segment.prototype.getFormat = function() {
233 	return this._parent;
234 };
235 AjxFormat.Segment.prototype.toSubPattern = function() {
236 	return this._s;
237 };
238 
239 // Protected methods
240 
241 AjxFormat.Segment.prototype._getFixedLength = function() {
242 	var fixedlen;
243 	if (this._index + 1 < this._parent._segments.length) {
244 		var nextSegment = this._parent._segments[this._index + 1];
245 		if (!(nextSegment instanceof AjxFormat.TextSegment)) {
246 			fixedlen = this._s.length;
247 		}
248 	}
249 	return fixedlen;
250 };
251 
252 // Protected static methods
253 
254 AjxFormat.Segment._parseLiteral = function(literal, s, index) {
255 	if (s.length - index < literal.length) {
256 		throw new AjxFormat.ParsingException(this._parent, this, "input too short"); // I18n
257 	}
258 	for (var i = 0; i < literal.length; i++) {
259 		if (literal.charAt(i) != s.charAt(index + i)) {
260 			throw new AjxFormat.ParsingException(this._parent, this, "input doesn't match"); // I18n
261 		}
262 	}
263 	return index + literal.length;
264 };
265 
266 AjxFormat.Segment._parseLiterals = function(o, f, adjust, literals, s, index) {
267 	for (var i = 0; i < literals.length; i++) {
268 		try {
269 			var literal = literals[i];
270 			var nindex = AjxFormat.Segment._parseLiteral(literal, s, index);
271 			if (f) {
272 				var target = o || window;
273 				if (typeof f == "function") {
274 					f.call(target, i + adjust);
275 				}
276 				else {
277 					target[f] = i + adjust;
278 				}
279 			}
280 			return nindex;
281 		}
282 		catch (e) {
283 			// ignore. keep trying to find a match
284 		}
285 	}
286 	return -1;
287 };
288 
289 /**
290  * Parses an integer at the offset of the given string and calls a
291  * method on the specified object.
292  *
293  * @param o         [object]   The target object.
294  * @param f         [function|string] The method to call on the target object.
295  *                             If this parameter is a string, then it is used
296  *                             as the name of the property to set on the
297  *                             target object.
298  * @param adjust    [number]   The numeric adjustment to make on the
299  *                             value before calling the object method.
300  * @param s         [string]   The string to parse.
301  * @param index     [number]   The index within the string to start parsing.
302  * @param fixedlen  [number]   If specified, specifies the required number
303  *                             of digits to be parsed.
304  * @param radix     [number]   Optional. Specifies the radix of the parse
305  *                             string. Defaults to 10 if not specified.
306  */
307 AjxFormat.Segment._parseInt = function(o, f, adjust, s, index, fixedlen, radix) {
308 	var len = fixedlen || s.length - index;
309 	var head = index;
310 	for (var i = 0; i < len; i++) {
311 		if (!s.charAt(index++).match(/\d/)) {
312 			index--;
313 			break;
314 		}
315 	}
316 	var tail = index;
317 	if (head == tail) {
318 		throw new AjxFormat.ParsingException(this._parent, this, "number not present"); // I18n
319 	}
320 	if (fixedlen && tail - head != fixedlen) {
321 		throw new AjxFormat.ParsingException(this._parent, this, "number too short"); // I18n
322 	}
323 	var value = parseInt(s.substring(head, tail), radix || 10);
324 	if (f) {
325 		var target = o || window;
326 		if (typeof f == "function") {
327 			f.call(target, value + adjust);
328 		}
329 		else {
330 			target[f] = value + adjust;
331 		}
332 	}
333 	return tail;
334 };
335 
336 //
337 // Date format class
338 //
339 
340 /**
341  * The AjxDateFormat class formats Date objects according to a specified 
342  * pattern. The patterns are defined the same as the SimpleDateFormat
343  * class in the Java libraries. <strong>Note:</strong> <em>Only the
344  * Gregorian Calendar is supported at this time.</em> Supporting other
345  * calendars would require a lot more information downloaded to the
346  * client. Limiting dates to the Gregorian calendar is a trade-off.
347  * <p>
348  * <strong>Note:</strong>
349  * The date format differs from the Java patterns a few ways: the pattern
350  * "EEEEE" (5 'E's) denotes a <em>short</em> weekday and the pattern "MMMMM"
351  * (5 'M's) denotes a <em>short</em> month name. This matches the extended 
352  * pattern found in the Common Locale Data Repository (CLDR) found at: 
353  * http://www.unicode.org/cldr/.
354  *
355  * @param {string} pattern The date format pattern.
356  *
357  * @class
358  * @constructor
359  */
360 AjxDateFormat = function(pattern) {
361     if (arguments.length == 0) { return; }
362 	AjxFormat.call(this, pattern);
363 	if (pattern == null) { return; }
364 	if (typeof pattern == "number") {
365 		switch (pattern) {
366 			case AjxDateFormat.SHORT: pattern = I18nMsg.formatDateShort; break;
367 			case AjxDateFormat.MEDIUM: pattern = I18nMsg.formatDateMedium; break;
368 			case AjxDateFormat.LONG: pattern = I18nMsg.formatDateLong; break;
369 			case AjxDateFormat.FULL: pattern = I18nMsg.formatDateFull; break;
370             case AjxDateFormat.NUMBER: pattern = I18nMsg.formatDateNumber; break;
371 		}
372 	}	
373 	for (var i = 0; i < pattern.length; i++) {
374 		// literal
375 		var c = pattern.charAt(i);
376 		if (c == "'") {
377 			var head = i + 1;
378 			for (i++ ; i < pattern.length; i++) {
379 				var c = pattern.charAt(i);
380 				if (c == "'") {
381 					if (i + 1 < pattern.length && pattern.charAt(i + 1) == "'") {
382 						pattern = pattern.substr(0, i) + pattern.substr(i + 1);
383 					}
384 					else {
385 						break;
386 					}
387 				}
388 			}
389 //			if (i == pattern.length) {
390 				// NOTE: try to avoid silent errors
391 //				throw new FormatException(this, "unterminated string literal"); // I18n
392 //			}
393 			var tail = i;
394 			var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail));
395 			this._segments.push(segment);
396 			continue;
397 		}
398 
399 		// non-meta chars
400 		var head = i;
401 		while(i < pattern.length) {
402 			c = pattern.charAt(i);
403 			if (AjxDateFormat._META_CHARS.indexOf(c) != -1 || c == "'") {
404 				break;
405 			}
406 			i++;
407 		}
408 		var tail = i;
409 		if (head != tail) {
410 			var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail));
411 			this._segments.push(segment);
412 			i--;
413 			continue;
414 		}
415 		
416 		// meta char
417 		var head = i;
418 		while(++i < pattern.length) {
419 			if (pattern.charAt(i) != c) {
420 				break;
421 			}		
422 		}
423 		var tail = i--;
424 		var count = tail - head;
425 		var field = pattern.substr(head, count);
426 		var segment = null;
427 		switch (c) {
428 			case 'G': segment = new AjxDateFormat.EraSegment(this, field); break;
429 			case 'y': segment = new AjxDateFormat.YearSegment(this, field); break;
430 			case 'M': segment = new AjxDateFormat.MonthSegment(this, field); break;
431 			case 'w': segment = new AjxDateFormat.WeekSegment(this, field); break;
432 			case 'W': segment = new AjxDateFormat.WeekSegment(this, field); break;
433 			case 'D': segment = new AjxDateFormat.DaySegment(this, field); break;
434 			case 'd': segment = new AjxDateFormat.DaySegment(this, field); break;
435 			case 'F': segment = new AjxDateFormat.WeekdaySegment(this, field); break;
436 			case 'E': segment = new AjxDateFormat.WeekdaySegment(this, field); break;
437 			case 'a': segment = new AjxDateFormat.AmPmSegment(this, field); break;
438 			case 'H': segment = new AjxDateFormat.HourSegment(this, field); break;
439 			case 'k': segment = new AjxDateFormat.HourSegment(this, field); break;
440 			case 'K': segment = new AjxDateFormat.HourSegment(this, field); break;
441 			case 'h': segment = new AjxDateFormat.HourSegment(this, field); break;
442 			case 'm': segment = new AjxDateFormat.MinuteSegment(this, field); break;
443 			case 's': segment = new AjxDateFormat.SecondSegment(this, field); break;
444 			case 'S': segment = new AjxDateFormat.SecondSegment(this, field); break;
445 			case 'z': segment = new AjxDateFormat.TimezoneSegment(this, field); break;
446 			case 'Z': segment = new AjxDateFormat.TimezoneSegment(this, field); break;
447 		}
448 		if (segment != null) {
449 			segment._index = this._segments.length;
450 			this._segments.push(segment);
451 		}
452 	}
453 }
454 AjxDateFormat.prototype = new AjxFormat;
455 AjxDateFormat.prototype.constructor = AjxDateFormat;
456 
457 AjxDateFormat.prototype.toString = function() {
458 	return "[AjxDateFormat: "+AjxFormat.prototype.toString.call(this)+"]";
459 };
460 
461 // Constants
462 
463 /** Short date/time format style. */
464 AjxDateFormat.SHORT = 0;
465 /** Medium date/time format style. */
466 AjxDateFormat.MEDIUM = 1;
467 /** Long date/time format style. */
468 AjxDateFormat.LONG = 2;
469 /** Full date/time format style. */
470 AjxDateFormat.FULL = 3;
471 /** Default date/time format style. */
472 AjxDateFormat.DEFAULT = AjxDateFormat.MEDIUM;
473 
474 AjxDateFormat._META_CHARS = "GyMwWDdFEaHkKhmsSzZ";
475 
476 /** Number date . */
477 AjxDateFormat.NUMBER = 4;
478 // Static methods
479 
480 /**
481  * Get a date formatter.
482  *
483  * @param style The format style.
484  * @return {AjxDateFormat} The date formatter.
485  *
486  * @see {AjxDateFormat.SHORT}
487  * @see {AjxDateFormat.MEDIUM}
488  * @see {AjxDateFormat.LONG}
489  * @see {AjxDateFormat.FULL}
490  */
491 AjxDateFormat.getDateInstance = function(style) {
492 	// lazily create formatters
493 	style = style != null ? style : AjxDateFormat.DEFAULT;
494 	if (!AjxDateFormat._DATE_FORMATTERS[style]) {
495 		AjxDateFormat._DATE_FORMATTERS[style] = new AjxDateFormat(AjxDateFormat._dateFormats[style]);
496 	}
497 	return AjxDateFormat._DATE_FORMATTERS[style];
498 };
499 
500 /**
501  * Get a time formatter.
502  *
503  * @param style The format style.
504  * @return {AjxDateFormat} The time formatter.
505  *
506  * @see {AjxDateFormat.SHORT}
507  * @see {AjxDateFormat.MEDIUM}
508  * @see {AjxDateFormat.LONG}
509  * @see {AjxDateFormat.FULL}
510  */
511 AjxDateFormat.getTimeInstance = function(style) {
512 	// lazily create formatters
513 	style = style != null ? style : AjxDateFormat.DEFAULT;
514 	if (!AjxDateFormat._TIME_FORMATTERS[style]) {
515 		AjxDateFormat._TIME_FORMATTERS[style] = new AjxDateFormat(AjxDateFormat._timeFormats[style]);
516 	}
517 	return AjxDateFormat._TIME_FORMATTERS[style];
518 };
519 
520 /**
521  * Get a date and time formatter.
522  *
523  * @param dateStyle The format style for the date.
524  * @param timeStyle The format style for the time.
525  * @return {AjxDateFormat} The date and time formatter. 
526  *
527  * @see {AjxDateFormat.SHORT}
528  * @see {AjxDateFormat.MEDIUM}
529  * @see {AjxDateFormat.LONG}
530  * @see {AjxDateFormat.FULL}
531  */
532 AjxDateFormat.getDateTimeInstance = function(dateStyle, timeStyle) {
533 	// lazily create formatters
534 	dateStyle = dateStyle != null ? dateStyle : AjxDateFormat.DEFAULT;
535 	timeStyle = timeStyle != null ? timeStyle : AjxDateFormat.DEFAULT;
536 	var style = dateStyle * 10 + timeStyle;
537 	if (!AjxDateFormat._DATETIME_FORMATTERS[style]) {
538 		var pattern = I18nMsg.formatDateTime;
539 		var params = [ AjxDateFormat._dateFormats[dateStyle], AjxDateFormat._timeFormats[timeStyle] ];
540 		
541 		var dateTimePattern = AjxMessageFormat.format(pattern, params);
542 		AjxDateFormat._DATETIME_FORMATTERS[style] = new AjxDateFormat(dateTimePattern);
543 	}
544 	return AjxDateFormat._DATETIME_FORMATTERS[style];
545 };
546 
547 /**
548  * Format a date. Equivalent to <code>new AjxDateFormat(pattern).format(date)</code>.
549  *
550  * @param {string} pattern  The format.
551  * @param {Date}   date     The date to format.
552  * @return {string} The formatted string.
553  */
554 AjxDateFormat.format = function(pattern, date) {
555 	return new AjxDateFormat(pattern).format(date);
556 };
557 
558 /**
559  * Parse a date. Equivalent to <code>new AjxDateFormat(pattern).parse(dateStr)</code>.
560  *
561  * @param {string} pattern  The format.
562  * @param {string} dateStr  The input string to parse.
563  * @return {Date} The parsed date object.
564  */
565 AjxDateFormat.parse = function(pattern, dateStr) {
566 	return new AjxDateFormat(pattern).parse(dateStr);
567 };
568 
569 AjxDateFormat.initialize = function() {
570 	// format
571 	AjxDateFormat._dateFormats = [
572 		I18nMsg.formatDateShort, I18nMsg.formatDateMedium,
573 		I18nMsg.formatDateLong, I18nMsg.formatDateFull,I18nMsg.formatDateNumber
574 	];
575 	AjxDateFormat._timeFormats = [
576 		I18nMsg.formatTimeShort, I18nMsg.formatTimeMedium,
577 		I18nMsg.formatTimeLong, I18nMsg.formatTimeFull
578 	];
579 	AjxDateFormat._timeParsers = [];
580 	for (var i=1, fmt; fmt = I18nMsg["parseTime"+i]; i++) {
581 		AjxDateFormat._timeParsers.push(new RegExp(fmt,"i"));
582 	}
583 	
584 
585 	AjxDateFormat._DATE_FORMATTERS = {};
586 	AjxDateFormat._TIME_FORMATTERS = {};
587 	AjxDateFormat._DATETIME_FORMATTERS = {};
588 
589 	// segments
590 	AjxDateFormat.MonthSegment.initialize();
591 	AjxDateFormat.WeekdaySegment.initialize();
592 };
593 
594 // Public methods
595 
596 /**
597  * Parses the given time using one of the AjxDateFormat._timeParsers, which are regexes defined in the properties
598  */
599 AjxDateFormat.parseTime = function(timeStr) {
600 	for (var i=0; i<AjxDateFormat._timeParsers.length; i++) {
601 		var result = AjxDateFormat._timeParsers[i].exec(timeStr);
602 		if (result) {
603 			var hours = parseInt(result[1],10) || 0;
604 			var minutes = parseInt(result[2],10) || 0;
605 			var ampm = result[3];
606 			if (ampm) {
607 				var pmChars = I18nMsg.parseTimePMChars;
608 				if (hours == 12) {
609 					hours = 0;
610 				}
611 				if (pmChars.indexOf(ampm)!=-1) {
612 					hours += 12;
613 				}
614 			}
615 			if (hours < 24 && minutes < 60) {
616 				date = new Date();
617 				date.setHours(hours);
618 				date.setMinutes(minutes);
619 				date.setSeconds(0);
620 				return date;
621 			}
622 		}
623 	}
624 };
625 
626 /** 
627  * Parses the given string and returns a date. If the string cannot be
628  * parsed as a date, <code>null</code> is returned.
629  *
630  * @param {string} s The string to parse.
631  * @return {Date} The parsed date object.
632  */
633 AjxDateFormat.prototype.parse = function(s) {
634 	var object = null;
635 	try {
636 		object = AjxFormat.prototype.parse.call(this, s);
637 		
638 		// set the date components in proper order
639 
640 		// Use day 2 for the initial date - even if the setFullYear call screws up (see Bugzilla@Mozilla,
641 		// Bug 1079720), it won't get the wrong year.
642 		var date = new Date(0, 0, 2, 0, 0, 0, 0);
643 		if (object.year != null) { date.setFullYear(object.year); }
644 		if (object.month != null) { date.setMonth(object.month); }
645 		// To insure the same behavior (if object does not set the day), init the day to 1.
646 		date.setDate(1);
647 		if (object.dayofmonth != null) { date.setDate(object.dayofmonth); }
648 		else if (object.dayofyear != null) { date.setMonth(0, object.dayofyear); }
649 		if (object.hours != null) { date.setHours(object.hours); }
650 		if (object.minutes != null) { date.setMinutes(object.minutes); }
651 		if (object.seconds != null) { date.setSeconds(object.seconds); }
652 		if (object.milliseconds != null) { date.setMilliseconds(object.milliseconds); }
653 		if (object.ampm != null) {
654 			var hours = date.getHours();
655 			if (hours == 12 && object.ampm == 0) {
656 				hours = 0;
657 			}
658 			else if (hours != 12 && object.ampm == 1) {
659 				hours += 12;
660 			}
661 			date.setHours(hours);
662 		}
663         if (object.timezone != null) { date.setMinutes(date.getMinutes() - object.timezone); }
664 		// TODO: era
665 
666 		if (isNaN(date.getTime())) {
667 			return null; //in some cases (see bug 51266) the date is invalid without throwing an exception. return null in this case 
668 		}
669 
670 		object = date;
671 	}
672 	catch (e) {
673         DBG.println(AjxDebug.DBG3, e);
674 		// do nothing
675 	}
676 	return object;
677 };
678 
679 // Protected methods
680 
681 AjxDateFormat.prototype._createParseObject = function() {
682 	// NOTE: An object to hold the parsed parts is returned instead of
683 	//       a Date object because setting the day is dependent on which
684 	//       year is set. For example, if the user parsed "2/29/2008",
685 	//       which is a leap year, with the pattern "M/d/yyyy", then the
686 	//       year must be set before the month and date or else the Date
687 	//       object will roll it over to Mar 1.
688 	return { 
689 		year: null, month: null, dayofmonth: null, dayofyear: null,
690 		hours: null, minutes: null, seconds: null, milliseconds: null,
691 		ampm: null, era: null, timezone: null
692 	};
693 };
694 
695 //
696 // Text segment class
697 //
698 
699 AjxFormat.TextSegment = function(format, s) {
700     if (arguments.length == 0) return;
701 	AjxFormat.Segment.call(this, format, s);
702 };
703 AjxFormat.TextSegment.prototype = new AjxFormat.Segment;
704 AjxFormat.TextSegment.prototype.constructor = AjxFormat.TextSegment;
705 
706 AjxFormat.TextSegment.prototype.toString = function() { 
707 	return "text: \""+this._s+'"'; 
708 };
709 
710 // Public methods
711 
712 AjxFormat.TextSegment.prototype.parse = function(o, s, index) {
713 	return AjxFormat.Segment._parseLiteral(this._s, s, index);
714 };
715 
716 //
717 // Date segment class
718 //
719 
720 AjxDateFormat.DateSegment = function(format, s) {
721     if (arguments.length == 0) return;
722 	AjxFormat.Segment.call(this, format, s);
723 }
724 AjxDateFormat.DateSegment.prototype = new AjxFormat.Segment;
725 AjxDateFormat.DateSegment.prototype.constructor = AjxDateFormat.DateSegment;
726 
727 //
728 // Date era segment class
729 //
730 
731 AjxDateFormat.EraSegment = function(format, s) {
732     if (arguments.length == 0) return;
733 	AjxDateFormat.DateSegment.call(this, format, s);
734 };
735 AjxDateFormat.EraSegment.prototype = new AjxDateFormat.DateSegment;
736 AjxDateFormat.EraSegment.prototype.constructor = AjxDateFormat.EraSegment;
737 
738 AjxDateFormat.EraSegment.prototype.toString = function() { 
739 	return "dateEra: \""+this._s+'"'; 
740 };
741 
742 // Public methods
743 
744 AjxDateFormat.EraSegment.prototype.format = function(date) { 
745 	// NOTE: Only support current era at the moment...
746 	return I18nMsg.eraAD;
747 };
748 
749 //
750 // Date year segment class
751 //
752 
753 AjxDateFormat.YearSegment = function(format, s) {
754     if (arguments.length == 0) return;
755 	AjxDateFormat.DateSegment.call(this, format, s);
756 };
757 AjxDateFormat.YearSegment.prototype = new AjxDateFormat.DateSegment;
758 AjxDateFormat.YearSegment.prototype.constructor = AjxDateFormat.YearSegment;
759 
760 AjxDateFormat.YearSegment.prototype.toString = function() { 
761 	return "dateYear: \""+this._s+'"'; 
762 };
763 
764 // Public methods
765 
766 AjxDateFormat.YearSegment.prototype.format = function(date) { 
767 	var year = String(date.getFullYear());
768 	return this._s.length < 4 ? year.substr(year.length - 2) : AjxFormat._zeroPad(year, this._s.length);
769 };
770 
771 AjxDateFormat.YearSegment.prototype.parse = function(object, s, index) {
772 	var fixedlen = this._getFixedLength();
773 	var nindex = AjxFormat.Segment._parseInt(object, "year", 0, s, index, fixedlen);
774 	// adjust 2-digit years
775 	if (nindex - index == 2) {
776 		if (!AjxDateFormat._2digitStartYear) {
777 			AjxDateFormat._2digitStartYear = parseInt(AjxMsg.dateParsing2DigitStartYear);
778 		}
779 		var syear = AjxDateFormat._2digitStartYear;
780 		var pyear = parseInt(s.substr(index,2), 10);
781 		var century = (Math.floor(syear / 100) + (pyear < (syear % 100) ? 1 : 0)) * 100;
782 		var year = century + pyear;
783 		object.year = year;
784 	}
785 	return nindex;
786 };
787 
788 //
789 // Date month segment class
790 //
791 
792 AjxDateFormat.MonthSegment = function(format, s) {
793     if (arguments.length == 0) return;
794 	AjxDateFormat.DateSegment.call(this, format, s);
795 };
796 AjxDateFormat.MonthSegment.prototype = new AjxDateFormat.DateSegment;
797 AjxDateFormat.MonthSegment.prototype.constructor = AjxDateFormat.MonthSegment;
798 
799 AjxDateFormat.MonthSegment.prototype.toString = function() { 
800 	return "dateMonth: \""+this._s+'"'; 
801 };
802 
803 // Static functions
804 
805 AjxDateFormat.MonthSegment.initialize = function() {
806 	AjxDateFormat.MonthSegment.MONTHS = {};
807 	AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT] = [
808 		AjxMsg.monthJanShort, AjxMsg.monthFebShort, AjxMsg.monthMarShort,
809 		AjxMsg.monthAprShort, AjxMsg.monthMayShort, AjxMsg.monthJunShort,
810 		AjxMsg.monthJulShort, AjxMsg.monthAugShort, AjxMsg.monthSepShort,
811 		AjxMsg.monthOctShort, AjxMsg.monthNovShort, AjxMsg.monthDecShort
812 	];
813 	AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM] = [
814 		I18nMsg.monthJanMedium, I18nMsg.monthFebMedium, I18nMsg.monthMarMedium,
815 		I18nMsg.monthAprMedium, I18nMsg.monthMayMedium, I18nMsg.monthJunMedium,
816 		I18nMsg.monthJulMedium, I18nMsg.monthAugMedium, I18nMsg.monthSepMedium,
817 		I18nMsg.monthOctMedium, I18nMsg.monthNovMedium, I18nMsg.monthDecMedium
818 	];
819 	AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG] = [
820 		I18nMsg.monthJanLong, I18nMsg.monthFebLong, I18nMsg.monthMarLong,
821 		I18nMsg.monthAprLong, I18nMsg.monthMayLong, I18nMsg.monthJunLong,
822 		I18nMsg.monthJulLong, I18nMsg.monthAugLong, I18nMsg.monthSepLong,
823 		I18nMsg.monthOctLong, I18nMsg.monthNovLong, I18nMsg.monthDecLong
824 	];
825 };
826 
827 // Public methods
828 
829 AjxDateFormat.MonthSegment.prototype.format = function(date) {
830 	var month = date.getMonth();
831 	switch (this._s.length) {
832 		case 1: return String(month + 1);
833 		case 2: return AjxFormat._zeroPad(month + 1, 2);
834 		case 3: return AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM][month];
835 		case 5: return AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT][month];
836 	}
837 	return AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG][month];
838 };
839 
840 AjxDateFormat.MonthSegment.prototype.parse = function(object, s, index) {
841 	var months;
842 	switch (this._s.length) {
843 		case 3: 
844 			months = AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.MEDIUM];
845 		case 4: 
846 			months = months || AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.LONG];
847 		case 5: {
848 			months = months || AjxDateFormat.MonthSegment.MONTHS[AjxDateFormat.SHORT];
849 			var nindex = AjxFormat.Segment._parseLiterals(object, "month", 0, months, s, index);
850 			if (nindex == -1) {
851 				throw new AjxFormat.ParsingException(this._parent, this, "no match"); // I18n
852 			}
853 			return nindex;
854 		}
855 	}
856 	var fixedlen = this._getFixedLength();
857 	return AjxFormat.Segment._parseInt(object, "month", -1, s, index, fixedlen);
858 };
859 
860 //
861 // Date week segment class
862 //
863 
864 AjxDateFormat.WeekSegment = function(format, s) {
865     if (arguments.length == 0) return;
866 	AjxDateFormat.DateSegment.call(this, format, s);
867 };
868 AjxDateFormat.WeekSegment.prototype = new AjxDateFormat.DateSegment;
869 AjxDateFormat.WeekSegment.prototype.constructor = AjxDateFormat.WeekSegment;
870 
871 AjxDateFormat.WeekSegment.prototype.toString = function() { 
872 	return "weekMonth: \""+this._s+'"'; 
873 };
874 
875 // Public methods
876 
877 AjxDateFormat.WeekSegment.prototype.format = function(date) {
878 	var year = date.getYear();
879 	var month = date.getMonth();
880 	var day = date.getDate();
881 	
882 	var ofYear = /w/.test(this._s);
883 	var date2 = new Date(year, ofYear ? 0 : month, 1);
884 
885 	var week = 0;
886 	while (true) {
887 		week++;
888 		if (date2.getMonth() > month || (date2.getMonth() == month && date2.getDate() >= day)) {
889 			break;
890 		}
891 		date2.setDate(date2.getDate() + 7);
892 	}
893 
894 	return AjxFormat._zeroPad(week, this._s.length);
895 };
896 
897 AjxDateFormat.WeekSegment.prototype.parse = function(object, s, index) {
898 	var fixedlen = this._getFixedLength();
899 	return AjxFormat.Segment._parseInt(null, null, 0, s, index, fixedlen)
900 };
901 
902 //
903 // Date day segment class
904 //
905 
906 AjxDateFormat.DaySegment = function(format, s) {
907     if (arguments.length == 0) return;
908 	AjxDateFormat.DateSegment.call(this, format, s);
909 };
910 AjxDateFormat.DaySegment.prototype = new AjxDateFormat.DateSegment;
911 AjxDateFormat.DaySegment.prototype.constructor = AjxDateFormat.DaySegment;
912 
913 AjxDateFormat.DaySegment.prototype.toString = function() { 
914 	return "dateDay: \""+this._s+'"'; 
915 };
916 
917 // Public methods
918 
919 AjxDateFormat.DaySegment.prototype.format = function(date) {
920 	var month = date.getMonth();
921 	var day = date.getDate();
922 	if (/D/.test(this._s) && month > 0) {
923 		var year = date.getYear();
924 		do {
925 			// set date to first day of month and then go back one day
926 			var date2 = new Date(year, month, 1);
927 			date2.setDate(0); 
928 			
929 			day += date2.getDate();
930 			month--;
931 		} while (month > 0);
932 	}
933 	return AjxFormat._zeroPad(day, this._s.length);
934 };
935 
936 AjxDateFormat.DaySegment.prototype.parse = function(object, s, index) {
937 	var fixedlen = this._getFixedLength();
938     var dayofmonth = function(day) {
939                       if(day > AjxDateUtil.MAX_DAYS_PER_MONTH) { throw new AjxFormat.ParsingException(this._parent, this, "number too long"); }
940                       else { object["dayofmonth"] = day; }
941                      };
942 	var pname = /D/.test(this._s) ? "dayofyear" : dayofmonth;
943 	return AjxFormat.Segment._parseInt(object, pname, 0, s, index, fixedlen);
944 };
945 
946 //
947 // Date weekday segment class
948 //
949 
950 AjxDateFormat.WeekdaySegment = function(format, s) {
951     if (arguments.length == 0) return;
952 	AjxDateFormat.DateSegment.call(this, format, s);
953 };
954 AjxDateFormat.WeekdaySegment.prototype = new AjxDateFormat.DateSegment;
955 AjxDateFormat.WeekdaySegment.prototype.constructor = AjxDateFormat.WeekdaySegment;
956 
957 AjxDateFormat.DaySegment.prototype.toString = function() { 
958 	return "dateDay: \""+this._s+'"'; 
959 };
960 
961 // Static functions
962 
963 AjxDateFormat.WeekdaySegment.initialize = function() {
964 	AjxDateFormat.WeekdaySegment.WEEKDAYS = {};
965 	// NOTE: The short names aren't available in Java so we have to define them.
966 	AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.SHORT] = [
967 		AjxMsg.weekdaySunShort, AjxMsg.weekdayMonShort, AjxMsg.weekdayTueShort,
968 		AjxMsg.weekdayWedShort, AjxMsg.weekdayThuShort, AjxMsg.weekdayFriShort,
969 		AjxMsg.weekdaySatShort
970 	];
971 	AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.MEDIUM] = [
972 		I18nMsg.weekdaySunMedium, I18nMsg.weekdayMonMedium, I18nMsg.weekdayTueMedium,
973 		I18nMsg.weekdayWedMedium, I18nMsg.weekdayThuMedium, I18nMsg.weekdayFriMedium,
974 		I18nMsg.weekdaySatMedium
975 	];
976 	AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.LONG] = [
977 		I18nMsg.weekdaySunLong, I18nMsg.weekdayMonLong, I18nMsg.weekdayTueLong,
978 		I18nMsg.weekdayWedLong, I18nMsg.weekdayThuLong, I18nMsg.weekdayFriLong,
979 		I18nMsg.weekdaySatLong
980 	];
981 };
982 
983 // Public methods
984 
985 AjxDateFormat.WeekdaySegment.prototype.format = function(date) {
986 	var weekday = date.getDay();
987 	if (/E/.test(this._s)) {
988 		var style;
989 		switch (this._s.length) {
990 			case 4: style = AjxDateFormat.LONG; break;
991 			case 5: style = AjxDateFormat.SHORT; break;
992 			default: style = AjxDateFormat.MEDIUM;
993 		}
994 		return AjxDateFormat.WeekdaySegment.WEEKDAYS[style][weekday];
995 	}
996 	return AjxFormat._zeroPad(weekday, this._s.length);
997 };
998 
999 AjxDateFormat.WeekdaySegment.prototype.parse = function(object, s, index) {
1000 	var weekdays;
1001 	switch (this._s.length) {
1002 		case 3: 
1003 			weekdays = AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.MEDIUM];
1004 		case 4: 
1005 			weekdays = weekdays || AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.LONG];
1006 		case 5: {
1007 			weekdays = weekdays || AjxDateFormat.WeekdaySegment.WEEKDAYS[AjxDateFormat.SHORT];
1008 			var nindex = AjxFormat.Segment._parseLiterals(null, null, 0, weekdays, s, index);
1009 			if (nindex == -1) {
1010 				throw new AjxFormat.ParsingException(this._parent, this, "no match"); // I18n
1011 			}
1012 			return nindex;
1013 		}
1014 	}
1015 	var fixedlen = this._getFixedLength();
1016 	return AjxFormat.Segment._parseInt(null, null, 0, s, index, fixedlen);
1017 };
1018 
1019 //
1020 // Time segment class
1021 //
1022 
1023 AjxDateFormat.TimeSegment = function(format, s) {
1024     if (arguments.length == 0) return;
1025 	AjxFormat.Segment.call(this, format, s);
1026 };
1027 AjxDateFormat.TimeSegment.prototype = new AjxFormat.Segment;
1028 AjxDateFormat.TimeSegment.prototype.constructor = AjxDateFormat.TimeSegment;
1029 
1030 //
1031 // Time hour segment class
1032 //
1033 
1034 AjxDateFormat.HourSegment = function(format, s) {
1035     if (arguments.length == 0) return;
1036 	AjxFormat.Segment.call(this, format, s);
1037 };
1038 AjxDateFormat.HourSegment.prototype = new AjxDateFormat.TimeSegment;
1039 AjxDateFormat.HourSegment.prototype.constructor = AjxDateFormat.HourSegment;
1040 
1041 AjxDateFormat.HourSegment.prototype.toString = function() { 
1042 	return "timeHour: \""+this._s+'"'; 
1043 };
1044 
1045 // Public methods
1046 
1047 AjxDateFormat.HourSegment.prototype.format = function(date) {
1048 	//getHours returns 0-23
1049 	var hours = date.getHours();
1050     if (hours > 12 && /[hK]/.test(this._s)) {
1051 		//changing 13-23 to 1-11 (pm)
1052 		hours -= 12;
1053 	}
1054     else if (hours == 0 && /[h]/.test(this._s)) {
1055 		//h: 1-12 am/pm
1056         hours = 12;
1057     }
1058 	else if (hours === 12 && /K/.test(this._s)) {
1059 		//K: 0-11 am/pm
1060 		hours = 0;
1061  	}
1062 	else if (hours === 0 && /k/.test(this._s)) {
1063 		//k: 1-24
1064 		hours = 24;
1065 	}
1066     /***
1067 	// NOTE: This is commented out to match the Java formatter output
1068 	//       but from the comments for these meta-chars, it doesn't
1069 	//       seem right.
1070 	if (/[Hk]/.test(this._s)) {
1071 		hours--;
1072 	}
1073 	/***/
1074 	return AjxFormat._zeroPad(hours, this._s.length);
1075 };
1076 
1077 AjxDateFormat.HourSegment.prototype.parse = function(object, s, index) {
1078 	var fixedlen = this._getFixedLength();
1079 	return AjxFormat.Segment._parseInt(object, "hours", 0, s, index, fixedlen);
1080 };
1081 
1082 //
1083 // Time minute segment class
1084 //
1085 
1086 AjxDateFormat.MinuteSegment = function(format, s) {
1087     if (arguments.length == 0) return;
1088 	AjxFormat.Segment.call(this, format, s);
1089 };
1090 AjxDateFormat.MinuteSegment.prototype = new AjxDateFormat.TimeSegment;
1091 AjxDateFormat.MinuteSegment.prototype.constructor = AjxDateFormat.MinuteSegment;
1092 
1093 AjxDateFormat.MinuteSegment.prototype.toString = function() { 
1094 	return "timeMinute: \""+this._s+'"'; 
1095 };
1096 
1097 // Public methods
1098 
1099 AjxDateFormat.MinuteSegment.prototype.format = function(date) {
1100 	var minutes = date.getMinutes();
1101 	return AjxFormat._zeroPad(minutes, this._s.length);
1102 };
1103 
1104 AjxDateFormat.MinuteSegment.prototype.parse = function(object, s, index) {
1105 	var fixedlen = this._getFixedLength();
1106 	return AjxFormat.Segment._parseInt(object, "minutes", 0, s, index, fixedlen);
1107 };
1108 
1109 //
1110 // Time second segment class
1111 //
1112 
1113 AjxDateFormat.SecondSegment = function(format, s) {
1114     if (arguments.length == 0) return;
1115 	AjxFormat.Segment.call(this, format, s);
1116 };
1117 AjxDateFormat.SecondSegment.prototype = new AjxDateFormat.TimeSegment;
1118 AjxDateFormat.SecondSegment.prototype.constructor = AjxDateFormat.SecondSegment;
1119 
1120 AjxDateFormat.SecondSegment.prototype.toString = function() { 
1121 	return "timeSecond: \""+this._s+'"'; 
1122 };
1123 
1124 // Public methods
1125 
1126 AjxDateFormat.SecondSegment.prototype.format = function(date) {
1127 	var minutes = /s/.test(this._s) ? date.getSeconds() : date.getMilliseconds();
1128 	return AjxFormat._zeroPad(minutes, this._s.length);
1129 };
1130 
1131 AjxDateFormat.SecondSegment.prototype.parse = function(object, s, index) {
1132     var isSeconds = /s/.test(this._s);
1133     var pname = isSeconds ? "seconds" : "milliseconds";
1134 	var fixedlen = isSeconds ? this._getFixedLength() : 0;
1135 	return AjxFormat.Segment._parseInt(object, pname, 0, s, index, fixedlen);
1136 };
1137 
1138 //
1139 // Time am/pm segment class
1140 //
1141 
1142 AjxDateFormat.AmPmSegment = function(format, s) {
1143     if (arguments.length == 0) return;
1144 	AjxFormat.Segment.call(this, format, s);
1145 };
1146 AjxDateFormat.AmPmSegment.prototype = new AjxDateFormat.TimeSegment;
1147 AjxDateFormat.AmPmSegment.prototype.constructor = AjxDateFormat.AmPmSegment;
1148 
1149 AjxDateFormat.AmPmSegment.prototype.toString = function() { 
1150 	return "timeAmPm: \""+this._s+'"'; 
1151 };
1152 
1153 // Public methods
1154 
1155 AjxDateFormat.AmPmSegment.prototype.format = function(date) {
1156 	var hours = date.getHours();
1157 	return hours < 12 ? I18nMsg.periodAm : I18nMsg.periodPm;
1158 };
1159 
1160 AjxDateFormat.AmPmSegment.prototype.parse = function(object, s, index) {
1161 	var periods = [ 
1162 		I18nMsg.periodAm.toLowerCase(), I18nMsg.periodPm.toLowerCase(),
1163 		I18nMsg.periodAm.toUpperCase(), I18nMsg.periodPm.toUpperCase()
1164 	];
1165 	var nindex = AjxFormat.Segment._parseLiterals(object, "ampm", 0, periods, s, index);
1166 	if (nindex == -1) {
1167 		throw new AjxFormat.ParsingException(this._parent, this, "no match"); // I18n
1168 	}
1169 	object.ampm = object.ampm % 2;
1170 	return nindex;
1171 };
1172 
1173 //
1174 // Time timezone segment class
1175 //
1176 
1177 AjxDateFormat.TimezoneSegment = function(format, s) {
1178     if (arguments.length == 0) return;
1179 	AjxFormat.Segment.call(this, format, s);
1180 };
1181 AjxDateFormat.TimezoneSegment.prototype = new AjxDateFormat.TimeSegment;
1182 AjxDateFormat.TimezoneSegment.prototype.constructor = AjxDateFormat.TimezoneSegment;
1183 
1184 AjxDateFormat.TimezoneSegment.prototype.toString = function() { 
1185 	return "timeTimezone: \""+this._s+'"'; 
1186 };
1187 
1188 // Public methods
1189 
1190 AjxDateFormat.TimezoneSegment.prototype.format = function(date) {
1191 	var clientId = date.timezone || AjxTimezone.DEFAULT;
1192 	if (/Z/.test(this._s)) {
1193 		return AjxTimezone.getShortName(clientId);
1194 	}
1195 	return this._s.length < 4 ? AjxTimezone.getMediumName(clientId) : AjxTimezone.getLongName(clientId);
1196 };
1197 
1198 //
1199 // Message format class
1200 //
1201 
1202 /**
1203  * Message formatter based on the Java <code>MessageFormat</code> class. 
1204  * <p>
1205  * <strong>Note:</strong>
1206  * This implementation augments Java's <code>MessageFormat</code> patterns
1207  * to support list formatting. The following forms differ from the originals
1208  * defined in the JavaDoc for <code>MessageFormat</code>:
1209  * <pre>
1210  * <i>FormatElement</i>:
1211  *       { <i>ArgumentIndex</i> }
1212  *       { <i>ArgumentIndex</i> , <i>FormatType</i> }
1213  *       { <i>ArgumentIndex</i> , <i>ListFormatType</i> , <i>FormatType</i> }
1214  *       { <i>ArgumentIndex</i> , <i>FormatType</i> , <i>FormatStyle</i> }
1215  *       { <i>ArgumentIndex</i> , <i>ListFormatType , <i>FormatType</i> , <i>FormatStyle</i> }
1216  * <i>ListFormatType</i>:
1217  *       list
1218  * </pre>
1219  *
1220  * @param {string} pattern The message format pattern.
1221  *
1222  * @class
1223  * @constructor
1224  */
1225 AjxMessageFormat = function(pattern) {
1226     if (arguments.length == 0) { return; }
1227 	AjxFormat.call(this, pattern);
1228 	if (pattern == null) { return; }
1229 	for (var i = 0; i < pattern.length; i++) {
1230 		// literal
1231 		var c = pattern.charAt(i);
1232 		if (c == "'") {
1233 			if (i + 1 < pattern.length && pattern.charAt(i + 1) == "'") {
1234 				var segment = new AjxFormat.TextSegment(this, "'");
1235 				this._segments.push(segment);
1236 				i++;
1237 				continue;
1238 			}
1239 			var head = i + 1;
1240 			for (i++ ; i < pattern.length; i++) {
1241 				var c = pattern.charAt(i);
1242 				if (c == "'") {
1243 					if (i + 1 < pattern.length && pattern.charAt(i + 1) == "'") {
1244 						pattern = pattern.substr(0, i) + pattern.substr(i + 1);
1245 					}
1246 					else {
1247 						break;
1248 					}
1249 				}
1250 			}
1251 //			if (i == pattern.length) {
1252 				// NOTE: try to avoid silent errors
1253 //				throw new AjxFormat.FormatException(this, "unterminated string literal"); // I18n
1254 //)			}
1255 			var tail = i;
1256 			var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail));
1257 			this._segments.push(segment);
1258 			continue;
1259 		}
1260 		
1261 		// non-meta chars
1262 		var head = i;
1263 		while(i < pattern.length) {
1264 			c = pattern.charAt(i);
1265 			if (c == '{' || c == "'") {
1266 				break;
1267 			}
1268 			i++;
1269 		}
1270 		var tail = i;
1271 		if (head != tail) {
1272 			var segment = new AjxFormat.TextSegment(this, pattern.substring(head, tail));
1273 			this._segments.push(segment);
1274 			i--;
1275 			continue;
1276 		}
1277 		
1278 		// meta char
1279 		var head = i + 1;
1280 		var depth = 0;
1281 		while(++i < pattern.length) {
1282 			var c = pattern.charAt(i);
1283 			if (c == '{') {
1284 				depth++;
1285 			}
1286 			else if (c == '}') {
1287 				if (depth == 0) {
1288 					break;
1289 				}
1290 				depth--;
1291 			}		
1292 		}
1293 		var tail = i;
1294 		var count = tail - head;
1295 		var field = pattern.substr(head, count);
1296 		var segment = new AjxMessageFormat.MessageSegment(this, field);		
1297 		if (segment != null) {
1298 			this._segments.push(segment);
1299 		}
1300 	}
1301 }
1302 AjxMessageFormat.prototype = new AjxFormat;
1303 AjxMessageFormat.prototype.constructor = AjxMessageFormat;
1304 
1305 AjxMessageFormat.prototype.toString = function() {
1306 	return "[AjxMessageFormat: "+AjxFormat.prototype.toString.call(this)+"]";
1307 };
1308 
1309 // Static methods
1310 
1311 /**
1312  * Format a message pattern with replacement parameters. Equivalent to
1313  * <code>new AjxMessageFormat(pattern).format(params)</code>.
1314  *
1315  * @param {string} pattern  Message pattern.
1316  * @param {Array}  params   Replacement parameters.
1317  * @return {string} Formatted message.
1318  */
1319 AjxMessageFormat.format = function(pattern, params) {
1320 	return new AjxMessageFormat(pattern).format(params);
1321 };
1322 
1323 // Public methods
1324 
1325 /**
1326  * Format with replacement parameters.
1327  *
1328  * @param {Array}  params   Replacement parameters.
1329  * @return {string} Formatted message.
1330  */
1331 AjxMessageFormat.prototype.format = function(params) {
1332 	if (!(params instanceof Array)) {
1333 		params = [ params ];
1334 	}
1335 	return AjxFormat.prototype.format.call(this, params);
1336 };
1337 
1338 AjxMessageFormat.prototype.getFormats = function() {
1339 	var formats = [];
1340 	for (var i = 0; i < this._segments.length; i++) {
1341 		var segment = this._segments[i];
1342 		if (segment instanceof AjxMessageFormat.MessageSegment) {
1343 			formats.push(segment.getSegmentFormat());
1344 		}
1345 	}
1346 	return formats;
1347 };
1348 AjxMessageFormat.prototype.getFormatsByArgumentIndex = function() {
1349 	var formats = [];
1350 	for (var i = 0; i < this._segments.length; i++) {
1351 		var segment = this._segments[i];
1352 		if (segment instanceof AjxMessageFormat.MessageSegment) {
1353 			formats[segment.getIndex()] = segment.getSegmentFormat();
1354 		}
1355 	}
1356 	return formats;
1357 };
1358 
1359 //
1360 // AjxMessageFormat.MessageSegment class
1361 //
1362 
1363 AjxMessageFormat.MessageSegment = function(format, s) {
1364     if (arguments.length == 0) return;
1365 	AjxFormat.Segment.call(this, format, s);
1366 	var parts = AjxMessageFormat.MessageSegment._split(s, ',');
1367 	this._index = Number(parts[0]);
1368 	this._type = parts[1] || "string";
1369 	this._style = parts[2];
1370 	if (this._type == "list") {
1371 		this._isList = true;
1372 		this._type = parts[2] || "string";
1373 		this._style = parts[3];
1374 	}
1375 	switch (this._type) {
1376 		case "number": {
1377 			switch (this._style) {
1378 				case "integer": this._formatter = AjxNumberFormat.getIntegerInstance(); break;
1379 				case "currency": this._formatter = AjxNumberFormat.getCurrencyInstance(); break;
1380 				case "percent": this._formatter = AjxNumberFormat.getPercentInstance(); break;
1381 				default: this._formatter = this._style == null ? AjxNumberFormat.getInstance() : new AjxNumberFormat(this._style);
1382 			}
1383 			break;
1384 		}
1385 		case "date": case "time": {
1386 			var func = this._type == "date" ? AjxDateFormat.getDateInstance : AjxDateFormat.getTimeInstance;
1387 			switch (this._style) {
1388 				case "short": this._formatter = func(AjxDateFormat.SHORT); break;
1389 				case "medium": this._formatter = func(AjxDateFormat.MEDIUM); break;
1390 				case "long": this._formatter = func(AjxDateFormat.LONG); break;
1391 				case "full": this._formatter = func(AjxDateFormat.FULL); break;
1392 				default: this._formatter = this._style == null ? func(AjxDateFormat.DEFAULT) : new AjxDateFormat(this._style);
1393 			}
1394 			break;
1395 		}
1396 		case "choice": {
1397 			this._formatter = new AjxChoiceFormat(this._style);
1398 			break;
1399 		}
1400 	}
1401 	if (this._isList) {
1402 		this._formatter = new AjxListFormat(this._formatter);
1403 	}	
1404 };
1405 AjxMessageFormat.MessageSegment.prototype = new AjxFormat.Segment;
1406 AjxMessageFormat.MessageSegment.prototype.constructor = AjxMessageFormat.MessageSegment;
1407 
1408 AjxMessageFormat.MessageSegment.prototype.toString = function() {
1409 	var a = [ "message: \"", this._s, "\", index: ", this._index ];
1410 	if (this._isList) a.push(", list: ", this._isList);
1411 	if (this._type) a.push(", type: ", this._type);
1412 	if (this._style) a.push(", style: ", this._style);
1413 	if (this._formatter) a.push(", formatter: ", this._formatter.toString());
1414 	return a.join("");
1415 };
1416 
1417 // Data
1418 
1419 AjxMessageFormat.MessageSegment.prototype._isList = false;
1420 
1421 // Public methods
1422 
1423 AjxMessageFormat.MessageSegment.prototype.format = function(args) {
1424 	var object = args[this._index];
1425 	if (this._formatter instanceof AjxChoiceFormat) {
1426 		return this._formatter.format(args, this._index);
1427 	}
1428 	return this._formatter ? this._formatter.format(object) : String(object);
1429 };
1430 
1431 AjxMessageFormat.MessageSegment.prototype.getIndex = function() {
1432 	return this._index;
1433 };
1434 AjxMessageFormat.MessageSegment.prototype.getType = function() {
1435 	return this._type;
1436 };
1437 AjxMessageFormat.MessageSegment.prototype.getStyle = function() {
1438 	return this._style;
1439 };
1440 AjxMessageFormat.MessageSegment.prototype.getSegmentFormat = function() {
1441 	return this._formatter;
1442 };
1443 
1444 // Protected static functions
1445 
1446 AjxMessageFormat.MessageSegment._split = function(s, delimiter) {
1447 	var parts = [];
1448 	var head = 0;
1449 	var tail;
1450 	var depth = 0;
1451 	for (tail = 0; tail < s.length; tail++) {
1452 		var c = s.charAt(tail);
1453 		if (c == '{') {
1454 			depth++;
1455 		}
1456 		else if (c == '}') {
1457 			depth--;
1458 		}
1459 		else if (c == delimiter && depth == 0) {
1460 			parts.push(s.substring(head, tail));
1461 			head = tail + 1;
1462 		}
1463 	}
1464 	if (tail > head) {
1465 		parts.push(s.substring(head, tail));
1466 	}
1467 	return parts;
1468 };
1469 
1470 
1471 //
1472 // AjxNumberFormat class
1473 //
1474 
1475 /**
1476  * Number formatter based on Java's <code>DecimalFormat</code>.
1477  *
1478  * @param {string}  pattern       The number pattern.
1479  * @param {boolean} skipNegFormat Specifies whether to skip the generation of this
1480  *                                format's negative value formatter.
1481  *                                <p>
1482  *                                <strong>Note:</strong>
1483  *                                This parameter is only used by the implementation
1484  *                                and should not be passed by application code
1485  *                                instantiating a custom number format.
1486  * @class
1487  * @constructor
1488  */
1489 AjxNumberFormat = function(pattern, skipNegFormat) {
1490     if (arguments.length == 0) { return; }
1491 	AjxFormat.call(this, pattern);
1492 	if (!pattern) { return; }
1493 
1494 	var patterns = pattern.split(/;/);
1495 	var pattern = patterns[0];
1496 	
1497 	// parse prefix
1498 	var i = 0;
1499 	var results = this.__parseStatic(pattern, i);
1500 	i = results.offset;
1501 	var hasPrefix = results.text != "";
1502 	if (hasPrefix) {
1503 		this._segments.push(new AjxFormat.TextSegment(this, results.text));
1504 	}
1505 	
1506 	// parse number descriptor
1507 	var start = i;
1508 	while (i < pattern.length &&
1509 	       AjxNumberFormat._META_CHARS.indexOf(pattern.charAt(i)) != -1) {
1510 		i++;
1511 	}
1512 	var end = i;
1513 
1514 	var numPattern = pattern.substring(start, end);
1515 	var e = numPattern.indexOf('E');
1516 	var expon = e != -1 ? numPattern.substring(e + 1) : null;
1517 	if (expon) {
1518 		numPattern = numPattern.substring(0, e);
1519 		this._showExponent = true;
1520 	}
1521 	
1522 	var dot = numPattern.indexOf(I18nMsg.numberSeparatorDecimal !='' ? I18nMsg.numberSeparatorDecimal : '.');
1523 	var whole = dot != -1 ? numPattern.substring(0, dot) : numPattern;
1524 	if (whole) {
1525 		var comma = whole.lastIndexOf(I18nMsg.numberSeparatorGrouping !='' ? I18nMsg.numberSeparatorGrouping : ',');
1526 		if (comma != -1) {
1527 			this._groupingOffset = whole.length - comma - 1;
1528 		}
1529 		whole = whole.replace(/[^#0]/g,"");
1530 		var zero = whole.indexOf('0');
1531 		if (zero != -1) {
1532 			this._minIntDigits = whole.length - zero;
1533 		}
1534 		this._maxIntDigits = whole.length;
1535 	}
1536 	
1537 	var fract = dot != -1 ? numPattern.substring(dot + 1) : null;
1538 	if (fract) {
1539 		var zero = fract.lastIndexOf('0');
1540 		if (zero != -1) {
1541 			this._minFracDigits = zero + 1;
1542 		}
1543 		this._maxFracDigits = fract.replace(/[^#0]/g,"").length;
1544 	}
1545 	
1546 	this._segments.push(new AjxNumberFormat.NumberSegment(this, numPattern));
1547 	
1548 	// parse suffix
1549 	var results = this.__parseStatic(pattern, i);
1550 	i = results.offset;
1551 	if (results.text != "") {
1552 		this._segments.push(new AjxFormat.TextSegment(this, results.text));
1553 	}
1554 	
1555 	// add negative formatter
1556 	if (skipNegFormat) return;
1557 	
1558 	if (patterns.length > 1) {
1559 		var pattern = patterns[1];
1560 		this._negativeFormatter = new AjxNumberFormat(pattern, true);
1561 	}
1562 	else {
1563 		// no negative pattern; insert minus sign before number segment
1564 		var formatter = new AjxNumberFormat("");
1565 		formatter._segments = formatter._segments.concat(this._segments);
1566 
1567 		var index = hasPrefix ? 1 : 0;
1568 		var minus = new AjxFormat.TextSegment(formatter, I18nMsg.numberSignMinus);
1569 		formatter._segments.splice(index, 0, minus);
1570 		
1571 		this._negativeFormatter = formatter;
1572 	}
1573 }
1574 AjxNumberFormat.prototype = new AjxFormat;
1575 AjxNumberFormat.prototype.constructor = AjxNumberFormat;
1576 
1577 AjxNumberFormat.prototype.toString = function() {
1578 	var array = [ 
1579 		"[AjxNumberFormat: ", 
1580 		"formatter=", AjxFormat.prototype.toString.call(this) 
1581 	];
1582 	if (this._negativeFormatter) {
1583 		array.push(", negativeFormatter=", this._negativeFormatter.toString());
1584 	}
1585 	array.push(']');
1586 	return array.join("");
1587 };
1588 
1589 // Constants
1590 
1591 AjxNumberFormat._NUMBER = "number";
1592 AjxNumberFormat._INTEGER = "integer";
1593 AjxNumberFormat._CURRENCY = "currency";
1594 AjxNumberFormat._PERCENT = "percent";
1595 
1596 AjxNumberFormat._META_CHARS = "0#.,E";
1597 
1598 // Data
1599 
1600 AjxNumberFormat.prototype._groupingOffset = Number.MAX_VALUE;
1601 AjxNumberFormat.prototype._minIntDigits = 1;
1602 AjxNumberFormat.prototype._isCurrency = false;
1603 AjxNumberFormat.prototype._isPercent = false;
1604 AjxNumberFormat.prototype._isPerMille = false;
1605 AjxNumberFormat.prototype._showExponent = false;
1606 
1607 // Static functions
1608 
1609 /**
1610  * Get general number formatter.
1611  *
1612  * @return {AjxNumberFormat} Number formatter.
1613  */
1614 AjxNumberFormat.getInstance = function() {
1615 	if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._NUMBER]) {
1616 		AjxNumberFormat._FORMATTERS[AjxNumberFormat._NUMBER] = new AjxNumberFormat(I18nMsg.formatNumber);
1617 	}
1618 	return AjxNumberFormat._FORMATTERS[AjxNumberFormat._NUMBER];
1619 };
1620 /**
1621  * Get general number formatter.
1622  * <strong>Note:</strong>
1623  * Same as <code>AjxNumberFormat.getInstance()</code>.
1624  *
1625  * @return {AjxNumberFormat} Number formatter.
1626  */
1627 AjxNumberFormat.getNumberInstance = AjxNumberFormat.getInstance;
1628 /**
1629  * Get currency number formatter.
1630  *
1631  * @return {AjxNumberFormat} Number formatter.
1632  */
1633 AjxNumberFormat.getCurrencyInstance = function() {
1634 	if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._CURRENCY]) {
1635 		AjxNumberFormat._FORMATTERS[AjxNumberFormat._CURRENCY] = new AjxNumberFormat(I18nMsg.formatNumberCurrency);
1636 	}
1637 	return AjxNumberFormat._FORMATTERS[AjxNumberFormat._CURRENCY];
1638 };
1639 /**
1640  * Get integer number formatter.
1641  *
1642  * @return {AjxNumberFormat} Number formatter.
1643  */
1644 AjxNumberFormat.getIntegerInstance = function() {
1645 	if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._INTEGER]) {
1646 		AjxNumberFormat._FORMATTERS[AjxNumberFormat._INTEGER] = new AjxNumberFormat(I18nMsg.formatNumberInteger);
1647 	}
1648 	return AjxNumberFormat._FORMATTERS[AjxNumberFormat._INTEGER];
1649 };
1650 /**
1651  * Get percent number formatter.
1652  *
1653  * @return {AjxNumberFormat} Number formatter.
1654  */
1655 AjxNumberFormat.getPercentInstance = function() {
1656 	if (!AjxNumberFormat._FORMATTERS[AjxNumberFormat._PERCENT]) {
1657 		AjxNumberFormat._FORMATTERS[AjxNumberFormat._PERCENT] = new AjxNumberFormat(I18nMsg.formatNumberPercent);
1658 	}
1659 	return AjxNumberFormat._FORMATTERS[AjxNumberFormat._PERCENT];
1660 };
1661 
1662 /**
1663  * Formats a number based on a given pattern. Equivalent to
1664  * <code>new AjxNumberFormat(pattern).format(number)</code>.
1665  *
1666  * @param {string}  pattern The format pattern.
1667  * @param {number}  number  The number to format.
1668  * @return {string} The formatted number string.
1669  */
1670 AjxNumberFormat.format = function(pattern, number) {
1671 	return new AjxNumberFormat(pattern).format(number);
1672 };
1673 
1674 AjxNumberFormat.initialize = function() {
1675 	AjxNumberFormat._FORMATTERS = {};
1676 };
1677 
1678 // Public methods
1679 
1680 /**
1681  * Formats a number.
1682  *
1683  * @param {number}  number  The number to format.
1684  * @return {string} The formatted number string.
1685  */
1686 AjxNumberFormat.prototype.format = function(number) {
1687 	if (number < 0 && this._negativeFormatter) {
1688 		return this._negativeFormatter.format(number);
1689 	}
1690 	return AjxFormat.prototype.format.call(this, number);
1691 };
1692 
1693 // Private methods
1694 
1695 AjxNumberFormat.prototype.__parseStatic = function(s, i) {
1696 	var data = [];
1697 	while (i < s.length) {
1698 		var c = s.charAt(i++);
1699 		if (AjxNumberFormat._META_CHARS.indexOf(c) != -1) {
1700 			i--;
1701 			break;
1702 		}
1703 		switch (c) {
1704 			case "'": {
1705 				var start = i;
1706 				while (i < s.length && s.charAt(i++) != "'") {
1707 					// do nothing
1708 				}
1709 				var end = i;
1710 				c = end - start == 0 ? "'" : s.substring(start, end);
1711 				break;
1712 			}
1713 			case '%': {
1714 				c = I18nMsg.numberSignPercent; 
1715 				this._isPercent = true;
1716 				break;
1717 			}
1718 			case '\u2030': {
1719 				c = I18nMsg.numberSignPerMill; 
1720 				this._isPerMille = true;
1721 				break;
1722 			}
1723 			case '\u00a4': {
1724 				c = s.charAt(i) == '\u00a4'
1725 				  ? I18nMsg.currencyCode : I18nMsg.currencySymbol;
1726 				this._isCurrency = true;
1727 				break;
1728 			}
1729 		}
1730 		data.push(c);
1731 	}
1732 	return { text: data.join(""), offset: i };
1733 };
1734 
1735 //
1736 // AjxNumberFormat.NumberSegment class
1737 //
1738 
1739 AjxNumberFormat.NumberSegment = function(format, s) {
1740     if (arguments.length == 0) return;
1741 	AjxFormat.Segment.call(this, format, s);
1742 };
1743 AjxNumberFormat.NumberSegment.prototype = new AjxFormat.Segment;
1744 AjxNumberFormat.NumberSegment.prototype.constructor = AjxNumberFormat.NumberSegment;
1745 
1746 AjxNumberFormat.NumberSegment.prototype.toString = function() {
1747 	return "number: \""+this._s+"\"";
1748 };
1749 
1750 // Public methods
1751 
1752 AjxNumberFormat.NumberSegment.prototype.format = function(number) {
1753 	// special values
1754 	if (isNaN(number)) return I18nMsg.numberNaN;
1755 	if (number === Number.NEGATIVE_INFINITY || number === Number.POSITIVE_INFINITY) {
1756 		return I18nMsg.numberInfinity;
1757 	}
1758 
1759 	// adjust value
1760 	if (typeof number != "number") number = Number(number);
1761 	number = Math.abs(number); // NOTE: minus sign is part of pattern
1762 	if (this._parent._isPercent) number *= 100;
1763 	else if (this._parent._isPerMille) number *= 1000;
1764 
1765 	// format
1766 	var s = this._parent._showExponent
1767 	      ? number.toExponential(this._parent._maxFracDigits).toUpperCase().replace(/E\+/,"E")
1768 	      : number.toFixed(this._parent._maxFracDigits || 0);
1769 	s = this._normalize(s);
1770 	return s;
1771 };
1772 
1773 // Protected methods
1774 
1775 AjxNumberFormat.NumberSegment.prototype._normalize = function(s) {
1776 	var match = s.split(/[\.Ee]/);
1777 	
1778 	// normalize whole part
1779 	var whole = match.shift();
1780 	if (whole.length < this._parent._minIntDigits) {
1781 		whole = AjxFormat._zeroPad(whole, this._parent._minIntDigits, I18nMsg.numberZero);
1782 	}
1783 	if (whole.length > this._parent._groupingOffset) {
1784 		var a = [];
1785 		
1786 		var i = whole.length - this._parent._groupingOffset;
1787 		while (i > 0) {
1788 			a.unshift(whole.substr(i, this._parent._groupingOffset));
1789 			a.unshift(I18nMsg.numberSeparatorGrouping);
1790 			i -= this._parent._groupingOffset;
1791 		}
1792 		a.unshift(whole.substring(0, i + this._parent._groupingOffset));
1793 		
1794 		whole = a.join("");
1795 	}
1796 	
1797 	// normalize rest
1798 	var fract = '0';
1799 	var expon;
1800 
1801     if(s.match(/\./))
1802         fract = match.shift();
1803     else if(s.match(/\e/) || s.match(/\E/))
1804         expon = match.shift();
1805 
1806     fract = fract.replace(/0+$/,"");
1807 	if (fract.length < this._parent._minFracDigits) {
1808 		fract = AjxFormat._zeroPad(fract, this._parent._minFracDigits, I18nMsg.numberZero, true);
1809 	}
1810 	
1811 	var a = [ whole ];
1812 	if (fract.length > 0) {
1813 		var decimal = this._parent._isCurrency
1814 		            ? I18nMsg.numberSeparatorMoneyDecimal
1815 		            : I18nMsg.numberSeparatorDecimal;
1816 		a.push(decimal, fract);
1817 	}
1818 	if (expon) {
1819 		a.push('E', expon.replace(/^\+/,""));
1820 	}
1821 	
1822 	// return normalize result
1823 	return a.join("");
1824 };
1825 
1826 //
1827 // AjxChoiceFormat class
1828 //
1829 
1830 /**
1831  * Choice formatter typically used to format plurals. This class is
1832  * modeled after Java's <code>ChoiceFormat</code>.
1833  * <p>
1834  * The arguments passed to this constructor can be either:
1835  * <ul>
1836  * <li>A single argument that represents a string pattern that specifies the 
1837  *     limits and formats separated by pipe characters (|).
1838  * <li>Two arguments, an array of limits and and array of format patterns.
1839  * </ul>
1840  * <p>
1841  * For complete details, see the JavaDoc for java.text.ChoiceFormat.
1842  *
1843  * @param {string} pattern Format pattern.
1844  *
1845  * @class
1846  * @constructor
1847  */
1848 AjxChoiceFormat = function(pattern) {
1849     if (arguments.length == 0) { return; }
1850 	AjxFormat.call(this, pattern);
1851 	if (pattern == null) { return; }
1852 	var choices = pattern.split("|");
1853 	if (arguments.length == 1) {
1854 		this._limits = new Array(choices.length);
1855 		this._lessThan = new Array(choices.length);
1856 		this._formats = new Array(choices.length);
1857 		var regex = new RegExp("^([^#<\u2264]+)([#<\u2264])(.*)$");
1858 		for (var i = 0; i < choices.length; i++) {
1859 			var choice = choices[i];
1860 			var results = regex.exec(choice);
1861 			var limit = results[1];
1862 			var separator = results[2];
1863 			var message = results[3];
1864 			// set limit
1865 			if (limit == '\u221E') {
1866 				this._limits[i] = Number.POSITIVE_INFINITY;
1867 			}
1868 			else if (limit == '-\u221E') {
1869 				this._limits[i] = Number.NEGATIVE_INFINITY;
1870 			}
1871 			else {
1872 				this._limits[i] = parseFloat(limit);
1873 			}
1874 			// set less-than
1875 			this._lessThan[i] = separator == '#' || separator == '\u2264';
1876 			// set format
1877 			this._formats[i] = new AjxMessageFormat(message);
1878 		}
1879 	}
1880 	else {
1881 		this._limits = arguments[0];
1882 		this._lessThan = new Array(arguments[0].length);
1883 		this._formats = arguments[1];
1884 		this._pattern = [];
1885 		for (var i = 0; i < this._formats.length; i++) {
1886 			if (i > 0) {
1887 				this._pattern.push("|");
1888 			}
1889 			this._pattern.push(this._limits[i], '#', this._formats[i]);
1890 			this._lessThan[i] = false;
1891 			this._formats[i] = new AjxMessageFormat(this._formats[i]);
1892 		}
1893 		this._pattern = this._pattern.join("");
1894 	}
1895 }
1896 AjxChoiceFormat.prototype = new AjxFormat;
1897 AjxChoiceFormat.prototype.constructor = AjxChoiceFormat;
1898 
1899 AjxChoiceFormat.prototype.toString = function() {
1900 	return [
1901 		"[AjxChoiceFormat: ",
1902 		"limits={ ", this._limits.join(", "), " }, ",
1903 		"formats={ ", this._formats.join(", "), " }, ",
1904 		"lessThan={ ", this._lessThan.join(", "), " }]"
1905 	].join("");
1906 };
1907 
1908 // Public methods
1909 
1910 AjxChoiceFormat.prototype.getLimits = function() {
1911 	return this._limits;
1912 };
1913 AjxChoiceFormat.prototype.getFormats = function() {
1914 	return this._formats;
1915 };
1916 
1917 /**
1918  * Format a number based on the choice pattern.
1919  *
1920  * @param {number|Array} number Specifies the number to format. If called
1921  *                              from a message segment, this argument is
1922  *                              the array of arguments passed to the message
1923  *                              formatter.
1924  * @param {number}       index  Optional. If called from a message format,
1925  *                              this argument is the index into the array that
1926  *                              is passed as the first argument. The value at
1927  *                              the index in the array is the number to format.
1928  * @return {string} The formatted choice.
1929  */
1930 AjxChoiceFormat.prototype.format = function(number, index) {
1931 	var num = number instanceof Array ? number[index] : number;
1932 	var formatter;
1933 	if (isNaN(num) || num < this._limits[0]) {
1934 		formatter = this._formats[0];
1935 	}
1936 	else {
1937 		for (var i = 0; i < this._limits.length - 1; i++) {
1938 			var a = this._limits[i];
1939 			var b = this._limits[i+1];
1940 			var aGEb = num >= a;
1941 			var aLTb = this._lessThan[i+1] ? num < b : num <= b;
1942 			if (aGEb && aLTb) {
1943 				formatter = this._formats[i];
1944 				break;
1945 			}
1946 		}
1947 		if (!formatter) {
1948 			formatter = this._formats[this._formats.length-1];
1949 		}
1950 	}
1951 	return formatter.format(number);
1952 };
1953 
1954 //
1955 // AjxListFormat class
1956 //
1957 
1958 /**
1959  * This class allows the user to format a list by applying a specific 
1960  * format to each object in an array and joining the results. Each formatted
1961  * item in the list is separated by a list separator string.
1962  * <p>
1963  * <strong>Note:</strong>
1964  * This format is <em>not</em> one of the standard formatter classes 
1965  * available in Java. 
1966  *
1967  * @param {AjxFormat} formatter     The formatter.
1968  * @param {string}    separator     Optional. The list separator string. If
1969  *                                  not specified, <code>AjxMsg.separatorList</code> 
1970  *                                  is used.
1971  * @param {string}    lastSeparator Optional. The list separator string for
1972  *                                  the last item in the list (e.g. " and ").
1973  *                                  If not specified, <code>AjxMsg.separatorListLast</code>
1974  *                                  is used.
1975  * @param {string}    twoSeparator	Optional. Separator used if the list consists of 
1976  * 									exactly two items. If not specified, <code>AjxMsg.listSeparatorTwo</code>
1977  * 									is used.	
1978  */
1979 AjxListFormat = function(formatter, separator, lastSeparator, twoSeparator) {
1980 	AjxFormat.call(this, formatter ? formatter.toPattern() : "");
1981 	this._formatter = formatter;
1982 	this._separator = separator || AjxMsg.listSeparator;
1983 	this._lastSeparator = lastSeparator || AjxMsg.listSeparatorLast;
1984 	this._twoSeparator = twoSeparator || AjxMsg.listSeparatorTwo;
1985 }
1986 AjxListFormat.prototype = new AjxFormat;
1987 AjxListFormat.prototype.constructor = AjxListFormat;
1988 
1989 // Public methods
1990 
1991 /**
1992  * Formats a list of items.
1993  *
1994  * @param {Array} array Array of objects to be formatted in a list.
1995  * @return {string} The formatted list string.
1996  */
1997 AjxListFormat.prototype.format = function(array) {
1998 	array = array instanceof Array ? array : [ array ];
1999 	var list = [];
2000 	var num = array.length;
2001 	for (var i = 0; i < num; i++) {
2002 		if (i > 0) {
2003 			list.push((i < num - 1) ? this._separator : (num == 2) ?
2004 					this._twoSeparator : this._lastSeparator);
2005 		}
2006 		var item = array[i];
2007 		list.push(this._formatter ? this._formatter.format(item) : String(item));
2008 	}
2009 	return list.join("");
2010 };
2011 
2012 //
2013 // INITIALIZE
2014 //
2015 
2016 AjxFormat.initialize();
2017