Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / uglify-js / tmp / uglify-hangs.js
1 /** 
2  * @fileoverview 
3  *
4  * JsWorld
5  *
6  * <p>Javascript library for localised formatting and parsing of:
7  *    <ul>
8  *        <li>Numbers
9  *        <li>Dates and times
10  *        <li>Currency
11  *    </ul>
12  *
13  * <p>The library classes are configured with standard POSIX locale definitions
14  * derived from Unicode's Common Locale Data Repository (CLDR).
15  *
16  * <p>Website: <a href="http://software.dzhuvinov.com/jsworld.html">JsWorld</a>
17  *
18  * @author Vladimir Dzhuvinov
19  * @version 2.5 (2011-12-23)
20  */
21
22
23
24 /** 
25  * @namespace Namespace container for the JsWorld library objects.
26  */
27 jsworld = {};
28
29
30 /** 
31  * @function
32  * 
33  * @description Formats a JavaScript Date object as an ISO-8601 date/time 
34  * string.
35  *
36  * @param {Date} [d] A valid JavaScript Date object. If undefined the 
37  *        current date/time will be used.
38  * @param {Boolean} [withTZ] Include timezone offset, default false.
39  *
40  * @returns {String} The date/time formatted as YYYY-MM-DD HH:MM:SS.
41  */
42 jsworld.formatIsoDateTime = function(d, withTZ) {
43
44         if (typeof d === "undefined")
45                 d = new Date(); // now
46         
47         if (typeof withTZ === "undefined")
48                 withTZ = false;
49         
50         var s = jsworld.formatIsoDate(d) + " " + jsworld.formatIsoTime(d);
51         
52         if (withTZ) {
53         
54                 var diff = d.getHours() - d.getUTCHours();
55                 var hourDiff = Math.abs(diff);
56                 
57                 var minuteUTC = d.getUTCMinutes();
58                 var minute = d.getMinutes();
59                 
60                 if (minute != minuteUTC && minuteUTC < 30 && diff < 0)
61                         hourDiff--;
62                         
63                 if (minute != minuteUTC && minuteUTC > 30 && diff > 0)
64                         hourDiff--;
65                 
66                 var minuteDiff;
67                 if (minute != minuteUTC)
68                         minuteDiff = ":30";
69                 else
70                         minuteDiff = ":00";
71                 
72                 var timezone;
73                 if (hourDiff < 10)
74                         timezone = "0" + hourDiff + minuteDiff;
75                 
76                 else
77                         timezone = "" + hourDiff + minuteDiff;
78
79                 if (diff < 0)
80                         timezone = "-" + timezone;
81                 
82                 else
83                         timezone = "+" + timezone;
84                 
85                 s = s + timezone;
86         }
87         
88         return s;
89 };
90
91
92 /** 
93  * @function
94  * 
95  * @description Formats a JavaScript Date object as an ISO-8601 date string.
96  *
97  * @param {Date} [d] A valid JavaScript Date object. If undefined the current 
98  *        date will be used.
99  *
100  * @returns {String} The date formatted as YYYY-MM-DD.
101  */
102 jsworld.formatIsoDate = function(d) {
103
104         if (typeof d === "undefined")
105                 d = new Date(); // now
106         
107         var year = d.getFullYear();
108         var month = d.getMonth() + 1;
109         var day = d.getDate();
110         
111         return year + "-" + jsworld._zeroPad(month, 2) + "-" + jsworld._zeroPad(day, 2);
112 };
113
114
115 /** 
116  * @function
117  * 
118  * @description Formats a JavaScript Date object as an ISO-8601 time string.
119  *
120  * @param {Date} [d] A valid JavaScript Date object. If undefined the current 
121  *        time will be used.
122  *
123  * @returns {String} The time formatted as HH:MM:SS.
124  */
125 jsworld.formatIsoTime = function(d) {
126
127         if (typeof d === "undefined")
128                 d = new Date(); // now
129         
130         var hour = d.getHours();
131         var minute = d.getMinutes();
132         var second = d.getSeconds();
133         
134         return jsworld._zeroPad(hour, 2) + ":" + jsworld._zeroPad(minute, 2) + ":" + jsworld._zeroPad(second, 2);
135 };
136
137
138 /** 
139  * @function
140  * 
141  * @description Parses an ISO-8601 formatted date/time string to a JavaScript 
142  * Date object.
143  *
144  * @param {String} isoDateTimeVal An ISO-8601 formatted date/time string.
145  *
146  * <p>Accepted formats:
147  *
148  * <ul>
149  *     <li>YYYY-MM-DD HH:MM:SS
150  *     <li>YYYYMMDD HHMMSS
151  *     <li>YYYY-MM-DD HHMMSS
152  *     <li>YYYYMMDD HH:MM:SS
153  * </ul>
154  *
155  * @returns {Date} The corresponding Date object.
156  *
157  * @throws Error on a badly formatted date/time string or on a invalid date.
158  */
159 jsworld.parseIsoDateTime = function(isoDateTimeVal) {
160
161         if (typeof isoDateTimeVal != "string")
162                 throw "Error: The parameter must be a string";
163
164         // First, try to match "YYYY-MM-DD HH:MM:SS" format
165         var matches = isoDateTimeVal.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)/);
166         
167         // If unsuccessful, try to match "YYYYMMDD HHMMSS" format
168         if (matches === null)
169                 matches = isoDateTimeVal.match(/^(\d\d\d\d)(\d\d)(\d\d)[T ](\d\d)(\d\d)(\d\d)/);
170                 
171         // ... try to match "YYYY-MM-DD HHMMSS" format
172         if (matches === null)
173                 matches = isoDateTimeVal.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d)(\d\d)(\d\d)/);
174         
175         // ... try to match "YYYYMMDD HH:MM:SS" format
176         if (matches === null)
177                 matches = isoDateTimeVal.match(/^(\d\d\d\d)-(\d\d)-(\d\d)[T ](\d\d):(\d\d):(\d\d)/);
178
179         // Report bad date/time string
180         if (matches === null)
181                 throw "Error: Invalid ISO-8601 date/time string";
182
183         // Force base 10 parse int as some values may have leading zeros!
184         // (to avoid implicit octal base conversion)
185         var year = parseInt(matches[1], 10);
186         var month = parseInt(matches[2], 10);
187         var day = parseInt(matches[3], 10);
188         
189         var hour = parseInt(matches[4], 10);
190         var mins = parseInt(matches[5], 10);
191         var secs = parseInt(matches[6], 10);
192         
193         // Simple value range check, leap years not checked
194         // Note: the originial ISO time spec for leap hours (24:00:00) and seconds (00:00:60) is not supported
195         if (month < 1 || month > 12 ||
196             day   < 1 || day   > 31 ||
197             hour  < 0 || hour  > 23 ||
198             mins  < 0 || mins  > 59 ||
199             secs  < 0 || secs  > 59    )
200             
201                 throw "Error: Invalid ISO-8601 date/time value";
202
203         var d = new Date(year, month - 1, day, hour, mins, secs);
204         
205         // Check if the input date was valid 
206         // (JS Date does automatic forward correction)
207         if (d.getDate() != day || d.getMonth() +1 != month)
208                 throw "Error: Invalid date";
209         
210         return d;
211 };
212
213
214 /** 
215  * @function
216  * 
217  * @description Parses an ISO-8601 formatted date string to a JavaScript 
218  * Date object.
219  *
220  * @param {String} isoDateVal An ISO-8601 formatted date string.
221  *
222  * <p>Accepted formats:
223  *
224  * <ul>
225  *     <li>YYYY-MM-DD
226  *     <li>YYYYMMDD
227  * </ul>
228  *
229  * @returns {Date} The corresponding Date object.
230  *
231  * @throws Error on a badly formatted date string or on a invalid date.
232  */
233 jsworld.parseIsoDate = function(isoDateVal) {
234
235         if (typeof isoDateVal != "string")
236                 throw "Error: The parameter must be a string";
237
238         // First, try to match "YYYY-MM-DD" format
239         var matches = isoDateVal.match(/^(\d\d\d\d)-(\d\d)-(\d\d)/);
240         
241         // If unsuccessful, try to match "YYYYMMDD" format
242         if (matches === null)
243                 matches = isoDateVal.match(/^(\d\d\d\d)(\d\d)(\d\d)/);
244
245         // Report bad date/time string
246         if (matches === null)
247                 throw "Error: Invalid ISO-8601 date string";
248
249         // Force base 10 parse int as some values may have leading zeros!
250         // (to avoid implicit octal base conversion)
251         var year = parseInt(matches[1], 10);
252         var month = parseInt(matches[2], 10);
253         var day = parseInt(matches[3], 10);
254         
255         // Simple value range check, leap years not checked
256         if (month < 1 || month > 12 ||
257             day   < 1 || day   > 31    )
258             
259                 throw "Error: Invalid ISO-8601 date value";
260
261         var d = new Date(year, month - 1, day);
262         
263         // Check if the input date was valid 
264         // (JS Date does automatic forward correction)
265         if (d.getDate() != day || d.getMonth() +1 != month)
266                 throw "Error: Invalid date";
267         
268         return d;
269 };
270
271
272 /** 
273  * @function
274  * 
275  * @description Parses an ISO-8601 formatted time string to a JavaScript 
276  * Date object.
277  *
278  * @param {String} isoTimeVal An ISO-8601 formatted time string.
279  *
280  * <p>Accepted formats:
281  *
282  * <ul>
283  *     <li>HH:MM:SS
284  *     <li>HHMMSS
285  * </ul>
286  *
287  * @returns {Date} The corresponding Date object, with year, month and day set
288  *          to zero.
289  *
290  * @throws Error on a badly formatted time string.
291  */
292 jsworld.parseIsoTime = function(isoTimeVal) {
293
294         if (typeof isoTimeVal != "string")
295                 throw "Error: The parameter must be a string";
296
297         // First, try to match "HH:MM:SS" format
298         var matches = isoTimeVal.match(/^(\d\d):(\d\d):(\d\d)/);
299         
300         // If unsuccessful, try to match "HHMMSS" format
301         if (matches === null)
302                 matches = isoTimeVal.match(/^(\d\d)(\d\d)(\d\d)/);
303         
304         // Report bad date/time string
305         if (matches === null)
306                 throw "Error: Invalid ISO-8601 date/time string";
307
308         // Force base 10 parse int as some values may have leading zeros!
309         // (to avoid implicit octal base conversion)
310         var hour = parseInt(matches[1], 10);
311         var mins = parseInt(matches[2], 10);
312         var secs = parseInt(matches[3], 10);
313         
314         // Simple value range check, leap years not checked
315         if (hour < 0 || hour > 23 ||
316             mins < 0 || mins > 59 ||
317             secs < 0 || secs > 59    )
318             
319                 throw "Error: Invalid ISO-8601 time value";
320
321         return new Date(0, 0, 0, hour, mins, secs);
322 };
323
324
325 /**
326  * @private
327  *
328  * @description Trims leading and trailing whitespace from a string.
329  *
330  * <p>Used non-regexp the method from http://blog.stevenlevithan.com/archives/faster-trim-javascript
331  *
332  * @param {String} str The string to trim.
333  *
334  * @returns {String} The trimmed string.
335  */
336 jsworld._trim = function(str) {
337
338         var whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
339         
340         for (var i = 0; i < str.length; i++) {
341         
342                 if (whitespace.indexOf(str.charAt(i)) === -1) {
343                         str = str.substring(i);
344                         break;
345                 }
346         }
347         
348         for (i = str.length - 1; i >= 0; i--) {
349                 if (whitespace.indexOf(str.charAt(i)) === -1) {
350                         str = str.substring(0, i + 1);
351                         break;
352                 }
353         }
354         
355         return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
356 };
357
358
359
360 /**
361  * @private
362  *
363  * @description Returns true if the argument represents a decimal number.
364  *
365  * @param {Number|String} arg The argument to test.
366  *
367  * @returns {Boolean} true if the argument represents a decimal number, 
368  *          otherwise false.
369  */
370 jsworld._isNumber = function(arg) {
371
372         if (typeof arg == "number")
373                 return true;
374         
375         if (typeof arg != "string")
376                 return false;
377         
378         // ensure string
379         var s = arg + "";
380         
381         return (/^-?(\d+|\d*\.\d+)$/).test(s);
382 };
383
384
385 /**
386  * @private
387  *
388  * @description Returns true if the argument represents a decimal integer.
389  *
390  * @param {Number|String} arg The argument to test.
391  *
392  * @returns {Boolean} true if the argument represents an integer, otherwise 
393  *          false.
394  */
395 jsworld._isInteger = function(arg) {
396
397         if (typeof arg != "number" && typeof arg != "string")
398                 return false;
399
400         // convert to string
401         var s = arg + "";
402
403         return (/^-?\d+$/).test(s);
404 };
405
406
407 /**
408  * @private
409  *
410  * @description Returns true if the argument represents a decimal float.
411  *
412  * @param {Number|String} arg The argument to test.
413  *
414  * @returns {Boolean} true if the argument represents a float, otherwise false.
415  */
416 jsworld._isFloat = function(arg) {
417
418         if (typeof arg != "number" && typeof arg != "string")
419                 return false;
420         
421         // convert to string
422         var s = arg + "";
423         
424         return (/^-?\.\d+?$/).test(s);
425 };
426
427
428 /** 
429  * @private
430  *
431  * @description Checks if the specified formatting option is contained 
432  * within the options string.
433  * 
434  * @param {String} option The option to search for.
435  * @param {String} optionsString The options string.
436  *
437  * @returns {Boolean} true if the flag is found, else false
438  */
439 jsworld._hasOption = function(option, optionsString) {
440
441         if (typeof option != "string" || typeof optionsString != "string")
442                 return false;
443
444         if (optionsString.indexOf(option) != -1)
445                 return true;
446         else
447                 return false;
448 };
449
450
451 /**
452  * @private
453  *
454  * @description String replacement function.
455  *
456  * @param {String} s The string to work on.
457  * @param {String} target The string to search for.
458  * @param {String} replacement The replacement.
459  *
460  * @returns {String} The new string.
461  */
462 jsworld._stringReplaceAll = function(s, target, replacement) {
463
464         var out;
465
466         if (target.length == 1 && replacement.length == 1) {
467                 // simple char/char case somewhat faster
468                 out = "";
469         
470                 for (var i = 0; i < s.length; i++) {
471                         
472                         if (s.charAt(i) == target.charAt(0))
473                                 out = out + replacement.charAt(0);
474                         else
475                                 out = out + s.charAt(i);
476                 }
477                 
478                 return out;
479         }
480         else {
481                 // longer target and replacement strings
482                 out = s;
483
484                 var index = out.indexOf(target);
485                 
486                 while (index != -1) {
487                 
488                         out = out.replace(target, replacement);
489                         
490                         index = out.indexOf(target);
491                 }
492
493                 return out;
494         }
495 };
496
497
498 /**
499  * @private
500  *
501  * @description Tests if a string starts with the specified substring.
502  *
503  * @param {String} testedString The string to test.
504  * @param {String} sub The string to match.
505  *
506  * @returns {Boolean} true if the test succeeds.
507  */
508 jsworld._stringStartsWith = function (testedString, sub) {
509         
510         if (testedString.length < sub.length)
511                 return false;
512         
513         for (var i = 0; i < sub.length; i++) {
514                 if (testedString.charAt(i) != sub.charAt(i))
515                         return false;
516         }
517         
518         return true;
519 };
520
521
522 /** 
523  * @private
524  *
525  * @description Gets the requested precision from an options string.
526  *
527  * <p>Example: ".3" returns 3 decimal places precision.
528  *
529  * @param {String} optionsString The options string.
530  *
531  * @returns {integer Number} The requested precision, -1 if not specified.
532  */
533 jsworld._getPrecision = function (optionsString) {
534
535         if (typeof optionsString != "string")
536                 return -1;
537
538         var m = optionsString.match(/\.(\d)/);
539         if (m)
540                 return parseInt(m[1], 10);
541         else
542                 return -1;
543 };
544
545
546 /** 
547  * @private
548  *
549  * @description Takes a decimal numeric amount (optionally as string) and 
550  * returns its integer and fractional parts packed into an object.
551  *
552  * @param {Number|String} amount The amount, e.g. "123.45" or "-56.78"
553  * 
554  * @returns {object} Parsed amount object with properties:
555  *         {String} integer  : the integer part
556  *         {String} fraction : the fraction part
557  */
558 jsworld._splitNumber = function (amount) {
559
560         if (typeof amount == "number")
561                 amount = amount + "";
562
563         var obj = {};
564
565         // remove negative sign
566         if (amount.charAt(0) == "-")
567                 amount = amount.substring(1);
568
569         // split amount into integer and decimal parts
570         var amountParts = amount.split(".");
571         if (!amountParts[1])
572                 amountParts[1] = ""; // we need "" instead of null
573
574         obj.integer = amountParts[0];
575         obj.fraction = amountParts[1];
576
577         return obj;
578 };
579
580
581 /** 
582  * @private
583  *
584  * @description Formats the integer part using the specified grouping
585  * and thousands separator.
586  * 
587  * @param {String} intPart The integer part of the amount, as string.
588  * @param {String} grouping The grouping definition.
589  * @param {String} thousandsSep The thousands separator.
590  * 
591  * @returns {String} The formatted integer part.
592  */
593 jsworld._formatIntegerPart = function (intPart, grouping, thousandsSep) {
594
595         // empty separator string? no grouping?
596         // -> return immediately with no formatting!
597         if (thousandsSep == "" || grouping == "-1")
598                 return intPart;
599
600         // turn the semicolon-separated string of integers into an array
601         var groupSizes = grouping.split(";");
602
603         // the formatted output string
604         var out = "";
605
606         // the intPart string position to process next,
607         // start at string end, e.g. "10000000<starts here"
608         var pos = intPart.length;
609
610         // process the intPart string backwards
611         //     "1000000000"
612         //            <---\ direction
613         var size;
614         
615         while (pos > 0) {
616
617                 // get next group size (if any, otherwise keep last)
618                 if (groupSizes.length > 0)
619                         size = parseInt(groupSizes.shift(), 10);
620
621                 // int parse error?
622                 if (isNaN(size))
623                         throw "Error: Invalid grouping";
624
625                 // size is -1? -> no more grouping, so just copy string remainder
626                 if (size == -1) {
627                         out = intPart.substring(0, pos) + out;
628                         break;
629                 }
630
631                 pos -= size; // move to next sep. char. position
632
633                 // position underrun? -> just copy string remainder
634                 if (pos < 1) {
635                         out = intPart.substring(0, pos + size) + out;
636                         break;
637                 }
638
639                 // extract group and apply sep. char.
640                 out = thousandsSep + intPart.substring(pos, pos + size) + out;
641         }
642
643         return out;
644 };
645         
646         
647 /** 
648  * @private
649  *
650  * @description Formats the fractional part to the specified decimal 
651  * precision.
652  *
653  * @param {String} fracPart The fractional part of the amount
654  * @param {integer Number} precision The desired decimal precision
655  *
656  * @returns {String} The formatted fractional part.
657  */
658 jsworld._formatFractionPart = function (fracPart, precision) {
659
660         // append zeroes up to precision if necessary
661         for (var i=0; fracPart.length < precision; i++)
662                 fracPart = fracPart + "0";
663
664         return fracPart;
665 };
666
667
668 /** 
669  * @private 
670  *
671  * @desription Converts a number to string and pad it with leading zeroes if the
672  * string is shorter than length.
673  *
674  * @param {integer Number} number The number value subjected to selective padding.
675  * @param {integer Number} length If the number has fewer digits than this length
676  *        apply padding.
677  *
678  * @returns {String} The formatted string.
679  */
680 jsworld._zeroPad = function(number, length) {
681
682         // ensure string
683         var s = number + "";
684
685         while (s.length < length)
686                 s = "0" + s;
687         
688         return s;
689 };
690
691
692 /** 
693  * @private 
694  * @description Converts a number to string and pads it with leading spaces if 
695  * the string is shorter than length.
696  *
697  * @param {integer Number} number The number value subjected to selective padding.
698  * @param {integer Number} length If the number has fewer digits than this length
699  *        apply padding.
700  *
701  * @returns {String} The formatted string.
702  */
703 jsworld._spacePad = function(number, length) {
704
705         // ensure string
706         var s = number + "";
707
708         while (s.length < length)
709                 s = " " + s;
710         
711         return s;
712 };
713
714
715
716 /**
717  * @class
718  * Represents a POSIX-style locale with its numeric, monetary and date/time 
719  * properties. Also provides a set of locale helper methods.
720  *
721  * <p>The locale properties follow the POSIX standards:
722  *
723  * <ul>
724  *     <li><a href="http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap07.html#tag_07_03_04">POSIX LC_NUMERIC</a>
725  *     <li><a href="http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_03_03">POSIX LC_MONETARY</a>
726  *     <li><a href="http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap07.html#tag_07_03_05">POSIX LC_TIME</a>
727  * </ul>
728  *
729  * @public
730  * @constructor
731  * @description Creates a new locale object (POSIX-style) with the specified
732  * properties.
733  *
734  * @param {object} properties An object containing the raw locale properties:
735  *
736  *        @param {String} properties.decimal_point
737  *
738  *        A string containing the symbol that shall be used as the decimal
739  *        delimiter (radix character) in numeric, non-monetary formatted
740  *        quantities. This property cannot be omitted and cannot be set to the
741  *        empty string.
742  *
743  *
744  *        @param {String} properties.thousands_sep
745  *
746  *        A string containing the symbol that shall be used as a separator for
747  *        groups of digits to the left of the decimal delimiter in numeric,
748  *        non-monetary formatted monetary quantities.
749  *
750  *
751  *        @param {String} properties.grouping
752  *
753  *        Defines the size of each group of digits in formatted non-monetary
754  *        quantities. The operand is a sequence of integers separated by
755  *        semicolons. Each integer specifies the number of digits in each group,
756  *        with the initial integer defining the size of the group immediately
757  *        preceding the decimal delimiter, and the following integers defining
758  *        the preceding groups. If the last integer is not -1, then the size of
759  *        the previous group (if any) shall be repeatedly used for the
760  *        remainder of the digits. If the last integer is -1, then no further
761  *        grouping shall be performed.
762  *
763  *
764  *        @param {String} properties.int_curr_symbol
765  *
766  *        The first three letters signify the ISO-4217 currency code,
767  *        the fourth letter is the international symbol separation character
768  *        (normally a space).
769  *
770  *
771  *        @param {String} properties.currency_symbol
772  *
773  *        The local shorthand currency symbol, e.g. "$" for the en_US locale
774  *
775  *
776  *        @param {String} properties.mon_decimal_point
777  *
778  *        The symbol to be used as the decimal delimiter (radix character)
779  *
780  *
781  *        @param {String} properties.mon_thousands_sep
782  *
783  *        The symbol to be used as a separator for groups of digits to the
784  *        left of the decimal delimiter.
785  *
786  *
787  *        @param {String} properties.mon_grouping
788  *
789  *        A string that defines the size of each group of digits. The
790  *        operand is a sequence of integers separated by semicolons (";").
791  *        Each integer specifies the number of digits in each group, with the
792  *        initial integer defining the size of the group preceding the
793  *        decimal delimiter, and the following integers defining the
794  *        preceding groups. If the last integer is not -1, then the size of
795  *        the previous group (if any) must be repeatedly used for the
796  *        remainder of the digits. If the last integer is -1, then no
797  *        further grouping is to be performed.
798  *
799  *
800  *        @param {String} properties.positive_sign
801  *
802  *        The string to indicate a non-negative monetary amount.
803  *
804  *
805  *        @param {String} properties.negative_sign
806  *
807  *        The string to indicate a negative monetary amount.
808  *
809  *
810  *        @param {integer Number} properties.frac_digits
811  *
812  *        An integer representing the number of fractional digits (those to
813  *        the right of the decimal delimiter) to be written in a formatted
814  *        monetary quantity using currency_symbol.
815  *
816  *
817  *        @param {integer Number} properties.int_frac_digits
818  *
819  *        An integer representing the number of fractional digits (those to
820  *        the right of the decimal delimiter) to be written in a formatted
821  *        monetary quantity using int_curr_symbol.
822  *
823  *
824  *        @param {integer Number} properties.p_cs_precedes
825  *
826  *        An integer set to 1 if the currency_symbol precedes the value for a
827  *        monetary quantity with a non-negative value, and set to 0 if the
828  *        symbol succeeds the value.
829  *
830  *
831  *        @param {integer Number} properties.n_cs_precedes
832  *
833  *        An integer set to 1 if the currency_symbol precedes the value for a
834  *        monetary quantity with a negative value, and set to 0 if the symbol
835  *        succeeds the value.
836  *
837  *
838  *        @param {integer Number} properties.p_sep_by_space
839  *
840  *        Set to a value indicating the separation of the currency_symbol,
841  *        the sign string, and the value for a non-negative formatted monetary
842  *        quantity:
843  *        
844  *             <p>0 No space separates the currency symbol and value.</p>
845  *
846  *             <p>1 If the currency symbol and sign string are adjacent, a space
847  *                  separates them from the value; otherwise, a space separates
848  *                  the currency symbol from the value.</p>
849  *
850  *             <p>2 If the currency symbol and sign string are adjacent, a space
851  *                  separates them; otherwise, a space separates the sign string
852  *                  from the value.</p>
853  *
854  *
855  *        @param {integer Number} properties.n_sep_by_space
856  *
857  *        Set to a value indicating the separation of the currency_symbol,
858  *        the sign string, and the value for a negative formatted monetary
859  *        quantity. Rules same as for p_sep_by_space.
860  *
861  *
862  *        @param {integer Number} properties.p_sign_posn
863  *
864  *        An integer set to a value indicating the positioning of the
865  *        positive_sign for a monetary quantity with a non-negative value:
866  *      
867  *             <p>0 Parentheses enclose the quantity and the currency_symbol.</p>
868  *
869  *             <p>1 The sign string precedes the quantity and the currency_symbol.</p>
870  *
871  *             <p>2 The sign string succeeds the quantity and the currency_symbol.</p>
872  *
873  *             <p>3 The sign string precedes the currency_symbol.</p>
874  *
875  *             <p>4 The sign string succeeds the currency_symbol.</p>
876  *
877  *
878  *        @param {integer Number} properties.n_sign_posn
879  *
880  *        An integer set to a value indicating the positioning of the
881  *        negative_sign for a negative formatted monetary quantity. Rules same
882  *        as for p_sign_posn.
883  *
884  *
885  *        @param {integer Number} properties.int_p_cs_precedes
886  *
887  *        An integer set to 1 if the int_curr_symbol precedes the value for a
888  *        monetary quantity with a non-negative value, and set to 0 if the
889  *        symbol succeeds the value.
890  *
891  *
892  *        @param {integer Number} properties.int_n_cs_precedes
893  *
894  *        An integer set to 1 if the int_curr_symbol precedes the value for a
895  *        monetary quantity with a negative value, and set to 0 if the symbol
896  *        succeeds the value.
897  *
898  *
899  *        @param {integer Number} properties.int_p_sep_by_space
900  *
901  *        Set to a value indicating the separation of the int_curr_symbol,
902  *        the sign string, and the value for a non-negative internationally
903  *        formatted monetary quantity. Rules same as for p_sep_by_space.
904  *
905  *
906  *        @param {integer Number} properties.int_n_sep_by_space
907  *
908  *        Set to a value indicating the separation of the int_curr_symbol,
909  *        the sign string, and the value for a negative internationally
910  *        formatted monetary quantity. Rules same as for p_sep_by_space.
911  *
912  *
913  *        @param {integer Number} properties.int_p_sign_posn
914  *
915  *        An integer set to a value indicating the positioning of the
916  *        positive_sign for a positive monetary quantity formatted with the
917  *        international format. Rules same as for p_sign_posn.
918  *
919  *
920  *        @param {integer Number} properties.int_n_sign_posn
921  *
922  *        An integer set to a value indicating the positioning of the
923  *        negative_sign for a negative monetary quantity formatted with the
924  *        international format. Rules same as for p_sign_posn.
925  *
926  *
927  *        @param {String[] | String} properties.abday
928  *
929  *        The abbreviated weekday names, corresponding to the %a conversion
930  *        specification. The property must be either an array of 7 strings or
931  *        a string consisting of 7 semicolon-separated substrings, each 
932  *        surrounded by double-quotes. The first must be the abbreviated name 
933  *        of the day corresponding to Sunday, the second the abbreviated name 
934  *        of the day corresponding to Monday, and so on.
935  *        
936  *
937  *        @param {String[] | String} properties.day
938  *
939  *        The full weekday names, corresponding to the %A conversion
940  *        specification. The property must be either an array of 7 strings or
941  *        a string consisting of 7 semicolon-separated substrings, each 
942  *        surrounded by double-quotes. The first must be the full name of the 
943  *        day corresponding to Sunday, the second the full name of the day 
944  *        corresponding to Monday, and so on.
945  *        
946  *
947  *        @param {String[] | String} properties.abmon
948  *
949  *        The abbreviated month names, corresponding to the %b conversion
950  *        specification. The property must be either an array of 12 strings or
951  *        a string consisting of 12 semicolon-separated substrings, each 
952  *        surrounded by double-quotes. The first must be the abbreviated name 
953  *        of the first month of the year (January), the second the abbreviated 
954  *        name of the second month, and so on.
955  *        
956  *
957  *        @param {String[] | String} properties.mon
958  *
959  *        The full month names, corresponding to the %B conversion
960  *        specification. The property must be either an array of 12 strings or
961  *        a string consisting of 12 semicolon-separated substrings, each 
962  *        surrounded by double-quotes. The first must be the full name of the 
963  *        first month of the year (January), the second the full name of the second 
964  *        month, and so on.
965  *        
966  *
967  *        @param {String} properties.d_fmt
968  *
969  *        The appropriate date representation. The string may contain any
970  *        combination of characters and conversion specifications (%<char>).
971  *        
972  *
973  *        @param {String} properties.t_fmt
974  *
975  *        The appropriate time representation. The string may contain any
976  *        combination of characters and conversion specifications (%<char>).
977  *        
978  *
979  *        @param {String} properties.d_t_fmt
980  *
981  *        The appropriate date and time representation. The string may contain
982  *        any combination of characters and conversion specifications (%<char>).
983  *
984  *
985  *        @param {String[] | String} properties.am_pm
986  *
987  *        The appropriate representation of the ante-meridiem and post-meridiem
988  *        strings, corresponding to the %p conversion specification. The property 
989  *        must be either an array of 2 strings or a string consisting of 2 
990  *        semicolon-separated substrings, each surrounded by double-quotes. 
991  *        The first string must represent the ante-meridiem designation, the 
992  *        last string the post-meridiem designation.
993  *
994  *
995  * @throws @throws Error on a undefined or invalid locale property.
996  */
997 jsworld.Locale = function(properties) {
998         
999         
1000         /**
1001          * @private
1002          *
1003          * @description Identifies the class for internal library purposes.
1004          */
1005         this._className = "jsworld.Locale";
1006         
1007         
1008         /** 
1009          * @private 
1010          *
1011          * @description Parses a day or month name definition list, which
1012          * could be a ready JS array, e.g. ["Mon", "Tue", "Wed"...] or
1013          * it could be a string formatted according to the classic POSIX
1014          * definition e.g. "Mon";"Tue";"Wed";...
1015          *
1016          * @param {String[] | String} namesAn array or string defining 
1017          *        the week/month names.
1018          * @param {integer Number} expectedItems The number of expected list
1019          *        items, e.g. 7 for weekdays, 12 for months.
1020          *
1021          * @returns {String[]} The parsed (and checked) items.
1022          * 
1023          * @throws Error on missing definition, unexpected item count or
1024          *         missing double-quotes.
1025          */
1026         this._parseList = function(names, expectedItems) {
1027                 
1028                 var array = [];
1029                 
1030                 if (names == null) {
1031                         throw "Names not defined";
1032                 }
1033                 else if (typeof names == "object") {
1034                         // we got a ready array
1035                         array = names;
1036                 }
1037                 else if (typeof names == "string") {
1038                         // we got the names in the classic POSIX form, do parse
1039                         array = names.split(";", expectedItems);
1040                 
1041                         for (var i = 0; i < array.length; i++) {
1042                                 // check for and strip double quotes
1043                                 if (array[i][0] == "\"" && array[i][array[i].length - 1] == "\"")
1044                                         array[i] = array[i].slice(1, -1);
1045                                 else
1046                                         throw "Missing double quotes";
1047                         }
1048                 }
1049                 else {
1050                         throw "Names must be an array or a string";
1051                 }
1052                 
1053                 if (array.length != expectedItems)
1054                         throw "Expected " + expectedItems + " items, got " + array.length;
1055                 
1056                 return array;
1057         };
1058         
1059         
1060         /**
1061          * @private
1062          *
1063          * @description Validates a date/time format string, such as "H:%M:%S". 
1064          * Checks that the argument is of type "string" and is not empty.
1065          *
1066          * @param {String} formatString The format string.
1067          *
1068          * @returns {String} The validated string.
1069          *
1070          * @throws Error on null or empty string.
1071          */
1072         this._validateFormatString = function(formatString) {
1073                 
1074                 if (typeof formatString == "string" && formatString.length > 0)
1075                         return formatString;
1076                 else
1077                         throw "Empty or no string";
1078         };
1079         
1080         
1081         // LC_NUMERIC
1082
1083         if (properties == null || typeof properties != "object")
1084                 throw "Error: Invalid/missing locale properties";
1085         
1086         
1087         if (typeof properties.decimal_point != "string")
1088                 throw "Error: Invalid/missing decimal_point property";
1089         
1090         this.decimal_point = properties.decimal_point;
1091         
1092         
1093         if (typeof properties.thousands_sep != "string")
1094                 throw "Error: Invalid/missing thousands_sep property";
1095         
1096         this.thousands_sep = properties.thousands_sep;
1097         
1098         
1099         if (typeof properties.grouping != "string")
1100                 throw "Error: Invalid/missing grouping property";
1101         
1102         this.grouping = properties.grouping;
1103         
1104         
1105         // LC_MONETARY
1106         
1107         if (typeof properties.int_curr_symbol != "string")
1108                 throw "Error: Invalid/missing int_curr_symbol property";
1109         
1110         if (! /[A-Za-z]{3}.?/.test(properties.int_curr_symbol))
1111                 throw "Error: Invalid int_curr_symbol property";
1112         
1113         this.int_curr_symbol = properties.int_curr_symbol;
1114         
1115
1116         if (typeof properties.currency_symbol != "string")
1117                 throw "Error: Invalid/missing currency_symbol property";
1118         
1119         this.currency_symbol = properties.currency_symbol;
1120         
1121         
1122         if (typeof properties.frac_digits != "number" && properties.frac_digits < 0)
1123                 throw "Error: Invalid/missing frac_digits property";
1124         
1125         this.frac_digits = properties.frac_digits;
1126         
1127         
1128         // may be empty string/null for currencies with no fractional part
1129         if (properties.mon_decimal_point === null || properties.mon_decimal_point == "") {
1130         
1131                 if (this.frac_digits > 0)
1132                         throw "Error: Undefined mon_decimal_point property";
1133                 else
1134                         properties.mon_decimal_point = "";
1135         }
1136         
1137         if (typeof properties.mon_decimal_point != "string")
1138                 throw "Error: Invalid/missing mon_decimal_point property";
1139         
1140         this.mon_decimal_point = properties.mon_decimal_point;
1141         
1142         
1143         if (typeof properties.mon_thousands_sep != "string")
1144                 throw "Error: Invalid/missing mon_thousands_sep property";
1145         
1146         this.mon_thousands_sep = properties.mon_thousands_sep;
1147         
1148         
1149         if (typeof properties.mon_grouping != "string")
1150                 throw "Error: Invalid/missing mon_grouping property";
1151         
1152         this.mon_grouping = properties.mon_grouping;
1153         
1154         
1155         if (typeof properties.positive_sign != "string")
1156                 throw "Error: Invalid/missing positive_sign property";
1157         
1158         this.positive_sign = properties.positive_sign;
1159         
1160         
1161         if (typeof properties.negative_sign != "string")
1162                 throw "Error: Invalid/missing negative_sign property";
1163         
1164         this.negative_sign = properties.negative_sign;
1165         
1166         
1167         
1168         if (properties.p_cs_precedes !== 0 && properties.p_cs_precedes !== 1)
1169                 throw "Error: Invalid/missing p_cs_precedes property, must be 0 or 1";
1170         
1171         this.p_cs_precedes = properties.p_cs_precedes;
1172         
1173         
1174         if (properties.n_cs_precedes !== 0 && properties.n_cs_precedes !== 1)
1175                 throw "Error: Invalid/missing n_cs_precedes, must be 0 or 1";
1176         
1177         this.n_cs_precedes = properties.n_cs_precedes;
1178         
1179
1180         if (properties.p_sep_by_space !== 0 &&
1181             properties.p_sep_by_space !== 1 &&
1182             properties.p_sep_by_space !== 2)
1183                 throw "Error: Invalid/missing p_sep_by_space property, must be 0, 1 or 2";
1184         
1185         this.p_sep_by_space = properties.p_sep_by_space;
1186         
1187
1188         if (properties.n_sep_by_space !== 0 &&
1189             properties.n_sep_by_space !== 1 &&
1190             properties.n_sep_by_space !== 2)
1191                 throw "Error: Invalid/missing n_sep_by_space property, must be 0, 1, or 2";
1192         
1193         this.n_sep_by_space = properties.n_sep_by_space;
1194         
1195
1196         if (properties.p_sign_posn !== 0 &&
1197             properties.p_sign_posn !== 1 &&
1198             properties.p_sign_posn !== 2 &&
1199             properties.p_sign_posn !== 3 &&
1200             properties.p_sign_posn !== 4)
1201                 throw "Error: Invalid/missing p_sign_posn property, must be 0, 1, 2, 3 or 4";
1202         
1203         this.p_sign_posn = properties.p_sign_posn;
1204
1205
1206         if (properties.n_sign_posn !== 0 &&
1207             properties.n_sign_posn !== 1 &&
1208             properties.n_sign_posn !== 2 &&
1209             properties.n_sign_posn !== 3 &&
1210             properties.n_sign_posn !== 4)
1211                 throw "Error: Invalid/missing n_sign_posn property, must be 0, 1, 2, 3 or 4";
1212         
1213         this.n_sign_posn = properties.n_sign_posn;
1214
1215
1216         if (typeof properties.int_frac_digits != "number" && properties.int_frac_digits < 0)
1217                 throw "Error: Invalid/missing int_frac_digits property";
1218
1219         this.int_frac_digits = properties.int_frac_digits;
1220         
1221         
1222         if (properties.int_p_cs_precedes !== 0 && properties.int_p_cs_precedes !== 1)
1223                 throw "Error: Invalid/missing int_p_cs_precedes property, must be 0 or 1";
1224         
1225         this.int_p_cs_precedes = properties.int_p_cs_precedes;
1226         
1227         
1228         if (properties.int_n_cs_precedes !== 0 && properties.int_n_cs_precedes !== 1)
1229                 throw "Error: Invalid/missing int_n_cs_precedes property, must be 0 or 1";
1230         
1231         this.int_n_cs_precedes = properties.int_n_cs_precedes;
1232         
1233
1234         if (properties.int_p_sep_by_space !== 0 &&
1235             properties.int_p_sep_by_space !== 1 &&
1236             properties.int_p_sep_by_space !== 2)
1237                 throw "Error: Invalid/missing int_p_sep_by_spacev, must be 0, 1 or 2";
1238                 
1239         this.int_p_sep_by_space = properties.int_p_sep_by_space;
1240
1241
1242         if (properties.int_n_sep_by_space !== 0 &&
1243             properties.int_n_sep_by_space !== 1 &&
1244             properties.int_n_sep_by_space !== 2)
1245                 throw "Error: Invalid/missing int_n_sep_by_space property, must be 0, 1, or 2";
1246         
1247         this.int_n_sep_by_space = properties.int_n_sep_by_space;
1248         
1249
1250         if (properties.int_p_sign_posn !== 0 &&
1251             properties.int_p_sign_posn !== 1 &&
1252             properties.int_p_sign_posn !== 2 &&
1253             properties.int_p_sign_posn !== 3 &&
1254             properties.int_p_sign_posn !== 4)
1255                 throw "Error: Invalid/missing int_p_sign_posn property, must be 0, 1, 2, 3 or 4";
1256         
1257         this.int_p_sign_posn = properties.int_p_sign_posn;
1258         
1259         
1260         if (properties.int_n_sign_posn !== 0 &&
1261             properties.int_n_sign_posn !== 1 &&
1262             properties.int_n_sign_posn !== 2 &&
1263             properties.int_n_sign_posn !== 3 &&
1264             properties.int_n_sign_posn !== 4)
1265                 throw "Error: Invalid/missing int_n_sign_posn property, must be 0, 1, 2, 3 or 4";
1266
1267         this.int_n_sign_posn = properties.int_n_sign_posn;
1268         
1269         
1270         // LC_TIME
1271         
1272         if (properties == null || typeof properties != "object")
1273                 throw "Error: Invalid/missing time locale properties";
1274         
1275         
1276         // parse the supported POSIX LC_TIME properties
1277         
1278         // abday
1279         try  {
1280                 this.abday = this._parseList(properties.abday, 7);
1281         }
1282         catch (error) {
1283                 throw "Error: Invalid abday property: " + error;
1284         }
1285         
1286         // day
1287         try {
1288                 this.day = this._parseList(properties.day, 7);
1289         }
1290         catch (error) {
1291                 throw "Error: Invalid day property: " + error;
1292         }
1293         
1294         // abmon
1295         try {
1296                 this.abmon = this._parseList(properties.abmon, 12);
1297         } catch (error) {
1298                 throw "Error: Invalid abmon property: " + error;
1299         }
1300         
1301         // mon
1302         try {
1303                 this.mon = this._parseList(properties.mon, 12);
1304         } catch (error) {
1305                 throw "Error: Invalid mon property: " + error;
1306         }
1307         
1308         // d_fmt
1309         try {
1310                 this.d_fmt = this._validateFormatString(properties.d_fmt);
1311         } catch (error) {
1312                 throw "Error: Invalid d_fmt property: " + error;
1313         }
1314         
1315         // t_fmt
1316         try {
1317                 this.t_fmt = this._validateFormatString(properties.t_fmt);
1318         } catch (error) {
1319                 throw "Error: Invalid t_fmt property: " + error;
1320         }
1321         
1322         // d_t_fmt
1323         try {
1324                 this.d_t_fmt = this._validateFormatString(properties.d_t_fmt);
1325         } catch (error) {
1326                 throw "Error: Invalid d_t_fmt property: " + error;
1327         }
1328         
1329         // am_pm
1330         try {
1331                 var am_pm_strings = this._parseList(properties.am_pm, 2);
1332                 this.am = am_pm_strings[0];
1333                 this.pm = am_pm_strings[1];
1334         } catch (error) {
1335                 // ignore empty/null string errors
1336                 this.am = "";
1337                 this.pm = "";
1338         }
1339         
1340         
1341         /**
1342          * @public
1343          *
1344          * @description Returns the abbreviated name of the specified weekday.
1345          *
1346          * @param {integer Number} [weekdayNum] An integer between 0 and 6. Zero 
1347          *        corresponds to Sunday, one to Monday, etc. If omitted the
1348          *        method will return an array of all abbreviated weekday 
1349          *        names.
1350          *
1351          * @returns {String | String[]} The abbreviated name of the specified weekday
1352          *          or an array of all abbreviated weekday names.
1353          *
1354          * @throws Error on invalid argument.
1355          */
1356         this.getAbbreviatedWeekdayName = function(weekdayNum) {
1357         
1358                 if (typeof weekdayNum == "undefined" || weekdayNum === null)
1359                         return this.abday;
1360                 
1361                 if (! jsworld._isInteger(weekdayNum) || weekdayNum < 0 || weekdayNum > 6)
1362                         throw "Error: Invalid weekday argument, must be an integer [0..6]";
1363                         
1364                 return this.abday[weekdayNum];
1365         };
1366         
1367         
1368         /**
1369          * @public
1370          *
1371          * @description Returns the name of the specified weekday.
1372          *
1373          * @param {integer Number} [weekdayNum] An integer between 0 and 6. Zero 
1374          *        corresponds to Sunday, one to Monday, etc. If omitted the
1375          *        method will return an array of all weekday names.
1376          *
1377          * @returns {String | String[]} The name of the specified weekday or an 
1378          *          array of all weekday names.
1379          *
1380          * @throws Error on invalid argument.
1381          */
1382         this.getWeekdayName = function(weekdayNum) {
1383                 
1384                 if (typeof weekdayNum == "undefined" || weekdayNum === null)
1385                         return this.day;
1386                 
1387                 if (! jsworld._isInteger(weekdayNum) || weekdayNum < 0 || weekdayNum > 6)
1388                         throw "Error: Invalid weekday argument, must be an integer [0..6]";
1389                         
1390                 return this.day[weekdayNum];
1391         };
1392         
1393         
1394         /**
1395          * @public
1396          *
1397          * @description Returns the abbreviated name of the specified month.
1398          *
1399          * @param {integer Number} [monthNum] An integer between 0 and 11. Zero 
1400          *        corresponds to January, one to February, etc. If omitted the
1401          *        method will return an array of all abbreviated month names.
1402          *
1403          * @returns {String | String[]} The abbreviated name of the specified month
1404          *          or an array of all abbreviated month names.
1405          *
1406          * @throws Error on invalid argument.
1407          */
1408         this.getAbbreviatedMonthName = function(monthNum) {
1409         
1410                 if (typeof monthNum == "undefined" || monthNum === null)
1411                         return this.abmon;
1412                 
1413                 if (! jsworld._isInteger(monthNum) || monthNum < 0 || monthNum > 11)
1414                         throw "Error: Invalid month argument, must be an integer [0..11]";
1415                 
1416                 return this.abmon[monthNum];
1417         };
1418         
1419         
1420         /**
1421          * @public
1422          *
1423          * @description Returns the name of the specified month.
1424          *
1425          * @param {integer Number} [monthNum] An integer between 0 and 11. Zero 
1426          *        corresponds to January, one to February, etc. If omitted the
1427          *        method will return an array of all month names.
1428          *
1429          * @returns {String | String[]} The name of the specified month or an array 
1430          *          of all month names.
1431          *
1432          * @throws Error on invalid argument.
1433          */
1434         this.getMonthName = function(monthNum) {
1435         
1436                 if (typeof monthNum == "undefined" || monthNum === null)
1437                         return this.mon;
1438                 
1439                 if (! jsworld._isInteger(monthNum) || monthNum < 0 || monthNum > 11)
1440                         throw "Error: Invalid month argument, must be an integer [0..11]";
1441                 
1442                 return this.mon[monthNum];
1443         };
1444         
1445         
1446         
1447         /** 
1448          * @public
1449          *
1450          * @description Gets the decimal delimiter (radix) character for
1451          * numeric quantities.
1452          *
1453          * @returns {String} The radix character.
1454          */
1455         this.getDecimalPoint = function() {
1456                 
1457                 return this.decimal_point;
1458         };
1459         
1460         
1461         /** 
1462          * @public
1463          *
1464          * @description Gets the local shorthand currency symbol.
1465          *
1466          * @returns {String} The currency symbol.
1467          */
1468         this.getCurrencySymbol = function() {
1469                 
1470                 return this.currency_symbol;
1471         };
1472         
1473         
1474         /**
1475          * @public
1476          *
1477          * @description Gets the internaltion currency symbol (ISO-4217 code).
1478          *
1479          * @returns {String} The international currency symbol.
1480          */
1481         this.getIntCurrencySymbol = function() {
1482         
1483                 return this.int_curr_symbol.substring(0,3);
1484         };
1485         
1486         
1487         /** 
1488          * @public
1489          *
1490          * @description Gets the position of the local (shorthand) currency 
1491          * symbol relative to the amount. Assumes a non-negative amount.
1492          *
1493          * @returns {Boolean} True if the symbol precedes the amount, false if
1494          * the symbol succeeds the amount.
1495          */
1496         this.currencySymbolPrecedes = function() {
1497                 
1498                 if (this.p_cs_precedes == 1)
1499                         return true;
1500                 else
1501                         return false;
1502         };
1503         
1504         
1505         /** 
1506          * @public
1507          *
1508          * @description Gets the position of the international (ISO-4217 code) 
1509          * currency symbol relative to the amount. Assumes a non-negative 
1510          * amount.
1511          *
1512          * @returns {Boolean} True if the symbol precedes the amount, false if
1513          * the symbol succeeds the amount.
1514          */
1515         this.intCurrencySymbolPrecedes = function() {
1516                 
1517                 if (this.int_p_cs_precedes == 1)
1518                         return true;
1519                 else
1520                         return false;
1521
1522         };
1523         
1524         
1525         /** 
1526          * @public
1527          *
1528          * @description Gets the decimal delimiter (radix) for monetary
1529          * quantities.
1530          *
1531          * @returns {String} The radix character.
1532          */
1533         this.getMonetaryDecimalPoint = function() {
1534                 
1535                 return this.mon_decimal_point;
1536         };
1537         
1538         
1539         /** 
1540          * @public
1541          *
1542          * @description Gets the number of fractional digits for local
1543          * (shorthand) symbol formatting.
1544          *
1545          * @returns {integer Number} The number of fractional digits.
1546          */
1547         this.getFractionalDigits = function() {
1548                 
1549                 return this.frac_digits;
1550         };
1551         
1552         
1553         /** 
1554          * @public
1555          *
1556          * @description Gets the number of fractional digits for
1557          * international (ISO-4217 code) formatting.
1558          *
1559          * @returns {integer Number} The number of fractional digits.
1560          */
1561         this.getIntFractionalDigits = function() {
1562                 
1563                 return this.int_frac_digits;
1564         };
1565 };
1566
1567
1568
1569 /** 
1570  * @class 
1571  * Class for localised formatting of numbers.
1572  *
1573  * <p>See: <a href="http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap07.html#tag_07_03_04">
1574  * POSIX LC_NUMERIC</a>.
1575  *
1576  *
1577  * @public
1578  * @constructor 
1579  * @description Creates a new numeric formatter for the specified locale.
1580  *
1581  * @param {jsworld.Locale} locale A locale object specifying the required 
1582  *        POSIX LC_NUMERIC formatting properties.
1583  *
1584  * @throws Error on constructor failure.
1585  */
1586 jsworld.NumericFormatter = function(locale) {
1587
1588         if (typeof locale != "object" || locale._className != "jsworld.Locale")
1589                 throw "Constructor error: You must provide a valid jsworld.Locale instance";
1590         
1591         this.lc = locale;
1592         
1593         
1594         /** 
1595          * @public
1596          * 
1597          * @description Formats a decimal numeric value according to the preset
1598          * locale.
1599          *
1600          * @param {Number|String} number The number to format.
1601          * @param {String} [options] Options to modify the formatted output:
1602          *        <ul>
1603          *            <li>"^"  suppress grouping
1604          *            <li>"+"  force positive sign for positive amounts
1605          *            <li>"~"  suppress positive/negative sign
1606          *            <li>".n" specify decimal precision 'n'
1607          *        </ul>
1608          *
1609          * @returns {String} The formatted number.
1610          *
1611          * @throws "Error: Invalid input" on bad input.
1612          */
1613         this.format = function(number, options) {
1614                 
1615                 if (typeof number == "string")
1616                         number = jsworld._trim(number);
1617                 
1618                 if (! jsworld._isNumber(number))
1619                         throw "Error: The input is not a number";
1620                 
1621                 var floatAmount = parseFloat(number, 10);
1622                 
1623                 // get the required precision
1624                 var reqPrecision = jsworld._getPrecision(options);
1625                 
1626                 // round to required precision
1627                 if (reqPrecision != -1)
1628                         floatAmount = Math.round(floatAmount * Math.pow(10, reqPrecision)) / Math.pow(10, reqPrecision);
1629                 
1630                 
1631                 // convert the float number to string and parse into
1632                 // object with properties integer and fraction
1633                 var parsedAmount = jsworld._splitNumber(String(floatAmount));
1634                 
1635                 // format integer part with grouping chars
1636                 var formattedIntegerPart;
1637                 
1638                 if (floatAmount === 0)
1639                         formattedIntegerPart = "0";
1640                 else
1641                         formattedIntegerPart = jsworld._hasOption("^", options) ?
1642                                 parsedAmount.integer :
1643                                 jsworld._formatIntegerPart(parsedAmount.integer, 
1644                                                            this.lc.grouping, 
1645                                                            this.lc.thousands_sep);
1646                 
1647                 // format the fractional part
1648                 var formattedFractionPart =
1649                         reqPrecision != -1 ?
1650                         jsworld._formatFractionPart(parsedAmount.fraction, reqPrecision) :
1651                         parsedAmount.fraction;
1652                 
1653                 
1654                 // join the integer and fraction parts using the decimal_point property
1655                 var formattedAmount =
1656                         formattedFractionPart.length ?
1657                         formattedIntegerPart + this.lc.decimal_point + formattedFractionPart :
1658                         formattedIntegerPart;
1659                 
1660                 // prepend sign?
1661                 if (jsworld._hasOption("~", options) || floatAmount === 0) {
1662                         // suppress both '+' and '-' signs, i.e. return abs value
1663                         return formattedAmount; 
1664                 }
1665                 else {
1666                         if (jsworld._hasOption("+", options) || floatAmount < 0) {
1667                                 if (floatAmount > 0)
1668                                         // force '+' sign for positive amounts
1669                                         return "+" + formattedAmount;
1670                                 else if (floatAmount < 0)
1671                                         // prepend '-' sign
1672                                         return "-" + formattedAmount;
1673                                 else
1674                                         // zero case
1675                                         return formattedAmount;
1676                         }
1677                         else {
1678                                 // positive amount with no '+' sign
1679                                 return formattedAmount;
1680                         }
1681                 }
1682         };
1683 };
1684
1685
1686 /** 
1687  * @class 
1688  * Class for localised formatting of dates and times.
1689  *
1690  * <p>See: <a href="http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap07.html#tag_07_03_05">
1691  * POSIX LC_TIME</a>.
1692  *
1693  * @public
1694  * @constructor
1695  * @description Creates a new date/time formatter for the specified locale.
1696  *
1697  * @param {jsworld.Locale} locale A locale object specifying the required 
1698  *        POSIX LC_TIME formatting properties.
1699  *
1700  * @throws Error on constructor failure.
1701  */
1702 jsworld.DateTimeFormatter = function(locale) {
1703         
1704                 
1705         if (typeof locale != "object" || locale._className != "jsworld.Locale")
1706                 throw "Constructor error: You must provide a valid jsworld.Locale instance.";
1707         
1708         this.lc = locale;
1709
1710         
1711         /** 
1712          * @public 
1713          *
1714          * @description Formats a date according to the preset locale.
1715          *
1716          * @param {Date|String} date A valid Date object instance or a string
1717          *        containing a valid ISO-8601 formatted date, e.g. "2010-31-03" 
1718          *        or "2010-03-31 23:59:59".
1719          *
1720          * @returns {String} The formatted date
1721          *
1722          * @throws Error on invalid date argument
1723          */
1724         this.formatDate = function(date) {
1725                 
1726                 var d = null;
1727                 
1728                 if (typeof date == "string") {
1729                         // assume ISO-8601 date string
1730                         try {
1731                                 d = jsworld.parseIsoDate(date);
1732                         } catch (error) {
1733                                 // try full ISO-8601 date/time string
1734                                 d = jsworld.parseIsoDateTime(date);
1735                         }
1736                 }
1737                 else if (date !== null && typeof date == "object") {
1738                         // assume ready Date object
1739                         d = date;
1740                 }
1741                 else {
1742                         throw "Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string";
1743                 }
1744                 
1745                 return this._applyFormatting(d, this.lc.d_fmt);
1746         };
1747         
1748         
1749         /** 
1750          * @public 
1751          *
1752          * @description Formats a time according to the preset locale.
1753          *
1754          * @param {Date|String} date A valid Date object instance or a string
1755          *        containing a valid ISO-8601 formatted time, e.g. "23:59:59"
1756          *        or "2010-03-31 23:59:59".
1757          *
1758          * @returns {String} The formatted time.
1759          *
1760          * @throws Error on invalid date argument.
1761          */
1762         this.formatTime = function(date) {
1763                 
1764                 var d = null;
1765                 
1766                 if (typeof date == "string") {
1767                         // assume ISO-8601 time string
1768                         try {
1769                                 d = jsworld.parseIsoTime(date);
1770                         } catch (error) {
1771                                 // try full ISO-8601 date/time string
1772                                 d = jsworld.parseIsoDateTime(date);
1773                         }
1774                 }
1775                 else if (date !== null && typeof date == "object") {
1776                         // assume ready Date object
1777                         d = date;
1778                 }
1779                 else {
1780                         throw "Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string";
1781                 }
1782                 
1783                 return this._applyFormatting(d, this.lc.t_fmt);
1784         };
1785         
1786         
1787         /** 
1788          * @public 
1789          *
1790          * @description Formats a date/time value according to the preset 
1791          * locale.
1792          *
1793          * @param {Date|String} date A valid Date object instance or a string
1794          *        containing a valid ISO-8601 formatted date/time, e.g.
1795          *        "2010-03-31 23:59:59".
1796          *
1797          * @returns {String} The formatted time.
1798          *
1799          * @throws Error on invalid argument.
1800          */
1801         this.formatDateTime = function(date) {
1802                 
1803                 var d = null;
1804                 
1805                 if (typeof date == "string") {
1806                         // assume ISO-8601 format
1807                         d = jsworld.parseIsoDateTime(date);
1808                 }
1809                 else if (date !== null && typeof date == "object") {
1810                         // assume ready Date object
1811                         d = date;
1812                 }
1813                 else {
1814                         throw "Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string";
1815                 }
1816                 
1817                 return this._applyFormatting(d, this.lc.d_t_fmt);
1818         };
1819         
1820         
1821         /** 
1822          * @private 
1823          *
1824          * @description Apples formatting to the Date object according to the
1825          * format string.
1826          *
1827          * @param {Date} d A valid Date instance.
1828          * @param {String} s The formatting string with '%' placeholders.
1829          *
1830          * @returns {String} The formatted string.
1831          */
1832         this._applyFormatting = function(d, s) {
1833                 
1834                 s = s.replace(/%%/g, '%');
1835                 s = s.replace(/%a/g, this.lc.abday[d.getDay()]);
1836                 s = s.replace(/%A/g, this.lc.day[d.getDay()]);
1837                 s = s.replace(/%b/g, this.lc.abmon[d.getMonth()]);
1838                 s = s.replace(/%B/g, this.lc.mon[d.getMonth()]);
1839                 s = s.replace(/%d/g, jsworld._zeroPad(d.getDate(), 2));
1840                 s = s.replace(/%e/g, jsworld._spacePad(d.getDate(), 2));
1841                 s = s.replace(/%F/g, d.getFullYear() +
1842                                          "-" +
1843                                          jsworld._zeroPad(d.getMonth()+1, 2) +
1844                                          "-" +
1845                                          jsworld._zeroPad(d.getDate(), 2));
1846                 s = s.replace(/%h/g, this.lc.abmon[d.getMonth()]); // same as %b
1847                 s = s.replace(/%H/g, jsworld._zeroPad(d.getHours(), 2));
1848                 s = s.replace(/%I/g, jsworld._zeroPad(this._hours12(d.getHours()), 2));
1849                 s = s.replace(/%k/g, d.getHours());
1850                 s = s.replace(/%l/g, this._hours12(d.getHours()));
1851                 s = s.replace(/%m/g, jsworld._zeroPad(d.getMonth()+1, 2));
1852                 s = s.replace(/%n/g, "\n");
1853                 s = s.replace(/%M/g, jsworld._zeroPad(d.getMinutes(), 2));
1854                 s = s.replace(/%p/g, this._getAmPm(d.getHours()));
1855                 s = s.replace(/%P/g, this._getAmPm(d.getHours()).toLocaleLowerCase()); // safe?
1856                 s = s.replace(/%R/g, jsworld._zeroPad(d.getHours(), 2) +
1857                                         ":" +
1858                                         jsworld._zeroPad(d.getMinutes(), 2));
1859                 s = s.replace(/%S/g, jsworld._zeroPad(d.getSeconds(), 2));
1860                 s = s.replace(/%T/g, jsworld._zeroPad(d.getHours(), 2) +
1861                                         ":" +
1862                                         jsworld._zeroPad(d.getMinutes(), 2) +
1863                                         ":" +
1864                                         jsworld._zeroPad(d.getSeconds(), 2));
1865                 s = s.replace(/%w/g, this.lc.day[d.getDay()]);
1866                 s = s.replace(/%y/g, new String(d.getFullYear()).substring(2));
1867                 s = s.replace(/%Y/g, d.getFullYear());
1868                 
1869                 s = s.replace(/%Z/g, ""); // to do: ignored until a reliable TMZ method found
1870                 
1871                 s = s.replace(/%[a-zA-Z]/g, ""); // ignore all other % sequences
1872                 
1873                 return s;
1874         };
1875         
1876         
1877         /** 
1878          * @private 
1879          *
1880          * @description Does 24 to 12 hour conversion.
1881          *
1882          * @param {integer Number} hour24 Hour [0..23].
1883          * 
1884          * @returns {integer Number} Corresponding hour [1..12].
1885          */
1886         this._hours12 = function(hour24) {
1887                 
1888                 if (hour24 === 0)
1889                         return 12; // 00h is 12AM
1890                         
1891                 else if (hour24 > 12)
1892                         return hour24 - 12; // 1PM to 11PM
1893                 
1894                 else
1895                         return hour24; // 1AM to 12PM
1896         };
1897         
1898         
1899         /** 
1900          * @private 
1901          * 
1902          * @description Gets the appropriate localised AM or PM string depending
1903          * on the day hour. Special cases: midnight is 12AM, noon is 12PM.
1904          *
1905          * @param {integer Number} hour24 Hour [0..23].
1906          * 
1907          * @returns {String} The corresponding localised AM or PM string.
1908          */
1909         this._getAmPm = function(hour24) {
1910                 
1911                 if (hour24 < 12)
1912                         return this.lc.am;
1913                 else
1914                         return this.lc.pm;
1915         };
1916 };
1917
1918
1919
1920 /** 
1921  * @class Class for localised formatting of currency amounts.
1922  *
1923  * <p>See: <a href="http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_03_03">
1924  * POSIX LC_MONETARY</a>.
1925  *
1926  * @public
1927  * @constructor
1928  * @description Creates a new monetary formatter for the specified locale.
1929  *
1930  * @param {jsworld.Locale} locale A locale object specifying the required 
1931  *        POSIX LC_MONETARY formatting properties.
1932  * @param {String} [currencyCode] Set the currency explicitly by
1933  *        passing its international ISO-4217 code, e.g. "USD", "EUR", "GBP".
1934  *        Use this optional parameter to override the default local currency
1935  * @param {String} [altIntSymbol] Non-local currencies are formatted
1936  *        with their international ISO-4217 code to prevent ambiguity.
1937  *        Use this optional argument to force a different symbol, such as the
1938  *        currency's shorthand sign. This is mostly useful when the shorthand
1939  *        sign is both internationally recognised and identifies the currency
1940  *        uniquely (e.g. the Euro sign).
1941  *
1942  * @throws Error on constructor failure.
1943  */
1944 jsworld.MonetaryFormatter = function(locale, currencyCode, altIntSymbol) {
1945         
1946         if (typeof locale != "object" || locale._className != "jsworld.Locale")
1947                 throw "Constructor error: You must provide a valid jsworld.Locale instance";
1948         
1949         this.lc = locale;
1950         
1951         /** 
1952          * @private
1953          * @description Lookup table to determine the fraction digits for a
1954          * specific currency; most currencies subdivide at 1/100 (2 fractional
1955          * digits), so we store only those that deviate from the default.
1956          *
1957          * <p>The data is from Unicode's CLDR version 1.7.0. The two currencies
1958          * with non-decimal subunits (MGA and MRO) are marked as having no
1959          * fractional digits as well as all currencies that have no subunits
1960          * in circulation.
1961          * 
1962          * <p>It is "hard-wired" for referential convenience and is only looked
1963          * up when an overriding currencyCode parameter is supplied.
1964          */
1965         this.currencyFractionDigits = {
1966                 "AFN" : 0, "ALL" : 0, "AMD" : 0, "BHD" : 3, "BIF" : 0,
1967                 "BYR" : 0, "CLF" : 0, "CLP" : 0, "COP" : 0, "CRC" : 0, 
1968                 "DJF" : 0, "GNF" : 0, "GYD" : 0, "HUF" : 0, "IDR" : 0, 
1969                 "IQD" : 0, "IRR" : 0, "ISK" : 0, "JOD" : 3, "JPY" : 0, 
1970                 "KMF" : 0, "KRW" : 0, "KWD" : 3, "LAK" : 0, "LBP" : 0,
1971                 "LYD" : 3, "MGA" : 0, "MMK" : 0, "MNT" : 0, "MRO" : 0,
1972                 "MUR" : 0, "OMR" : 3, "PKR" : 0, "PYG" : 0, "RSD" : 0, 
1973                 "RWF" : 0, "SLL" : 0, "SOS" : 0, "STD" : 0, "SYP" : 0, 
1974                 "TND" : 3, "TWD" : 0, "TZS" : 0, "UGX" : 0, "UZS" : 0, 
1975                 "VND" : 0, "VUV" : 0, "XAF" : 0, "XOF" : 0, "XPF" : 0, 
1976                 "YER" : 0, "ZMK" : 0
1977         };
1978         
1979         
1980         // optional currencyCode argument?
1981         if (typeof currencyCode == "string") {
1982                 // user wanted to override the local currency
1983                 this.currencyCode = currencyCode.toUpperCase();
1984                 
1985                 // must override the frac digits too, for some
1986                 // currencies have 0, 2 or 3!
1987                 var numDigits = this.currencyFractionDigits[this.currencyCode];
1988                 if (typeof numDigits != "number")
1989                         numDigits = 2; // default for most currencies
1990                 this.lc.frac_digits = numDigits;
1991                 this.lc.int_frac_digits = numDigits;
1992         }
1993         else {
1994                 // use local currency
1995                 this.currencyCode = this.lc.int_curr_symbol.substring(0,3).toUpperCase();
1996         }
1997         
1998         // extract intl. currency separator
1999         this.intSep = this.lc.int_curr_symbol.charAt(3);
2000         
2001         // flag local or intl. sign formatting?
2002         if (this.currencyCode == this.lc.int_curr_symbol.substring(0,3)) {
2003                 // currency matches the local one? ->
2004                 // formatting with local symbol and parameters
2005                 this.internationalFormatting = false;
2006                 this.curSym = this.lc.currency_symbol;
2007         }
2008         else {
2009                 // currency doesn't match the local ->
2010                 
2011                 // do we have an overriding currency symbol?
2012                 if (typeof altIntSymbol == "string") {
2013                         // -> force formatting with local parameters, using alt symbol
2014                         this.curSym = altIntSymbol;
2015                         this.internationalFormatting = false;
2016                 }
2017                 else {
2018                         // -> force formatting with intl. sign and parameters
2019                         this.internationalFormatting = true;
2020                 }
2021         }
2022         
2023         
2024         /** 
2025          * @public
2026          *
2027          * @description Gets the currency symbol used in formatting.
2028          *
2029          * @returns {String} The currency symbol.
2030          */
2031         this.getCurrencySymbol = function() {
2032                 
2033                 return this.curSym;
2034         };
2035         
2036         
2037         /** 
2038          * @public
2039          *
2040          * @description Gets the position of the currency symbol relative to
2041          * the amount. Assumes a non-negative amount and local formatting.
2042          *
2043          * @param {String} intFlag Optional flag to force international
2044          * formatting by passing the string "i".
2045          *
2046          * @returns {Boolean} True if the symbol precedes the amount, false if
2047          * the symbol succeeds the amount.
2048          */
2049         this.currencySymbolPrecedes = function(intFlag) {
2050                 
2051                 if (typeof intFlag == "string" && intFlag == "i") {
2052                         // international formatting was forced
2053                         if (this.lc.int_p_cs_precedes == 1)
2054                                 return true;
2055                         else
2056                                 return false;
2057                         
2058                 }
2059                 else {
2060                         // check whether local formatting is on or off
2061                         if (this.internationalFormatting) {
2062                                 if (this.lc.int_p_cs_precedes == 1)
2063                                         return true;
2064                                 else
2065                                         return false;
2066                         }
2067                         else {
2068                                 if (this.lc.p_cs_precedes == 1)
2069                                         return true;
2070                                 else
2071                                         return false;
2072                         }
2073                 }
2074         };
2075         
2076         
2077         /** 
2078          * @public
2079          *
2080          * @description Gets the decimal delimiter (radix) used in formatting.
2081          *
2082          * @returns {String} The radix character.
2083          */
2084         this.getDecimalPoint = function() {
2085                 
2086                 return this.lc.mon_decimal_point;
2087         };
2088         
2089         
2090         /** 
2091          * @public
2092          *
2093          * @description Gets the number of fractional digits. Assumes local
2094          * formatting.
2095          *
2096          * @param {String} intFlag Optional flag to force international
2097          *        formatting by passing the string "i".
2098          *
2099          * @returns {integer Number} The number of fractional digits.
2100          */
2101         this.getFractionalDigits = function(intFlag) {
2102                 
2103                 if (typeof intFlag == "string" && intFlag == "i") {
2104                         // international formatting was forced
2105                         return this.lc.int_frac_digits;
2106                 }
2107                 else {
2108                         // check whether local formatting is on or off
2109                         if (this.internationalFormatting)
2110                                 return this.lc.int_frac_digits;
2111                         else
2112                                 return this.lc.frac_digits;
2113                 }
2114         };
2115         
2116         
2117         /** 
2118          * @public
2119          *
2120          * @description Formats a monetary amount according to the preset 
2121          * locale.
2122          *
2123          * <pre>
2124          * For local currencies the native shorthand symbol will be used for
2125          * formatting.
2126          * Example:
2127          *        locale is en_US
2128          *        currency is USD
2129          *        -> the "$" symbol will be used, e.g. $123.45
2130          *        
2131          * For non-local currencies the international ISO-4217 code will be
2132          * used for formatting.
2133          * Example:
2134          *       locale is en_US (which has USD as currency)
2135          *       currency is EUR
2136          *       -> the ISO three-letter code will be used, e.g. EUR 123.45
2137          *
2138          * If the currency is non-local, but an alternative currency symbol was
2139          * provided, this will be used instead.
2140          * Example
2141          *       locale is en_US (which has USD as currency)
2142          *       currency is EUR
2143          *       an alternative symbol is provided - "€"
2144          *       -> the alternative symbol will be used, e.g. â‚¬123.45
2145          * </pre>
2146          * 
2147          * @param {Number|String} amount The amount to format as currency.
2148          * @param {String} [options] Options to modify the formatted output:
2149          *       <ul>
2150          *           <li>"^"  suppress grouping
2151          *           <li>"!"  suppress the currency symbol
2152          *           <li>"~"  suppress the currency symbol and the sign (positive or negative)
2153          *           <li>"i"  force international sign (ISO-4217 code) formatting
2154          *           <li>".n" specify decimal precision
2155          *       
2156          * @returns The formatted currency amount as string.
2157          *
2158          * @throws "Error: Invalid amount" on bad amount.
2159          */
2160         this.format = function(amount, options) {
2161                 
2162                 // if the amount is passed as string, check that it parses to a float
2163                 var floatAmount;
2164                 
2165                 if (typeof amount == "string") {
2166                         amount = jsworld._trim(amount);
2167                         floatAmount = parseFloat(amount);
2168                         
2169                         if (typeof floatAmount != "number" || isNaN(floatAmount))
2170                                 throw "Error: Amount string not a number";
2171                 }
2172                 else if (typeof amount == "number") {
2173                         floatAmount = amount;
2174                 }
2175                 else {
2176                         throw "Error: Amount not a number";
2177                 }
2178                 
2179                 // get the required precision, ".n" option arg overrides default locale config
2180                 var reqPrecision = jsworld._getPrecision(options);
2181                 
2182                 if (reqPrecision == -1) {
2183                         if (this.internationalFormatting || jsworld._hasOption("i", options))
2184                                 reqPrecision = this.lc.int_frac_digits;
2185                         else
2186                                 reqPrecision = this.lc.frac_digits;
2187                 }
2188                 
2189                 // round
2190                 floatAmount = Math.round(floatAmount * Math.pow(10, reqPrecision)) / Math.pow(10, reqPrecision);
2191                 
2192                 
2193                 // convert the float amount to string and parse into
2194                 // object with properties integer and fraction
2195                 var parsedAmount = jsworld._splitNumber(String(floatAmount));
2196                 
2197                 // format integer part with grouping chars
2198                 var formattedIntegerPart;
2199                 
2200                 if (floatAmount === 0)
2201                         formattedIntegerPart = "0";
2202                 else
2203                         formattedIntegerPart = jsworld._hasOption("^", options) ?
2204                                 parsedAmount.integer :
2205                                 jsworld._formatIntegerPart(parsedAmount.integer, 
2206                                                            this.lc.mon_grouping, 
2207                                                            this.lc.mon_thousands_sep);
2208                 
2209                 
2210                 // format the fractional part
2211                 var formattedFractionPart;
2212                 
2213                 if (reqPrecision == -1) {
2214                         // pad fraction with trailing zeros accoring to default locale [int_]frac_digits
2215                         if (this.internationalFormatting || jsworld._hasOption("i", options))
2216                                 formattedFractionPart =
2217                                         jsworld._formatFractionPart(parsedAmount.fraction, this.lc.int_frac_digits);
2218                         else
2219                                 formattedFractionPart =
2220                                         jsworld._formatFractionPart(parsedAmount.fraction, this.lc.frac_digits);
2221                 }
2222                 else {
2223                         // pad fraction with trailing zeros according to optional format parameter
2224                         formattedFractionPart =
2225                                 jsworld._formatFractionPart(parsedAmount.fraction, reqPrecision);
2226                 }
2227                 
2228                 
2229                 // join integer and decimal parts using the mon_decimal_point property
2230                 var quantity;
2231                 
2232                 if (this.lc.frac_digits > 0 || formattedFractionPart.length)
2233                         quantity = formattedIntegerPart + this.lc.mon_decimal_point + formattedFractionPart;
2234                 else
2235                         quantity = formattedIntegerPart;
2236                 
2237                 
2238                 // do final formatting with sign and symbol
2239                 if (jsworld._hasOption("~", options)) {
2240                         return quantity;
2241                 }
2242                 else {
2243                         var suppressSymbol = jsworld._hasOption("!", options) ? true : false;
2244                         
2245                         var sign = floatAmount < 0 ? "-" : "+";
2246                         
2247                         if (this.internationalFormatting || jsworld._hasOption("i", options)) {
2248                                 
2249                                 // format with ISO-4217 code (suppressed or not)
2250                                 if (suppressSymbol)
2251                                         return this._formatAsInternationalCurrencyWithNoSym(sign, quantity);
2252                                 else
2253                                         return this._formatAsInternationalCurrency(sign, quantity);
2254                         }
2255                         else {
2256                                 // format with local currency code (suppressed or not)
2257                                 if (suppressSymbol)
2258                                         return this._formatAsLocalCurrencyWithNoSym(sign, quantity);
2259                                 else
2260                                         return this._formatAsLocalCurrency(sign, quantity);
2261                         }
2262                 }
2263         };
2264         
2265         
2266         /** 
2267          * @private
2268          *
2269          * @description Assembles the final string with sign, separator and symbol as local
2270          * currency.
2271          *
2272          * @param {String} sign The amount sign: "+" or "-".
2273          * @param {String} q The formatted quantity (unsigned).
2274          *
2275          * @returns {String} The final formatted string.
2276          */
2277         this._formatAsLocalCurrency = function (sign, q) {
2278                 
2279                 // assemble final formatted amount by going over all possible value combinations of:
2280                 // sign {+,-} , sign position {0,1,2,3,4} , separator {0,1,2} , symbol position {0,1}
2281                 if (sign == "+") {
2282                         
2283                         // parentheses
2284                         if      (this.lc.p_sign_posn === 0 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 0) {
2285                                 return "(" + q + this.curSym + ")";
2286                         }
2287                         else if (this.lc.p_sign_posn === 0 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 1) {
2288                                 return "(" + this.curSym + q + ")";
2289                         }
2290                         else if (this.lc.p_sign_posn === 0 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 0) {
2291                                 return "(" + q + " " + this.curSym + ")";
2292                         }
2293                         else if (this.lc.p_sign_posn === 0 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 1) {
2294                                 return "(" + this.curSym + " " + q + ")";
2295                         }
2296                         
2297                         // sign before q + sym
2298                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 0) {
2299                                 return this.lc.positive_sign + q + this.curSym;
2300                         }
2301                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 1) {
2302                                 return this.lc.positive_sign + this.curSym + q;
2303                         }
2304                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 0) {
2305                                 return this.lc.positive_sign + q + " " + this.curSym;
2306                         }
2307                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 1) {
2308                                 return this.lc.positive_sign + this.curSym + " " + q;
2309                         }
2310                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 0) {
2311                                 return this.lc.positive_sign + " " + q + this.curSym;
2312                         }
2313                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 1) {
2314                                 return this.lc.positive_sign + " " + this.curSym + q;
2315                         }
2316                         
2317                         // sign after q + sym
2318                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 0) {
2319                                 return q + this.curSym + this.lc.positive_sign;
2320                         }
2321                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 1) {
2322                                 return this.curSym + q + this.lc.positive_sign;
2323                         }
2324                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 0) {
2325                                 return  q + " " + this.curSym + this.lc.positive_sign;
2326                         }
2327                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 1) {
2328                                 return this.curSym + " " + q + this.lc.positive_sign;
2329                         }
2330                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 0) {
2331                                 return q + this.curSym + " " + this.lc.positive_sign;
2332                         }
2333                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 1) {
2334                                 return this.curSym + q + " " + this.lc.positive_sign;
2335                         }
2336                         
2337                         // sign before sym
2338                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 0) {
2339                                 return q + this.lc.positive_sign + this.curSym;
2340                         }
2341                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 1) {
2342                                 return this.lc.positive_sign + this.curSym + q;
2343                         }
2344                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 0) {
2345                                 return q + " " + this.lc.positive_sign + this.curSym;
2346                         }
2347                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 1) {
2348                                 return this.lc.positive_sign + this.curSym + " " + q;
2349                         }
2350                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 0) {
2351                                 return q + this.lc.positive_sign + " " + this.curSym;
2352                         }
2353                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 1) {
2354                                 return this.lc.positive_sign + " " + this.curSym + q;
2355                         }
2356                         
2357                         // sign after symbol
2358                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 0) {
2359                                 return q + this.curSym + this.lc.positive_sign;
2360                         }
2361                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 1) {
2362                                 return this.curSym + this.lc.positive_sign + q;
2363                         }
2364                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 0) {
2365                                 return  q + " " + this.curSym + this.lc.positive_sign;
2366                         }
2367                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 1) {
2368                                 return this.curSym + this.lc.positive_sign + " " + q;
2369                         }
2370                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 0) {
2371                                 return q + this.curSym + " " + this.lc.positive_sign;
2372                         }
2373                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 1) {
2374                                 return this.curSym + " " + this.lc.positive_sign + q;
2375                         }
2376                         
2377                 }
2378                 else if (sign == "-") {
2379                         
2380                         // parentheses enclose q + sym
2381                         if      (this.lc.n_sign_posn === 0 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 0) {
2382                                 return "(" + q + this.curSym + ")";
2383                         }
2384                         else if (this.lc.n_sign_posn === 0 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 1) {
2385                                 return "(" + this.curSym + q + ")";
2386                         }
2387                         else if (this.lc.n_sign_posn === 0 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 0) {
2388                                 return "(" + q + " " + this.curSym + ")";
2389                         }
2390                         else if (this.lc.n_sign_posn === 0 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 1) {
2391                                 return "(" + this.curSym + " " + q + ")";
2392                         }
2393                         
2394                         // sign before q + sym
2395                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 0) {
2396                                 return this.lc.negative_sign + q + this.curSym;
2397                         }
2398                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 1) {
2399                                 return this.lc.negative_sign + this.curSym + q;
2400                         }
2401                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 0) {
2402                                 return this.lc.negative_sign + q + " " + this.curSym;
2403                         }
2404                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 1) {
2405                                 return this.lc.negative_sign + this.curSym + " " + q;
2406                         }
2407                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 0) {
2408                                 return this.lc.negative_sign + " " + q + this.curSym;
2409                         }
2410                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 1) {
2411                                 return this.lc.negative_sign + " " + this.curSym + q;
2412                         }
2413                         
2414                         // sign after q + sym
2415                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 0) {
2416                                 return q + this.curSym + this.lc.negative_sign;
2417                         }
2418                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 1) {
2419                                 return this.curSym + q + this.lc.negative_sign;
2420                         }
2421                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 0) {
2422                                 return  q + " " + this.curSym + this.lc.negative_sign;
2423                         }
2424                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 1) {
2425                                 return this.curSym + " " + q + this.lc.negative_sign;
2426                         }
2427                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 0) {
2428                                 return q + this.curSym + " " + this.lc.negative_sign;
2429                         }
2430                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 1) {
2431                                 return this.curSym + q + " " + this.lc.negative_sign;
2432                         }
2433                         
2434                         // sign before sym
2435                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 0) {
2436                                 return q + this.lc.negative_sign + this.curSym;
2437                         }
2438                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 1) {
2439                                 return this.lc.negative_sign + this.curSym + q;
2440                         }
2441                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 0) {
2442                                 return q + " " + this.lc.negative_sign + this.curSym;
2443                         }
2444                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 1) {
2445                                 return this.lc.negative_sign + this.curSym + " " + q;
2446                         }
2447                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 0) {
2448                                 return q + this.lc.negative_sign + " " + this.curSym;
2449                         }
2450                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 1) {
2451                                 return this.lc.negative_sign + " " + this.curSym + q;
2452                         }
2453                         
2454                         // sign after symbol
2455                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 0) {
2456                                 return q + this.curSym + this.lc.negative_sign;
2457                         }
2458                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 1) {
2459                                 return this.curSym + this.lc.negative_sign + q;
2460                         }
2461                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 0) {
2462                                 return  q + " " + this.curSym + this.lc.negative_sign;
2463                         }
2464                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 1) {
2465                                 return this.curSym + this.lc.negative_sign + " " + q;
2466                         }
2467                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 0) {
2468                                 return q + this.curSym + " " + this.lc.negative_sign;
2469                         }
2470                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 1) {
2471                                 return this.curSym + " " + this.lc.negative_sign + q;
2472                         }
2473                 }
2474                 
2475                 // throw error if we fall through
2476                 throw "Error: Invalid POSIX LC MONETARY definition";
2477         };
2478         
2479         
2480         /** 
2481          * @private
2482          *
2483          * @description Assembles the final string with sign, separator and ISO-4217
2484          * currency code.
2485          *
2486          * @param {String} sign The amount sign: "+" or "-".
2487          * @param {String} q The formatted quantity (unsigned).
2488          *
2489          * @returns {String} The final formatted string.
2490          */
2491         this._formatAsInternationalCurrency = function (sign, q) {
2492                 
2493                 // assemble the final formatted amount by going over all possible value combinations of:
2494                 // sign {+,-} , sign position {0,1,2,3,4} , separator {0,1,2} , symbol position {0,1}
2495                 
2496                 if (sign == "+") {
2497                         
2498                         // parentheses
2499                         if      (this.lc.int_p_sign_posn === 0 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 0) {
2500                                 return "(" + q + this.currencyCode + ")";
2501                         }
2502                         else if (this.lc.int_p_sign_posn === 0 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 1) {
2503                                 return "(" + this.currencyCode + q + ")";
2504                         }
2505                         else if (this.lc.int_p_sign_posn === 0 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 0) {
2506                                 return "(" + q + this.intSep + this.currencyCode + ")";
2507                         }
2508                         else if (this.lc.int_p_sign_posn === 0 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 1) {
2509                                 return "(" + this.currencyCode + this.intSep + q + ")";
2510                         }
2511                         
2512                         // sign before q + sym
2513                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 0) {
2514                                 return this.lc.positive_sign + q + this.currencyCode;
2515                         }
2516                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 1) {
2517                                 return this.lc.positive_sign + this.currencyCode + q;
2518                         }
2519                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 0) {
2520                                 return this.lc.positive_sign + q + this.intSep + this.currencyCode;
2521                         }
2522                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 1) {
2523                                 return this.lc.positive_sign + this.currencyCode + this.intSep + q;
2524                         }
2525                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 0) {
2526                                 return this.lc.positive_sign + this.intSep + q + this.currencyCode;
2527                         }
2528                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 1) {
2529                                 return this.lc.positive_sign + this.intSep + this.currencyCode + q;
2530                         }
2531                         
2532                         // sign after q + sym
2533                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 0) {
2534                                 return q + this.currencyCode + this.lc.positive_sign;
2535                         }
2536                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 1) {
2537                                 return this.currencyCode + q + this.lc.positive_sign;
2538                         }
2539                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 0) {
2540                                 return  q + this.intSep + this.currencyCode + this.lc.positive_sign;
2541                         }
2542                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 1) {
2543                                 return this.currencyCode + this.intSep + q + this.lc.positive_sign;
2544                         }
2545                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 0) {
2546                                 return q + this.currencyCode + this.intSep + this.lc.positive_sign;
2547                         }
2548                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 1) {
2549                                 return this.currencyCode + q + this.intSep + this.lc.positive_sign;
2550                         }
2551                         
2552                         // sign before sym
2553                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 0) {
2554                                 return q + this.lc.positive_sign + this.currencyCode;
2555                         }
2556                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 1) {
2557                                 return this.lc.positive_sign + this.currencyCode + q;
2558                         }
2559                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 0) {
2560                                 return q + this.intSep + this.lc.positive_sign + this.currencyCode;
2561                         }
2562                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 1) {
2563                                 return this.lc.positive_sign + this.currencyCode + this.intSep + q;
2564                         }
2565                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 0) {
2566                                 return q + this.lc.positive_sign + this.intSep + this.currencyCode;
2567                         }
2568                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 1) {
2569                                 return this.lc.positive_sign + this.intSep + this.currencyCode + q;
2570                         }
2571                         
2572                         // sign after symbol
2573                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 0) {
2574                                 return q + this.currencyCode + this.lc.positive_sign;
2575                         }
2576                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 1) {
2577                                 return this.currencyCode + this.lc.positive_sign + q;
2578                         }
2579                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 0) {
2580                                 return  q + this.intSep + this.currencyCode + this.lc.positive_sign;
2581                         }
2582                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 1) {
2583                                 return this.currencyCode + this.lc.positive_sign + this.intSep + q;
2584                         }
2585                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 0) {
2586                                 return q + this.currencyCode + this.intSep + this.lc.positive_sign;
2587                         }
2588                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 1) {
2589                                 return this.currencyCode + this.intSep + this.lc.positive_sign + q;
2590                         }
2591                         
2592                 }
2593                 else if (sign == "-") {
2594                         
2595                         // parentheses enclose q + sym
2596                         if      (this.lc.int_n_sign_posn === 0 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 0) {
2597                                 return "(" + q + this.currencyCode + ")";
2598                         }
2599                         else if (this.lc.int_n_sign_posn === 0 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 1) {
2600                                 return "(" + this.currencyCode + q + ")";
2601                         }
2602                         else if (this.lc.int_n_sign_posn === 0 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 0) {
2603                                 return "(" + q + this.intSep + this.currencyCode + ")";
2604                         }
2605                         else if (this.lc.int_n_sign_posn === 0 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 1) {
2606                                 return "(" + this.currencyCode + this.intSep + q + ")";
2607                         }
2608                         
2609                         // sign before q + sym
2610                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 0) {
2611                                 return this.lc.negative_sign + q + this.currencyCode;
2612                         }
2613                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 1) {
2614                                 return this.lc.negative_sign + this.currencyCode + q;
2615                         }
2616                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 0) {
2617                                 return this.lc.negative_sign + q + this.intSep + this.currencyCode;
2618                         }
2619                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 1) {
2620                                 return this.lc.negative_sign + this.currencyCode + this.intSep + q;
2621                         }
2622                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 0) {
2623                                 return this.lc.negative_sign + this.intSep + q + this.currencyCode;
2624                         }
2625                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 1) {
2626                                 return this.lc.negative_sign + this.intSep + this.currencyCode + q;
2627                         }
2628                         
2629                         // sign after q + sym
2630                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 0) {
2631                                 return q + this.currencyCode + this.lc.negative_sign;
2632                         }
2633                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 1) {
2634                                 return this.currencyCode + q + this.lc.negative_sign;
2635                         }
2636                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 0) {
2637                                 return  q + this.intSep + this.currencyCode + this.lc.negative_sign;
2638                         }
2639                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 1) {
2640                                 return this.currencyCode + this.intSep + q + this.lc.negative_sign;
2641                         }
2642                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 0) {
2643                                 return q + this.currencyCode + this.intSep + this.lc.negative_sign;
2644                         }
2645                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 1) {
2646                                 return this.currencyCode + q + this.intSep + this.lc.negative_sign;
2647                         }
2648                         
2649                         // sign before sym
2650                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 0) {
2651                                 return q + this.lc.negative_sign + this.currencyCode;
2652                         }
2653                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 1) {
2654                                 return this.lc.negative_sign + this.currencyCode + q;
2655                         }
2656                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 0) {
2657                                 return q + this.intSep + this.lc.negative_sign + this.currencyCode;
2658                         }
2659                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 1) {
2660                                 return this.lc.negative_sign + this.currencyCode + this.intSep + q;
2661                         }
2662                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 0) {
2663                                 return q + this.lc.negative_sign + this.intSep + this.currencyCode;
2664                         }
2665                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 1) {
2666                                 return this.lc.negative_sign + this.intSep + this.currencyCode + q;
2667                         }
2668                         
2669                         // sign after symbol
2670                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 0) {
2671                                 return q + this.currencyCode + this.lc.negative_sign;
2672                         }
2673                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 1) {
2674                                 return this.currencyCode + this.lc.negative_sign + q;
2675                         }
2676                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 0) {
2677                                 return  q + this.intSep + this.currencyCode + this.lc.negative_sign;
2678                         }
2679                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 1) {
2680                                 return this.currencyCode + this.lc.negative_sign + this.intSep + q;
2681                         }
2682                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 0) {
2683                                 return q + this.currencyCode + this.intSep + this.lc.negative_sign;
2684                         }
2685                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 1) {
2686                                 return this.currencyCode + this.intSep + this.lc.negative_sign + q;
2687                         }
2688                 }
2689                 
2690                 // throw error if we fall through
2691                 throw "Error: Invalid POSIX LC MONETARY definition";
2692         };
2693         
2694         
2695         /** 
2696          * @private
2697          *
2698          * @description Assembles the final string with sign and separator, but suppress the
2699          * local currency symbol.
2700          *
2701          * @param {String} sign The amount sign: "+" or "-".
2702          * @param {String} q The formatted quantity (unsigned).
2703          *
2704          * @returns {String} The final formatted string
2705          */
2706         this._formatAsLocalCurrencyWithNoSym = function (sign, q) {
2707                 
2708                 // assemble the final formatted amount by going over all possible value combinations of:
2709                 // sign {+,-} , sign position {0,1,2,3,4} , separator {0,1,2} , symbol position {0,1}
2710                 
2711                 if (sign == "+") {
2712                         
2713                         // parentheses
2714                         if      (this.lc.p_sign_posn === 0) {
2715                                 return "(" + q + ")";
2716                         }
2717                         
2718                         // sign before q + sym
2719                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 0) {
2720                                 return this.lc.positive_sign + q;
2721                         }
2722                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 1) {
2723                                 return this.lc.positive_sign + q;
2724                         }
2725                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 0) {
2726                                 return this.lc.positive_sign + q;
2727                         }
2728                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 1) {
2729                                 return this.lc.positive_sign + q;
2730                         }
2731                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 0) {
2732                                 return this.lc.positive_sign + " " + q;
2733                         }
2734                         else if (this.lc.p_sign_posn === 1 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 1) {
2735                                 return this.lc.positive_sign + " " + q;
2736                         }
2737                         
2738                         // sign after q + sym
2739                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 0) {
2740                                 return q + this.lc.positive_sign;
2741                         }
2742                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 1) {
2743                                 return q + this.lc.positive_sign;
2744                         }
2745                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 0) {
2746                                 return  q + " " + this.lc.positive_sign;
2747                         }
2748                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 1) {
2749                                 return q + this.lc.positive_sign;
2750                         }
2751                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 0) {
2752                                 return q + this.lc.positive_sign;
2753                         }
2754                         else if (this.lc.p_sign_posn === 2 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 1) {
2755                                 return q + " " + this.lc.positive_sign;
2756                         }
2757                         
2758                         // sign before sym
2759                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 0) {
2760                                 return q + this.lc.positive_sign;
2761                         }
2762                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 1) {
2763                                 return this.lc.positive_sign + q;
2764                         }
2765                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 0) {
2766                                 return q + " " + this.lc.positive_sign;
2767                         }
2768                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 1) {
2769                                 return this.lc.positive_sign + " " + q;
2770                         }
2771                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 0) {
2772                                 return q + this.lc.positive_sign;
2773                         }
2774                         else if (this.lc.p_sign_posn === 3 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 1) {
2775                                 return this.lc.positive_sign + " " + q;
2776                         }
2777                         
2778                         // sign after symbol
2779                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 0) {
2780                                 return q + this.lc.positive_sign;
2781                         }
2782                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 0 && this.lc.p_cs_precedes === 1) {
2783                                 return this.lc.positive_sign + q;
2784                         }
2785                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 0) {
2786                                 return  q + " " + this.lc.positive_sign;
2787                         }
2788                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 1 && this.lc.p_cs_precedes === 1) {
2789                                 return this.lc.positive_sign + " " + q;
2790                         }
2791                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 0) {
2792                                 return q + " " + this.lc.positive_sign;
2793                         }
2794                         else if (this.lc.p_sign_posn === 4 && this.lc.p_sep_by_space === 2 && this.lc.p_cs_precedes === 1) {
2795                                 return this.lc.positive_sign + q;
2796                         }
2797                         
2798                 }
2799                 else if (sign == "-") {
2800                         
2801                         // parentheses enclose q + sym
2802                         if      (this.lc.n_sign_posn === 0) {
2803                                 return "(" + q + ")";
2804                         }
2805                         
2806                         // sign before q + sym
2807                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 0) {
2808                                 return this.lc.negative_sign + q;
2809                         }
2810                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 1) {
2811                                 return this.lc.negative_sign + q;
2812                         }
2813                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 0) {
2814                                 return this.lc.negative_sign + q;
2815                         }
2816                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 1) {
2817                                 return this.lc.negative_sign + " " + q;
2818                         }
2819                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 0) {
2820                                 return this.lc.negative_sign + " " + q;
2821                         }
2822                         else if (this.lc.n_sign_posn === 1 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 1) {
2823                                 return this.lc.negative_sign + " " + q;
2824                         }
2825                         
2826                         // sign after q + sym
2827                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 0) {
2828                                 return q + this.lc.negative_sign;
2829                         }
2830                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 1) {
2831                                 return q + this.lc.negative_sign;
2832                         }
2833                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 0) {
2834                                 return  q + " " + this.lc.negative_sign;
2835                         }
2836                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 1) {
2837                                 return q + this.lc.negative_sign;
2838                         }
2839                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 0) {
2840                                 return q + " " + this.lc.negative_sign;
2841                         }
2842                         else if (this.lc.n_sign_posn === 2 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 1) {
2843                                 return q + " " + this.lc.negative_sign;
2844                         }
2845                         
2846                         // sign before sym
2847                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 0) {
2848                                 return q + this.lc.negative_sign;
2849                         }
2850                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 1) {
2851                                 return this.lc.negative_sign + q;
2852                         }
2853                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 0) {
2854                                 return q + " " + this.lc.negative_sign;
2855                         }
2856                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 1) {
2857                                 return this.lc.negative_sign + " " + q;
2858                         }
2859                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 0) {
2860                                 return q + this.lc.negative_sign;
2861                         }
2862                         else if (this.lc.n_sign_posn === 3 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 1) {
2863                                 return this.lc.negative_sign + " " + q;
2864                         }
2865                         
2866                         // sign after symbol
2867                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 0) {
2868                                 return q + this.lc.negative_sign;
2869                         }
2870                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 0 && this.lc.n_cs_precedes === 1) {
2871                                 return this.lc.negative_sign + q;
2872                         }
2873                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 0) {
2874                                 return  q + " " + this.lc.negative_sign;
2875                         }
2876                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 1 && this.lc.n_cs_precedes === 1) {
2877                                 return this.lc.negative_sign + " " + q;
2878                         }
2879                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 0) {
2880                                 return q + " " + this.lc.negative_sign;
2881                         }
2882                         else if (this.lc.n_sign_posn === 4 && this.lc.n_sep_by_space === 2 && this.lc.n_cs_precedes === 1) {
2883                                 return this.lc.negative_sign + q;
2884                         }
2885                 }
2886                 
2887                 // throw error if we fall through
2888                 throw "Error: Invalid POSIX LC MONETARY definition";
2889         };
2890         
2891         
2892         /** 
2893          * @private
2894          *
2895          * @description Assembles the final string with sign and separator, but suppress
2896          * the ISO-4217 currency code.
2897          *
2898          * @param {String} sign The amount sign: "+" or "-".
2899          * @param {String} q The formatted quantity (unsigned).
2900          *
2901          * @returns {String} The final formatted string.
2902          */
2903         this._formatAsInternationalCurrencyWithNoSym = function (sign, q) {
2904                 
2905                 // assemble the final formatted amount by going over all possible value combinations of:
2906                 // sign {+,-} , sign position {0,1,2,3,4} , separator {0,1,2} , symbol position {0,1}
2907                 
2908                 if (sign == "+") {
2909                         
2910                         // parentheses
2911                         if      (this.lc.int_p_sign_posn === 0) {
2912                                 return "(" + q + ")";
2913                         }
2914                         
2915                         // sign before q + sym
2916                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 0) {
2917                                 return this.lc.positive_sign + q;
2918                         }
2919                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 1) {
2920                                 return this.lc.positive_sign + q;
2921                         }
2922                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 0) {
2923                                 return this.lc.positive_sign + q;
2924                         }
2925                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 1) {
2926                                 return this.lc.positive_sign + this.intSep + q;
2927                         }
2928                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 0) {
2929                                 return this.lc.positive_sign + this.intSep + q;
2930                         }
2931                         else if (this.lc.int_p_sign_posn === 1 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 1) {
2932                                 return this.lc.positive_sign + this.intSep + q;
2933                         }
2934                         
2935                         // sign after q + sym
2936                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 0) {
2937                                 return q + this.lc.positive_sign;
2938                         }
2939                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 1) {
2940                                 return q + this.lc.positive_sign;
2941                         }
2942                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 0) {
2943                                 return  q + this.intSep + this.lc.positive_sign;
2944                         }
2945                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 1) {
2946                                 return q + this.lc.positive_sign;
2947                         }
2948                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 0) {
2949                                 return q + this.intSep + this.lc.positive_sign;
2950                         }
2951                         else if (this.lc.int_p_sign_posn === 2 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 1) {
2952                                 return q + this.intSep + this.lc.positive_sign;
2953                         }
2954                         
2955                         // sign before sym
2956                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 0) {
2957                                 return q + this.lc.positive_sign;
2958                         }
2959                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 1) {
2960                                 return this.lc.positive_sign + q;
2961                         }
2962                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 0) {
2963                                 return q + this.intSep + this.lc.positive_sign;
2964                         }
2965                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 1) {
2966                                 return this.lc.positive_sign + this.intSep + q;
2967                         }
2968                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 0) {
2969                                 return q + this.lc.positive_sign;
2970                         }
2971                         else if (this.lc.int_p_sign_posn === 3 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 1) {
2972                                 return this.lc.positive_sign + this.intSep + q;
2973                         }
2974                         
2975                         // sign after symbol
2976                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 0) {
2977                                 return q + this.lc.positive_sign;
2978                         }
2979                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 0 && this.lc.int_p_cs_precedes === 1) {
2980                                 return this.lc.positive_sign + q;
2981                         }
2982                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 0) {
2983                                 return  q + this.intSep + this.lc.positive_sign;
2984                         }
2985                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 1 && this.lc.int_p_cs_precedes === 1) {
2986                                 return this.lc.positive_sign + this.intSep + q;
2987                         }
2988                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 0) {
2989                                 return q + this.intSep + this.lc.positive_sign;
2990                         }
2991                         else if (this.lc.int_p_sign_posn === 4 && this.lc.int_p_sep_by_space === 2 && this.lc.int_p_cs_precedes === 1) {
2992                                 return this.lc.positive_sign + q;
2993                         }
2994                         
2995                 }
2996                 else if (sign == "-") {
2997                         
2998                         // parentheses enclose q + sym
2999                         if      (this.lc.int_n_sign_posn === 0) {
3000                                 return "(" + q + ")";
3001                         }
3002                         
3003                         // sign before q + sym
3004                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 0) {
3005                                 return this.lc.negative_sign + q;
3006                         }
3007                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 1) {
3008                                 return this.lc.negative_sign + q;
3009                         }
3010                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 0) {
3011                                 return this.lc.negative_sign + q;
3012                         }
3013                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 1) {
3014                                 return this.lc.negative_sign + this.intSep + q;
3015                         }
3016                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 0) {
3017                                 return this.lc.negative_sign + this.intSep + q;
3018                         }
3019                         else if (this.lc.int_n_sign_posn === 1 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 1) {
3020                                 return this.lc.negative_sign + this.intSep + q;
3021                         }
3022                         
3023                         // sign after q + sym
3024                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 0) {
3025                                 return q + this.lc.negative_sign;
3026                         }
3027                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 1) {
3028                                 return q + this.lc.negative_sign;
3029                         }
3030                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 0) {
3031                                 return  q + this.intSep + this.lc.negative_sign;
3032                         }
3033                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 1) {
3034                                 return q + this.lc.negative_sign;
3035                         }
3036                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 0) {
3037                                 return q + this.intSep + this.lc.negative_sign;
3038                         }
3039                         else if (this.lc.int_n_sign_posn === 2 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 1) {
3040                                 return q + this.intSep + this.lc.negative_sign;
3041                         }
3042                         
3043                         // sign before sym
3044                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 0) {
3045                                 return q + this.lc.negative_sign;
3046                         }
3047                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 1) {
3048                                 return this.lc.negative_sign + q;
3049                         }
3050                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 0) {
3051                                 return q + this.intSep + this.lc.negative_sign;
3052                         }
3053                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 1) {
3054                                 return this.lc.negative_sign + this.intSep + q;
3055                         }
3056                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 0) {
3057                                 return q + this.lc.negative_sign;
3058                         }
3059                         else if (this.lc.int_n_sign_posn === 3 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 1) {
3060                                 return this.lc.negative_sign + this.intSep + q;
3061                         }
3062                         
3063                         // sign after symbol
3064                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 0) {
3065                                 return q + this.lc.negative_sign;
3066                         }
3067                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 0 && this.lc.int_n_cs_precedes === 1) {
3068                                 return this.lc.negative_sign + q;
3069                         }
3070                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 0) {
3071                                 return  q + this.intSep + this.lc.negative_sign;
3072                         }
3073                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 1 && this.lc.int_n_cs_precedes === 1) {
3074                                 return this.lc.negative_sign + this.intSep + q;
3075                         }
3076                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 0) {
3077                                 return q + this.intSep + this.lc.negative_sign;
3078                         }
3079                         else if (this.lc.int_n_sign_posn === 4 && this.lc.int_n_sep_by_space === 2 && this.lc.int_n_cs_precedes === 1) {
3080                                 return this.lc.negative_sign + q;
3081                         }
3082                 }
3083                 
3084                 // throw error if we fall through
3085                 throw "Error: Invalid POSIX LC_MONETARY definition";
3086         };
3087 };
3088
3089
3090 /** 
3091  * @class 
3092  * Class for parsing localised number strings.
3093  *
3094  * @public
3095  * @constructor 
3096  * @description Creates a new numeric parser for the specified locale.
3097  *
3098  * @param {jsworld.Locale} locale A locale object specifying the required 
3099  *        POSIX LC_NUMERIC formatting properties.
3100  *
3101  * @throws Error on constructor failure.
3102  */
3103 jsworld.NumericParser = function(locale) {
3104
3105         if (typeof locale != "object" || locale._className != "jsworld.Locale")
3106                 throw "Constructor error: You must provide a valid jsworld.Locale instance";
3107
3108         this.lc = locale;
3109         
3110         
3111         /**
3112          * @public
3113          *
3114          * @description Parses a numeric string formatted according to the 
3115          * preset locale. Leading and trailing whitespace is ignored; the number
3116          * may also be formatted without thousands separators.
3117          *
3118          * @param {String} formattedNumber The formatted number.
3119          *
3120          * @returns {Number} The parsed number.
3121          *
3122          * @throws Error on a parse exception.
3123          */
3124         this.parse = function(formattedNumber) {
3125         
3126                 if (typeof formattedNumber != "string")
3127                         throw "Parse error: Argument must be a string";
3128         
3129                 // trim whitespace
3130                 var s = jsworld._trim(formattedNumber);
3131         
3132                 // remove any thousand separator symbols
3133                 s = jsworld._stringReplaceAll(formattedNumber, this.lc.thousands_sep, "");
3134                 
3135                 // replace any local decimal point symbols with the symbol used
3136                 // in JavaScript "."
3137                 s = jsworld._stringReplaceAll(s, this.lc.decimal_point, ".");
3138                 
3139                 // test if the string represents a number
3140                 if (jsworld._isNumber(s))
3141                         return parseFloat(s, 10);               
3142                 else
3143                         throw "Parse error: Invalid number string";
3144         };
3145 };
3146
3147
3148 /** 
3149  * @class 
3150  * Class for parsing localised date and time strings.
3151  *
3152  * @public
3153  * @constructor 
3154  * @description Creates a new date/time parser for the specified locale.
3155  *
3156  * @param {jsworld.Locale} locale A locale object specifying the required 
3157  *        POSIX LC_TIME formatting properties.
3158  *
3159  * @throws Error on constructor failure.
3160  */
3161 jsworld.DateTimeParser = function(locale) {
3162
3163         if (typeof locale != "object" || locale._className != "jsworld.Locale")
3164                 throw "Constructor error: You must provide a valid jsworld.Locale instance.";
3165
3166         this.lc = locale;
3167
3168         
3169         /**
3170          * @public
3171          *
3172          * @description Parses a time string formatted according to the 
3173          * POSIX LC_TIME t_fmt property of the preset locale.
3174          *
3175          * @param {String} formattedTime The formatted time.
3176          *
3177          * @returns {String} The parsed time in ISO-8601 format (HH:MM:SS), e.g.
3178          *          "23:59:59".
3179          *
3180          * @throws Error on a parse exception.
3181          */
3182         this.parseTime = function(formattedTime) {
3183         
3184                 if (typeof formattedTime != "string")
3185                         throw "Parse error: Argument must be a string";
3186         
3187                 var dt = this._extractTokens(this.lc.t_fmt, formattedTime);
3188                 
3189                 var timeDefined = false;
3190                 
3191                 if (dt.hour !== null && dt.minute !== null && dt.second !== null) {
3192                         timeDefined = true;
3193                 }
3194                 else if (dt.hourAmPm !== null && dt.am !== null && dt.minute !== null && dt.second !== null) {
3195                         if (dt.am) {
3196                                 // AM [12(midnight), 1 .. 11]
3197                                 if (dt.hourAmPm == 12)
3198                                         dt.hour = 0;
3199                                 else
3200                                         dt.hour = parseInt(dt.hourAmPm, 10);
3201                         }
3202                         else {
3203                                 // PM [12(noon), 1 .. 11]
3204                                 if (dt.hourAmPm == 12)
3205                                         dt.hour = 12;
3206                                 else
3207                                         dt.hour = parseInt(dt.hourAmPm, 10) + 12;
3208                         }
3209                         timeDefined = true;
3210                 }
3211                 
3212                 if (timeDefined)
3213                         return jsworld._zeroPad(dt.hour, 2) + 
3214                                ":" + 
3215                                jsworld._zeroPad(dt.minute, 2) + 
3216                                ":" + 
3217                                jsworld._zeroPad(dt.second, 2);
3218                 else
3219                         throw "Parse error: Invalid/ambiguous time string";
3220         };
3221         
3222         
3223         /**
3224          * @public
3225          *
3226          * @description Parses a date string formatted according to the 
3227          * POSIX LC_TIME d_fmt property of the preset locale.
3228          *
3229          * @param {String} formattedDate The formatted date, must be valid.
3230          *
3231          * @returns {String} The parsed date in ISO-8601 format (YYYY-MM-DD), 
3232          *          e.g. "2010-03-31".
3233          *
3234          * @throws Error on a parse exception.
3235          */
3236         this.parseDate = function(formattedDate) {
3237         
3238                 if (typeof formattedDate != "string")
3239                         throw "Parse error: Argument must be a string";
3240         
3241                 var dt = this._extractTokens(this.lc.d_fmt, formattedDate);
3242                 
3243                 var dateDefined = false;
3244                 
3245                 if (dt.year !== null && dt.month !== null && dt.day !== null) {
3246                         dateDefined = true;
3247                 }
3248                 
3249                 if (dateDefined)
3250                         return jsworld._zeroPad(dt.year, 4) + 
3251                                "-" + 
3252                                jsworld._zeroPad(dt.month, 2) + 
3253                                "-" + 
3254                                jsworld._zeroPad(dt.day, 2);
3255                 else
3256                         throw "Parse error: Invalid date string";
3257         };
3258         
3259         
3260         /**
3261          * @public
3262          *
3263          * @description Parses a date/time string formatted according to the 
3264          * POSIX LC_TIME d_t_fmt property of the preset locale.
3265          *
3266          * @param {String} formattedDateTime The formatted date/time, must be
3267          *        valid.
3268          *
3269          * @returns {String} The parsed date/time in ISO-8601 format 
3270          *          (YYYY-MM-DD HH:MM:SS), e.g. "2010-03-31 23:59:59".
3271          *
3272          * @throws Error on a parse exception.
3273          */
3274         this.parseDateTime = function(formattedDateTime) {
3275         
3276                 if (typeof formattedDateTime != "string")
3277                         throw "Parse error: Argument must be a string";
3278                 
3279                 var dt = this._extractTokens(this.lc.d_t_fmt, formattedDateTime);
3280                 
3281                 var timeDefined = false;
3282                 var dateDefined = false;
3283         
3284                 if (dt.hour !== null && dt.minute !== null && dt.second !== null) {
3285                         timeDefined = true;
3286                 }
3287                 else if (dt.hourAmPm !== null && dt.am !== null && dt.minute !== null && dt.second !== null) {
3288                         if (dt.am) {
3289                                 // AM [12(midnight), 1 .. 11]
3290                                 if (dt.hourAmPm == 12)
3291                                         dt.hour = 0;
3292                                 else
3293                                         dt.hour = parseInt(dt.hourAmPm, 10);
3294                         }
3295                         else {
3296                                 // PM [12(noon), 1 .. 11]
3297                                 if (dt.hourAmPm == 12)
3298                                         dt.hour = 12;
3299                                 else
3300                                         dt.hour = parseInt(dt.hourAmPm, 10) + 12;
3301                         }
3302                         timeDefined = true;
3303                 }
3304                 
3305                 if (dt.year !== null && dt.month !== null && dt.day !== null) {
3306                         dateDefined = true;
3307                 }
3308                 
3309                 if (dateDefined && timeDefined)
3310                         return jsworld._zeroPad(dt.year, 4) + 
3311                                "-" + 
3312                                jsworld._zeroPad(dt.month, 2) + 
3313                                "-" + 
3314                                jsworld._zeroPad(dt.day, 2) + 
3315                                " " +
3316                                jsworld._zeroPad(dt.hour, 2) + 
3317                                ":" + 
3318                                jsworld._zeroPad(dt.minute, 2) + 
3319                                ":" + 
3320                                jsworld._zeroPad(dt.second, 2);
3321                 else
3322                         throw "Parse error: Invalid/ambiguous date/time string";
3323         };
3324         
3325         
3326         /**
3327          * @private
3328          *
3329          * @description Parses a string according to the specified format
3330          * specification.
3331          *
3332          * @param {String} fmtSpec The format specification, e.g. "%I:%M:%S %p".
3333          * @param {String} s The string to parse.
3334          *
3335          * @returns {object} An object with set properties year, month, day,
3336          *          hour, minute and second if the corresponding values are
3337          *          found in the parsed string.
3338          *
3339          * @throws Error on a parse exception.
3340          */
3341         this._extractTokens = function(fmtSpec, s) {
3342         
3343                 // the return object containing the parsed date/time properties
3344                 var dt = {
3345                         // for date and date/time strings
3346                         "year"     : null,
3347                         "month"    : null,
3348                         "day"      : null,
3349                         
3350                         // for time and date/time strings
3351                         "hour"     : null,
3352                         "hourAmPm" : null,
3353                         "am"       : null,
3354                         "minute"   : null,
3355                         "second"   : null,
3356                         
3357                         // used internally only
3358                         "weekday"  : null
3359                 };
3360
3361         
3362                 // extract and process each token in the date/time spec
3363                 while (fmtSpec.length > 0) {
3364                 
3365                         // Do we have a valid "%\w" placeholder in stream?
3366                         if (fmtSpec.charAt(0) == "%" && fmtSpec.charAt(1) != "") {
3367                                 
3368                                 // get placeholder
3369                                 var placeholder = fmtSpec.substring(0,2);
3370                                 
3371                                 if (placeholder == "%%") {
3372                                         // escaped '%''
3373                                         s = s.substring(1);
3374                                 }
3375                                 else if (placeholder == "%a") {
3376                                         // abbreviated weekday name
3377                                         for (var i = 0; i < this.lc.abday.length; i++) {
3378                                         
3379                                                 if (jsworld._stringStartsWith(s, this.lc.abday[i])) {
3380                                                         dt.weekday = i;
3381                                                         s = s.substring(this.lc.abday[i].length);
3382                                                         break;
3383                                                 }
3384                                         }
3385                                         
3386                                         if (dt.weekday === null)
3387                                                 throw "Parse error: Unrecognised abbreviated weekday name (%a)";
3388                                 }
3389                                 else if (placeholder == "%A") {
3390                                         // weekday name
3391                                         for (var i = 0; i < this.lc.day.length; i++) {
3392                                         
3393                                                 if (jsworld._stringStartsWith(s, this.lc.day[i])) {
3394                                                         dt.weekday = i;
3395                                                         s = s.substring(this.lc.day[i].length);
3396                                                         break;
3397                                                 }
3398                                         }
3399                                         
3400                                         if (dt.weekday === null)
3401                                                 throw "Parse error: Unrecognised weekday name (%A)";
3402                                 }
3403                                 else if (placeholder == "%b" || placeholder == "%h") {
3404                                         // abbreviated month name
3405                                         for (var i = 0; i < this.lc.abmon.length; i++) {
3406                         
3407                                                 if (jsworld._stringStartsWith(s, this.lc.abmon[i])) {
3408                                                         dt.month = i + 1;
3409                                                         s = s.substring(this.lc.abmon[i].length);
3410                                                         break;
3411                                                 }
3412                                         }
3413
3414                                         if (dt.month === null)
3415                                                 throw "Parse error: Unrecognised abbreviated month name (%b)";
3416                                 }
3417                                 else if (placeholder == "%B") {
3418                                         // month name
3419                                         for (var i = 0; i < this.lc.mon.length; i++) {
3420                         
3421                                                 if (jsworld._stringStartsWith(s, this.lc.mon[i])) {
3422                                                         dt.month = i + 1;
3423                                                         s = s.substring(this.lc.mon[i].length);
3424                                                         break;
3425                                                 }
3426                                         }
3427
3428                                         if (dt.month === null)
3429                                                 throw "Parse error: Unrecognised month name (%B)";
3430                                 }
3431                                 else if (placeholder == "%d") {
3432                                         // day of the month [01..31]
3433                                         if (/^0[1-9]|[1-2][0-9]|3[0-1]/.test(s)) {
3434                                                 dt.day = parseInt(s.substring(0,2), 10);
3435                                                 s = s.substring(2);
3436                                         }
3437                                         else    
3438                                                 throw "Parse error: Unrecognised day of the month (%d)";
3439                                 }
3440                                 else if (placeholder == "%e") {
3441                                         // day of the month [1..31]
3442                                         
3443                                         // Note: if %e is leading in fmt string -> space padded!
3444                                         
3445                                         var day = s.match(/^\s?(\d{1,2})/);
3446                                         dt.day = parseInt(day, 10);
3447                                         
3448                                         if (isNaN(dt.day) || dt.day < 1 || dt.day > 31)
3449                                                 throw "Parse error: Unrecognised day of the month (%e)";
3450                                         
3451                                         s = s.substring(day.length);
3452                                 }
3453                                 else if (placeholder == "%F") {
3454                                         // equivalent to %Y-%m-%d (ISO-8601 date format)
3455                                         
3456                                         // year [nnnn]
3457                                         if (/^\d\d\d\d/.test(s)) {
3458                                                 dt.year = parseInt(s.substring(0,4), 10);
3459                                                 s = s.substring(4);
3460                                         }
3461                                         else {
3462                                                 throw "Parse error: Unrecognised date (%F)";
3463                                         }
3464                                         
3465                                         // -
3466                                         if (jsworld._stringStartsWith(s, "-"))
3467                                                 s = s.substring(1);
3468                                         else
3469                                                 throw "Parse error: Unrecognised date (%F)";
3470                                         
3471                                         // month [01..12]
3472                                         if (/^0[1-9]|1[0-2]/.test(s)) {
3473                                                 dt.month = parseInt(s.substring(0,2), 10);
3474                                                 s = s.substring(2);
3475                                         }
3476                                         else    
3477                                                 throw "Parse error: Unrecognised date (%F)";
3478
3479                                         // -
3480                                         if (jsworld._stringStartsWith(s, "-"))
3481                                                 s = s.substring(1);
3482                                         else
3483                                                 throw "Parse error: Unrecognised date (%F)";
3484                                         
3485                                         // day of the month [01..31]
3486                                         if (/^0[1-9]|[1-2][0-9]|3[0-1]/.test(s)) {
3487                                                 dt.day = parseInt(s.substring(0,2), 10);
3488                                                 s = s.substring(2);
3489                                         }
3490                                         else    
3491                                                 throw "Parse error: Unrecognised date (%F)";
3492                                 }
3493                                 else if (placeholder == "%H") {
3494                                         // hour [00..23]
3495                                         if (/^[0-1][0-9]|2[0-3]/.test(s)) {
3496                                                 dt.hour = parseInt(s.substring(0,2), 10);
3497                                                 s = s.substring(2);
3498                                         }
3499                                         else
3500                                                 throw "Parse error: Unrecognised hour (%H)";
3501                                 }
3502                                 else if (placeholder == "%I") {
3503                                         // hour [01..12]
3504                                         if (/^0[1-9]|1[0-2]/.test(s)) {
3505                                                 dt.hourAmPm = parseInt(s.substring(0,2), 10);
3506                                                 s = s.substring(2);
3507                                         }
3508                                         else
3509                                                 throw "Parse error: Unrecognised hour (%I)";
3510                                 }
3511                                 else if (placeholder == "%k") {
3512                                         // hour [0..23]
3513                                         var h = s.match(/^(\d{1,2})/);
3514                                         dt.hour = parseInt(h, 10);
3515                                         
3516                                         if (isNaN(dt.hour) || dt.hour < 0 || dt.hour > 23)
3517                                                 throw "Parse error: Unrecognised hour (%k)";
3518                                         
3519                                         s = s.substring(h.length);
3520                                 }
3521                                 else if (placeholder == "%l") {
3522                                         // hour AM/PM [1..12]
3523                                         var h = s.match(/^(\d{1,2})/);
3524                                         dt.hourAmPm = parseInt(h, 10);
3525                                         
3526                                         if (isNaN(dt.hourAmPm) || dt.hourAmPm < 1 || dt.hourAmPm > 12)
3527                                                 throw "Parse error: Unrecognised hour (%l)";
3528                                         
3529                                         s = s.substring(h.length);
3530                                 }
3531                                 else if (placeholder == "%m") {
3532                                         // month [01..12]
3533                                         if (/^0[1-9]|1[0-2]/.test(s)) {
3534                                                 dt.month = parseInt(s.substring(0,2), 10);
3535                                                 s = s.substring(2);
3536                                         }
3537                                         else    
3538                                                 throw "Parse error: Unrecognised month (%m)";
3539                                 }
3540                                 else if (placeholder == "%M") {
3541                                         // minute [00..59]
3542                                         if (/^[0-5][0-9]/.test(s)) {
3543                                                 dt.minute = parseInt(s.substring(0,2), 10);
3544                                                 s = s.substring(2);
3545                                         }
3546                                         else    
3547                                                 throw "Parse error: Unrecognised minute (%M)";
3548                                 }
3549                                 else if (placeholder == "%n") {
3550                                         // new line
3551                                         
3552                                         if (s.charAt(0) == "\n")
3553                                                 s = s.substring(1);
3554                                         else
3555                                                 throw "Parse error: Unrecognised new line (%n)";
3556                                 }
3557                                 else if (placeholder == "%p") {
3558                                         // locale's equivalent of AM/PM
3559                                         if (jsworld._stringStartsWith(s, this.lc.am)) {
3560                                                 dt.am = true;
3561                                                 s = s.substring(this.lc.am.length);
3562                                         }
3563                                         else if (jsworld._stringStartsWith(s, this.lc.pm)) {
3564                                                 dt.am = false;
3565                                                 s = s.substring(this.lc.pm.length);
3566                                         }
3567                                         else
3568                                                 throw "Parse error: Unrecognised AM/PM value (%p)";
3569                                 }
3570                                 else if (placeholder == "%P") {
3571                                         // same as %p but forced lower case
3572                                         if (jsworld._stringStartsWith(s, this.lc.am.toLowerCase())) {
3573                                                 dt.am = true;
3574                                                 s = s.substring(this.lc.am.length);
3575                                         }
3576                                         else if (jsworld._stringStartsWith(s, this.lc.pm.toLowerCase())) {
3577                                                 dt.am = false;
3578                                                 s = s.substring(this.lc.pm.length);
3579                                         }
3580                                         else
3581                                                 throw "Parse error: Unrecognised AM/PM value (%P)";
3582                                 }
3583                                 else if (placeholder == "%R") {
3584                                         // same as %H:%M
3585                                         
3586                                         // hour [00..23]
3587                                         if (/^[0-1][0-9]|2[0-3]/.test(s)) {
3588                                                 dt.hour = parseInt(s.substring(0,2), 10);
3589                                                 s = s.substring(2);
3590                                         }
3591                                         else
3592                                                 throw "Parse error: Unrecognised time (%R)";
3593                                         
3594                                         // :
3595                                         if (jsworld._stringStartsWith(s, ":"))
3596                                                 s = s.substring(1);
3597                                         else
3598                                                 throw "Parse error: Unrecognised time (%R)";
3599
3600                                         // minute [00..59]
3601                                         if (/^[0-5][0-9]/.test(s)) {
3602                                                 dt.minute = parseInt(s.substring(0,2), 10);
3603                                                 s = s.substring(2);
3604                                         }
3605                                         else    
3606                                                 throw "Parse error: Unrecognised time (%R)";
3607
3608                                 }
3609                                 else if (placeholder == "%S") {
3610                                         // second [00..59]
3611                                         if (/^[0-5][0-9]/.test(s)) {
3612                                                 dt.second = parseInt(s.substring(0,2), 10);
3613                                                 s = s.substring(2);
3614                                         }
3615                                         else
3616                                                 throw "Parse error: Unrecognised second (%S)";
3617                                 }
3618                                 else if (placeholder == "%T") {
3619                                         // same as %H:%M:%S
3620                                         
3621                                         // hour [00..23]
3622                                         if (/^[0-1][0-9]|2[0-3]/.test(s)) {
3623                                                 dt.hour = parseInt(s.substring(0,2), 10);
3624                                                 s = s.substring(2);
3625                                         }
3626                                         else
3627                                                 throw "Parse error: Unrecognised time (%T)";
3628                                         
3629                                         // :
3630                                         if (jsworld._stringStartsWith(s, ":"))
3631                                                 s = s.substring(1);
3632                                         else
3633                                                 throw "Parse error: Unrecognised time (%T)";
3634
3635                                         // minute [00..59]
3636                                         if (/^[0-5][0-9]/.test(s)) {
3637                                                 dt.minute = parseInt(s.substring(0,2), 10);
3638                                                 s = s.substring(2);
3639                                         }
3640                                         else    
3641                                                 throw "Parse error: Unrecognised time (%T)";
3642                                         
3643                                         // :
3644                                         if (jsworld._stringStartsWith(s, ":"))
3645                                                 s = s.substring(1);
3646                                         else
3647                                                 throw "Parse error: Unrecognised time (%T)";
3648                                         
3649                                         // second [00..59]
3650                                         if (/^[0-5][0-9]/.test(s)) {
3651                                                 dt.second = parseInt(s.substring(0,2), 10);
3652                                                 s = s.substring(2);
3653                                         }
3654                                         else
3655                                                 throw "Parse error: Unrecognised time (%T)";
3656                                 }
3657                                 else if (placeholder == "%w") {
3658                                         // weekday [0..6]
3659                                         if (/^\d/.test(s)) {
3660                                                 dt.weekday = parseInt(s.substring(0,1), 10);
3661                                                 s = s.substring(1);
3662                                         }
3663                                         else 
3664                                                 throw "Parse error: Unrecognised weekday number (%w)";
3665                                 }
3666                                 else if (placeholder == "%y") {
3667                                         // year [00..99]
3668                                         if (/^\d\d/.test(s)) {
3669                                                 var year2digits = parseInt(s.substring(0,2), 10);
3670                                                 
3671                                                 // this conversion to year[nnnn] is arbitrary!!!
3672                                                 if (year2digits > 50)
3673                                                         dt.year = 1900 + year2digits;
3674                                                 else
3675                                                         dt.year = 2000 + year2digits;
3676                                                 
3677                                                 s = s.substring(2);
3678                                         }
3679                                         else
3680                                                 throw "Parse error: Unrecognised year (%y)";
3681                                 }
3682                                 else if (placeholder == "%Y") {
3683                                         // year [nnnn]
3684                                         if (/^\d\d\d\d/.test(s)) {
3685                                                 dt.year = parseInt(s.substring(0,4), 10);
3686                                                 s = s.substring(4);
3687                                         }
3688                                         else
3689                                                 throw "Parse error: Unrecognised year (%Y)";
3690                                 }
3691                                 
3692                                 else if (placeholder == "%Z") {
3693                                         // time-zone place holder is not supported
3694                                         
3695                                         if (fmtSpec.length === 0)
3696                                                 break; // ignore rest of fmt spec
3697                                 }
3698
3699                                 // remove the spec placeholder that was just parsed
3700                                 fmtSpec = fmtSpec.substring(2);
3701                         }
3702                         else {
3703                                 // If we don't have a placeholder, the chars
3704                                 // at pos. 0 of format spec and parsed string must match
3705                                 
3706                                 // Note: Space chars treated 1:1 !
3707                                 
3708                                 if (fmtSpec.charAt(0) != s.charAt(0))
3709                                         throw "Parse error: Unexpected symbol \"" + s.charAt(0) + "\" in date/time string";
3710                         
3711                                 fmtSpec = fmtSpec.substring(1);
3712                                 s = s.substring(1);
3713                         }
3714                 }
3715                 
3716                 // parsing finished, return composite date/time object
3717                 return dt;
3718         };
3719 };
3720
3721
3722 /** 
3723  * @class 
3724  * Class for parsing localised currency amount strings.
3725  *
3726  * @public
3727  * @constructor 
3728  * @description Creates a new monetary parser for the specified locale.
3729  *
3730  * @param {jsworld.Locale} locale A locale object specifying the required 
3731  *        POSIX LC_MONETARY formatting properties.
3732  *
3733  * @throws Error on constructor failure.
3734  */
3735 jsworld.MonetaryParser = function(locale) {
3736
3737         if (typeof locale != "object" || locale._className != "jsworld.Locale")
3738                 throw "Constructor error: You must provide a valid jsworld.Locale instance";
3739
3740
3741         this.lc = locale;
3742         
3743         
3744         /**
3745          * @public
3746          *
3747          * @description Parses a currency amount string formatted according to 
3748          * the preset locale. Leading and trailing whitespace is ignored; the 
3749          * amount may also be formatted without thousands separators. Both
3750          * the local (shorthand) symbol and the ISO 4217 code are accepted to 
3751          * designate the currency in the formatted amount.
3752          *
3753          * @param {String} formattedCurrency The formatted currency amount.
3754          *
3755          * @returns {Number} The parsed amount.
3756          *
3757          * @throws Error on a parse exception.
3758          */
3759         this.parse = function(formattedCurrency) {
3760         
3761                 if (typeof formattedCurrency != "string")
3762                         throw "Parse error: Argument must be a string";
3763         
3764                 // Detect the format type and remove the currency symbol
3765                 var symbolType = this._detectCurrencySymbolType(formattedCurrency);
3766         
3767                 var formatType, s;
3768         
3769                 if (symbolType == "local") {
3770                         formatType = "local";
3771                         s = formattedCurrency.replace(this.lc.getCurrencySymbol(), "");
3772                 }
3773                 else if (symbolType == "int") {
3774                         formatType = "int";
3775                         s = formattedCurrency.replace(this.lc.getIntCurrencySymbol(), "");
3776                 }
3777                 else if (symbolType == "none") {
3778                         formatType = "local"; // assume local
3779                         s = formattedCurrency;
3780                 }
3781                 else
3782                         throw "Parse error: Internal assert failure";
3783                 
3784                 // Remove any thousands separators
3785                 s = jsworld._stringReplaceAll(s, this.lc.mon_thousands_sep, "");
3786                 
3787                 // Replace any local radix char with JavaScript's "."
3788                 s = s.replace(this.lc.mon_decimal_point, ".");
3789                 
3790                 // Remove all whitespaces
3791                 s = s.replace(/\s*/g, "");
3792                 
3793                 // Remove any local non-negative sign
3794                 s = this._removeLocalNonNegativeSign(s, formatType);
3795                 
3796                 // Replace any local minus sign with JavaScript's "-" and put
3797                 // it in front of the amount if necessary
3798                 // (special parentheses rule checked too)
3799                 s = this._normaliseNegativeSign(s, formatType);
3800                 
3801                 // Finally, we should be left with a bare parsable decimal number
3802                 if (jsworld._isNumber(s))
3803                         return parseFloat(s, 10);
3804                 else
3805                         throw "Parse error: Invalid currency amount string";
3806         };
3807         
3808         
3809         /**
3810          * @private
3811          *
3812          * @description Tries to detect the symbol type used in the specified
3813          *              formatted currency string: local(shorthand), 
3814          *              international (ISO-4217 code) or none.
3815          *
3816          * @param {String} formattedCurrency The the formatted currency string.
3817          *
3818          * @return {String} With possible values "local", "int" or "none".
3819          */
3820         this._detectCurrencySymbolType = function(formattedCurrency) {
3821         
3822                 // Check for whichever sign (int/local) is longer first
3823                 // to cover cases such as MOP/MOP$ and ZAR/R
3824                 
3825                 if (this.lc.getCurrencySymbol().length > this.lc.getIntCurrencySymbol().length) {
3826                 
3827                         if (formattedCurrency.indexOf(this.lc.getCurrencySymbol()) != -1)
3828                                 return "local";
3829                         else if (formattedCurrency.indexOf(this.lc.getIntCurrencySymbol()) != -1)
3830                                 return "int";
3831                         else
3832                                 return "none";
3833                 }
3834                 else {
3835                         if (formattedCurrency.indexOf(this.lc.getIntCurrencySymbol()) != -1)
3836                                 return "int";
3837                         else if (formattedCurrency.indexOf(this.lc.getCurrencySymbol()) != -1)
3838                                 return "local";
3839                         else
3840                                 return "none";
3841                 }
3842         };
3843         
3844         
3845         /**
3846          * @private
3847          *
3848          * @description Removes a local non-negative sign in a formatted 
3849          * currency string if it is found. This is done according to the
3850          * locale properties p_sign_posn and int_p_sign_posn.
3851          *
3852          * @param {String} s The input string.
3853          * @param {String} formatType With possible values "local" or "int".
3854          *
3855          * @returns {String} The processed string.
3856          */
3857         this._removeLocalNonNegativeSign = function(s, formatType) {
3858         
3859                 s = s.replace(this.lc.positive_sign, "");
3860         
3861                 // check for enclosing parentheses rule
3862                 if (((formatType == "local" && this.lc.p_sign_posn     === 0) ||
3863                      (formatType == "int"   && this.lc.int_p_sign_posn === 0)    ) &&
3864                       /\(\d+\.?\d*\)/.test(s)) {
3865                         s = s.replace("(", "");
3866                         s = s.replace(")", "");
3867                 }
3868                 
3869                 return s;
3870         };
3871         
3872         
3873         /**
3874          * @private
3875          *
3876          * @description Replaces a local negative sign with the standard
3877          * JavaScript minus ("-") sign placed in the correct position 
3878          * (preceding the amount). This is done according to the locale
3879          * properties for negative sign symbol and relative position.
3880          *
3881          * @param {String} s The input string.
3882          * @param {String} formatType With possible values "local" or "int".
3883          *
3884          * @returns {String} The processed string.
3885          */
3886         this._normaliseNegativeSign = function(s, formatType) {
3887         
3888                 // replace local negative symbol with JavaScript's "-"
3889                 s = s.replace(this.lc.negative_sign, "-");
3890         
3891                 // check for enclosing parentheses rule and replace them
3892                 // with negative sign before the amount
3893                 if ((formatType == "local" && this.lc.n_sign_posn     === 0) ||
3894                     (formatType == "int"   && this.lc.int_n_sign_posn === 0)    ) {
3895                     
3896                         if (/^\(\d+\.?\d*\)$/.test(s)) {
3897                      
3898                                 s = s.replace("(", "");
3899                                 s = s.replace(")", "");
3900                                 return "-" + s;
3901                         }
3902                 }
3903                 
3904                 // check for rule negative sign succeeding the amount
3905                 if (formatType == "local" && this.lc.n_sign_posn     == 2 ||
3906                     formatType == "int"   && this.lc.int_n_sign_posn == 2   ) {
3907                 
3908                         if (/^\d+\.?\d*-$/.test(s)) {
3909                                 s = s.replace("-", "");
3910                                 return "-" + s;
3911                         }
3912                 }
3913         
3914                 // check for rule cur. sym. succeeds and sign adjacent
3915                 if (formatType == "local" && this.lc.n_cs_precedes     === 0 && this.lc.n_sign_posn     == 3 ||
3916                     formatType == "local" && this.lc.n_cs_precedes     === 0 && this.lc.n_sign_posn     == 4 ||
3917                     formatType == "int"   && this.lc.int_n_cs_precedes === 0 && this.lc.int_n_sign_posn == 3 ||
3918                     formatType == "int"   && this.lc.int_n_cs_precedes === 0 && this.lc.int_n_sign_posn == 4    ) {
3919                     
3920                         if (/^\d+\.?\d*-$/.test(s)) {
3921                                 s = s.replace("-", "");
3922                                 return "-" + s;
3923                         }
3924                 }
3925                 
3926                 return s;
3927         };
3928 };
3929
3930 // end-of-file