6 * <p>Javascript library for localised formatting and parsing of:
13 * <p>The library classes are configured with standard POSIX locale definitions
14 * derived from Unicode's Common Locale Data Repository (CLDR).
16 * <p>Website: <a href="http://software.dzhuvinov.com/jsworld.html">JsWorld</a>
18 * @author Vladimir Dzhuvinov
19 * @version 2.5 (2011-12-23)
25 * @namespace Namespace container for the JsWorld library objects.
33 * @description Formats a JavaScript Date object as an ISO-8601 date/time
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.
40 * @returns {String} The date/time formatted as YYYY-MM-DD HH:MM:SS.
42 jsworld.formatIsoDateTime = function(d, withTZ) {
44 if (typeof d === "undefined")
45 d = new Date(); // now
47 if (typeof withTZ === "undefined")
50 var s = jsworld.formatIsoDate(d) + " " + jsworld.formatIsoTime(d);
54 var diff = d.getHours() - d.getUTCHours();
55 var hourDiff = Math.abs(diff);
57 var minuteUTC = d.getUTCMinutes();
58 var minute = d.getMinutes();
60 if (minute != minuteUTC && minuteUTC < 30 && diff < 0)
63 if (minute != minuteUTC && minuteUTC > 30 && diff > 0)
67 if (minute != minuteUTC)
74 timezone = "0" + hourDiff + minuteDiff;
77 timezone = "" + hourDiff + minuteDiff;
80 timezone = "-" + timezone;
83 timezone = "+" + timezone;
95 * @description Formats a JavaScript Date object as an ISO-8601 date string.
97 * @param {Date} [d] A valid JavaScript Date object. If undefined the current
100 * @returns {String} The date formatted as YYYY-MM-DD.
102 jsworld.formatIsoDate = function(d) {
104 if (typeof d === "undefined")
105 d = new Date(); // now
107 var year = d.getFullYear();
108 var month = d.getMonth() + 1;
109 var day = d.getDate();
111 return year + "-" + jsworld._zeroPad(month, 2) + "-" + jsworld._zeroPad(day, 2);
118 * @description Formats a JavaScript Date object as an ISO-8601 time string.
120 * @param {Date} [d] A valid JavaScript Date object. If undefined the current
123 * @returns {String} The time formatted as HH:MM:SS.
125 jsworld.formatIsoTime = function(d) {
127 if (typeof d === "undefined")
128 d = new Date(); // now
130 var hour = d.getHours();
131 var minute = d.getMinutes();
132 var second = d.getSeconds();
134 return jsworld._zeroPad(hour, 2) + ":" + jsworld._zeroPad(minute, 2) + ":" + jsworld._zeroPad(second, 2);
141 * @description Parses an ISO-8601 formatted date/time string to a JavaScript
144 * @param {String} isoDateTimeVal An ISO-8601 formatted date/time string.
146 * <p>Accepted formats:
149 * <li>YYYY-MM-DD HH:MM:SS
150 * <li>YYYYMMDD HHMMSS
151 * <li>YYYY-MM-DD HHMMSS
152 * <li>YYYYMMDD HH:MM:SS
155 * @returns {Date} The corresponding Date object.
157 * @throws Error on a badly formatted date/time string or on a invalid date.
159 jsworld.parseIsoDateTime = function(isoDateTimeVal) {
161 if (typeof isoDateTimeVal != "string")
162 throw "Error: The parameter must be a string";
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)/);
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)/);
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)/);
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)/);
179 // Report bad date/time string
180 if (matches === null)
181 throw "Error: Invalid ISO-8601 date/time string";
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);
189 var hour = parseInt(matches[4], 10);
190 var mins = parseInt(matches[5], 10);
191 var secs = parseInt(matches[6], 10);
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 )
201 throw "Error: Invalid ISO-8601 date/time value";
203 var d = new Date(year, month - 1, day, hour, mins, secs);
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";
217 * @description Parses an ISO-8601 formatted date string to a JavaScript
220 * @param {String} isoDateVal An ISO-8601 formatted date string.
222 * <p>Accepted formats:
229 * @returns {Date} The corresponding Date object.
231 * @throws Error on a badly formatted date string or on a invalid date.
233 jsworld.parseIsoDate = function(isoDateVal) {
235 if (typeof isoDateVal != "string")
236 throw "Error: The parameter must be a string";
238 // First, try to match "YYYY-MM-DD" format
239 var matches = isoDateVal.match(/^(\d\d\d\d)-(\d\d)-(\d\d)/);
241 // If unsuccessful, try to match "YYYYMMDD" format
242 if (matches === null)
243 matches = isoDateVal.match(/^(\d\d\d\d)(\d\d)(\d\d)/);
245 // Report bad date/time string
246 if (matches === null)
247 throw "Error: Invalid ISO-8601 date string";
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);
255 // Simple value range check, leap years not checked
256 if (month < 1 || month > 12 ||
257 day < 1 || day > 31 )
259 throw "Error: Invalid ISO-8601 date value";
261 var d = new Date(year, month - 1, day);
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";
275 * @description Parses an ISO-8601 formatted time string to a JavaScript
278 * @param {String} isoTimeVal An ISO-8601 formatted time string.
280 * <p>Accepted formats:
287 * @returns {Date} The corresponding Date object, with year, month and day set
290 * @throws Error on a badly formatted time string.
292 jsworld.parseIsoTime = function(isoTimeVal) {
294 if (typeof isoTimeVal != "string")
295 throw "Error: The parameter must be a string";
297 // First, try to match "HH:MM:SS" format
298 var matches = isoTimeVal.match(/^(\d\d):(\d\d):(\d\d)/);
300 // If unsuccessful, try to match "HHMMSS" format
301 if (matches === null)
302 matches = isoTimeVal.match(/^(\d\d)(\d\d)(\d\d)/);
304 // Report bad date/time string
305 if (matches === null)
306 throw "Error: Invalid ISO-8601 date/time string";
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);
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 )
319 throw "Error: Invalid ISO-8601 time value";
321 return new Date(0, 0, 0, hour, mins, secs);
328 * @description Trims leading and trailing whitespace from a string.
330 * <p>Used non-regexp the method from http://blog.stevenlevithan.com/archives/faster-trim-javascript
332 * @param {String} str The string to trim.
334 * @returns {String} The trimmed string.
336 jsworld._trim = function(str) {
338 var whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
340 for (var i = 0; i < str.length; i++) {
342 if (whitespace.indexOf(str.charAt(i)) === -1) {
343 str = str.substring(i);
348 for (i = str.length - 1; i >= 0; i--) {
349 if (whitespace.indexOf(str.charAt(i)) === -1) {
350 str = str.substring(0, i + 1);
355 return whitespace.indexOf(str.charAt(0)) === -1 ? str : '';
363 * @description Returns true if the argument represents a decimal number.
365 * @param {Number|String} arg The argument to test.
367 * @returns {Boolean} true if the argument represents a decimal number,
370 jsworld._isNumber = function(arg) {
372 if (typeof arg == "number")
375 if (typeof arg != "string")
381 return (/^-?(\d+|\d*\.\d+)$/).test(s);
388 * @description Returns true if the argument represents a decimal integer.
390 * @param {Number|String} arg The argument to test.
392 * @returns {Boolean} true if the argument represents an integer, otherwise
395 jsworld._isInteger = function(arg) {
397 if (typeof arg != "number" && typeof arg != "string")
403 return (/^-?\d+$/).test(s);
410 * @description Returns true if the argument represents a decimal float.
412 * @param {Number|String} arg The argument to test.
414 * @returns {Boolean} true if the argument represents a float, otherwise false.
416 jsworld._isFloat = function(arg) {
418 if (typeof arg != "number" && typeof arg != "string")
424 return (/^-?\.\d+?$/).test(s);
431 * @description Checks if the specified formatting option is contained
432 * within the options string.
434 * @param {String} option The option to search for.
435 * @param {String} optionsString The options string.
437 * @returns {Boolean} true if the flag is found, else false
439 jsworld._hasOption = function(option, optionsString) {
441 if (typeof option != "string" || typeof optionsString != "string")
444 if (optionsString.indexOf(option) != -1)
454 * @description String replacement function.
456 * @param {String} s The string to work on.
457 * @param {String} target The string to search for.
458 * @param {String} replacement The replacement.
460 * @returns {String} The new string.
462 jsworld._stringReplaceAll = function(s, target, replacement) {
466 if (target.length == 1 && replacement.length == 1) {
467 // simple char/char case somewhat faster
470 for (var i = 0; i < s.length; i++) {
472 if (s.charAt(i) == target.charAt(0))
473 out = out + replacement.charAt(0);
475 out = out + s.charAt(i);
481 // longer target and replacement strings
484 var index = out.indexOf(target);
486 while (index != -1) {
488 out = out.replace(target, replacement);
490 index = out.indexOf(target);
501 * @description Tests if a string starts with the specified substring.
503 * @param {String} testedString The string to test.
504 * @param {String} sub The string to match.
506 * @returns {Boolean} true if the test succeeds.
508 jsworld._stringStartsWith = function (testedString, sub) {
510 if (testedString.length < sub.length)
513 for (var i = 0; i < sub.length; i++) {
514 if (testedString.charAt(i) != sub.charAt(i))
525 * @description Gets the requested precision from an options string.
527 * <p>Example: ".3" returns 3 decimal places precision.
529 * @param {String} optionsString The options string.
531 * @returns {integer Number} The requested precision, -1 if not specified.
533 jsworld._getPrecision = function (optionsString) {
535 if (typeof optionsString != "string")
538 var m = optionsString.match(/\.(\d)/);
540 return parseInt(m[1], 10);
549 * @description Takes a decimal numeric amount (optionally as string) and
550 * returns its integer and fractional parts packed into an object.
552 * @param {Number|String} amount The amount, e.g. "123.45" or "-56.78"
554 * @returns {object} Parsed amount object with properties:
555 * {String} integer : the integer part
556 * {String} fraction : the fraction part
558 jsworld._splitNumber = function (amount) {
560 if (typeof amount == "number")
561 amount = amount + "";
565 // remove negative sign
566 if (amount.charAt(0) == "-")
567 amount = amount.substring(1);
569 // split amount into integer and decimal parts
570 var amountParts = amount.split(".");
572 amountParts[1] = ""; // we need "" instead of null
574 obj.integer = amountParts[0];
575 obj.fraction = amountParts[1];
584 * @description Formats the integer part using the specified grouping
585 * and thousands separator.
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.
591 * @returns {String} The formatted integer part.
593 jsworld._formatIntegerPart = function (intPart, grouping, thousandsSep) {
595 // empty separator string? no grouping?
596 // -> return immediately with no formatting!
597 if (thousandsSep == "" || grouping == "-1")
600 // turn the semicolon-separated string of integers into an array
601 var groupSizes = grouping.split(";");
603 // the formatted output string
606 // the intPart string position to process next,
607 // start at string end, e.g. "10000000<starts here"
608 var pos = intPart.length;
610 // process the intPart string backwards
617 // get next group size (if any, otherwise keep last)
618 if (groupSizes.length > 0)
619 size = parseInt(groupSizes.shift(), 10);
623 throw "Error: Invalid grouping";
625 // size is -1? -> no more grouping, so just copy string remainder
627 out = intPart.substring(0, pos) + out;
631 pos -= size; // move to next sep. char. position
633 // position underrun? -> just copy string remainder
635 out = intPart.substring(0, pos + size) + out;
639 // extract group and apply sep. char.
640 out = thousandsSep + intPart.substring(pos, pos + size) + out;
650 * @description Formats the fractional part to the specified decimal
653 * @param {String} fracPart The fractional part of the amount
654 * @param {integer Number} precision The desired decimal precision
656 * @returns {String} The formatted fractional part.
658 jsworld._formatFractionPart = function (fracPart, precision) {
660 // append zeroes up to precision if necessary
661 for (var i=0; fracPart.length < precision; i++)
662 fracPart = fracPart + "0";
671 * @desription Converts a number to string and pad it with leading zeroes if the
672 * string is shorter than length.
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
678 * @returns {String} The formatted string.
680 jsworld._zeroPad = function(number, length) {
685 while (s.length < length)
694 * @description Converts a number to string and pads it with leading spaces if
695 * the string is shorter than length.
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
701 * @returns {String} The formatted string.
703 jsworld._spacePad = function(number, length) {
708 while (s.length < length)
718 * Represents a POSIX-style locale with its numeric, monetary and date/time
719 * properties. Also provides a set of locale helper methods.
721 * <p>The locale properties follow the POSIX standards:
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>
731 * @description Creates a new locale object (POSIX-style) with the specified
734 * @param {object} properties An object containing the raw locale properties:
736 * @param {String} properties.decimal_point
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
744 * @param {String} properties.thousands_sep
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.
751 * @param {String} properties.grouping
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.
764 * @param {String} properties.int_curr_symbol
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).
771 * @param {String} properties.currency_symbol
773 * The local shorthand currency symbol, e.g. "$" for the en_US locale
776 * @param {String} properties.mon_decimal_point
778 * The symbol to be used as the decimal delimiter (radix character)
781 * @param {String} properties.mon_thousands_sep
783 * The symbol to be used as a separator for groups of digits to the
784 * left of the decimal delimiter.
787 * @param {String} properties.mon_grouping
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.
800 * @param {String} properties.positive_sign
802 * The string to indicate a non-negative monetary amount.
805 * @param {String} properties.negative_sign
807 * The string to indicate a negative monetary amount.
810 * @param {integer Number} properties.frac_digits
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.
817 * @param {integer Number} properties.int_frac_digits
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.
824 * @param {integer Number} properties.p_cs_precedes
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.
831 * @param {integer Number} properties.n_cs_precedes
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.
838 * @param {integer Number} properties.p_sep_by_space
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
844 * <p>0 No space separates the currency symbol and value.</p>
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>
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>
855 * @param {integer Number} properties.n_sep_by_space
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.
862 * @param {integer Number} properties.p_sign_posn
864 * An integer set to a value indicating the positioning of the
865 * positive_sign for a monetary quantity with a non-negative value:
867 * <p>0 Parentheses enclose the quantity and the currency_symbol.</p>
869 * <p>1 The sign string precedes the quantity and the currency_symbol.</p>
871 * <p>2 The sign string succeeds the quantity and the currency_symbol.</p>
873 * <p>3 The sign string precedes the currency_symbol.</p>
875 * <p>4 The sign string succeeds the currency_symbol.</p>
878 * @param {integer Number} properties.n_sign_posn
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.
885 * @param {integer Number} properties.int_p_cs_precedes
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.
892 * @param {integer Number} properties.int_n_cs_precedes
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.
899 * @param {integer Number} properties.int_p_sep_by_space
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.
906 * @param {integer Number} properties.int_n_sep_by_space
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.
913 * @param {integer Number} properties.int_p_sign_posn
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.
920 * @param {integer Number} properties.int_n_sign_posn
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.
927 * @param {String[] | String} properties.abday
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.
937 * @param {String[] | String} properties.day
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.
947 * @param {String[] | String} properties.abmon
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.
957 * @param {String[] | String} properties.mon
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
967 * @param {String} properties.d_fmt
969 * The appropriate date representation. The string may contain any
970 * combination of characters and conversion specifications (%<char>).
973 * @param {String} properties.t_fmt
975 * The appropriate time representation. The string may contain any
976 * combination of characters and conversion specifications (%<char>).
979 * @param {String} properties.d_t_fmt
981 * The appropriate date and time representation. The string may contain
982 * any combination of characters and conversion specifications (%<char>).
985 * @param {String[] | String} properties.am_pm
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.
995 * @throws @throws Error on a undefined or invalid locale property.
997 jsworld.Locale = function(properties) {
1003 * @description Identifies the class for internal library purposes.
1005 this._className = "jsworld.Locale";
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";...
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.
1021 * @returns {String[]} The parsed (and checked) items.
1023 * @throws Error on missing definition, unexpected item count or
1024 * missing double-quotes.
1026 this._parseList = function(names, expectedItems) {
1030 if (names == null) {
1031 throw "Names not defined";
1033 else if (typeof names == "object") {
1034 // we got a ready array
1037 else if (typeof names == "string") {
1038 // we got the names in the classic POSIX form, do parse
1039 array = names.split(";", expectedItems);
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);
1046 throw "Missing double quotes";
1050 throw "Names must be an array or a string";
1053 if (array.length != expectedItems)
1054 throw "Expected " + expectedItems + " items, got " + array.length;
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.
1066 * @param {String} formatString The format string.
1068 * @returns {String} The validated string.
1070 * @throws Error on null or empty string.
1072 this._validateFormatString = function(formatString) {
1074 if (typeof formatString == "string" && formatString.length > 0)
1075 return formatString;
1077 throw "Empty or no string";
1083 if (properties == null || typeof properties != "object")
1084 throw "Error: Invalid/missing locale properties";
1087 if (typeof properties.decimal_point != "string")
1088 throw "Error: Invalid/missing decimal_point property";
1090 this.decimal_point = properties.decimal_point;
1093 if (typeof properties.thousands_sep != "string")
1094 throw "Error: Invalid/missing thousands_sep property";
1096 this.thousands_sep = properties.thousands_sep;
1099 if (typeof properties.grouping != "string")
1100 throw "Error: Invalid/missing grouping property";
1102 this.grouping = properties.grouping;
1107 if (typeof properties.int_curr_symbol != "string")
1108 throw "Error: Invalid/missing int_curr_symbol property";
1110 if (! /[A-Za-z]{3}.?/.test(properties.int_curr_symbol))
1111 throw "Error: Invalid int_curr_symbol property";
1113 this.int_curr_symbol = properties.int_curr_symbol;
1116 if (typeof properties.currency_symbol != "string")
1117 throw "Error: Invalid/missing currency_symbol property";
1119 this.currency_symbol = properties.currency_symbol;
1122 if (typeof properties.frac_digits != "number" && properties.frac_digits < 0)
1123 throw "Error: Invalid/missing frac_digits property";
1125 this.frac_digits = properties.frac_digits;
1128 // may be empty string/null for currencies with no fractional part
1129 if (properties.mon_decimal_point === null || properties.mon_decimal_point == "") {
1131 if (this.frac_digits > 0)
1132 throw "Error: Undefined mon_decimal_point property";
1134 properties.mon_decimal_point = "";
1137 if (typeof properties.mon_decimal_point != "string")
1138 throw "Error: Invalid/missing mon_decimal_point property";
1140 this.mon_decimal_point = properties.mon_decimal_point;
1143 if (typeof properties.mon_thousands_sep != "string")
1144 throw "Error: Invalid/missing mon_thousands_sep property";
1146 this.mon_thousands_sep = properties.mon_thousands_sep;
1149 if (typeof properties.mon_grouping != "string")
1150 throw "Error: Invalid/missing mon_grouping property";
1152 this.mon_grouping = properties.mon_grouping;
1155 if (typeof properties.positive_sign != "string")
1156 throw "Error: Invalid/missing positive_sign property";
1158 this.positive_sign = properties.positive_sign;
1161 if (typeof properties.negative_sign != "string")
1162 throw "Error: Invalid/missing negative_sign property";
1164 this.negative_sign = properties.negative_sign;
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";
1171 this.p_cs_precedes = properties.p_cs_precedes;
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";
1177 this.n_cs_precedes = properties.n_cs_precedes;
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";
1185 this.p_sep_by_space = properties.p_sep_by_space;
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";
1193 this.n_sep_by_space = properties.n_sep_by_space;
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";
1203 this.p_sign_posn = properties.p_sign_posn;
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";
1213 this.n_sign_posn = properties.n_sign_posn;
1216 if (typeof properties.int_frac_digits != "number" && properties.int_frac_digits < 0)
1217 throw "Error: Invalid/missing int_frac_digits property";
1219 this.int_frac_digits = properties.int_frac_digits;
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";
1225 this.int_p_cs_precedes = properties.int_p_cs_precedes;
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";
1231 this.int_n_cs_precedes = properties.int_n_cs_precedes;
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";
1239 this.int_p_sep_by_space = properties.int_p_sep_by_space;
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";
1247 this.int_n_sep_by_space = properties.int_n_sep_by_space;
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";
1257 this.int_p_sign_posn = properties.int_p_sign_posn;
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";
1267 this.int_n_sign_posn = properties.int_n_sign_posn;
1272 if (properties == null || typeof properties != "object")
1273 throw "Error: Invalid/missing time locale properties";
1276 // parse the supported POSIX LC_TIME properties
1280 this.abday = this._parseList(properties.abday, 7);
1283 throw "Error: Invalid abday property: " + error;
1288 this.day = this._parseList(properties.day, 7);
1291 throw "Error: Invalid day property: " + error;
1296 this.abmon = this._parseList(properties.abmon, 12);
1298 throw "Error: Invalid abmon property: " + error;
1303 this.mon = this._parseList(properties.mon, 12);
1305 throw "Error: Invalid mon property: " + error;
1310 this.d_fmt = this._validateFormatString(properties.d_fmt);
1312 throw "Error: Invalid d_fmt property: " + error;
1317 this.t_fmt = this._validateFormatString(properties.t_fmt);
1319 throw "Error: Invalid t_fmt property: " + error;
1324 this.d_t_fmt = this._validateFormatString(properties.d_t_fmt);
1326 throw "Error: Invalid d_t_fmt property: " + error;
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];
1335 // ignore empty/null string errors
1344 * @description Returns the abbreviated name of the specified weekday.
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
1351 * @returns {String | String[]} The abbreviated name of the specified weekday
1352 * or an array of all abbreviated weekday names.
1354 * @throws Error on invalid argument.
1356 this.getAbbreviatedWeekdayName = function(weekdayNum) {
1358 if (typeof weekdayNum == "undefined" || weekdayNum === null)
1361 if (! jsworld._isInteger(weekdayNum) || weekdayNum < 0 || weekdayNum > 6)
1362 throw "Error: Invalid weekday argument, must be an integer [0..6]";
1364 return this.abday[weekdayNum];
1371 * @description Returns the name of the specified weekday.
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.
1377 * @returns {String | String[]} The name of the specified weekday or an
1378 * array of all weekday names.
1380 * @throws Error on invalid argument.
1382 this.getWeekdayName = function(weekdayNum) {
1384 if (typeof weekdayNum == "undefined" || weekdayNum === null)
1387 if (! jsworld._isInteger(weekdayNum) || weekdayNum < 0 || weekdayNum > 6)
1388 throw "Error: Invalid weekday argument, must be an integer [0..6]";
1390 return this.day[weekdayNum];
1397 * @description Returns the abbreviated name of the specified month.
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.
1403 * @returns {String | String[]} The abbreviated name of the specified month
1404 * or an array of all abbreviated month names.
1406 * @throws Error on invalid argument.
1408 this.getAbbreviatedMonthName = function(monthNum) {
1410 if (typeof monthNum == "undefined" || monthNum === null)
1413 if (! jsworld._isInteger(monthNum) || monthNum < 0 || monthNum > 11)
1414 throw "Error: Invalid month argument, must be an integer [0..11]";
1416 return this.abmon[monthNum];
1423 * @description Returns the name of the specified month.
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.
1429 * @returns {String | String[]} The name of the specified month or an array
1430 * of all month names.
1432 * @throws Error on invalid argument.
1434 this.getMonthName = function(monthNum) {
1436 if (typeof monthNum == "undefined" || monthNum === null)
1439 if (! jsworld._isInteger(monthNum) || monthNum < 0 || monthNum > 11)
1440 throw "Error: Invalid month argument, must be an integer [0..11]";
1442 return this.mon[monthNum];
1450 * @description Gets the decimal delimiter (radix) character for
1451 * numeric quantities.
1453 * @returns {String} The radix character.
1455 this.getDecimalPoint = function() {
1457 return this.decimal_point;
1464 * @description Gets the local shorthand currency symbol.
1466 * @returns {String} The currency symbol.
1468 this.getCurrencySymbol = function() {
1470 return this.currency_symbol;
1477 * @description Gets the internaltion currency symbol (ISO-4217 code).
1479 * @returns {String} The international currency symbol.
1481 this.getIntCurrencySymbol = function() {
1483 return this.int_curr_symbol.substring(0,3);
1490 * @description Gets the position of the local (shorthand) currency
1491 * symbol relative to the amount. Assumes a non-negative amount.
1493 * @returns {Boolean} True if the symbol precedes the amount, false if
1494 * the symbol succeeds the amount.
1496 this.currencySymbolPrecedes = function() {
1498 if (this.p_cs_precedes == 1)
1508 * @description Gets the position of the international (ISO-4217 code)
1509 * currency symbol relative to the amount. Assumes a non-negative
1512 * @returns {Boolean} True if the symbol precedes the amount, false if
1513 * the symbol succeeds the amount.
1515 this.intCurrencySymbolPrecedes = function() {
1517 if (this.int_p_cs_precedes == 1)
1528 * @description Gets the decimal delimiter (radix) for monetary
1531 * @returns {String} The radix character.
1533 this.getMonetaryDecimalPoint = function() {
1535 return this.mon_decimal_point;
1542 * @description Gets the number of fractional digits for local
1543 * (shorthand) symbol formatting.
1545 * @returns {integer Number} The number of fractional digits.
1547 this.getFractionalDigits = function() {
1549 return this.frac_digits;
1556 * @description Gets the number of fractional digits for
1557 * international (ISO-4217 code) formatting.
1559 * @returns {integer Number} The number of fractional digits.
1561 this.getIntFractionalDigits = function() {
1563 return this.int_frac_digits;
1571 * Class for localised formatting of numbers.
1573 * <p>See: <a href="http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap07.html#tag_07_03_04">
1574 * POSIX LC_NUMERIC</a>.
1579 * @description Creates a new numeric formatter for the specified locale.
1581 * @param {jsworld.Locale} locale A locale object specifying the required
1582 * POSIX LC_NUMERIC formatting properties.
1584 * @throws Error on constructor failure.
1586 jsworld.NumericFormatter = function(locale) {
1588 if (typeof locale != "object" || locale._className != "jsworld.Locale")
1589 throw "Constructor error: You must provide a valid jsworld.Locale instance";
1597 * @description Formats a decimal numeric value according to the preset
1600 * @param {Number|String} number The number to format.
1601 * @param {String} [options] Options to modify the formatted output:
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'
1609 * @returns {String} The formatted number.
1611 * @throws "Error: Invalid input" on bad input.
1613 this.format = function(number, options) {
1615 if (typeof number == "string")
1616 number = jsworld._trim(number);
1618 if (! jsworld._isNumber(number))
1619 throw "Error: The input is not a number";
1621 var floatAmount = parseFloat(number, 10);
1623 // get the required precision
1624 var reqPrecision = jsworld._getPrecision(options);
1626 // round to required precision
1627 if (reqPrecision != -1)
1628 floatAmount = Math.round(floatAmount * Math.pow(10, reqPrecision)) / Math.pow(10, reqPrecision);
1631 // convert the float number to string and parse into
1632 // object with properties integer and fraction
1633 var parsedAmount = jsworld._splitNumber(String(floatAmount));
1635 // format integer part with grouping chars
1636 var formattedIntegerPart;
1638 if (floatAmount === 0)
1639 formattedIntegerPart = "0";
1641 formattedIntegerPart = jsworld._hasOption("^", options) ?
1642 parsedAmount.integer :
1643 jsworld._formatIntegerPart(parsedAmount.integer,
1645 this.lc.thousands_sep);
1647 // format the fractional part
1648 var formattedFractionPart =
1649 reqPrecision != -1 ?
1650 jsworld._formatFractionPart(parsedAmount.fraction, reqPrecision) :
1651 parsedAmount.fraction;
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;
1661 if (jsworld._hasOption("~", options) || floatAmount === 0) {
1662 // suppress both '+' and '-' signs, i.e. return abs value
1663 return formattedAmount;
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)
1672 return "-" + formattedAmount;
1675 return formattedAmount;
1678 // positive amount with no '+' sign
1679 return formattedAmount;
1688 * Class for localised formatting of dates and times.
1690 * <p>See: <a href="http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap07.html#tag_07_03_05">
1691 * POSIX LC_TIME</a>.
1695 * @description Creates a new date/time formatter for the specified locale.
1697 * @param {jsworld.Locale} locale A locale object specifying the required
1698 * POSIX LC_TIME formatting properties.
1700 * @throws Error on constructor failure.
1702 jsworld.DateTimeFormatter = function(locale) {
1705 if (typeof locale != "object" || locale._className != "jsworld.Locale")
1706 throw "Constructor error: You must provide a valid jsworld.Locale instance.";
1714 * @description Formats a date according to the preset locale.
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".
1720 * @returns {String} The formatted date
1722 * @throws Error on invalid date argument
1724 this.formatDate = function(date) {
1728 if (typeof date == "string") {
1729 // assume ISO-8601 date string
1731 d = jsworld.parseIsoDate(date);
1733 // try full ISO-8601 date/time string
1734 d = jsworld.parseIsoDateTime(date);
1737 else if (date !== null && typeof date == "object") {
1738 // assume ready Date object
1742 throw "Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string";
1745 return this._applyFormatting(d, this.lc.d_fmt);
1752 * @description Formats a time according to the preset locale.
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".
1758 * @returns {String} The formatted time.
1760 * @throws Error on invalid date argument.
1762 this.formatTime = function(date) {
1766 if (typeof date == "string") {
1767 // assume ISO-8601 time string
1769 d = jsworld.parseIsoTime(date);
1771 // try full ISO-8601 date/time string
1772 d = jsworld.parseIsoDateTime(date);
1775 else if (date !== null && typeof date == "object") {
1776 // assume ready Date object
1780 throw "Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string";
1783 return this._applyFormatting(d, this.lc.t_fmt);
1790 * @description Formats a date/time value according to the preset
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".
1797 * @returns {String} The formatted time.
1799 * @throws Error on invalid argument.
1801 this.formatDateTime = function(date) {
1805 if (typeof date == "string") {
1806 // assume ISO-8601 format
1807 d = jsworld.parseIsoDateTime(date);
1809 else if (date !== null && typeof date == "object") {
1810 // assume ready Date object
1814 throw "Error: Invalid date argument, must be a Date object or an ISO-8601 date/time string";
1817 return this._applyFormatting(d, this.lc.d_t_fmt);
1824 * @description Apples formatting to the Date object according to the
1827 * @param {Date} d A valid Date instance.
1828 * @param {String} s The formatting string with '%' placeholders.
1830 * @returns {String} The formatted string.
1832 this._applyFormatting = function(d, s) {
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() +
1843 jsworld._zeroPad(d.getMonth()+1, 2) +
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) +
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) +
1862 jsworld._zeroPad(d.getMinutes(), 2) +
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());
1869 s = s.replace(/%Z/g, ""); // to do: ignored until a reliable TMZ method found
1871 s = s.replace(/%[a-zA-Z]/g, ""); // ignore all other % sequences
1880 * @description Does 24 to 12 hour conversion.
1882 * @param {integer Number} hour24 Hour [0..23].
1884 * @returns {integer Number} Corresponding hour [1..12].
1886 this._hours12 = function(hour24) {
1889 return 12; // 00h is 12AM
1891 else if (hour24 > 12)
1892 return hour24 - 12; // 1PM to 11PM
1895 return hour24; // 1AM to 12PM
1902 * @description Gets the appropriate localised AM or PM string depending
1903 * on the day hour. Special cases: midnight is 12AM, noon is 12PM.
1905 * @param {integer Number} hour24 Hour [0..23].
1907 * @returns {String} The corresponding localised AM or PM string.
1909 this._getAmPm = function(hour24) {
1921 * @class Class for localised formatting of currency amounts.
1923 * <p>See: <a href="http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap07.html#tag_07_03_03">
1924 * POSIX LC_MONETARY</a>.
1928 * @description Creates a new monetary formatter for the specified locale.
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).
1942 * @throws Error on constructor failure.
1944 jsworld.MonetaryFormatter = function(locale, currencyCode, altIntSymbol) {
1946 if (typeof locale != "object" || locale._className != "jsworld.Locale")
1947 throw "Constructor error: You must provide a valid jsworld.Locale instance";
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.
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
1962 * <p>It is "hard-wired" for referential convenience and is only looked
1963 * up when an overriding currencyCode parameter is supplied.
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
1980 // optional currencyCode argument?
1981 if (typeof currencyCode == "string") {
1982 // user wanted to override the local currency
1983 this.currencyCode = currencyCode.toUpperCase();
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;
1994 // use local currency
1995 this.currencyCode = this.lc.int_curr_symbol.substring(0,3).toUpperCase();
1998 // extract intl. currency separator
1999 this.intSep = this.lc.int_curr_symbol.charAt(3);
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;
2009 // currency doesn't match the local ->
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;
2018 // -> force formatting with intl. sign and parameters
2019 this.internationalFormatting = true;
2027 * @description Gets the currency symbol used in formatting.
2029 * @returns {String} The currency symbol.
2031 this.getCurrencySymbol = function() {
2040 * @description Gets the position of the currency symbol relative to
2041 * the amount. Assumes a non-negative amount and local formatting.
2043 * @param {String} intFlag Optional flag to force international
2044 * formatting by passing the string "i".
2046 * @returns {Boolean} True if the symbol precedes the amount, false if
2047 * the symbol succeeds the amount.
2049 this.currencySymbolPrecedes = function(intFlag) {
2051 if (typeof intFlag == "string" && intFlag == "i") {
2052 // international formatting was forced
2053 if (this.lc.int_p_cs_precedes == 1)
2060 // check whether local formatting is on or off
2061 if (this.internationalFormatting) {
2062 if (this.lc.int_p_cs_precedes == 1)
2068 if (this.lc.p_cs_precedes == 1)
2080 * @description Gets the decimal delimiter (radix) used in formatting.
2082 * @returns {String} The radix character.
2084 this.getDecimalPoint = function() {
2086 return this.lc.mon_decimal_point;
2093 * @description Gets the number of fractional digits. Assumes local
2096 * @param {String} intFlag Optional flag to force international
2097 * formatting by passing the string "i".
2099 * @returns {integer Number} The number of fractional digits.
2101 this.getFractionalDigits = function(intFlag) {
2103 if (typeof intFlag == "string" && intFlag == "i") {
2104 // international formatting was forced
2105 return this.lc.int_frac_digits;
2108 // check whether local formatting is on or off
2109 if (this.internationalFormatting)
2110 return this.lc.int_frac_digits;
2112 return this.lc.frac_digits;
2120 * @description Formats a monetary amount according to the preset
2124 * For local currencies the native shorthand symbol will be used for
2129 * -> the "$" symbol will be used, e.g. $123.45
2131 * For non-local currencies the international ISO-4217 code will be
2132 * used for formatting.
2134 * locale is en_US (which has USD as currency)
2136 * -> the ISO three-letter code will be used, e.g. EUR 123.45
2138 * If the currency is non-local, but an alternative currency symbol was
2139 * provided, this will be used instead.
2141 * locale is en_US (which has USD as currency)
2143 * an alternative symbol is provided - "€"
2144 * -> the alternative symbol will be used, e.g. €123.45
2147 * @param {Number|String} amount The amount to format as currency.
2148 * @param {String} [options] Options to modify the formatted output:
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
2156 * @returns The formatted currency amount as string.
2158 * @throws "Error: Invalid amount" on bad amount.
2160 this.format = function(amount, options) {
2162 // if the amount is passed as string, check that it parses to a float
2165 if (typeof amount == "string") {
2166 amount = jsworld._trim(amount);
2167 floatAmount = parseFloat(amount);
2169 if (typeof floatAmount != "number" || isNaN(floatAmount))
2170 throw "Error: Amount string not a number";
2172 else if (typeof amount == "number") {
2173 floatAmount = amount;
2176 throw "Error: Amount not a number";
2179 // get the required precision, ".n" option arg overrides default locale config
2180 var reqPrecision = jsworld._getPrecision(options);
2182 if (reqPrecision == -1) {
2183 if (this.internationalFormatting || jsworld._hasOption("i", options))
2184 reqPrecision = this.lc.int_frac_digits;
2186 reqPrecision = this.lc.frac_digits;
2190 floatAmount = Math.round(floatAmount * Math.pow(10, reqPrecision)) / Math.pow(10, reqPrecision);
2193 // convert the float amount to string and parse into
2194 // object with properties integer and fraction
2195 var parsedAmount = jsworld._splitNumber(String(floatAmount));
2197 // format integer part with grouping chars
2198 var formattedIntegerPart;
2200 if (floatAmount === 0)
2201 formattedIntegerPart = "0";
2203 formattedIntegerPart = jsworld._hasOption("^", options) ?
2204 parsedAmount.integer :
2205 jsworld._formatIntegerPart(parsedAmount.integer,
2206 this.lc.mon_grouping,
2207 this.lc.mon_thousands_sep);
2210 // format the fractional part
2211 var formattedFractionPart;
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);
2219 formattedFractionPart =
2220 jsworld._formatFractionPart(parsedAmount.fraction, this.lc.frac_digits);
2223 // pad fraction with trailing zeros according to optional format parameter
2224 formattedFractionPart =
2225 jsworld._formatFractionPart(parsedAmount.fraction, reqPrecision);
2229 // join integer and decimal parts using the mon_decimal_point property
2232 if (this.lc.frac_digits > 0 || formattedFractionPart.length)
2233 quantity = formattedIntegerPart + this.lc.mon_decimal_point + formattedFractionPart;
2235 quantity = formattedIntegerPart;
2238 // do final formatting with sign and symbol
2239 if (jsworld._hasOption("~", options)) {
2243 var suppressSymbol = jsworld._hasOption("!", options) ? true : false;
2245 var sign = floatAmount < 0 ? "-" : "+";
2247 if (this.internationalFormatting || jsworld._hasOption("i", options)) {
2249 // format with ISO-4217 code (suppressed or not)
2251 return this._formatAsInternationalCurrencyWithNoSym(sign, quantity);
2253 return this._formatAsInternationalCurrency(sign, quantity);
2256 // format with local currency code (suppressed or not)
2258 return this._formatAsLocalCurrencyWithNoSym(sign, quantity);
2260 return this._formatAsLocalCurrency(sign, quantity);
2269 * @description Assembles the final string with sign, separator and symbol as local
2272 * @param {String} sign The amount sign: "+" or "-".
2273 * @param {String} q The formatted quantity (unsigned).
2275 * @returns {String} The final formatted string.
2277 this._formatAsLocalCurrency = function (sign, q) {
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}
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 + ")";
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 + ")";
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 + ")";
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 + ")";
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
2378 else if (sign == "-") {
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 + ")";
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 + ")";
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 + ")";
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 + ")";
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
2475 // throw error if we fall through
2476 throw "Error: Invalid POSIX LC MONETARY definition";
2483 * @description Assembles the final string with sign, separator and ISO-4217
2486 * @param {String} sign The amount sign: "+" or "-".
2487 * @param {String} q The formatted quantity (unsigned).
2489 * @returns {String} The final formatted string.
2491 this._formatAsInternationalCurrency = function (sign, q) {
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}
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 + ")";
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 + ")";
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 + ")";
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 + ")";
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
2593 else if (sign == "-") {
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 + ")";
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 + ")";
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 + ")";
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 + ")";
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
2690 // throw error if we fall through
2691 throw "Error: Invalid POSIX LC MONETARY definition";
2698 * @description Assembles the final string with sign and separator, but suppress the
2699 * local currency symbol.
2701 * @param {String} sign The amount sign: "+" or "-".
2702 * @param {String} q The formatted quantity (unsigned).
2704 * @returns {String} The final formatted string
2706 this._formatAsLocalCurrencyWithNoSym = function (sign, q) {
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}
2714 if (this.lc.p_sign_posn === 0) {
2715 return "(" + q + ")";
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
2799 else if (sign == "-") {
2801 // parentheses enclose q + sym
2802 if (this.lc.n_sign_posn === 0) {
2803 return "(" + q + ")";
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
2887 // throw error if we fall through
2888 throw "Error: Invalid POSIX LC MONETARY definition";
2895 * @description Assembles the final string with sign and separator, but suppress
2896 * the ISO-4217 currency code.
2898 * @param {String} sign The amount sign: "+" or "-".
2899 * @param {String} q The formatted quantity (unsigned).
2901 * @returns {String} The final formatted string.
2903 this._formatAsInternationalCurrencyWithNoSym = function (sign, q) {
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}
2911 if (this.lc.int_p_sign_posn === 0) {
2912 return "(" + q + ")";
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
2996 else if (sign == "-") {
2998 // parentheses enclose q + sym
2999 if (this.lc.int_n_sign_posn === 0) {
3000 return "(" + q + ")";
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
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;
3084 // throw error if we fall through
3085 throw "Error: Invalid POSIX LC_MONETARY definition";
3092 * Class for parsing localised number strings.
3096 * @description Creates a new numeric parser for the specified locale.
3098 * @param {jsworld.Locale} locale A locale object specifying the required
3099 * POSIX LC_NUMERIC formatting properties.
3101 * @throws Error on constructor failure.
3103 jsworld.NumericParser = function(locale) {
3105 if (typeof locale != "object" || locale._className != "jsworld.Locale")
3106 throw "Constructor error: You must provide a valid jsworld.Locale instance";
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.
3118 * @param {String} formattedNumber The formatted number.
3120 * @returns {Number} The parsed number.
3122 * @throws Error on a parse exception.
3124 this.parse = function(formattedNumber) {
3126 if (typeof formattedNumber != "string")
3127 throw "Parse error: Argument must be a string";
3130 var s = jsworld._trim(formattedNumber);
3132 // remove any thousand separator symbols
3133 s = jsworld._stringReplaceAll(formattedNumber, this.lc.thousands_sep, "");
3135 // replace any local decimal point symbols with the symbol used
3136 // in JavaScript "."
3137 s = jsworld._stringReplaceAll(s, this.lc.decimal_point, ".");
3139 // test if the string represents a number
3140 if (jsworld._isNumber(s))
3141 return parseFloat(s, 10);
3143 throw "Parse error: Invalid number string";
3150 * Class for parsing localised date and time strings.
3154 * @description Creates a new date/time parser for the specified locale.
3156 * @param {jsworld.Locale} locale A locale object specifying the required
3157 * POSIX LC_TIME formatting properties.
3159 * @throws Error on constructor failure.
3161 jsworld.DateTimeParser = function(locale) {
3163 if (typeof locale != "object" || locale._className != "jsworld.Locale")
3164 throw "Constructor error: You must provide a valid jsworld.Locale instance.";
3172 * @description Parses a time string formatted according to the
3173 * POSIX LC_TIME t_fmt property of the preset locale.
3175 * @param {String} formattedTime The formatted time.
3177 * @returns {String} The parsed time in ISO-8601 format (HH:MM:SS), e.g.
3180 * @throws Error on a parse exception.
3182 this.parseTime = function(formattedTime) {
3184 if (typeof formattedTime != "string")
3185 throw "Parse error: Argument must be a string";
3187 var dt = this._extractTokens(this.lc.t_fmt, formattedTime);
3189 var timeDefined = false;
3191 if (dt.hour !== null && dt.minute !== null && dt.second !== null) {
3194 else if (dt.hourAmPm !== null && dt.am !== null && dt.minute !== null && dt.second !== null) {
3196 // AM [12(midnight), 1 .. 11]
3197 if (dt.hourAmPm == 12)
3200 dt.hour = parseInt(dt.hourAmPm, 10);
3203 // PM [12(noon), 1 .. 11]
3204 if (dt.hourAmPm == 12)
3207 dt.hour = parseInt(dt.hourAmPm, 10) + 12;
3213 return jsworld._zeroPad(dt.hour, 2) +
3215 jsworld._zeroPad(dt.minute, 2) +
3217 jsworld._zeroPad(dt.second, 2);
3219 throw "Parse error: Invalid/ambiguous time string";
3226 * @description Parses a date string formatted according to the
3227 * POSIX LC_TIME d_fmt property of the preset locale.
3229 * @param {String} formattedDate The formatted date, must be valid.
3231 * @returns {String} The parsed date in ISO-8601 format (YYYY-MM-DD),
3232 * e.g. "2010-03-31".
3234 * @throws Error on a parse exception.
3236 this.parseDate = function(formattedDate) {
3238 if (typeof formattedDate != "string")
3239 throw "Parse error: Argument must be a string";
3241 var dt = this._extractTokens(this.lc.d_fmt, formattedDate);
3243 var dateDefined = false;
3245 if (dt.year !== null && dt.month !== null && dt.day !== null) {
3250 return jsworld._zeroPad(dt.year, 4) +
3252 jsworld._zeroPad(dt.month, 2) +
3254 jsworld._zeroPad(dt.day, 2);
3256 throw "Parse error: Invalid date string";
3263 * @description Parses a date/time string formatted according to the
3264 * POSIX LC_TIME d_t_fmt property of the preset locale.
3266 * @param {String} formattedDateTime The formatted date/time, must be
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".
3272 * @throws Error on a parse exception.
3274 this.parseDateTime = function(formattedDateTime) {
3276 if (typeof formattedDateTime != "string")
3277 throw "Parse error: Argument must be a string";
3279 var dt = this._extractTokens(this.lc.d_t_fmt, formattedDateTime);
3281 var timeDefined = false;
3282 var dateDefined = false;
3284 if (dt.hour !== null && dt.minute !== null && dt.second !== null) {
3287 else if (dt.hourAmPm !== null && dt.am !== null && dt.minute !== null && dt.second !== null) {
3289 // AM [12(midnight), 1 .. 11]
3290 if (dt.hourAmPm == 12)
3293 dt.hour = parseInt(dt.hourAmPm, 10);
3296 // PM [12(noon), 1 .. 11]
3297 if (dt.hourAmPm == 12)
3300 dt.hour = parseInt(dt.hourAmPm, 10) + 12;
3305 if (dt.year !== null && dt.month !== null && dt.day !== null) {
3309 if (dateDefined && timeDefined)
3310 return jsworld._zeroPad(dt.year, 4) +
3312 jsworld._zeroPad(dt.month, 2) +
3314 jsworld._zeroPad(dt.day, 2) +
3316 jsworld._zeroPad(dt.hour, 2) +
3318 jsworld._zeroPad(dt.minute, 2) +
3320 jsworld._zeroPad(dt.second, 2);
3322 throw "Parse error: Invalid/ambiguous date/time string";
3329 * @description Parses a string according to the specified format
3332 * @param {String} fmtSpec The format specification, e.g. "%I:%M:%S %p".
3333 * @param {String} s The string to parse.
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.
3339 * @throws Error on a parse exception.
3341 this._extractTokens = function(fmtSpec, s) {
3343 // the return object containing the parsed date/time properties
3345 // for date and date/time strings
3350 // for time and date/time strings
3357 // used internally only
3362 // extract and process each token in the date/time spec
3363 while (fmtSpec.length > 0) {
3365 // Do we have a valid "%\w" placeholder in stream?
3366 if (fmtSpec.charAt(0) == "%" && fmtSpec.charAt(1) != "") {
3369 var placeholder = fmtSpec.substring(0,2);
3371 if (placeholder == "%%") {
3375 else if (placeholder == "%a") {
3376 // abbreviated weekday name
3377 for (var i = 0; i < this.lc.abday.length; i++) {
3379 if (jsworld._stringStartsWith(s, this.lc.abday[i])) {
3381 s = s.substring(this.lc.abday[i].length);
3386 if (dt.weekday === null)
3387 throw "Parse error: Unrecognised abbreviated weekday name (%a)";
3389 else if (placeholder == "%A") {
3391 for (var i = 0; i < this.lc.day.length; i++) {
3393 if (jsworld._stringStartsWith(s, this.lc.day[i])) {
3395 s = s.substring(this.lc.day[i].length);
3400 if (dt.weekday === null)
3401 throw "Parse error: Unrecognised weekday name (%A)";
3403 else if (placeholder == "%b" || placeholder == "%h") {
3404 // abbreviated month name
3405 for (var i = 0; i < this.lc.abmon.length; i++) {
3407 if (jsworld._stringStartsWith(s, this.lc.abmon[i])) {
3409 s = s.substring(this.lc.abmon[i].length);
3414 if (dt.month === null)
3415 throw "Parse error: Unrecognised abbreviated month name (%b)";
3417 else if (placeholder == "%B") {
3419 for (var i = 0; i < this.lc.mon.length; i++) {
3421 if (jsworld._stringStartsWith(s, this.lc.mon[i])) {
3423 s = s.substring(this.lc.mon[i].length);
3428 if (dt.month === null)
3429 throw "Parse error: Unrecognised month name (%B)";
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);
3438 throw "Parse error: Unrecognised day of the month (%d)";
3440 else if (placeholder == "%e") {
3441 // day of the month [1..31]
3443 // Note: if %e is leading in fmt string -> space padded!
3445 var day = s.match(/^\s?(\d{1,2})/);
3446 dt.day = parseInt(day, 10);
3448 if (isNaN(dt.day) || dt.day < 1 || dt.day > 31)
3449 throw "Parse error: Unrecognised day of the month (%e)";
3451 s = s.substring(day.length);
3453 else if (placeholder == "%F") {
3454 // equivalent to %Y-%m-%d (ISO-8601 date format)
3457 if (/^\d\d\d\d/.test(s)) {
3458 dt.year = parseInt(s.substring(0,4), 10);
3462 throw "Parse error: Unrecognised date (%F)";
3466 if (jsworld._stringStartsWith(s, "-"))
3469 throw "Parse error: Unrecognised date (%F)";
3472 if (/^0[1-9]|1[0-2]/.test(s)) {
3473 dt.month = parseInt(s.substring(0,2), 10);
3477 throw "Parse error: Unrecognised date (%F)";
3480 if (jsworld._stringStartsWith(s, "-"))
3483 throw "Parse error: Unrecognised date (%F)";
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);
3491 throw "Parse error: Unrecognised date (%F)";
3493 else if (placeholder == "%H") {
3495 if (/^[0-1][0-9]|2[0-3]/.test(s)) {
3496 dt.hour = parseInt(s.substring(0,2), 10);
3500 throw "Parse error: Unrecognised hour (%H)";
3502 else if (placeholder == "%I") {
3504 if (/^0[1-9]|1[0-2]/.test(s)) {
3505 dt.hourAmPm = parseInt(s.substring(0,2), 10);
3509 throw "Parse error: Unrecognised hour (%I)";
3511 else if (placeholder == "%k") {
3513 var h = s.match(/^(\d{1,2})/);
3514 dt.hour = parseInt(h, 10);
3516 if (isNaN(dt.hour) || dt.hour < 0 || dt.hour > 23)
3517 throw "Parse error: Unrecognised hour (%k)";
3519 s = s.substring(h.length);
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);
3526 if (isNaN(dt.hourAmPm) || dt.hourAmPm < 1 || dt.hourAmPm > 12)
3527 throw "Parse error: Unrecognised hour (%l)";
3529 s = s.substring(h.length);
3531 else if (placeholder == "%m") {
3533 if (/^0[1-9]|1[0-2]/.test(s)) {
3534 dt.month = parseInt(s.substring(0,2), 10);
3538 throw "Parse error: Unrecognised month (%m)";
3540 else if (placeholder == "%M") {
3542 if (/^[0-5][0-9]/.test(s)) {
3543 dt.minute = parseInt(s.substring(0,2), 10);
3547 throw "Parse error: Unrecognised minute (%M)";
3549 else if (placeholder == "%n") {
3552 if (s.charAt(0) == "\n")
3555 throw "Parse error: Unrecognised new line (%n)";
3557 else if (placeholder == "%p") {
3558 // locale's equivalent of AM/PM
3559 if (jsworld._stringStartsWith(s, this.lc.am)) {
3561 s = s.substring(this.lc.am.length);
3563 else if (jsworld._stringStartsWith(s, this.lc.pm)) {
3565 s = s.substring(this.lc.pm.length);
3568 throw "Parse error: Unrecognised AM/PM value (%p)";
3570 else if (placeholder == "%P") {
3571 // same as %p but forced lower case
3572 if (jsworld._stringStartsWith(s, this.lc.am.toLowerCase())) {
3574 s = s.substring(this.lc.am.length);
3576 else if (jsworld._stringStartsWith(s, this.lc.pm.toLowerCase())) {
3578 s = s.substring(this.lc.pm.length);
3581 throw "Parse error: Unrecognised AM/PM value (%P)";
3583 else if (placeholder == "%R") {
3587 if (/^[0-1][0-9]|2[0-3]/.test(s)) {
3588 dt.hour = parseInt(s.substring(0,2), 10);
3592 throw "Parse error: Unrecognised time (%R)";
3595 if (jsworld._stringStartsWith(s, ":"))
3598 throw "Parse error: Unrecognised time (%R)";
3601 if (/^[0-5][0-9]/.test(s)) {
3602 dt.minute = parseInt(s.substring(0,2), 10);
3606 throw "Parse error: Unrecognised time (%R)";
3609 else if (placeholder == "%S") {
3611 if (/^[0-5][0-9]/.test(s)) {
3612 dt.second = parseInt(s.substring(0,2), 10);
3616 throw "Parse error: Unrecognised second (%S)";
3618 else if (placeholder == "%T") {
3622 if (/^[0-1][0-9]|2[0-3]/.test(s)) {
3623 dt.hour = parseInt(s.substring(0,2), 10);
3627 throw "Parse error: Unrecognised time (%T)";
3630 if (jsworld._stringStartsWith(s, ":"))
3633 throw "Parse error: Unrecognised time (%T)";
3636 if (/^[0-5][0-9]/.test(s)) {
3637 dt.minute = parseInt(s.substring(0,2), 10);
3641 throw "Parse error: Unrecognised time (%T)";
3644 if (jsworld._stringStartsWith(s, ":"))
3647 throw "Parse error: Unrecognised time (%T)";
3650 if (/^[0-5][0-9]/.test(s)) {
3651 dt.second = parseInt(s.substring(0,2), 10);
3655 throw "Parse error: Unrecognised time (%T)";
3657 else if (placeholder == "%w") {
3659 if (/^\d/.test(s)) {
3660 dt.weekday = parseInt(s.substring(0,1), 10);
3664 throw "Parse error: Unrecognised weekday number (%w)";
3666 else if (placeholder == "%y") {
3668 if (/^\d\d/.test(s)) {
3669 var year2digits = parseInt(s.substring(0,2), 10);
3671 // this conversion to year[nnnn] is arbitrary!!!
3672 if (year2digits > 50)
3673 dt.year = 1900 + year2digits;
3675 dt.year = 2000 + year2digits;
3680 throw "Parse error: Unrecognised year (%y)";
3682 else if (placeholder == "%Y") {
3684 if (/^\d\d\d\d/.test(s)) {
3685 dt.year = parseInt(s.substring(0,4), 10);
3689 throw "Parse error: Unrecognised year (%Y)";
3692 else if (placeholder == "%Z") {
3693 // time-zone place holder is not supported
3695 if (fmtSpec.length === 0)
3696 break; // ignore rest of fmt spec
3699 // remove the spec placeholder that was just parsed
3700 fmtSpec = fmtSpec.substring(2);
3703 // If we don't have a placeholder, the chars
3704 // at pos. 0 of format spec and parsed string must match
3706 // Note: Space chars treated 1:1 !
3708 if (fmtSpec.charAt(0) != s.charAt(0))
3709 throw "Parse error: Unexpected symbol \"" + s.charAt(0) + "\" in date/time string";
3711 fmtSpec = fmtSpec.substring(1);
3716 // parsing finished, return composite date/time object
3724 * Class for parsing localised currency amount strings.
3728 * @description Creates a new monetary parser for the specified locale.
3730 * @param {jsworld.Locale} locale A locale object specifying the required
3731 * POSIX LC_MONETARY formatting properties.
3733 * @throws Error on constructor failure.
3735 jsworld.MonetaryParser = function(locale) {
3737 if (typeof locale != "object" || locale._className != "jsworld.Locale")
3738 throw "Constructor error: You must provide a valid jsworld.Locale instance";
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.
3753 * @param {String} formattedCurrency The formatted currency amount.
3755 * @returns {Number} The parsed amount.
3757 * @throws Error on a parse exception.
3759 this.parse = function(formattedCurrency) {
3761 if (typeof formattedCurrency != "string")
3762 throw "Parse error: Argument must be a string";
3764 // Detect the format type and remove the currency symbol
3765 var symbolType = this._detectCurrencySymbolType(formattedCurrency);
3769 if (symbolType == "local") {
3770 formatType = "local";
3771 s = formattedCurrency.replace(this.lc.getCurrencySymbol(), "");
3773 else if (symbolType == "int") {
3775 s = formattedCurrency.replace(this.lc.getIntCurrencySymbol(), "");
3777 else if (symbolType == "none") {
3778 formatType = "local"; // assume local
3779 s = formattedCurrency;
3782 throw "Parse error: Internal assert failure";
3784 // Remove any thousands separators
3785 s = jsworld._stringReplaceAll(s, this.lc.mon_thousands_sep, "");
3787 // Replace any local radix char with JavaScript's "."
3788 s = s.replace(this.lc.mon_decimal_point, ".");
3790 // Remove all whitespaces
3791 s = s.replace(/\s*/g, "");
3793 // Remove any local non-negative sign
3794 s = this._removeLocalNonNegativeSign(s, formatType);
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);
3801 // Finally, we should be left with a bare parsable decimal number
3802 if (jsworld._isNumber(s))
3803 return parseFloat(s, 10);
3805 throw "Parse error: Invalid currency amount string";
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.
3816 * @param {String} formattedCurrency The the formatted currency string.
3818 * @return {String} With possible values "local", "int" or "none".
3820 this._detectCurrencySymbolType = function(formattedCurrency) {
3822 // Check for whichever sign (int/local) is longer first
3823 // to cover cases such as MOP/MOP$ and ZAR/R
3825 if (this.lc.getCurrencySymbol().length > this.lc.getIntCurrencySymbol().length) {
3827 if (formattedCurrency.indexOf(this.lc.getCurrencySymbol()) != -1)
3829 else if (formattedCurrency.indexOf(this.lc.getIntCurrencySymbol()) != -1)
3835 if (formattedCurrency.indexOf(this.lc.getIntCurrencySymbol()) != -1)
3837 else if (formattedCurrency.indexOf(this.lc.getCurrencySymbol()) != -1)
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.
3852 * @param {String} s The input string.
3853 * @param {String} formatType With possible values "local" or "int".
3855 * @returns {String} The processed string.
3857 this._removeLocalNonNegativeSign = function(s, formatType) {
3859 s = s.replace(this.lc.positive_sign, "");
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(")", "");
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.
3881 * @param {String} s The input string.
3882 * @param {String} formatType With possible values "local" or "int".
3884 * @returns {String} The processed string.
3886 this._normaliseNegativeSign = function(s, formatType) {
3888 // replace local negative symbol with JavaScript's "-"
3889 s = s.replace(this.lc.negative_sign, "-");
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) ) {
3896 if (/^\(\d+\.?\d*\)$/.test(s)) {
3898 s = s.replace("(", "");
3899 s = s.replace(")", "");
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 ) {
3908 if (/^\d+\.?\d*-$/.test(s)) {
3909 s = s.replace("-", "");
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 ) {
3920 if (/^\d+\.?\d*-$/.test(s)) {
3921 s = s.replace("-", "");