uglify-hangs.js 123 KB

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