1 /*< ilib.js */ 2 /* 3 * ilib.js - define the ilib name space 4 * 5 * Copyright © 2012-2015, JEDLSoft 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 /** 22 * @namespace The global namespace that contains general ilib functions useful 23 * to all of ilib 24 * 25 * @version "12.0.4" 26 */ 27 var ilib = ilib || {}; 28 29 /** @private */ 30 ilib._ver = function() { 31 return "12.0.4" 32 ; 33 }; 34 35 /** 36 * Return the current version of ilib. 37 * 38 * @static 39 * @return {string} a version string for this instance of ilib 40 */ 41 ilib.getVersion = function () { 42 // TODO: need some way of getting the version number under dynamic load code 43 return ilib._ver() || "12.0"; 44 }; 45 46 /** 47 * Place where resources and such are eventually assigned. 48 */ 49 ilib.data = { 50 /** @type {{ccc:Object.<string,number>,nfd:Object.<string,string>,nfc:Object.<string,string>,nfkd:Object.<string,string>,nfkc:Object.<string,string>}} */ 51 norm: { 52 ccc: {}, 53 nfd: {}, 54 nfc: {}, 55 nfkd: {}, 56 nfkc: {} 57 }, 58 zoneinfo: { 59 "Etc/UTC":{"o":"0:0","f":"UTC"}, 60 "local":{"f":"local"} 61 }, 62 /** @type {Object.<string,{to:Object.<string,string>,from:Object.<string,number>}>} */ charmaps: {}, 63 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype: null, 64 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_c: null, 65 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_l: null, 66 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_m: null, 67 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_p: null, 68 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_z: null, 69 /** @type {null|Object.<string,Array.<Array.<number>>>} */ scriptToRange: null, 70 /** @type {null|Object.<string,string|Object.<string|Object.<string,string>>>} */ dateformats: null, 71 /** @type {null|Array.<string>} */ timezones: [] 72 }; 73 74 /* 75 if (typeof(window) !== 'undefined') { 76 window["ilib"] = ilib; 77 } 78 */ 79 80 // export ilib for use as a module in nodejs 81 if (typeof(module) !== 'undefined') { 82 83 module.exports.ilib = ilib; // for backwards compatibility with older versions of ilib 84 } 85 86 /** 87 * Sets the pseudo locale. Pseudolocalization (or pseudo-localization) is used for testing 88 * internationalization aspects of software. Instead of translating the text of the software 89 * into a foreign language, as in the process of localization, the textual elements of an application 90 * are replaced with an altered version of the original language.These specific alterations make 91 * the original words appear readable, but include the most problematic characteristics of 92 * the world's languages: varying length of text or characters, language direction, and so on. 93 * Regular Latin pseudo locale: eu-ES and RTL pseudo locale: ps-AF 94 * 95 * @param {string|undefined|null} localename the locale specifier for the pseudo locale 96 */ 97 ilib.setAsPseudoLocale = function (localename) { 98 if (localename) { 99 ilib.pseudoLocales.push(localename) 100 } 101 }; 102 103 /** 104 * Reset the list of pseudo locales back to the default single locale of zxx-XX. 105 * @static 106 */ 107 ilib.clearPseudoLocales = function() { 108 ilib.pseudoLocales = [ 109 "zxx-XX", 110 "zxx-Cyrl-XX", 111 "zxx-Hans-XX", 112 "zxx-Hebr-XX" 113 ]; 114 }; 115 116 ilib.clearPseudoLocales(); 117 118 /** 119 * Return the name of the platform 120 * @private 121 * @static 122 * @return {string} string naming the platform 123 */ 124 ilib._getPlatform = function () { 125 if (!ilib._platform) { 126 try { 127 if (typeof(java.lang.Object) !== 'undefined') { 128 ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino"; 129 return ilib._platform; 130 } 131 } catch (e) {} 132 133 if (typeof(process) !== 'undefined' && typeof(module) !== 'undefined') { 134 ilib._platform = "nodejs"; 135 } else if (typeof(Qt) !== 'undefined') { 136 ilib._platform = "qt"; 137 } else if (typeof(window) !== 'undefined') { 138 ilib._platform = (typeof(PalmSystem) !== 'undefined') ? "webos" : "browser"; 139 } else { 140 ilib._platform = "unknown"; 141 } 142 } 143 return ilib._platform; 144 }; 145 146 /** 147 * If this ilib is running in a browser, return the name of that browser. 148 * @private 149 * @static 150 * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie", 151 * "safari", or "opera"), or undefined if this is not running in a browser or if 152 * the browser name could not be determined 153 */ 154 ilib._getBrowser = function () { 155 var browser = undefined; 156 if (ilib._getPlatform() === "browser") { 157 if (navigator && navigator.userAgent) { 158 if (navigator.userAgent.indexOf("Firefox") > -1) { 159 browser = "firefox"; 160 } 161 if (navigator.userAgent.indexOf("Opera") > -1) { 162 browser = "opera"; 163 } 164 if (navigator.userAgent.indexOf("Chrome") > -1) { 165 browser = "chrome"; 166 } 167 if (navigator.userAgent.indexOf(" .NET") > -1) { 168 browser = "ie"; 169 } 170 if (navigator.userAgent.indexOf("Safari") > -1) { 171 // chrome also has the string Safari in its userAgent, but the chrome case is 172 // already taken care of above 173 browser = "safari"; 174 } 175 if (navigator.userAgent.indexOf("Edge") > -1) { 176 browser = "Edge"; 177 } 178 if (navigator.userAgent.search(/iPad|iPhone|iPod/) > -1) { 179 // Due to constraints of the iOS platform, 180 // all browser must be built on top of the WebKit rendering engine 181 browser = "iOS"; 182 } 183 } 184 } 185 return browser; 186 }; 187 188 /** 189 * Return the value of a global variable given its name in a way that works 190 * correctly for the current platform. 191 * @private 192 * @static 193 * @param {string} name the name of the variable to return 194 * @return {*} the global variable, or undefined if it does not exist 195 */ 196 ilib._global = function(name) { 197 switch (ilib._getPlatform()) { 198 case "rhino": 199 var top = (function() { 200 return (typeof global === 'object') ? global : this; 201 })(); 202 break; 203 case "nodejs": 204 case "trireme": 205 top = typeof(global) !== 'undefined' ? global : this; 206 //console.log("ilib._global: top is " + (typeof(global) !== 'undefined' ? "global" : "this")); 207 break; 208 case "qt": 209 return undefined; 210 default: 211 top = window; 212 break; 213 } 214 try { 215 return top[name]; 216 } catch (e) { 217 return undefined; 218 } 219 }; 220 221 /** 222 * Return true if the global variable is defined on this platform. 223 * @private 224 * @static 225 * @param {string} name the name of the variable to check 226 * @return {boolean} true if the global variable is defined on this platform, false otherwise 227 */ 228 ilib._isGlobal = function(name) { 229 return typeof(ilib._global(name)) !== 'undefined'; 230 }; 231 232 /** 233 * Sets the default locale for all of ilib. This locale will be used 234 * when no explicit locale is passed to any ilib class. If the default 235 * locale is not set, ilib will attempt to use the locale of the 236 * environment it is running in, if it can find that. If not, it will 237 * default to the locale "en-US". If a type of parameter is string, 238 * ilib will take only well-formed BCP-47 tag <p> 239 * 240 * 241 * @static 242 * @param {string|undefined|null} spec the locale specifier for the default locale 243 */ 244 ilib.setLocale = function (spec) { 245 if (typeof(spec) === 'string' || !spec) { 246 ilib.locale = spec; 247 } 248 // else ignore other data types, as we don't have the dependencies 249 // to look into them to find a locale 250 }; 251 252 /** 253 * Return the default locale for all of ilib if one has been set. This 254 * locale will be used when no explicit locale is passed to any ilib 255 * class. If the default 256 * locale is not set, ilib will attempt to use the locale of the 257 * environment it is running in, if it can find that. If not, it will 258 * default to the locale "en-US".<p> 259 * 260 * 261 * @static 262 * @return {string} the locale specifier for the default locale 263 */ 264 ilib.getLocale = function () { 265 if (typeof(ilib.locale) !== 'string') { 266 var plat = ilib._getPlatform(); 267 switch (plat) { 268 case 'browser': 269 // running in a browser 270 if(typeof(navigator.language) !== 'undefined') { 271 ilib.locale = navigator.language.substring(0,3) + navigator.language.substring(3,5).toUpperCase(); // FF/Opera/Chrome/Webkit 272 } 273 if (!ilib.locale) { 274 // IE on Windows 275 var lang = typeof(navigator.browserLanguage) !== 'undefined' ? 276 navigator.browserLanguage : 277 (typeof(navigator.userLanguage) !== 'undefined' ? 278 navigator.userLanguage : 279 (typeof(navigator.systemLanguage) !== 'undefined' ? 280 navigator.systemLanguage : 281 undefined)); 282 if (typeof(lang) !== 'undefined' && lang) { 283 // for some reason, MS uses lower case region tags 284 ilib.locale = lang.substring(0,3) + lang.substring(3,5).toUpperCase(); 285 } 286 } 287 break; 288 case 'webos': 289 // webOS 290 if (typeof(PalmSystem.locales) !== 'undefined' && 291 typeof(PalmSystem.locales.UI) != 'undefined' && 292 PalmSystem.locales.UI.length > 0) { 293 ilib.locale = PalmSystem.locales.UI; 294 } else if (typeof(PalmSystem.locale) !== 'undefined') { 295 ilib.locale = PalmSystem.locale; 296 } 297 break; 298 case 'rhino': 299 if (typeof(environment) !== 'undefined' && environment.user && typeof(environment.user.language) === 'string' && environment.user.language.length > 0) { 300 // running under plain rhino 301 ilib.locale = environment.user.language; 302 if (typeof(environment.user.country) === 'string' && environment.user.country.length > 0) { 303 ilib.locale += '-' + environment.user.country; 304 } 305 } 306 break; 307 case "trireme": 308 // under trireme on rhino emulating nodejs 309 var lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL; 310 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 311 // where language and region are the correct ISO codes separated by 312 // an underscore. This translate it back to the BCP-47 form. 313 if (lang && typeof(lang) !== 'undefined') { 314 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 315 } 316 break; 317 case 'nodejs': 318 // running under nodejs 319 var lang = process.env.LANG || process.env.LC_ALL; 320 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 321 // where language and region are the correct ISO codes separated by 322 // an underscore. This translate it back to the BCP-47 form. 323 if (lang && typeof(lang) !== 'undefined') { 324 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 325 } 326 break; 327 case 'qt': 328 // running in the Javascript engine under Qt/QML 329 var locobj = Qt.locale(); 330 var lang = locobj.name && locobj.name.replace("_", "-") || "en-US"; 331 break; 332 } 333 ilib.locale = typeof(ilib.locale) === 'string' && ilib.locale ? ilib.locale : 'en-US'; 334 if (ilib.locale === "en") { 335 ilib.locale = "en-US"; // hack to get various platforms working correctly 336 } 337 } 338 return ilib.locale; 339 }; 340 341 /** 342 * Sets the default time zone for all of ilib. This time zone will be used when 343 * no explicit time zone is passed to any ilib class. If the default time zone 344 * is not set, ilib will attempt to use the time zone of the 345 * environment it is running in, if it can find that. If not, it will 346 * default to the the UTC zone "Etc/UTC".<p> 347 * 348 * 349 * @static 350 * @param {string} tz the name of the time zone to set as the default time zone 351 */ 352 ilib.setTimeZone = function (tz) { 353 ilib.tz = tz || ilib.tz; 354 }; 355 356 /** 357 * Return the default time zone for all of ilib if one has been set. This 358 * time zone will be used when no explicit time zone is passed to any ilib 359 * class. If the default time zone 360 * is not set, ilib will attempt to use the locale of the 361 * environment it is running in, if it can find that. If not, it will 362 * default to the the zone "local".<p> 363 * 364 * 365 * @static 366 * @return {string} the default time zone for ilib 367 */ 368 ilib.getTimeZone = function() { 369 if (typeof(ilib.tz) === 'undefined') { 370 if (typeof(navigator) !== 'undefined' && typeof(navigator.timezone) !== 'undefined') { 371 // running in a browser 372 if (navigator.timezone.length > 0) { 373 ilib.tz = navigator.timezone; 374 } 375 } else if (typeof(PalmSystem) !== 'undefined' && typeof(PalmSystem.timezone) !== 'undefined') { 376 // running in webkit on webOS 377 if (PalmSystem.timezone.length > 0) { 378 ilib.tz = PalmSystem.timezone; 379 } 380 } else if (typeof(environment) !== 'undefined' && typeof(environment.user) !== 'undefined') { 381 // running under rhino 382 if (typeof(environment.user.timezone) !== 'undefined' && environment.user.timezone.length > 0) { 383 ilib.tz = environment.user.timezone; 384 } 385 } else if (typeof(process) !== 'undefined' && typeof(process.env) !== 'undefined') { 386 // running in nodejs 387 if (process.env.TZ && typeof(process.env.TZ) !== "undefined") { 388 ilib.tz = process.env.TZ; 389 } 390 } 391 392 ilib.tz = ilib.tz || "local"; 393 } 394 395 return ilib.tz; 396 }; 397 398 /** 399 * @class 400 * Defines the interface for the loader class for ilib. The main method of the 401 * loader object is loadFiles(), which loads a set of requested locale data files 402 * from where-ever it is stored. 403 * @interface 404 */ 405 ilib.Loader = function() {}; 406 407 /** 408 * Load a set of files from where-ever it is stored.<p> 409 * 410 * This is the main function define a callback function for loading missing locale 411 * data or resources. 412 * If this copy of ilib is assembled without including the required locale data 413 * or resources, then that data can be lazy loaded dynamically when it is 414 * needed by calling this method. Each ilib class will first 415 * check for the existence of data under ilib.data, and if it is not there, 416 * it will attempt to load it by calling this method of the laoder, and then place 417 * it there.<p> 418 * 419 * Suggested implementations of this method might load files 420 * directly from disk under nodejs or rhino, or within web pages, to load 421 * files from the server with XHR calls.<p> 422 * 423 * The first parameter to this method, paths, is an array of relative paths within 424 * the ilib dir structure for the 425 * requested data. These paths will already have the locale spec integrated 426 * into them, so no further tweaking needs to happen to load the data. Simply 427 * load the named files. The second 428 * parameter tells the loader whether to load the files synchronously or asynchronously. 429 * If the sync parameters is false, then the onLoad function must also be specified. 430 * The third parameter gives extra parameters to the loader passed from the calling 431 * code. This may contain any property/value pairs. The last parameter, callback, 432 * is a callback function to call when all of the data is finishing loading. Make 433 * sure to call the callback with the context of "this" so that the caller has their 434 * context back again.<p> 435 * 436 * The loader function must be able to operate either synchronously or asychronously. 437 * If the loader function is called with an undefined callback function, it is 438 * expected to load the data synchronously, convert it to javascript 439 * objects, and return the array of json objects as the return value of the 440 * function. If the loader 441 * function is called with a callback function, it may load the data 442 * synchronously or asynchronously (doesn't matter which) as long as it calls 443 * the callback function with the data converted to a javascript objects 444 * when it becomes available. If a particular file could not be loaded, the 445 * loader function should put undefined into the corresponding entry in the 446 * results array. 447 * Note that it is important that all the data is loaded before the callback 448 * is called.<p> 449 * 450 * An example implementation for nodejs might be: 451 * 452 * <pre> 453 * * 454 * var myLoader = function() {}; 455 * myLoader.prototype = new Loader(); 456 * myLoader.prototype.constructor = myLoader; 457 * myLoader.prototype.loadFiles = function(paths, sync, params, callback) { 458 * if (sync) { 459 * var ret = []; 460 * // synchronous load -- just return the result 461 * paths.forEach(function (path) { 462 * var json = fs.readFileSync(path, "utf-8"); 463 * ret.push(json ? JSON.parse(json) : undefined); 464 * }); 465 * 466 * return ret; 467 * } 468 * this.callback = callback; 469 * 470 * // asynchronous 471 * this.results = []; 472 * this._loadFilesAsync(paths); 473 * } 474 * myLoader.prototype._loadFilesAsync = function (paths) { 475 * if (paths.length > 0) { 476 * var file = paths.shift(); 477 * fs.readFile(file, "utf-8", function(err, json) { 478 * this.results.push(err ? undefined : JSON.parse(json)); 479 * // call self recursively so that the callback is only called at the end 480 * // when all the files are loaded sequentially 481 * if (paths.length > 0) { 482 * this._loadFilesAsync(paths); 483 * } else { 484 * this.callback(this.results); 485 * } 486 * }); 487 * } 488 * } 489 * 490 * // bind to "this" so that "this" is relative to your own instance 491 * ilib.setLoaderCallback(new myLoader()); 492 * </pre> 493 494 * @param {Array.<string>} paths An array of paths to load from wherever the files are stored 495 * @param {Boolean} sync if true, load the files synchronously, and false means asynchronously 496 * @param {Object} params an object with any extra parameters for the loader. These can be 497 * anything. The caller of the ilib class passes these parameters in. Presumably, the code that 498 * calls ilib and the code that provides the loader are together and can have a private 499 * agreement between them about what the parameters should contain. 500 * @param {function(Object)} callback function to call when the files are all loaded. The 501 * parameter of the callback function is the contents of the files. 502 */ 503 ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {}; 504 505 /** 506 * Return all files available for loading using this loader instance. 507 * This method returns an object where the properties are the paths to 508 * directories where files are loaded from and the values are an array 509 * of strings containing the relative paths under the directory of each 510 * file that can be loaded.<p> 511 * 512 * Example: 513 * <pre> 514 * { 515 * "/usr/share/javascript/ilib/locale": [ 516 * "dateformats.json", 517 * "aa/dateformats.json", 518 * "af/dateformats.json", 519 * "agq/dateformats.json", 520 * "ak/dateformats.json", 521 * ... 522 * "zxx/dateformats.json" 523 * ] 524 * } 525 * </pre> 526 * @returns {Object} a hash containing directory names and 527 * paths to file that can be loaded by this loader 528 */ 529 ilib.Loader.prototype.listAvailableFiles = function() {}; 530 531 /** 532 * Return true if the file in the named path is available for loading using 533 * this loader. The path may be given as an absolute path, in which case 534 * only that file is checked, or as a relative path, in which case, the 535 * relative path may appear underneath any of the directories that the loader 536 * knows about. 537 * @returns {boolean} true if the file in the named path is available for loading, and 538 * false otherwise 539 */ 540 ilib.Loader.prototype.isAvailable = function(path) {}; 541 542 /** 543 * Set the custom loader used to load ilib's locale data in your environment. 544 * The instance passed in must implement the Loader interface. See the 545 * Loader class documentation for more information about loaders. 546 * 547 * @static 548 * @param {ilib.Loader} loader class to call to access the requested data. 549 * @return {boolean} true if the loader was installed correctly, or false 550 * if not 551 */ 552 ilib.setLoaderCallback = function(loader) { 553 // only a basic check 554 if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') || 555 typeof(loader) === 'function' || typeof(loader) === 'undefined') { 556 //console.log("setting callback loader to " + (loader ? loader.name : "undefined")); 557 ilib._load = loader; 558 return true; 559 } 560 return false; 561 }; 562 563 /** 564 * Return the custom Loader instance currently in use with this instance 565 * of ilib. If there is no loader, this method returns undefined. 566 * 567 * @protected 568 * @static 569 * @return {ilib.Loader|undefined} the loader instance currently in use, or 570 * undefined if there is no such loader 571 */ 572 ilib.getLoader = function() { 573 return ilib._load; 574 }; 575 576 /** 577 * Test whether an object is an javascript array. 578 * 579 * @static 580 * @param {*} object The object to test 581 * @return {boolean} return true if the object is an array 582 * and false otherwise 583 */ 584 ilib.isArray = function(object) { 585 if (typeof(object) === 'object') { 586 return Object.prototype.toString.call(object) === '[object Array]'; 587 } 588 return false; 589 }; 590 591 /** 592 * Extend object1 by mixing in everything from object2 into it. The objects 593 * are deeply extended, meaning that this method recursively descends the 594 * tree in the objects and mixes them in at each level. Arrays are extended 595 * by concatenating the elements of object2 onto those of object1. 596 * 597 * @static 598 * @param {Object} object1 the target object to extend 599 * @param {Object=} object2 the object to mix in to object1 600 * @return {Object} returns object1 601 */ 602 ilib.extend = function (object1, object2) { 603 var prop = undefined; 604 if (object2) { 605 for (prop in object2) { 606 // don't extend object with undefined or functions 607 if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") { 608 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 609 //console.log("Merging array prop " + prop); 610 object1[prop] = object1[prop].concat(object2[prop]); 611 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 612 //console.log("Merging object prop " + prop); 613 if (prop !== "ilib") { 614 object1[prop] = ilib.extend(object1[prop], object2[prop]); 615 } 616 } else { 617 //console.log("Copying prop " + prop); 618 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 619 object1[prop] = object2[prop]; 620 } 621 } 622 } 623 } 624 return object1; 625 }; 626 627 ilib.extend2 = function (object1, object2) { 628 var prop = undefined; 629 if (object2) { 630 for (prop in object2) { 631 // don't extend object with undefined or functions 632 if (prop && typeof(object2[prop]) !== 'undefined') { 633 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 634 //console.log("Merging array prop " + prop); 635 object1[prop] = object1[prop].concat(object2[prop]); 636 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 637 //console.log("Merging object prop " + prop); 638 if (prop !== "ilib") { 639 object1[prop] = ilib.extend2(object1[prop], object2[prop]); 640 } 641 } else { 642 //console.log("Copying prop " + prop); 643 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 644 object1[prop] = object2[prop]; 645 } 646 } 647 } 648 } 649 return object1; 650 }; 651 652 /** 653 * If Function.prototype.bind does not exist in this JS engine, this 654 * function reimplements it in terms of older JS functions. 655 * bind() doesn't exist in many older browsers. 656 * 657 * @static 658 * @param {Object} scope object that the method should operate on 659 * @param {function(...)} method method to call 660 * @return {function(...)|undefined} function that calls the given method 661 * in the given scope with all of its arguments properly attached, or 662 * undefined if there was a problem with the arguments 663 */ 664 ilib.bind = function(scope, method/*, bound arguments*/){ 665 if (!scope || !method) { 666 return undefined; 667 } 668 669 /** @protected 670 * @param {Arguments} inArrayLike 671 * @param {number=} inOffset 672 */ 673 function cloneArray(inArrayLike, inOffset) { 674 var arr = []; 675 for(var i = inOffset || 0, l = inArrayLike.length; i<l; i++){ 676 arr.push(inArrayLike[i]); 677 } 678 return arr; 679 } 680 681 if (typeof(method) === 'function') { 682 var func, args = cloneArray(arguments, 2); 683 if (typeof(method.bind) === 'function') { 684 func = method.bind.apply(method, [scope].concat(args)); 685 } else { 686 func = function() { 687 var nargs = cloneArray(arguments); 688 // invoke with collected args 689 return method.apply(scope, args.concat(nargs)); 690 }; 691 } 692 return func; 693 } 694 return undefined; 695 }; 696 697 /** 698 * @private 699 */ 700 ilib._dyncode = false; 701 702 /** 703 * Return true if this copy of ilib is using dynamically loaded code. It returns 704 * false for pre-assembled code. 705 * 706 * @static 707 * @return {boolean} true if this ilib uses dynamically loaded code, and false otherwise 708 */ 709 ilib.isDynCode = function() { 710 return ilib._dyncode; 711 }; 712 713 /** 714 * @private 715 */ 716 ilib._dyndata = false; 717 718 /** 719 * Return true if this copy of ilib is using dynamically loaded locale data. It returns 720 * false for pre-assembled data. 721 * 722 * @static 723 * @return {boolean} true if this ilib uses dynamically loaded locale data, and false otherwise 724 */ 725 ilib.isDynData = function() { 726 return ilib._dyndata; 727 }; 728 729 ilib._loadtime = new Date().getTime(); 730 731 /*< JSUtils.js */ 732 /* 733 * JSUtils.js - Misc utilities to work around Javascript engine differences 734 * 735 * Copyright © 2013-2015, JEDLSoft 736 * 737 * Licensed under the Apache License, Version 2.0 (the "License"); 738 * you may not use this file except in compliance with the License. 739 * You may obtain a copy of the License at 740 * 741 * http://www.apache.org/licenses/LICENSE-2.0 742 * 743 * Unless required by applicable law or agreed to in writing, software 744 * distributed under the License is distributed on an "AS IS" BASIS, 745 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 746 * 747 * See the License for the specific language governing permissions and 748 * limitations under the License. 749 */ 750 751 // !depends ilib.js 752 753 754 var JSUtils = {}; 755 756 /** 757 * Perform a shallow copy of the source object to the target object. This only 758 * copies the assignments of the source properties to the target properties, 759 * but not recursively from there.<p> 760 * 761 * 762 * @static 763 * @param {Object} source the source object to copy properties from 764 * @param {Object} target the target object to copy properties into 765 */ 766 JSUtils.shallowCopy = function (source, target) { 767 var prop = undefined; 768 if (source && target) { 769 for (prop in source) { 770 if (prop !== undefined && typeof(source[prop]) !== 'undefined') { 771 target[prop] = source[prop]; 772 } 773 } 774 } 775 }; 776 777 /** 778 * Perform a recursive deep copy from the "from" object to the "deep" object. 779 * 780 * @static 781 * @param {Object} from the object to copy from 782 * @param {Object} to the object to copy to 783 * @return {Object} a reference to the the "to" object 784 */ 785 JSUtils.deepCopy = function(from, to) { 786 var prop; 787 788 for (prop in from) { 789 if (prop) { 790 if (typeof(from[prop]) === 'object') { 791 to[prop] = {}; 792 JSUtils.deepCopy(from[prop], to[prop]); 793 } else { 794 to[prop] = from[prop]; 795 } 796 } 797 } 798 return to; 799 }; 800 801 /** 802 * Map a string to the given set of alternate characters. If the target set 803 * does not contain a particular character in the input string, then that 804 * character will be copied to the output unmapped. 805 * 806 * @static 807 * @param {string} str a string to map to an alternate set of characters 808 * @param {Array.<string>|Object} map a mapping to alternate characters 809 * @return {string} the source string where each character is mapped to alternate characters 810 */ 811 JSUtils.mapString = function (str, map) { 812 var mapped = ""; 813 if (map && str) { 814 for (var i = 0; i < str.length; i++) { 815 var c = str.charAt(i); // TODO use a char iterator? 816 mapped += map[c] || c; 817 } 818 } else { 819 mapped = str; 820 } 821 return mapped; 822 }; 823 824 /** 825 * Check if an object is a member of the given array. If this javascript engine 826 * support indexOf, it is used directly. Otherwise, this function implements it 827 * itself. The idea is to make sure that you can use the quick indexOf if it is 828 * available, but use a slower implementation in older engines as well. 829 * 830 * @static 831 * @param {Array.<Object>} array array to search 832 * @param {Object} obj object being sought. This should be of the same type as the 833 * members of the array being searched. If not, this function will not return 834 * any results. 835 * @return {number} index of the object in the array, or -1 if it is not in the array. 836 */ 837 JSUtils.indexOf = function(array, obj) { 838 if (!array || !obj) { 839 return -1; 840 } 841 if (typeof(array.indexOf) === 'function') { 842 return array.indexOf(obj); 843 } else { 844 for (var i = 0; i < array.length; i++) { 845 if (array[i] === obj) { 846 return i; 847 } 848 } 849 return -1; 850 } 851 }; 852 853 /** 854 * Pad the str with zeros to the given length of digits. 855 * 856 * @static 857 * @param {string|number} str the string or number to pad 858 * @param {number} length the desired total length of the output string, padded 859 * @param {boolean=} right if true, pad on the right side of the number rather than the left. 860 * Default is false. 861 */ 862 JSUtils.pad = function (str, length, right) { 863 if (typeof(str) !== 'string') { 864 str = "" + str; 865 } 866 var start = 0; 867 // take care of negative numbers 868 if (str.charAt(0) === '-') { 869 start++; 870 } 871 return (str.length >= length+start) ? str : 872 (right ? str + JSUtils.pad.zeros.substring(0,length-str.length+start) : 873 str.substring(0, start) + JSUtils.pad.zeros.substring(0,length-str.length+start) + str.substring(start)); 874 }; 875 876 /** @private */ 877 JSUtils.pad.zeros = "00000000000000000000000000000000"; 878 879 /** 880 * Convert a string into the hexadecimal representation 881 * of the Unicode characters in that string. 882 * 883 * @static 884 * @param {string} string The string to convert 885 * @param {number=} limit the number of digits to use to represent the character (1 to 8) 886 * @return {string} a hexadecimal representation of the 887 * Unicode characters in the input string 888 */ 889 JSUtils.toHexString = function(string, limit) { 890 var i, 891 result = "", 892 lim = (limit && limit < 9) ? limit : 4; 893 894 if (!string) { 895 return ""; 896 } 897 for (i = 0; i < string.length; i++) { 898 var ch = string.charCodeAt(i).toString(16); 899 result += JSUtils.pad(ch, lim); 900 } 901 return result.toUpperCase(); 902 }; 903 904 /** 905 * Test whether an object in a Javascript Date. 906 * 907 * @static 908 * @param {Object|null|undefined} object The object to test 909 * @return {boolean} return true if the object is a Date 910 * and false otherwise 911 */ 912 JSUtils.isDate = function(object) { 913 if (typeof(object) === 'object') { 914 return Object.prototype.toString.call(object) === '[object Date]'; 915 } 916 return false; 917 }; 918 919 /** 920 * Merge the properties of object2 into object1 in a deep manner and return a merged 921 * object. If the property exists in both objects, the value in object2 will overwrite 922 * the value in object1. If a property exists in object1, but not in object2, its value 923 * will not be touched. If a property exists in object2, but not in object1, it will be 924 * added to the merged result.<p> 925 * 926 * Name1 and name2 are for creating debug output only. They are not necessary.<p> 927 * 928 * 929 * @static 930 * @param {*} object1 the object to merge into 931 * @param {*} object2 the object to merge 932 * @param {boolean=} replace if true, replace the array elements in object1 with those in object2. 933 * If false, concatenate array elements in object1 with items in object2. 934 * @param {string=} name1 name of the object being merged into 935 * @param {string=} name2 name of the object being merged in 936 * @return {Object} the merged object 937 */ 938 JSUtils.merge = function (object1, object2, replace, name1, name2) { 939 var prop = undefined, 940 newObj = {}; 941 for (prop in object1) { 942 if (prop && typeof(object1[prop]) !== 'undefined') { 943 newObj[prop] = object1[prop]; 944 } 945 } 946 for (prop in object2) { 947 if (prop && typeof(object2[prop]) !== 'undefined') { 948 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 949 if (typeof(replace) !== 'boolean' || !replace) { 950 newObj[prop] = [].concat(object1[prop]); 951 newObj[prop] = newObj[prop].concat(object2[prop]); 952 } else { 953 newObj[prop] = object2[prop]; 954 } 955 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 956 newObj[prop] = JSUtils.merge(object1[prop], object2[prop], replace); 957 } else { 958 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 959 if (name1 && name2 && newObj[prop] == object2[prop]) { 960 console.log("Property " + prop + " in " + name1 + " is being overridden by the same value in " + name2); 961 } 962 newObj[prop] = object2[prop]; 963 } 964 } 965 } 966 return newObj; 967 }; 968 969 /** 970 * Return true if the given object has no properties.<p> 971 * 972 * 973 * @static 974 * @param {Object} obj the object to check 975 * @return {boolean} true if the given object has no properties, false otherwise 976 */ 977 JSUtils.isEmpty = function (obj) { 978 var prop = undefined; 979 980 if (!obj) { 981 return true; 982 } 983 984 for (prop in obj) { 985 if (prop && typeof(obj[prop]) !== 'undefined') { 986 return false; 987 } 988 } 989 return true; 990 }; 991 992 /** 993 * @static 994 */ 995 JSUtils.hashCode = function(obj) { 996 var hash = 0; 997 998 function addHash(hash, newValue) { 999 // co-prime numbers creates a nicely distributed hash 1000 hash *= 65543; 1001 hash += newValue; 1002 hash %= 2147483647; 1003 return hash; 1004 } 1005 1006 function stringHash(str) { 1007 var hash = 0; 1008 for (var i = 0; i < str.length; i++) { 1009 hash = addHash(hash, str.charCodeAt(i)); 1010 } 1011 return hash; 1012 } 1013 1014 switch (typeof(obj)) { 1015 case 'undefined': 1016 hash = 0; 1017 break; 1018 case 'string': 1019 hash = stringHash(obj); 1020 break; 1021 case 'function': 1022 case 'number': 1023 case 'xml': 1024 hash = stringHash(String(obj)); 1025 break; 1026 case 'boolean': 1027 hash = obj ? 1 : 0; 1028 break; 1029 case 'object': 1030 var props = []; 1031 for (var p in obj) { 1032 if (obj.hasOwnProperty(p)) { 1033 props.push(p); 1034 } 1035 } 1036 // make sure the order of the properties doesn't matter 1037 props.sort(); 1038 for (var i = 0; i < props.length; i++) { 1039 hash = addHash(hash, stringHash(props[i])); 1040 hash = addHash(hash, JSUtils.hashCode(obj[props[i]])); 1041 } 1042 break; 1043 } 1044 1045 return hash; 1046 }; 1047 1048 1049 1050 1051 /*< Locale.js */ 1052 /* 1053 * Locale.js - Locale specifier definition 1054 * 1055 * Copyright © 2012-2015, JEDLSoft 1056 * 1057 * Licensed under the Apache License, Version 2.0 (the "License"); 1058 * you may not use this file except in compliance with the License. 1059 * You may obtain a copy of the License at 1060 * 1061 * http://www.apache.org/licenses/LICENSE-2.0 1062 * 1063 * Unless required by applicable law or agreed to in writing, software 1064 * distributed under the License is distributed on an "AS IS" BASIS, 1065 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1066 * 1067 * See the License for the specific language governing permissions and 1068 * limitations under the License. 1069 */ 1070 1071 // !depends ilib.js JSUtils.js 1072 1073 1074 /** 1075 * @class 1076 * Create a new locale instance. Locales are specified either with a specifier string 1077 * that follows the BCP-47 convention (roughly: "language-region-script-variant") or 1078 * with 4 parameters that specify the language, region, variant, and script individually.<p> 1079 * 1080 * The language is given as an ISO 639-1 two-letter, lower-case language code. You 1081 * can find a full list of these codes at 1082 * <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes</a><p> 1083 * 1084 * The region is given as an ISO 3166-1 two-letter, upper-case region code. You can 1085 * find a full list of these codes at 1086 * <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2</a>.<p> 1087 * 1088 * The variant is any string that does not contain a dash which further differentiates 1089 * locales from each other.<p> 1090 * 1091 * The script is given as the ISO 15924 four-letter script code. In some locales, 1092 * text may be validly written in more than one script. For example, Serbian is often 1093 * written in both Latin and Cyrillic, though not usually mixed together. You can find a 1094 * full list of these codes at 1095 * <a href="http://en.wikipedia.org/wiki/ISO_15924#List_of_codes">http://en.wikipedia.org/wiki/ISO_15924#List_of_codes</a>.<p> 1096 * 1097 * As an example in ilib, the script can be used in the date formatter. Dates formatted 1098 * in Serbian could have day-of-week names or month names written in the Latin 1099 * or Cyrillic script. Often one script is default such that sr-SR-Latn is the same 1100 * as sr-SR so the script code "Latn" can be left off of the locale spec.<p> 1101 * 1102 * Each part is optional, and an empty string in the specifier before or after a 1103 * dash or as a parameter to the constructor denotes an unspecified value. In this 1104 * case, many of the ilib functions will treat the locale as generic. For example 1105 * the locale "en-" is equivalent to "en" and to "en--" and denotes a locale 1106 * of "English" with an unspecified region and variant, which typically matches 1107 * any region or variant.<p> 1108 * 1109 * Without any arguments to the constructor, this function returns the locale of 1110 * the host Javascript engine.<p> 1111 * 1112 * 1113 * @constructor 1114 * @param {?string|Locale=} language the ISO 639 2-letter code for the language, or a full 1115 * locale spec in BCP-47 format, or another Locale instance to copy from 1116 * @param {string=} region the ISO 3166 2-letter code for the region 1117 * @param {string=} variant the name of the variant of this locale, if any 1118 * @param {string=} script the ISO 15924 code of the script for this locale, if any 1119 */ 1120 var Locale = function(language, region, variant, script) { 1121 if (typeof(region) === 'undefined') { 1122 var spec = language || ilib.getLocale(); 1123 if (typeof(spec) === 'string') { 1124 var parts = spec.split('-'); 1125 for ( var i = 0; i < parts.length; i++ ) { 1126 if (Locale._isLanguageCode(parts[i])) { 1127 /** 1128 * @private 1129 * @type {string|undefined} 1130 */ 1131 this.language = parts[i]; 1132 } else if (Locale._isRegionCode(parts[i])) { 1133 /** 1134 * @private 1135 * @type {string|undefined} 1136 */ 1137 this.region = parts[i]; 1138 } else if (Locale._isScriptCode(parts[i])) { 1139 /** 1140 * @private 1141 * @type {string|undefined} 1142 */ 1143 this.script = parts[i]; 1144 } else { 1145 /** 1146 * @private 1147 * @type {string|undefined} 1148 */ 1149 this.variant = parts[i]; 1150 } 1151 } 1152 this.language = this.language || undefined; 1153 this.region = this.region || undefined; 1154 this.script = this.script || undefined; 1155 this.variant = this.variant || undefined; 1156 } else if (typeof(spec) === 'object') { 1157 this.language = spec.language || undefined; 1158 this.region = spec.region || undefined; 1159 this.script = spec.script || undefined; 1160 this.variant = spec.variant || undefined; 1161 } 1162 } else { 1163 if (language) { 1164 language = language.trim(); 1165 this.language = language.length > 0 ? language.toLowerCase() : undefined; 1166 } else { 1167 this.language = undefined; 1168 } 1169 if (region) { 1170 region = region.trim(); 1171 this.region = region.length > 0 ? region.toUpperCase() : undefined; 1172 } else { 1173 this.region = undefined; 1174 } 1175 if (variant) { 1176 variant = variant.trim(); 1177 this.variant = variant.length > 0 ? variant : undefined; 1178 } else { 1179 this.variant = undefined; 1180 } 1181 if (script) { 1182 script = script.trim(); 1183 this.script = script.length > 0 ? script : undefined; 1184 } else { 1185 this.script = undefined; 1186 } 1187 } 1188 this._genSpec(); 1189 }; 1190 1191 // from http://en.wikipedia.org/wiki/ISO_3166-1 1192 Locale.a2toa3regmap = { 1193 "AF": "AFG", 1194 "AX": "ALA", 1195 "AL": "ALB", 1196 "DZ": "DZA", 1197 "AS": "ASM", 1198 "AD": "AND", 1199 "AO": "AGO", 1200 "AI": "AIA", 1201 "AQ": "ATA", 1202 "AG": "ATG", 1203 "AR": "ARG", 1204 "AM": "ARM", 1205 "AW": "ABW", 1206 "AU": "AUS", 1207 "AT": "AUT", 1208 "AZ": "AZE", 1209 "BS": "BHS", 1210 "BH": "BHR", 1211 "BD": "BGD", 1212 "BB": "BRB", 1213 "BY": "BLR", 1214 "BE": "BEL", 1215 "BZ": "BLZ", 1216 "BJ": "BEN", 1217 "BM": "BMU", 1218 "BT": "BTN", 1219 "BO": "BOL", 1220 "BQ": "BES", 1221 "BA": "BIH", 1222 "BW": "BWA", 1223 "BV": "BVT", 1224 "BR": "BRA", 1225 "IO": "IOT", 1226 "BN": "BRN", 1227 "BG": "BGR", 1228 "BF": "BFA", 1229 "BI": "BDI", 1230 "KH": "KHM", 1231 "CM": "CMR", 1232 "CA": "CAN", 1233 "CV": "CPV", 1234 "KY": "CYM", 1235 "CF": "CAF", 1236 "TD": "TCD", 1237 "CL": "CHL", 1238 "CN": "CHN", 1239 "CX": "CXR", 1240 "CC": "CCK", 1241 "CO": "COL", 1242 "KM": "COM", 1243 "CG": "COG", 1244 "CD": "COD", 1245 "CK": "COK", 1246 "CR": "CRI", 1247 "CI": "CIV", 1248 "HR": "HRV", 1249 "CU": "CUB", 1250 "CW": "CUW", 1251 "CY": "CYP", 1252 "CZ": "CZE", 1253 "DK": "DNK", 1254 "DJ": "DJI", 1255 "DM": "DMA", 1256 "DO": "DOM", 1257 "EC": "ECU", 1258 "EG": "EGY", 1259 "SV": "SLV", 1260 "GQ": "GNQ", 1261 "ER": "ERI", 1262 "EE": "EST", 1263 "ET": "ETH", 1264 "FK": "FLK", 1265 "FO": "FRO", 1266 "FJ": "FJI", 1267 "FI": "FIN", 1268 "FR": "FRA", 1269 "GF": "GUF", 1270 "PF": "PYF", 1271 "TF": "ATF", 1272 "GA": "GAB", 1273 "GM": "GMB", 1274 "GE": "GEO", 1275 "DE": "DEU", 1276 "GH": "GHA", 1277 "GI": "GIB", 1278 "GR": "GRC", 1279 "GL": "GRL", 1280 "GD": "GRD", 1281 "GP": "GLP", 1282 "GU": "GUM", 1283 "GT": "GTM", 1284 "GG": "GGY", 1285 "GN": "GIN", 1286 "GW": "GNB", 1287 "GY": "GUY", 1288 "HT": "HTI", 1289 "HM": "HMD", 1290 "VA": "VAT", 1291 "HN": "HND", 1292 "HK": "HKG", 1293 "HU": "HUN", 1294 "IS": "ISL", 1295 "IN": "IND", 1296 "ID": "IDN", 1297 "IR": "IRN", 1298 "IQ": "IRQ", 1299 "IE": "IRL", 1300 "IM": "IMN", 1301 "IL": "ISR", 1302 "IT": "ITA", 1303 "JM": "JAM", 1304 "JP": "JPN", 1305 "JE": "JEY", 1306 "JO": "JOR", 1307 "KZ": "KAZ", 1308 "KE": "KEN", 1309 "KI": "KIR", 1310 "KP": "PRK", 1311 "KR": "KOR", 1312 "KW": "KWT", 1313 "KG": "KGZ", 1314 "LA": "LAO", 1315 "LV": "LVA", 1316 "LB": "LBN", 1317 "LS": "LSO", 1318 "LR": "LBR", 1319 "LY": "LBY", 1320 "LI": "LIE", 1321 "LT": "LTU", 1322 "LU": "LUX", 1323 "MO": "MAC", 1324 "MK": "MKD", 1325 "MG": "MDG", 1326 "MW": "MWI", 1327 "MY": "MYS", 1328 "MV": "MDV", 1329 "ML": "MLI", 1330 "MT": "MLT", 1331 "MH": "MHL", 1332 "MQ": "MTQ", 1333 "MR": "MRT", 1334 "MU": "MUS", 1335 "YT": "MYT", 1336 "MX": "MEX", 1337 "FM": "FSM", 1338 "MD": "MDA", 1339 "MC": "MCO", 1340 "MN": "MNG", 1341 "ME": "MNE", 1342 "MS": "MSR", 1343 "MA": "MAR", 1344 "MZ": "MOZ", 1345 "MM": "MMR", 1346 "NA": "NAM", 1347 "NR": "NRU", 1348 "NP": "NPL", 1349 "NL": "NLD", 1350 "NC": "NCL", 1351 "NZ": "NZL", 1352 "NI": "NIC", 1353 "NE": "NER", 1354 "NG": "NGA", 1355 "NU": "NIU", 1356 "NF": "NFK", 1357 "MP": "MNP", 1358 "NO": "NOR", 1359 "OM": "OMN", 1360 "PK": "PAK", 1361 "PW": "PLW", 1362 "PS": "PSE", 1363 "PA": "PAN", 1364 "PG": "PNG", 1365 "PY": "PRY", 1366 "PE": "PER", 1367 "PH": "PHL", 1368 "PN": "PCN", 1369 "PL": "POL", 1370 "PT": "PRT", 1371 "PR": "PRI", 1372 "QA": "QAT", 1373 "RE": "REU", 1374 "RO": "ROU", 1375 "RU": "RUS", 1376 "RW": "RWA", 1377 "BL": "BLM", 1378 "SH": "SHN", 1379 "KN": "KNA", 1380 "LC": "LCA", 1381 "MF": "MAF", 1382 "PM": "SPM", 1383 "VC": "VCT", 1384 "WS": "WSM", 1385 "SM": "SMR", 1386 "ST": "STP", 1387 "SA": "SAU", 1388 "SN": "SEN", 1389 "RS": "SRB", 1390 "SC": "SYC", 1391 "SL": "SLE", 1392 "SG": "SGP", 1393 "SX": "SXM", 1394 "SK": "SVK", 1395 "SI": "SVN", 1396 "SB": "SLB", 1397 "SO": "SOM", 1398 "ZA": "ZAF", 1399 "GS": "SGS", 1400 "SS": "SSD", 1401 "ES": "ESP", 1402 "LK": "LKA", 1403 "SD": "SDN", 1404 "SR": "SUR", 1405 "SJ": "SJM", 1406 "SZ": "SWZ", 1407 "SE": "SWE", 1408 "CH": "CHE", 1409 "SY": "SYR", 1410 "TW": "TWN", 1411 "TJ": "TJK", 1412 "TZ": "TZA", 1413 "TH": "THA", 1414 "TL": "TLS", 1415 "TG": "TGO", 1416 "TK": "TKL", 1417 "TO": "TON", 1418 "TT": "TTO", 1419 "TN": "TUN", 1420 "TR": "TUR", 1421 "TM": "TKM", 1422 "TC": "TCA", 1423 "TV": "TUV", 1424 "UG": "UGA", 1425 "UA": "UKR", 1426 "AE": "ARE", 1427 "GB": "GBR", 1428 "US": "USA", 1429 "UM": "UMI", 1430 "UY": "URY", 1431 "UZ": "UZB", 1432 "VU": "VUT", 1433 "VE": "VEN", 1434 "VN": "VNM", 1435 "VG": "VGB", 1436 "VI": "VIR", 1437 "WF": "WLF", 1438 "EH": "ESH", 1439 "YE": "YEM", 1440 "ZM": "ZMB", 1441 "ZW": "ZWE" 1442 }; 1443 1444 1445 Locale.a1toa3langmap = { 1446 "ab": "abk", 1447 "aa": "aar", 1448 "af": "afr", 1449 "ak": "aka", 1450 "sq": "sqi", 1451 "am": "amh", 1452 "ar": "ara", 1453 "an": "arg", 1454 "hy": "hye", 1455 "as": "asm", 1456 "av": "ava", 1457 "ae": "ave", 1458 "ay": "aym", 1459 "az": "aze", 1460 "bm": "bam", 1461 "ba": "bak", 1462 "eu": "eus", 1463 "be": "bel", 1464 "bn": "ben", 1465 "bh": "bih", 1466 "bi": "bis", 1467 "bs": "bos", 1468 "br": "bre", 1469 "bg": "bul", 1470 "my": "mya", 1471 "ca": "cat", 1472 "ch": "cha", 1473 "ce": "che", 1474 "ny": "nya", 1475 "zh": "zho", 1476 "cv": "chv", 1477 "kw": "cor", 1478 "co": "cos", 1479 "cr": "cre", 1480 "hr": "hrv", 1481 "cs": "ces", 1482 "da": "dan", 1483 "dv": "div", 1484 "nl": "nld", 1485 "dz": "dzo", 1486 "en": "eng", 1487 "eo": "epo", 1488 "et": "est", 1489 "ee": "ewe", 1490 "fo": "fao", 1491 "fj": "fij", 1492 "fi": "fin", 1493 "fr": "fra", 1494 "ff": "ful", 1495 "gl": "glg", 1496 "ka": "kat", 1497 "de": "deu", 1498 "el": "ell", 1499 "gn": "grn", 1500 "gu": "guj", 1501 "ht": "hat", 1502 "ha": "hau", 1503 "he": "heb", 1504 "hz": "her", 1505 "hi": "hin", 1506 "ho": "hmo", 1507 "hu": "hun", 1508 "ia": "ina", 1509 "id": "ind", 1510 "ie": "ile", 1511 "ga": "gle", 1512 "ig": "ibo", 1513 "ik": "ipk", 1514 "io": "ido", 1515 "is": "isl", 1516 "it": "ita", 1517 "iu": "iku", 1518 "ja": "jpn", 1519 "jv": "jav", 1520 "kl": "kal", 1521 "kn": "kan", 1522 "kr": "kau", 1523 "ks": "kas", 1524 "kk": "kaz", 1525 "km": "khm", 1526 "ki": "kik", 1527 "rw": "kin", 1528 "ky": "kir", 1529 "kv": "kom", 1530 "kg": "kon", 1531 "ko": "kor", 1532 "ku": "kur", 1533 "kj": "kua", 1534 "la": "lat", 1535 "lb": "ltz", 1536 "lg": "lug", 1537 "li": "lim", 1538 "ln": "lin", 1539 "lo": "lao", 1540 "lt": "lit", 1541 "lu": "lub", 1542 "lv": "lav", 1543 "gv": "glv", 1544 "mk": "mkd", 1545 "mg": "mlg", 1546 "ms": "msa", 1547 "ml": "mal", 1548 "mt": "mlt", 1549 "mi": "mri", 1550 "mr": "mar", 1551 "mh": "mah", 1552 "mn": "mon", 1553 "na": "nau", 1554 "nv": "nav", 1555 "nb": "nob", 1556 "nd": "nde", 1557 "ne": "nep", 1558 "ng": "ndo", 1559 "nn": "nno", 1560 "no": "nor", 1561 "ii": "iii", 1562 "nr": "nbl", 1563 "oc": "oci", 1564 "oj": "oji", 1565 "cu": "chu", 1566 "om": "orm", 1567 "or": "ori", 1568 "os": "oss", 1569 "pa": "pan", 1570 "pi": "pli", 1571 "fa": "fas", 1572 "pl": "pol", 1573 "ps": "pus", 1574 "pt": "por", 1575 "qu": "que", 1576 "rm": "roh", 1577 "rn": "run", 1578 "ro": "ron", 1579 "ru": "rus", 1580 "sa": "san", 1581 "sc": "srd", 1582 "sd": "snd", 1583 "se": "sme", 1584 "sm": "smo", 1585 "sg": "sag", 1586 "sr": "srp", 1587 "gd": "gla", 1588 "sn": "sna", 1589 "si": "sin", 1590 "sk": "slk", 1591 "sl": "slv", 1592 "so": "som", 1593 "st": "sot", 1594 "es": "spa", 1595 "su": "sun", 1596 "sw": "swa", 1597 "ss": "ssw", 1598 "sv": "swe", 1599 "ta": "tam", 1600 "te": "tel", 1601 "tg": "tgk", 1602 "th": "tha", 1603 "ti": "tir", 1604 "bo": "bod", 1605 "tk": "tuk", 1606 "tl": "tgl", 1607 "tn": "tsn", 1608 "to": "ton", 1609 "tr": "tur", 1610 "ts": "tso", 1611 "tt": "tat", 1612 "tw": "twi", 1613 "ty": "tah", 1614 "ug": "uig", 1615 "uk": "ukr", 1616 "ur": "urd", 1617 "uz": "uzb", 1618 "ve": "ven", 1619 "vi": "vie", 1620 "vo": "vol", 1621 "wa": "wln", 1622 "cy": "cym", 1623 "wo": "wol", 1624 "fy": "fry", 1625 "xh": "xho", 1626 "yi": "yid", 1627 "yo": "yor", 1628 "za": "zha", 1629 "zu": "zul" 1630 }; 1631 1632 /** 1633 * Tell whether or not the str does not start with a lower case ASCII char. 1634 * @private 1635 * @param {string} str the char to check 1636 * @return {boolean} true if the char is not a lower case ASCII char 1637 */ 1638 Locale._notLower = function(str) { 1639 // do this with ASCII only so we don't have to depend on the CType functions 1640 var ch = str.charCodeAt(0); 1641 return ch < 97 || ch > 122; 1642 }; 1643 1644 /** 1645 * Tell whether or not the str does not start with an upper case ASCII char. 1646 * @private 1647 * @param {string} str the char to check 1648 * @return {boolean} true if the char is a not an upper case ASCII char 1649 */ 1650 Locale._notUpper = function(str) { 1651 // do this with ASCII only so we don't have to depend on the CType functions 1652 var ch = str.charCodeAt(0); 1653 return ch < 65 || ch > 90; 1654 }; 1655 1656 /** 1657 * Tell whether or not the str does not start with a digit char. 1658 * @private 1659 * @param {string} str the char to check 1660 * @return {boolean} true if the char is a not an upper case ASCII char 1661 */ 1662 Locale._notDigit = function(str) { 1663 // do this with ASCII only so we don't have to depend on the CType functions 1664 var ch = str.charCodeAt(0); 1665 return ch < 48 || ch > 57; 1666 }; 1667 1668 /** 1669 * Tell whether or not the given string has the correct syntax to be 1670 * an ISO 639 language code. 1671 * 1672 * @private 1673 * @param {string} str the string to parse 1674 * @return {boolean} true if the string could syntactically be a language code. 1675 */ 1676 Locale._isLanguageCode = function(str) { 1677 if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { 1678 return false; 1679 } 1680 1681 for (var i = 0; i < str.length; i++) { 1682 if (Locale._notLower(str.charAt(i))) { 1683 return false; 1684 } 1685 } 1686 1687 return true; 1688 }; 1689 1690 /** 1691 * Tell whether or not the given string has the correct syntax to be 1692 * an ISO 3166 2-letter region code or M.49 3-digit region code. 1693 * 1694 * @private 1695 * @param {string} str the string to parse 1696 * @return {boolean} true if the string could syntactically be a language code. 1697 */ 1698 Locale._isRegionCode = function (str) { 1699 if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { 1700 return false; 1701 } 1702 1703 if (str.length === 2) { 1704 for (var i = 0; i < str.length; i++) { 1705 if (Locale._notUpper(str.charAt(i))) { 1706 return false; 1707 } 1708 } 1709 } else { 1710 for (var i = 0; i < str.length; i++) { 1711 if (Locale._notDigit(str.charAt(i))) { 1712 return false; 1713 } 1714 } 1715 } 1716 1717 return true; 1718 }; 1719 1720 /** 1721 * Tell whether or not the given string has the correct syntax to be 1722 * an ISO 639 language code. 1723 * 1724 * @private 1725 * @param {string} str the string to parse 1726 * @return {boolean} true if the string could syntactically be a language code. 1727 */ 1728 Locale._isScriptCode = function(str) { 1729 if (typeof(str) === 'undefined' || str.length !== 4 || Locale._notUpper(str.charAt(0))) { 1730 return false; 1731 } 1732 1733 for (var i = 1; i < 4; i++) { 1734 if (Locale._notLower(str.charAt(i))) { 1735 return false; 1736 } 1737 } 1738 1739 return true; 1740 }; 1741 1742 /** 1743 * Return the ISO-3166 alpha3 equivalent region code for the given ISO 3166 alpha2 1744 * region code. If the given alpha2 code is not found, this function returns its 1745 * argument unchanged. 1746 * @static 1747 * @param {string|undefined} alpha2 the alpha2 code to map 1748 * @return {string|undefined} the alpha3 equivalent of the given alpha2 code, or the alpha2 1749 * parameter if the alpha2 value is not found 1750 */ 1751 Locale.regionAlpha2ToAlpha3 = function(alpha2) { 1752 return Locale.a2toa3regmap[alpha2] || alpha2; 1753 }; 1754 1755 /** 1756 * Return the ISO-639 alpha3 equivalent language code for the given ISO 639 alpha1 1757 * language code. If the given alpha1 code is not found, this function returns its 1758 * argument unchanged. 1759 * @static 1760 * @param {string|undefined} alpha1 the alpha1 code to map 1761 * @return {string|undefined} the alpha3 equivalent of the given alpha1 code, or the alpha1 1762 * parameter if the alpha1 value is not found 1763 */ 1764 Locale.languageAlpha1ToAlpha3 = function(alpha1) { 1765 return Locale.a1toa3langmap[alpha1] || alpha1; 1766 }; 1767 1768 Locale.prototype = { 1769 /** 1770 * @private 1771 */ 1772 _genSpec: function () { 1773 this.spec = this.language || ""; 1774 1775 if (this.script) { 1776 if (this.spec.length > 0) { 1777 this.spec += "-"; 1778 } 1779 this.spec += this.script; 1780 } 1781 1782 if (this.region) { 1783 if (this.spec.length > 0) { 1784 this.spec += "-"; 1785 } 1786 this.spec += this.region; 1787 } 1788 1789 if (this.variant) { 1790 if (this.spec.length > 0) { 1791 this.spec += "-"; 1792 } 1793 this.spec += this.variant; 1794 } 1795 }, 1796 1797 /** 1798 * Return the ISO 639 language code for this locale. 1799 * @return {string|undefined} the language code for this locale 1800 */ 1801 getLanguage: function() { 1802 return this.language; 1803 }, 1804 1805 /** 1806 * Return the language of this locale as an ISO-639-alpha3 language code 1807 * @return {string|undefined} the alpha3 language code of this locale 1808 */ 1809 getLanguageAlpha3: function() { 1810 return Locale.languageAlpha1ToAlpha3(this.language); 1811 }, 1812 1813 /** 1814 * Return the ISO 3166 region code for this locale. 1815 * @return {string|undefined} the region code of this locale 1816 */ 1817 getRegion: function() { 1818 return this.region; 1819 }, 1820 1821 /** 1822 * Return the region of this locale as an ISO-3166-alpha3 region code 1823 * @return {string|undefined} the alpha3 region code of this locale 1824 */ 1825 getRegionAlpha3: function() { 1826 return Locale.regionAlpha2ToAlpha3(this.region); 1827 }, 1828 1829 /** 1830 * Return the ISO 15924 script code for this locale 1831 * @return {string|undefined} the script code of this locale 1832 */ 1833 getScript: function () { 1834 return this.script; 1835 }, 1836 1837 /** 1838 * Return the variant code for this locale 1839 * @return {string|undefined} the variant code of this locale, if any 1840 */ 1841 getVariant: function() { 1842 return this.variant; 1843 }, 1844 1845 /** 1846 * Return the whole locale specifier as a string. 1847 * @return {string} the locale specifier 1848 */ 1849 getSpec: function() { 1850 return this.spec; 1851 }, 1852 1853 /** 1854 * Express this locale object as a string. Currently, this simply calls the getSpec 1855 * function to represent the locale as its specifier. 1856 * 1857 * @return {string} the locale specifier 1858 */ 1859 toString: function() { 1860 return this.getSpec(); 1861 }, 1862 1863 /** 1864 * Return true if the the other locale is exactly equal to the current one. 1865 * @return {boolean} whether or not the other locale is equal to the current one 1866 */ 1867 equals: function(other) { 1868 return this.language === other.language && 1869 this.region === other.region && 1870 this.script === other.script && 1871 this.variant === other.variant; 1872 }, 1873 1874 /** 1875 * Return true if the current locale is the special pseudo locale. 1876 * @return {boolean} true if the current locale is the special pseudo locale 1877 */ 1878 isPseudo: function () { 1879 return JSUtils.indexOf(ilib.pseudoLocales, this.spec) > -1; 1880 } 1881 }; 1882 1883 // static functions 1884 /** 1885 * @private 1886 */ 1887 Locale.locales = [ 1888 1889 ]; 1890 1891 /** 1892 * Return the list of available locales that this iLib file supports. 1893 * If this copy of ilib is pre-assembled with locale data, then the 1894 * list locales may be much smaller 1895 * than the list of all available locales in the iLib repository. The 1896 * assembly tool will automatically fill in the list for an assembled 1897 * copy of iLib. If this copy is being used with dynamically loaded 1898 * data, then you 1899 * can load any locale that iLib supports. You can form a locale with any 1900 * combination of a language and region tags that exist in the locale 1901 * data directory. Language tags are in the root of the locale data dir, 1902 * and region tags can be found underneath the "und" directory. (The 1903 * region tags are separated into a different dir because the region names 1904 * conflict with language names on file systems that are case-insensitive.) 1905 * If you have culled the locale data directory to limit the size of 1906 * your app, then this function should return only those files that actually exist 1907 * according to the ilibmanifest.json file in the root of that locale 1908 * data dir. Make sure your ilibmanifest.json file is up-to-date with 1909 * respect to the list of files that exist in the locale data dir. 1910 * 1911 * @param {boolean} sync if false, load the list of available files from disk 1912 * asynchronously, otherwise load them synchronously. (Default: true/synchronously) 1913 * @param {Function} onLoad a callback function to call if asynchronous 1914 * load was requested and the list of files have been loaded. 1915 * @return {Array.<string>} this is an array of locale specs for which 1916 * this iLib file has locale data for 1917 */ 1918 Locale.getAvailableLocales = function (sync, onLoad) { 1919 var locales = []; 1920 if (Locale.locales.length || typeof(ilib._load.listAvailableFiles) !== 'function') { 1921 locales = Locale.locales; 1922 if (onLoad && typeof(onLoad) === 'function') { 1923 onLoad(locales); 1924 } 1925 } else { 1926 if (typeof(sync) === 'undefined') { 1927 sync = true; 1928 } 1929 ilib._load.listAvailableFiles(sync, function(manifest) { 1930 if (manifest) { 1931 for (var dir in manifest) { 1932 var filelist = manifest[dir]; 1933 for (var i = 0; i < filelist.length; i++) { 1934 if (filelist[i].length > 15 && filelist[i].substr(-15) === "localeinfo.json") { 1935 locales.push(filelist[i].substring(0,filelist[i].length-16).replace(/\//g, "-")); 1936 } 1937 } 1938 } 1939 } 1940 if (onLoad && typeof(onLoad) === 'function') { 1941 onLoad(locales); 1942 } 1943 }); 1944 } 1945 return locales; 1946 }; 1947 1948 1949 1950 /*< Utils.js */ 1951 /* 1952 * Utils.js - Core utility routines 1953 * 1954 * Copyright © 2012-2015, JEDLSoft 1955 * 1956 * Licensed under the Apache License, Version 2.0 (the "License"); 1957 * you may not use this file except in compliance with the License. 1958 * You may obtain a copy of the License at 1959 * 1960 * http://www.apache.org/licenses/LICENSE-2.0 1961 * 1962 * Unless required by applicable law or agreed to in writing, software 1963 * distributed under the License is distributed on an "AS IS" BASIS, 1964 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1965 * 1966 * See the License for the specific language governing permissions and 1967 * limitations under the License. 1968 */ 1969 1970 // !depends ilib.js Locale.js JSUtils.js 1971 1972 1973 var Utils = {}; 1974 1975 /** 1976 * Find and merge all the locale data for a particular prefix in the given locale 1977 * and return it as a single javascript object. This merges the data in the 1978 * correct order: 1979 * 1980 * <ol> 1981 * <li>shared data (usually English) 1982 * <li>data for language 1983 * <li>data for language + region 1984 * <li>data for language + region + script 1985 * <li>data for language + region + script + variant 1986 * </ol> 1987 * 1988 * It is okay for any of the above to be missing. This function will just skip the 1989 * missing data. However, if everything except the shared data is missing, this 1990 * function returns undefined, allowing the caller to go and dynamically load the 1991 * data instead. 1992 * 1993 * @static 1994 * @param {string} prefix prefix under ilib.data of the data to merge 1995 * @param {Locale} locale locale of the data being sought 1996 * @param {boolean=} replaceArrays if true, replace the array elements in object1 with those in object2. 1997 * If false, concatenate array elements in object1 with items in object2. 1998 * @param {boolean=} returnOne if true, only return the most locale-specific data. If false, 1999 * merge all the relevant locale data together. 2000 * @return {Object?} the merged locale data 2001 */ 2002 Utils.mergeLocData = function (prefix, locale, replaceArrays, returnOne) { 2003 var data = undefined; 2004 var loc = locale || new Locale(); 2005 var foundLocaleData = false; 2006 var property = prefix; 2007 var mostSpecific; 2008 2009 data = ilib.data[prefix] || {}; 2010 2011 mostSpecific = data; 2012 2013 if (loc.getLanguage()) { 2014 property = prefix + '_' + loc.getLanguage(); 2015 if (ilib.data[property]) { 2016 foundLocaleData = true; 2017 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2018 mostSpecific = ilib.data[property]; 2019 } 2020 } 2021 2022 if (loc.getRegion()) { 2023 property = prefix + '_' + loc.getRegion(); 2024 if (ilib.data[property]) { 2025 foundLocaleData = true; 2026 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2027 mostSpecific = ilib.data[property]; 2028 } 2029 } 2030 2031 if (loc.getLanguage()) { 2032 property = prefix + '_' + loc.getLanguage(); 2033 2034 if (loc.getScript()) { 2035 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript(); 2036 if (ilib.data[property]) { 2037 foundLocaleData = true; 2038 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2039 mostSpecific = ilib.data[property]; 2040 } 2041 } 2042 2043 if (loc.getRegion()) { 2044 property = prefix + '_' + loc.getLanguage() + '_' + loc.getRegion(); 2045 if (ilib.data[property]) { 2046 foundLocaleData = true; 2047 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2048 mostSpecific = ilib.data[property]; 2049 } 2050 } 2051 } 2052 2053 if (loc.getRegion() && loc.getVariant()) { 2054 property = prefix + '_' + loc.getLanguage() + '_' + loc.getVariant(); 2055 if (ilib.data[property]) { 2056 foundLocaleData = true; 2057 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2058 mostSpecific = ilib.data[property]; 2059 } 2060 } 2061 2062 if (loc.getLanguage() && loc.getScript() && loc.getRegion()) { 2063 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript() + '_' + loc.getRegion(); 2064 if (ilib.data[property]) { 2065 foundLocaleData = true; 2066 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2067 mostSpecific = ilib.data[property]; 2068 } 2069 } 2070 2071 if (loc.getLanguage() && loc.getRegion() && loc.getVariant()) { 2072 property = prefix + '_' + loc.getLanguage() + '_' + loc.getRegion() + '_' + loc.getVariant(); 2073 if (ilib.data[property]) { 2074 foundLocaleData = true; 2075 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2076 mostSpecific = ilib.data[property]; 2077 } 2078 } 2079 2080 if (loc.getLanguage() && loc.getScript() && loc.getRegion() && loc.getVariant()) { 2081 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript() + '_' + loc.getRegion() + '_' + loc.getVariant(); 2082 if (ilib.data[property]) { 2083 foundLocaleData = true; 2084 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2085 mostSpecific = ilib.data[property]; 2086 } 2087 } 2088 2089 return foundLocaleData ? (returnOne ? mostSpecific : data) : undefined; 2090 }; 2091 2092 /** 2093 * Return an array of relative path names for the 2094 * files that represent the data for the given locale.<p> 2095 * 2096 * Note that to prevent the situation where a directory for 2097 * a language exists next to the directory for a region where 2098 * the language code and region code differ only by case, the 2099 * plain region directories are located under the special 2100 * "undefined" language directory which has the ISO code "und". 2101 * The reason is that some platforms have case-insensitive 2102 * file systems, and you cannot have 2 directories with the 2103 * same name which only differ by case. For example, "es" is 2104 * the ISO 639 code for the language "Spanish" and "ES" is 2105 * the ISO 3166 code for the region "Spain", so both the 2106 * directories cannot exist underneath "locale". The region 2107 * therefore will be loaded from "und/ES" instead.<p> 2108 * 2109 * <h4>Variations</h4> 2110 * 2111 * With only language and region specified, the following 2112 * sequence of paths will be generated:<p> 2113 * 2114 * <pre> 2115 * language 2116 * und/region 2117 * language/region 2118 * </pre> 2119 * 2120 * With only language and script specified:<p> 2121 * 2122 * <pre> 2123 * language 2124 * language/script 2125 * </pre> 2126 * 2127 * With only script and region specified:<p> 2128 * 2129 * <pre> 2130 * und/region 2131 * </pre> 2132 * 2133 * With only region and variant specified:<p> 2134 * 2135 * <pre> 2136 * und/region 2137 * region/variant 2138 * </pre> 2139 * 2140 * With only language, script, and region specified:<p> 2141 * 2142 * <pre> 2143 * language 2144 * und/region 2145 * language/script 2146 * language/region 2147 * language/script/region 2148 * </pre> 2149 * 2150 * With only language, region, and variant specified:<p> 2151 * 2152 * <pre> 2153 * language 2154 * und/region 2155 * language/region 2156 * region/variant 2157 * language/region/variant 2158 * </pre> 2159 * 2160 * With all parts specified:<p> 2161 * 2162 * <pre> 2163 * language 2164 * und/region 2165 * language/script 2166 * language/region 2167 * region/variant 2168 * language/script/region 2169 * language/region/variant 2170 * language/script/region/variant 2171 * </pre> 2172 * 2173 * @static 2174 * @param {Locale} locale load the files for this locale 2175 * @param {string?} name the file name of each file to load without 2176 * any path 2177 * @return {Array.<string>} An array of relative path names 2178 * for the files that contain the locale data 2179 */ 2180 Utils.getLocFiles = function(locale, name) { 2181 var dir = ""; 2182 var files = []; 2183 var filename = name || "resources.json"; 2184 var loc = locale || new Locale(); 2185 2186 var language = loc.getLanguage(); 2187 var region = loc.getRegion(); 2188 var script = loc.getScript(); 2189 var variant = loc.getVariant(); 2190 2191 files.push(filename); // generic shared file 2192 2193 if (language) { 2194 dir = language + "/"; 2195 files.push(dir + filename); 2196 } 2197 2198 if (region) { 2199 dir = "und/" + region + "/"; 2200 files.push(dir + filename); 2201 } 2202 2203 if (language) { 2204 if (script) { 2205 dir = language + "/" + script + "/"; 2206 files.push(dir + filename); 2207 } 2208 if (region) { 2209 dir = language + "/" + region + "/"; 2210 files.push(dir + filename); 2211 } 2212 } 2213 2214 if (region && variant) { 2215 dir = "und/" + region + "/" + variant + "/"; 2216 files.push(dir + filename); 2217 } 2218 2219 if (language && script && region) { 2220 dir = language + "/" + script + "/" + region + "/"; 2221 files.push(dir + filename); 2222 } 2223 2224 if (language && region && variant) { 2225 dir = language + "/" + region + "/" + variant + "/"; 2226 files.push(dir + filename); 2227 } 2228 2229 if (language && script && region && variant) { 2230 dir = language + "/" + script + "/" + region + "/" + variant + "/"; 2231 files.push(dir + filename); 2232 } 2233 2234 return files; 2235 }; 2236 2237 /** 2238 * Load data using the new loader object or via the old function callback. 2239 * @static 2240 * @private 2241 */ 2242 Utils._callLoadData = function (files, sync, params, callback) { 2243 // console.log("Utils._callLoadData called"); 2244 if (typeof(ilib._load) === 'function') { 2245 // console.log("Utils._callLoadData: calling as a regular function"); 2246 return ilib._load(files, sync, params, callback); 2247 } else if (typeof(ilib._load) === 'object' && typeof(ilib._load.loadFiles) === 'function') { 2248 // console.log("Utils._callLoadData: calling as an object"); 2249 return ilib._load.loadFiles(files, sync, params, callback); 2250 } 2251 2252 // console.log("Utils._callLoadData: not calling. Type is " + typeof(ilib._load) + " and instanceof says " + (ilib._load instanceof Loader)); 2253 return undefined; 2254 }; 2255 2256 /** 2257 * Find locale data or load it in. If the data with the given name is preassembled, it will 2258 * find the data in ilib.data. If the data is not preassembled but there is a loader function, 2259 * this function will call it to load the data. Otherwise, the callback will be called with 2260 * undefined as the data. This function will create a cache under the given class object. 2261 * If data was successfully loaded, it will be set into the cache so that future access to 2262 * the same data for the same locale is much quicker.<p> 2263 * 2264 * The parameters can specify any of the following properties:<p> 2265 * 2266 * <ul> 2267 * <li><i>name</i> - String. The name of the file being loaded. Default: ResBundle.json 2268 * <li><i>object</i> - Object. The class attempting to load data. The cache is stored inside of here. 2269 * <li><i>locale</i> - Locale. The locale for which data is loaded. Default is the current locale. 2270 * <li><i>nonlocale</i> - boolean. If true, the data being loaded is not locale-specific. 2271 * <li><i>type</i> - String. Type of file to load. This can be "json" or "other" type. Default: "json" 2272 * <li><i>replace</i> - boolean. When merging json objects, this parameter controls whether to merge arrays 2273 * or have arrays replace each other. If true, arrays in child objects replace the arrays in parent 2274 * objects. When false, the arrays in child objects are concatenated with the arrays in parent objects. 2275 * <li><i>loadParams</i> - Object. An object with parameters to pass to the loader function 2276 * <li><i>sync</i> - boolean. Whether or not to load the data synchronously 2277 * <li><i>callback</i> - function(?)=. callback Call back function to call when the data is available. 2278 * Data is not returned from this method, so a callback function is mandatory. 2279 * </ul> 2280 * 2281 * @static 2282 * @param {Object} params Parameters configuring how to load the files (see above) 2283 */ 2284 Utils.loadData = function(params) { 2285 var name = "resources.json", 2286 object = undefined, 2287 locale = new Locale(ilib.getLocale()), 2288 sync = false, 2289 type = undefined, 2290 loadParams = {}, 2291 callback = undefined, 2292 nonlocale = false, 2293 replace = false, 2294 basename; 2295 2296 if (!params || typeof(params.callback) !== 'function') { 2297 return; 2298 } 2299 2300 if (params.name) { 2301 name = params.name; 2302 } 2303 if (params.object) { 2304 object = params.object; 2305 } 2306 if (params.locale) { 2307 locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 2308 } 2309 if (params.type) { 2310 type = params.type; 2311 } 2312 if (params.loadParams) { 2313 loadParams = params.loadParams; 2314 } 2315 if (params.sync) { 2316 sync = params.sync; 2317 } 2318 if (params.nonlocale) { 2319 nonlocale = !!params.nonlocale; 2320 } 2321 if (typeof(params.replace) === 'boolean') { 2322 replace = params.replace; 2323 } 2324 2325 callback = params.callback; 2326 2327 if (object && !object.cache) { 2328 object.cache = {}; 2329 } 2330 2331 if (!type) { 2332 var dot = name.lastIndexOf("."); 2333 type = (dot !== -1) ? name.substring(dot+1) : "text"; 2334 } 2335 2336 var spec = ((!nonlocale && locale.getSpec().replace(/-/g, '_')) || "root") + "," + name + "," + String(JSUtils.hashCode(loadParams)); 2337 if (!object || typeof(object.cache[spec]) === 'undefined') { 2338 var data, returnOne = (loadParams && loadParams.returnOne); 2339 2340 if (type === "json") { 2341 // console.log("type is json"); 2342 basename = name.substring(0, name.lastIndexOf(".")); 2343 if (nonlocale) { 2344 basename = basename.replace(/[\.:\(\)\/\\\+\-]/g, "_"); 2345 data = ilib.data[basename]; 2346 } else { 2347 data = Utils.mergeLocData(basename, locale, replace, returnOne); 2348 } 2349 if (data) { 2350 // console.log("found assembled data"); 2351 if (object) { 2352 object.cache[spec] = data; 2353 } 2354 callback(data); 2355 return; 2356 } 2357 } 2358 2359 // console.log("ilib._load is " + typeof(ilib._load)); 2360 if (typeof(ilib._load) !== 'undefined') { 2361 // the data is not preassembled, so attempt to load it dynamically 2362 var files = nonlocale ? [ name || "resources.json" ] : Utils.getLocFiles(locale, name); 2363 if (type !== "json") { 2364 loadParams.returnOne = true; 2365 } 2366 2367 Utils._callLoadData(files, sync, loadParams, ilib.bind(this, function(arr) { 2368 if (type === "json") { 2369 data = ilib.data[basename] || {}; 2370 for (var i = 0; i < arr.length; i++) { 2371 if (typeof(arr[i]) !== 'undefined') { 2372 data = loadParams.returnOne ? arr[i] : JSUtils.merge(data, arr[i], replace); 2373 } 2374 } 2375 2376 if (object) { 2377 object.cache[spec] = data; 2378 } 2379 callback(data); 2380 } else { 2381 var i = arr.length-1; 2382 while (i > -1 && !arr[i]) { 2383 i--; 2384 } 2385 if (i > -1) { 2386 if (object) { 2387 object.cache[spec] = arr[i]; 2388 } 2389 callback(arr[i]); 2390 } else { 2391 callback(undefined); 2392 } 2393 } 2394 })); 2395 } else { 2396 // no data other than the generic shared data 2397 if (type === "json") { 2398 data = ilib.data[basename]; 2399 } 2400 if (object && data) { 2401 object.cache[spec] = data; 2402 } 2403 callback(data); 2404 } 2405 } else { 2406 callback(object.cache[spec]); 2407 } 2408 }; 2409 2410 2411 /*< LocaleInfo.js */ 2412 /* 2413 * LocaleInfo.js - Encode locale-specific defaults 2414 * 2415 * Copyright © 2012-2015, JEDLSoft 2416 * 2417 * Licensed under the Apache License, Version 2.0 (the "License"); 2418 * you may not use this file except in compliance with the License. 2419 * You may obtain a copy of the License at 2420 * 2421 * http://www.apache.org/licenses/LICENSE-2.0 2422 * 2423 * Unless required by applicable law or agreed to in writing, software 2424 * distributed under the License is distributed on an "AS IS" BASIS, 2425 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2426 * 2427 * See the License for the specific language governing permissions and 2428 * limitations under the License. 2429 */ 2430 2431 // !depends ilib.js Locale.js Utils.js 2432 2433 // !data localeinfo 2434 2435 2436 /** 2437 * @class 2438 * Create a new locale info instance. Locale info instances give information about 2439 * the default settings for a particular locale. These settings may be overridden 2440 * by various parts of the code, and should be used as a fall-back setting of last 2441 * resort. <p> 2442 * 2443 * The optional options object holds extra parameters if they are necessary. The 2444 * current list of supported options are: 2445 * 2446 * <ul> 2447 * <li><i>onLoad</i> - a callback function to call when the locale info object is fully 2448 * loaded. When the onLoad option is given, the localeinfo object will attempt to 2449 * load any missing locale data using the ilib loader callback. 2450 * When the constructor is done (even if the data is already preassembled), the 2451 * onLoad function is called with the current instance as a parameter, so this 2452 * callback can be used with preassembled or dynamic loading or a mix of the two. 2453 * 2454 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 2455 * asynchronously. If this option is given as "false", then the "onLoad" 2456 * callback must be given, as the instance returned from this constructor will 2457 * not be usable for a while. 2458 * 2459 * <li><i>loadParams</i> - an object containing parameters to pass to the 2460 * loader callback function when locale data is missing. The parameters are not 2461 * interpretted or modified in any way. They are simply passed along. The object 2462 * may contain any property/value pairs as long as the calling code is in 2463 * agreement with the loader callback function as to what those parameters mean. 2464 * </ul> 2465 * 2466 * If this copy of ilib is pre-assembled and all the data is already available, 2467 * or if the data was already previously loaded, then this constructor will call 2468 * the onLoad callback immediately when the initialization is done. 2469 * If the onLoad option is not given, this class will only attempt to load any 2470 * missing locale data synchronously. 2471 * 2472 * 2473 * @constructor 2474 * @see {ilib.setLoaderCallback} for information about registering a loader callback 2475 * function 2476 * @param {Locale|string=} locale the locale for which the info is sought, or undefined for 2477 * @param {Object=} options the locale for which the info is sought, or undefined for 2478 * the current locale 2479 */ 2480 var LocaleInfo = function(locale, options) { 2481 var sync = true, 2482 loadParams = undefined; 2483 2484 /** 2485 @private 2486 @type {{ 2487 calendar:string, 2488 clock:string, 2489 currency:string, 2490 delimiter: {quotationStart:string,quotationEnd:string,alternateQuotationStart:string,alternateQuotationEnd:string}, 2491 firstDayOfWeek:number, 2492 meridiems:string, 2493 numfmt:{ 2494 currencyFormats:{common:string,commonNegative:string,iso:string,isoNegative:string}, 2495 decimalChar:string, 2496 exponential:string, 2497 groupChar:string, 2498 negativenumFmt:string, 2499 negativepctFmt:string, 2500 pctChar:string, 2501 pctFmt:string, 2502 prigroupSize:number, 2503 roundingMode:string, 2504 script:string, 2505 secgroupSize:number 2506 }, 2507 timezone:string, 2508 units:string, 2509 weekendEnd:number, 2510 weekendStart:number, 2511 paperSizes:{regular:string} 2512 }} 2513 */ 2514 this.info = LocaleInfo.defaultInfo; 2515 2516 switch (typeof(locale)) { 2517 case "string": 2518 this.locale = new Locale(locale); 2519 break; 2520 default: 2521 case "undefined": 2522 this.locale = new Locale(); 2523 break; 2524 case "object": 2525 this.locale = locale; 2526 break; 2527 } 2528 2529 if (options) { 2530 if (typeof(options.sync) !== 'undefined') { 2531 sync = (options.sync == true); 2532 } 2533 2534 if (typeof(options.loadParams) !== 'undefined') { 2535 loadParams = options.loadParams; 2536 } 2537 } 2538 2539 if (!LocaleInfo.cache) { 2540 LocaleInfo.cache = {}; 2541 } 2542 2543 Utils.loadData({ 2544 object: LocaleInfo, 2545 locale: this.locale, 2546 name: "localeinfo.json", 2547 sync: sync, 2548 loadParams: loadParams, 2549 callback: ilib.bind(this, function (info) { 2550 if (!info) { 2551 info = LocaleInfo.defaultInfo; 2552 var spec = this.locale.getSpec().replace(/-/g, "_"); 2553 LocaleInfo.cache[spec] = info; 2554 } 2555 this.info = info; 2556 if (options && typeof(options.onLoad) === 'function') { 2557 options.onLoad(this); 2558 } 2559 }) 2560 }); 2561 }; 2562 2563 LocaleInfo.defaultInfo = ilib.data.localeinfo; 2564 LocaleInfo.defaultInfo = LocaleInfo.defaultInfo || { 2565 "calendar": "gregorian", 2566 "clock": "24", 2567 "currency": "USD", 2568 "delimiter": { 2569 "quotationStart": "“", 2570 "quotationEnd": "”", 2571 "alternateQuotationStart": "‘", 2572 "alternateQuotationEnd": "’" 2573 }, 2574 "firstDayOfWeek": 1, 2575 "meridiems": "gregorian", 2576 "numfmt": { 2577 "currencyFormats": { 2578 "common": "{s}{n}", 2579 "commonNegative": "({s}{n})", 2580 "iso": "{s}{n}", 2581 "isoNegative": "({s}{n})" 2582 }, 2583 "decimalChar": ".", 2584 "exponential": "E", 2585 "groupChar": ",", 2586 "negativenumFmt": "-{n}", 2587 "negativepctFmt": "-{n}%", 2588 "pctChar": "%", 2589 "pctFmt": "{n}%", 2590 "prigroupSize": 3, 2591 "roundingMode": "halfdown", 2592 "script": "Latn", 2593 "secgroupSize": 0, 2594 "useNative": false 2595 }, 2596 "timezone": "Etc/UTC", 2597 "units": "metric", 2598 "weekendStart": 6, 2599 "weekendEnd": 0 2600 }; 2601 2602 LocaleInfo.prototype = { 2603 /** 2604 * Return the name of the locale's language in English. 2605 * @returns {string} the name of the locale's language in English 2606 */ 2607 getLanguageName: function () { 2608 return this.info["language.name"]; 2609 }, 2610 2611 /** 2612 * Return the name of the locale's region in English. If the locale 2613 * has no region, this returns undefined. 2614 * 2615 * @returns {string|undefined} the name of the locale's region in English 2616 */ 2617 getRegionName: function () { 2618 return this.info["region.name"]; 2619 }, 2620 2621 /** 2622 * Return whether this locale commonly uses the 12- or the 24-hour clock. 2623 * 2624 * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24" 2625 * if the locale commonly uses a 24-hour clock. 2626 */ 2627 getClock: function() { 2628 return this.info.clock; 2629 }, 2630 2631 /** 2632 * Return the locale that this info object was created with. 2633 * @returns {Locale} The locale spec of the locale used to construct this info instance 2634 */ 2635 getLocale: function () { 2636 return this.locale; 2637 }, 2638 2639 /** 2640 * Return the name of the measuring system that is commonly used in the given locale. 2641 * Valid values are "uscustomary", "imperial", and "metric". 2642 * 2643 * @returns {string} The name of the measuring system commonly used in the locale 2644 */ 2645 getUnits: function () { 2646 return this.info.units; 2647 }, 2648 2649 /** 2650 * Return the name of the calendar that is commonly used in the given locale. 2651 * 2652 * @returns {string} The name of the calendar commonly used in the locale 2653 */ 2654 getCalendar: function () { 2655 return this.info.calendar; 2656 }, 2657 2658 /** 2659 * Return the day of week that starts weeks in the current locale. Days are still 2660 * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars 2661 * should be displayed and weeks calculated with the day of week returned from this 2662 * function as the first day of the week. 2663 * 2664 * @returns {number} the day of the week that starts weeks in the current locale. 2665 */ 2666 getFirstDayOfWeek: function () { 2667 return this.info.firstDayOfWeek; 2668 }, 2669 2670 /** 2671 * Return the day of week that starts weekend in the current locale. Days are still 2672 * numbered the standard way with 0 for Sunday through 6 for Saturday. 2673 * 2674 * @returns {number} the day of the week that starts weeks in the current locale. 2675 */ 2676 getWeekEndStart: function () { 2677 return this.info.weekendStart; 2678 }, 2679 2680 /** 2681 * Return the day of week that starts weekend in the current locale. Days are still 2682 * numbered the standard way with 0 for Sunday through 6 for Saturday. 2683 * 2684 * @returns {number} the day of the week that starts weeks in the current locale. 2685 */ 2686 getWeekEndEnd: function () { 2687 return this.info.weekendEnd; 2688 }, 2689 2690 /** 2691 * Return the default time zone for this locale. Many locales span across multiple 2692 * time zones. In this case, the time zone with the largest population is chosen 2693 * to represent the locale. This is obviously not that accurate, but then again, 2694 * this method's return value should only be used as a default anyways. 2695 * @returns {string} the default time zone for this locale. 2696 */ 2697 getTimeZone: function () { 2698 return this.info.timezone; 2699 }, 2700 2701 /** 2702 * Return the decimal separator for formatted numbers in this locale. 2703 * @returns {string} the decimal separator char 2704 */ 2705 getDecimalSeparator: function () { 2706 return this.info.numfmt.decimalChar; 2707 }, 2708 2709 /** 2710 * Return the decimal separator for formatted numbers in this locale for native script. 2711 * @returns {string} the decimal separator char 2712 */ 2713 getNativeDecimalSeparator: function () { 2714 return (this.info.native_numfmt && this.info.native_numfmt.decimalChar) || this.info.numfmt.decimalChar; 2715 }, 2716 2717 /** 2718 * Return the separator character used to separate groups of digits on the 2719 * integer side of the decimal character. 2720 * @returns {string} the grouping separator char 2721 */ 2722 getGroupingSeparator: function () { 2723 return this.info.numfmt.groupChar; 2724 }, 2725 2726 /** 2727 * Return the separator character used to separate groups of digits on the 2728 * integer side of the decimal character for the native script if present other than the default script. 2729 * @returns {string} the grouping separator char 2730 */ 2731 getNativeGroupingSeparator: function () { 2732 return (this.info.native_numfmt && this.info.native_numfmt.groupChar) || this.info.numfmt.groupChar; 2733 }, 2734 2735 /** 2736 * Return the minimum number of digits grouped together on the integer side 2737 * for the first (primary) group. 2738 * In western European cultures, groupings are in 1000s, so the number of digits 2739 * is 3. 2740 * @returns {number} the number of digits in a primary grouping, or 0 for no grouping 2741 */ 2742 getPrimaryGroupingDigits: function () { 2743 return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' && this.info.numfmt.prigroupSize) || 0; 2744 }, 2745 2746 /** 2747 * Return the minimum number of digits grouped together on the integer side 2748 * for the second or more (secondary) group.<p> 2749 * 2750 * In western European cultures, all groupings are by 1000s, so the secondary 2751 * size should be 0 because there is no secondary size. In general, if this 2752 * method returns 0, then all groupings are of the primary size.<p> 2753 * 2754 * For some other cultures, the first grouping (primary) 2755 * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be 2756 * written as: "1,00,000". 2757 * 2758 * @returns {number} the number of digits in a secondary grouping, or 0 for no 2759 * secondary grouping. 2760 */ 2761 getSecondaryGroupingDigits: function () { 2762 return this.info.numfmt.secgroupSize || 0; 2763 }, 2764 2765 /** 2766 * Return the format template used to format percentages in this locale. 2767 * @returns {string} the format template for formatting percentages 2768 */ 2769 getPercentageFormat: function () { 2770 return this.info.numfmt.pctFmt; 2771 }, 2772 2773 /** 2774 * Return the format template used to format percentages in this locale 2775 * with negative amounts. 2776 * @returns {string} the format template for formatting percentages 2777 */ 2778 getNegativePercentageFormat: function () { 2779 return this.info.numfmt.negativepctFmt; 2780 }, 2781 2782 /** 2783 * Return the symbol used for percentages in this locale. 2784 * @returns {string} the symbol used for percentages in this locale 2785 */ 2786 getPercentageSymbol: function () { 2787 return this.info.numfmt.pctChar || "%"; 2788 }, 2789 2790 /** 2791 * Return the symbol used for exponential in this locale. 2792 * @returns {string} the symbol used for exponential in this locale 2793 */ 2794 getExponential: function () { 2795 return this.info.numfmt.exponential; 2796 }, 2797 2798 /** 2799 * Return the symbol used for exponential in this locale for native script. 2800 * @returns {string} the symbol used for exponential in this locale for native script 2801 */ 2802 getNativeExponential: function () { 2803 return (this.info.native_numfmt && this.info.native_numfmt.exponential) || this.info.numfmt.exponential; 2804 }, 2805 2806 /** 2807 * Return the symbol used for percentages in this locale for native script. 2808 * @returns {string} the symbol used for percentages in this locale for native script 2809 */ 2810 getNativePercentageSymbol: function () { 2811 return (this.info.native_numfmt && this.info.native_numfmt.pctChar) || this.info.numfmt.pctChar || "%"; 2812 2813 }, 2814 /** 2815 * Return the format template used to format negative numbers in this locale. 2816 * @returns {string} the format template for formatting negative numbers 2817 */ 2818 getNegativeNumberFormat: function () { 2819 return this.info.numfmt.negativenumFmt; 2820 }, 2821 2822 /** 2823 * Return an object containing the format templates for formatting currencies 2824 * in this locale. The object has a number of properties in it that each are 2825 * a particular style of format. Normally, this contains a "common" and an "iso" 2826 * style, but may contain others in the future. 2827 * @returns {Object} an object containing the format templates for currencies 2828 */ 2829 getCurrencyFormats: function () { 2830 return this.info.numfmt.currencyFormats; 2831 }, 2832 2833 /** 2834 * Return the currency that is legal in the locale, or which is most commonly 2835 * used in regular commerce. 2836 * @returns {string} the ISO 4217 code for the currency of this locale 2837 */ 2838 getCurrency: function () { 2839 return this.info.currency; 2840 }, 2841 2842 /** 2843 * Return a string that describes the style of digits used by this locale. 2844 * Possible return values are: 2845 * <ul> 2846 * <li><i>western</i> - uses the regular western 10-based digits 0 through 9 2847 * <li><i>optional</i> - native 10-based digits exist, but in modern usage, 2848 * this locale most often uses western digits 2849 * <li><i>native</i> - native 10-based native digits exist and are used 2850 * regularly by this locale 2851 * <li><i>custom</i> - uses native digits by default that are not 10-based 2852 * </ul> 2853 * @returns {string} string that describes the style of digits used in this locale 2854 */ 2855 getDigitsStyle: function () { 2856 if (this.info.numfmt.useNative) { 2857 return "native"; 2858 } 2859 if (typeof(this.info.native_numfmt) !== 'undefined') { 2860 return "optional"; 2861 } 2862 return "western"; 2863 }, 2864 2865 /** 2866 * Return the digits of the default script if they are defined. 2867 * If not defined, the default should be the regular "Arabic numerals" 2868 * used in the Latin script. (0-9) 2869 * @returns {string|undefined} the digits used in the default script 2870 */ 2871 getDigits: function () { 2872 return this.info.numfmt.digits; 2873 }, 2874 2875 /** 2876 * Return the digits of the native script if they are defined. 2877 * @returns {string|undefined} the digits used in the default script 2878 */ 2879 getNativeDigits: function () { 2880 return (this.info.numfmt.useNative && this.info.numfmt.digits) || (this.info.native_numfmt && this.info.native_numfmt.digits); 2881 }, 2882 2883 /** 2884 * If this locale typically uses a different type of rounding for numeric 2885 * formatting other than halfdown, especially for currency, then it can be 2886 * specified in the localeinfo. If the locale uses the default, then this 2887 * method returns undefined. The locale's rounding method overrides the 2888 * rounding method for the currency itself, which can sometimes shared 2889 * between various locales so it is less specific. 2890 * @returns {string} the name of the rounding mode typically used in this 2891 * locale, or "halfdown" if the locale does not override the default 2892 */ 2893 getRoundingMode: function () { 2894 return this.info.numfmt.roundingMode; 2895 }, 2896 2897 /** 2898 * Return the default script used to write text in the language of this 2899 * locale. Text for most languages is written in only one script, but there 2900 * are some languages where the text can be written in a number of scripts, 2901 * depending on a variety of things such as the region, ethnicity, religion, 2902 * etc. of the author. This method returns the default script for the 2903 * locale, in which the language is most commonly written.<p> 2904 * 2905 * The script is returned as an ISO 15924 4-letter code. 2906 * 2907 * @returns {string} the ISO 15924 code for the default script used to write 2908 * text in this locale 2909 */ 2910 getDefaultScript: function() { 2911 return (this.info.scripts) ? this.info.scripts[0] : "Latn"; 2912 }, 2913 2914 /** 2915 * Return the script used for the current locale. If the current locale 2916 * explicitly defines a script, then this script is returned. If not, then 2917 * the default script for the locale is returned. 2918 * 2919 * @see LocaleInfo.getDefaultScript 2920 * @returns {string} the ISO 15924 code for the script used to write 2921 * text in this locale 2922 */ 2923 getScript: function() { 2924 return this.locale.getScript() || this.getDefaultScript(); 2925 }, 2926 2927 /** 2928 * Return an array of script codes which are used to write text in the current 2929 * language. Text for most languages is written in only one script, but there 2930 * are some languages where the text can be written in a number of scripts, 2931 * depending on a variety of things such as the region, ethnicity, religion, 2932 * etc. of the author. This method returns an array of script codes in which 2933 * the language is commonly written. 2934 * 2935 * @returns {Array.<string>} an array of ISO 15924 codes for the scripts used 2936 * to write text in this language 2937 */ 2938 getAllScripts: function() { 2939 return this.info.scripts || ["Latn"]; 2940 }, 2941 2942 /** 2943 * Return the default style of meridiems used in this locale. Meridiems are 2944 * times of day like AM/PM. In a few locales with some calendars, for example 2945 * Amharic/Ethiopia using the Ethiopic calendar, the times of day may be 2946 * split into different segments than simple AM/PM as in the Gregorian 2947 * calendar. Only a few locales are like that. For most locales, formatting 2948 * a Gregorian date will use the regular Gregorian AM/PM meridiems. 2949 * 2950 * @returns {string} the default meridiems style used in this locale. Possible 2951 * values are "gregorian", "chinese", and "ethiopic" 2952 */ 2953 getMeridiemsStyle: function () { 2954 return this.info.meridiems || "gregorian"; 2955 }, 2956 /** 2957 * Return the default PaperSize information in this locale. 2958 * @returns {string} default PaperSize in this locale 2959 */ 2960 getPaperSize: function () { 2961 return this.info.paperSizes.regular; 2962 }, 2963 /** 2964 * Return the default Delimiter QuotationStart information in this locale. 2965 * @returns {string} default QuotationStart in this locale 2966 */ 2967 getDelimiterQuotationStart: function () { 2968 return this.info.delimiter.quotationStart; 2969 }, 2970 /** 2971 * Return the default Delimiter QuotationEnd information in this locale. 2972 * @returns {string} default QuotationEnd in this locale 2973 */ 2974 getDelimiterQuotationEnd: function () { 2975 return this.info.delimiter.quotationEnd; 2976 } 2977 }; 2978 2979 2980 2981 /*< IDate.js */ 2982 /* 2983 * IDate.js - Represent a date in any calendar. This class is subclassed for each 2984 * calendar and includes some shared functionality. 2985 * 2986 * Copyright © 2012-2015, JEDLSoft 2987 * 2988 * Licensed under the Apache License, Version 2.0 (the "License"); 2989 * you may not use this file except in compliance with the License. 2990 * You may obtain a copy of the License at 2991 * 2992 * http://www.apache.org/licenses/LICENSE-2.0 2993 * 2994 * Unless required by applicable law or agreed to in writing, software 2995 * distributed under the License is distributed on an "AS IS" BASIS, 2996 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2997 * 2998 * See the License for the specific language governing permissions and 2999 * limitations under the License. 3000 */ 3001 3002 /* !depends LocaleInfo.js */ 3003 3004 3005 /** 3006 * @class 3007 * Superclass for all the calendar date classes that contains shared 3008 * functionality. This class is never instantiated on its own. Instead, 3009 * you should use the {@link DateFactory} function to manufacture a new 3010 * instance of a subclass of IDate. This class is called IDate for "ilib 3011 * date" so that it does not conflict with the built-in Javascript Date 3012 * class. 3013 * 3014 * @private 3015 * @constructor 3016 * @param {Object=} options The date components to initialize this date with 3017 */ 3018 var IDate = function(options) { 3019 }; 3020 3021 /* place for the subclasses to put their constructors so that the factory method 3022 * can find them. Do this to add your date after it's defined: 3023 * IDate._constructors["mytype"] = IDate.MyTypeConstructor; 3024 */ 3025 IDate._constructors = {}; 3026 3027 IDate.prototype = { 3028 getType: function() { 3029 return "date"; 3030 }, 3031 3032 /** 3033 * Return the unix time equivalent to this date instance. Unix time is 3034 * the number of milliseconds since midnight on Jan 1, 1970 UTC (Gregorian). This 3035 * method only returns a valid number for dates between midnight, 3036 * Jan 1, 1970 UTC (Gregorian) and Jan 19, 2038 at 3:14:07am UTC (Gregorian) when 3037 * the unix time runs out. If this instance encodes a date outside of that range, 3038 * this method will return -1. For date types that are not Gregorian, the point 3039 * in time represented by this date object will only give a return value if it 3040 * is in the correct range in the Gregorian calendar as given previously. 3041 * 3042 * @return {number} a number giving the unix time, or -1 if the date is outside the 3043 * valid unix time range 3044 */ 3045 getTime: function() { 3046 return this.rd.getTime(); 3047 }, 3048 3049 /** 3050 * Return the extended unix time equivalent to this Gregorian date instance. Unix time is 3051 * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time 3052 * (or the type "time_t" in C/C++) is only encoded with an unsigned 32 bit integer, and thus 3053 * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above 3054 * 32 bits and the Date object allows you to encode up to 100 million days worth of time 3055 * after Jan 1, 1970, and even more interestingly, 100 million days worth of time before 3056 * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended 3057 * range. If this instance encodes a date outside of that range, this method will return 3058 * NaN. 3059 * 3060 * @return {number} a number giving the extended unix time, or Nan if the date is outside 3061 * the valid extended unix time range 3062 */ 3063 getTimeExtended: function() { 3064 return this.rd.getTimeExtended(); 3065 }, 3066 3067 /** 3068 * Set the time of this instance according to the given unix time. Unix time is 3069 * the number of milliseconds since midnight on Jan 1, 1970. 3070 * 3071 * @param {number} millis the unix time to set this date to in milliseconds 3072 */ 3073 setTime: function(millis) { 3074 this.rd = this.newRd({ 3075 unixtime: millis, 3076 cal: this.cal 3077 }); 3078 this._calcDateComponents(); 3079 }, 3080 3081 getDays: function() { 3082 return this.day; 3083 }, 3084 getMonths: function() { 3085 return this.month; 3086 }, 3087 getYears: function() { 3088 return this.year; 3089 }, 3090 getHours: function() { 3091 return this.hour; 3092 }, 3093 getMinutes: function() { 3094 return this.minute; 3095 }, 3096 getSeconds: function() { 3097 return this.second; 3098 }, 3099 getMilliseconds: function() { 3100 return this.millisecond; 3101 }, 3102 getEra: function() { 3103 return (this.year < 1) ? -1 : 1; 3104 }, 3105 3106 setDays: function(day) { 3107 this.day = parseInt(day, 10) || 1; 3108 this.rd._setDateComponents(this); 3109 }, 3110 setMonths: function(month) { 3111 this.month = parseInt(month, 10) || 1; 3112 this.rd._setDateComponents(this); 3113 }, 3114 setYears: function(year) { 3115 this.year = parseInt(year, 10) || 0; 3116 this.rd._setDateComponents(this); 3117 }, 3118 3119 setHours: function(hour) { 3120 this.hour = parseInt(hour, 10) || 0; 3121 this.rd._setDateComponents(this); 3122 }, 3123 setMinutes: function(minute) { 3124 this.minute = parseInt(minute, 10) || 0; 3125 this.rd._setDateComponents(this); 3126 }, 3127 setSeconds: function(second) { 3128 this.second = parseInt(second, 10) || 0; 3129 this.rd._setDateComponents(this); 3130 }, 3131 setMilliseconds: function(milli) { 3132 this.millisecond = parseInt(milli, 10) || 0; 3133 this.rd._setDateComponents(this); 3134 }, 3135 3136 /** 3137 * Return a new date instance in the current calendar that represents the first instance 3138 * of the given day of the week before the current date. The day of the week is encoded 3139 * as a number where 0 = Sunday, 1 = Monday, etc. 3140 * 3141 * @param {number} dow the day of the week before the current date that is being sought 3142 * @return {IDate} the date being sought 3143 */ 3144 before: function (dow) { 3145 return new this.constructor({ 3146 rd: this.rd.before(dow, this.offset), 3147 timezone: this.timezone 3148 }); 3149 }, 3150 3151 /** 3152 * Return a new date instance in the current calendar that represents the first instance 3153 * of the given day of the week after the current date. The day of the week is encoded 3154 * as a number where 0 = Sunday, 1 = Monday, etc. 3155 * 3156 * @param {number} dow the day of the week after the current date that is being sought 3157 * @return {IDate} the date being sought 3158 */ 3159 after: function (dow) { 3160 return new this.constructor({ 3161 rd: this.rd.after(dow, this.offset), 3162 timezone: this.timezone 3163 }); 3164 }, 3165 3166 /** 3167 * Return a new Gregorian date instance that represents the first instance of the 3168 * given day of the week on or before the current date. The day of the week is encoded 3169 * as a number where 0 = Sunday, 1 = Monday, etc. 3170 * 3171 * @param {number} dow the day of the week on or before the current date that is being sought 3172 * @return {IDate} the date being sought 3173 */ 3174 onOrBefore: function (dow) { 3175 return new this.constructor({ 3176 rd: this.rd.onOrBefore(dow, this.offset), 3177 timezone: this.timezone 3178 }); 3179 }, 3180 3181 /** 3182 * Return a new Gregorian date instance that represents the first instance of the 3183 * given day of the week on or after the current date. The day of the week is encoded 3184 * as a number where 0 = Sunday, 1 = Monday, etc. 3185 * 3186 * @param {number} dow the day of the week on or after the current date that is being sought 3187 * @return {IDate} the date being sought 3188 */ 3189 onOrAfter: function (dow) { 3190 return new this.constructor({ 3191 rd: this.rd.onOrAfter(dow, this.offset), 3192 timezone: this.timezone 3193 }); 3194 }, 3195 3196 /** 3197 * Return a Javascript Date object that is equivalent to this date 3198 * object. 3199 * 3200 * @return {Date|undefined} a javascript Date object 3201 */ 3202 getJSDate: function() { 3203 var unix = this.rd.getTimeExtended(); 3204 return isNaN(unix) ? undefined : new Date(unix); 3205 }, 3206 3207 /** 3208 * Return the Rata Die (fixed day) number of this date. 3209 * 3210 * @protected 3211 * @return {number} the rd date as a number 3212 */ 3213 getRataDie: function() { 3214 return this.rd.getRataDie(); 3215 }, 3216 3217 /** 3218 * Set the date components of this instance based on the given rd. 3219 * @protected 3220 * @param {number} rd the rata die date to set 3221 */ 3222 setRd: function (rd) { 3223 this.rd = this.newRd({ 3224 rd: rd, 3225 cal: this.cal 3226 }); 3227 this._calcDateComponents(); 3228 }, 3229 3230 /** 3231 * Return the Julian Day equivalent to this calendar date as a number. 3232 * 3233 * @return {number} the julian date equivalent of this date 3234 */ 3235 getJulianDay: function() { 3236 return this.rd.getJulianDay(); 3237 }, 3238 3239 /** 3240 * Set the date of this instance using a Julian Day. 3241 * @param {number|JulianDay} date the Julian Day to use to set this date 3242 */ 3243 setJulianDay: function (date) { 3244 this.rd = this.newRd({ 3245 julianday: (typeof(date) === 'object') ? date.getDate() : date, 3246 cal: this.cal 3247 }); 3248 this._calcDateComponents(); 3249 }, 3250 3251 /** 3252 * Return the time zone associated with this date, or 3253 * undefined if none was specified in the constructor. 3254 * 3255 * @return {string|undefined} the name of the time zone for this date instance 3256 */ 3257 getTimeZone: function() { 3258 return this.timezone || "local"; 3259 }, 3260 3261 /** 3262 * Set the time zone associated with this date. 3263 * @param {string=} tzName the name of the time zone to set into this date instance, 3264 * or "undefined" to unset the time zone 3265 */ 3266 setTimeZone: function (tzName) { 3267 if (!tzName || tzName === "") { 3268 // same as undefining it 3269 this.timezone = undefined; 3270 this.tz = undefined; 3271 } else if (typeof(tzName) === 'string') { 3272 this.timezone = tzName; 3273 this.tz = undefined; 3274 // assuming the same UTC time, but a new time zone, now we have to 3275 // recalculate what the date components are 3276 this._calcDateComponents(); 3277 } 3278 }, 3279 3280 /** 3281 * Return the rd number of the first Sunday of the given ISO year. 3282 * @protected 3283 * @param {number} year the year for which the first Sunday is being sought 3284 * @return {number} the rd of the first Sunday of the ISO year 3285 */ 3286 firstSunday: function (year) { 3287 var firstDay = this.newRd({ 3288 year: year, 3289 month: 1, 3290 day: 1, 3291 hour: 0, 3292 minute: 0, 3293 second: 0, 3294 millisecond: 0, 3295 cal: this.cal 3296 }); 3297 var firstThu = this.newRd({ 3298 rd: firstDay.onOrAfter(4), 3299 cal: this.cal 3300 }); 3301 return firstThu.before(0); 3302 }, 3303 3304 /** 3305 * Return the ISO 8601 week number in the current year for the current date. The week 3306 * number ranges from 0 to 55, as some years have 55 weeks assigned to them in some 3307 * calendars. 3308 * 3309 * @return {number} the week number for the current date 3310 */ 3311 getWeekOfYear: function() { 3312 var rd = Math.floor(this.rd.getRataDie()); 3313 var year = this._calcYear(rd + this.offset); 3314 var yearStart = this.firstSunday(year); 3315 var nextYear; 3316 3317 // if we have a January date, it may be in this ISO year or the previous year 3318 if (rd < yearStart) { 3319 yearStart = this.firstSunday(year-1); 3320 } else { 3321 // if we have a late December date, it may be in this ISO year, or the next year 3322 nextYear = this.firstSunday(year+1); 3323 if (rd >= nextYear) { 3324 yearStart = nextYear; 3325 } 3326 } 3327 3328 return Math.floor((rd-yearStart)/7) + 1; 3329 }, 3330 3331 /** 3332 * Return the ordinal number of the week within the month. The first week of a month is 3333 * the first one that contains 4 or more days in that month. If any days precede this 3334 * first week, they are marked as being in week 0. This function returns values from 0 3335 * through 6.<p> 3336 * 3337 * The locale is a required parameter because different locales that use the same 3338 * Gregorian calendar consider different days of the week to be the beginning of 3339 * the week. This can affect the week of the month in which some days are located. 3340 * 3341 * @param {Locale|string} locale the locale or locale spec to use when figuring out 3342 * the first day of the week 3343 * @return {number} the ordinal number of the week within the current month 3344 */ 3345 getWeekOfMonth: function(locale) { 3346 var li = new LocaleInfo(locale); 3347 3348 var first = this.newRd({ 3349 year: this._calcYear(this.rd.getRataDie()+this.offset), 3350 month: this.getMonths(), 3351 day: 1, 3352 hour: 0, 3353 minute: 0, 3354 second: 0, 3355 millisecond: 0, 3356 cal: this.cal 3357 }); 3358 var weekStart = first.onOrAfter(li.getFirstDayOfWeek()); 3359 3360 if (weekStart - first.getRataDie() > 3) { 3361 // if the first week has 4 or more days in it of the current month, then consider 3362 // that week 1. Otherwise, it is week 0. To make it week 1, move the week start 3363 // one week earlier. 3364 weekStart -= 7; 3365 } 3366 return Math.floor((this.rd.getRataDie() - weekStart) / 7) + 1; 3367 } 3368 }; 3369 3370 3371 /*< MathUtils.js */ 3372 /* 3373 * MathUtils.js - Misc math utility routines 3374 * 3375 * Copyright © 2013-2015, JEDLSoft 3376 * 3377 * Licensed under the Apache License, Version 2.0 (the "License"); 3378 * you may not use this file except in compliance with the License. 3379 * You may obtain a copy of the License at 3380 * 3381 * http://www.apache.org/licenses/LICENSE-2.0 3382 * 3383 * Unless required by applicable law or agreed to in writing, software 3384 * distributed under the License is distributed on an "AS IS" BASIS, 3385 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3386 * 3387 * See the License for the specific language governing permissions and 3388 * limitations under the License. 3389 */ 3390 3391 var MathUtils = {}; 3392 3393 /** 3394 * Return the sign of the given number. If the sign is negative, this function 3395 * returns -1. If the sign is positive or zero, this function returns 1. 3396 * @static 3397 * @param {number} num the number to test 3398 * @return {number} -1 if the number is negative, and 1 otherwise 3399 */ 3400 MathUtils.signum = function (num) { 3401 var n = num; 3402 if (typeof(num) === 'string') { 3403 n = parseInt(num, 10); 3404 } else if (typeof(num) !== 'number') { 3405 return 1; 3406 } 3407 return (n < 0) ? -1 : 1; 3408 }; 3409 3410 /** 3411 * @static 3412 * @protected 3413 * @param {number} num number to round 3414 * @return {number} rounded number 3415 */ 3416 MathUtils.floor = function (num) { 3417 return Math.floor(num); 3418 }; 3419 3420 /** 3421 * @static 3422 * @protected 3423 * @param {number} num number to round 3424 * @return {number} rounded number 3425 */ 3426 MathUtils.ceiling = function (num) { 3427 return Math.ceil(num); 3428 }; 3429 3430 /** 3431 * @static 3432 * @protected 3433 * @param {number} num number to round 3434 * @return {number} rounded number 3435 */ 3436 MathUtils.down = function (num) { 3437 return (num < 0) ? Math.ceil(num) : Math.floor(num); 3438 }; 3439 3440 /** 3441 * @static 3442 * @protected 3443 * @param {number} num number to round 3444 * @return {number} rounded number 3445 */ 3446 MathUtils.up = function (num) { 3447 return (num < 0) ? Math.floor(num) : Math.ceil(num); 3448 }; 3449 3450 /** 3451 * @static 3452 * @protected 3453 * @param {number} num number to round 3454 * @return {number} rounded number 3455 */ 3456 MathUtils.halfup = function (num) { 3457 return (num < 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 3458 }; 3459 3460 /** 3461 * @static 3462 * @protected 3463 * @param {number} num number to round 3464 * @return {number} rounded number 3465 */ 3466 MathUtils.halfdown = function (num) { 3467 return (num < 0) ? Math.floor(num + 0.5) : Math.ceil(num - 0.5); 3468 }; 3469 3470 /** 3471 * @static 3472 * @protected 3473 * @param {number} num number to round 3474 * @return {number} rounded number 3475 */ 3476 MathUtils.halfeven = function (num) { 3477 return (Math.floor(num) % 2 === 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 3478 }; 3479 3480 /** 3481 * @static 3482 * @protected 3483 * @param {number} num number to round 3484 * @return {number} rounded number 3485 */ 3486 MathUtils.halfodd = function (num) { 3487 return (Math.floor(num) % 2 !== 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 3488 }; 3489 3490 /** 3491 * Do a proper modulo function. The Javascript % operator will give the truncated 3492 * division algorithm, but for calendrical calculations, we need the Euclidean 3493 * division algorithm where the remainder of any division, whether the dividend 3494 * is negative or not, is always a positive number in the range [0, modulus).<p> 3495 * 3496 * 3497 * @static 3498 * @param {number} dividend the number being divided 3499 * @param {number} modulus the number dividing the dividend. This should always be a positive number. 3500 * @return the remainder of dividing the dividend by the modulus. 3501 */ 3502 MathUtils.mod = function (dividend, modulus) { 3503 if (modulus == 0) { 3504 return 0; 3505 } 3506 var x = dividend % modulus; 3507 return (x < 0) ? x + modulus : x; 3508 }; 3509 3510 /** 3511 * Do a proper adjusted modulo function. The Javascript % operator will give the truncated 3512 * division algorithm, but for calendrical calculations, we need the Euclidean 3513 * division algorithm where the remainder of any division, whether the dividend 3514 * is negative or not, is always a positive number in the range (0, modulus]. The adjusted 3515 * modulo function differs from the regular modulo function in that when the remainder is 3516 * zero, the modulus should be returned instead.<p> 3517 * 3518 * 3519 * @static 3520 * @param {number} dividend the number being divided 3521 * @param {number} modulus the number dividing the dividend. This should always be a positive number. 3522 * @return the remainder of dividing the dividend by the modulus. 3523 */ 3524 MathUtils.amod = function (dividend, modulus) { 3525 if (modulus == 0) { 3526 return 0; 3527 } 3528 var x = dividend % modulus; 3529 return (x <= 0) ? x + modulus : x; 3530 }; 3531 3532 3533 3534 /*< IString.js */ 3535 /* 3536 * IString.js - ilib string subclass definition 3537 * 3538 * Copyright © 2012-2015, JEDLSoft 3539 * 3540 * Licensed under the Apache License, Version 2.0 (the "License"); 3541 * you may not use this file except in compliance with the License. 3542 * You may obtain a copy of the License at 3543 * 3544 * http://www.apache.org/licenses/LICENSE-2.0 3545 * 3546 * Unless required by applicable law or agreed to in writing, software 3547 * distributed under the License is distributed on an "AS IS" BASIS, 3548 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3549 * 3550 * See the License for the specific language governing permissions and 3551 * limitations under the License. 3552 */ 3553 3554 // !depends ilib.js Utils.js Locale.js MathUtils.js 3555 3556 // !data plurals 3557 3558 3559 /** 3560 * @class 3561 * Create a new ilib string instance. This string inherits from and 3562 * extends the Javascript String class. It can be 3563 * used almost anywhere that a normal Javascript string is used, though in 3564 * some instances you will need to call the {@link #toString} method when 3565 * a built-in Javascript string is needed. The formatting methods are 3566 * methods that are not in the intrinsic String class and are most useful 3567 * when localizing strings in an app or web site in combination with 3568 * the ResBundle class.<p> 3569 * 3570 * This class is named IString ("ilib string") so as not to conflict with the 3571 * built-in Javascript String class. 3572 * 3573 * @constructor 3574 * @param {string|IString=} string initialize this instance with this string 3575 */ 3576 var IString = function (string) { 3577 if (typeof(string) === 'object') { 3578 if (string instanceof IString) { 3579 this.str = string.str; 3580 } else { 3581 this.str = string.toString(); 3582 } 3583 } else if (typeof(string) === 'string') { 3584 this.str = new String(string); 3585 } else { 3586 this.str = ""; 3587 } 3588 this.length = this.str.length; 3589 this.cpLength = -1; 3590 this.localeSpec = ilib.getLocale(); 3591 }; 3592 3593 /** 3594 * Return true if the given character is a Unicode surrogate character, 3595 * either high or low. 3596 * 3597 * @private 3598 * @static 3599 * @param {string} ch character to check 3600 * @return {boolean} true if the character is a surrogate 3601 */ 3602 IString._isSurrogate = function (ch) { 3603 var n = ch.charCodeAt(0); 3604 return ((n >= 0xDC00 && n <= 0xDFFF) || (n >= 0xD800 && n <= 0xDBFF)); 3605 }; 3606 3607 /** 3608 * Convert a UCS-4 code point to a Javascript string. The codepoint can be any valid 3609 * UCS-4 Unicode character, including supplementary characters. Standard Javascript 3610 * only supports supplementary characters using the UTF-16 encoding, which has 3611 * values in the range 0x0000-0xFFFF. String.fromCharCode() will only 3612 * give you a string containing 16-bit characters, and will not properly convert 3613 * the code point for a supplementary character (which has a value > 0xFFFF) into 3614 * two UTF-16 surrogate characters. Instead, it will just just give you whatever 3615 * single character happens to be the same as your code point modulo 0x10000, which 3616 * is almost never what you want.<p> 3617 * 3618 * Similarly, that means if you use String.charCodeAt() 3619 * you will only retrieve a 16-bit value, which may possibly be a single 3620 * surrogate character that is part of a surrogate pair representing a character 3621 * in the supplementary plane. It will not give you a code point. Use 3622 * IString.codePointAt() to access code points in a string, or use 3623 * an iterator to walk through the code points in a string. 3624 * 3625 * @static 3626 * @param {number} codepoint UCS-4 code point to convert to a character 3627 * @return {string} a string containing the character represented by the codepoint 3628 */ 3629 IString.fromCodePoint = function (codepoint) { 3630 if (codepoint < 0x10000) { 3631 return String.fromCharCode(codepoint); 3632 } else { 3633 var high = Math.floor(codepoint / 0x10000) - 1; 3634 var low = codepoint & 0xFFFF; 3635 3636 return String.fromCharCode(0xD800 | ((high & 0x000F) << 6) | ((low & 0xFC00) >> 10)) + 3637 String.fromCharCode(0xDC00 | (low & 0x3FF)); 3638 } 3639 }; 3640 3641 /** 3642 * Convert the character or the surrogate pair at the given 3643 * index into the intrinsic Javascript string to a Unicode 3644 * UCS-4 code point. 3645 * 3646 * @static 3647 * @param {string} str string to get the code point from 3648 * @param {number} index index into the string 3649 * @return {number} code point of the character at the 3650 * given index into the string 3651 */ 3652 IString.toCodePoint = function(str, index) { 3653 if (!str || str.length === 0) { 3654 return -1; 3655 } 3656 var code = -1, high = str.charCodeAt(index); 3657 if (high >= 0xD800 && high <= 0xDBFF) { 3658 if (str.length > index+1) { 3659 var low = str.charCodeAt(index+1); 3660 if (low >= 0xDC00 && low <= 0xDFFF) { 3661 code = (((high & 0x3C0) >> 6) + 1) << 16 | 3662 (((high & 0x3F) << 10) | (low & 0x3FF)); 3663 } 3664 } 3665 } else { 3666 code = high; 3667 } 3668 3669 return code; 3670 }; 3671 3672 /** 3673 * Load the plural the definitions of plurals for the locale. 3674 * @param {boolean=} sync 3675 * @param {Locale|string=} locale 3676 * @param {Object=} loadParams 3677 * @param {function(*)=} onLoad 3678 */ 3679 IString.loadPlurals = function (sync, locale, loadParams, onLoad) { 3680 var loc; 3681 if (locale) { 3682 loc = (typeof(locale) === 'string') ? new Locale(locale) : locale; 3683 } else { 3684 loc = new Locale(ilib.getLocale()); 3685 } 3686 var spec = loc.getLanguage(); 3687 if (!ilib.data["plurals_" + spec]) { 3688 Utils.loadData({ 3689 name: "plurals.json", 3690 object: IString, 3691 locale: loc, 3692 sync: sync, 3693 loadParams: loadParams, 3694 callback: ilib.bind(this, function(plurals) { 3695 if (!plurals) { 3696 IString.cache[spec] = {}; 3697 } 3698 ilib.data["plurals_" + spec] = plurals || {}; 3699 if (onLoad && typeof(onLoad) === 'function') { 3700 onLoad(ilib.data["plurals_" + spec]); 3701 } 3702 }) 3703 }); 3704 } else { 3705 if (onLoad && typeof(onLoad) === 'function') { 3706 onLoad(ilib.data["plurals_" + spec]); 3707 } 3708 } 3709 }; 3710 3711 /** 3712 * @private 3713 * @static 3714 */ 3715 IString._fncs = { 3716 /** 3717 * @private 3718 * @param {Object} obj 3719 * @return {string|undefined} 3720 */ 3721 firstProp: function (obj) { 3722 for (var p in obj) { 3723 if (p && obj[p]) { 3724 return p; 3725 } 3726 } 3727 return undefined; // should never get here 3728 }, 3729 3730 /** 3731 * @private 3732 * @param {Object} obj 3733 * @return {string|undefined} 3734 */ 3735 firstPropRule: function (obj) { 3736 if (Object.prototype.toString.call(obj) === '[object Array]') { 3737 return "inrange"; 3738 } else if (Object.prototype.toString.call(obj) === '[object Object]') { 3739 for (var p in obj) { 3740 if (p && obj[p]) { 3741 return p; 3742 } 3743 } 3744 3745 } 3746 return undefined; // should never get here 3747 }, 3748 3749 /** 3750 * @private 3751 * @param {Object} obj 3752 * @param {number|Object} n 3753 * @return {?} 3754 */ 3755 getValue: function (obj, n) { 3756 if (typeof(obj) === 'object') { 3757 var subrule = IString._fncs.firstPropRule(obj); 3758 if (subrule === "inrange") { 3759 return IString._fncs[subrule](obj, n); 3760 } 3761 return IString._fncs[subrule](obj[subrule], n); 3762 } else if (typeof(obj) === 'string') { 3763 if (typeof(n) === 'object'){ 3764 return n[obj]; 3765 } 3766 return n; 3767 } else { 3768 return obj; 3769 } 3770 }, 3771 3772 /** 3773 * @private 3774 * @param {number|Object} n 3775 * @param {Array.<number|Array.<number>>|Object} range 3776 * @return {boolean} 3777 */ 3778 matchRangeContinuous: function(n, range) { 3779 3780 for (var num in range) { 3781 if (typeof(num) !== 'undefined' && typeof(range[num]) !== 'undefined') { 3782 var obj = range[num]; 3783 if (typeof(obj) === 'number') { 3784 if (n === range[num]) { 3785 return true; 3786 } else if (n >= range[0] && n <= range[1]) { 3787 return true; 3788 } 3789 } else if (Object.prototype.toString.call(obj) === '[object Array]') { 3790 if (n >= obj[0] && n <= obj[1]) { 3791 return true; 3792 } 3793 } 3794 } 3795 } 3796 return false; 3797 }, 3798 3799 /** 3800 * @private 3801 * @param {*} number 3802 * @return {Object} 3803 */ 3804 calculateNumberDigits: function(number) { 3805 var numberToString = number.toString(); 3806 var parts = []; 3807 var numberDigits = {}; 3808 var operandSymbol = {}; 3809 var integerPart, decimalPartLength, decimalPart; 3810 3811 if (numberToString.indexOf('.') !== -1) { //decimal 3812 parts = numberToString.split('.', 2); 3813 numberDigits.integerPart = parseInt(parts[0], 10); 3814 numberDigits.decimalPartLength = parts[1].length; 3815 numberDigits.decimalPart = parseInt(parts[1], 10); 3816 3817 operandSymbol.n = parseFloat(number); 3818 operandSymbol.i = numberDigits.integerPart; 3819 operandSymbol.v = numberDigits.decimalPartLength; 3820 operandSymbol.w = numberDigits.decimalPartLength; 3821 operandSymbol.f = numberDigits.decimalPart; 3822 operandSymbol.t = numberDigits.decimalPart; 3823 3824 } else { 3825 numberDigits.integerPart = number; 3826 numberDigits.decimalPartLength = 0; 3827 numberDigits.decimalPart = 0; 3828 3829 operandSymbol.n = parseInt(number, 10); 3830 operandSymbol.i = numberDigits.integerPart; 3831 operandSymbol.v = 0; 3832 operandSymbol.w = 0; 3833 operandSymbol.f = 0; 3834 operandSymbol.t = 0; 3835 3836 } 3837 return operandSymbol 3838 }, 3839 3840 /** 3841 * @private 3842 * @param {number|Object} n 3843 * @param {Array.<number|Array.<number>>|Object} range 3844 * @return {boolean} 3845 */ 3846 matchRange: function(n, range) { 3847 return IString._fncs.matchRangeContinuous(n, range); 3848 }, 3849 3850 /** 3851 * @private 3852 * @param {Object} rule 3853 * @param {number} n 3854 * @return {boolean} 3855 */ 3856 is: function(rule, n) { 3857 var left = IString._fncs.getValue(rule[0], n); 3858 var right = IString._fncs.getValue(rule[1], n); 3859 return left == right; 3860 }, 3861 3862 /** 3863 * @private 3864 * @param {Object} rule 3865 * @param {number} n 3866 * @return {boolean} 3867 */ 3868 isnot: function(rule, n) { 3869 return IString._fncs.getValue(rule[0], n) != IString._fncs.getValue(rule[1], n); 3870 }, 3871 3872 /** 3873 * @private 3874 * @param {Object} rule 3875 * @param {number|Object} n 3876 * @return {boolean} 3877 */ 3878 inrange: function(rule, n) { 3879 if (typeof(rule[0]) === 'number') { 3880 if(typeof(n) === 'object') { 3881 return IString._fncs.matchRange(n.n,rule); 3882 } 3883 return IString._fncs.matchRange(n,rule); 3884 } else if (typeof(rule[0]) === 'undefined') { 3885 var subrule = IString._fncs.firstPropRule(rule); 3886 return IString._fncs[subrule](rule[subrule], n); 3887 } else { 3888 return IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); 3889 } 3890 }, 3891 /** 3892 * @private 3893 * @param {Object} rule 3894 * @param {number} n 3895 * @return {boolean} 3896 */ 3897 notin: function(rule, n) { 3898 return !IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); 3899 }, 3900 3901 /** 3902 * @private 3903 * @param {Object} rule 3904 * @param {number} n 3905 * @return {boolean} 3906 */ 3907 within: function(rule, n) { 3908 return IString._fncs.matchRangeContinuous(IString._fncs.getValue(rule[0], n), rule[1]); 3909 }, 3910 3911 /** 3912 * @private 3913 * @param {Object} rule 3914 * @param {number} n 3915 * @return {number} 3916 */ 3917 mod: function(rule, n) { 3918 return MathUtils.mod(IString._fncs.getValue(rule[0], n), IString._fncs.getValue(rule[1], n)); 3919 }, 3920 3921 /** 3922 * @private 3923 * @param {Object} rule 3924 * @param {number} n 3925 * @return {number} 3926 */ 3927 n: function(rule, n) { 3928 return n; 3929 }, 3930 3931 /** 3932 * @private 3933 * @param {Object} rule 3934 * @param {number|Object} n 3935 * @return {boolean} 3936 */ 3937 or: function(rule, n) { 3938 var ruleLength = rule.length; 3939 var result, i; 3940 for (i=0; i < ruleLength; i++) { 3941 result = IString._fncs.getValue(rule[i], n); 3942 if (result) { 3943 return true; 3944 } 3945 } 3946 return false; 3947 }, 3948 /** 3949 * @private 3950 * @param {Object} rule 3951 * @param {number|Object} n 3952 * @return {boolean} 3953 */ 3954 and: function(rule, n) { 3955 var ruleLength = rule.length; 3956 var result, i; 3957 for (i=0; i < ruleLength; i++) { 3958 result= IString._fncs.getValue(rule[i], n); 3959 if (!result) { 3960 return false; 3961 } 3962 } 3963 return true; 3964 }, 3965 /** 3966 * @private 3967 * @param {Object} rule 3968 * @param {number|Object} n 3969 * @return {boolean} 3970 */ 3971 eq: function(rule, n) { 3972 var valueLeft = IString._fncs.getValue(rule[0], n); 3973 var valueRight; 3974 3975 if (typeof(rule[0]) === 'string') { 3976 if (typeof(n) === 'object'){ 3977 valueRight = n[rule[0]]; 3978 if (typeof(rule[1])=== 'number'){ 3979 valueRight = IString._fncs.getValue(rule[1], n); 3980 } else if (typeof(rule[1])=== 'object' && (IString._fncs.firstPropRule(rule[1]) === "inrange" )){ 3981 valueRight = IString._fncs.getValue(rule[1], n); 3982 } 3983 } 3984 } else { 3985 if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod 3986 valueRight = IString._fncs.getValue(rule[1], valueLeft); 3987 } else { 3988 valueRight = IString._fncs.getValue(rule[1], n); 3989 } 3990 } 3991 if(typeof(valueRight) === 'boolean') { 3992 return (valueRight ? true : false); 3993 } else { 3994 return (valueLeft == valueRight ? true :false); 3995 } 3996 }, 3997 /** 3998 * @private 3999 * @param {Object} rule 4000 * @param {number|Object} n 4001 * @return {boolean} 4002 */ 4003 neq: function(rule, n) { 4004 var valueLeft = IString._fncs.getValue(rule[0], n); 4005 var valueRight; 4006 4007 if (typeof(rule[0]) === 'string') { 4008 valueRight = n[rule[0]]; 4009 if (typeof(rule[1])=== 'number'){ 4010 valueRight = IString._fncs.getValue(rule[1], n); 4011 } 4012 } else { 4013 if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod 4014 valueRight = IString._fncs.getValue(rule[1], valueLeft); 4015 } else { 4016 valueRight = IString._fncs.getValue(rule[1], n); 4017 } 4018 } 4019 4020 if(typeof(valueRight) === 'boolean') {//mod 4021 return (valueRight? false : true); 4022 } else { 4023 return (valueLeft !== valueRight ? true :false); 4024 } 4025 4026 } 4027 }; 4028 4029 IString.prototype = { 4030 /** 4031 * Return the length of this string in characters. This function defers to the regular 4032 * Javascript string class in order to perform the length function. Please note that this 4033 * method is a real method, whereas the length property of Javascript strings is 4034 * implemented by native code and appears as a property.<p> 4035 * 4036 * Example: 4037 * 4038 * <pre> 4039 * var str = new IString("this is a string"); 4040 * console.log("String is " + str._length() + " characters long."); 4041 * </pre> 4042 * @private 4043 */ 4044 _length: function () { 4045 return this.str.length; 4046 }, 4047 4048 /** 4049 * Format this string instance as a message, replacing the parameters with 4050 * the given values.<p> 4051 * 4052 * The string can contain any text that a regular Javascript string can 4053 * contain. Replacement parameters have the syntax: 4054 * 4055 * <pre> 4056 * {name} 4057 * </pre> 4058 * 4059 * Where "name" can be any string surrounded by curly brackets. The value of 4060 * "name" is taken from the parameters argument.<p> 4061 * 4062 * Example: 4063 * 4064 * <pre> 4065 * var str = new IString("There are {num} objects."); 4066 * console.log(str.format({ 4067 * num: 12 4068 * }); 4069 * </pre> 4070 * 4071 * Would give the output: 4072 * 4073 * <pre> 4074 * There are 12 objects. 4075 * </pre> 4076 * 4077 * If a property is missing from the parameter block, the replacement 4078 * parameter substring is left untouched in the string, and a different 4079 * set of parameters may be applied a second time. This way, different 4080 * parts of the code may format different parts of the message that they 4081 * happen to know about.<p> 4082 * 4083 * Example: 4084 * 4085 * <pre> 4086 * var str = new IString("There are {num} objects in the {container}."); 4087 * console.log(str.format({ 4088 * num: 12 4089 * }); 4090 * </pre> 4091 * 4092 * Would give the output:<p> 4093 * 4094 * <pre> 4095 * There are 12 objects in the {container}. 4096 * </pre> 4097 * 4098 * The result can then be formatted again with a different parameter block that 4099 * specifies a value for the container property. 4100 * 4101 * @param params a Javascript object containing values for the replacement 4102 * parameters in the current string 4103 * @return a new IString instance with as many replacement parameters filled 4104 * out as possible with real values. 4105 */ 4106 format: function (params) { 4107 var formatted = this.str; 4108 if (params) { 4109 var regex; 4110 for (var p in params) { 4111 if (typeof(params[p]) !== 'undefined') { 4112 regex = new RegExp("\{"+p+"\}", "g"); 4113 formatted = formatted.replace(regex, params[p]); 4114 } 4115 } 4116 } 4117 return formatted.toString(); 4118 }, 4119 4120 /** 4121 * Format a string as one of a choice of strings dependent on the value of 4122 * a particular argument index.<p> 4123 * 4124 * The syntax of the choice string is as follows. The string contains a 4125 * series of choices separated by a vertical bar character "|". Each choice 4126 * has a value or range of values to match followed by a hash character "#" 4127 * followed by the string to use if the variable matches the criteria.<p> 4128 * 4129 * Example string: 4130 * 4131 * <pre> 4132 * var num = 2; 4133 * var str = new IString("0#There are no objects.|1#There is one object.|2#There are {number} objects."); 4134 * console.log(str.formatChoice(num, { 4135 * number: num 4136 * })); 4137 * </pre> 4138 * 4139 * Gives the output: 4140 * 4141 * <pre> 4142 * "There are 2 objects." 4143 * </pre> 4144 * 4145 * The strings to format may contain replacement variables that will be formatted 4146 * using the format() method above and the params argument as a source of values 4147 * to use while formatting those variables.<p> 4148 * 4149 * If the criterion for a particular choice is empty, that choice will be used 4150 * as the default one for use when none of the other choice's criteria match.<p> 4151 * 4152 * Example string: 4153 * 4154 * <pre> 4155 * var num = 22; 4156 * var str = new IString("0#There are no objects.|1#There is one object.|#There are {number} objects."); 4157 * console.log(str.formatChoice(num, { 4158 * number: num 4159 * })); 4160 * </pre> 4161 * 4162 * Gives the output: 4163 * 4164 * <pre> 4165 * "There are 22 objects." 4166 * </pre> 4167 * 4168 * If multiple choice patterns can match a given argument index, the first one 4169 * encountered in the string will be used. If no choice patterns match the 4170 * argument index, then the default choice will be used. If there is no default 4171 * choice defined, then this method will return an empty string.<p> 4172 * 4173 * <b>Special Syntax</b><p> 4174 * 4175 * For any choice format string, all of the patterns in the string should be 4176 * of a single type: numeric, boolean, or string/regexp. The type of the 4177 * patterns is determined by the type of the argument index parameter.<p> 4178 * 4179 * If the argument index is numeric, then some special syntax can be used 4180 * in the patterns to match numeric ranges.<p> 4181 * 4182 * <ul> 4183 * <li><i>>x</i> - match any number that is greater than x 4184 * <li><i>>=x</i> - match any number that is greater than or equal to x 4185 * <li><i><x</i> - match any number that is less than x 4186 * <li><i><=x</i> - match any number that is less than or equal to x 4187 * <li><i>start-end</i> - match any number in the range [start,end) 4188 * <li><i>zero</i> - match any number in the class "zero". (See below for 4189 * a description of number classes.) 4190 * <li><i>one</i> - match any number in the class "one" 4191 * <li><i>two</i> - match any number in the class "two" 4192 * <li><i>few</i> - match any number in the class "few" 4193 * <li><i>many</i> - match any number in the class "many" 4194 * </ul> 4195 * 4196 * A number class defines a set of numbers that receive a particular syntax 4197 * in the strings. For example, in Slovenian, integers ending in the digit 4198 * "1" are in the "one" class, including 1, 21, 31, ... 101, 111, etc. 4199 * Similarly, integers ending in the digit "2" are in the "two" class. 4200 * Integers ending in the digits "3" or "4" are in the "few" class, and 4201 * every other integer is handled by the default string.<p> 4202 * 4203 * The definition of what numbers are included in a class is locale-dependent. 4204 * They are defined in the data file plurals.json. If your string is in a 4205 * different locale than the default for ilib, you should call the setLocale() 4206 * method of the string instance before calling this method.<p> 4207 * 4208 * <b>Other Pattern Types</b><p> 4209 * 4210 * If the argument index is a boolean, the string values "true" and "false" 4211 * may appear as the choice patterns.<p> 4212 * 4213 * If the argument index is of type string, then the choice patterns may contain 4214 * regular expressions, or static strings as degenerate regexps. 4215 * 4216 * @param {*} argIndex The index into the choice array of the current parameter 4217 * @param {Object} params The hash of parameter values that replace the replacement 4218 * variables in the string 4219 * @throws "syntax error in choice format pattern: " if there is a syntax error 4220 * @return {string} the formatted string 4221 */ 4222 formatChoice: function(argIndex, params) { 4223 var choices = this.str.split("|"); 4224 var type = typeof(argIndex); 4225 var limits = []; 4226 var strings = []; 4227 var i; 4228 var parts; 4229 var limit; 4230 var arg; 4231 var result = undefined; 4232 var defaultCase = ""; 4233 var numberDigits = {}; 4234 var operandValue = {}; 4235 4236 if (this.str.length === 0) { 4237 // nothing to do 4238 return ""; 4239 } 4240 4241 // first parse all the choices 4242 for (i = 0; i < choices.length; i++) { 4243 parts = choices[i].split("#"); 4244 if (parts.length > 2) { 4245 limits[i] = parts[0]; 4246 parts = parts.shift(); 4247 strings[i] = parts.join("#"); 4248 } else if (parts.length === 2) { 4249 limits[i] = parts[0]; 4250 strings[i] = parts[1]; 4251 } else { 4252 // syntax error 4253 throw "syntax error in choice format pattern: " + choices[i]; 4254 } 4255 } 4256 4257 // then apply the argument index 4258 for (i = 0; i < limits.length; i++) { 4259 if (limits[i].length === 0) { 4260 // this is default case 4261 defaultCase = new IString(strings[i]); 4262 } else { 4263 switch (type) { 4264 case 'number': 4265 operandValue = IString._fncs.calculateNumberDigits(argIndex); 4266 4267 if (limits[i].substring(0,2) === "<=") { 4268 limit = parseFloat(limits[i].substring(2)); 4269 if (operandValue.n <= limit) { 4270 result = new IString(strings[i]); 4271 i = limits.length; 4272 } 4273 } else if (limits[i].substring(0,2) === ">=") { 4274 limit = parseFloat(limits[i].substring(2)); 4275 if (operandValue.n >= limit) { 4276 result = new IString(strings[i]); 4277 i = limits.length; 4278 } 4279 } else if (limits[i].charAt(0) === "<") { 4280 limit = parseFloat(limits[i].substring(1)); 4281 if (operandValue.n < limit) { 4282 result = new IString(strings[i]); 4283 i = limits.length; 4284 } 4285 } else if (limits[i].charAt(0) === ">") { 4286 limit = parseFloat(limits[i].substring(1)); 4287 if (operandValue.n > limit) { 4288 result = new IString(strings[i]); 4289 i = limits.length; 4290 } 4291 } else { 4292 this.locale = this.locale || new Locale(this.localeSpec); 4293 switch (limits[i]) { 4294 case "zero": 4295 case "one": 4296 case "two": 4297 case "few": 4298 case "many": 4299 // CLDR locale-dependent number classes 4300 var ruleset = ilib.data["plurals_" + this.locale.getLanguage()]; 4301 if (ruleset) { 4302 var rule = ruleset[limits[i]]; 4303 if (IString._fncs.getValue(rule, operandValue)) { 4304 result = new IString(strings[i]); 4305 i = limits.length; 4306 } 4307 } 4308 break; 4309 default: 4310 var dash = limits[i].indexOf("-"); 4311 if (dash !== -1) { 4312 // range 4313 var start = limits[i].substring(0, dash); 4314 var end = limits[i].substring(dash+1); 4315 if (operandValue.n >= parseInt(start, 10) && operandValue.n <= parseInt(end, 10)) { 4316 result = new IString(strings[i]); 4317 i = limits.length; 4318 } 4319 } else if (operandValue.n === parseInt(limits[i], 10)) { 4320 // exact amount 4321 result = new IString(strings[i]); 4322 i = limits.length; 4323 } 4324 break; 4325 } 4326 } 4327 break; 4328 case 'boolean': 4329 if (limits[i] === "true" && argIndex === true) { 4330 result = new IString(strings[i]); 4331 i = limits.length; 4332 } else if (limits[i] === "false" && argIndex === false) { 4333 result = new IString(strings[i]); 4334 i = limits.length; 4335 } 4336 break; 4337 case 'string': 4338 var regexp = new RegExp(limits[i], "i"); 4339 if (regexp.test(argIndex)) { 4340 result = new IString(strings[i]); 4341 i = limits.length; 4342 } 4343 break; 4344 case 'object': 4345 throw "syntax error: fmtChoice parameter for the argument index cannot be an object"; 4346 } 4347 } 4348 } 4349 4350 if (!result) { 4351 result = defaultCase || new IString(""); 4352 } 4353 4354 result = result.format(params); 4355 4356 return result.toString(); 4357 }, 4358 4359 // delegates 4360 /** 4361 * Same as String.toString() 4362 * @return {string} this instance as regular Javascript string 4363 */ 4364 toString: function () { 4365 return this.str.toString(); 4366 }, 4367 4368 /** 4369 * Same as String.valueOf() 4370 * @return {string} this instance as a regular Javascript string 4371 */ 4372 valueOf: function () { 4373 return this.str.valueOf(); 4374 }, 4375 4376 /** 4377 * Same as String.charAt() 4378 * @param {number} index the index of the character being sought 4379 * @return {IString} the character at the given index 4380 */ 4381 charAt: function(index) { 4382 return new IString(this.str.charAt(index)); 4383 }, 4384 4385 /** 4386 * Same as String.charCodeAt(). This only reports on 4387 * 2-byte UCS-2 Unicode values, and does not take into 4388 * account supplementary characters encoded in UTF-16. 4389 * If you would like to take account of those characters, 4390 * use codePointAt() instead. 4391 * @param {number} index the index of the character being sought 4392 * @return {number} the character code of the character at the 4393 * given index in the string 4394 */ 4395 charCodeAt: function(index) { 4396 return this.str.charCodeAt(index); 4397 }, 4398 4399 /** 4400 * Same as String.concat() 4401 * @param {string} strings strings to concatenate to the current one 4402 * @return {IString} a concatenation of the given strings 4403 */ 4404 concat: function(strings) { 4405 return new IString(this.str.concat(strings)); 4406 }, 4407 4408 /** 4409 * Same as String.indexOf() 4410 * @param {string} searchValue string to search for 4411 * @param {number} start index into the string to start searching, or 4412 * undefined to search the entire string 4413 * @return {number} index into the string of the string being sought, 4414 * or -1 if the string is not found 4415 */ 4416 indexOf: function(searchValue, start) { 4417 return this.str.indexOf(searchValue, start); 4418 }, 4419 4420 /** 4421 * Same as String.lastIndexOf() 4422 * @param {string} searchValue string to search for 4423 * @param {number} start index into the string to start searching, or 4424 * undefined to search the entire string 4425 * @return {number} index into the string of the string being sought, 4426 * or -1 if the string is not found 4427 */ 4428 lastIndexOf: function(searchValue, start) { 4429 return this.str.lastIndexOf(searchValue, start); 4430 }, 4431 4432 /** 4433 * Same as String.match() 4434 * @param {string} regexp the regular expression to match 4435 * @return {Array.<string>} an array of matches 4436 */ 4437 match: function(regexp) { 4438 return this.str.match(regexp); 4439 }, 4440 4441 /** 4442 * Same as String.replace() 4443 * @param {string} searchValue a regular expression to search for 4444 * @param {string} newValue the string to replace the matches with 4445 * @return {IString} a new string with all the matches replaced 4446 * with the new value 4447 */ 4448 replace: function(searchValue, newValue) { 4449 return new IString(this.str.replace(searchValue, newValue)); 4450 }, 4451 4452 /** 4453 * Same as String.search() 4454 * @param {string} regexp the regular expression to search for 4455 * @return {number} position of the match, or -1 for no match 4456 */ 4457 search: function(regexp) { 4458 return this.str.search(regexp); 4459 }, 4460 4461 /** 4462 * Same as String.slice() 4463 * @param {number} start first character to include in the string 4464 * @param {number} end include all characters up to, but not including 4465 * the end character 4466 * @return {IString} a slice of the current string 4467 */ 4468 slice: function(start, end) { 4469 return new IString(this.str.slice(start, end)); 4470 }, 4471 4472 /** 4473 * Same as String.split() 4474 * @param {string} separator regular expression to match to find 4475 * separations between the parts of the text 4476 * @param {number} limit maximum number of items in the final 4477 * output array. Any items beyond that limit will be ignored. 4478 * @return {Array.<string>} the parts of the current string split 4479 * by the separator 4480 */ 4481 split: function(separator, limit) { 4482 return this.str.split(separator, limit); 4483 }, 4484 4485 /** 4486 * Same as String.substr() 4487 * @param {number} start the index of the character that should 4488 * begin the returned substring 4489 * @param {number} length the number of characters to return after 4490 * the start character. 4491 * @return {IString} the requested substring 4492 */ 4493 substr: function(start, length) { 4494 var plat = ilib._getPlatform(); 4495 if (plat === "qt" || plat === "rhino" || plat === "trireme") { 4496 // qt and rhino have a broken implementation of substr(), so 4497 // work around it 4498 if (typeof(length) === "undefined") { 4499 length = this.str.length - start; 4500 } 4501 } 4502 return new IString(this.str.substr(start, length)); 4503 }, 4504 4505 /** 4506 * Same as String.substring() 4507 * @param {number} from the index of the character that should 4508 * begin the returned substring 4509 * @param {number} to the index where to stop the extraction. If 4510 * omitted, extracts the rest of the string 4511 * @return {IString} the requested substring 4512 */ 4513 substring: function(from, to) { 4514 return this.str.substring(from, to); 4515 }, 4516 4517 /** 4518 * Same as String.toLowerCase(). Note that this method is 4519 * not locale-sensitive. 4520 * @return {IString} a string with the first character 4521 * lower-cased 4522 */ 4523 toLowerCase: function() { 4524 return this.str.toLowerCase(); 4525 }, 4526 4527 /** 4528 * Same as String.toUpperCase(). Note that this method is 4529 * not locale-sensitive. Use toLocaleUpperCase() instead 4530 * to get locale-sensitive behaviour. 4531 * @return {IString} a string with the first character 4532 * upper-cased 4533 */ 4534 toUpperCase: function() { 4535 return this.str.toUpperCase(); 4536 }, 4537 4538 /** 4539 * Convert the character or the surrogate pair at the given 4540 * index into the string to a Unicode UCS-4 code point. 4541 * @protected 4542 * @param {number} index index into the string 4543 * @return {number} code point of the character at the 4544 * given index into the string 4545 */ 4546 _toCodePoint: function (index) { 4547 return IString.toCodePoint(this.str, index); 4548 }, 4549 4550 /** 4551 * Call the callback with each character in the string one at 4552 * a time, taking care to step through the surrogate pairs in 4553 * the UTF-16 encoding properly.<p> 4554 * 4555 * The standard Javascript String's charAt() method only 4556 * returns a particular 16-bit character in the 4557 * UTF-16 encoding scheme. 4558 * If the index to charAt() is pointing to a low- or 4559 * high-surrogate character, 4560 * it will return the surrogate character rather 4561 * than the the character 4562 * in the supplementary planes that the two surrogates together 4563 * encode. This function will call the callback with the full 4564 * character, making sure to join two 4565 * surrogates into one character in the supplementary planes 4566 * where necessary.<p> 4567 * 4568 * @param {function(string)} callback a callback function to call with each 4569 * full character in the current string 4570 */ 4571 forEach: function(callback) { 4572 if (typeof(callback) === 'function') { 4573 var it = this.charIterator(); 4574 while (it.hasNext()) { 4575 callback(it.next()); 4576 } 4577 } 4578 }, 4579 4580 /** 4581 * Call the callback with each numeric code point in the string one at 4582 * a time, taking care to step through the surrogate pairs in 4583 * the UTF-16 encoding properly.<p> 4584 * 4585 * The standard Javascript String's charCodeAt() method only 4586 * returns information about a particular 16-bit character in the 4587 * UTF-16 encoding scheme. 4588 * If the index to charCodeAt() is pointing to a low- or 4589 * high-surrogate character, 4590 * it will return the code point of the surrogate character rather 4591 * than the code point of the character 4592 * in the supplementary planes that the two surrogates together 4593 * encode. This function will call the callback with the full 4594 * code point of each character, making sure to join two 4595 * surrogates into one code point in the supplementary planes.<p> 4596 * 4597 * @param {function(string)} callback a callback function to call with each 4598 * code point in the current string 4599 */ 4600 forEachCodePoint: function(callback) { 4601 if (typeof(callback) === 'function') { 4602 var it = this.iterator(); 4603 while (it.hasNext()) { 4604 callback(it.next()); 4605 } 4606 } 4607 }, 4608 4609 /** 4610 * Return an iterator that will step through all of the characters 4611 * in the string one at a time and return their code points, taking 4612 * care to step through the surrogate pairs in UTF-16 encoding 4613 * properly.<p> 4614 * 4615 * The standard Javascript String's charCodeAt() method only 4616 * returns information about a particular 16-bit character in the 4617 * UTF-16 encoding scheme. 4618 * If the index is pointing to a low- or high-surrogate character, 4619 * it will return a code point of the surrogate character rather 4620 * than the code point of the character 4621 * in the supplementary planes that the two surrogates together 4622 * encode.<p> 4623 * 4624 * The iterator instance returned has two methods, hasNext() which 4625 * returns true if the iterator has more code points to iterate through, 4626 * and next() which returns the next code point as a number.<p> 4627 * 4628 * @return {Object} an iterator 4629 * that iterates through all the code points in the string 4630 */ 4631 iterator: function() { 4632 /** 4633 * @constructor 4634 */ 4635 function _iterator (istring) { 4636 this.index = 0; 4637 this.hasNext = function () { 4638 return (this.index < istring.str.length); 4639 }; 4640 this.next = function () { 4641 if (this.index < istring.str.length) { 4642 var num = istring._toCodePoint(this.index); 4643 this.index += ((num > 0xFFFF) ? 2 : 1); 4644 } else { 4645 num = -1; 4646 } 4647 return num; 4648 }; 4649 }; 4650 return new _iterator(this); 4651 }, 4652 4653 /** 4654 * Return an iterator that will step through all of the characters 4655 * in the string one at a time, taking 4656 * care to step through the surrogate pairs in UTF-16 encoding 4657 * properly.<p> 4658 * 4659 * The standard Javascript String's charAt() method only 4660 * returns information about a particular 16-bit character in the 4661 * UTF-16 encoding scheme. 4662 * If the index is pointing to a low- or high-surrogate character, 4663 * it will return that surrogate character rather 4664 * than the surrogate pair which represents a character 4665 * in the supplementary planes.<p> 4666 * 4667 * The iterator instance returned has two methods, hasNext() which 4668 * returns true if the iterator has more characters to iterate through, 4669 * and next() which returns the next character.<p> 4670 * 4671 * @return {Object} an iterator 4672 * that iterates through all the characters in the string 4673 */ 4674 charIterator: function() { 4675 /** 4676 * @constructor 4677 */ 4678 function _chiterator (istring) { 4679 this.index = 0; 4680 this.hasNext = function () { 4681 return (this.index < istring.str.length); 4682 }; 4683 this.next = function () { 4684 var ch; 4685 if (this.index < istring.str.length) { 4686 ch = istring.str.charAt(this.index); 4687 if (IString._isSurrogate(ch) && 4688 this.index+1 < istring.str.length && 4689 IString._isSurrogate(istring.str.charAt(this.index+1))) { 4690 this.index++; 4691 ch += istring.str.charAt(this.index); 4692 } 4693 this.index++; 4694 } 4695 return ch; 4696 }; 4697 }; 4698 return new _chiterator(this); 4699 }, 4700 4701 /** 4702 * Return the code point at the given index when the string is viewed 4703 * as an array of code points. If the index is beyond the end of the 4704 * array of code points or if the index is negative, -1 is returned. 4705 * @param {number} index index of the code point 4706 * @return {number} code point of the character at the given index into 4707 * the string 4708 */ 4709 codePointAt: function (index) { 4710 if (index < 0) { 4711 return -1; 4712 } 4713 var count, 4714 it = this.iterator(), 4715 ch; 4716 for (count = index; count >= 0 && it.hasNext(); count--) { 4717 ch = it.next(); 4718 } 4719 return (count < 0) ? ch : -1; 4720 }, 4721 4722 /** 4723 * Set the locale to use when processing choice formats. The locale 4724 * affects how number classes are interpretted. In some cultures, 4725 * the limit "few" maps to "any integer that ends in the digits 2 to 9" and 4726 * in yet others, "few" maps to "any integer that ends in the digits 4727 * 3 or 4". 4728 * @param {Locale|string} locale locale to use when processing choice 4729 * formats with this string 4730 * @param {boolean=} sync [optional] whether to load the locale data synchronously 4731 * or not 4732 * @param {Object=} loadParams [optional] parameters to pass to the loader function 4733 * @param {function(*)=} onLoad [optional] function to call when the loading is done 4734 */ 4735 setLocale: function (locale, sync, loadParams, onLoad) { 4736 if (typeof(locale) === 'object') { 4737 this.locale = locale; 4738 } else { 4739 this.localeSpec = locale; 4740 this.locale = new Locale(locale); 4741 } 4742 4743 IString.loadPlurals(typeof(sync) !== 'undefined' ? sync : true, this.locale, loadParams, onLoad); 4744 }, 4745 4746 /** 4747 * Return the locale to use when processing choice formats. The locale 4748 * affects how number classes are interpretted. In some cultures, 4749 * the limit "few" maps to "any integer that ends in the digits 2 to 9" and 4750 * in yet others, "few" maps to "any integer that ends in the digits 4751 * 3 or 4". 4752 * @return {string} localespec to use when processing choice 4753 * formats with this string 4754 */ 4755 getLocale: function () { 4756 return (this.locale ? this.locale.getSpec() : this.localeSpec) || ilib.getLocale(); 4757 }, 4758 4759 /** 4760 * Return the number of code points in this string. This may be different 4761 * than the number of characters, as the UTF-16 encoding that Javascript 4762 * uses for its basis returns surrogate pairs separately. Two 2-byte 4763 * surrogate characters together make up one character/code point in 4764 * the supplementary character planes. If your string contains no 4765 * characters in the supplementary planes, this method will return the 4766 * same thing as the length() method. 4767 * @return {number} the number of code points in this string 4768 */ 4769 codePointLength: function () { 4770 if (this.cpLength === -1) { 4771 var it = this.iterator(); 4772 this.cpLength = 0; 4773 while (it.hasNext()) { 4774 this.cpLength++; 4775 it.next(); 4776 }; 4777 } 4778 return this.cpLength; 4779 } 4780 }; 4781 4782 4783 /*< Calendar.js */ 4784 /* 4785 * Calendar.js - Represent a calendar object. 4786 * 4787 * Copyright © 2012-2015, JEDLSoft 4788 * 4789 * Licensed under the Apache License, Version 2.0 (the "License"); 4790 * you may not use this file except in compliance with the License. 4791 * You may obtain a copy of the License at 4792 * 4793 * http://www.apache.org/licenses/LICENSE-2.0 4794 * 4795 * Unless required by applicable law or agreed to in writing, software 4796 * distributed under the License is distributed on an "AS IS" BASIS, 4797 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4798 * 4799 * See the License for the specific language governing permissions and 4800 * limitations under the License. 4801 */ 4802 4803 /** 4804 * @class 4805 * Superclass for all calendar subclasses that contains shared 4806 * functionality. This class is never instantiated on its own. Instead, 4807 * you should use the {@link CalendarFactory} function to manufacture a new 4808 * instance of a subclass of Calendar. 4809 * 4810 * @private 4811 * @constructor 4812 */ 4813 var Calendar = function() { 4814 }; 4815 4816 /* place for the subclasses to put their constructors so that the factory method 4817 * can find them. Do this to add your calendar after it's defined: 4818 * Calendar._constructors["mytype"] = Calendar.MyTypeConstructor; 4819 */ 4820 Calendar._constructors = {}; 4821 4822 Calendar.prototype = { 4823 /** 4824 * Return the type of this calendar. 4825 * 4826 * @return {string} the name of the type of this calendar 4827 */ 4828 getType: function() { 4829 throw "Cannot call methods of abstract class Calendar"; 4830 }, 4831 4832 /** 4833 * Return the number of months in the given year. The number of months in a year varies 4834 * for some luni-solar calendars because in some years, an extra month is needed to extend the 4835 * days in a year to an entire solar year. The month is represented as a 1-based number 4836 * where 1=first month, 2=second month, etc. 4837 * 4838 * @param {number} year a year for which the number of months is sought 4839 * @return {number} The number of months in the given year 4840 */ 4841 getNumMonths: function(year) { 4842 throw "Cannot call methods of abstract class Calendar"; 4843 }, 4844 4845 /** 4846 * Return the number of days in a particular month in a particular year. This function 4847 * can return a different number for a month depending on the year because of things 4848 * like leap years. 4849 * 4850 * @param {number} month the month for which the length is sought 4851 * @param {number} year the year within which that month can be found 4852 * @return {number} the number of days within the given month in the given year 4853 */ 4854 getMonLength: function(month, year) { 4855 throw "Cannot call methods of abstract class Calendar"; 4856 }, 4857 4858 /** 4859 * Return true if the given year is a leap year in this calendar. 4860 * The year parameter may be given as a number. 4861 * 4862 * @param {number} year the year for which the leap year information is being sought 4863 * @return {boolean} true if the given year is a leap year 4864 */ 4865 isLeapYear: function(year) { 4866 throw "Cannot call methods of abstract class Calendar"; 4867 } 4868 }; 4869 4870 4871 /*< CalendarFactory.js */ 4872 /* 4873 * CalendarFactory.js - Constructs new instances of the right subclass of Calendar 4874 * 4875 * Copyright © 2015, JEDLSoft 4876 * 4877 * Licensed under the Apache License, Version 2.0 (the "License"); 4878 * you may not use this file except in compliance with the License. 4879 * You may obtain a copy of the License at 4880 * 4881 * http://www.apache.org/licenses/LICENSE-2.0 4882 * 4883 * Unless required by applicable law or agreed to in writing, software 4884 * distributed under the License is distributed on an "AS IS" BASIS, 4885 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4886 * 4887 * See the License for the specific language governing permissions and 4888 * limitations under the License. 4889 */ 4890 4891 /* !depends 4892 ilib.js 4893 Locale.js 4894 LocaleInfo.js 4895 Calendar.js 4896 */ 4897 4898 4899 /** 4900 * Factory method to create a new instance of a calendar subclass.<p> 4901 * 4902 * The options parameter can be an object that contains the following 4903 * properties: 4904 * 4905 * <ul> 4906 * <li><i>type</i> - specify the type of the calendar desired. The 4907 * list of valid values changes depending on which calendars are 4908 * defined. When assembling your iliball.js, include those calendars 4909 * you wish to use in your program or web page, and they will register 4910 * themselves with this factory method. The "official", "gregorian", 4911 * and "julian" calendars are all included by default, as they are the 4912 * standard calendars for much of the world. 4913 * <li><i>locale</i> - some calendars vary depending on the locale. 4914 * For example, the "official" calendar transitions from a Julian-style 4915 * calendar to a Gregorian-style calendar on a different date for 4916 * each country, as the governments of those countries decided to 4917 * adopt the Gregorian calendar at different times. 4918 * 4919 * <li><i>onLoad</i> - a callback function to call when the calendar object is fully 4920 * loaded. When the onLoad option is given, the calendar factory will attempt to 4921 * load any missing locale data using the ilib loader callback. 4922 * When the constructor is done (even if the data is already preassembled), the 4923 * onLoad function is called with the current instance as a parameter, so this 4924 * callback can be used with preassembled or dynamic loading or a mix of the two. 4925 * 4926 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 4927 * asynchronously. If this option is given as "false", then the "onLoad" 4928 * callback must be given, as the instance returned from this constructor will 4929 * not be usable for a while. 4930 * 4931 * <li><i>loadParams</i> - an object containing parameters to pass to the 4932 * loader callback function when locale data is missing. The parameters are not 4933 * interpretted or modified in any way. They are simply passed along. The object 4934 * may contain any property/value pairs as long as the calling code is in 4935 * agreement with the loader callback function as to what those parameters mean. 4936 * </ul> 4937 * 4938 * If a locale is specified, but no type, then the calendar that is default for 4939 * the locale will be instantiated and returned. If neither the type nor 4940 * the locale are specified, then the calendar for the default locale will 4941 * be used. 4942 * 4943 * @static 4944 * @param {Object=} options options controlling the construction of this instance, or 4945 * undefined to use the default options 4946 * @return {Calendar} an instance of a calendar object of the appropriate type 4947 */ 4948 var CalendarFactory = function (options) { 4949 var locale, 4950 type, 4951 sync = true, 4952 instance; 4953 4954 if (options) { 4955 if (options.locale) { 4956 locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 4957 } 4958 4959 type = options.type || options.calendar; 4960 4961 if (typeof(options.sync) === 'boolean') { 4962 sync = options.sync; 4963 } 4964 } 4965 4966 if (!locale) { 4967 locale = new Locale(); // default locale 4968 } 4969 4970 if (!type) { 4971 new LocaleInfo(locale, { 4972 sync: sync, 4973 loadParams: options && options.loadParams, 4974 onLoad: ilib.bind(this, function(info) { 4975 type = info.getCalendar(); 4976 4977 instance = CalendarFactory._init(type, options); 4978 4979 if (options && typeof(options.onLoad) === 'function') { 4980 options.onLoad(instance); 4981 } 4982 }) 4983 }); 4984 } else { 4985 instance = CalendarFactory._init(type, options); 4986 } 4987 4988 return instance; 4989 }; 4990 4991 /** 4992 * Map calendar names to classes to initialize in the dynamic code model. 4993 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 4994 * @private 4995 */ 4996 CalendarFactory._dynMap = { 4997 "coptic": "Coptic", 4998 "ethiopic": "Ethiopic", 4999 "gregorian": "Gregorian", 5000 "han": "Han", 5001 "hebrew": "Hebrew", 5002 "islamic": "Islamic", 5003 "julian": "Julian", 5004 "persian": "Persian", 5005 "persian-algo": "PersianAlgo", 5006 "thaisolar": "ThaiSolar" 5007 }; 5008 5009 /** 5010 * Dynamically load the code for a calendar and calendar class if necessary. 5011 * @protected 5012 */ 5013 CalendarFactory._dynLoadCalendar = function (name) { 5014 if (!Calendar._constructors[name]) { 5015 var entry = CalendarFactory._dynMap[name]; 5016 if (entry) { 5017 Calendar._constructors[name] = require("./" + entry + "Cal.js"); 5018 } 5019 } 5020 return Calendar._constructors[name]; 5021 }; 5022 5023 /** @private */ 5024 CalendarFactory._init = function(type, options) { 5025 var cons; 5026 5027 if (ilib.isDynCode()) { 5028 CalendarFactory._dynLoadCalendar(type); 5029 } 5030 5031 cons = Calendar._constructors[type]; 5032 5033 // pass the same options through to the constructor so the subclass 5034 // has the ability to do something with if it needs to 5035 return cons && new cons(options); 5036 }; 5037 5038 /** 5039 * Return an array of known calendar types that the factory method can instantiate. 5040 * 5041 * @return {Array.<string>} an array of calendar types 5042 */ 5043 CalendarFactory.getCalendars = function () { 5044 var arr = [], 5045 c; 5046 5047 if (ilib.isDynCode()) { 5048 for (c in CalendarFactory._dynMap) { 5049 CalendarFactory._dynLoadCalendar(c); 5050 } 5051 } 5052 5053 for (c in Calendar._constructors) { 5054 if (c && Calendar._constructors[c]) { 5055 arr.push(c); // code like a pirate 5056 } 5057 } 5058 5059 return arr; 5060 }; 5061 5062 5063 /*< GregorianCal.js */ 5064 /* 5065 * gregorian.js - Represent a Gregorian calendar object. 5066 * 5067 * Copyright © 2012-2015, JEDLSoft 5068 * 5069 * Licensed under the Apache License, Version 2.0 (the "License"); 5070 * you may not use this file except in compliance with the License. 5071 * You may obtain a copy of the License at 5072 * 5073 * http://www.apache.org/licenses/LICENSE-2.0 5074 * 5075 * Unless required by applicable law or agreed to in writing, software 5076 * distributed under the License is distributed on an "AS IS" BASIS, 5077 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5078 * 5079 * See the License for the specific language governing permissions and 5080 * limitations under the License. 5081 */ 5082 5083 5084 /* !depends ilib.js Calendar.js Utils.js MathUtils.js */ 5085 5086 5087 /** 5088 * @class 5089 * Construct a new Gregorian calendar object. This class encodes information about 5090 * a Gregorian calendar.<p> 5091 * 5092 * 5093 * @constructor 5094 * @param {{noinstance:boolean}=} options 5095 * @extends Calendar 5096 */ 5097 var GregorianCal = function(options) { 5098 if (!options || !options.noinstance) { 5099 this.type = "gregorian"; 5100 } 5101 }; 5102 5103 /** 5104 * the lengths of each month 5105 * @private 5106 * @const 5107 * @type Array.<number> 5108 */ 5109 GregorianCal.monthLengths = [ 5110 31, /* Jan */ 5111 28, /* Feb */ 5112 31, /* Mar */ 5113 30, /* Apr */ 5114 31, /* May */ 5115 30, /* Jun */ 5116 31, /* Jul */ 5117 31, /* Aug */ 5118 30, /* Sep */ 5119 31, /* Oct */ 5120 30, /* Nov */ 5121 31 /* Dec */ 5122 ]; 5123 5124 /** 5125 * Return the number of months in the given year. The number of months in a year varies 5126 * for some luni-solar calendars because in some years, an extra month is needed to extend the 5127 * days in a year to an entire solar year. The month is represented as a 1-based number 5128 * where 1=first month, 2=second month, etc. 5129 * 5130 * @param {number} year a year for which the number of months is sought 5131 * @return {number} The number of months in the given year 5132 */ 5133 GregorianCal.prototype.getNumMonths = function(year) { 5134 return 12; 5135 }; 5136 5137 /** 5138 * Return the number of days in a particular month in a particular year. This function 5139 * can return a different number for a month depending on the year because of things 5140 * like leap years. 5141 * 5142 * @param {number} month the month for which the length is sought 5143 * @param {number} year the year within which that month can be found 5144 * @return {number} the number of days within the given month in the given year 5145 */ 5146 GregorianCal.prototype.getMonLength = function(month, year) { 5147 if (month !== 2 || !this.isLeapYear(year)) { 5148 return GregorianCal.monthLengths[month-1]; 5149 } else { 5150 return 29; 5151 } 5152 }; 5153 5154 /** 5155 * Return true if the given year is a leap year in the Gregorian calendar. 5156 * The year parameter may be given as a number, or as a GregDate object. 5157 * @param {number|GregorianDate} year the year for which the leap year information is being sought 5158 * @return {boolean} true if the given year is a leap year 5159 */ 5160 GregorianCal.prototype.isLeapYear = function(year) { 5161 var y = (typeof(year) === 'number' ? year : year.getYears()); 5162 var centuries = MathUtils.mod(y, 400); 5163 return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); 5164 }; 5165 5166 /** 5167 * Return the type of this calendar. 5168 * 5169 * @return {string} the name of the type of this calendar 5170 */ 5171 GregorianCal.prototype.getType = function() { 5172 return this.type; 5173 }; 5174 5175 /** 5176 * Return a date instance for this calendar type using the given 5177 * options. 5178 * @param {Object} options options controlling the construction of 5179 * the date instance 5180 * @return {IDate} a date appropriate for this calendar type 5181 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 5182 */ 5183 GregorianCal.prototype.newDateInstance = function (options) { 5184 return new GregorianDate(options); 5185 }; 5186 5187 /* register this calendar for the factory method */ 5188 Calendar._constructors["gregorian"] = GregorianCal; 5189 5190 5191 /*< JulianDay.js */ 5192 /* 5193 * JulianDay.js - A Julian Day object. 5194 * 5195 * Copyright © 2012-2015, JEDLSoft 5196 * 5197 * Licensed under the Apache License, Version 2.0 (the "License"); 5198 * you may not use this file except in compliance with the License. 5199 * You may obtain a copy of the License at 5200 * 5201 * http://www.apache.org/licenses/LICENSE-2.0 5202 * 5203 * Unless required by applicable law or agreed to in writing, software 5204 * distributed under the License is distributed on an "AS IS" BASIS, 5205 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5206 * 5207 * See the License for the specific language governing permissions and 5208 * limitations under the License. 5209 */ 5210 5211 /** 5212 * @class 5213 * A Julian Day class. A Julian Day is a date based on the Julian Day count 5214 * of time invented by Joseph Scaliger in 1583 for use with astronomical calculations. 5215 * Do not confuse it with a date in the Julian calendar, which it has very 5216 * little in common with. The naming is unfortunately close, and comes from history.<p> 5217 * 5218 * 5219 * @constructor 5220 * @param {number} num the Julian Day expressed as a floating point number 5221 */ 5222 var JulianDay = function(num) { 5223 this.jd = num; 5224 this.days = Math.floor(this.jd); 5225 this.frac = num - this.days; 5226 }; 5227 5228 JulianDay.prototype = { 5229 /** 5230 * Return the integral portion of this Julian Day instance. This corresponds to 5231 * the number of days since the beginning of the epoch. 5232 * 5233 * @return {number} the integral portion of this Julian Day 5234 */ 5235 getDays: function() { 5236 return this.days; 5237 }, 5238 5239 /** 5240 * Set the date of this Julian Day instance. 5241 * 5242 * @param {number} days the julian date expressed as a floating point number 5243 */ 5244 setDays: function(days) { 5245 this.days = Math.floor(days); 5246 this.jd = this.days + this.frac; 5247 }, 5248 5249 /** 5250 * Return the fractional portion of this Julian Day instance. This portion 5251 * corresponds to the time of day for the instance. 5252 */ 5253 getDayFraction: function() { 5254 return this.frac; 5255 }, 5256 5257 /** 5258 * Set the fractional part of the Julian Day. The fractional part represents 5259 * the portion of a fully day. Julian dates start at noon, and proceed until 5260 * noon of the next day. That would mean midnight is represented as a fractional 5261 * part of 0.5. 5262 * 5263 * @param {number} fraction The fractional part of the Julian date 5264 */ 5265 setDayFraction: function(fraction) { 5266 var t = Math.floor(fraction); 5267 this.frac = fraction - t; 5268 this.jd = this.days + this.frac; 5269 }, 5270 5271 /** 5272 * Return the Julian Day expressed as a floating point number. 5273 * @return {number} the Julian Day as a number 5274 */ 5275 getDate: function () { 5276 return this.jd; 5277 }, 5278 5279 /** 5280 * Set the date of this Julian Day instance. 5281 * 5282 * @param {number} num the numeric Julian Day to set into this instance 5283 */ 5284 setDate: function (num) { 5285 this.jd = num; 5286 }, 5287 5288 /** 5289 * Add an offset to the current date instance. The offset should be expressed in 5290 * terms of Julian days. That is, each integral unit represents one day of time, and 5291 * fractional part represents a fraction of a regular 24-hour day. 5292 * 5293 * @param {number} offset an amount to add (or subtract) to the current result instance. 5294 */ 5295 addDate: function(offset) { 5296 if (typeof(offset) === 'number') { 5297 this.jd += offset; 5298 this.days = Math.floor(this.jd); 5299 this.frac = this.jd - this.days; 5300 } 5301 } 5302 }; 5303 5304 5305 5306 /*< RataDie.js */ 5307 /* 5308 * ratadie.js - Represent the RD date number in the calendar 5309 * 5310 * Copyright © 2014-2015, JEDLSoft 5311 * 5312 * Licensed under the Apache License, Version 2.0 (the "License"); 5313 * you may not use this file except in compliance with the License. 5314 * You may obtain a copy of the License at 5315 * 5316 * http://www.apache.org/licenses/LICENSE-2.0 5317 * 5318 * Unless required by applicable law or agreed to in writing, software 5319 * distributed under the License is distributed on an "AS IS" BASIS, 5320 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5321 * 5322 * See the License for the specific language governing permissions and 5323 * limitations under the License. 5324 */ 5325 5326 /* !depends 5327 ilib.js 5328 JulianDay.js 5329 MathUtils.js 5330 JSUtils.js 5331 */ 5332 5333 5334 /** 5335 * @class 5336 * Construct a new RD date number object. The constructor parameters can 5337 * contain any of the following properties: 5338 * 5339 * <ul> 5340 * <li><i>unixtime<i> - sets the time of this instance according to the given 5341 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 5342 * 5343 * <li><i>julianday</i> - sets the time of this instance according to the given 5344 * Julian Day instance or the Julian Day given as a float 5345 * 5346 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 5347 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 5348 * linear count of years since the beginning of the epoch, much like other calendars. This linear 5349 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 5350 * to 60 and treated as if it were a year in the regular 60-year cycle. 5351 * 5352 * <li><i>year</i> - any integer, including 0 5353 * 5354 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 5355 * 5356 * <li><i>day</i> - 1 to 31 5357 * 5358 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 5359 * is always done with an unambiguous 24 hour representation 5360 * 5361 * <li><i>minute</i> - 0 to 59 5362 * 5363 * <li><i>second</i> - 0 to 59 5364 * 5365 * <li><i>millisecond</i> - 0 to 999 5366 * 5367 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 5368 * the parts or specify the minutes, seconds, and milliseconds, but not both. This is only used 5369 * in the Hebrew calendar. 5370 * 5371 * <li><i>minute</i> - 0 to 59 5372 * 5373 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 5374 * </ul> 5375 * 5376 * If the constructor is called with another date instance instead of 5377 * a parameter block, the other instance acts as a parameter block and its 5378 * settings are copied into the current instance.<p> 5379 * 5380 * If the constructor is called with no arguments at all or if none of the 5381 * properties listed above are present, then the RD is calculate based on 5382 * the current date at the time of instantiation. <p> 5383 * 5384 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 5385 * specified in the params, it is assumed that they have the smallest possible 5386 * value in the range for the property (zero or one).<p> 5387 * 5388 * 5389 * @private 5390 * @constructor 5391 * @param {Object=} params parameters that govern the settings and behaviour of this RD date 5392 */ 5393 var RataDie = function(params) { 5394 if (params) { 5395 if (typeof(params.date) !== 'undefined') { 5396 // accept JS Date classes or strings 5397 var date = params.date; 5398 if (!(JSUtils.isDate(date))) { 5399 date = new Date(date); // maybe a string initializer? 5400 } 5401 this._setTime(date.getTime()); 5402 } else if (typeof(params.unixtime) !== 'undefined') { 5403 this._setTime(parseInt(params.unixtime, 10)); 5404 } else if (typeof(params.julianday) !== 'undefined') { 5405 // JD time is defined to be UTC 5406 this._setJulianDay(parseFloat(params.julianday)); 5407 } else if (params.year || params.month || params.day || params.hour || 5408 params.minute || params.second || params.millisecond || params.parts || params.cycle) { 5409 this._setDateComponents(params); 5410 } else if (typeof(params.rd) !== 'undefined') { 5411 /** 5412 * @type {number} the Rata Die number of this date for this calendar type 5413 */ 5414 this.rd = (typeof(params.rd) === 'object' && params.rd instanceof RataDie) ? params.rd.rd : params.rd; 5415 } 5416 } 5417 5418 if (typeof(this.rd) === 'undefined' || isNaN(this.rd)) { 5419 var now = new Date(); 5420 this._setTime(now.getTime()); 5421 } 5422 }; 5423 5424 /** 5425 * @private 5426 * @const 5427 * @type {number} 5428 */ 5429 RataDie.gregorianEpoch = 1721424.5; 5430 5431 RataDie.prototype = { 5432 /** 5433 * @protected 5434 * @type {number} 5435 * the difference between a zero Julian day and the zero Gregorian date. 5436 */ 5437 epoch: RataDie.gregorianEpoch, 5438 5439 /** 5440 * Set the RD of this instance according to the given unix time. Unix time is 5441 * the number of milliseconds since midnight on Jan 1, 1970. 5442 * 5443 * @protected 5444 * @param {number} millis the unix time to set this date to in milliseconds 5445 */ 5446 _setTime: function(millis) { 5447 // 2440587.5 is the julian day of midnight Jan 1, 1970, UTC (Gregorian) 5448 this._setJulianDay(2440587.5 + millis / 86400000); 5449 }, 5450 5451 /** 5452 * Set the date of this instance using a Julian Day. 5453 * @protected 5454 * @param {number} date the Julian Day to use to set this date 5455 */ 5456 _setJulianDay: function (date) { 5457 var jd = (typeof(date) === 'number') ? new JulianDay(date) : date; 5458 // round to the nearest millisecond 5459 this.rd = MathUtils.halfup((jd.getDate() - this.epoch) * 86400000) / 86400000; 5460 }, 5461 5462 /** 5463 * Return the rd number of the particular day of the week on or before the 5464 * given rd. eg. The Sunday on or before the given rd. 5465 * @protected 5466 * @param {number} rd the rata die date of the reference date 5467 * @param {number} dayOfWeek the day of the week that is being sought relative 5468 * to the current date 5469 * @return {number} the rd of the day of the week 5470 */ 5471 _onOrBefore: function(rd, dayOfWeek) { 5472 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 2, 7); 5473 }, 5474 5475 /** 5476 * Return the rd number of the particular day of the week on or before the current rd. 5477 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 5478 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5479 * wall time, so it it would give the wrong day of the week if this calculation was 5480 * done in UTC time when the caller really wanted wall time. Even though the calculation 5481 * may be done in wall time, the return value is nonetheless always given in UTC. 5482 * @param {number} dayOfWeek the day of the week that is being sought relative 5483 * to the current date 5484 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5485 * not given 5486 * @return {number} the rd of the day of the week 5487 */ 5488 onOrBefore: function(dayOfWeek, offset) { 5489 offset = offset || 0; 5490 return this._onOrBefore(this.rd + offset, dayOfWeek) - offset; 5491 }, 5492 5493 /** 5494 * Return the rd number of the particular day of the week on or before the current rd. 5495 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 5496 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5497 * wall time, so it it would give the wrong day of the week if this calculation was 5498 * done in UTC time when the caller really wanted wall time. Even though the calculation 5499 * may be done in wall time, the return value is nonetheless always given in UTC. 5500 * @param {number} dayOfWeek the day of the week that is being sought relative 5501 * to the reference date 5502 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5503 * not given 5504 * @return {number} the day of the week 5505 */ 5506 onOrAfter: function(dayOfWeek, offset) { 5507 offset = offset || 0; 5508 return this._onOrBefore(this.rd+6+offset, dayOfWeek) - offset; 5509 }, 5510 5511 /** 5512 * Return the rd number of the particular day of the week before the current rd. 5513 * eg. The Sunday before the current rd. If the offset is given, the calculation 5514 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5515 * wall time, so it it would give the wrong day of the week if this calculation was 5516 * done in UTC time when the caller really wanted wall time. Even though the calculation 5517 * may be done in wall time, the return value is nonetheless always given in UTC. 5518 * @param {number} dayOfWeek the day of the week that is being sought relative 5519 * to the reference date 5520 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5521 * not given 5522 * @return {number} the day of the week 5523 */ 5524 before: function(dayOfWeek, offset) { 5525 offset = offset || 0; 5526 return this._onOrBefore(this.rd-1+offset, dayOfWeek) - offset; 5527 }, 5528 5529 /** 5530 * Return the rd number of the particular day of the week after the current rd. 5531 * eg. The Sunday after the current rd. If the offset is given, the calculation 5532 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5533 * wall time, so it it would give the wrong day of the week if this calculation was 5534 * done in UTC time when the caller really wanted wall time. Even though the calculation 5535 * may be done in wall time, the return value is nonetheless always given in UTC. 5536 * @param {number} dayOfWeek the day of the week that is being sought relative 5537 * to the reference date 5538 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5539 * not given 5540 * @return {number} the day of the week 5541 */ 5542 after: function(dayOfWeek, offset) { 5543 offset = offset || 0; 5544 return this._onOrBefore(this.rd+7+offset, dayOfWeek) - offset; 5545 }, 5546 5547 /** 5548 * Return the unix time equivalent to this Gregorian date instance. Unix time is 5549 * the number of milliseconds since midnight on Jan 1, 1970 UTC. This method only 5550 * returns a valid number for dates between midnight, Jan 1, 1970 and 5551 * Jan 19, 2038 at 3:14:07am when the unix time runs out. If this instance 5552 * encodes a date outside of that range, this method will return -1. 5553 * 5554 * @return {number} a number giving the unix time, or -1 if the date is outside the 5555 * valid unix time range 5556 */ 5557 getTime: function() { 5558 // earlier than Jan 1, 1970 5559 // or later than Jan 19, 2038 at 3:14:07am 5560 var jd = this.getJulianDay(); 5561 if (jd < 2440587.5 || jd > 2465442.634803241) { 5562 return -1; 5563 } 5564 5565 // avoid the rounding errors in the floating point math by only using 5566 // the whole days from the rd, and then calculating the milliseconds directly 5567 return Math.round((jd - 2440587.5) * 86400000); 5568 }, 5569 5570 /** 5571 * Return the extended unix time equivalent to this Gregorian date instance. Unix time is 5572 * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time 5573 * (or the type "time_t" in C/C++) is only encoded with a unsigned 32 bit integer, and thus 5574 * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above 5575 * 32 bits and the Date object allows you to encode up to 100 million days worth of time 5576 * after Jan 1, 1970, and even more interestingly 100 million days worth of time before 5577 * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended 5578 * range. If this instance encodes a date outside of that range, this method will return 5579 * NaN. 5580 * 5581 * @return {number} a number giving the extended unix time, or NaN if the date is outside 5582 * the valid extended unix time range 5583 */ 5584 getTimeExtended: function() { 5585 var jd = this.getJulianDay(); 5586 5587 // test if earlier than Jan 1, 1970 - 100 million days 5588 // or later than Jan 1, 1970 + 100 million days 5589 if (jd < -97559412.5 || jd > 102440587.5) { 5590 return NaN; 5591 } 5592 5593 // avoid the rounding errors in the floating point math by only using 5594 // the whole days from the rd, and then calculating the milliseconds directly 5595 return Math.round((jd - 2440587.5) * 86400000); 5596 }, 5597 5598 /** 5599 * Return the Julian Day equivalent to this calendar date as a number. 5600 * This returns the julian day in UTC. 5601 * 5602 * @return {number} the julian date equivalent of this date 5603 */ 5604 getJulianDay: function() { 5605 return this.rd + this.epoch; 5606 }, 5607 5608 /** 5609 * Return the Rata Die (fixed day) number of this RD date. 5610 * 5611 * @return {number} the rd date as a number 5612 */ 5613 getRataDie: function() { 5614 return this.rd; 5615 } 5616 }; 5617 5618 5619 /*< GregRataDie.js */ 5620 /* 5621 * gregratadie.js - Represent the RD date number in the Gregorian calendar 5622 * 5623 * Copyright © 2014-2015, JEDLSoft 5624 * 5625 * Licensed under the Apache License, Version 2.0 (the "License"); 5626 * you may not use this file except in compliance with the License. 5627 * You may obtain a copy of the License at 5628 * 5629 * http://www.apache.org/licenses/LICENSE-2.0 5630 * 5631 * Unless required by applicable law or agreed to in writing, software 5632 * distributed under the License is distributed on an "AS IS" BASIS, 5633 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5634 * 5635 * See the License for the specific language governing permissions and 5636 * limitations under the License. 5637 */ 5638 5639 /* !depends 5640 ilib.js 5641 GregorianCal.js 5642 RataDie.js 5643 MathUtils.js 5644 */ 5645 5646 5647 /** 5648 * @class 5649 * Construct a new Gregorian RD date number object. The constructor parameters can 5650 * contain any of the following properties: 5651 * 5652 * <ul> 5653 * <li><i>unixtime<i> - sets the time of this instance according to the given 5654 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 5655 * 5656 * <li><i>julianday</i> - sets the time of this instance according to the given 5657 * Julian Day instance or the Julian Day given as a float 5658 * 5659 * <li><i>year</i> - any integer, including 0 5660 * 5661 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 5662 * 5663 * <li><i>day</i> - 1 to 31 5664 * 5665 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 5666 * is always done with an unambiguous 24 hour representation 5667 * 5668 * <li><i>minute</i> - 0 to 59 5669 * 5670 * <li><i>second</i> - 0 to 59 5671 * 5672 * <li><i>millisecond</i> - 0 to 999 5673 * 5674 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 5675 * </ul> 5676 * 5677 * If the constructor is called with another Gregorian date instance instead of 5678 * a parameter block, the other instance acts as a parameter block and its 5679 * settings are copied into the current instance.<p> 5680 * 5681 * If the constructor is called with no arguments at all or if none of the 5682 * properties listed above are present, then the RD is calculate based on 5683 * the current date at the time of instantiation. <p> 5684 * 5685 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 5686 * specified in the params, it is assumed that they have the smallest possible 5687 * value in the range for the property (zero or one).<p> 5688 * 5689 * 5690 * @private 5691 * @constructor 5692 * @extends RataDie 5693 * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian RD date 5694 */ 5695 var GregRataDie = function(params) { 5696 this.cal = params && params.cal || new GregorianCal(); 5697 /** @type {number|undefined} */ 5698 this.rd = NaN; 5699 RataDie.call(this, params); 5700 }; 5701 5702 GregRataDie.prototype = new RataDie(); 5703 GregRataDie.prototype.parent = RataDie; 5704 GregRataDie.prototype.constructor = GregRataDie; 5705 5706 /** 5707 * the cumulative lengths of each month, for a non-leap year 5708 * @private 5709 * @const 5710 * @type Array.<number> 5711 */ 5712 GregRataDie.cumMonthLengths = [ 5713 0, /* Jan */ 5714 31, /* Feb */ 5715 59, /* Mar */ 5716 90, /* Apr */ 5717 120, /* May */ 5718 151, /* Jun */ 5719 181, /* Jul */ 5720 212, /* Aug */ 5721 243, /* Sep */ 5722 273, /* Oct */ 5723 304, /* Nov */ 5724 334, /* Dec */ 5725 365 5726 ]; 5727 5728 /** 5729 * the cumulative lengths of each month, for a leap year 5730 * @private 5731 * @const 5732 * @type Array.<number> 5733 */ 5734 GregRataDie.cumMonthLengthsLeap = [ 5735 0, /* Jan */ 5736 31, /* Feb */ 5737 60, /* Mar */ 5738 91, /* Apr */ 5739 121, /* May */ 5740 152, /* Jun */ 5741 182, /* Jul */ 5742 213, /* Aug */ 5743 244, /* Sep */ 5744 274, /* Oct */ 5745 305, /* Nov */ 5746 335, /* Dec */ 5747 366 5748 ]; 5749 5750 /** 5751 * Calculate the Rata Die (fixed day) number of the given date. 5752 * 5753 * @private 5754 * @param {Object} date the date components to calculate the RD from 5755 */ 5756 GregRataDie.prototype._setDateComponents = function(date) { 5757 var year = parseInt(date.year, 10) || 0; 5758 var month = parseInt(date.month, 10) || 1; 5759 var day = parseInt(date.day, 10) || 1; 5760 var hour = parseInt(date.hour, 10) || 0; 5761 var minute = parseInt(date.minute, 10) || 0; 5762 var second = parseInt(date.second, 10) || 0; 5763 var millisecond = parseInt(date.millisecond, 10) || 0; 5764 5765 var years = 365 * (year - 1) + 5766 Math.floor((year-1)/4) - 5767 Math.floor((year-1)/100) + 5768 Math.floor((year-1)/400); 5769 5770 var dayInYear = (month > 1 ? GregRataDie.cumMonthLengths[month-1] : 0) + 5771 day + 5772 (GregorianCal.prototype.isLeapYear.call(this.cal, year) && month > 2 ? 1 : 0); 5773 var rdtime = (hour * 3600000 + 5774 minute * 60000 + 5775 second * 1000 + 5776 millisecond) / 5777 86400000; 5778 /* 5779 debug("getRataDie: converting " + JSON.stringify(this)); 5780 debug("getRataDie: year is " + years); 5781 debug("getRataDie: day in year is " + dayInYear); 5782 debug("getRataDie: rdtime is " + rdtime); 5783 debug("getRataDie: rd is " + (years + dayInYear + rdtime)); 5784 */ 5785 5786 /** 5787 * @type {number|undefined} the RD number of this Gregorian date 5788 */ 5789 this.rd = years + dayInYear + rdtime; 5790 }; 5791 5792 /** 5793 * Return the rd number of the particular day of the week on or before the 5794 * given rd. eg. The Sunday on or before the given rd. 5795 * @private 5796 * @param {number} rd the rata die date of the reference date 5797 * @param {number} dayOfWeek the day of the week that is being sought relative 5798 * to the current date 5799 * @return {number} the rd of the day of the week 5800 */ 5801 GregRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 5802 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); 5803 }; 5804 5805 5806 /*< TimeZone.js */ 5807 /* 5808 * TimeZone.js - Definition of a time zone class 5809 * 5810 * Copyright © 2012-2015, JEDLSoft 5811 * 5812 * Licensed under the Apache License, Version 2.0 (the "License"); 5813 * you may not use this file except in compliance with the License. 5814 * You may obtain a copy of the License at 5815 * 5816 * http://www.apache.org/licenses/LICENSE-2.0 5817 * 5818 * Unless required by applicable law or agreed to in writing, software 5819 * distributed under the License is distributed on an "AS IS" BASIS, 5820 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5821 * 5822 * See the License for the specific language governing permissions and 5823 * limitations under the License. 5824 */ 5825 5826 /* 5827 !depends 5828 ilib.js 5829 Locale.js 5830 LocaleInfo.js 5831 Utils.js 5832 MathUtils.js 5833 JSUtils.js 5834 GregRataDie.js 5835 IString.js 5836 CalendarFactory.js 5837 */ 5838 5839 // !data localeinfo zoneinfo 5840 5841 5842 5843 5844 /** 5845 * @class 5846 * Create a time zone instance. 5847 * 5848 * This class reports and transforms 5849 * information about particular time zones.<p> 5850 * 5851 * The options parameter may contain any of the following properties: 5852 * 5853 * <ul> 5854 * <li><i>id</i> - The id of the requested time zone such as "Europe/London" or 5855 * "America/Los_Angeles". These are taken from the IANA time zone database. (See 5856 * http://www.iana.org/time-zones for more information.) <p> 5857 * 5858 * There is one special 5859 * time zone that is not taken from the IANA database called simply "local". In 5860 * this case, this class will attempt to discover the current time zone and 5861 * daylight savings time settings by calling standard Javascript classes to 5862 * determine the offsets from UTC. 5863 * 5864 * <li><i>locale</i> - The locale for this time zone. 5865 * 5866 * <li><i>offset</i> - Choose the time zone based on the offset from UTC given in 5867 * number of minutes (negative is west, positive is east). 5868 * 5869 * <li><i>onLoad</i> - a callback function to call when the data is fully 5870 * loaded. When the onLoad option is given, this class will attempt to 5871 * load any missing locale data using the ilib loader callback. 5872 * When the data is loaded, the onLoad function is called with the current 5873 * instance as a parameter. 5874 * 5875 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 5876 * asynchronously. If this option is given as "false", then the "onLoad" 5877 * callback must be given, as the instance returned from this constructor will 5878 * not be usable for a while. 5879 * 5880 * <li><i>loadParams</i> - an object containing parameters to pass to the 5881 * loader callback function when locale data is missing. The parameters are not 5882 * interpretted or modified in any way. They are simply passed along. The object 5883 * may contain any property/value pairs as long as the calling code is in 5884 * agreement with the loader callback function as to what those parameters mean. 5885 * </ul> 5886 * 5887 * There is currently no way in the ECMAscript 5888 * standard to tell which exact time zone is currently in use. Choosing the 5889 * id "locale" or specifying an explicit offset will not give a specific time zone, 5890 * as it is impossible to tell with certainty which zone the offsets 5891 * match.<p> 5892 * 5893 * When the id "local" is given or the offset option is specified, this class will 5894 * have the following behaviours: 5895 * <ul> 5896 * <li>The display name will always be given as the RFC822 style, no matter what 5897 * style is requested 5898 * <li>The id will also be returned as the RFC822 style display name 5899 * <li>When the offset is explicitly given, this class will assume the time zone 5900 * does not support daylight savings time, and the offsets will be calculated 5901 * the same way year round. 5902 * <li>When the offset is explicitly given, the inDaylightSavings() method will 5903 * always return false. 5904 * <li>When the id "local" is given, this class will attempt to determine the 5905 * daylight savings time settings by examining the offset from UTC on Jan 1 5906 * and June 1 of the current year. If they are different, this class assumes 5907 * that the local time zone uses DST. When the offset for a particular date is 5908 * requested, it will use the built-in Javascript support to determine the 5909 * offset for that date. 5910 * </ul> 5911 * 5912 * If a more specific time zone is 5913 * needed with display names and known start/stop times for DST, use the "id" 5914 * property instead to specify the time zone exactly. You can perhaps ask the 5915 * user which time zone they prefer so that your app does not need to guess.<p> 5916 * 5917 * If the id and the offset are both not given, the default time zone for the 5918 * locale is retrieved from 5919 * the locale info. If the locale is not specified, the default locale for the 5920 * library is used.<p> 5921 * 5922 * Because this class was designed for use in web sites, and the vast majority 5923 * of dates and times being formatted are recent date/times, this class is simplified 5924 * by not implementing historical time zones. That is, when governments change the 5925 * time zone rules for a particular zone, only the latest such rule is implemented 5926 * in this class. That means that determining the offset for a date that is prior 5927 * to the last change may give the wrong result. Historical time zone calculations 5928 * may be implemented in a later version of iLib if there is enough demand for it, 5929 * but it would entail a much larger set of time zone data that would have to be 5930 * loaded. 5931 * 5932 * 5933 * @constructor 5934 * @param {Object} options Options guiding the construction of this time zone instance 5935 */ 5936 var TimeZone = function(options) { 5937 this.sync = true; 5938 this.locale = new Locale(); 5939 this.isLocal = false; 5940 5941 if (options) { 5942 if (options.locale) { 5943 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 5944 } 5945 5946 if (options.id) { 5947 var id = options.id.toString(); 5948 if (id === 'local') { 5949 this.isLocal = true; 5950 5951 // use standard Javascript Date to figure out the time zone offsets 5952 var now = new Date(), 5953 jan1 = new Date(now.getFullYear(), 0, 1), // months in std JS Date object are 0-based 5954 jun1 = new Date(now.getFullYear(), 5, 1); 5955 5956 // Javascript's method returns the offset backwards, so we have to 5957 // take the negative to get the correct offset 5958 this.offsetJan1 = -jan1.getTimezoneOffset(); 5959 this.offsetJun1 = -jun1.getTimezoneOffset(); 5960 // the offset of the standard time for the time zone is always the one that is closest 5961 // to negative infinity of the two, no matter whether you are in the northern or southern 5962 // hemisphere, east or west 5963 this.offset = Math.min(this.offsetJan1, this.offsetJun1); 5964 } 5965 this.id = id; 5966 } else if (options.offset) { 5967 this.offset = (typeof(options.offset) === 'string') ? parseInt(options.offset, 10) : options.offset; 5968 this.id = this.getDisplayName(undefined, undefined); 5969 } 5970 5971 if (typeof(options.sync) !== 'undefined') { 5972 this.sync = !!options.sync; 5973 } 5974 5975 this.loadParams = options.loadParams; 5976 this.onLoad = options.onLoad; 5977 } 5978 5979 //console.log("timezone: locale is " + this.locale); 5980 5981 if (!this.id) { 5982 new LocaleInfo(this.locale, { 5983 sync: this.sync, 5984 onLoad: ilib.bind(this, function (li) { 5985 this.id = li.getTimeZone() || "Etc/UTC"; 5986 this._loadtzdata(); 5987 }) 5988 }); 5989 } else { 5990 this._loadtzdata(); 5991 } 5992 5993 //console.log("localeinfo is: " + JSON.stringify(this.locinfo)); 5994 //console.log("id is: " + JSON.stringify(this.id)); 5995 }; 5996 5997 /* 5998 * Explanation of the compressed time zone info properties. 5999 * { 6000 * "o": "8:0", // offset from UTC 6001 * "f": "W{c}T", // standard abbreviation. For time zones that observe DST, the {c} replacement is replaced with the 6002 * // letter in the e.c or s.c properties below 6003 * "e": { // info about the end of DST 6004 * "j": 78322.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and 6005 * // "t" properties, but not both sets. 6006 * "m": 3, // month that it ends 6007 * "r": "l0", // rule for the day it ends "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". 6008 * // This means the 0-day (Sun) after the 7th of the month. Other possible operators are <, >, <=, >= 6009 * "t": "2:0", // time of day that the DST turns off, hours:minutes 6010 * "c": "S" // character to replace into the abbreviation for standard time 6011 * }, 6012 * "s": { // info about the start of DST 6013 * "j": 78189.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and 6014 * // "t" properties, but not both sets. 6015 * "m": 10, // month that it starts 6016 * "r": "l0", // rule for the day it starts "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". 6017 * // This means the 0-day (Sun) after the 7th of the month. Other possible operators are <, >, <=, >= 6018 * "t": "2:0", // time of day that the DST turns on, hours:minutes 6019 * "v": "1:0", // amount of time saved in hours:minutes 6020 * "c": "D" // character to replace into the abbreviation for daylight time 6021 * }, 6022 * "c": "AU", // ISO code for the country that contains this time zone 6023 * "n": "W. Australia {c} Time" 6024 * // long English name of the zone. The {c} replacement is for the word "Standard" or "Daylight" as appropriate 6025 * } 6026 */ 6027 TimeZone.prototype._loadtzdata = function () { 6028 var zoneName = this.id.replace(/-/g, "m").replace(/\+/g, "p"); 6029 // console.log("id is: " + JSON.stringify(this.id)); 6030 // console.log("zoneinfo is: " + JSON.stringify(ilib.data.zoneinfo[zoneName])); 6031 if (!ilib.data.zoneinfo[zoneName] && typeof(this.offset) === 'undefined') { 6032 Utils.loadData({ 6033 object: TimeZone, 6034 nonlocale: true, // locale independent 6035 name: "zoneinfo/" + this.id + ".json", 6036 sync: this.sync, 6037 loadParams: this.loadParams, 6038 callback: ilib.bind(this, function (tzdata) { 6039 if (tzdata && !JSUtils.isEmpty(tzdata)) { 6040 ilib.data.zoneinfo[zoneName] = tzdata; 6041 } 6042 this._initZone(zoneName); 6043 }) 6044 }); 6045 } else { 6046 this._initZone(zoneName); 6047 } 6048 }; 6049 6050 TimeZone.prototype._initZone = function(zoneName) { 6051 /** 6052 * @private 6053 * @type {{o:string,f:string,e:Object.<{m:number,r:string,t:string,z:string}>,s:Object.<{m:number,r:string,t:string,z:string,v:string,c:string}>,c:string,n:string}} 6054 */ 6055 this.zone = ilib.data.zoneinfo[zoneName]; 6056 if (!this.zone && typeof(this.offset) === 'undefined') { 6057 this.id = "Etc/UTC"; 6058 this.zone = ilib.data.zoneinfo[this.id]; 6059 } 6060 6061 this._calcDSTSavings(); 6062 6063 if (typeof(this.offset) === 'undefined' && this.zone.o) { 6064 var offsetParts = this._offsetStringToObj(this.zone.o); 6065 /** 6066 * @private 6067 * @type {number} raw offset from UTC without DST, in minutes 6068 */ 6069 this.offset = (Math.abs(offsetParts.h || 0) * 60 + (offsetParts.m || 0)) * MathUtils.signum(offsetParts.h || 0); 6070 } 6071 6072 if (this.onLoad && typeof(this.onLoad) === 'function') { 6073 this.onLoad(this); 6074 } 6075 }; 6076 6077 /** @private */ 6078 TimeZone._marshallIds = function (country, sync, callback) { 6079 var tz, ids = []; 6080 6081 if (!country) { 6082 // local is a special zone meaning "the local time zone according to the JS engine we are running upon" 6083 ids.push("local"); 6084 for (tz in ilib.data.timezones) { 6085 if (ilib.data.timezones[tz]) { 6086 ids.push(ilib.data.timezones[tz]); 6087 } 6088 } 6089 if (typeof(callback) === 'function') { 6090 callback(ids); 6091 } 6092 } else { 6093 if (!ilib.data.zoneinfo.zonetab) { 6094 Utils.loadData({ 6095 object: TimeZone, 6096 nonlocale: true, // locale independent 6097 name: "zoneinfo/zonetab.json", 6098 sync: sync, 6099 callback: ilib.bind(this, function (tzdata) { 6100 if (tzdata) { 6101 ilib.data.zoneinfo.zonetab = tzdata; 6102 } 6103 6104 ids = ilib.data.zoneinfo.zonetab[country]; 6105 6106 if (typeof(callback) === 'function') { 6107 callback(ids); 6108 } 6109 }) 6110 }); 6111 } else { 6112 ids = ilib.data.zoneinfo.zonetab[country]; 6113 if (typeof(callback) === 'function') { 6114 callback(ids); 6115 } 6116 } 6117 } 6118 6119 return ids; 6120 }; 6121 6122 /** 6123 * Return an array of available zone ids that the constructor knows about. 6124 * The country parameter is optional. If it is not given, all time zones will 6125 * be returned. If it specifies a country code, then only time zones for that 6126 * country will be returned. 6127 * 6128 * @param {string|undefined} country country code for which time zones are being sought 6129 * @param {boolean} sync whether to find the available ids synchronously (true) or asynchronously (false) 6130 * @param {function(Array.<string>)} onLoad callback function to call when the data is finished loading 6131 * @return {Array.<string>} an array of zone id strings 6132 */ 6133 TimeZone.getAvailableIds = function (country, sync, onLoad) { 6134 var tz, ids = []; 6135 6136 if (typeof(sync) !== 'boolean') { 6137 sync = true; 6138 } 6139 6140 if (ilib.data.timezones.length === 0) { 6141 if (typeof(ilib._load) !== 'undefined' && typeof(ilib._load.listAvailableFiles) === 'function') { 6142 ilib._load.listAvailableFiles(sync, function(hash) { 6143 for (var dir in hash) { 6144 var files = hash[dir]; 6145 if (ilib.isArray(files)) { 6146 files.forEach(function (filename) { 6147 if (filename && filename.match(/^zoneinfo/)) { 6148 ilib.data.timezones.push(filename.replace(/^zoneinfo\//, "").replace(/\.json$/, "")); 6149 } 6150 }); 6151 } 6152 } 6153 ids = TimeZone._marshallIds(country, sync, onLoad); 6154 }); 6155 } else { 6156 for (tz in ilib.data.zoneinfo) { 6157 if (ilib.data.zoneinfo[tz]) { 6158 ilib.data.timezones.push(tz); 6159 } 6160 } 6161 ids = TimeZone._marshallIds(country, sync, onLoad); 6162 } 6163 } else { 6164 ids = TimeZone._marshallIds(country, sync, onLoad); 6165 } 6166 6167 return ids; 6168 }; 6169 6170 /** 6171 * Return the id used to uniquely identify this time zone. 6172 * @return {string} a unique id for this time zone 6173 */ 6174 TimeZone.prototype.getId = function () { 6175 return this.id.toString(); 6176 }; 6177 6178 /** 6179 * Return the abbreviation that is used for the current time zone on the given date. 6180 * The date may be in DST or during standard time, and many zone names have different 6181 * abbreviations depending on whether or not the date is falls within DST.<p> 6182 * 6183 * There are two styles that are supported: 6184 * 6185 * <ol> 6186 * <li>standard - returns the 3 to 5 letter abbreviation of the time zone name such 6187 * as "CET" for "Central European Time" or "PDT" for "Pacific Daylight Time" 6188 * <li>rfc822 - returns an RFC 822 style time zone specifier, which specifies more 6189 * explicitly what the offset is from UTC 6190 * <li>long - returns the long name of the zone in English 6191 * </ol> 6192 * 6193 * @param {IDate=} date a date to determine if it is in daylight time or standard time 6194 * @param {string=} style one of "standard" or "rfc822". Default if not specified is "standard" 6195 * @return {string} the name of the time zone, abbreviated according to the style 6196 */ 6197 TimeZone.prototype.getDisplayName = function (date, style) { 6198 style = (this.isLocal || typeof(this.zone) === 'undefined') ? "rfc822" : (style || "standard"); 6199 switch (style) { 6200 default: 6201 case 'standard': 6202 if (this.zone.f && this.zone.f !== "zzz") { 6203 if (this.zone.f.indexOf("{c}") !== -1) { 6204 var letter = ""; 6205 letter = this.inDaylightTime(date) ? this.zone.s && this.zone.s.c : this.zone.e && this.zone.e.c; 6206 var temp = new IString(this.zone.f); 6207 return temp.format({c: letter || ""}); 6208 } 6209 return this.zone.f; 6210 } 6211 var temp = "GMT" + this.zone.o; 6212 if (this.inDaylightTime(date)) { 6213 temp += "+" + this.zone.s.v; 6214 } 6215 return temp; 6216 break; 6217 case 'rfc822': 6218 var offset = this.getOffset(date), // includes the DST if applicable 6219 ret = "UTC", 6220 hour = offset.h || 0, 6221 minute = offset.m || 0; 6222 6223 if (hour !== 0) { 6224 ret += (hour > 0) ? "+" : "-"; 6225 if (Math.abs(hour) < 10) { 6226 ret += "0"; 6227 } 6228 ret += (hour < 0) ? -hour : hour; 6229 if (minute < 10) { 6230 ret += "0"; 6231 } 6232 ret += minute; 6233 } 6234 return ret; 6235 case 'long': 6236 if (this.zone.n) { 6237 if (this.zone.n.indexOf("{c}") !== -1) { 6238 var str = this.inDaylightTime(date) ? "Daylight" : "Standard"; 6239 var temp = new IString(this.zone.n); 6240 return temp.format({c: str || ""}); 6241 } 6242 return this.zone.n; 6243 } 6244 var temp = "GMT" + this.zone.o; 6245 if (this.inDaylightTime(date)) { 6246 temp += "+" + this.zone.s.v; 6247 } 6248 return temp; 6249 break; 6250 } 6251 }; 6252 6253 /** 6254 * Convert the offset string to an object with an h, m, and possibly s property 6255 * to indicate the hours, minutes, and seconds. 6256 * 6257 * @private 6258 * @param {string} str the offset string to convert to an object 6259 * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset for the zone at 6260 * the given date/time, in hours, minutes, and seconds 6261 */ 6262 TimeZone.prototype._offsetStringToObj = function (str) { 6263 var offsetParts = (typeof(str) === 'string') ? str.split(":") : [], 6264 ret = {h:0}, 6265 temp; 6266 6267 if (offsetParts.length > 0) { 6268 ret.h = parseInt(offsetParts[0], 10); 6269 if (offsetParts.length > 1) { 6270 temp = parseInt(offsetParts[1], 10); 6271 if (temp) { 6272 ret.m = temp; 6273 } 6274 if (offsetParts.length > 2) { 6275 temp = parseInt(offsetParts[2], 10); 6276 if (temp) { 6277 ret.s = temp; 6278 } 6279 } 6280 } 6281 } 6282 6283 return ret; 6284 }; 6285 6286 /** 6287 * Returns the offset of this time zone from UTC at the given date/time. If daylight saving 6288 * time is in effect at the given date/time, this method will return the offset value 6289 * adjusted by the amount of daylight saving. 6290 * @param {IDate=} date the date for which the offset is needed 6291 * @return {Object.<{h:number,m:number}>} an object giving the offset for the zone at 6292 * the given date/time, in hours, minutes, and seconds 6293 */ 6294 TimeZone.prototype.getOffset = function (date) { 6295 if (!date) { 6296 return this.getRawOffset(); 6297 } 6298 var offset = this.getOffsetMillis(date)/60000; 6299 6300 var hours = MathUtils.down(offset/60), 6301 minutes = Math.abs(offset) - Math.abs(hours)*60; 6302 6303 var ret = { 6304 h: hours 6305 }; 6306 if (minutes != 0) { 6307 ret.m = minutes; 6308 } 6309 return ret; 6310 }; 6311 6312 /** 6313 * Returns the offset of this time zone from UTC at the given date/time expressed in 6314 * milliseconds. If daylight saving 6315 * time is in effect at the given date/time, this method will return the offset value 6316 * adjusted by the amount of daylight saving. Negative numbers indicate offsets west 6317 * of UTC and conversely, positive numbers indicate offset east of UTC. 6318 * 6319 * @param {IDate=} date the date for which the offset is needed, or null for the 6320 * present date 6321 * @return {number} the number of milliseconds of offset from UTC that the given date is 6322 */ 6323 TimeZone.prototype.getOffsetMillis = function (date) { 6324 var ret; 6325 6326 // check if the dst property is defined -- the intrinsic JS Date object doesn't work so 6327 // well if we are in the overlap time at the end of DST 6328 if (this.isLocal && typeof(date.dst) === 'undefined') { 6329 var d = (!date) ? new Date() : new Date(date.getTimeExtended()); 6330 return -d.getTimezoneOffset() * 60000; 6331 } 6332 6333 ret = this.offset; 6334 6335 if (date && this.inDaylightTime(date)) { 6336 ret += this.dstSavings; 6337 } 6338 6339 return ret * 60000; 6340 }; 6341 6342 /** 6343 * Return the offset in milliseconds when the date has an RD number in wall 6344 * time rather than in UTC time. 6345 * @protected 6346 * @param date the date to check in wall time 6347 * @returns {number} the number of milliseconds of offset from UTC that the given date is 6348 */ 6349 TimeZone.prototype._getOffsetMillisWallTime = function (date) { 6350 var ret; 6351 6352 ret = this.offset; 6353 6354 if (date && this.inDaylightTime(date, true)) { 6355 ret += this.dstSavings; 6356 } 6357 6358 return ret * 60000; 6359 }; 6360 6361 /** 6362 * Returns the offset of this time zone from UTC at the given date/time. If daylight saving 6363 * time is in effect at the given date/time, this method will return the offset value 6364 * adjusted by the amount of daylight saving. 6365 * @param {IDate=} date the date for which the offset is needed 6366 * @return {string} the offset for the zone at the given date/time as a string in the 6367 * format "h:m:s" 6368 */ 6369 TimeZone.prototype.getOffsetStr = function (date) { 6370 var offset = this.getOffset(date), 6371 ret; 6372 6373 ret = offset.h; 6374 if (typeof(offset.m) !== 'undefined') { 6375 ret += ":" + offset.m; 6376 if (typeof(offset.s) !== 'undefined') { 6377 ret += ":" + offset.s; 6378 } 6379 } else { 6380 ret += ":0"; 6381 } 6382 6383 return ret; 6384 }; 6385 6386 /** 6387 * Gets the offset from UTC for this time zone. 6388 * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset from 6389 * UTC for this time zone, in hours, minutes, and seconds 6390 */ 6391 TimeZone.prototype.getRawOffset = function () { 6392 var hours = MathUtils.down(this.offset/60), 6393 minutes = Math.abs(this.offset) - Math.abs(hours)*60; 6394 6395 var ret = { 6396 h: hours 6397 }; 6398 if (minutes != 0) { 6399 ret.m = minutes; 6400 } 6401 return ret; 6402 }; 6403 6404 /** 6405 * Gets the offset from UTC for this time zone expressed in milliseconds. Negative numbers 6406 * indicate zones west of UTC, and positive numbers indicate zones east of UTC. 6407 * 6408 * @return {number} an number giving the offset from 6409 * UTC for this time zone in milliseconds 6410 */ 6411 TimeZone.prototype.getRawOffsetMillis = function () { 6412 return this.offset * 60000; 6413 }; 6414 6415 /** 6416 * Gets the offset from UTC for this time zone without DST savings. 6417 * @return {string} the offset from UTC for this time zone, in the format "h:m:s" 6418 */ 6419 TimeZone.prototype.getRawOffsetStr = function () { 6420 var off = this.getRawOffset(); 6421 return off.h + ":" + (off.m || "0"); 6422 }; 6423 6424 /** 6425 * Return the amount of time in hours:minutes that the clock is advanced during 6426 * daylight savings time. 6427 * @return {Object.<{h:number,m:number,s:number}>} the amount of time that the 6428 * clock advances for DST in hours, minutes, and seconds 6429 */ 6430 TimeZone.prototype.getDSTSavings = function () { 6431 if (this.isLocal) { 6432 // take the absolute because the difference in the offsets may be positive or 6433 // negative, depending on the hemisphere 6434 var savings = Math.abs(this.offsetJan1 - this.offsetJun1); 6435 var hours = MathUtils.down(savings/60), 6436 minutes = savings - hours*60; 6437 return { 6438 h: hours, 6439 m: minutes 6440 }; 6441 } else if (this.zone && this.zone.s) { 6442 return this._offsetStringToObj(this.zone.s.v); // this.zone.start.savings 6443 } 6444 return {h:0}; 6445 }; 6446 6447 /** 6448 * Return the amount of time in hours:minutes that the clock is advanced during 6449 * daylight savings time. 6450 * @return {string} the amount of time that the clock advances for DST in the 6451 * format "h:m:s" 6452 */ 6453 TimeZone.prototype.getDSTSavingsStr = function () { 6454 if (this.isLocal) { 6455 var savings = this.getDSTSavings(); 6456 return savings.h + ":" + savings.m; 6457 } else if (typeof(this.offset) !== 'undefined' && this.zone && this.zone.s) { 6458 return this.zone.s.v; // this.zone.start.savings 6459 } 6460 return "0:0"; 6461 }; 6462 6463 /** 6464 * return the rd of the start of DST transition for the given year 6465 * @protected 6466 * @param {Object} rule set of rules 6467 * @param {number} year year to check 6468 * @return {number} the rd of the start of DST for the year 6469 */ 6470 TimeZone.prototype._calcRuleStart = function (rule, year) { 6471 var type = "=", 6472 weekday = 0, 6473 day, 6474 refDay, 6475 cal, 6476 hour = 0, 6477 minute = 0, 6478 second = 0, 6479 time, 6480 i; 6481 6482 if (typeof(rule.j) !== 'undefined') { 6483 refDay = new GregRataDie({ 6484 julianday: rule.j 6485 }); 6486 } else { 6487 if (rule.r.charAt(0) == 'l' || rule.r.charAt(0) == 'f') { 6488 cal = CalendarFactory({type: "gregorian"}); 6489 type = rule.r.charAt(0); 6490 weekday = parseInt(rule.r.substring(1), 10); 6491 day = (type === 'l') ? cal.getMonLength(rule.m, year) : 1; 6492 //console.log("_calcRuleStart: Calculating the " + 6493 // (rule.r.charAt(0) == 'f' ? "first " : "last ") + weekday + 6494 // " of month " + rule.m); 6495 } else { 6496 i = rule.r.indexOf('<'); 6497 if (i == -1) { 6498 i = rule.r.indexOf('>'); 6499 } 6500 6501 if (i != -1) { 6502 type = rule.r.charAt(i); 6503 weekday = parseInt(rule.r.substring(0, i), 10); 6504 day = parseInt(rule.r.substring(i+1), 10); 6505 //console.log("_calcRuleStart: Calculating the " + weekday + 6506 // type + day + " of month " + rule.m); 6507 } else { 6508 day = parseInt(rule.r, 10); 6509 //console.log("_calcRuleStart: Calculating the " + day + " of month " + rule.m); 6510 } 6511 } 6512 6513 if (rule.t) { 6514 time = rule.t.split(":"); 6515 hour = parseInt(time[0], 10); 6516 if (time.length > 1) { 6517 minute = parseInt(time[1], 10); 6518 if (time.length > 2) { 6519 second = parseInt(time[2], 10); 6520 } 6521 } 6522 } 6523 //console.log("calculating rd of " + year + "/" + rule.m + "/" + day); 6524 refDay = new GregRataDie({ 6525 year: year, 6526 month: rule.m, 6527 day: day, 6528 hour: hour, 6529 minute: minute, 6530 second: second 6531 }); 6532 } 6533 //console.log("refDay is " + JSON.stringify(refDay)); 6534 var d = refDay.getRataDie(); 6535 6536 switch (type) { 6537 case 'l': 6538 case '<': 6539 //console.log("returning " + refDay.onOrBefore(rd, weekday)); 6540 d = refDay.onOrBefore(weekday); 6541 break; 6542 case 'f': 6543 case '>': 6544 //console.log("returning " + refDay.onOrAfterRd(rd, weekday)); 6545 d = refDay.onOrAfter(weekday); 6546 break; 6547 } 6548 return d; 6549 }; 6550 6551 /** 6552 * @private 6553 */ 6554 TimeZone.prototype._calcDSTSavings = function () { 6555 var saveParts = this.getDSTSavings(); 6556 6557 /** 6558 * @private 6559 * @type {number} savings in minutes when DST is in effect 6560 */ 6561 this.dstSavings = (Math.abs(saveParts.h || 0) * 60 + (saveParts.m || 0)) * MathUtils.signum(saveParts.h || 0); 6562 }; 6563 6564 /** 6565 * @private 6566 */ 6567 TimeZone.prototype._getDSTStartRule = function (year) { 6568 // TODO: update this when historic/future zones are supported 6569 return this.zone.s; 6570 }; 6571 6572 /** 6573 * @private 6574 */ 6575 TimeZone.prototype._getDSTEndRule = function (year) { 6576 // TODO: update this when historic/future zones are supported 6577 return this.zone.e; 6578 }; 6579 6580 /** 6581 * Returns whether or not the given date is in daylight saving time for the current 6582 * zone. Note that daylight savings time is observed for the summer. Because 6583 * the seasons are reversed, daylight savings time in the southern hemisphere usually 6584 * runs from the end of the year through New Years into the first few months of the 6585 * next year. This method will correctly calculate the start and end of DST for any 6586 * location. 6587 * 6588 * @param {IDate=} date a date for which the info about daylight time is being sought, 6589 * or undefined to tell whether we are currently in daylight savings time 6590 * @param {boolean=} wallTime if true, then the given date is in wall time. If false or 6591 * undefined, it is in the usual UTC time. 6592 * @return {boolean} true if the given date is in DST for the current zone, and false 6593 * otherwise. 6594 */ 6595 TimeZone.prototype.inDaylightTime = function (date, wallTime) { 6596 var rd, startRd, endRd, year; 6597 6598 if (this.isLocal) { 6599 // check if the dst property is defined -- the intrinsic JS Date object doesn't work so 6600 // well if we are in the overlap time at the end of DST, so we have to work around that 6601 // problem by adding in the savings ourselves 6602 var offset = 0; 6603 if (typeof(date.dst) !== 'undefined' && !date.dst) { 6604 offset = this.dstSavings * 60000; 6605 } 6606 6607 var d = new Date(date ? date.getTimeExtended() + offset: undefined); 6608 // the DST offset is always the one that is closest to positive infinity, no matter 6609 // if you are in the northern or southern hemisphere, east or west 6610 var dst = Math.max(this.offsetJan1, this.offsetJun1); 6611 return (-d.getTimezoneOffset() === dst); 6612 } 6613 6614 if (!date || !date.cal || date.cal.type !== "gregorian") { 6615 // convert to Gregorian so that we can tell if it is in DST or not 6616 var time = date && typeof(date.getTimeExtended) === 'function' ? date.getTimeExtended() : undefined; 6617 rd = new GregRataDie({unixtime: time}).getRataDie(); 6618 year = new Date(time).getUTCFullYear(); 6619 } else { 6620 rd = date.rd.getRataDie(); 6621 year = date.year; 6622 } 6623 // rd should be a Gregorian RD number now, in UTC 6624 6625 // if we aren't using daylight time in this zone for the given year, then we are 6626 // not in daylight time 6627 if (!this.useDaylightTime(year)) { 6628 return false; 6629 } 6630 6631 // these calculate the start/end in local wall time 6632 var startrule = this._getDSTStartRule(year); 6633 var endrule = this._getDSTEndRule(year); 6634 startRd = this._calcRuleStart(startrule, year); 6635 endRd = this._calcRuleStart(endrule, year); 6636 6637 if (wallTime) { 6638 // rd is in wall time, so we have to make sure to skip the missing time 6639 // at the start of DST when standard time ends and daylight time begins 6640 startRd += this.dstSavings/1440; 6641 } else { 6642 // rd is in UTC, so we have to convert the start/end to UTC time so 6643 // that they can be compared directly to the UTC rd number of the date 6644 6645 // when DST starts, time is standard time already, so we only have 6646 // to subtract the offset to get to UTC and not worry about the DST savings 6647 startRd -= this.offset/1440; 6648 6649 // when DST ends, time is in daylight time already, so we have to 6650 // subtract the DST savings to get back to standard time, then the 6651 // offset to get to UTC 6652 endRd -= (this.offset + this.dstSavings)/1440; 6653 } 6654 6655 // In the northern hemisphere, the start comes first some time in spring (Feb-Apr), 6656 // then the end some time in the fall (Sept-Nov). In the southern 6657 // hemisphere, it is the other way around because the seasons are reversed. Standard 6658 // time is still in the winter, but the winter months are May-Aug, and daylight 6659 // savings time usually starts Aug-Oct of one year and runs through Mar-May of the 6660 // next year. 6661 if (rd < endRd && endRd - rd <= this.dstSavings/1440 && typeof(date.dst) === 'boolean') { 6662 // take care of the magic overlap time at the end of DST 6663 return date.dst; 6664 } 6665 if (startRd < endRd) { 6666 // northern hemisphere 6667 return (rd >= startRd && rd < endRd) ? true : false; 6668 } 6669 // southern hemisphere 6670 return (rd >= startRd || rd < endRd) ? true : false; 6671 }; 6672 6673 /** 6674 * Returns true if this time zone switches to daylight savings time at some point 6675 * in the year, and false otherwise. 6676 * @param {number} year Whether or not the time zone uses daylight time in the given year. If 6677 * this parameter is not given, the current year is assumed. 6678 * @return {boolean} true if the time zone uses daylight savings time 6679 */ 6680 TimeZone.prototype.useDaylightTime = function (year) { 6681 6682 // this zone uses daylight savings time iff there is a rule defining when to start 6683 // and when to stop the DST 6684 return (this.isLocal && this.offsetJan1 !== this.offsetJun1) || 6685 (typeof(this.zone) !== 'undefined' && 6686 typeof(this.zone.s) !== 'undefined' && 6687 typeof(this.zone.e) !== 'undefined'); 6688 }; 6689 6690 /** 6691 * Returns the ISO 3166 code of the country for which this time zone is defined. 6692 * @return {string} the ISO 3166 code of the country for this zone 6693 */ 6694 TimeZone.prototype.getCountry = function () { 6695 return this.zone.c; 6696 }; 6697 6698 6699 6700 /*< SearchUtils.js */ 6701 /* 6702 * SearchUtils.js - Misc search utility routines 6703 * 6704 * Copyright © 2013-2015, JEDLSoft 6705 * 6706 * Licensed under the Apache License, Version 2.0 (the "License"); 6707 * you may not use this file except in compliance with the License. 6708 * You may obtain a copy of the License at 6709 * 6710 * http://www.apache.org/licenses/LICENSE-2.0 6711 * 6712 * Unless required by applicable law or agreed to in writing, software 6713 * distributed under the License is distributed on an "AS IS" BASIS, 6714 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6715 * 6716 * See the License for the specific language governing permissions and 6717 * limitations under the License. 6718 */ 6719 6720 var SearchUtils = {}; 6721 6722 /** 6723 * Binary search a sorted array for a particular target value. 6724 * If the exact value is not found, it returns the index of the smallest 6725 * entry that is greater than the given target value.<p> 6726 * 6727 * The comparator 6728 * parameter is a function that knows how to compare elements of the 6729 * array and the target. The function should return a value greater than 0 6730 * if the array element is greater than the target, a value less than 0 if 6731 * the array element is less than the target, and 0 if the array element 6732 * and the target are equivalent.<p> 6733 * 6734 * If the comparator function is not specified, this function assumes 6735 * the array and the target are numeric values and should be compared 6736 * as such.<p> 6737 * 6738 * 6739 * @static 6740 * @param {*} target element being sought 6741 * @param {Array} arr the array being searched 6742 * @param {?function(*,*)=} comparator a comparator that is appropriate for comparing two entries 6743 * in the array 6744 * @return the index of the array into which the value would fit if 6745 * inserted, or -1 if given array is not an array or the target is not 6746 * a number 6747 */ 6748 SearchUtils.bsearch = function(target, arr, comparator) { 6749 if (typeof(arr) === 'undefined' || !arr || typeof(target) === 'undefined') { 6750 return -1; 6751 } 6752 6753 var high = arr.length - 1, 6754 low = 0, 6755 mid = 0, 6756 value, 6757 cmp = comparator || SearchUtils.bsearch.numbers; 6758 6759 while (low <= high) { 6760 mid = Math.floor((high+low)/2); 6761 value = cmp(arr[mid], target); 6762 if (value > 0) { 6763 high = mid - 1; 6764 } else if (value < 0) { 6765 low = mid + 1; 6766 } else { 6767 return mid; 6768 } 6769 } 6770 6771 return low; 6772 }; 6773 6774 /** 6775 * Returns whether or not the given element is greater than, less than, 6776 * or equal to the given target.<p> 6777 * 6778 * @private 6779 * @static 6780 * @param {number} element the element being tested 6781 * @param {number} target the target being sought 6782 */ 6783 SearchUtils.bsearch.numbers = function(element, target) { 6784 return element - target; 6785 }; 6786 6787 /** 6788 * Do a bisection search of a function for a particular target value.<p> 6789 * 6790 * The function to search is a function that takes a numeric parameter, 6791 * does calculations, and returns gives a numeric result. The 6792 * function should should be smooth and not have any discontinuities 6793 * between the low and high values of the parameter. 6794 * 6795 * 6796 * @static 6797 * @param {number} target value being sought 6798 * @param {number} low the lower bounds to start searching 6799 * @param {number} high the upper bounds to start searching 6800 * @param {number} precision minimum precision to support. Use 0 if you want to use the default. 6801 * @param {?function(number)=} func function to search 6802 * @return an approximation of the input value to the function that gives the desired 6803 * target output value, correct to within the error range of Javascript floating point 6804 * arithmetic, or NaN if there was some error 6805 */ 6806 SearchUtils.bisectionSearch = function(target, low, high, precision, func) { 6807 if (typeof(target) !== 'number' || 6808 typeof(low) !== 'number' || 6809 typeof(high) !== 'number' || 6810 typeof(func) !== 'function') { 6811 return NaN; 6812 } 6813 6814 var mid = 0, 6815 value, 6816 pre = precision > 0 ? precision : 1e-13; 6817 6818 do { 6819 mid = (high+low)/2; 6820 value = func(mid); 6821 if (value > target) { 6822 high = mid; 6823 } else if (value < target) { 6824 low = mid; 6825 } 6826 } while (high - low > pre); 6827 6828 return mid; 6829 }; 6830 6831 6832 6833 /*< GregorianDate.js */ 6834 /* 6835 * GregorianDate.js - Represent a date in the Gregorian calendar 6836 * 6837 * Copyright © 2012-2015, JEDLSoft 6838 * 6839 * Licensed under the Apache License, Version 2.0 (the "License"); 6840 * you may not use this file except in compliance with the License. 6841 * You may obtain a copy of the License at 6842 * 6843 * http://www.apache.org/licenses/LICENSE-2.0 6844 * 6845 * Unless required by applicable law or agreed to in writing, software 6846 * distributed under the License is distributed on an "AS IS" BASIS, 6847 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6848 * 6849 * See the License for the specific language governing permissions and 6850 * limitations under the License. 6851 */ 6852 6853 /* !depends 6854 ilib.js 6855 IDate.js 6856 GregorianCal.js 6857 SearchUtils.js 6858 MathUtils.js 6859 Locale.js 6860 LocaleInfo.js 6861 JulianDay.js 6862 GregRataDie.js 6863 TimeZone.js 6864 */ 6865 6866 6867 6868 6869 /** 6870 * @class 6871 * Construct a new Gregorian date object. The constructor parameters can 6872 * contain any of the following properties: 6873 * 6874 * <ul> 6875 * <li><i>unixtime<i> - sets the time of this instance according to the given 6876 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 6877 * 6878 * <li><i>julianday</i> - sets the time of this instance according to the given 6879 * Julian Day instance or the Julian Day given as a float 6880 * 6881 * <li><i>year</i> - any integer, including 0 6882 * 6883 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 6884 * 6885 * <li><i>day</i> - 1 to 31 6886 * 6887 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 6888 * is always done with an unambiguous 24 hour representation 6889 * 6890 * <li><i>minute</i> - 0 to 59 6891 * 6892 * <li><i>second</i> - 0 to 59 6893 * 6894 * <li><i>millisecond</i> - 0 to 999 6895 * 6896 * <li><i>dst</i> - boolean used to specify whether the given time components are 6897 * intended to be in daylight time or not. This is only used in the overlap 6898 * time when transitioning from DST to standard time, and the time components are 6899 * ambiguous. Otherwise at all other times of the year, this flag is ignored. 6900 * If you specify the date using unix time (UTC) or a julian day, then the time is 6901 * already unambiguous and this flag does not need to be specified. 6902 * <p> 6903 * For example, in the US, the transition out of daylight savings time 6904 * in 2014 happens at Nov 2, 2014 2:00am Daylight Time, when the time falls 6905 * back to Nov 2, 2014 1:00am Standard Time. If you give a date/time components as 6906 * "Nov 2, 2014 1:30am", then there are two 1:30am times in that day, and you would 6907 * have to give the standard flag to indicate which of those two you mean. 6908 * (dst=true means daylight time, dst=false means standard time). 6909 * 6910 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 6911 * of this gregorian date. The date/time is kept in the local time. The time zone 6912 * is used later if this date is formatted according to a different time zone and 6913 * the difference has to be calculated, or when the date format has a time zone 6914 * component in it. 6915 * 6916 * <li><i>locale</i> - locale for this gregorian date. If the time zone is not 6917 * given, it can be inferred from this locale. For locales that span multiple 6918 * time zones, the one with the largest population is chosen as the one that 6919 * represents the locale. 6920 * 6921 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 6922 * </ul> 6923 * 6924 * If the constructor is called with another Gregorian date instance instead of 6925 * a parameter block, the other instance acts as a parameter block and its 6926 * settings are copied into the current instance.<p> 6927 * 6928 * If the constructor is called with no arguments at all or if none of the 6929 * properties listed above 6930 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 6931 * components are 6932 * filled in with the current date at the time of instantiation. Note that if 6933 * you do not give the time zone when defaulting to the current time and the 6934 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 6935 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 6936 * Mean Time").<p> 6937 * 6938 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 6939 * specified in the params, it is assumed that they have the smallest possible 6940 * value in the range for the property (zero or one).<p> 6941 * 6942 * 6943 * @constructor 6944 * @extends IDate 6945 * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian date 6946 */ 6947 var GregorianDate = function(params) { 6948 this.cal = new GregorianCal(); 6949 this.timezone = "local"; 6950 6951 if (params) { 6952 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 6953 // for doing inheritance, so don't need to fill in the data. The inheriting class only wants the methods. 6954 return; 6955 } 6956 if (params.locale) { 6957 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 6958 var li = new LocaleInfo(this.locale); 6959 this.timezone = li.getTimeZone(); 6960 } 6961 if (params.timezone) { 6962 this.timezone = params.timezone.toString(); 6963 } 6964 6965 if (params.year || params.month || params.day || params.hour || 6966 params.minute || params.second || params.millisecond ) { 6967 this.year = parseInt(params.year, 10) || 0; 6968 this.month = parseInt(params.month, 10) || 1; 6969 this.day = parseInt(params.day, 10) || 1; 6970 this.hour = parseInt(params.hour, 10) || 0; 6971 this.minute = parseInt(params.minute, 10) || 0; 6972 this.second = parseInt(params.second, 10) || 0; 6973 this.millisecond = parseInt(params.millisecond, 10) || 0; 6974 if (typeof(params.dst) === 'boolean') { 6975 this.dst = params.dst; 6976 } 6977 this.rd = this.newRd(params); 6978 6979 // add the time zone offset to the rd to convert to UTC 6980 this.offset = 0; 6981 if (this.timezone === "local" && typeof(params.dst) === 'undefined') { 6982 // if dst is defined, the intrinsic Date object has no way of specifying which version of a time you mean 6983 // in the overlap time at the end of DST. Do you mean the daylight 1:30am or the standard 1:30am? In this 6984 // case, use the ilib calculations below, which can distinguish between the two properly 6985 var d = new Date(this.year, this.month-1, this.day, this.hour, this.minute, this.second, this.millisecond); 6986 this.offset = -d.getTimezoneOffset() / 1440; 6987 } else { 6988 if (!this.tz) { 6989 this.tz = new TimeZone({id: this.timezone}); 6990 } 6991 // getOffsetMillis requires that this.year, this.rd, and this.dst 6992 // are set in order to figure out which time zone rules apply and 6993 // what the offset is at that point in the year 6994 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 6995 } 6996 if (this.offset !== 0) { 6997 this.rd = this.newRd({ 6998 rd: this.rd.getRataDie() - this.offset 6999 }); 7000 } 7001 } 7002 } 7003 7004 if (!this.rd) { 7005 this.rd = this.newRd(params); 7006 this._calcDateComponents(); 7007 } 7008 }; 7009 7010 GregorianDate.prototype = new IDate({noinstance: true}); 7011 GregorianDate.prototype.parent = IDate; 7012 GregorianDate.prototype.constructor = GregorianDate; 7013 7014 /** 7015 * Return a new RD for this date type using the given params. 7016 * @private 7017 * @param {Object=} params the parameters used to create this rata die instance 7018 * @returns {RataDie} the new RD instance for the given params 7019 */ 7020 GregorianDate.prototype.newRd = function (params) { 7021 return new GregRataDie(params); 7022 }; 7023 7024 /** 7025 * Calculates the Gregorian year for a given rd number. 7026 * @private 7027 * @static 7028 */ 7029 GregorianDate._calcYear = function(rd) { 7030 var days400, 7031 days100, 7032 days4, 7033 years400, 7034 years100, 7035 years4, 7036 years1, 7037 year; 7038 7039 years400 = Math.floor((rd - 1) / 146097); 7040 days400 = MathUtils.mod((rd - 1), 146097); 7041 years100 = Math.floor(days400 / 36524); 7042 days100 = MathUtils.mod(days400, 36524); 7043 years4 = Math.floor(days100 / 1461); 7044 days4 = MathUtils.mod(days100, 1461); 7045 years1 = Math.floor(days4 / 365); 7046 7047 year = 400 * years400 + 100 * years100 + 4 * years4 + years1; 7048 if (years100 !== 4 && years1 !== 4) { 7049 year++; 7050 } 7051 return year; 7052 }; 7053 7054 /** 7055 * @private 7056 */ 7057 GregorianDate.prototype._calcYear = function(rd) { 7058 return GregorianDate._calcYear(rd); 7059 }; 7060 7061 /** 7062 * Calculate the date components for the current time zone 7063 * @private 7064 */ 7065 GregorianDate.prototype._calcDateComponents = function () { 7066 if (this.timezone === "local" && this.rd.getRataDie() >= -99280837 && this.rd.getRataDie() <= 100719163) { 7067 // console.log("using js Date to calculate offset"); 7068 // use the intrinsic JS Date object to do the tz conversion for us, which 7069 // guarantees that it follows the system tz database settings 7070 var d = new Date(this.rd.getTimeExtended()); 7071 7072 /** 7073 * Year in the Gregorian calendar. 7074 * @type number 7075 */ 7076 this.year = d.getFullYear(); 7077 7078 /** 7079 * The month number, ranging from 1 (January) to 12 (December). 7080 * @type number 7081 */ 7082 this.month = d.getMonth()+1; 7083 7084 /** 7085 * The day of the month. This ranges from 1 to 31. 7086 * @type number 7087 */ 7088 this.day = d.getDate(); 7089 7090 /** 7091 * The hour of the day. This can be a number from 0 to 23, as times are 7092 * stored unambiguously in the 24-hour clock. 7093 * @type number 7094 */ 7095 this.hour = d.getHours(); 7096 7097 /** 7098 * The minute of the hours. Ranges from 0 to 59. 7099 * @type number 7100 */ 7101 this.minute = d.getMinutes(); 7102 7103 /** 7104 * The second of the minute. Ranges from 0 to 59. 7105 * @type number 7106 */ 7107 this.second = d.getSeconds(); 7108 7109 /** 7110 * The millisecond of the second. Ranges from 0 to 999. 7111 * @type number 7112 */ 7113 this.millisecond = d.getMilliseconds(); 7114 7115 this.offset = -d.getTimezoneOffset() / 1440; 7116 } else { 7117 // console.log("using ilib to calculate offset. tz is " + this.timezone); 7118 // console.log("GregDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 7119 if (typeof(this.offset) === "undefined") { 7120 // console.log("calculating offset"); 7121 this.year = this._calcYear(this.rd.getRataDie()); 7122 7123 // now offset the RD by the time zone, then recalculate in case we were 7124 // near the year boundary 7125 if (!this.tz) { 7126 this.tz = new TimeZone({id: this.timezone}); 7127 } 7128 this.offset = this.tz.getOffsetMillis(this) / 86400000; 7129 // } else { 7130 // console.log("offset is already defined somehow. type is " + typeof(this.offset)); 7131 // console.trace("Stack is this one"); 7132 } 7133 // console.log("offset is " + this.offset); 7134 var rd = this.rd.getRataDie(); 7135 if (this.offset !== 0) { 7136 rd += this.offset; 7137 } 7138 this.year = this._calcYear(rd); 7139 7140 var yearStartRd = this.newRd({ 7141 year: this.year, 7142 month: 1, 7143 day: 1, 7144 cal: this.cal 7145 }); 7146 7147 // remainder is days into the year 7148 var remainder = rd - yearStartRd.getRataDie() + 1; 7149 7150 var cumulative = GregorianCal.prototype.isLeapYear.call(this.cal, this.year) ? 7151 GregRataDie.cumMonthLengthsLeap : 7152 GregRataDie.cumMonthLengths; 7153 7154 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 7155 remainder = remainder - cumulative[this.month-1]; 7156 7157 this.day = Math.floor(remainder); 7158 remainder -= this.day; 7159 // now convert to milliseconds for the rest of the calculation 7160 remainder = Math.round(remainder * 86400000); 7161 7162 this.hour = Math.floor(remainder/3600000); 7163 remainder -= this.hour * 3600000; 7164 7165 this.minute = Math.floor(remainder/60000); 7166 remainder -= this.minute * 60000; 7167 7168 this.second = Math.floor(remainder/1000); 7169 remainder -= this.second * 1000; 7170 7171 this.millisecond = Math.floor(remainder); 7172 } 7173 }; 7174 7175 /** 7176 * Return the day of the week of this date. The day of the week is encoded 7177 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 7178 * 7179 * @return {number} the day of the week 7180 */ 7181 GregorianDate.prototype.getDayOfWeek = function() { 7182 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 7183 return MathUtils.mod(rd, 7); 7184 }; 7185 7186 /** 7187 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 7188 * 365, regardless of months or weeks, etc. That is, January 1st is day 1, and 7189 * December 31st is 365 in regular years, or 366 in leap years. 7190 * @return {number} the ordinal day of the year 7191 */ 7192 GregorianDate.prototype.getDayOfYear = function() { 7193 var cumulativeMap = this.cal.isLeapYear(this.year) ? 7194 GregRataDie.cumMonthLengthsLeap : 7195 GregRataDie.cumMonthLengths; 7196 7197 return cumulativeMap[this.month-1] + this.day; 7198 }; 7199 7200 /** 7201 * Return the era for this date as a number. The value for the era for Gregorian 7202 * calendars is -1 for "before the common era" (BCE) and 1 for "the common era" (CE). 7203 * BCE dates are any date before Jan 1, 1 CE. In the proleptic Gregorian calendar, 7204 * there is a year 0, so any years that are negative or zero are BCE. In the Julian 7205 * calendar, there is no year 0. Instead, the calendar goes straight from year -1 to 7206 * 1. 7207 * @return {number} 1 if this date is in the common era, -1 if it is before the 7208 * common era 7209 */ 7210 GregorianDate.prototype.getEra = function() { 7211 return (this.year < 1) ? -1 : 1; 7212 }; 7213 7214 /** 7215 * Return the name of the calendar that governs this date. 7216 * 7217 * @return {string} a string giving the name of the calendar 7218 */ 7219 GregorianDate.prototype.getCalendar = function() { 7220 return "gregorian"; 7221 }; 7222 7223 // register with the factory method 7224 IDate._constructors["gregorian"] = GregorianDate; 7225 7226 7227 /*< DateFactory.js */ 7228 /* 7229 * DateFactory.js - Factory class to create the right subclasses of a date for any 7230 * calendar or locale. 7231 * 7232 * Copyright © 2012-2015, JEDLSoft 7233 * 7234 * Licensed under the Apache License, Version 2.0 (the "License"); 7235 * you may not use this file except in compliance with the License. 7236 * You may obtain a copy of the License at 7237 * 7238 * http://www.apache.org/licenses/LICENSE-2.0 7239 * 7240 * Unless required by applicable law or agreed to in writing, software 7241 * distributed under the License is distributed on an "AS IS" BASIS, 7242 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7243 * 7244 * See the License for the specific language governing permissions and 7245 * limitations under the License. 7246 */ 7247 7248 /* !depends ilib.js Locale.js LocaleInfo.js JulianDay.js JSUtils.js CalendarFactory.js IDate.js GregorianDate.js*/ 7249 7250 7251 7252 // Statically depend on these even though we don't use them 7253 // to guarantee they are loaded into the cache already. 7254 7255 /** 7256 * Factory method to create a new instance of a date subclass.<p> 7257 * 7258 * The options parameter can be an object that contains the following 7259 * properties: 7260 * 7261 * <ul> 7262 * <li><i>type</i> - specify the type/calendar of the date desired. The 7263 * list of valid values changes depending on which calendars are 7264 * defined. When assembling your iliball.js, include those date type 7265 * you wish to use in your program or web page, and they will register 7266 * themselves with this factory method. The "gregorian", 7267 * and "julian" calendars are all included by default, as they are the 7268 * standard calendars for much of the world. If not specified, the type 7269 * of the date returned is the one that is appropriate for the locale. 7270 * This property may also be given as "calendar" instead of "type". 7271 * 7272 * <li><i>onLoad</i> - a callback function to call when the date object is fully 7273 * loaded. When the onLoad option is given, the date factory will attempt to 7274 * load any missing locale data using the ilib loader callback. 7275 * When the constructor is done (even if the data is already preassembled), the 7276 * onLoad function is called with the current instance as a parameter, so this 7277 * callback can be used with preassembled or dynamic loading or a mix of the two. 7278 * 7279 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 7280 * asynchronously. If this option is given as "false", then the "onLoad" 7281 * callback must be given, as the instance returned from this constructor will 7282 * not be usable for a while. 7283 * 7284 * <li><i>loadParams</i> - an object containing parameters to pass to the 7285 * loader callback function when locale data is missing. The parameters are not 7286 * interpretted or modified in any way. They are simply passed along. The object 7287 * may contain any property/value pairs as long as the calling code is in 7288 * agreement with the loader callback function as to what those parameters mean. 7289 * </ul> 7290 * 7291 * The options object is also passed down to the date constructor, and 7292 * thus can contain the the properties as the date object being instantiated. 7293 * See the documentation for {@link GregorianDate}, and other 7294 * subclasses for more details on other parameter that may be passed in.<p> 7295 * 7296 * Please note that if you do not give the type parameter, this factory 7297 * method will create a date object that is appropriate for the calendar 7298 * that is most commonly used in the specified or current ilib locale. 7299 * For example, in Thailand, the most common calendar is the Thai solar 7300 * calendar. If the current locale is "th-TH" (Thai for Thailand) and you 7301 * use this factory method to construct a new date without specifying the 7302 * type, it will automatically give you back an instance of 7303 * {@link ThaiSolarDate}. This is convenient because you do not 7304 * need to know which locales use which types of dates. In fact, you 7305 * should always use this factory method to make new date instances unless 7306 * you know that you specifically need a date in a particular calendar.<p> 7307 * 7308 * Also note that when you pass in the date components such as year, month, 7309 * day, etc., these components should be appropriate for the given date 7310 * being instantiated. That is, in our Thai example in the previous 7311 * paragraph, the year and such should be given as a Thai solar year, not 7312 * the Gregorian year that you get from the Javascript Date class. In 7313 * order to initialize a date instance when you don't know what subclass 7314 * will be instantiated for the locale, use a parameter such as "unixtime" 7315 * or "julianday" which are unambiguous and based on UTC time, instead of 7316 * the year/month/date date components. The date components for that UTC 7317 * time will be calculated and the time zone offset will be automatically 7318 * factored in. 7319 * 7320 * @static 7321 * @param {Object=} options options controlling the construction of this instance, or 7322 * undefined to use the default options 7323 * @return {IDate} an instance of a calendar object of the appropriate type 7324 */ 7325 var DateFactory = function(options) { 7326 var locale, 7327 type, 7328 cons, 7329 sync = true, 7330 obj; 7331 7332 if (options) { 7333 if (options.locale) { 7334 locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 7335 } 7336 7337 type = options.type || options.calendar; 7338 7339 if (typeof(options.sync) === 'boolean') { 7340 sync = options.sync; 7341 } 7342 } 7343 7344 if (!locale) { 7345 locale = new Locale(); // default locale 7346 } 7347 7348 if (!type) { 7349 new LocaleInfo(locale, { 7350 sync: sync, 7351 loadParams: options && options.loadParams, 7352 onLoad: ilib.bind(this, function(info) { 7353 type = info.getCalendar(); 7354 7355 obj = DateFactory._init(type, options); 7356 7357 if (options && typeof(options.onLoad) === 'function') { 7358 options.onLoad(obj); 7359 } 7360 }) 7361 }); 7362 } else { 7363 obj = DateFactory._init(type, options); 7364 } 7365 7366 return obj 7367 }; 7368 7369 /** 7370 * Map calendar names to classes to initialize in the dynamic code model. 7371 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 7372 * @private 7373 */ 7374 DateFactory._dynMap = { 7375 "coptic": "Coptic", 7376 "ethiopic": "Ethiopic", 7377 "gregorian": "Gregorian", 7378 "han": "Han", 7379 "hebrew": "Hebrew", 7380 "islamic": "Islamic", 7381 "julian": "Julian", 7382 "persian": "Persian", 7383 "persian-algo": "PersianAlgo", 7384 "thaisolar": "ThaiSolar" 7385 }; 7386 7387 /** 7388 * Dynamically load the code for a calendar and calendar class if necessary. 7389 * @protected 7390 */ 7391 DateFactory._dynLoadDate = function (name) { 7392 if (!IDate._constructors[name]) { 7393 var entry = DateFactory._dynMap[name]; 7394 if (entry) { 7395 IDate._constructors[name] = require("./" + entry + "Date.js"); 7396 } 7397 } 7398 return IDate._constructors[name]; 7399 }; 7400 7401 /** 7402 * @protected 7403 * @static 7404 */ 7405 DateFactory._init = function(type, options) { 7406 var cons; 7407 7408 if (ilib.isDynCode()) { 7409 DateFactory._dynLoadDate(type); 7410 CalendarFactory._dynLoadCalendar(type); 7411 } 7412 7413 cons = IDate._constructors[type]; 7414 7415 // pass the same options through to the constructor so the subclass 7416 // has the ability to do something with if it needs to 7417 return cons && new cons(options); 7418 }; 7419 7420 /** 7421 * Convert JavaScript Date objects and other types into native Dates. This accepts any 7422 * string or number that can be translated by the JavaScript Date class, 7423 * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) 7424 * any JavaScript Date classed object, any IDate subclass, an JulianDay object, an object 7425 * containing the normal options to initialize an IDate instance, or null (will 7426 * return null or undefined if input is null or undefined). Normal output is 7427 * a standard native subclass of the IDate object as appropriate for the locale. 7428 * 7429 * @static 7430 * @protected 7431 * @param {IDate|Object|JulianDay|Date|string|number=} inDate The input date object, string or Number. 7432 * @param {IString|string=} timezone timezone to use if a new date object is created 7433 * @param {Locale|string=} locale locale to use when constructing an IDate 7434 * @return {IDate|null|undefined} an IDate subclass equivalent to the given inDate 7435 */ 7436 DateFactory._dateToIlib = function(inDate, timezone, locale) { 7437 if (typeof(inDate) === 'undefined' || inDate === null) { 7438 return inDate; 7439 } 7440 if (inDate instanceof IDate) { 7441 return inDate; 7442 } 7443 if (typeof(inDate) === 'number') { 7444 return DateFactory({ 7445 unixtime: inDate, 7446 timezone: timezone, 7447 locale: locale 7448 }); 7449 } 7450 if (typeof(inDate) === 'string') { 7451 inDate = new Date(inDate); 7452 } 7453 if (JSUtils.isDate(inDate)) { 7454 return DateFactory({ 7455 unixtime: inDate.getTime(), 7456 timezone: timezone, 7457 locale: locale 7458 }); 7459 } 7460 if (inDate instanceof JulianDay) { 7461 return DateFactory({ 7462 jd: inDate, 7463 timezone: timezone, 7464 locale: locale 7465 }); 7466 } 7467 if (typeof(inDate) === 'object') { 7468 return DateFactory(inDate); 7469 } 7470 return DateFactory({ 7471 unixtime: inDate.getTime(), 7472 timezone: timezone, 7473 locale: locale 7474 }); 7475 }; 7476 7477 7478 /*< ResBundle.js */ 7479 /* 7480 * ResBundle.js - Resource bundle definition 7481 * 7482 * Copyright © 2012-2016, JEDLSoft 7483 * 7484 * Licensed under the Apache License, Version 2.0 (the "License"); 7485 * you may not use this file except in compliance with the License. 7486 * You may obtain a copy of the License at 7487 * 7488 * http://www.apache.org/licenses/LICENSE-2.0 7489 * 7490 * Unless required by applicable law or agreed to in writing, software 7491 * distributed under the License is distributed on an "AS IS" BASIS, 7492 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7493 * 7494 * See the License for the specific language governing permissions and 7495 * limitations under the License. 7496 */ 7497 7498 // !depends ilib.js Locale.js LocaleInfo.js IString.js Utils.js JSUtils.js 7499 7500 // !data pseudomap 7501 7502 7503 7504 7505 /** 7506 * @class 7507 * Create a new resource bundle instance. The resource bundle loads strings 7508 * appropriate for a particular locale and provides them via the getString 7509 * method.<p> 7510 * 7511 * The options object may contain any (or none) of the following properties: 7512 * 7513 * <ul> 7514 * <li><i>locale</i> - The locale of the strings to load. If not specified, the default 7515 * locale is the the default for the web page or app in which the bundle is 7516 * being loaded. 7517 * 7518 * <li><i>name</i> - Base name of the resource bundle to load. If not specified the default 7519 * base name is "resources". 7520 * 7521 * <li><i>type</i> - Name the type of strings this bundle contains. Valid values are 7522 * "xml", "html", "text", "c", or "raw". The default is "text". If the type is "xml" or "html", 7523 * then XML/HTML entities and tags are not pseudo-translated. During a real translation, 7524 * HTML character entities are translated to their corresponding characters in a source 7525 * string before looking that string up in the translations. Also, the characters "<", ">", 7526 * and "&" are converted to entities again in the output, but characters are left as they 7527 * are. If the type is "xml", "html", or "text" types, then the replacement parameter names 7528 * are not pseudo-translated as well so that the output can be used for formatting with 7529 * the IString class. If the type is "c" then all C language style printf replacement 7530 * parameters (eg. "%s" and "%d") are skipped automatically. If the type is raw, all characters 7531 * are pseudo-translated, including replacement parameters as well as XML/HTML tags and entities. 7532 * 7533 * <li><i>lengthen</i> - when pseudo-translating the string, tell whether or not to 7534 * automatically lengthen the string to simulate "long" languages such as German 7535 * or French. This is a boolean value. Default is false. 7536 * 7537 * <li><i>missing</i> - what to do when a resource is missing. The choices are: 7538 * <ul> 7539 * <li><i>source</i> - return the source string unchanged 7540 * <li><i>pseudo</i> - return the pseudo-translated source string, translated to the 7541 * script of the locale if the mapping is available, or just the default Latin 7542 * pseudo-translation if not 7543 * <li><i>empty</i> - return the empty string 7544 * </ul> 7545 * The default behaviour is the same as before, which is to return the source string 7546 * unchanged. 7547 * 7548 * <li><i>onLoad</i> - a callback function to call when the resources are fully 7549 * loaded. When the onLoad option is given, this class will attempt to 7550 * load any missing locale data using the ilib loader callback. 7551 * When the constructor is done (even if the data is already preassembled), the 7552 * onLoad function is called with the current instance as a parameter, so this 7553 * callback can be used with preassembled or dynamic loading or a mix of the two. 7554 * 7555 * <li>sync - tell whether to load any missing locale data synchronously or 7556 * asynchronously. If this option is given as "false", then the "onLoad" 7557 * callback must be given, as the instance returned from this constructor will 7558 * not be usable for a while. 7559 * 7560 * <li><i>loadParams</i> - an object containing parameters to pass to the 7561 * loader callback function when locale data is missing. The parameters are not 7562 * interpretted or modified in any way. They are simply passed along. The object 7563 * may contain any property/value pairs as long as the calling code is in 7564 * agreement with the loader callback function as to what those parameters mean. 7565 * </ul> 7566 * 7567 * The locale option may be given as a locale spec string or as an 7568 * Locale object. If the locale option is not specified, then strings for 7569 * the default locale will be loaded.<p> 7570 * 7571 * The name option can be used to put groups of strings together in a 7572 * single bundle. The strings will then appear together in a JS object in 7573 * a JS file that can be included before the ilib.<p> 7574 * 7575 * A resource bundle with a particular name is actually a set of bundles 7576 * that are each specific to a language, a language plus a region, etc. 7577 * All bundles with the same base name should 7578 * contain the same set of source strings, but with different translations for 7579 * the given locale. The user of the bundle does not need to be aware of 7580 * the locale of the bundle, as long as it contains values for the strings 7581 * it needs.<p> 7582 * 7583 * Strings in bundles for a particular locale are inherited from parent bundles 7584 * that are more generic. In general, the hierarchy is as follows (from 7585 * least locale-specific to most locale-specific): 7586 * 7587 * <ol> 7588 * <li> language 7589 * <li> region 7590 * <li> language_script 7591 * <li> language_region 7592 * <li> region_variant 7593 * <li> language_script_region 7594 * <li> language_region_variant 7595 * <li> language_script_region_variant 7596 * </ol> 7597 * 7598 * That is, if the translation for a string does not exist in the current 7599 * locale, the more-generic parent locale is searched for the string. In the 7600 * worst case scenario, the string is not found in the base locale's strings. 7601 * In this case, the missing option guides this class on what to do. If 7602 * the missing option is "source", then the original source is returned as 7603 * the translation. If it is "empty", the empty string is returned. If it 7604 * is "pseudo", then the pseudo-translated string that is appropriate for 7605 * the default script of the locale is returned.<p> 7606 * 7607 * This allows developers to create code with new or changed strings in it and check in that 7608 * code without waiting for the translations to be done first. The translated 7609 * version of the app or web site will still function properly, but will show 7610 * a spurious untranslated string here and there until the translations are 7611 * done and also checked in.<p> 7612 * 7613 * The base is whatever language your developers use to code in. For 7614 * a German web site, strings in the source code may be written in German 7615 * for example. Often this base is English, as many web sites are coded in 7616 * English, but that is not required.<p> 7617 * 7618 * The strings can be extracted with the ilib localization tool (which will be 7619 * shipped at some future time.) Once the strings 7620 * have been translated, the set of translated files can be generated with the 7621 * same tool. The output from the tool can be used as input to the ResBundle 7622 * object. It is up to the web page or app to make sure the JS file that defines 7623 * the bundle is included before creating the ResBundle instance.<p> 7624 * 7625 * A special locale "zxx-XX" is used as the pseudo-translation locale because 7626 * zxx means "no linguistic information" in the ISO 639 standard, and the region 7627 * code XX is defined to be user-defined in the ISO 3166 standard. 7628 * Pseudo-translation is a locale where the translations are generated on 7629 * the fly based on the contents of the source string. Characters in the source 7630 * string are replaced with other characters and returned. 7631 * 7632 * Example. If the source string is: 7633 * 7634 * <pre> 7635 * "This is a string" 7636 * </pre> 7637 * 7638 * then the pseudo-translated version might look something like this: 7639 * 7640 * <pre> 7641 * "Ţħïş ïş á şţřïñĝ" 7642 * </pre> 7643 * <p> 7644 * 7645 * Pseudo-translation can be used to test that your app or web site is translatable 7646 * before an actual translation has happened. These bugs can then be fixed 7647 * before the translation starts, avoiding an explosion of bugs later when 7648 * each language's tester registers the same bug complaining that the same 7649 * string is not translated. When pseudo-localizing with 7650 * the Latin script, this allows the strings to be readable in the UI in the 7651 * source language (if somewhat funky-looking), 7652 * so that a tester can easily verify that the string is properly externalized 7653 * and loaded from a resource bundle without the need to be able to read a 7654 * foreign language.<p> 7655 * 7656 * If one of a list of script tags is given in the pseudo-locale specifier, then the 7657 * pseudo-localization can map characters to very rough transliterations of 7658 * characters in the given script. For example, zxx-Hebr-XX maps strings to 7659 * Hebrew characters, which can be used to test your UI in a right-to-left 7660 * language to catch bidi bugs before a translation is done. Currently, the 7661 * list of target scripts includes Hebrew (Hebr), Chinese Simplified Han (Hans), 7662 * and Cyrillic (Cyrl) with more to be added later. If no script is explicitly 7663 * specified in the locale spec, or if the script is not supported, 7664 * then the default mapping maps Latin base characters to accented versions of 7665 * those Latin characters as in the example above. 7666 * 7667 * When the "lengthen" property is set to true in the options, the 7668 * pseudotranslation code will add digits to the end of the string to simulate 7669 * the lengthening that occurs when translating to other languages. The above 7670 * example will come out like this: 7671 * 7672 * <pre> 7673 * "Ţħïş ïş á şţřïñĝ76543210" 7674 * </pre> 7675 * 7676 * The string is lengthened according to the length of the source string. If 7677 * the source string is less than 20 characters long, the string is lengthened 7678 * by 50%. If the source string is 20-40 7679 * characters long, the string is lengthened by 33%. If te string is greater 7680 * than 40 characters long, the string is lengthened by 20%.<p> 7681 * 7682 * The pseudotranslation always ends a string with the digit "0". If you do 7683 * not see the digit "0" in the UI for your app, you know that truncation 7684 * has occurred, and the number you see at the end of the string tells you 7685 * how many characters were truncated.<p> 7686 * 7687 * 7688 * @constructor 7689 * @param {?Object} options Options controlling how the bundle is created 7690 */ 7691 var ResBundle = function (options) { 7692 var lookupLocale, spec; 7693 7694 this.locale = new Locale(); // use the default locale 7695 this.baseName = "strings"; 7696 this.type = "text"; 7697 this.loadParams = {}; 7698 this.missing = "source"; 7699 this.sync = true; 7700 7701 if (options) { 7702 if (options.locale) { 7703 this.locale = (typeof(options.locale) === 'string') ? 7704 new Locale(options.locale) : 7705 options.locale; 7706 } 7707 if (options.name) { 7708 this.baseName = options.name; 7709 } 7710 if (options.type) { 7711 this.type = options.type; 7712 } 7713 this.lengthen = options.lengthen || false; 7714 7715 if (typeof(options.sync) !== 'undefined') { 7716 this.sync = (options.sync == true); 7717 } 7718 7719 if (typeof(options.loadParams) !== 'undefined') { 7720 this.loadParams = options.loadParams; 7721 } 7722 if (typeof(options.missing) !== 'undefined') { 7723 if (options.missing === "pseudo" || options.missing === "empty") { 7724 this.missing = options.missing; 7725 } 7726 } 7727 } else { 7728 options = {}; 7729 } 7730 7731 this.map = {}; 7732 7733 if (!ResBundle[this.baseName]) { 7734 ResBundle[this.baseName] = {}; 7735 } 7736 7737 lookupLocale = this.locale.isPseudo() ? new Locale("en-US") : this.locale; 7738 7739 Utils.loadData({ 7740 object: ResBundle[this.baseName], 7741 locale: lookupLocale, 7742 name: this.baseName + ".json", 7743 sync: this.sync, 7744 loadParams: this.loadParams, 7745 callback: ilib.bind(this, function (map) { 7746 if (!map) { 7747 map = ilib.data[this.baseName] || {}; 7748 spec = lookupLocale.getSpec().replace(/-/g, '_'); 7749 ResBundle[this.baseName].cache[spec] = map; 7750 } 7751 this.map = map; 7752 if (this.locale.isPseudo()) { 7753 if (!ResBundle.pseudomap) { 7754 ResBundle.pseudomap = {}; 7755 } 7756 7757 this._loadPseudo(this.locale, options.onLoad); 7758 } else if (this.missing === "pseudo") { 7759 if (!ResBundle.pseudomap) { 7760 ResBundle.pseudomap = {}; 7761 } 7762 7763 new LocaleInfo(this.locale, { 7764 sync: this.sync, 7765 loadParams: this.loadParams, 7766 onLoad: ilib.bind(this, function (li) { 7767 var pseudoLocale = new Locale("zxx", "XX", undefined, li.getDefaultScript()); 7768 this._loadPseudo(pseudoLocale, options.onLoad); 7769 }) 7770 }); 7771 } else { 7772 if (typeof(options.onLoad) === 'function') { 7773 options.onLoad(this); 7774 } 7775 } 7776 }) 7777 }); 7778 7779 // console.log("Merged resources " + this.locale.toString() + " are: " + JSON.stringify(this.map)); 7780 //if (!this.locale.isPseudo() && JSUtils.isEmpty(this.map)) { 7781 // console.log("Resources for bundle " + this.baseName + " locale " + this.locale.toString() + " are not available."); 7782 //} 7783 }; 7784 7785 ResBundle.defaultPseudo = ilib.data.pseudomap || { 7786 "a": "à", 7787 "e": "ë", 7788 "i": "í", 7789 "o": "õ", 7790 "u": "ü", 7791 "y": "ÿ", 7792 "A": "Ã", 7793 "E": "Ë", 7794 "I": "Ï", 7795 "O": "Ø", 7796 "U": "Ú", 7797 "Y": "Ŷ" 7798 }; 7799 7800 ResBundle.prototype = { 7801 /** 7802 * @protected 7803 */ 7804 _loadPseudo: function (pseudoLocale, onLoad) { 7805 Utils.loadData({ 7806 object: ResBundle.pseudomap, 7807 locale: pseudoLocale, 7808 name: "pseudomap.json", 7809 sync: this.sync, 7810 loadParams: this.loadParams, 7811 callback: ilib.bind(this, function (map) { 7812 if (!map || JSUtils.isEmpty(map)) { 7813 map = ResBundle.defaultPseudo; 7814 var spec = pseudoLocale.getSpec().replace(/-/g, '_'); 7815 ResBundle.pseudomap.cache[spec] = map; 7816 } 7817 this.pseudomap = map; 7818 if (typeof(onLoad) === 'function') { 7819 onLoad(this); 7820 } 7821 }) 7822 }); 7823 }, 7824 7825 /** 7826 * Return the locale of this resource bundle. 7827 * @return {Locale} the locale of this resource bundle object 7828 */ 7829 getLocale: function () { 7830 return this.locale; 7831 }, 7832 7833 /** 7834 * Return the name of this resource bundle. This corresponds to the name option 7835 * given to the constructor. 7836 * @return {string} name of the the current instance 7837 */ 7838 getName: function () { 7839 return this.baseName; 7840 }, 7841 7842 /** 7843 * Return the type of this resource bundle. This corresponds to the type option 7844 * given to the constructor. 7845 * @return {string} type of the the current instance 7846 */ 7847 getType: function () { 7848 return this.type; 7849 }, 7850 7851 percentRE: new RegExp("%(\\d+\\$)?([\\-#\\+ 0,\\(])?(\\d+)?(\\.\\d+)?[bBhHsScCdoxXeEfgGaAtT%n]"), 7852 7853 /** 7854 * @private 7855 * Pseudo-translate a string 7856 */ 7857 _pseudo: function (str) { 7858 if (!str) { 7859 return undefined; 7860 } 7861 var ret = "", i; 7862 for (i = 0; i < str.length; i++) { 7863 if (this.type !== "raw") { 7864 if (this.type === "html" || this.type === "xml") { 7865 if (str.charAt(i) === '<') { 7866 ret += str.charAt(i++); 7867 while (i < str.length && str.charAt(i) !== '>') { 7868 ret += str.charAt(i++); 7869 } 7870 } else if (str.charAt(i) === '&') { 7871 ret += str.charAt(i++); 7872 while (i < str.length && str.charAt(i) !== ';' && str.charAt(i) !== ' ') { 7873 ret += str.charAt(i++); 7874 } 7875 } else if (str.charAt(i) === '\\' && str.charAt(i+1) === "u") { 7876 ret += str.substring(i, i+6); 7877 i += 6; 7878 } 7879 } else if (this.type === "c") { 7880 if (str.charAt(i) === "%") { 7881 var m = this.percentRE.exec(str.substring(i)); 7882 if (m && m.length) { 7883 // console.log("Match found: " + JSON.stringify(m[0].replace("%", "%%"))); 7884 ret += m[0]; 7885 i += m[0].length; 7886 } 7887 } 7888 7889 } 7890 if (i < str.length) { 7891 if (str.charAt(i) === '{') { 7892 ret += str.charAt(i++); 7893 while (i < str.length && str.charAt(i) !== '}') { 7894 ret += str.charAt(i++); 7895 } 7896 if (i < str.length) { 7897 ret += str.charAt(i); 7898 } 7899 } else { 7900 ret += this.pseudomap[str.charAt(i)] || str.charAt(i); 7901 } 7902 } 7903 } else { 7904 ret += this.pseudomap[str.charAt(i)] || str.charAt(i); 7905 } 7906 } 7907 if (this.lengthen) { 7908 var add; 7909 if (ret.length <= 20) { 7910 add = Math.round(ret.length / 2); 7911 } else if (ret.length > 20 && ret.length <= 40) { 7912 add = Math.round(ret.length / 3); 7913 } else { 7914 add = Math.round(ret.length / 5); 7915 } 7916 for (i = add-1; i >= 0; i--) { 7917 ret += (i % 10); 7918 } 7919 } 7920 if (this.locale.getScript() === "Hans" || this.locale.getScript() === "Hant" || 7921 this.locale.getScript() === "Hani" || 7922 this.locale.getScript() === "Hrkt" || this.locale.getScript() === "Jpan" || 7923 this.locale.getScript() === "Hira" || this.locale.getScript() === "Kana" ) { 7924 // simulate Asian languages by getting rid of all the spaces 7925 ret = ret.replace(/ /g, ""); 7926 } 7927 return ret; 7928 }, 7929 7930 /** 7931 * @private 7932 * Escape html characters in the output. 7933 */ 7934 _escapeXml: function (str) { 7935 str = str.replace(/&/g, '&'); 7936 str = str.replace(/</g, '<'); 7937 str = str.replace(/>/g, '>'); 7938 return str; 7939 }, 7940 7941 /** 7942 * @private 7943 * @param {string} str the string to unescape 7944 */ 7945 _unescapeXml: function (str) { 7946 str = str.replace(/&/g, '&'); 7947 str = str.replace(/</g, '<'); 7948 str = str.replace(/>/g, '>'); 7949 return str; 7950 }, 7951 7952 /** 7953 * @private 7954 * Create a key name out of a source string. All this does so far is 7955 * compress sequences of white space into a single space on the assumption 7956 * that this doesn't really change the meaning of the string, and therefore 7957 * all such strings that compress to the same thing should share the same 7958 * translation. 7959 * @param {null|string=} source the source string to make a key out of 7960 */ 7961 _makeKey: function (source) { 7962 if (!source) return undefined; 7963 var key = source.replace(/\s+/gm, ' '); 7964 return (this.type === "xml" || this.type === "html") ? this._unescapeXml(key) : key; 7965 }, 7966 7967 /** 7968 * Return a localized string. If the string is not found in the loaded set of 7969 * resources, the original source string is returned. If the key is not given, 7970 * then the source string itself is used as the key. In the case where the 7971 * source string is used as the key, the whitespace is compressed down to 1 space 7972 * each, and the whitespace at the beginning and end of the string is trimmed.<p> 7973 * 7974 * The escape mode specifies what type of output you are escaping the returned 7975 * string for. Modes are similar to the types: 7976 * 7977 * <ul> 7978 * <li>"html" -- prevents HTML injection by escaping the characters < > and & 7979 * <li>"xml" -- currently same as "html" mode 7980 * <li>"js" -- prevents breaking Javascript syntax by backslash escaping all quote and 7981 * double-quote characters 7982 * <li>"attribute" -- meant for HTML attribute values. Currently this is the same as 7983 * "js" escape mode. 7984 * <li>"default" -- use the type parameter from the constructor as the escape mode as well 7985 * <li>"none" or undefined -- no escaping at all. 7986 * </ul> 7987 * 7988 * The type parameter of the constructor specifies what type of strings this bundle 7989 * is operating upon. This allows pseudo-translation and automatic key generation 7990 * to happen properly by telling this class how to parse the string. The escape mode 7991 * for this method is different in that it specifies how this string will be used in 7992 * the calling code and therefore how to escape it properly.<p> 7993 * 7994 * For example, a section of Javascript code may be constructing an HTML snippet in a 7995 * string to add to the web page. In this case, the type parameter in the constructor should 7996 * be "html" so that the source string can be parsed properly, but the escape mode should 7997 * be "js" so that the output string can be used in Javascript without causing syntax 7998 * errors. 7999 * 8000 * @param {?string=} source the source string to translate 8001 * @param {?string=} key optional name of the key, if any 8002 * @param {?string=} escapeMode escape mode, if any 8003 * @return {IString|undefined} the translation of the given source/key or undefined 8004 * if the translation is not found and the source is undefined 8005 */ 8006 getString: function (source, key, escapeMode) { 8007 if (!source && !key) return new IString(""); 8008 8009 var trans; 8010 if (this.locale.isPseudo()) { 8011 var str = source ? source : this.map[key]; 8012 trans = this._pseudo(str || key); 8013 } else { 8014 var keyName = key || this._makeKey(source); 8015 if (typeof(this.map[keyName]) !== 'undefined') { 8016 trans = this.map[keyName]; 8017 } else if (this.missing === "pseudo") { 8018 trans = this._pseudo(source || key); 8019 } else if (this.missing === "empty") { 8020 trans = ""; 8021 } else { 8022 trans = source; 8023 } 8024 } 8025 8026 if (escapeMode && escapeMode !== "none") { 8027 if (escapeMode == "default") { 8028 escapeMode = this.type; 8029 } 8030 if (escapeMode === "xml" || escapeMode === "html") { 8031 trans = this._escapeXml(trans); 8032 } else if (escapeMode == "js" || escapeMode === "attribute") { 8033 trans = trans.replace(/'/g, "\\\'").replace(/"/g, "\\\""); 8034 } 8035 } 8036 if (trans === undefined) { 8037 return undefined; 8038 } else { 8039 var ret = new IString(trans); 8040 ret.setLocale(this.locale.getSpec(), true, this.loadParams); // no callback 8041 return ret; 8042 } 8043 }, 8044 8045 /** 8046 * Return a localized string as a Javascript object. This does the same thing as 8047 * the getString() method, but it returns a regular Javascript string instead of 8048 * and IString instance. This means it cannot be formatted with the format() 8049 * method without being wrapped in an IString instance first. 8050 * 8051 * @param {?string=} source the source string to translate 8052 * @param {?string=} key optional name of the key, if any 8053 * @param {?string=} escapeMode escape mode, if any 8054 * @return {string|undefined} the translation of the given source/key or undefined 8055 * if the translation is not found and the source is undefined 8056 */ 8057 getStringJS: function(source, key, escapeMode) { 8058 return this.getString(source, key, escapeMode).toString(); 8059 }, 8060 8061 /** 8062 * Return true if the current bundle contains a translation for the given key and 8063 * source. The 8064 * getString method will always return a string for any given key and source 8065 * combination, so it cannot be used to tell if a translation exists. Either one 8066 * or both of the source and key must be specified. If both are not specified, 8067 * this method will return false. 8068 * 8069 * @param {?string=} source source string to look up 8070 * @param {?string=} key key to look up 8071 * @return {boolean} true if this bundle contains a translation for the key, and 8072 * false otherwise 8073 */ 8074 containsKey: function(source, key) { 8075 if (typeof(source) === 'undefined' && typeof(key) === 'undefined') { 8076 return false; 8077 } 8078 8079 var keyName = key || this._makeKey(source); 8080 return typeof(this.map[keyName]) !== 'undefined'; 8081 }, 8082 8083 /** 8084 * Return the merged resources as an entire object. When loading resources for a 8085 * locale that are not just a set of translated strings, but instead an entire 8086 * structured javascript object, you can gain access to that object via this call. This method 8087 * will ensure that all the of the parts of the object are correct for the locale.<p> 8088 * 8089 * For pre-assembled data, it starts by loading <i>ilib.data[name]</i>, where 8090 * <i>name</i> is the base name for this set of resources. Then, it successively 8091 * merges objects in the base data using progressively more locale-specific data. 8092 * It loads it in this order from <i>ilib.data</i>: 8093 * 8094 * <ol> 8095 * <li> language 8096 * <li> region 8097 * <li> language_script 8098 * <li> language_region 8099 * <li> region_variant 8100 * <li> language_script_region 8101 * <li> language_region_variant 8102 * <li> language_script_region_variant 8103 * </ol> 8104 * 8105 * For dynamically loaded data, the code attempts to load the same sequence as 8106 * above, but with slash path separators instead of underscores.<p> 8107 * 8108 * Loading the resources this way allows the program to share resources between all 8109 * locales that share a common language, region, or script. As a 8110 * general rule-of-thumb, resources should be as generic as possible in order to 8111 * cover as many locales as possible. 8112 * 8113 * @return {Object} returns the object that is the basis for this resources instance 8114 */ 8115 getResObj: function () { 8116 return this.map; 8117 } 8118 }; 8119 8120 8121 8122 /*< ISet.js */ 8123 /* 8124 * ISet.js - ilib Set class definition for platforms older than ES6 8125 * 8126 * Copyright © 2015, JEDLSoft 8127 * 8128 * Licensed under the Apache License, Version 2.0 (the "License"); 8129 * you may not use this file except in compliance with the License. 8130 * You may obtain a copy of the License at 8131 * 8132 * http://www.apache.org/licenses/LICENSE-2.0 8133 * 8134 * Unless required by applicable law or agreed to in writing, software 8135 * distributed under the License is distributed on an "AS IS" BASIS, 8136 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8137 * 8138 * See the License for the specific language governing permissions and 8139 * limitations under the License. 8140 */ 8141 8142 /** 8143 * Create a new set with elements in the given array. The type of 8144 * the set is gleaned from the type of the first element in the 8145 * elements array, or the first element added to the set. The type 8146 * may be "string" or "number", and all elements will be returned 8147 * as elements of that type. 8148 * 8149 * @class 8150 * @param {Array.<string|number>=} elements initial elements to add to the set 8151 * @constructor 8152 */ 8153 var ISet = function(elements) { 8154 this.elements = {}; 8155 8156 if (elements && elements.length) { 8157 for (var i = 0; i < elements.length; i++) { 8158 this.elements[elements[i]] = true; 8159 } 8160 8161 this.type = typeof(elements[0]); 8162 } 8163 }; 8164 8165 /** 8166 * @private 8167 */ 8168 ISet.prototype._addOne = function(element) { 8169 if (this.isEmpty()) { 8170 this.type = typeof(element); 8171 } 8172 8173 if (!this.elements[element]) { 8174 this.elements[element] = true; 8175 return true; 8176 } 8177 8178 return false; 8179 }; 8180 8181 /** 8182 * Adds the specified element or array of elements to this set if it is or they are not 8183 * already present. 8184 * 8185 * @param {*|Array.<*>} element element or array of elements to add 8186 * @return {boolean} true if this set did not already contain the specified element[s] 8187 */ 8188 ISet.prototype.add = function(element) { 8189 var ret = false; 8190 8191 if (typeof(element) === "object") { 8192 for (var i = 0; i < element.length; i++) { 8193 ret = this._addOne(element[i]) || ret; 8194 } 8195 } else { 8196 ret = this._addOne(element); 8197 } 8198 8199 return ret; 8200 }; 8201 8202 /** 8203 * Removes all of the elements from this set. 8204 */ 8205 ISet.prototype.clear = function() { 8206 this.elements = {}; 8207 }; 8208 8209 /** 8210 * Returns true if this set contains the specified element. 8211 * @param {*} element the element to test 8212 * @return {boolean} 8213 */ 8214 ISet.prototype.contains = function(element) { 8215 return this.elements[element] || false; 8216 }; 8217 8218 /** 8219 * Returns true if this set contains no elements. 8220 * @return {boolean} 8221 */ 8222 ISet.prototype.isEmpty = function() { 8223 return (Object.keys(this.elements).length === 0); 8224 }; 8225 8226 /** 8227 * Removes the specified element from this set if it is present. 8228 * @param {*} element the element to remove 8229 * @return {boolean} true if the set contained the specified element 8230 */ 8231 ISet.prototype.remove = function(element) { 8232 if (this.elements[element]) { 8233 delete this.elements[element]; 8234 return true; 8235 } 8236 8237 return false; 8238 }; 8239 8240 /** 8241 * Return the set as a javascript array. 8242 * @return {Array.<*>} the set represented as a javascript array 8243 */ 8244 ISet.prototype.asArray = function() { 8245 var keys = Object.keys(this.elements); 8246 8247 // keys is an array of strings. Convert to numbers if necessary 8248 if (this.type === "number") { 8249 var tmp = []; 8250 for (var i = 0; i < keys.length; i++) { 8251 tmp.push(Number(keys[i]).valueOf()); 8252 } 8253 keys = tmp; 8254 } 8255 8256 return keys; 8257 }; 8258 8259 /** 8260 * Represents the current set as json. 8261 * @return {string} the current set represented as json 8262 */ 8263 ISet.prototype.toJson = function() { 8264 return JSON.stringify(this.asArray()); 8265 }; 8266 8267 /** 8268 * Convert to a javascript representation of this object. 8269 * In this case, it is a normal JS array. 8270 * @return {*} the JS representation of this object 8271 */ 8272 ISet.prototype.toJS = function() { 8273 return this.asArray(); 8274 }; 8275 8276 /** 8277 * Convert from a js representation to an internal one. 8278 * @return {ISet|undefined} the current object, or undefined if the conversion did not work 8279 */ 8280 ISet.prototype.fromJS = function(obj) { 8281 return this.add(obj) ? this : undefined; 8282 }; 8283 8284 8285 8286 /*< DateFmt.js */ 8287 /* 8288 * DateFmt.js - Date formatter definition 8289 * 8290 * Copyright © 2012-2015, JEDLSoft 8291 * 8292 * Licensed under the Apache License, Version 2.0 (the "License"); 8293 * you may not use this file except in compliance with the License. 8294 * You may obtain a copy of the License at 8295 * 8296 * http://www.apache.org/licenses/LICENSE-2.0 8297 * 8298 * Unless required by applicable law or agreed to in writing, software 8299 * distributed under the License is distributed on an "AS IS" BASIS, 8300 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8301 * 8302 * See the License for the specific language governing permissions and 8303 * limitations under the License. 8304 */ 8305 8306 /* 8307 !depends 8308 ilib.js 8309 Locale.js 8310 IDate.js 8311 DateFactory.js 8312 IString.js 8313 ResBundle.js 8314 Calendar.js 8315 CalendarFactory.js 8316 LocaleInfo.js 8317 TimeZone.js 8318 GregorianCal.js 8319 JSUtils.js 8320 Utils.js 8321 ISet.js 8322 */ 8323 8324 // !data dateformats sysres 8325 8326 8327 8328 8329 8330 8331 /** 8332 * @class 8333 * Create a new date formatter instance. The date formatter is immutable once 8334 * it is created, but can format as many different dates as needed with the same 8335 * options. Create different date formatter instances for different purposes 8336 * and then keep them cached for use later if you have more than one date to 8337 * format.<p> 8338 * 8339 * The options may contain any of the following properties: 8340 * 8341 * <ul> 8342 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 8343 * not specified, then the default locale of the app or web page will be used. 8344 * 8345 * <li><i>calendar</i> - the type of calendar to use for this format. The value should 8346 * be a sting containing the name of the calendar. Currently, the supported 8347 * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the 8348 * calendar is not specified, then the default calendar for the locale is used. When the 8349 * calendar type is specified, then the format method must be called with an instance of 8350 * the appropriate date type. (eg. Gregorian calendar means that the format method must 8351 * be called with a GregDate instance.) 8352 * 8353 * <li><i>timezone</i> - time zone to use when formatting times. This may be a time zone 8354 * instance or a time zone specifier from the IANA list of time zone database names 8355 * (eg. "America/Los_Angeles"), 8356 * the string "local", or a string specifying the offset in RFC 822 format. The IANA 8357 * list of time zone names can be viewed at 8358 * <a href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">this page</a>. 8359 * If the time zone is given as "local", the offset from UTC as given by 8360 * the Javascript system is used. If the offset is given as an RFC 822 style offset 8361 * specifier, it will parse that string and use the resulting offset. If the time zone 8362 * is not specified, the 8363 * default time zone for the locale is used. If both the date object and this formatter 8364 * instance contain time zones and those time zones are different from each other, the 8365 * formatter will calculate the offset between the time zones and subtract it from the 8366 * date before formatting the result for the current time zone. The theory is that a date 8367 * object that contains a time zone specifies a specific instant in time that is valid 8368 * around the world, whereas a date object without one is a local time and can only be 8369 * used for doing things in the local time zone of the user. 8370 * 8371 * <li><i>type</i> - Specify whether this formatter should format times only, dates only, or 8372 * both times and dates together. Valid values are "time", "date", and "datetime". Note that 8373 * in some locales, the standard format uses the order "time followed by date" and in others, 8374 * the order is exactly opposite, so it is better to create a single "datetime" formatter 8375 * than it is to create a time formatter and a date formatter separately and concatenate the 8376 * results. A "datetime" formatter will get the order correct for the locale.<p> 8377 * 8378 * The default type if none is specified in with the type option is "date". 8379 * 8380 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 8381 * formatted string. 8382 * 8383 * <ul> 8384 * <li><i>short</i> - use a short representation of the time. This is the most compact format possible for the locale. 8385 * <li><i>medium</i> - use a medium length representation of the time. This is a slightly longer format. 8386 * <li><i>long</i> - use a long representation of the time. This is a fully specified format, but some of the textual 8387 * components may still be abbreviated 8388 * <li><i>full</i> - use a full representation of the time. This is a fully specified format where all the textual 8389 * components are spelled out completely 8390 * </ul> 8391 * 8392 * eg. The "short" format for an en_US date may be "MM/dd/yy", whereas the long format might be "d MMM, yyyy". In the long 8393 * format, the month name is textual instead of numeric and is longer, the year is 4 digits instead of 2, and the format 8394 * contains slightly more spaces and formatting characters.<p> 8395 * 8396 * Note that the length parameter does not specify which components are to be formatted. Use the "date" and the "time" 8397 * properties to specify the components. Also, very few of the components of a time format differ according to the length, 8398 * so this property has little to no affect on time formatting. 8399 * 8400 * <li><i>date</i> - This property tells 8401 * which components of a date format to use. For example, 8402 * sometimes you may wish to format a date that only contains the month and date 8403 * without the year, such as when displaying a person's yearly birthday. The value 8404 * of this property allows you to specify only those components you want to see in the 8405 * final output, ordered correctly for the locale. <p> 8406 * 8407 * Valid values are: 8408 * 8409 * <ul> 8410 * <li><i>dmwy</i> - format all components, weekday, date, month, and year 8411 * <li><i>dmy</i> - format only date, month, and year 8412 * <li><i>dmw</i> - format only weekday, date, and month 8413 * <li><i>dm</i> - format only date and month 8414 * <li><i>my</i> - format only month and year 8415 * <li><i>dw</i> - format only the weekday and date 8416 * <li><i>d</i> - format only the date 8417 * <li><i>m</i> - format only the month, in numbers for shorter lengths, and letters for 8418 * longer lengths 8419 * <li><i>n</i> - format only the month, in letters only for all lengths 8420 * <li><i>y</i> - format only the year 8421 * </ul> 8422 * Default components, if this property is not specified, is "dmy". This property may be specified 8423 * but has no affect if the current formatter is for times only.<p> 8424 * 8425 * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you 8426 * get from <a href="http://icu-project.org/apiref/icu4c432/classDateTimePatternGenerator.html#aa30c251609c1eea5ad60c95fc497251e">DateTimePatternGenerator.getSkeleton()</a>. 8427 * It will not extract the length from the skeleton so you still need to pass the length property, 8428 * but it will extract the date components. 8429 * 8430 * <li><i>time</i> - This property gives which components of a time format to use. The time will be formatted 8431 * correctly for the locale with only the time components requested. For example, a clock might only display 8432 * the hour and minute and not need the seconds or the am/pm component. In this case, the time property should be set 8433 * to "hm". <p> 8434 * 8435 * Valid values for this property are: 8436 * 8437 * <ul> 8438 * <li><i>ahmsz</i> - format the hours, minutes, seconds, am/pm (if using a 12 hour clock), and the time zone 8439 * <li><i>ahms</i> - format the hours, minutes, seconds, and am/pm (if using a 12 hour clock) 8440 * <li><i>hmsz</i> - format the hours, minutes, seconds, and the time zone 8441 * <li><i>hms</i> - format the hours, minutes, and seconds 8442 * <li><i>ahmz</i> - format the hours, minutes, am/pm (if using a 12 hour clock), and the time zone 8443 * <li><i>ahm</i> - format the hours, minutes, and am/pm (if using a 12 hour clock) 8444 * <li><i>hmz</i> - format the hours, minutes, and the time zone 8445 * <li><i>ah</i> - format only the hours and am/pm if using a 12 hour clock 8446 * <li><i>hm</i> - format only the hours and minutes 8447 * <li><i>ms</i> - format only the minutes and seconds 8448 * <li><i>h</i> - format only the hours 8449 * <li><i>m</i> - format only the minutes 8450 * <li><i>s</i> - format only the seconds 8451 * </ul> 8452 * 8453 * If you want to format a length of time instead of a particular instant 8454 * in time, use the duration formatter object (DurationFmt) instead because this 8455 * formatter is geared towards instants. A date formatter will make sure that each component of the 8456 * time is within the normal range 8457 * for that component. That is, the minutes will always be between 0 and 59, no matter 8458 * what is specified in the date to format. A duration format will allow the number 8459 * of minutes to exceed 59 if, for example, you were displaying the length of 8460 * a movie of 198 minutes.<p> 8461 * 8462 * Default value if this property is not specified is "hma".<p> 8463 * 8464 * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you 8465 * get from <a href="http://icu-project.org/apiref/icu4c432/classDateTimePatternGenerator.html#aa30c251609c1eea5ad60c95fc497251e">DateTimePatternGenerator.getSkeleton()</a>. 8466 * It will not extract the length from the skeleton so you still need to pass the length property, 8467 * but it will extract the time components. 8468 * 8469 * <li><i>clock</i> - specify that the time formatter should use a 12 or 24 hour clock. 8470 * Valid values are "12" and "24".<p> 8471 * 8472 * In some locales, both clocks are used. For example, in en_US, the general populace uses 8473 * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or 8474 * scientific writing, it is more common to use a 24 hour clock. This property allows you to 8475 * construct a formatter that overrides the default for the locale.<p> 8476 * 8477 * If this property is not specified, the default is to use the most widely used convention 8478 * for the locale. 8479 * 8480 * <li><i>template</i> - use the given template string as a fixed format when formatting 8481 * the date/time. Valid codes to use in a template string are as follows: 8482 * 8483 * <ul> 8484 * <li><i>a</i> - am/pm marker 8485 * <li><i>d</i> - 1 or 2 digit date of month, not padded 8486 * <li><i>dd</i> - 1 or 2 digit date of month, 0 padded to 2 digits 8487 * <li><i>O</i> - ordinal representation of the date of month (eg. "1st", "2nd", etc.) 8488 * <li><i>D</i> - 1 to 3 digit day of year 8489 * <li><i>DD</i> - 1 to 3 digit day of year, 0 padded to 2 digits 8490 * <li><i>DDD</i> - 1 to 3 digit day of year, 0 padded to 3 digits 8491 * <li><i>M</i> - 1 or 2 digit month number, not padded 8492 * <li><i>MM</i> - 1 or 2 digit month number, 0 padded to 2 digits 8493 * <li><i>N</i> - 1 character month name abbreviation 8494 * <li><i>NN</i> - 2 character month name abbreviation 8495 * <li><i>MMM</i> - 3 character month month name abbreviation 8496 * <li><i>MMMM</i> - fully spelled out month name 8497 * <li><i>yy</i> - 2 digit year 8498 * <li><i>yyyy</i> - 4 digit year 8499 * <li><i>E</i> - day-of-week name, abbreviated to a single character 8500 * <li><i>EE</i> - day-of-week name, abbreviated to a max of 2 characters 8501 * <li><i>EEE</i> - day-of-week name, abbreviated to a max of 3 characters 8502 * <li><i>EEEE</i> - day-of-week name fully spelled out 8503 * <li><i>G</i> - era designator 8504 * <li><i>w</i> - week number in year 8505 * <li><i>ww</i> - week number in year, 0 padded to 2 digits 8506 * <li><i>W</i> - week in month 8507 * <li><i>h</i> - hour (12 followed by 1 to 11) 8508 * <li><i>hh</i> - hour (12, followed by 1 to 11), 0 padded to 2 digits 8509 * <li><i>k</i> - hour (1 to 24) 8510 * <li><i>kk</i> - hour (1 to 24), 0 padded to 2 digits 8511 * <li><i>H</i> - hour (0 to 23) 8512 * <li><i>HH</i> - hour (0 to 23), 0 padded to 2 digits 8513 * <li><i>K</i> - hour (0 to 11) 8514 * <li><i>KK</i> - hour (0 to 11), 0 padded to 2 digits 8515 * <li><i>m</i> - minute in hour 8516 * <li><i>mm</i> - minute in hour, 0 padded to 2 digits 8517 * <li><i>s</i> - second in minute 8518 * <li><i>ss</i> - second in minute, 0 padded to 2 digits 8519 * <li><i>S</i> - millisecond (1 to 3 digits) 8520 * <li><i>SSS</i> - millisecond, 0 padded to 3 digits 8521 * <li><i>z</i> - general time zone 8522 * <li><i>Z</i> - RFC 822 time zone 8523 * </ul> 8524 * 8525 * <li><i>useNative</i> - the flag used to determine whether to use the native script settings 8526 * for formatting the numbers. 8527 * 8528 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 8529 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 8530 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 8531 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 8532 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 8533 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 8534 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 8535 * when formatting dates in the Gregorian calendar. 8536 * 8537 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 8538 * loaded. When the onLoad option is given, the DateFmt object will attempt to 8539 * load any missing locale data using the ilib loader callback. 8540 * When the constructor is done (even if the data is already preassembled), the 8541 * onLoad function is called with the current instance as a parameter, so this 8542 * callback can be used with preassembled or dynamic loading or a mix of the two. 8543 * 8544 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 8545 * asynchronously. If this option is given as "false", then the "onLoad" 8546 * callback must be given, as the instance returned from this constructor will 8547 * not be usable for a while. 8548 * 8549 * <li><i>loadParams</i> - an object containing parameters to pass to the 8550 * loader callback function when locale data is missing. The parameters are not 8551 * interpretted or modified in any way. They are simply passed along. The object 8552 * may contain any property/value pairs as long as the calling code is in 8553 * agreement with the loader callback function as to what those parameters mean. 8554 * </ul> 8555 * 8556 * Any substring containing letters within single or double quotes will be used 8557 * as-is in the final output and will not be interpretted for codes as above.<p> 8558 * 8559 * Example: a date format in Spanish might be given as: "'El' d. 'de' MMMM", where 8560 * the 'El' and the 'de' are left as-is in the output because they are quoted. Typical 8561 * output for this example template might be, "El 5. de Mayo". 8562 * 8563 * The following options will be used when formatting a date/time with an explicit 8564 * template: 8565 * 8566 * <ul> 8567 * <li>locale - the locale is only used for 8568 * translations of things like month names or day-of-week names. 8569 * <li>calendar - used to translate a date instance into date/time component values 8570 * that can be formatted into the template 8571 * <li>timezone - used to figure out the offset to add or subtract from the time to 8572 * get the final time component values 8573 * <li>clock - used to figure out whether to format times with a 12 or 24 hour clock. 8574 * If this option is specified, it will override the hours portion of a time format. 8575 * That is, "hh" is switched with "HH" and "kk" is switched with "KK" as appropriate. 8576 * If this option is not specified, the 12/24 code in the template will dictate whether 8577 * to use the 12 or 24 clock, and the 12/24 default in the locale will be ignored. 8578 * </ul> 8579 * 8580 * All other options will be ignored and their corresponding getter methods will 8581 * return the empty string.<p> 8582 * 8583 * 8584 * @constructor 8585 * @param {Object} options options governing the way this date formatter instance works 8586 */ 8587 var DateFmt = function(options) { 8588 var arr, i, bad, 8589 sync = true, 8590 loadParams = undefined; 8591 8592 this.locale = new Locale(); 8593 this.type = "date"; 8594 this.length = "s"; 8595 this.dateComponents = "dmy"; 8596 this.timeComponents = "ahm"; 8597 this.meridiems = "default"; 8598 8599 if (options) { 8600 if (options.locale) { 8601 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 8602 } 8603 8604 if (options.type) { 8605 if (options.type === 'date' || options.type === 'time' || options.type === 'datetime') { 8606 this.type = options.type; 8607 } 8608 } 8609 8610 if (options.calendar) { 8611 this.calName = options.calendar; 8612 } 8613 8614 if (options.length) { 8615 if (options.length === 'short' || 8616 options.length === 'medium' || 8617 options.length === 'long' || 8618 options.length === 'full') { 8619 // only use the first char to save space in the json files 8620 this.length = options.length.charAt(0); 8621 } 8622 } 8623 8624 if (options.date) { 8625 arr = options.date.split(""); 8626 var dateComps = new ISet(); 8627 bad = false; 8628 for (i = 0; i < arr.length; i++) { 8629 var c = arr[i].toLowerCase(); 8630 if (c === "e") c = "w"; // map ICU -> ilib 8631 if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'n') { 8632 // ignore time components and the era 8633 if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z' && c !== 'g') { 8634 bad = true; 8635 break; 8636 } 8637 } else { 8638 dateComps.add(c); 8639 } 8640 } 8641 if (!bad) { 8642 var comps = dateComps.asArray().sort(function (left, right) { 8643 return (left < right) ? -1 : ((right < left) ? 1 : 0); 8644 }); 8645 this.dateComponents = comps.join(""); 8646 } 8647 } 8648 8649 if (options.time) { 8650 arr = options.time.split(""); 8651 var timeComps = new ISet(); 8652 this.badTime = false; 8653 for (i = 0; i < arr.length; i++) { 8654 var c = arr[i].toLowerCase(); 8655 if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z') { 8656 // ignore the date components 8657 if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'e' && c !== 'n' && c !== 'g') { 8658 this.badTime = true; 8659 break; 8660 } 8661 } else { 8662 timeComps.add(c); 8663 } 8664 } 8665 if (!this.badTime) { 8666 var comps = timeComps.asArray().sort(function (left, right) { 8667 return (left < right) ? -1 : ((right < left) ? 1 : 0); 8668 }); 8669 this.timeComponents = comps.join(""); 8670 } 8671 } 8672 8673 if (options.clock && (options.clock === '12' || options.clock === '24')) { 8674 this.clock = options.clock; 8675 } 8676 8677 if (options.template) { 8678 // many options are not useful when specifying the template directly, so zero 8679 // them out. 8680 this.type = ""; 8681 this.length = ""; 8682 this.dateComponents = ""; 8683 this.timeComponents = ""; 8684 8685 this.template = options.template; 8686 } 8687 8688 if (options.timezone) { 8689 if (options.timezone instanceof TimeZone) { 8690 this.tz = options.timezone; 8691 } else { 8692 this.tz = new TimeZone({ 8693 locale: this.locale, 8694 id: options.timezone 8695 }); 8696 } 8697 } else if (options.locale) { 8698 // if an explicit locale was given, then get the time zone for that locale 8699 this.tz = new TimeZone({ 8700 locale: this.locale 8701 }); 8702 } // else just assume time zone "local" 8703 8704 if (typeof(options.useNative) === 'boolean') { 8705 this.useNative = options.useNative; 8706 } 8707 8708 if (typeof(options.meridiems) !== 'undefined' && 8709 (options.meridiems === "chinese" || 8710 options.meridiems === "gregorian" || 8711 options.meridiems === "ethiopic")) { 8712 this.meridiems = options.meridiems; 8713 } 8714 8715 if (typeof(options.sync) !== 'undefined') { 8716 sync = (options.sync === true); 8717 } 8718 8719 loadParams = options.loadParams; 8720 } 8721 8722 if (!DateFmt.cache) { 8723 DateFmt.cache = {}; 8724 } 8725 8726 new LocaleInfo(this.locale, { 8727 sync: sync, 8728 loadParams: loadParams, 8729 onLoad: ilib.bind(this, function (li) { 8730 this.locinfo = li; 8731 8732 // get the default calendar name from the locale, and if the locale doesn't define 8733 // one, use the hard-coded gregorian as the last resort 8734 this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; 8735 if (ilib.isDynCode()) { 8736 // If we are running in the dynamic code loading assembly of ilib, the following 8737 // will attempt to dynamically load the calendar date class for this calendar. If 8738 // it doesn't work, this just goes on and it will use Gregorian instead. 8739 DateFactory._dynLoadDate(this.calName); 8740 } 8741 8742 this.cal = CalendarFactory({ 8743 type: this.calName 8744 }); 8745 if (!this.cal) { 8746 this.cal = new GregorianCal(); 8747 } 8748 if (this.meridiems === "default") { 8749 this.meridiems = li.getMeridiemsStyle(); 8750 } 8751 8752 /* 8753 if (this.timeComponents && 8754 (this.clock === '24' || 8755 (!this.clock && this.locinfo.getClock() === "24"))) { 8756 // make sure we don't have am/pm in 24 hour mode unless the user specifically 8757 // requested it in the time component option 8758 this.timeComponents = this.timeComponents.replace("a", ""); 8759 } 8760 */ 8761 8762 // load the strings used to translate the components 8763 new ResBundle({ 8764 locale: this.locale, 8765 name: "sysres", 8766 sync: sync, 8767 loadParams: loadParams, 8768 onLoad: ilib.bind(this, function (rb) { 8769 this.sysres = rb; 8770 8771 if (!this.template) { 8772 Utils.loadData({ 8773 object: DateFmt, 8774 locale: this.locale, 8775 name: "dateformats.json", 8776 sync: sync, 8777 loadParams: loadParams, 8778 callback: ilib.bind(this, function (formats) { 8779 var spec = this.locale.getSpec().replace(/-/g, '_'); 8780 if (!formats) { 8781 formats = ilib.data.dateformats || DateFmt.defaultFmt; 8782 DateFmt.cache[spec] = formats; 8783 } 8784 /* 8785 if (!ilib.data.dateformats) { 8786 ilib.data.dateformats = {}; 8787 } 8788 if (!ilib.data.dateformats[spec]) { 8789 ilib.data.dateformats[spec] = formats; 8790 } 8791 */ 8792 if (typeof(this.clock) === 'undefined') { 8793 // default to the locale instead 8794 this.clock = this.locinfo.getClock(); 8795 } 8796 this._initTemplate(formats); 8797 this._massageTemplate(); 8798 if (options && typeof(options.onLoad) === 'function') { 8799 options.onLoad(this); 8800 } 8801 }) 8802 }); 8803 } else { 8804 this._massageTemplate(); 8805 if (options && typeof(options.onLoad) === 'function') { 8806 options.onLoad(this); 8807 } 8808 } 8809 }) 8810 }); 8811 }) 8812 }); 8813 }; 8814 8815 // used in getLength 8816 DateFmt.lenmap = { 8817 "s": "short", 8818 "m": "medium", 8819 "l": "long", 8820 "f": "full" 8821 }; 8822 8823 DateFmt.defaultFmt = { 8824 "gregorian": { 8825 "order": "{date} {time}", 8826 "date": { 8827 "dmwy": "EEE d/MM/yyyy", 8828 "dmy": "d/MM/yyyy", 8829 "dmw": "EEE d/MM", 8830 "dm": "d/MM", 8831 "my": "MM/yyyy", 8832 "dw": "EEE d", 8833 "d": "dd", 8834 "m": "MM", 8835 "y": "yyyy", 8836 "n": "NN", 8837 "w": "EEE" 8838 }, 8839 "time": { 8840 "12": "h:mm:ssa", 8841 "24": "H:mm:ss" 8842 }, 8843 "range": { 8844 "c00": "{st} - {et}, {sd}/{sm}/{sy}", 8845 "c01": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 8846 "c02": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 8847 "c03": "{sd}/{sm}/{sy} {st} - {ed}/{em}/{ey} {et}", 8848 "c10": "{sd}-{ed}/{sm}/{sy}", 8849 "c11": "{sd}/{sm} - {ed}/{em} {sy}", 8850 "c12": "{sd}/{sm}/{sy} - {ed}/{em}/{ey}", 8851 "c20": "{sm}/{sy} - {em}/{ey}", 8852 "c30": "{sy} - {ey}" 8853 } 8854 }, 8855 "islamic": "gregorian", 8856 "hebrew": "gregorian", 8857 "julian": "gregorian", 8858 "buddhist": "gregorian", 8859 "persian": "gregorian", 8860 "persian-algo": "gregorian", 8861 "han": "gregorian" 8862 }; 8863 8864 /** 8865 * @static 8866 * @private 8867 */ 8868 DateFmt.monthNameLenMap = { 8869 "short" : "N", 8870 "medium": "NN", 8871 "long": "MMM", 8872 "full": "MMMM" 8873 }; 8874 8875 /** 8876 * @static 8877 * @private 8878 */ 8879 DateFmt.weekDayLenMap = { 8880 "short" : "E", 8881 "medium": "EE", 8882 "long": "EEE", 8883 "full": "EEEE" 8884 }; 8885 8886 /** 8887 * Return the range of possible meridiems (times of day like "AM" or 8888 * "PM") in this date formatter.<p> 8889 * 8890 * The options may contain any of the following properties: 8891 * 8892 * <ul> 8893 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 8894 * not specified, then the default locale of the app or web page will be used. 8895 * 8896 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 8897 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 8898 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 8899 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 8900 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 8901 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 8902 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 8903 * when formatting dates in the Gregorian calendar. 8904 * </ul> 8905 * 8906 * @static 8907 * @public 8908 * @param {Object} options options governing the way this date formatter instance works for getting meridiems range 8909 * @return {Array.<{name:string,start:string,end:string}>} 8910 */ 8911 DateFmt.getMeridiemsRange = function (options) { 8912 options = options || {}; 8913 var args = {}; 8914 if (options.locale) { 8915 args.locale = options.locale; 8916 } 8917 8918 if (options.meridiems) { 8919 args.meridiems = options.meridiems; 8920 } 8921 8922 var fmt = new DateFmt(args); 8923 8924 return fmt.getMeridiemsRange(); 8925 }; 8926 8927 DateFmt.prototype = { 8928 /** 8929 * @protected 8930 * @param {string|{ 8931 * order:(string|{ 8932 * s:string, 8933 * m:string, 8934 * l:string, 8935 * f:string 8936 * }), 8937 * date:Object.<string, (string|{ 8938 * s:string, 8939 * m:string, 8940 * l:string, 8941 * f:string 8942 * })>, 8943 * time:Object.<string,Object.<string,(string|{ 8944 * s:string, 8945 * m:string, 8946 * l:string, 8947 * f:string 8948 * })>>, 8949 * range:Object.<string, (string|{ 8950 * s:string, 8951 * m:string, 8952 * l:string, 8953 * f:string 8954 * })> 8955 * }} formats 8956 */ 8957 _initTemplate: function (formats) { 8958 if (formats[this.calName]) { 8959 var name = formats[this.calName]; 8960 // may be an alias to another calendar type 8961 this.formats = (typeof(name) === "string") ? formats[name] : name; 8962 8963 this.template = ""; 8964 8965 switch (this.type) { 8966 case "datetime": 8967 this.template = (this.formats && this._getLengthFormat(this.formats.order, this.length)) || "{date} {time}"; 8968 this.template = this.template.replace("{date}", this._getFormat(this.formats.date, this.dateComponents, this.length) || ""); 8969 this.template = this.template.replace("{time}", this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length) || ""); 8970 break; 8971 case "date": 8972 this.template = this._getFormat(this.formats.date, this.dateComponents, this.length); 8973 break; 8974 case "time": 8975 this.template = this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length); 8976 break; 8977 } 8978 } else { 8979 throw "No formats available for calendar " + this.calName + " in locale " + this.locale.toString(); 8980 } 8981 }, 8982 8983 /** 8984 * @protected 8985 */ 8986 _massageTemplate: function () { 8987 var i; 8988 8989 if (this.clock && this.template) { 8990 // explicitly set the hours to the requested type 8991 var temp = ""; 8992 switch (this.clock) { 8993 case "24": 8994 for (i = 0; i < this.template.length; i++) { 8995 if (this.template.charAt(i) == "'") { 8996 temp += this.template.charAt(i++); 8997 while (i < this.template.length && this.template.charAt(i) !== "'") { 8998 temp += this.template.charAt(i++); 8999 } 9000 if (i < this.template.length) { 9001 temp += this.template.charAt(i); 9002 } 9003 } else if (this.template.charAt(i) == 'K') { 9004 temp += 'k'; 9005 } else if (this.template.charAt(i) == 'h') { 9006 temp += 'H'; 9007 } else { 9008 temp += this.template.charAt(i); 9009 } 9010 } 9011 this.template = temp; 9012 break; 9013 case "12": 9014 for (i = 0; i < this.template.length; i++) { 9015 if (this.template.charAt(i) == "'") { 9016 temp += this.template.charAt(i++); 9017 while (i < this.template.length && this.template.charAt(i) !== "'") { 9018 temp += this.template.charAt(i++); 9019 } 9020 if (i < this.template.length) { 9021 temp += this.template.charAt(i); 9022 } 9023 } else if (this.template.charAt(i) == 'k') { 9024 temp += 'K'; 9025 } else if (this.template.charAt(i) == 'H') { 9026 temp += 'h'; 9027 } else { 9028 temp += this.template.charAt(i); 9029 } 9030 } 9031 this.template = temp; 9032 break; 9033 } 9034 } 9035 9036 // tokenize it now for easy formatting 9037 this.templateArr = this._tokenize(this.template); 9038 9039 var digits; 9040 // set up the mapping to native or alternate digits if necessary 9041 if (typeof(this.useNative) === "boolean") { 9042 if (this.useNative) { 9043 digits = this.locinfo.getNativeDigits(); 9044 if (digits) { 9045 this.digits = digits; 9046 } 9047 } 9048 } else if (this.locinfo.getDigitsStyle() === "native") { 9049 digits = this.locinfo.getNativeDigits(); 9050 if (digits) { 9051 this.useNative = true; 9052 this.digits = digits; 9053 } 9054 } 9055 }, 9056 9057 /** 9058 * Convert the template into an array of date components separated by formatting chars. 9059 * @protected 9060 * @param {string} template Format template to tokenize into components 9061 * @return {Array.<string>} a tokenized array of date format components 9062 */ 9063 _tokenize: function (template) { 9064 var i = 0, start, ch, letter, arr = []; 9065 9066 // console.log("_tokenize: tokenizing template " + template); 9067 if (template) { 9068 while (i < template.length) { 9069 ch = template.charAt(i); 9070 start = i; 9071 if (ch === "'") { 9072 // console.log("found quoted string"); 9073 i++; 9074 // escaped string - push as-is, then dequote later 9075 while (i < template.length && template.charAt(i) !== "'") { 9076 i++; 9077 } 9078 if (i < template.length) { 9079 i++; // grab the other quote too 9080 } 9081 } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 9082 letter = template.charAt(i); 9083 // console.log("found letters " + letter); 9084 while (i < template.length && ch === letter) { 9085 ch = template.charAt(++i); 9086 } 9087 } else { 9088 // console.log("found other"); 9089 while (i < template.length && ch !== "'" && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { 9090 ch = template.charAt(++i); 9091 } 9092 } 9093 arr.push(template.substring(start,i)); 9094 // console.log("start is " + start + " i is " + i + " and substr is " + template.substring(start,i)); 9095 } 9096 } 9097 return arr; 9098 }, 9099 9100 /** 9101 * @protected 9102 * @param {Object.<string, (string|{s:string,m:string,l:string,f:string})>} obj Object to search 9103 * @param {string} components Format components to search 9104 * @param {string} length Length of the requested format 9105 * @return {string|undefined} the requested format 9106 */ 9107 _getFormatInternal: function getFormatInternal(obj, components, length) { 9108 if (typeof(components) !== 'undefined' && obj && obj[components]) { 9109 return this._getLengthFormat(obj[components], length); 9110 } 9111 return undefined; 9112 }, 9113 9114 // stand-alone of m (month) is l 9115 // stand-alone of d (day) is a 9116 // stand-alone of w (weekday) is e 9117 // stand-alone of y (year) is r 9118 _standAlones: { 9119 "m": "l", 9120 "d": "a", 9121 "w": "e", 9122 "y": "r" 9123 }, 9124 9125 /** 9126 * @protected 9127 * @param {Object.<string, (string|{s:string,m:string,l:string,f:string})>} obj Object to search 9128 * @param {string} components Format components to search 9129 * @param {string} length Length of the requested format 9130 * @return {string|undefined} the requested format 9131 */ 9132 _getFormat: function getFormat(obj, components, length) { 9133 // handle some special cases for stand-alone formats 9134 if (components && this._standAlones[components]) { 9135 var tmp = this._getFormatInternal(obj, this._standAlones[components], length); 9136 if (tmp) { 9137 return tmp; 9138 } 9139 } 9140 9141 // if no stand-alone format is available, fall back to the regular format 9142 return this._getFormatInternal(obj, components, length); 9143 }, 9144 9145 /** 9146 * @protected 9147 * @param {(string|{s:string,m:string,l:string,f:string})} obj Object to search 9148 * @param {string} length Length of the requested format 9149 * @return {(string|undefined)} the requested format 9150 */ 9151 _getLengthFormat: function getLengthFormat(obj, length) { 9152 if (typeof(obj) === 'string') { 9153 return obj; 9154 } else if (obj[length]) { 9155 return obj[length]; 9156 } 9157 return undefined; 9158 }, 9159 9160 /** 9161 * Return the locale used with this formatter instance. 9162 * @return {Locale} the Locale instance for this formatter 9163 */ 9164 getLocale: function() { 9165 return this.locale; 9166 }, 9167 9168 /** 9169 * Return the template string that is used to format date/times for this 9170 * formatter instance. This will work, even when the template property is not explicitly 9171 * given in the options to the constructor. Without the template option, the constructor 9172 * will build the appropriate template according to the options and use that template 9173 * in the format method. 9174 * 9175 * @return {string} the format template for this formatter 9176 */ 9177 getTemplate: function() { 9178 return this.template; 9179 }, 9180 9181 /** 9182 * Return the type of this formatter. The type is a string that has one of the following 9183 * values: "time", "date", "datetime". 9184 * @return {string} the type of the formatter 9185 */ 9186 getType: function() { 9187 return this.type; 9188 }, 9189 9190 /** 9191 * Return the name of the calendar used to format date/times for this 9192 * formatter instance. 9193 * @return {string} the name of the calendar used by this formatter 9194 */ 9195 getCalendar: function () { 9196 return this.cal.getType(); 9197 }, 9198 9199 /** 9200 * Return the length used to format date/times in this formatter. This is either the 9201 * value of the length option to the constructor, or the default value. 9202 * 9203 * @return {string} the length of formats this formatter returns 9204 */ 9205 getLength: function () { 9206 return DateFmt.lenmap[this.length] || ""; 9207 }, 9208 9209 /** 9210 * Return the date components that this formatter formats. This is either the 9211 * value of the date option to the constructor, or the default value. If this 9212 * formatter is a time-only formatter, this method will return the empty 9213 * string. The date component letters may be specified in any order in the 9214 * constructor, but this method will reorder the given components to a standard 9215 * order. 9216 * 9217 * @return {string} the date components that this formatter formats 9218 */ 9219 getDateComponents: function () { 9220 return this.dateComponents || ""; 9221 }, 9222 9223 /** 9224 * Return the time components that this formatter formats. This is either the 9225 * value of the time option to the constructor, or the default value. If this 9226 * formatter is a date-only formatter, this method will return the empty 9227 * string. The time component letters may be specified in any order in the 9228 * constructor, but this method will reorder the given components to a standard 9229 * order. 9230 * 9231 * @return {string} the time components that this formatter formats 9232 */ 9233 getTimeComponents: function () { 9234 return this.timeComponents || ""; 9235 }, 9236 9237 /** 9238 * Return the time zone used to format date/times for this formatter 9239 * instance. 9240 * @return a string naming the time zone 9241 */ 9242 getTimeZone: function () { 9243 // Lazy load the time zone. If it wasn't explicitly set up before, set 9244 // it up now, but use the 9245 // default TZ for the locale. This way, if the caller never uses the 9246 // time zone in their format, we never have to load up a TimeZone 9247 // instance into this formatter. 9248 if (!this.tz) { 9249 this.tz = new TimeZone({id: ilib.getTimeZone()}); 9250 } 9251 return this.tz; 9252 }, 9253 /** 9254 * Return the clock option set in the constructor. If the clock option was 9255 * not given, the default from the locale is returned instead. 9256 * @return {string} "12" or "24" depending on whether this formatter uses 9257 * the 12-hour or 24-hour clock 9258 */ 9259 getClock: function () { 9260 return this.clock || this.locinfo.getClock(); 9261 }, 9262 /** 9263 * Return the meridiems range in current locale. 9264 * @return {Array.<{name:string,start:string,end:string}>} the range of available meridiems 9265 */ 9266 getMeridiemsRange: function () { 9267 var result; 9268 var _getSysString = function (key) { 9269 return (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)).toString(); 9270 }; 9271 9272 switch (this.meridiems) { 9273 case "chinese": 9274 result = [ 9275 { 9276 name: _getSysString.call(this, "azh0"), 9277 start: "00:00", 9278 end: "05:59" 9279 }, 9280 { 9281 name: _getSysString.call(this, "azh1"), 9282 start: "06:00", 9283 end: "08:59" 9284 }, 9285 { 9286 name: _getSysString.call(this, "azh2"), 9287 start: "09:00", 9288 end: "11:59" 9289 }, 9290 { 9291 name: _getSysString.call(this, "azh3"), 9292 start: "12:00", 9293 end: "12:59" 9294 }, 9295 { 9296 name: _getSysString.call(this, "azh4"), 9297 start: "13:00", 9298 end: "17:59" 9299 }, 9300 { 9301 name: _getSysString.call(this, "azh5"), 9302 start: "18:00", 9303 end: "20:59" 9304 }, 9305 { 9306 name: _getSysString.call(this, "azh6"), 9307 start: "21:00", 9308 end: "23:59" 9309 } 9310 ]; 9311 break; 9312 case "ethiopic": 9313 result = [ 9314 { 9315 name: _getSysString.call(this, "a0-ethiopic"), 9316 start: "00:00", 9317 end: "05:59" 9318 }, 9319 { 9320 name: _getSysString.call(this, "a1-ethiopic"), 9321 start: "06:00", 9322 end: "06:00" 9323 }, 9324 { 9325 name: _getSysString.call(this, "a2-ethiopic"), 9326 start: "06:01", 9327 end: "11:59" 9328 }, 9329 { 9330 name: _getSysString.call(this, "a3-ethiopic"), 9331 start: "12:00", 9332 end: "17:59" 9333 }, 9334 { 9335 name: _getSysString.call(this, "a4-ethiopic"), 9336 start: "18:00", 9337 end: "23:59" 9338 } 9339 ]; 9340 break; 9341 default: 9342 result = [ 9343 { 9344 name: _getSysString.call(this, "a0"), 9345 start: "00:00", 9346 end: "11:59" 9347 }, 9348 { 9349 name: _getSysString.call(this, "a1"), 9350 start: "12:00", 9351 end: "23:59" 9352 } 9353 ]; 9354 break; 9355 } 9356 9357 return result; 9358 }, 9359 9360 /** 9361 * @private 9362 */ 9363 _getTemplate: function (prefix, calendar) { 9364 if (calendar !== "gregorian") { 9365 return prefix + "-" + calendar; 9366 } 9367 return prefix; 9368 }, 9369 9370 /** 9371 * Returns an array of the months of the year, formatted to the optional length specified. 9372 * i.e. ...getMonthsOfYear() OR ...getMonthsOfYear({length: "short"}) 9373 * <p> 9374 * The options parameter may contain any of the following properties: 9375 * 9376 * <ul> 9377 * <li><i>length</i> - length of the names of the months being sought. This may be one of 9378 * "short", "medium", "long", or "full" 9379 * <li><i>date</i> - retrieve the names of the months in the date of the given date 9380 * <li><i>year</i> - retrieve the names of the months in the given year. In some calendars, 9381 * the months have different names depending if that year is a leap year or not. 9382 * </ul> 9383 * 9384 * @param {Object=} options an object-literal that contains any of the above properties 9385 * @return {Array} an array of the names of all of the months of the year in the current calendar 9386 */ 9387 getMonthsOfYear: function(options) { 9388 var length = (options && options.length) || this.getLength(), 9389 template = DateFmt.monthNameLenMap[length], 9390 months = [undefined], 9391 date, 9392 monthCount; 9393 9394 if (options) { 9395 if (options.date) { 9396 date = DateFactory._dateToIlib(options.date); 9397 } 9398 9399 if (options.year) { 9400 date = DateFactory({year: options.year, month: 1, day: 1, type: this.cal.getType()}); 9401 } 9402 } 9403 9404 if (!date) { 9405 date = DateFactory({ 9406 calendar: this.cal.getType() 9407 }); 9408 } 9409 9410 monthCount = this.cal.getNumMonths(date.getYears()); 9411 for (var i = 1; i <= monthCount; i++) { 9412 months[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 9413 } 9414 return months; 9415 }, 9416 9417 /** 9418 * Returns an array of the days of the week, formatted to the optional length specified. 9419 * i.e. ...getDaysOfWeek() OR ...getDaysOfWeek({length: "short"}) 9420 * <p> 9421 * The options parameter may contain any of the following properties: 9422 * 9423 * <ul> 9424 * <li><i>length</i> - length of the names of the months being sought. This may be one of 9425 * "short", "medium", "long", or "full" 9426 * </ul> 9427 * @param {Object=} options an object-literal that contains one key 9428 * "length" with the standard length strings 9429 * @return {Array} an array of all of the names of the days of the week 9430 */ 9431 getDaysOfWeek: function(options) { 9432 var length = (options && options.length) || this.getLength(), 9433 template = DateFmt.weekDayLenMap[length], 9434 days = []; 9435 for (var i = 0; i < 7; i++) { 9436 days[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 9437 } 9438 return days; 9439 }, 9440 9441 9442 /** 9443 * Convert this formatter to a string representation by returning the 9444 * format template. This method delegates to getTemplate. 9445 * 9446 * @return {string} the format template 9447 */ 9448 toString: function() { 9449 return this.getTemplate(); 9450 }, 9451 9452 /** 9453 * @private 9454 * Format a date according to a sequence of components. 9455 * @param {IDate} date a date/time object to format 9456 * @param {Array.<string>} templateArr an array of components to format 9457 * @return {string} the formatted date 9458 */ 9459 _formatTemplate: function (date, templateArr) { 9460 var i, key, temp, tz, str = ""; 9461 for (i = 0; i < templateArr.length; i++) { 9462 switch (templateArr[i]) { 9463 case 'd': 9464 str += (date.day || 1); 9465 break; 9466 case 'dd': 9467 str += JSUtils.pad(date.day || "1", 2); 9468 break; 9469 case 'yy': 9470 temp = "" + ((date.year || 0) % 100); 9471 str += JSUtils.pad(temp, 2); 9472 break; 9473 case 'yyyy': 9474 str += JSUtils.pad(date.year || "0", 4); 9475 break; 9476 case 'M': 9477 str += (date.month || 1); 9478 break; 9479 case 'MM': 9480 str += JSUtils.pad(date.month || "1", 2); 9481 break; 9482 case 'h': 9483 temp = (date.hour || 0) % 12; 9484 if (temp == 0) { 9485 temp = "12"; 9486 } 9487 str += temp; 9488 break; 9489 case 'hh': 9490 temp = (date.hour || 0) % 12; 9491 if (temp == 0) { 9492 temp = "12"; 9493 } 9494 str += JSUtils.pad(temp, 2); 9495 break; 9496 /* 9497 case 'j': 9498 temp = (date.hour || 0) % 12 + 1; 9499 str += temp; 9500 break; 9501 case 'jj': 9502 temp = (date.hour || 0) % 12 + 1; 9503 str += JSUtils.pad(temp, 2); 9504 break; 9505 */ 9506 case 'K': 9507 temp = (date.hour || 0) % 12; 9508 str += temp; 9509 break; 9510 case 'KK': 9511 temp = (date.hour || 0) % 12; 9512 str += JSUtils.pad(temp, 2); 9513 break; 9514 9515 case 'H': 9516 str += (date.hour || "0"); 9517 break; 9518 case 'HH': 9519 str += JSUtils.pad(date.hour || "0", 2); 9520 break; 9521 case 'k': 9522 str += (date.hour == 0 ? "24" : date.hour); 9523 break; 9524 case 'kk': 9525 temp = (date.hour == 0 ? "24" : date.hour); 9526 str += JSUtils.pad(temp, 2); 9527 break; 9528 9529 case 'm': 9530 str += (date.minute || "0"); 9531 break; 9532 case 'mm': 9533 str += JSUtils.pad(date.minute || "0", 2); 9534 break; 9535 case 's': 9536 str += (date.minute || "0"); 9537 break; 9538 case 'ss': 9539 str += JSUtils.pad(date.second || "0", 2); 9540 break; 9541 case 'S': 9542 str += (date.millisecond || "0"); 9543 break; 9544 case 'SSS': 9545 str += JSUtils.pad(date.millisecond || "0", 3); 9546 break; 9547 9548 case 'N': 9549 case 'NN': 9550 case 'MMM': 9551 case 'MMMM': 9552 case 'L': 9553 case 'LL': 9554 case 'LLL': 9555 case 'LLLL': 9556 key = templateArr[i] + (date.month || 1); 9557 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9558 break; 9559 9560 case 'E': 9561 case 'EE': 9562 case 'EEE': 9563 case 'EEEE': 9564 case 'c': 9565 case 'cc': 9566 case 'ccc': 9567 case 'cccc': 9568 key = templateArr[i] + date.getDayOfWeek(); 9569 //console.log("finding " + key + " in the resources"); 9570 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9571 break; 9572 9573 case 'a': 9574 switch (this.meridiems) { 9575 case "chinese": 9576 if (date.hour < 6) { 9577 key = "azh0"; // before dawn 9578 } else if (date.hour < 9) { 9579 key = "azh1"; // morning 9580 } else if (date.hour < 12) { 9581 key = "azh2"; // late morning/day before noon 9582 } else if (date.hour < 13) { 9583 key = "azh3"; // noon hour/midday 9584 } else if (date.hour < 18) { 9585 key = "azh4"; // afternoon 9586 } else if (date.hour < 21) { 9587 key = "azh5"; // evening time/dusk 9588 } else { 9589 key = "azh6"; // night time 9590 } 9591 break; 9592 case "ethiopic": 9593 if (date.hour < 6) { 9594 key = "a0-ethiopic"; // morning 9595 } else if (date.hour === 6 && date.minute === 0) { 9596 key = "a1-ethiopic"; // noon 9597 } else if (date.hour >= 6 && date.hour < 12) { 9598 key = "a2-ethiopic"; // afternoon 9599 } else if (date.hour >= 12 && date.hour < 18) { 9600 key = "a3-ethiopic"; // evening 9601 } else if (date.hour >= 18) { 9602 key = "a4-ethiopic"; // night 9603 } 9604 break; 9605 default: 9606 key = date.hour < 12 ? "a0" : "a1"; 9607 break; 9608 } 9609 //console.log("finding " + key + " in the resources"); 9610 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9611 break; 9612 9613 case 'w': 9614 str += date.getWeekOfYear(); 9615 break; 9616 case 'ww': 9617 str += JSUtils.pad(date.getWeekOfYear(), 2); 9618 break; 9619 9620 case 'D': 9621 str += date.getDayOfYear(); 9622 break; 9623 case 'DD': 9624 str += JSUtils.pad(date.getDayOfYear(), 2); 9625 break; 9626 case 'DDD': 9627 str += JSUtils.pad(date.getDayOfYear(), 3); 9628 break; 9629 case 'W': 9630 str += date.getWeekOfMonth(this.locale); 9631 break; 9632 9633 case 'G': 9634 key = "G" + date.getEra(); 9635 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9636 break; 9637 9638 case 'O': 9639 temp = this.sysres.getString("1#1st|2#2nd|3#3rd|21#21st|22#22nd|23#23rd|31#31st|#{num}th", "ordinalChoice"); 9640 str += temp.formatChoice(date.day, {num: date.day}); 9641 break; 9642 9643 case 'z': // general time zone 9644 tz = this.getTimeZone(); // lazy-load the tz 9645 str += tz.getDisplayName(date, "standard"); 9646 break; 9647 case 'Z': // RFC 822 time zone 9648 tz = this.getTimeZone(); // lazy-load the tz 9649 str += tz.getDisplayName(date, "rfc822"); 9650 break; 9651 9652 default: 9653 str += templateArr[i].replace(/'/g, ""); 9654 break; 9655 } 9656 } 9657 9658 if (this.digits) { 9659 str = JSUtils.mapString(str, this.digits); 9660 } 9661 return str; 9662 }, 9663 9664 /** 9665 * Format a particular date instance according to the settings of this 9666 * formatter object. The type of the date instance being formatted must 9667 * correspond exactly to the calendar type with which this formatter was 9668 * constructed. If the types are not compatible, this formatter will 9669 * produce bogus results. 9670 * 9671 * @param {IDate|Number|String|Date|JulianDay|null|undefined} dateLike a date-like object to format 9672 * @return {string} the formatted version of the given date instance 9673 */ 9674 format: function (dateLike) { 9675 var thisZoneName = this.tz && this.tz.getId() || "local"; 9676 9677 var date = DateFactory._dateToIlib(dateLike, thisZoneName, this.locale); 9678 9679 if (!date.getCalendar || !(date instanceof IDate)) { 9680 throw "Wrong date type passed to DateFmt.format()"; 9681 } 9682 9683 var dateZoneName = date.timezone || "local"; 9684 9685 // convert to the time zone of this formatter before formatting 9686 if (dateZoneName !== thisZoneName || date.getCalendar() !== this.calName) { 9687 // console.log("Differing time zones date: " + dateZoneName + " and fmt: " + thisZoneName + ". Converting..."); 9688 // this will recalculate the date components based on the new time zone 9689 // and/or convert a date in another calendar to the current calendar before formatting it 9690 var newDate = DateFactory({ 9691 type: this.calName, 9692 timezone: thisZoneName, 9693 julianday: date.getJulianDay() 9694 }); 9695 9696 date = newDate; 9697 } 9698 return this._formatTemplate(date, this.templateArr); 9699 }, 9700 9701 /** 9702 * Return a string that describes a date relative to the given 9703 * reference date. The string returned is text that for the locale that 9704 * was specified when the formatter instance was constructed.<p> 9705 * 9706 * The date can be in the future relative to the reference date or in 9707 * the past, and the formatter will generate the appropriate string.<p> 9708 * 9709 * The text used to describe the relative reference depends on the length 9710 * of time between the date and the reference. If the time was in the 9711 * past, it will use the "ago" phrase, and in the future, it will use 9712 * the "in" phrase. Examples:<p> 9713 * 9714 * <ul> 9715 * <li>within a minute: either "X seconds ago" or "in X seconds" 9716 * <li>within an hour: either "X minutes ago" or "in X minutes" 9717 * <li>within a day: either "X hours ago" or "in X hours" 9718 * <li>within 2 weeks: either "X days ago" or "in X days" 9719 * <li>within 12 weeks (~3 months): either "X weeks ago" or "in X weeks" 9720 * <li>within two years: either "X months ago" or "in X months" 9721 * <li>longer than 2 years: "X years ago" or "in X years" 9722 * </ul> 9723 * 9724 * @param {IDate|Number|String|Date|JulianDay|null|undefined} reference a date that the date parameter should be relative to 9725 * @param {IDate|Number|String|Date|JulianDay|null|undefined} date a date being formatted 9726 * @throws "Wrong calendar type" when the start or end dates are not the same 9727 * calendar type as the formatter itself 9728 * @return {string} the formatted relative date 9729 */ 9730 formatRelative: function(reference, date) { 9731 reference = DateFactory._dateToIlib(reference); 9732 date = DateFactory._dateToIlib(date); 9733 9734 var referenceRd, dateRd, fmt, time, diff, num; 9735 9736 if (typeof(reference) !== 'object' || !reference.getCalendar || reference.getCalendar() !== this.calName || 9737 typeof(date) !== 'object' || !date.getCalendar || date.getCalendar() !== this.calName) { 9738 throw "Wrong calendar type"; 9739 } 9740 9741 referenceRd = reference.getRataDie(); 9742 dateRd = date.getRataDie(); 9743 9744 if (dateRd < referenceRd) { 9745 diff = referenceRd - dateRd; 9746 fmt = this.sysres.getString("{duration} ago"); 9747 } else { 9748 diff = dateRd - referenceRd; 9749 fmt = this.sysres.getString("in {duration}"); 9750 } 9751 9752 if (diff < 0.000694444) { 9753 num = Math.round(diff * 86400); 9754 switch (this.length) { 9755 case 's': 9756 time = this.sysres.getString("#{num}s"); 9757 break; 9758 case 'm': 9759 time = this.sysres.getString("1#1 se|#{num} sec"); 9760 break; 9761 case 'l': 9762 time = this.sysres.getString("1#1 sec|#{num} sec"); 9763 break; 9764 default: 9765 case 'f': 9766 time = this.sysres.getString("1#1 second|#{num} seconds"); 9767 break; 9768 } 9769 } else if (diff < 0.041666667) { 9770 num = Math.round(diff * 1440); 9771 switch (this.length) { 9772 case 's': 9773 time = this.sysres.getString("#{num}m", "durationShortMinutes"); 9774 break; 9775 case 'm': 9776 time = this.sysres.getString("1#1 mi|#{num} min"); 9777 break; 9778 case 'l': 9779 time = this.sysres.getString("1#1 min|#{num} min"); 9780 break; 9781 default: 9782 case 'f': 9783 time = this.sysres.getString("1#1 minute|#{num} minutes"); 9784 break; 9785 } 9786 } else if (diff < 1) { 9787 num = Math.round(diff * 24); 9788 switch (this.length) { 9789 case 's': 9790 time = this.sysres.getString("#{num}h"); 9791 break; 9792 case 'm': 9793 time = this.sysres.getString("1#1 hr|#{num} hrs", "durationMediumHours"); 9794 break; 9795 case 'l': 9796 time = this.sysres.getString("1#1 hr|#{num} hrs"); 9797 break; 9798 default: 9799 case 'f': 9800 time = this.sysres.getString("1#1 hour|#{num} hours"); 9801 break; 9802 } 9803 } else if (diff < 14) { 9804 num = Math.round(diff); 9805 switch (this.length) { 9806 case 's': 9807 time = this.sysres.getString("#{num}d"); 9808 break; 9809 case 'm': 9810 time = this.sysres.getString("1#1 dy|#{num} dys"); 9811 break; 9812 case 'l': 9813 time = this.sysres.getString("1#1 day|#{num} days", "durationLongDays"); 9814 break; 9815 default: 9816 case 'f': 9817 time = this.sysres.getString("1#1 day|#{num} days"); 9818 break; 9819 } 9820 } else if (diff < 84) { 9821 num = Math.round(diff/7); 9822 switch (this.length) { 9823 case 's': 9824 time = this.sysres.getString("#{num}w"); 9825 break; 9826 case 'm': 9827 time = this.sysres.getString("1#1 wk|#{num} wks", "durationMediumWeeks"); 9828 break; 9829 case 'l': 9830 time = this.sysres.getString("1#1 wk|#{num} wks"); 9831 break; 9832 default: 9833 case 'f': 9834 time = this.sysres.getString("1#1 week|#{num} weeks"); 9835 break; 9836 } 9837 } else if (diff < 730) { 9838 num = Math.round(diff/30.4); 9839 switch (this.length) { 9840 case 's': 9841 time = this.sysres.getString("#{num}m", "durationShortMonths"); 9842 break; 9843 case 'm': 9844 time = this.sysres.getString("1#1 mo|#{num} mos"); 9845 break; 9846 case 'l': 9847 time = this.sysres.getString("1#1 mon|#{num} mons"); 9848 break; 9849 default: 9850 case 'f': 9851 time = this.sysres.getString("1#1 month|#{num} months"); 9852 break; 9853 } 9854 } else { 9855 num = Math.round(diff/365); 9856 switch (this.length) { 9857 case 's': 9858 time = this.sysres.getString("#{num}y"); 9859 break; 9860 case 'm': 9861 time = this.sysres.getString("1#1 yr|#{num} yrs", "durationMediumYears"); 9862 break; 9863 case 'l': 9864 time = this.sysres.getString("1#1 yr|#{num} yrs"); 9865 break; 9866 default: 9867 case 'f': 9868 time = this.sysres.getString("1#1 year|#{num} years"); 9869 break; 9870 } 9871 } 9872 return fmt.format({duration: time.formatChoice(num, {num: num})}); 9873 } 9874 }; 9875 9876 9877 9878 /*< DateRngFmt.js */ 9879 /* 9880 * DateFmt.js - Date formatter definition 9881 * 9882 * Copyright © 2012-2015, JEDLSoft 9883 * 9884 * Licensed under the Apache License, Version 2.0 (the "License"); 9885 * you may not use this file except in compliance with the License. 9886 * You may obtain a copy of the License at 9887 * 9888 * http://www.apache.org/licenses/LICENSE-2.0 9889 * 9890 * Unless required by applicable law or agreed to in writing, software 9891 * distributed under the License is distributed on an "AS IS" BASIS, 9892 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9893 * 9894 * See the License for the specific language governing permissions and 9895 * limitations under the License. 9896 */ 9897 9898 /* 9899 !depends 9900 ilib.js 9901 Locale.js 9902 IDate.js 9903 IString.js 9904 CalendarFactory.js 9905 LocaleInfo.js 9906 TimeZone.js 9907 DateFmt.js 9908 GregorianCal.js 9909 JSUtils.js 9910 Utils.js 9911 */ 9912 9913 // !data dateformats sysres 9914 9915 9916 9917 9918 9919 /** 9920 * @class 9921 * Create a new date range formatter instance. The date range formatter is immutable once 9922 * it is created, but can format as many different date ranges as needed with the same 9923 * options. Create different date range formatter instances for different purposes 9924 * and then keep them cached for use later if you have more than one range to 9925 * format.<p> 9926 * 9927 * The options may contain any of the following properties: 9928 * 9929 * <ul> 9930 * <li><i>locale</i> - locale to use when formatting the date/times in the range. If the 9931 * locale is not specified, then the default locale of the app or web page will be used. 9932 * 9933 * <li><i>calendar</i> - the type of calendar to use for this format. The value should 9934 * be a sting containing the name of the calendar. Currently, the supported 9935 * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the 9936 * calendar is not specified, then the default calendar for the locale is used. When the 9937 * calendar type is specified, then the format method must be called with an instance of 9938 * the appropriate date type. (eg. Gregorian calendar means that the format method must 9939 * be called with a GregDate instance.) 9940 * 9941 * <li><i>timezone</i> - time zone to use when formatting times. This may be a time zone 9942 * instance or a time zone specifier string in RFC 822 format. If not specified, the 9943 * default time zone for the locale is used. 9944 * 9945 * <li><i>length</i> - Specify the length of the format to use as a string. The length 9946 * is the approximate size of the formatted string. 9947 * 9948 * <ul> 9949 * <li><i>short</i> - use a short representation of the time. This is the most compact format possible for the locale. 9950 * <li><i>medium</i> - use a medium length representation of the time. This is a slightly longer format. 9951 * <li><i>long</i> - use a long representation of the time. This is a fully specified format, but some of the textual 9952 * components may still be abbreviated. (eg. "Tue" instead of "Tuesday") 9953 * <li><i>full</i> - use a full representation of the time. This is a fully specified format where all the textual 9954 * components are spelled out completely. 9955 * </ul> 9956 * 9957 * eg. The "short" format for an en_US range may be "MM/yy - MM/yy", whereas the long format might be 9958 * "MMM, yyyy - MMM, yyyy". In the long format, the month name is textual instead of numeric 9959 * and is longer, the year is 4 digits instead of 2, and the format contains slightly more 9960 * spaces and formatting characters.<p> 9961 * 9962 * Note that the length parameter does not specify which components are to be formatted. The 9963 * components that are formatted depend on the length of time in the range. 9964 * 9965 * <li><i>clock</i> - specify that formatted times should use a 12 or 24 hour clock if the 9966 * format happens to include times. Valid values are "12" and "24".<p> 9967 * 9968 * In some locales, both clocks are used. For example, in en_US, the general populace uses 9969 * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or 9970 * scientific writing, it is more common to use a 24 hour clock. This property allows you to 9971 * construct a formatter that overrides the default for the locale.<p> 9972 * 9973 * If this property is not specified, the default is to use the most widely used convention 9974 * for the locale. 9975 * <li>onLoad - a callback function to call when the date range format object is fully 9976 * loaded. When the onLoad option is given, the DateRngFmt object will attempt to 9977 * load any missing locale data using the ilib loader callback. 9978 * When the constructor is done (even if the data is already preassembled), the 9979 * onLoad function is called with the current instance as a parameter, so this 9980 * callback can be used with preassembled or dynamic loading or a mix of the two. 9981 * 9982 * <li>sync - tell whether to load any missing locale data synchronously or 9983 * asynchronously. If this option is given as "false", then the "onLoad" 9984 * callback must be given, as the instance returned from this constructor will 9985 * not be usable for a while. 9986 * 9987 * <li><i>loadParams</i> - an object containing parameters to pass to the 9988 * loader callback function when locale data is missing. The parameters are not 9989 * interpretted or modified in any way. They are simply passed along. The object 9990 * may contain any property/value pairs as long as the calling code is in 9991 * agreement with the loader callback function as to what those parameters mean. 9992 * </ul> 9993 * <p> 9994 * 9995 * 9996 * @constructor 9997 * @param {Object} options options governing the way this date range formatter instance works 9998 */ 9999 var DateRngFmt = function(options) { 10000 var sync = true; 10001 var loadParams = undefined; 10002 this.locale = new Locale(); 10003 this.length = "s"; 10004 10005 if (options) { 10006 if (options.locale) { 10007 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 10008 } 10009 10010 if (options.calendar) { 10011 this.calName = options.calendar; 10012 } 10013 10014 if (options.length) { 10015 if (options.length === 'short' || 10016 options.length === 'medium' || 10017 options.length === 'long' || 10018 options.length === 'full') { 10019 // only use the first char to save space in the json files 10020 this.length = options.length.charAt(0); 10021 } 10022 } 10023 if (typeof(options.sync) !== 'undefined') { 10024 sync = (options.sync == true); 10025 } 10026 10027 loadParams = options.loadParams; 10028 } 10029 10030 var opts = {}; 10031 JSUtils.shallowCopy(options, opts); 10032 opts.sync = sync; 10033 opts.loadParams = loadParams; 10034 10035 /** 10036 * @private 10037 */ 10038 opts.onLoad = ilib.bind(this, function (fmt) { 10039 this.dateFmt = fmt; 10040 if (fmt) { 10041 this.locinfo = this.dateFmt.locinfo; 10042 10043 // get the default calendar name from the locale, and if the locale doesn't define 10044 // one, use the hard-coded gregorian as the last resort 10045 this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; 10046 this.cal = CalendarFactory({ 10047 type: this.calName 10048 }); 10049 if (!this.cal) { 10050 this.cal = new GregorianCal(); 10051 } 10052 10053 this.timeTemplate = this.dateFmt._getFormat(this.dateFmt.formats.time[this.dateFmt.clock], this.dateFmt.timeComponents, this.length) || "hh:mm"; 10054 this.timeTemplateArr = this.dateFmt._tokenize(this.timeTemplate); 10055 10056 if (options && typeof(options.onLoad) === 'function') { 10057 options.onLoad(this); 10058 } 10059 } 10060 }); 10061 10062 // delegate a bunch of the formatting to this formatter 10063 new DateFmt(opts); 10064 }; 10065 10066 DateRngFmt.prototype = { 10067 /** 10068 * Return the locale used with this formatter instance. 10069 * @return {Locale} the Locale instance for this formatter 10070 */ 10071 getLocale: function() { 10072 return this.locale; 10073 }, 10074 10075 /** 10076 * Return the name of the calendar used to format date/times for this 10077 * formatter instance. 10078 * @return {string} the name of the calendar used by this formatter 10079 */ 10080 getCalendar: function () { 10081 return this.dateFmt.getCalendar(); 10082 }, 10083 10084 /** 10085 * Return the length used to format date/times in this formatter. This is either the 10086 * value of the length option to the constructor, or the default value. 10087 * 10088 * @return {string} the length of formats this formatter returns 10089 */ 10090 getLength: function () { 10091 return DateFmt.lenmap[this.length] || ""; 10092 }, 10093 10094 /** 10095 * Return the time zone used to format date/times for this formatter 10096 * instance. 10097 * @return {TimeZone} a string naming the time zone 10098 */ 10099 getTimeZone: function () { 10100 return this.dateFmt.getTimeZone(); 10101 }, 10102 10103 /** 10104 * Return the clock option set in the constructor. If the clock option was 10105 * not given, the default from the locale is returned instead. 10106 * @return {string} "12" or "24" depending on whether this formatter uses 10107 * the 12-hour or 24-hour clock 10108 */ 10109 getClock: function () { 10110 return this.dateFmt.getClock(); 10111 }, 10112 10113 /** 10114 * Format a date/time range according to the settings of the current 10115 * formatter. The range is specified as being from the "start" date until 10116 * the "end" date. <p> 10117 * 10118 * The template that the date/time range uses depends on the 10119 * length of time between the dates, on the premise that a long date range 10120 * which is too specific is not useful. For example, when giving 10121 * the dates of the 100 Years War, in most situations it would be more 10122 * appropriate to format the range as "1337 - 1453" than to format it as 10123 * "10:37am November 9, 1337 - 4:37pm July 17, 1453", as the latter format 10124 * is much too specific given the length of time that the range represents. 10125 * If a very specific, but long, date range really is needed, the caller 10126 * should format two specific dates separately and put them 10127 * together as you might with other normal strings.<p> 10128 * 10129 * The format used for a date range contains the following date components, 10130 * where the order of those components is rearranged and the component values 10131 * are translated according to each locale: 10132 * 10133 * <ul> 10134 * <li>within 3 days: the times of day, dates, months, and years 10135 * <li>within 730 days (2 years): the dates, months, and years 10136 * <li>within 3650 days (10 years): the months and years 10137 * <li>longer than 10 years: the years only 10138 * </ul> 10139 * 10140 * In general, if any of the date components share a value between the 10141 * start and end date, that component is only given once. For example, 10142 * if the range is from November 15, 2011 to November 26, 2011, the 10143 * start and end dates both share the same month and year. The 10144 * range would then be formatted as "November 15-26, 2011". <p> 10145 * 10146 * If you want to format a length of time instead of a particular range of 10147 * time (for example, the length of an event rather than the specific start time 10148 * and end time of that event), then use a duration formatter instance 10149 * (DurationFmt) instead. The formatRange method will make sure that each component 10150 * of the date/time is within the normal range for that component. For example, 10151 * the minutes will always be between 0 and 59, no matter what is specified in 10152 * the date to format, because that is the normal range for minutes. A duration 10153 * format will allow the number of minutes to exceed 59. For example, if you 10154 * were displaying the length of a movie that is 198 minutes long, the minutes 10155 * component of a duration could be 198.<p> 10156 * 10157 * @param {IDate} start the starting date/time of the range. This must be of 10158 * the same calendar type as the formatter itself. 10159 * @param {IDate} end the ending date/time of the range. This must be of the 10160 * same calendar type as the formatter itself. 10161 * @throws "Wrong calendar type" when the start or end dates are not the same 10162 * calendar type as the formatter itself 10163 * @return {string} a date range formatted for the locale 10164 */ 10165 format: function (start, end) { 10166 var startRd, endRd, fmt = "", yearTemplate, monthTemplate, dayTemplate, formats; 10167 10168 if (typeof(start) !== 'object' || !start.getCalendar || start.getCalendar() !== this.calName || 10169 typeof(end) !== 'object' || !end.getCalendar || end.getCalendar() !== this.calName) { 10170 throw "Wrong calendar type"; 10171 } 10172 10173 startRd = start.getRataDie(); 10174 endRd = end.getRataDie(); 10175 10176 // 10177 // legend: 10178 // c00 - difference is less than 3 days. Year, month, and date are same, but time is different 10179 // c01 - difference is less than 3 days. Year and month are same but date and time are different 10180 // c02 - difference is less than 3 days. Year is same but month, date, and time are different. (ie. it straddles a month boundary) 10181 // c03 - difference is less than 3 days. Year, month, date, and time are all different. (ie. it straddles a year boundary) 10182 // c10 - difference is less than 2 years. Year and month are the same, but date is different. 10183 // c11 - difference is less than 2 years. Year is the same, but month, date, and time are different. 10184 // c12 - difference is less than 2 years. All fields are different. (ie. straddles a year boundary) 10185 // c20 - difference is less than 10 years. All fields are different. 10186 // c30 - difference is more than 10 years. All fields are different. 10187 // 10188 10189 if (endRd - startRd < 3) { 10190 if (start.year === end.year) { 10191 if (start.month === end.month) { 10192 if (start.day === end.day) { 10193 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c00", this.length)); 10194 } else { 10195 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c01", this.length)); 10196 } 10197 } else { 10198 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c02", this.length)); 10199 } 10200 } else { 10201 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c03", this.length)); 10202 } 10203 } else if (endRd - startRd < 730) { 10204 if (start.year === end.year) { 10205 if (start.month === end.month) { 10206 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c10", this.length)); 10207 } else { 10208 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c11", this.length)); 10209 } 10210 } else { 10211 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c12", this.length)); 10212 } 10213 } else if (endRd - startRd < 3650) { 10214 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c20", this.length)); 10215 } else { 10216 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c30", this.length)); 10217 } 10218 10219 formats = this.dateFmt.formats.date; 10220 yearTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "y", this.length) || "yyyy"); 10221 monthTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "m", this.length) || "MM"); 10222 dayTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "d", this.length) || "dd"); 10223 10224 /* 10225 console.log("fmt is " + fmt.toString()); 10226 console.log("year template is " + yearTemplate); 10227 console.log("month template is " + monthTemplate); 10228 console.log("day template is " + dayTemplate); 10229 */ 10230 10231 return fmt.format({ 10232 sy: this.dateFmt._formatTemplate(start, yearTemplate), 10233 sm: this.dateFmt._formatTemplate(start, monthTemplate), 10234 sd: this.dateFmt._formatTemplate(start, dayTemplate), 10235 st: this.dateFmt._formatTemplate(start, this.timeTemplateArr), 10236 ey: this.dateFmt._formatTemplate(end, yearTemplate), 10237 em: this.dateFmt._formatTemplate(end, monthTemplate), 10238 ed: this.dateFmt._formatTemplate(end, dayTemplate), 10239 et: this.dateFmt._formatTemplate(end, this.timeTemplateArr) 10240 }); 10241 } 10242 }; 10243 10244 10245 /*< HebrewCal.js */ 10246 /* 10247 * hebrew.js - Represent a Hebrew calendar object. 10248 * 10249 * Copyright © 2012-2015, JEDLSoft 10250 * 10251 * Licensed under the Apache License, Version 2.0 (the "License"); 10252 * you may not use this file except in compliance with the License. 10253 * You may obtain a copy of the License at 10254 * 10255 * http://www.apache.org/licenses/LICENSE-2.0 10256 * 10257 * Unless required by applicable law or agreed to in writing, software 10258 * distributed under the License is distributed on an "AS IS" BASIS, 10259 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10260 * 10261 * See the License for the specific language governing permissions and 10262 * limitations under the License. 10263 */ 10264 10265 10266 /* !depends ilib.js Calendar.js MathUtils.js */ 10267 10268 10269 /** 10270 * @class 10271 * Construct a new Hebrew calendar object. This class encodes information about 10272 * the Hebrew (Jewish) calendar. The Hebrew calendar is a tabular hebrew 10273 * calendar where the dates are calculated by arithmetic rules. This differs from 10274 * the religious Hebrew calendar which is used to mark the beginning of particular 10275 * holidays. The religious calendar depends on the first sighting of the new 10276 * crescent moon to determine the first day of the new month. Because humans and 10277 * weather are both involved, the actual time of sighting varies, so it is not 10278 * really possible to precalculate the religious calendar. Certain groups, such 10279 * as the Hebrew Society of North America, decreed in in 2007 that they will use 10280 * a calendar based on calculations rather than observations to determine the 10281 * beginning of lunar months, and therefore the dates of holidays.<p> 10282 * 10283 * 10284 * @constructor 10285 * @extends Calendar 10286 */ 10287 var HebrewCal = function() { 10288 this.type = "hebrew"; 10289 }; 10290 10291 /** 10292 * Return the number of days elapsed in the Hebrew calendar before the 10293 * given year starts. 10294 * @private 10295 * @param {number} year the year for which the number of days is sought 10296 * @return {number} the number of days elapsed in the Hebrew calendar before the 10297 * given year starts 10298 */ 10299 HebrewCal.elapsedDays = function(year) { 10300 var months = Math.floor(((235*year) - 234)/19); 10301 var parts = 204 + 793 * MathUtils.mod(months, 1080); 10302 var hours = 11 + 12 * months + 793 * Math.floor(months/1080) + 10303 Math.floor(parts/1080); 10304 var days = 29 * months + Math.floor(hours/24); 10305 return (MathUtils.mod(3 * (days + 1), 7) < 3) ? days + 1 : days; 10306 }; 10307 10308 /** 10309 * Return the number of days that the New Year's (Rosh HaShanah) in the Hebrew 10310 * calendar will be corrected for the given year. Corrections are caused because New 10311 * Year's is not allowed to start on certain days of the week. To deal with 10312 * it, the start of the new year is corrected for the next year by adding a 10313 * day to the 8th month (Heshvan) and/or the 9th month (Kislev) in the current 10314 * year to make them 30 days long instead of 29. 10315 * 10316 * @private 10317 * @param {number} year the year for which the correction is sought 10318 * @param {number} elapsed number of days elapsed up to this year 10319 * @return {number} the number of days correction in the current year to make sure 10320 * Rosh HaShanah does not fall on undesirable days of the week 10321 */ 10322 HebrewCal.newYearsCorrection = function(year, elapsed) { 10323 var lastYear = HebrewCal.elapsedDays(year-1), 10324 thisYear = elapsed, 10325 nextYear = HebrewCal.elapsedDays(year+1); 10326 10327 return (nextYear - thisYear) == 356 ? 2 : ((thisYear - lastYear) == 382 ? 1 : 0); 10328 }; 10329 10330 /** 10331 * Return the rata die date of the new year for the given hebrew year. 10332 * @private 10333 * @param {number} year the year for which the new year is needed 10334 * @return {number} the rata die date of the new year 10335 */ 10336 HebrewCal.newYear = function(year) { 10337 var elapsed = HebrewCal.elapsedDays(year); 10338 10339 return elapsed + HebrewCal.newYearsCorrection(year, elapsed); 10340 }; 10341 10342 /** 10343 * Return the number of days in the given year. Years contain a variable number of 10344 * days because the date of Rosh HaShanah (New Year's) changes so that it doesn't 10345 * fall on particular days of the week. Days are added to the months of Heshvan 10346 * and/or Kislev in the previous year in order to prevent the current year's New 10347 * Year from being on Sunday, Wednesday, or Friday. 10348 * 10349 * @param {number} year the year for which the length is sought 10350 * @return {number} number of days in the given year 10351 */ 10352 HebrewCal.daysInYear = function(year) { 10353 return HebrewCal.newYear(year+1) - HebrewCal.newYear(year); 10354 }; 10355 10356 /** 10357 * Return true if the given year contains a long month of Heshvan. That is, 10358 * it is 30 days instead of 29. 10359 * 10360 * @private 10361 * @param {number} year the year in which that month is questioned 10362 * @return {boolean} true if the given year contains a long month of Heshvan 10363 */ 10364 HebrewCal.longHeshvan = function(year) { 10365 return MathUtils.mod(HebrewCal.daysInYear(year), 10) === 5; 10366 }; 10367 10368 /** 10369 * Return true if the given year contains a long month of Kislev. That is, 10370 * it is 30 days instead of 29. 10371 * 10372 * @private 10373 * @param {number} year the year in which that month is questioned 10374 * @return {boolean} true if the given year contains a short month of Kislev 10375 */ 10376 HebrewCal.longKislev = function(year) { 10377 return MathUtils.mod(HebrewCal.daysInYear(year), 10) !== 3; 10378 }; 10379 10380 /** 10381 * Return the date of the last day of the month for the given year. The date of 10382 * the last day of the month is variable because a number of months gain an extra 10383 * day in leap years, and it is variable which months gain a day for each leap 10384 * year and which do not. 10385 * 10386 * @param {number} month the month for which the number of days is sought 10387 * @param {number} year the year in which that month is 10388 * @return {number} the number of days in the given month and year 10389 */ 10390 HebrewCal.prototype.lastDayOfMonth = function(month, year) { 10391 switch (month) { 10392 case 2: 10393 case 4: 10394 case 6: 10395 case 10: 10396 return 29; 10397 case 13: 10398 return this.isLeapYear(year) ? 29 : 0; 10399 case 8: 10400 return HebrewCal.longHeshvan(year) ? 30 : 29; 10401 case 9: 10402 return HebrewCal.longKislev(year) ? 30 : 29; 10403 case 12: 10404 case 1: 10405 case 3: 10406 case 5: 10407 case 7: 10408 case 11: 10409 return 30; 10410 default: 10411 return 0; 10412 } 10413 }; 10414 10415 /** 10416 * Return the number of months in the given year. The number of months in a year varies 10417 * for luni-solar calendars because in some years, an extra month is needed to extend the 10418 * days in a year to an entire solar year. The month is represented as a 1-based number 10419 * where 1=first month, 2=second month, etc. 10420 * 10421 * @param {number} year a year for which the number of months is sought 10422 */ 10423 HebrewCal.prototype.getNumMonths = function(year) { 10424 return this.isLeapYear(year) ? 13 : 12; 10425 }; 10426 10427 /** 10428 * Return the number of days in a particular month in a particular year. This function 10429 * can return a different number for a month depending on the year because of leap years. 10430 * 10431 * @param {number} month the month for which the length is sought 10432 * @param {number} year the year within which that month can be found 10433 * @returns {number} the number of days within the given month in the given year, or 10434 * 0 for an invalid month in the year 10435 */ 10436 HebrewCal.prototype.getMonLength = function(month, year) { 10437 if (month < 1 || month > 13 || (month == 13 && !this.isLeapYear(year))) { 10438 return 0; 10439 } 10440 return this.lastDayOfMonth(month, year); 10441 }; 10442 10443 /** 10444 * Return true if the given year is a leap year in the Hebrew calendar. 10445 * The year parameter may be given as a number, or as a HebrewDate object. 10446 * @param {number|Object} year the year for which the leap year information is being sought 10447 * @returns {boolean} true if the given year is a leap year 10448 */ 10449 HebrewCal.prototype.isLeapYear = function(year) { 10450 var y = (typeof(year) == 'number') ? year : year.year; 10451 return (MathUtils.mod(1 + 7 * y, 19) < 7); 10452 }; 10453 10454 /** 10455 * Return the type of this calendar. 10456 * 10457 * @returns {string} the name of the type of this calendar 10458 */ 10459 HebrewCal.prototype.getType = function() { 10460 return this.type; 10461 }; 10462 10463 /** 10464 * Return a date instance for this calendar type using the given 10465 * options. 10466 * @param {Object} options options controlling the construction of 10467 * the date instance 10468 * @returns {HebrewDate} a date appropriate for this calendar type 10469 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 10470 */ 10471 HebrewCal.prototype.newDateInstance = function (options) { 10472 return new HebrewDate(options); 10473 }; 10474 10475 /*register this calendar for the factory method */ 10476 Calendar._constructors["hebrew"] = HebrewCal; 10477 10478 10479 10480 /*< HebrewRataDie.js */ 10481 /* 10482 * HebrewRataDie.js - Represent an RD date in the Hebrew calendar 10483 * 10484 * Copyright © 2012-2015, JEDLSoft 10485 * 10486 * Licensed under the Apache License, Version 2.0 (the "License"); 10487 * you may not use this file except in compliance with the License. 10488 * You may obtain a copy of the License at 10489 * 10490 * http://www.apache.org/licenses/LICENSE-2.0 10491 * 10492 * Unless required by applicable law or agreed to in writing, software 10493 * distributed under the License is distributed on an "AS IS" BASIS, 10494 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10495 * 10496 * See the License for the specific language governing permissions and 10497 * limitations under the License. 10498 */ 10499 10500 /* !depends 10501 MathUtils.js 10502 HebrewCal.js 10503 RataDie.js 10504 */ 10505 10506 10507 /** 10508 * @class 10509 * Construct a new Hebrew RD date number object. The constructor parameters can 10510 * contain any of the following properties: 10511 * 10512 * <ul> 10513 * <li><i>unixtime<i> - sets the time of this instance according to the given 10514 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 10515 * 10516 * <li><i>julianday</i> - sets the time of this instance according to the given 10517 * Julian Day instance or the Julian Day given as a float 10518 * 10519 * <li><i>year</i> - any integer, including 0 10520 * 10521 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 10522 * 10523 * <li><i>day</i> - 1 to 31 10524 * 10525 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 10526 * is always done with an unambiguous 24 hour representation 10527 * 10528 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 10529 * the parts or specify the minutes, seconds, and milliseconds, but not both. 10530 * 10531 * <li><i>minute</i> - 0 to 59 10532 * 10533 * <li><i>second</i> - 0 to 59 10534 * 10535 * <li><i>millisecond</i> - 0 to 999 10536 * 10537 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 10538 * </ul> 10539 * 10540 * If the constructor is called with another Hebrew date instance instead of 10541 * a parameter block, the other instance acts as a parameter block and its 10542 * settings are copied into the current instance.<p> 10543 * 10544 * If the constructor is called with no arguments at all or if none of the 10545 * properties listed above are present, then the RD is calculate based on 10546 * the current date at the time of instantiation. <p> 10547 * 10548 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 10549 * specified in the params, it is assumed that they have the smallest possible 10550 * value in the range for the property (zero or one).<p> 10551 * 10552 * 10553 * @private 10554 * @constructor 10555 * @extends RataDie 10556 * @param {Object=} params parameters that govern the settings and behaviour of this Hebrew RD date 10557 */ 10558 var HebrewRataDie = function(params) { 10559 this.cal = params && params.cal || new HebrewCal(); 10560 this.rd = NaN; 10561 RataDie.call(this, params); 10562 }; 10563 10564 HebrewRataDie.prototype = new RataDie(); 10565 HebrewRataDie.prototype.parent = RataDie; 10566 HebrewRataDie.prototype.constructor = HebrewRataDie; 10567 10568 /** 10569 * The difference between a zero Julian day and the first day of the Hebrew 10570 * calendar: sunset on Monday, Tishri 1, 1 = September 7, 3760 BC Gregorian = JD 347997.25 10571 * @private 10572 * @type number 10573 */ 10574 HebrewRataDie.prototype.epoch = 347997.25; 10575 10576 /** 10577 * the cumulative lengths of each month for a non-leap year, without new years corrections 10578 * @private 10579 * @const 10580 * @type Array.<number> 10581 */ 10582 HebrewRataDie.cumMonthLengths = [ 10583 176, /* Nisan */ 10584 206, /* Iyyar */ 10585 235, /* Sivan */ 10586 265, /* Tammuz */ 10587 294, /* Av */ 10588 324, /* Elul */ 10589 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10590 30, /* Heshvan */ 10591 59, /* Kislev */ 10592 88, /* Teveth */ 10593 117, /* Shevat */ 10594 147 /* Adar I */ 10595 ]; 10596 10597 /** 10598 * the cumulative lengths of each month for a leap year, without new years corrections 10599 * @private 10600 * @const 10601 * @type Array.<number> 10602 */ 10603 HebrewRataDie.cumMonthLengthsLeap = [ 10604 206, /* Nisan */ 10605 236, /* Iyyar */ 10606 265, /* Sivan */ 10607 295, /* Tammuz */ 10608 324, /* Av */ 10609 354, /* Elul */ 10610 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10611 30, /* Heshvan */ 10612 59, /* Kislev */ 10613 88, /* Teveth */ 10614 117, /* Shevat */ 10615 147, /* Adar I */ 10616 177 /* Adar II */ 10617 ]; 10618 10619 /** 10620 * Calculate the Rata Die (fixed day) number of the given date from the 10621 * date components. 10622 * 10623 * @private 10624 * @param {Object} date the date components to calculate the RD from 10625 */ 10626 HebrewRataDie.prototype._setDateComponents = function(date) { 10627 var elapsed = HebrewCal.elapsedDays(date.year); 10628 var days = elapsed + 10629 HebrewCal.newYearsCorrection(date.year, elapsed) + 10630 date.day - 1; 10631 var sum = 0, table; 10632 10633 //console.log("getRataDie: converting " + JSON.stringify(date)); 10634 //console.log("getRataDie: days is " + days); 10635 //console.log("getRataDie: new years correction is " + HebrewCal.newYearsCorrection(date.year, elapsed)); 10636 10637 table = this.cal.isLeapYear(date.year) ? 10638 HebrewRataDie.cumMonthLengthsLeap : 10639 HebrewRataDie.cumMonthLengths; 10640 sum = table[date.month-1]; 10641 10642 // gets cumulative without correction, so now add in the correction 10643 if ((date.month < 7 || date.month > 8) && HebrewCal.longHeshvan(date.year)) { 10644 sum++; 10645 } 10646 if ((date.month < 7 || date.month > 9) && HebrewCal.longKislev(date.year)) { 10647 sum++; 10648 } 10649 // console.log("getRataDie: cum days is now " + sum); 10650 10651 days += sum; 10652 10653 // the date starts at sunset, which we take as 18:00, so the hours from 10654 // midnight to 18:00 are on the current Gregorian day, and the hours from 10655 // 18:00 to midnight are on the previous Gregorian day. So to calculate the 10656 // number of hours into the current day that this time represents, we have 10657 // to count from 18:00 to midnight first, and add in 6 hours if the time is 10658 // less than 18:00 10659 var minute, second, millisecond; 10660 10661 if (typeof(date.parts) !== 'undefined') { 10662 // The parts (halaqim) of the hour. This can be a number from 0 to 1079. 10663 var parts = parseInt(date.parts, 10); 10664 var seconds = parseInt(parts, 10) * 3.333333333333; 10665 minute = Math.floor(seconds / 60); 10666 seconds -= minute * 60; 10667 second = Math.floor(seconds); 10668 millisecond = (seconds - second); 10669 } else { 10670 minute = parseInt(date.minute, 10) || 0; 10671 second = parseInt(date.second, 10) || 0; 10672 millisecond = parseInt(date.millisecond, 10) || 0; 10673 } 10674 10675 var time; 10676 if (date.hour >= 18) { 10677 time = ((date.hour - 18 || 0) * 3600000 + 10678 (minute || 0) * 60000 + 10679 (second || 0) * 1000 + 10680 (millisecond || 0)) / 10681 86400000; 10682 } else { 10683 time = 0.25 + // 6 hours from 18:00 to midnight on the previous gregorian day 10684 ((date.hour || 0) * 3600000 + 10685 (minute || 0) * 60000 + 10686 (second || 0) * 1000 + 10687 (millisecond || 0)) / 10688 86400000; 10689 } 10690 10691 //console.log("getRataDie: rd is " + (days + time)); 10692 this.rd = days + time; 10693 }; 10694 10695 /** 10696 * Return the rd number of the particular day of the week on or before the 10697 * given rd. eg. The Sunday on or before the given rd. 10698 * @private 10699 * @param {number} rd the rata die date of the reference date 10700 * @param {number} dayOfWeek the day of the week that is being sought relative 10701 * to the current date 10702 * @return {number} the rd of the day of the week 10703 */ 10704 HebrewRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 10705 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek + 1, 7); 10706 }; 10707 10708 10709 10710 /*< HebrewDate.js */ 10711 /* 10712 * HebrewDate.js - Represent a date in the Hebrew calendar 10713 * 10714 * Copyright © 2012-2015, JEDLSoft 10715 * 10716 * Licensed under the Apache License, Version 2.0 (the "License"); 10717 * you may not use this file except in compliance with the License. 10718 * You may obtain a copy of the License at 10719 * 10720 * http://www.apache.org/licenses/LICENSE-2.0 10721 * 10722 * Unless required by applicable law or agreed to in writing, software 10723 * distributed under the License is distributed on an "AS IS" BASIS, 10724 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10725 * 10726 * See the License for the specific language governing permissions and 10727 * limitations under the License. 10728 */ 10729 10730 /* !depends 10731 ilib.js 10732 Locale.js 10733 LocaleInfo.js 10734 TimeZone.js 10735 IDate.js 10736 MathUtils.js 10737 Calendar.js 10738 HebrewCal.js 10739 HebrewRataDie.js 10740 */ 10741 10742 10743 10744 10745 /** 10746 * @class 10747 * Construct a new civil Hebrew date object. The constructor can be called 10748 * with a params object that can contain the following properties:<p> 10749 * 10750 * <ul> 10751 * <li><i>julianday</i> - the Julian Day to set into this date 10752 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year 10753 * <li><i>month</i> - 1 to 12, where 1 means Nisan, 2 means Iyyar, etc. 10754 * <li><i>day</i> - 1 to 30 10755 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 10756 * is always done with an unambiguous 24 hour representation 10757 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 10758 * the parts or specify the minutes, seconds, and milliseconds, but not both. 10759 * <li><i>minute</i> - 0 to 59 10760 * <li><i>second</i> - 0 to 59 10761 * <li><i>millisecond</i> - 0 to 999 10762 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 10763 * of this julian date. The date/time is kept in the local time. The time zone 10764 * is used later if this date is formatted according to a different time zone and 10765 * the difference has to be calculated, or when the date format has a time zone 10766 * component in it. 10767 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 10768 * given, it can be inferred from this locale. For locales that span multiple 10769 * time zones, the one with the largest population is chosen as the one that 10770 * represents the locale. 10771 * 10772 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 10773 * </ul> 10774 * 10775 * If called with another Hebrew date argument, the date components of the given 10776 * date are copied into the current one.<p> 10777 * 10778 * If the constructor is called with no arguments at all or if none of the 10779 * properties listed above 10780 * from <i>julianday</i> through <i>millisecond</i> are present, then the date 10781 * components are 10782 * filled in with the current date at the time of instantiation. Note that if 10783 * you do not give the time zone when defaulting to the current time and the 10784 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 10785 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 10786 * Mean Time").<p> 10787 * 10788 * 10789 * @constructor 10790 * @extends IDate 10791 * @param {Object=} params parameters that govern the settings and behaviour of this Hebrew date 10792 */ 10793 var HebrewDate = function(params) { 10794 this.cal = new HebrewCal(); 10795 10796 if (params) { 10797 if (params.timezone) { 10798 this.timezone = params.timezone; 10799 } 10800 if (params.locale) { 10801 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 10802 if (!this.timezone) { 10803 var li = new LocaleInfo(this.locale); 10804 this.timezone = li.getTimeZone(); 10805 } 10806 } 10807 10808 if (params.year || params.month || params.day || params.hour || 10809 params.minute || params.second || params.millisecond || params.parts ) { 10810 /** 10811 * Year in the Hebrew calendar. 10812 * @type number 10813 */ 10814 this.year = parseInt(params.year, 10) || 0; 10815 10816 /** 10817 * The month number, ranging from 1 to 13. 10818 * @type number 10819 */ 10820 this.month = parseInt(params.month, 10) || 1; 10821 10822 /** 10823 * The day of the month. This ranges from 1 to 30. 10824 * @type number 10825 */ 10826 this.day = parseInt(params.day, 10) || 1; 10827 10828 /** 10829 * The hour of the day. This can be a number from 0 to 23, as times are 10830 * stored unambiguously in the 24-hour clock. 10831 * @type number 10832 */ 10833 this.hour = parseInt(params.hour, 10) || 0; 10834 10835 if (typeof(params.parts) !== 'undefined') { 10836 /** 10837 * The parts (halaqim) of the hour. This can be a number from 0 to 1079. 10838 * @type number 10839 */ 10840 this.parts = parseInt(params.parts, 10); 10841 var seconds = parseInt(params.parts, 10) * 3.333333333333; 10842 this.minute = Math.floor(seconds / 60); 10843 seconds -= this.minute * 60; 10844 this.second = Math.floor(seconds); 10845 this.millisecond = (seconds - this.second); 10846 } else { 10847 /** 10848 * The minute of the hours. Ranges from 0 to 59. 10849 * @type number 10850 */ 10851 this.minute = parseInt(params.minute, 10) || 0; 10852 10853 /** 10854 * The second of the minute. Ranges from 0 to 59. 10855 * @type number 10856 */ 10857 this.second = parseInt(params.second, 10) || 0; 10858 10859 /** 10860 * The millisecond of the second. Ranges from 0 to 999. 10861 * @type number 10862 */ 10863 this.millisecond = parseInt(params.millisecond, 10) || 0; 10864 } 10865 10866 /** 10867 * The day of the year. Ranges from 1 to 383. 10868 * @type number 10869 */ 10870 this.dayOfYear = parseInt(params.dayOfYear, 10); 10871 10872 if (typeof(params.dst) === 'boolean') { 10873 this.dst = params.dst; 10874 } 10875 10876 this.rd = this.newRd(this); 10877 10878 // add the time zone offset to the rd to convert to UTC 10879 if (!this.tz) { 10880 this.tz = new TimeZone({id: this.timezone}); 10881 } 10882 // getOffsetMillis requires that this.year, this.rd, and this.dst 10883 // are set in order to figure out which time zone rules apply and 10884 // what the offset is at that point in the year 10885 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 10886 if (this.offset !== 0) { 10887 this.rd = this.newRd({ 10888 rd: this.rd.getRataDie() - this.offset 10889 }); 10890 } 10891 } 10892 } 10893 10894 if (!this.rd) { 10895 this.rd = this.newRd(params); 10896 this._calcDateComponents(); 10897 } 10898 }; 10899 10900 HebrewDate.prototype = new IDate({noinstance: true}); 10901 HebrewDate.prototype.parent = IDate; 10902 HebrewDate.prototype.constructor = HebrewDate; 10903 10904 /** 10905 * the cumulative lengths of each month for a non-leap year, without new years corrections, 10906 * that can be used in reverse to map days to months 10907 * @private 10908 * @const 10909 * @type Array.<number> 10910 */ 10911 HebrewDate.cumMonthLengthsReverse = [ 10912 // [days, monthnumber], 10913 [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10914 [30, 8], /* Heshvan */ 10915 [59, 9], /* Kislev */ 10916 [88, 10], /* Teveth */ 10917 [117, 11], /* Shevat */ 10918 [147, 12], /* Adar I */ 10919 [176, 1], /* Nisan */ 10920 [206, 2], /* Iyyar */ 10921 [235, 3], /* Sivan */ 10922 [265, 4], /* Tammuz */ 10923 [294, 5], /* Av */ 10924 [324, 6], /* Elul */ 10925 [354, 7] /* end of year sentinel value */ 10926 ]; 10927 10928 /** 10929 * the cumulative lengths of each month for a leap year, without new years corrections 10930 * that can be used in reverse to map days to months 10931 * 10932 * @private 10933 * @const 10934 * @type Array.<number> 10935 */ 10936 HebrewDate.cumMonthLengthsLeapReverse = [ 10937 // [days, monthnumber], 10938 [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10939 [30, 8], /* Heshvan */ 10940 [59, 9], /* Kislev */ 10941 [88, 10], /* Teveth */ 10942 [117, 11], /* Shevat */ 10943 [147, 12], /* Adar I */ 10944 [177, 13], /* Adar II */ 10945 [206, 1], /* Nisan */ 10946 [236, 2], /* Iyyar */ 10947 [265, 3], /* Sivan */ 10948 [295, 4], /* Tammuz */ 10949 [324, 5], /* Av */ 10950 [354, 6], /* Elul */ 10951 [384, 7] /* end of year sentinel value */ 10952 ]; 10953 10954 /** 10955 * Number of days difference between RD 0 of the Hebrew calendar 10956 * (Jan 1, 1 Gregorian = JD 1721057.5) and RD 0 of the Hebrew calendar 10957 * (September 7, -3760 Gregorian = JD 347997.25) 10958 * @private 10959 * @const 10960 * @type number 10961 */ 10962 HebrewDate.GregorianDiff = 1373060.25; 10963 10964 /** 10965 * Return a new RD for this date type using the given params. 10966 * @private 10967 * @param {Object=} params the parameters used to create this rata die instance 10968 * @returns {RataDie} the new RD instance for the given params 10969 */ 10970 HebrewDate.prototype.newRd = function (params) { 10971 return new HebrewRataDie(params); 10972 }; 10973 10974 /** 10975 * Return the year for the given RD 10976 * @protected 10977 * @param {number} rd RD to calculate from 10978 * @returns {number} the year for the RD 10979 */ 10980 HebrewDate.prototype._calcYear = function(rd) { 10981 var year, approximation, nextNewYear; 10982 10983 // divide by the average number of days per year in the Hebrew calendar 10984 // to approximate the year, then tweak it to get the real year 10985 approximation = Math.floor(rd / 365.246822206) + 1; 10986 10987 // console.log("HebrewDate._calcYear: approx is " + approximation); 10988 10989 // search forward from approximation-1 for the year that actually contains this rd 10990 year = approximation; 10991 nextNewYear = HebrewCal.newYear(year); 10992 while (rd >= nextNewYear) { 10993 year++; 10994 nextNewYear = HebrewCal.newYear(year); 10995 } 10996 return year - 1; 10997 }; 10998 10999 /** 11000 * Calculate date components for the given RD date. 11001 * @protected 11002 */ 11003 HebrewDate.prototype._calcDateComponents = function () { 11004 var remainder, 11005 i, 11006 table, 11007 target, 11008 rd = this.rd.getRataDie(); 11009 11010 // console.log("HebrewDate.calcComponents: calculating for rd " + rd); 11011 11012 if (typeof(this.offset) === "undefined") { 11013 this.year = this._calcYear(rd); 11014 11015 // now offset the RD by the time zone, then recalculate in case we were 11016 // near the year boundary 11017 if (!this.tz) { 11018 this.tz = new TimeZone({id: this.timezone}); 11019 } 11020 this.offset = this.tz.getOffsetMillis(this) / 86400000; 11021 } 11022 11023 if (this.offset !== 0) { 11024 rd += this.offset; 11025 this.year = this._calcYear(rd); 11026 } 11027 11028 // console.log("HebrewDate.calcComponents: year is " + this.year + " with starting rd " + thisNewYear); 11029 11030 remainder = rd - HebrewCal.newYear(this.year); 11031 // console.log("HebrewDate.calcComponents: remainder is " + remainder); 11032 11033 // take out new years corrections so we get the right month when we look it up in the table 11034 if (remainder >= 59) { 11035 if (remainder >= 88) { 11036 if (HebrewCal.longKislev(this.year)) { 11037 remainder--; 11038 } 11039 } 11040 if (HebrewCal.longHeshvan(this.year)) { 11041 remainder--; 11042 } 11043 } 11044 11045 // console.log("HebrewDate.calcComponents: after new years corrections, remainder is " + remainder); 11046 11047 table = this.cal.isLeapYear(this.year) ? 11048 HebrewDate.cumMonthLengthsLeapReverse : 11049 HebrewDate.cumMonthLengthsReverse; 11050 11051 i = 0; 11052 target = Math.floor(remainder); 11053 while (i+1 < table.length && target >= table[i+1][0]) { 11054 i++; 11055 } 11056 11057 this.month = table[i][1]; 11058 // console.log("HebrewDate.calcComponents: remainder is " + remainder); 11059 remainder -= table[i][0]; 11060 11061 // console.log("HebrewDate.calcComponents: month is " + this.month + " and remainder is " + remainder); 11062 11063 this.day = Math.floor(remainder); 11064 remainder -= this.day; 11065 this.day++; // days are 1-based 11066 11067 // console.log("HebrewDate.calcComponents: day is " + this.day + " and remainder is " + remainder); 11068 11069 // now convert to milliseconds for the rest of the calculation 11070 remainder = Math.round(remainder * 86400000); 11071 11072 this.hour = Math.floor(remainder/3600000); 11073 remainder -= this.hour * 3600000; 11074 11075 // the hours from 0 to 6 are actually 18:00 to midnight of the previous 11076 // gregorian day, so we have to adjust for that 11077 if (this.hour >= 6) { 11078 this.hour -= 6; 11079 } else { 11080 this.hour += 18; 11081 } 11082 11083 this.minute = Math.floor(remainder/60000); 11084 remainder -= this.minute * 60000; 11085 11086 this.second = Math.floor(remainder/1000); 11087 remainder -= this.second * 1000; 11088 11089 this.millisecond = Math.floor(remainder); 11090 }; 11091 11092 /** 11093 * Return the day of the week of this date. The day of the week is encoded 11094 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 11095 * 11096 * @return {number} the day of the week 11097 */ 11098 HebrewDate.prototype.getDayOfWeek = function() { 11099 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 11100 return MathUtils.mod(rd+1, 7); 11101 }; 11102 11103 /** 11104 * Get the Halaqim (parts) of an hour. There are 1080 parts in an hour, which means 11105 * each part is 3.33333333 seconds long. This means the number returned may not 11106 * be an integer. 11107 * 11108 * @return {number} the halaqim parts of the current hour 11109 */ 11110 HebrewDate.prototype.getHalaqim = function() { 11111 if (this.parts < 0) { 11112 // convert to ms first, then to parts 11113 var h = this.minute * 60000 + this.second * 1000 + this.millisecond; 11114 this.parts = (h * 0.0003); 11115 } 11116 return this.parts; 11117 }; 11118 11119 /** 11120 * Return the rd number of the first Sunday of the given ISO year. 11121 * @protected 11122 * @return the rd of the first Sunday of the ISO year 11123 */ 11124 HebrewDate.prototype.firstSunday = function (year) { 11125 var tishri1 = this.newRd({ 11126 year: year, 11127 month: 7, 11128 day: 1, 11129 hour: 18, 11130 minute: 0, 11131 second: 0, 11132 millisecond: 0, 11133 cal: this.cal 11134 }); 11135 var firstThu = this.newRd({ 11136 rd: tishri1.onOrAfter(4), 11137 cal: this.cal 11138 }); 11139 return firstThu.before(0); 11140 }; 11141 11142 /** 11143 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 11144 * 385, regardless of months or weeks, etc. That is, Tishri 1st is day 1, and 11145 * Elul 29 is 385 for a leap year with a long Heshvan and long Kislev. 11146 * @return {number} the ordinal day of the year 11147 */ 11148 HebrewDate.prototype.getDayOfYear = function() { 11149 var table = this.cal.isLeapYear(this.year) ? 11150 HebrewRataDie.cumMonthLengthsLeap : 11151 HebrewRataDie.cumMonthLengths; 11152 var days = table[this.month-1]; 11153 if ((this.month < 7 || this.month > 8) && HebrewCal.longHeshvan(this.year)) { 11154 days++; 11155 } 11156 if ((this.month < 7 || this.month > 9) && HebrewCal.longKislev(this.year)) { 11157 days++; 11158 } 11159 11160 return days + this.day; 11161 }; 11162 11163 /** 11164 * Return the ordinal number of the week within the month. The first week of a month is 11165 * the first one that contains 4 or more days in that month. If any days precede this 11166 * first week, they are marked as being in week 0. This function returns values from 0 11167 * through 6.<p> 11168 * 11169 * The locale is a required parameter because different locales that use the same 11170 * Hebrew calendar consider different days of the week to be the beginning of 11171 * the week. This can affect the week of the month in which some days are located. 11172 * 11173 * @param {Locale|string} locale the locale or locale spec to use when figuring out 11174 * the first day of the week 11175 * @return {number} the ordinal number of the week within the current month 11176 */ 11177 HebrewDate.prototype.getWeekOfMonth = function(locale) { 11178 var li = new LocaleInfo(locale), 11179 first = this.newRd({ 11180 year: this.year, 11181 month: this.month, 11182 day: 1, 11183 hour: 18, 11184 minute: 0, 11185 second: 0, 11186 millisecond: 0 11187 }), 11188 rd = this.rd.getRataDie(), 11189 weekStart = first.onOrAfter(li.getFirstDayOfWeek()); 11190 11191 if (weekStart - first.getRataDie() > 3) { 11192 // if the first week has 4 or more days in it of the current month, then consider 11193 // that week 1. Otherwise, it is week 0. To make it week 1, move the week start 11194 // one week earlier. 11195 weekStart -= 7; 11196 } 11197 return (rd < weekStart) ? 0 : Math.floor((rd - weekStart) / 7) + 1; 11198 }; 11199 11200 /** 11201 * Return the era for this date as a number. The value for the era for Hebrew 11202 * calendars is -1 for "before the Hebrew era" and 1 for "the Hebrew era". 11203 * Hebrew era dates are any date after Tishri 1, 1, which is the same as 11204 * September 7, 3760 BC in the Gregorian calendar. 11205 * 11206 * @return {number} 1 if this date is in the Hebrew era, -1 if it is before the 11207 * Hebrew era 11208 */ 11209 HebrewDate.prototype.getEra = function() { 11210 return (this.year < 1) ? -1 : 1; 11211 }; 11212 11213 /** 11214 * Return the name of the calendar that governs this date. 11215 * 11216 * @return {string} a string giving the name of the calendar 11217 */ 11218 HebrewDate.prototype.getCalendar = function() { 11219 return "hebrew"; 11220 }; 11221 11222 // register with the factory method 11223 IDate._constructors["hebrew"] = HebrewDate; 11224 11225 11226 11227 /*< IslamicCal.js */ 11228 /* 11229 * islamic.js - Represent a Islamic calendar object. 11230 * 11231 * Copyright © 2012-2015, JEDLSoft 11232 * 11233 * Licensed under the Apache License, Version 2.0 (the "License"); 11234 * you may not use this file except in compliance with the License. 11235 * You may obtain a copy of the License at 11236 * 11237 * http://www.apache.org/licenses/LICENSE-2.0 11238 * 11239 * Unless required by applicable law or agreed to in writing, software 11240 * distributed under the License is distributed on an "AS IS" BASIS, 11241 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11242 * 11243 * See the License for the specific language governing permissions and 11244 * limitations under the License. 11245 */ 11246 11247 11248 /* !depends 11249 ilib.js 11250 Calendar.js 11251 MathUtils.js 11252 */ 11253 11254 11255 /** 11256 * @class 11257 * Construct a new Islamic calendar object. This class encodes information about 11258 * the civil Islamic calendar. The civil Islamic calendar is a tabular islamic 11259 * calendar where the dates are calculated by arithmetic rules. This differs from 11260 * the religious Islamic calendar which is used to mark the beginning of particular 11261 * holidays. The religious calendar depends on the first sighting of the new 11262 * crescent moon to determine the first day of the new month. Because humans and 11263 * weather are both involved, the actual time of sighting varies, so it is not 11264 * really possible to precalculate the religious calendar. Certain groups, such 11265 * as the Islamic Society of North America, decreed in in 2007 that they will use 11266 * a calendar based on calculations rather than observations to determine the 11267 * beginning of lunar months, and therefore the dates of holidays.<p> 11268 * 11269 * 11270 * @constructor 11271 * @extends Calendar 11272 */ 11273 var IslamicCal = function() { 11274 this.type = "islamic"; 11275 }; 11276 11277 /** 11278 * the lengths of each month 11279 * @private 11280 * @const 11281 * @type Array.<number> 11282 */ 11283 IslamicCal.monthLengths = [ 11284 30, /* Muharram */ 11285 29, /* Saffar */ 11286 30, /* Rabi'I */ 11287 29, /* Rabi'II */ 11288 30, /* Jumada I */ 11289 29, /* Jumada II */ 11290 30, /* Rajab */ 11291 29, /* Sha'ban */ 11292 30, /* Ramadan */ 11293 29, /* Shawwal */ 11294 30, /* Dhu al-Qa'da */ 11295 29 /* Dhu al-Hijja */ 11296 ]; 11297 11298 11299 /** 11300 * Return the number of months in the given year. The number of months in a year varies 11301 * for luni-solar calendars because in some years, an extra month is needed to extend the 11302 * days in a year to an entire solar year. The month is represented as a 1-based number 11303 * where 1=first month, 2=second month, etc. 11304 * 11305 * @param {number} year a year for which the number of months is sought 11306 */ 11307 IslamicCal.prototype.getNumMonths = function(year) { 11308 return 12; 11309 }; 11310 11311 /** 11312 * Return the number of days in a particular month in a particular year. This function 11313 * can return a different number for a month depending on the year because of things 11314 * like leap years. 11315 * 11316 * @param {number} month the month for which the length is sought 11317 * @param {number} year the year within which that month can be found 11318 * @return {number} the number of days within the given month in the given year 11319 */ 11320 IslamicCal.prototype.getMonLength = function(month, year) { 11321 if (month !== 12) { 11322 return IslamicCal.monthLengths[month-1]; 11323 } else { 11324 return this.isLeapYear(year) ? 30 : 29; 11325 } 11326 }; 11327 11328 /** 11329 * Return true if the given year is a leap year in the Islamic calendar. 11330 * The year parameter may be given as a number, or as a IslamicDate object. 11331 * @param {number} year the year for which the leap year information is being sought 11332 * @return {boolean} true if the given year is a leap year 11333 */ 11334 IslamicCal.prototype.isLeapYear = function(year) { 11335 return (MathUtils.mod((14 + 11 * year), 30) < 11); 11336 }; 11337 11338 /** 11339 * Return the type of this calendar. 11340 * 11341 * @return {string} the name of the type of this calendar 11342 */ 11343 IslamicCal.prototype.getType = function() { 11344 return this.type; 11345 }; 11346 11347 /** 11348 * Return a date instance for this calendar type using the given 11349 * options. 11350 * @param {Object} options options controlling the construction of 11351 * the date instance 11352 * @return {IslamicDate} a date appropriate for this calendar type 11353 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 11354 */ 11355 IslamicCal.prototype.newDateInstance = function (options) { 11356 return new IslamicDate(options); 11357 }; 11358 11359 /*register this calendar for the factory method */ 11360 Calendar._constructors["islamic"] = IslamicCal; 11361 11362 11363 /*< IslamicRataDie.js */ 11364 /* 11365 * IslamicRataDie.js - Represent an RD date in the Islamic calendar 11366 * 11367 * Copyright © 2012-2015, JEDLSoft 11368 * 11369 * Licensed under the Apache License, Version 2.0 (the "License"); 11370 * you may not use this file except in compliance with the License. 11371 * You may obtain a copy of the License at 11372 * 11373 * http://www.apache.org/licenses/LICENSE-2.0 11374 * 11375 * Unless required by applicable law or agreed to in writing, software 11376 * distributed under the License is distributed on an "AS IS" BASIS, 11377 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11378 * 11379 * See the License for the specific language governing permissions and 11380 * limitations under the License. 11381 */ 11382 11383 /* !depends 11384 IslamicCal.js 11385 RataDie.js 11386 */ 11387 11388 11389 /** 11390 * @class 11391 * Construct a new Islamic RD date number object. The constructor parameters can 11392 * contain any of the following properties: 11393 * 11394 * <ul> 11395 * <li><i>unixtime<i> - sets the time of this instance according to the given 11396 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 11397 * 11398 * <li><i>julianday</i> - sets the time of this instance according to the given 11399 * Julian Day instance or the Julian Day given as a float 11400 * 11401 * <li><i>year</i> - any integer, including 0 11402 * 11403 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 11404 * 11405 * <li><i>day</i> - 1 to 31 11406 * 11407 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11408 * is always done with an unambiguous 24 hour representation 11409 * 11410 * <li><i>minute</i> - 0 to 59 11411 * 11412 * <li><i>second</i> - 0 to 59 11413 * 11414 * <li><i>millisecond</i> - 0 to 999 11415 * 11416 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11417 * </ul> 11418 * 11419 * If the constructor is called with another Islamic date instance instead of 11420 * a parameter block, the other instance acts as a parameter block and its 11421 * settings are copied into the current instance.<p> 11422 * 11423 * If the constructor is called with no arguments at all or if none of the 11424 * properties listed above are present, then the RD is calculate based on 11425 * the current date at the time of instantiation. <p> 11426 * 11427 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 11428 * specified in the params, it is assumed that they have the smallest possible 11429 * value in the range for the property (zero or one).<p> 11430 * 11431 * 11432 * @private 11433 * @constructor 11434 * @extends RataDie 11435 * @param {Object=} params parameters that govern the settings and behaviour of this Islamic RD date 11436 */ 11437 var IslamicRataDie = function(params) { 11438 this.cal = params && params.cal || new IslamicCal(); 11439 this.rd = NaN; 11440 RataDie.call(this, params); 11441 }; 11442 11443 IslamicRataDie.prototype = new RataDie(); 11444 IslamicRataDie.prototype.parent = RataDie; 11445 IslamicRataDie.prototype.constructor = IslamicRataDie; 11446 11447 /** 11448 * The difference between a zero Julian day and the first Islamic date 11449 * of Friday, July 16, 622 CE Julian. 11450 * @private 11451 * @type number 11452 */ 11453 IslamicRataDie.prototype.epoch = 1948439.5; 11454 11455 /** 11456 * Calculate the Rata Die (fixed day) number of the given date from the 11457 * date components. 11458 * 11459 * @protected 11460 * @param {Object} date the date components to calculate the RD from 11461 */ 11462 IslamicRataDie.prototype._setDateComponents = function(date) { 11463 var days = (date.year - 1) * 354 + 11464 Math.ceil(29.5 * (date.month - 1)) + 11465 date.day + 11466 Math.floor((3 + 11 * date.year) / 30) - 1; 11467 var time = (date.hour * 3600000 + 11468 date.minute * 60000 + 11469 date.second * 1000 + 11470 date.millisecond) / 11471 86400000; 11472 11473 //console.log("getRataDie: converting " + JSON.stringify(date)); 11474 //console.log("getRataDie: days is " + days); 11475 //console.log("getRataDie: time is " + time); 11476 //console.log("getRataDie: rd is " + (days + time)); 11477 11478 this.rd = days + time; 11479 }; 11480 11481 11482 /*< IslamicDate.js */ 11483 /* 11484 * islamicDate.js - Represent a date in the Islamic calendar 11485 * 11486 * Copyright © 2012-2015, JEDLSoft 11487 * 11488 * Licensed under the Apache License, Version 2.0 (the "License"); 11489 * you may not use this file except in compliance with the License. 11490 * You may obtain a copy of the License at 11491 * 11492 * http://www.apache.org/licenses/LICENSE-2.0 11493 * 11494 * Unless required by applicable law or agreed to in writing, software 11495 * distributed under the License is distributed on an "AS IS" BASIS, 11496 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11497 * 11498 * See the License for the specific language governing permissions and 11499 * limitations under the License. 11500 */ 11501 11502 /* !depends 11503 ilib.js 11504 Locale.js 11505 LocaleInfo.js 11506 TimeZone.js 11507 IDate.js 11508 MathUtils.js 11509 SearchUtils.js 11510 Calendar.js 11511 IslamicCal.js 11512 IslamicRataDie.js 11513 */ 11514 11515 11516 11517 11518 /** 11519 * @class 11520 * Construct a new civil Islamic date object. The constructor can be called 11521 * with a params object that can contain the following properties:<p> 11522 * 11523 * <ul> 11524 * <li><i>julianday</i> - the Julian Day to set into this date 11525 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year 11526 * <li><i>month</i> - 1 to 12, where 1 means Muharram, 2 means Saffar, etc. 11527 * <li><i>day</i> - 1 to 30 11528 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11529 * is always done with an unambiguous 24 hour representation 11530 * <li><i>minute</i> - 0 to 59 11531 * <li><i>second</i> - 0 to 59 11532 * <li><i>millisecond</i> - 0 to 999 11533 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 11534 * of this julian date. The date/time is kept in the local time. The time zone 11535 * is used later if this date is formatted according to a different time zone and 11536 * the difference has to be calculated, or when the date format has a time zone 11537 * component in it. 11538 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 11539 * given, it can be inferred from this locale. For locales that span multiple 11540 * time zones, the one with the largest population is chosen as the one that 11541 * represents the locale. 11542 * 11543 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11544 * </ul> 11545 * 11546 * If called with another Islamic date argument, the date components of the given 11547 * date are copied into the current one.<p> 11548 * 11549 * If the constructor is called with no arguments at all or if none of the 11550 * properties listed above 11551 * from <i>julianday</i> through <i>millisecond</i> are present, then the date 11552 * components are 11553 * filled in with the current date at the time of instantiation. Note that if 11554 * you do not give the time zone when defaulting to the current time and the 11555 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 11556 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 11557 * Mean Time").<p> 11558 * 11559 * 11560 * @constructor 11561 * @extends IDate 11562 * @param {Object=} params parameters that govern the settings and behaviour of this Islamic date 11563 */ 11564 var IslamicDate = function(params) { 11565 this.cal = new IslamicCal(); 11566 11567 if (params) { 11568 if (params.locale) { 11569 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 11570 var li = new LocaleInfo(this.locale); 11571 this.timezone = li.getTimeZone(); 11572 } 11573 if (params.timezone) { 11574 this.timezone = params.timezone; 11575 } 11576 11577 if (params.year || params.month || params.day || params.hour || 11578 params.minute || params.second || params.millisecond ) { 11579 /** 11580 * Year in the Islamic calendar. 11581 * @type number 11582 */ 11583 this.year = parseInt(params.year, 10) || 0; 11584 11585 /** 11586 * The month number, ranging from 1 to 12 (December). 11587 * @type number 11588 */ 11589 this.month = parseInt(params.month, 10) || 1; 11590 11591 /** 11592 * The day of the month. This ranges from 1 to 30. 11593 * @type number 11594 */ 11595 this.day = parseInt(params.day, 10) || 1; 11596 11597 /** 11598 * The hour of the day. This can be a number from 0 to 23, as times are 11599 * stored unambiguously in the 24-hour clock. 11600 * @type number 11601 */ 11602 this.hour = parseInt(params.hour, 10) || 0; 11603 11604 /** 11605 * The minute of the hours. Ranges from 0 to 59. 11606 * @type number 11607 */ 11608 this.minute = parseInt(params.minute, 10) || 0; 11609 11610 /** 11611 * The second of the minute. Ranges from 0 to 59. 11612 * @type number 11613 */ 11614 this.second = parseInt(params.second, 10) || 0; 11615 11616 /** 11617 * The millisecond of the second. Ranges from 0 to 999. 11618 * @type number 11619 */ 11620 this.millisecond = parseInt(params.millisecond, 10) || 0; 11621 11622 /** 11623 * The day of the year. Ranges from 1 to 355. 11624 * @type number 11625 */ 11626 this.dayOfYear = parseInt(params.dayOfYear, 10); 11627 11628 if (typeof(params.dst) === 'boolean') { 11629 this.dst = params.dst; 11630 } 11631 11632 this.rd = this.newRd(this); 11633 11634 // add the time zone offset to the rd to convert to UTC 11635 if (!this.tz) { 11636 this.tz = new TimeZone({id: this.timezone}); 11637 } 11638 // getOffsetMillis requires that this.year, this.rd, and this.dst 11639 // are set in order to figure out which time zone rules apply and 11640 // what the offset is at that point in the year 11641 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 11642 if (this.offset !== 0) { 11643 this.rd = this.newRd({ 11644 rd: this.rd.getRataDie() - this.offset 11645 }); 11646 } 11647 } 11648 } 11649 11650 if (!this.rd) { 11651 this.rd = this.newRd(params); 11652 this._calcDateComponents(); 11653 } 11654 }; 11655 11656 IslamicDate.prototype = new IDate({noinstance: true}); 11657 IslamicDate.prototype.parent = IDate; 11658 IslamicDate.prototype.constructor = IslamicDate; 11659 11660 /** 11661 * the cumulative lengths of each month, for a non-leap year 11662 * @private 11663 * @const 11664 * @type Array.<number> 11665 */ 11666 IslamicDate.cumMonthLengths = [ 11667 0, /* Muharram */ 11668 30, /* Saffar */ 11669 59, /* Rabi'I */ 11670 89, /* Rabi'II */ 11671 118, /* Jumada I */ 11672 148, /* Jumada II */ 11673 177, /* Rajab */ 11674 207, /* Sha'ban */ 11675 236, /* Ramadan */ 11676 266, /* Shawwal */ 11677 295, /* Dhu al-Qa'da */ 11678 325, /* Dhu al-Hijja */ 11679 354 11680 ]; 11681 11682 /** 11683 * Number of days difference between RD 0 of the Gregorian calendar and 11684 * RD 0 of the Islamic calendar. 11685 * @private 11686 * @const 11687 * @type number 11688 */ 11689 IslamicDate.GregorianDiff = 227015; 11690 11691 /** 11692 * Return a new RD for this date type using the given params. 11693 * @protected 11694 * @param {Object=} params the parameters used to create this rata die instance 11695 * @returns {RataDie} the new RD instance for the given params 11696 */ 11697 IslamicDate.prototype.newRd = function (params) { 11698 return new IslamicRataDie(params); 11699 }; 11700 11701 /** 11702 * Return the year for the given RD 11703 * @protected 11704 * @param {number} rd RD to calculate from 11705 * @returns {number} the year for the RD 11706 */ 11707 IslamicDate.prototype._calcYear = function(rd) { 11708 return Math.floor((30 * rd + 10646) / 10631); 11709 }; 11710 11711 /** 11712 * Calculate date components for the given RD date. 11713 * @protected 11714 */ 11715 IslamicDate.prototype._calcDateComponents = function () { 11716 var remainder, 11717 rd = this.rd.getRataDie(); 11718 11719 this.year = this._calcYear(rd); 11720 11721 if (typeof(this.offset) === "undefined") { 11722 this.year = this._calcYear(rd); 11723 11724 // now offset the RD by the time zone, then recalculate in case we were 11725 // near the year boundary 11726 if (!this.tz) { 11727 this.tz = new TimeZone({id: this.timezone}); 11728 } 11729 this.offset = this.tz.getOffsetMillis(this) / 86400000; 11730 } 11731 11732 if (this.offset !== 0) { 11733 rd += this.offset; 11734 this.year = this._calcYear(rd); 11735 } 11736 11737 //console.log("IslamicDate.calcComponent: calculating for rd " + rd); 11738 //console.log("IslamicDate.calcComponent: year is " + ret.year); 11739 var yearStart = this.newRd({ 11740 year: this.year, 11741 month: 1, 11742 day: 1, 11743 hour: 0, 11744 minute: 0, 11745 second: 0, 11746 millisecond: 0 11747 }); 11748 remainder = rd - yearStart.getRataDie() + 1; 11749 11750 this.dayOfYear = remainder; 11751 11752 //console.log("IslamicDate.calcComponent: remainder is " + remainder); 11753 11754 this.month = SearchUtils.bsearch(remainder, IslamicDate.cumMonthLengths); 11755 remainder -= IslamicDate.cumMonthLengths[this.month-1]; 11756 11757 //console.log("IslamicDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 11758 11759 this.day = Math.floor(remainder); 11760 remainder -= this.day; 11761 11762 //console.log("IslamicDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 11763 11764 // now convert to milliseconds for the rest of the calculation 11765 remainder = Math.round(remainder * 86400000); 11766 11767 this.hour = Math.floor(remainder/3600000); 11768 remainder -= this.hour * 3600000; 11769 11770 this.minute = Math.floor(remainder/60000); 11771 remainder -= this.minute * 60000; 11772 11773 this.second = Math.floor(remainder/1000); 11774 remainder -= this.second * 1000; 11775 11776 this.millisecond = remainder; 11777 }; 11778 11779 /** 11780 * Return the day of the week of this date. The day of the week is encoded 11781 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 11782 * 11783 * @return {number} the day of the week 11784 */ 11785 IslamicDate.prototype.getDayOfWeek = function() { 11786 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 11787 return MathUtils.mod(rd-2, 7); 11788 }; 11789 11790 /** 11791 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 11792 * 354 or 355, regardless of months or weeks, etc. That is, Muharran 1st is day 1, and 11793 * Dhu al-Hijja 29 is 354. 11794 * @return {number} the ordinal day of the year 11795 */ 11796 IslamicDate.prototype.getDayOfYear = function() { 11797 return IslamicDate.cumMonthLengths[this.month-1] + this.day; 11798 }; 11799 11800 /** 11801 * Return the era for this date as a number. The value for the era for Islamic 11802 * calendars is -1 for "before the Islamic era" and 1 for "the Islamic era". 11803 * Islamic era dates are any date after Muharran 1, 1, which is the same as 11804 * July 16, 622 CE in the Gregorian calendar. 11805 * 11806 * @return {number} 1 if this date is in the common era, -1 if it is before the 11807 * common era 11808 */ 11809 IslamicDate.prototype.getEra = function() { 11810 return (this.year < 1) ? -1 : 1; 11811 }; 11812 11813 /** 11814 * Return the name of the calendar that governs this date. 11815 * 11816 * @return {string} a string giving the name of the calendar 11817 */ 11818 IslamicDate.prototype.getCalendar = function() { 11819 return "islamic"; 11820 }; 11821 11822 //register with the factory method 11823 IDate._constructors["islamic"] = IslamicDate; 11824 11825 11826 /*< JulianCal.js */ 11827 /* 11828 * julian.js - Represent a Julian calendar object. 11829 * 11830 * Copyright © 2012-2015, JEDLSoft 11831 * 11832 * Licensed under the Apache License, Version 2.0 (the "License"); 11833 * you may not use this file except in compliance with the License. 11834 * You may obtain a copy of the License at 11835 * 11836 * http://www.apache.org/licenses/LICENSE-2.0 11837 * 11838 * Unless required by applicable law or agreed to in writing, software 11839 * distributed under the License is distributed on an "AS IS" BASIS, 11840 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11841 * 11842 * See the License for the specific language governing permissions and 11843 * limitations under the License. 11844 */ 11845 11846 11847 /* !depends ilib.js Calendar.js MathUtils.js */ 11848 11849 11850 /** 11851 * @class 11852 * Construct a new Julian calendar object. This class encodes information about 11853 * a Julian calendar.<p> 11854 * 11855 * 11856 * @constructor 11857 * @extends Calendar 11858 */ 11859 var JulianCal = function() { 11860 this.type = "julian"; 11861 }; 11862 11863 /* the lengths of each month */ 11864 JulianCal.monthLengths = [ 11865 31, /* Jan */ 11866 28, /* Feb */ 11867 31, /* Mar */ 11868 30, /* Apr */ 11869 31, /* May */ 11870 30, /* Jun */ 11871 31, /* Jul */ 11872 31, /* Aug */ 11873 30, /* Sep */ 11874 31, /* Oct */ 11875 30, /* Nov */ 11876 31 /* Dec */ 11877 ]; 11878 11879 /** 11880 * the cumulative lengths of each month, for a non-leap year 11881 * @private 11882 * @const 11883 * @type Array.<number> 11884 */ 11885 JulianCal.cumMonthLengths = [ 11886 0, /* Jan */ 11887 31, /* Feb */ 11888 59, /* Mar */ 11889 90, /* Apr */ 11890 120, /* May */ 11891 151, /* Jun */ 11892 181, /* Jul */ 11893 212, /* Aug */ 11894 243, /* Sep */ 11895 273, /* Oct */ 11896 304, /* Nov */ 11897 334, /* Dec */ 11898 365 11899 ]; 11900 11901 /** 11902 * the cumulative lengths of each month, for a leap year 11903 * @private 11904 * @const 11905 * @type Array.<number> 11906 */ 11907 JulianCal.cumMonthLengthsLeap = [ 11908 0, /* Jan */ 11909 31, /* Feb */ 11910 60, /* Mar */ 11911 91, /* Apr */ 11912 121, /* May */ 11913 152, /* Jun */ 11914 182, /* Jul */ 11915 213, /* Aug */ 11916 244, /* Sep */ 11917 274, /* Oct */ 11918 305, /* Nov */ 11919 335, /* Dec */ 11920 366 11921 ]; 11922 11923 /** 11924 * Return the number of months in the given year. The number of months in a year varies 11925 * for lunar calendars because in some years, an extra month is needed to extend the 11926 * days in a year to an entire solar year. The month is represented as a 1-based number 11927 * where 1=Jaunary, 2=February, etc. until 12=December. 11928 * 11929 * @param {number} year a year for which the number of months is sought 11930 */ 11931 JulianCal.prototype.getNumMonths = function(year) { 11932 return 12; 11933 }; 11934 11935 /** 11936 * Return the number of days in a particular month in a particular year. This function 11937 * can return a different number for a month depending on the year because of things 11938 * like leap years. 11939 * 11940 * @param {number} month the month for which the length is sought 11941 * @param {number} year the year within which that month can be found 11942 * @return {number} the number of days within the given month in the given year 11943 */ 11944 JulianCal.prototype.getMonLength = function(month, year) { 11945 if (month !== 2 || !this.isLeapYear(year)) { 11946 return JulianCal.monthLengths[month-1]; 11947 } else { 11948 return 29; 11949 } 11950 }; 11951 11952 /** 11953 * Return true if the given year is a leap year in the Julian calendar. 11954 * The year parameter may be given as a number, or as a JulDate object. 11955 * @param {number|JulianDate} year the year for which the leap year information is being sought 11956 * @return {boolean} true if the given year is a leap year 11957 */ 11958 JulianCal.prototype.isLeapYear = function(year) { 11959 var y = (typeof(year) === 'number' ? year : year.year); 11960 return MathUtils.mod(y, 4) === ((year > 0) ? 0 : 3); 11961 }; 11962 11963 /** 11964 * Return the type of this calendar. 11965 * 11966 * @return {string} the name of the type of this calendar 11967 */ 11968 JulianCal.prototype.getType = function() { 11969 return this.type; 11970 }; 11971 11972 /** 11973 * Return a date instance for this calendar type using the given 11974 * options. 11975 * @param {Object} options options controlling the construction of 11976 * the date instance 11977 * @return {IDate} a date appropriate for this calendar type 11978 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 11979 */ 11980 JulianCal.prototype.newDateInstance = function (options) { 11981 return new JulianDate(options); 11982 }; 11983 11984 /* register this calendar for the factory method */ 11985 Calendar._constructors["julian"] = JulianCal; 11986 11987 11988 /*< JulianRataDie.js */ 11989 /* 11990 * julianDate.js - Represent a date in the Julian calendar 11991 * 11992 * Copyright © 2012-2015, JEDLSoft 11993 * 11994 * Licensed under the Apache License, Version 2.0 (the "License"); 11995 * you may not use this file except in compliance with the License. 11996 * You may obtain a copy of the License at 11997 * 11998 * http://www.apache.org/licenses/LICENSE-2.0 11999 * 12000 * Unless required by applicable law or agreed to in writing, software 12001 * distributed under the License is distributed on an "AS IS" BASIS, 12002 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12003 * 12004 * See the License for the specific language governing permissions and 12005 * limitations under the License. 12006 */ 12007 12008 /* !depends 12009 JulianCal.js 12010 RataDie.js 12011 */ 12012 12013 12014 /** 12015 * @class 12016 * Construct a new Julian RD date number object. The constructor parameters can 12017 * contain any of the following properties: 12018 * 12019 * <ul> 12020 * <li><i>unixtime<i> - sets the time of this instance according to the given 12021 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 12022 * 12023 * <li><i>julianday</i> - sets the time of this instance according to the given 12024 * Julian Day instance or the Julian Day given as a float 12025 * 12026 * <li><i>year</i> - any integer, including 0 12027 * 12028 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 12029 * 12030 * <li><i>day</i> - 1 to 31 12031 * 12032 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12033 * is always done with an unambiguous 24 hour representation 12034 * 12035 * <li><i>minute</i> - 0 to 59 12036 * 12037 * <li><i>second</i> - 0 to 59 12038 * 12039 * <li><i>millisecond</i> - 0 to 999 12040 * 12041 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 12042 * </ul> 12043 * 12044 * If the constructor is called with another Julian date instance instead of 12045 * a parameter block, the other instance acts as a parameter block and its 12046 * settings are copied into the current instance.<p> 12047 * 12048 * If the constructor is called with no arguments at all or if none of the 12049 * properties listed above are present, then the RD is calculate based on 12050 * the current date at the time of instantiation. <p> 12051 * 12052 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 12053 * specified in the params, it is assumed that they have the smallest possible 12054 * value in the range for the property (zero or one).<p> 12055 * 12056 * 12057 * @private 12058 * @constructor 12059 * @extends RataDie 12060 * @param {Object=} params parameters that govern the settings and behaviour of this Julian RD date 12061 */ 12062 var JulianRataDie = function(params) { 12063 this.cal = params && params.cal || new JulianCal(); 12064 this.rd = NaN; 12065 RataDie.call(this, params); 12066 }; 12067 12068 JulianRataDie.prototype = new RataDie(); 12069 JulianRataDie.prototype.parent = RataDie; 12070 JulianRataDie.prototype.constructor = JulianRataDie; 12071 12072 /** 12073 * The difference between a zero Julian day and the first Julian date 12074 * of Friday, July 16, 622 CE Julian. 12075 * @private 12076 * @type number 12077 */ 12078 JulianRataDie.prototype.epoch = 1721422.5; 12079 12080 /** 12081 * Calculate the Rata Die (fixed day) number of the given date from the 12082 * date components. 12083 * 12084 * @protected 12085 * @param {Object} date the date components to calculate the RD from 12086 */ 12087 JulianRataDie.prototype._setDateComponents = function(date) { 12088 var year = date.year + ((date.year < 0) ? 1 : 0); 12089 var years = 365 * (year - 1) + Math.floor((year-1)/4); 12090 var dayInYear = (date.month > 1 ? JulianCal.cumMonthLengths[date.month-1] : 0) + 12091 date.day + 12092 (this.cal.isLeapYear(date.year) && date.month > 2 ? 1 : 0); 12093 var rdtime = (date.hour * 3600000 + 12094 date.minute * 60000 + 12095 date.second * 1000 + 12096 date.millisecond) / 12097 86400000; 12098 12099 /* 12100 console.log("calcRataDie: converting " + JSON.stringify(parts)); 12101 console.log("getRataDie: year is " + years); 12102 console.log("getRataDie: day in year is " + dayInYear); 12103 console.log("getRataDie: rdtime is " + rdtime); 12104 console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); 12105 */ 12106 12107 this.rd = years + dayInYear + rdtime; 12108 }; 12109 12110 12111 /*< JulianDate.js */ 12112 /* 12113 * JulianDate.js - Represent a date in the Julian calendar 12114 * 12115 * Copyright © 2012-2015, JEDLSoft 12116 * 12117 * Licensed under the Apache License, Version 2.0 (the "License"); 12118 * you may not use this file except in compliance with the License. 12119 * You may obtain a copy of the License at 12120 * 12121 * http://www.apache.org/licenses/LICENSE-2.0 12122 * 12123 * Unless required by applicable law or agreed to in writing, software 12124 * distributed under the License is distributed on an "AS IS" BASIS, 12125 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12126 * 12127 * See the License for the specific language governing permissions and 12128 * limitations under the License. 12129 */ 12130 12131 /* !depends 12132 ilib.js 12133 Locale.js 12134 IDate.js 12135 TimeZone.js 12136 Calendar.js 12137 JulianCal.js 12138 SearchUtils.js 12139 MathUtils.js 12140 LocaleInfo.js 12141 JulianRataDie.js 12142 */ 12143 12144 12145 12146 12147 /** 12148 * @class 12149 * Construct a new date object for the Julian Calendar. The constructor can be called 12150 * with a parameter object that contains any of the following properties: 12151 * 12152 * <ul> 12153 * <li><i>unixtime<i> - sets the time of this instance according to the given 12154 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 12155 * <li><i>julianday</i> - the Julian Day to set into this date 12156 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero 12157 * year which doesn't exist in the Julian calendar 12158 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 12159 * <li><i>day</i> - 1 to 31 12160 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12161 * is always done with an unambiguous 24 hour representation 12162 * <li><i>minute</i> - 0 to 59 12163 * <li><i>second</i> - 0 to 59 12164 * <li><i>millisecond<i> - 0 to 999 12165 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 12166 * of this julian date. The date/time is kept in the local time. The time zone 12167 * is used later if this date is formatted according to a different time zone and 12168 * the difference has to be calculated, or when the date format has a time zone 12169 * component in it. 12170 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 12171 * given, it can be inferred from this locale. For locales that span multiple 12172 * time zones, the one with the largest population is chosen as the one that 12173 * represents the locale. 12174 * 12175 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 12176 * </ul> 12177 * 12178 * NB. The <a href="http://en.wikipedia.org/wiki/Julian_date">Julian Day</a> 12179 * (JulianDay) object is a <i>different</i> object than a 12180 * <a href="http://en.wikipedia.org/wiki/Julian_calendar">date in 12181 * the Julian calendar</a> and the two are not to be confused. The Julian Day 12182 * object represents time as a number of whole and fractional days since the 12183 * beginning of the epoch, whereas a date in the Julian 12184 * calendar is a regular date that signifies year, month, day, etc. using the rules 12185 * of the Julian calendar. The naming of Julian Days and the Julian calendar are 12186 * unfortunately close, and come from history.<p> 12187 * 12188 * If called with another Julian date argument, the date components of the given 12189 * date are copied into the current one.<p> 12190 * 12191 * If the constructor is called with no arguments at all or if none of the 12192 * properties listed above 12193 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 12194 * components are 12195 * filled in with the current date at the time of instantiation. Note that if 12196 * you do not give the time zone when defaulting to the current time and the 12197 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 12198 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 12199 * Mean Time").<p> 12200 * 12201 * 12202 * @constructor 12203 * @extends IDate 12204 * @param {Object=} params parameters that govern the settings and behaviour of this Julian date 12205 */ 12206 var JulianDate = function(params) { 12207 this.cal = new JulianCal(); 12208 12209 if (params) { 12210 if (params.locale) { 12211 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 12212 var li = new LocaleInfo(this.locale); 12213 this.timezone = li.getTimeZone(); 12214 } 12215 if (params.timezone) { 12216 this.timezone = params.timezone; 12217 } 12218 12219 if (params.year || params.month || params.day || params.hour || 12220 params.minute || params.second || params.millisecond ) { 12221 /** 12222 * Year in the Julian calendar. 12223 * @type number 12224 */ 12225 this.year = parseInt(params.year, 10) || 0; 12226 /** 12227 * The month number, ranging from 1 (January) to 12 (December). 12228 * @type number 12229 */ 12230 this.month = parseInt(params.month, 10) || 1; 12231 /** 12232 * The day of the month. This ranges from 1 to 31. 12233 * @type number 12234 */ 12235 this.day = parseInt(params.day, 10) || 1; 12236 /** 12237 * The hour of the day. This can be a number from 0 to 23, as times are 12238 * stored unambiguously in the 24-hour clock. 12239 * @type number 12240 */ 12241 this.hour = parseInt(params.hour, 10) || 0; 12242 /** 12243 * The minute of the hours. Ranges from 0 to 59. 12244 * @type number 12245 */ 12246 this.minute = parseInt(params.minute, 10) || 0; 12247 /** 12248 * The second of the minute. Ranges from 0 to 59. 12249 * @type number 12250 */ 12251 this.second = parseInt(params.second, 10) || 0; 12252 /** 12253 * The millisecond of the second. Ranges from 0 to 999. 12254 * @type number 12255 */ 12256 this.millisecond = parseInt(params.millisecond, 10) || 0; 12257 12258 /** 12259 * The day of the year. Ranges from 1 to 383. 12260 * @type number 12261 */ 12262 this.dayOfYear = parseInt(params.dayOfYear, 10); 12263 12264 if (typeof(params.dst) === 'boolean') { 12265 this.dst = params.dst; 12266 } 12267 12268 this.rd = this.newRd(this); 12269 12270 // add the time zone offset to the rd to convert to UTC 12271 if (!this.tz) { 12272 this.tz = new TimeZone({id: this.timezone}); 12273 } 12274 // getOffsetMillis requires that this.year, this.rd, and this.dst 12275 // are set in order to figure out which time zone rules apply and 12276 // what the offset is at that point in the year 12277 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 12278 if (this.offset !== 0) { 12279 this.rd = this.newRd({ 12280 rd: this.rd.getRataDie() - this.offset 12281 }); 12282 } 12283 } 12284 } 12285 12286 if (!this.rd) { 12287 this.rd = this.newRd(params); 12288 this._calcDateComponents(); 12289 } 12290 }; 12291 12292 JulianDate.prototype = new IDate({noinstance: true}); 12293 JulianDate.prototype.parent = IDate; 12294 JulianDate.prototype.constructor = JulianDate; 12295 12296 /** 12297 * Return a new RD for this date type using the given params. 12298 * @protected 12299 * @param {Object=} params the parameters used to create this rata die instance 12300 * @returns {RataDie} the new RD instance for the given params 12301 */ 12302 JulianDate.prototype.newRd = function (params) { 12303 return new JulianRataDie(params); 12304 }; 12305 12306 /** 12307 * Return the year for the given RD 12308 * @protected 12309 * @param {number} rd RD to calculate from 12310 * @returns {number} the year for the RD 12311 */ 12312 JulianDate.prototype._calcYear = function(rd) { 12313 var year = Math.floor((4*(Math.floor(rd)-1) + 1464)/1461); 12314 12315 return (year <= 0) ? year - 1 : year; 12316 }; 12317 12318 /** 12319 * Calculate date components for the given RD date. 12320 * @protected 12321 */ 12322 JulianDate.prototype._calcDateComponents = function () { 12323 var remainder, 12324 cumulative, 12325 rd = this.rd.getRataDie(); 12326 12327 this.year = this._calcYear(rd); 12328 12329 if (typeof(this.offset) === "undefined") { 12330 this.year = this._calcYear(rd); 12331 12332 // now offset the RD by the time zone, then recalculate in case we were 12333 // near the year boundary 12334 if (!this.tz) { 12335 this.tz = new TimeZone({id: this.timezone}); 12336 } 12337 this.offset = this.tz.getOffsetMillis(this) / 86400000; 12338 } 12339 12340 if (this.offset !== 0) { 12341 rd += this.offset; 12342 this.year = this._calcYear(rd); 12343 } 12344 12345 var jan1 = this.newRd({ 12346 year: this.year, 12347 month: 1, 12348 day: 1, 12349 hour: 0, 12350 minute: 0, 12351 second: 0, 12352 millisecond: 0 12353 }); 12354 remainder = rd + 1 - jan1.getRataDie(); 12355 12356 cumulative = this.cal.isLeapYear(this.year) ? 12357 JulianCal.cumMonthLengthsLeap : 12358 JulianCal.cumMonthLengths; 12359 12360 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 12361 remainder = remainder - cumulative[this.month-1]; 12362 12363 this.day = Math.floor(remainder); 12364 remainder -= this.day; 12365 // now convert to milliseconds for the rest of the calculation 12366 remainder = Math.round(remainder * 86400000); 12367 12368 this.hour = Math.floor(remainder/3600000); 12369 remainder -= this.hour * 3600000; 12370 12371 this.minute = Math.floor(remainder/60000); 12372 remainder -= this.minute * 60000; 12373 12374 this.second = Math.floor(remainder/1000); 12375 remainder -= this.second * 1000; 12376 12377 this.millisecond = remainder; 12378 }; 12379 12380 /** 12381 * Return the day of the week of this date. The day of the week is encoded 12382 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 12383 * 12384 * @return {number} the day of the week 12385 */ 12386 JulianDate.prototype.getDayOfWeek = function() { 12387 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 12388 return MathUtils.mod(rd-2, 7); 12389 }; 12390 12391 /** 12392 * Return the name of the calendar that governs this date. 12393 * 12394 * @return {string} a string giving the name of the calendar 12395 */ 12396 JulianDate.prototype.getCalendar = function() { 12397 return "julian"; 12398 }; 12399 12400 //register with the factory method 12401 IDate._constructors["julian"] = JulianDate; 12402 12403 12404 /*< ThaiSolarCal.js */ 12405 /* 12406 * thaisolar.js - Represent a Thai solar calendar object. 12407 * 12408 * Copyright © 2013-2015, JEDLSoft 12409 * 12410 * Licensed under the Apache License, Version 2.0 (the "License"); 12411 * you may not use this file except in compliance with the License. 12412 * You may obtain a copy of the License at 12413 * 12414 * http://www.apache.org/licenses/LICENSE-2.0 12415 * 12416 * Unless required by applicable law or agreed to in writing, software 12417 * distributed under the License is distributed on an "AS IS" BASIS, 12418 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12419 * 12420 * See the License for the specific language governing permissions and 12421 * limitations under the License. 12422 */ 12423 12424 12425 /* !depends ilib.js Calendar.js GregorianCal.js MathUtils.js */ 12426 12427 12428 /** 12429 * @class 12430 * Construct a new Thai solar calendar object. This class encodes information about 12431 * a Thai solar calendar.<p> 12432 * 12433 * 12434 * @constructor 12435 * @extends Calendar 12436 */ 12437 var ThaiSolarCal = function() { 12438 this.type = "thaisolar"; 12439 }; 12440 12441 ThaiSolarCal.prototype = new GregorianCal({noinstance: true}); 12442 ThaiSolarCal.prototype.parent = GregorianCal; 12443 ThaiSolarCal.prototype.constructor = ThaiSolarCal; 12444 12445 /** 12446 * Return true if the given year is a leap year in the Thai solar calendar. 12447 * The year parameter may be given as a number, or as a ThaiSolarDate object. 12448 * @param {number|ThaiSolarDate} year the year for which the leap year information is being sought 12449 * @return {boolean} true if the given year is a leap year 12450 */ 12451 ThaiSolarCal.prototype.isLeapYear = function(year) { 12452 var y = (typeof(year) === 'number' ? year : year.getYears()); 12453 y -= 543; 12454 var centuries = MathUtils.mod(y, 400); 12455 return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); 12456 }; 12457 12458 /** 12459 * Return a date instance for this calendar type using the given 12460 * options. 12461 * @param {Object} options options controlling the construction of 12462 * the date instance 12463 * @return {IDate} a date appropriate for this calendar type 12464 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 12465 */ 12466 ThaiSolarCal.prototype.newDateInstance = function (options) { 12467 return new ThaiSolarDate(options); 12468 }; 12469 12470 /* register this calendar for the factory method */ 12471 Calendar._constructors["thaisolar"] = ThaiSolarCal; 12472 12473 12474 /*< ThaiSolarDate.js */ 12475 /* 12476 * ThaiSolarDate.js - Represent a date in the ThaiSolar calendar 12477 * 12478 * Copyright © 2013-2015, JEDLSoft 12479 * 12480 * Licensed under the Apache License, Version 2.0 (the "License"); 12481 * you may not use this file except in compliance with the License. 12482 * You may obtain a copy of the License at 12483 * 12484 * http://www.apache.org/licenses/LICENSE-2.0 12485 * 12486 * Unless required by applicable law or agreed to in writing, software 12487 * distributed under the License is distributed on an "AS IS" BASIS, 12488 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12489 * 12490 * See the License for the specific language governing permissions and 12491 * limitations under the License. 12492 */ 12493 12494 /* !depends 12495 ilib.js 12496 IDate.js 12497 JSUtils.js 12498 GregorianDate.js 12499 ThaiSolarCal.js 12500 */ 12501 12502 12503 12504 12505 /** 12506 * @class 12507 * Construct a new Thai solar date object. The constructor parameters can 12508 * contain any of the following properties: 12509 * 12510 * <ul> 12511 * <li><i>unixtime<i> - sets the time of this instance according to the given 12512 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 12513 * 12514 * <li><i>julianday</i> - sets the time of this instance according to the given 12515 * Julian Day instance or the Julian Day given as a float 12516 * 12517 * <li><i>year</i> - any integer, including 0 12518 * 12519 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 12520 * 12521 * <li><i>day</i> - 1 to 31 12522 * 12523 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12524 * is always done with an unambiguous 24 hour representation 12525 * 12526 * <li><i>minute</i> - 0 to 59 12527 * 12528 * <li><i>second</i> - 0 to 59 12529 * 12530 * <li><i>millisecond</i> - 0 to 999 12531 * 12532 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 12533 * of this Thai solar date. The date/time is kept in the local time. The time zone 12534 * is used later if this date is formatted according to a different time zone and 12535 * the difference has to be calculated, or when the date format has a time zone 12536 * component in it. 12537 * 12538 * <li><i>locale</i> - locale for this Thai solar date. If the time zone is not 12539 * given, it can be inferred from this locale. For locales that span multiple 12540 * time zones, the one with the largest population is chosen as the one that 12541 * represents the locale. 12542 * </ul> 12543 * 12544 * If the constructor is called with another Thai solar date instance instead of 12545 * a parameter block, the other instance acts as a parameter block and its 12546 * settings are copied into the current instance.<p> 12547 * 12548 * If the constructor is called with no arguments at all or if none of the 12549 * properties listed above 12550 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 12551 * components are 12552 * filled in with the current date at the time of instantiation. Note that if 12553 * you do not give the time zone when defaulting to the current time and the 12554 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 12555 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 12556 * Mean Time").<p> 12557 * 12558 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 12559 * specified in the params, it is assumed that they have the smallest possible 12560 * value in the range for the property (zero or one).<p> 12561 * 12562 * 12563 * @constructor 12564 * @extends GregorianDate 12565 * @param {Object=} params parameters that govern the settings and behaviour of this Thai solar date 12566 */ 12567 var ThaiSolarDate = function(params) { 12568 var p = params; 12569 if (params) { 12570 // there is 198327 days difference between the Thai solar and 12571 // Gregorian epochs which is equivalent to 543 years 12572 p = {}; 12573 JSUtils.shallowCopy(params, p); 12574 if (typeof(p.year) !== 'undefined') { 12575 p.year -= 543; 12576 } 12577 if (typeof(p.rd) !== 'undefined') { 12578 p.rd -= 198327; 12579 } 12580 } 12581 this.rd = NaN; // clear these out so that the GregorianDate constructor can set it 12582 this.offset = undefined; 12583 //console.log("ThaiSolarDate.constructor: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 12584 GregorianDate.call(this, p); 12585 this.cal = new ThaiSolarCal(); 12586 // make sure the year is set correctly 12587 if (params && typeof(params.year) !== 'undefined') { 12588 this.year = parseInt(params.year, 10); 12589 } 12590 }; 12591 12592 ThaiSolarDate.prototype = new GregorianDate({noinstance: true}); 12593 ThaiSolarDate.prototype.parent = GregorianDate.prototype; 12594 ThaiSolarDate.prototype.constructor = ThaiSolarDate; 12595 12596 /** 12597 * the difference between a zero Julian day and the zero Thai Solar date. 12598 * This is some 543 years before the start of the Gregorian epoch. 12599 * @private 12600 * @type number 12601 */ 12602 ThaiSolarDate.epoch = 1523097.5; 12603 12604 /** 12605 * Calculate the date components for the current time zone 12606 * @protected 12607 */ 12608 ThaiSolarDate.prototype._calcDateComponents = function () { 12609 // there is 198327 days difference between the Thai solar and 12610 // Gregorian epochs which is equivalent to 543 years 12611 // console.log("ThaiSolarDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 12612 this.parent._calcDateComponents.call(this); 12613 this.year += 543; 12614 }; 12615 12616 /** 12617 * Return the Rata Die (fixed day) number of this date. 12618 * 12619 * @protected 12620 * @return {number} the rd date as a number 12621 */ 12622 ThaiSolarDate.prototype.getRataDie = function() { 12623 // there is 198327 days difference between the Thai solar and 12624 // Gregorian epochs which is equivalent to 543 years 12625 return this.rd.getRataDie() + 198327; 12626 }; 12627 12628 /** 12629 * Return a new Gregorian date instance that represents the first instance of the 12630 * given day of the week before the current date. The day of the week is encoded 12631 * as a number where 0 = Sunday, 1 = Monday, etc. 12632 * 12633 * @param {number} dow the day of the week before the current date that is being sought 12634 * @return {IDate} the date being sought 12635 */ 12636 ThaiSolarDate.prototype.before = function (dow) { 12637 return new ThaiSolarDate({ 12638 rd: this.rd.before(dow, this.offset) + 198327, 12639 timezone: this.timezone 12640 }); 12641 }; 12642 12643 /** 12644 * Return a new Gregorian date instance that represents the first instance of the 12645 * given day of the week after the current date. The day of the week is encoded 12646 * as a number where 0 = Sunday, 1 = Monday, etc. 12647 * 12648 * @param {number} dow the day of the week after the current date that is being sought 12649 * @return {IDate} the date being sought 12650 */ 12651 ThaiSolarDate.prototype.after = function (dow) { 12652 return new ThaiSolarDate({ 12653 rd: this.rd.after(dow, this.offset) + 198327, 12654 timezone: this.timezone 12655 }); 12656 }; 12657 12658 /** 12659 * Return a new Gregorian date instance that represents the first instance of the 12660 * given day of the week on or before the current date. The day of the week is encoded 12661 * as a number where 0 = Sunday, 1 = Monday, etc. 12662 * 12663 * @param {number} dow the day of the week on or before the current date that is being sought 12664 * @return {IDate} the date being sought 12665 */ 12666 ThaiSolarDate.prototype.onOrBefore = function (dow) { 12667 return new ThaiSolarDate({ 12668 rd: this.rd.onOrBefore(dow, this.offset) + 198327, 12669 timezone: this.timezone 12670 }); 12671 }; 12672 12673 /** 12674 * Return a new Gregorian date instance that represents the first instance of the 12675 * given day of the week on or after the current date. The day of the week is encoded 12676 * as a number where 0 = Sunday, 1 = Monday, etc. 12677 * 12678 * @param {number} dow the day of the week on or after the current date that is being sought 12679 * @return {IDate} the date being sought 12680 */ 12681 ThaiSolarDate.prototype.onOrAfter = function (dow) { 12682 return new ThaiSolarDate({ 12683 rd: this.rd.onOrAfter(dow, this.offset) + 198327, 12684 timezone: this.timezone 12685 }); 12686 }; 12687 12688 /** 12689 * Return the name of the calendar that governs this date. 12690 * 12691 * @return {string} a string giving the name of the calendar 12692 */ 12693 ThaiSolarDate.prototype.getCalendar = function() { 12694 return "thaisolar"; 12695 }; 12696 12697 //register with the factory method 12698 IDate._constructors["thaisolar"] = ThaiSolarDate; 12699 12700 12701 12702 /*< Astro.js */ 12703 /* 12704 * astro.js - Static functions to support astronomical calculations 12705 * 12706 * Copyright © 2014-2015, JEDLSoft 12707 * 12708 * Licensed under the Apache License, Version 2.0 (the "License"); 12709 * you may not use this file except in compliance with the License. 12710 * You may obtain a copy of the License at 12711 * 12712 * http://www.apache.org/licenses/LICENSE-2.0 12713 * 12714 * Unless required by applicable law or agreed to in writing, software 12715 * distributed under the License is distributed on an "AS IS" BASIS, 12716 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12717 * 12718 * See the License for the specific language governing permissions and 12719 * limitations under the License. 12720 */ 12721 12722 /* !depends 12723 ilib.js 12724 IDate.js 12725 Utils.js 12726 MathUtils.js 12727 SearchUtils.js 12728 GregorianDate.js 12729 GregRataDie.js 12730 */ 12731 12732 // !data astro 12733 12734 /* 12735 * These routines were derived from a public domain set of JavaScript 12736 * functions for positional astronomy by John Walker of Fourmilab, 12737 * September 1999. 12738 */ 12739 12740 12741 12742 var Astro = {}; 12743 12744 /** 12745 * Load in all the data needed for astrological calculations. 12746 * 12747 * @private 12748 * @param {boolean} sync 12749 * @param {*} loadParams 12750 * @param {function(*)|undefined} callback 12751 */ 12752 Astro.initAstro = function(sync, loadParams, callback) { 12753 if (!ilib.data.astro) { 12754 Utils.loadData({ 12755 name: "astro.json", // countries in their own language 12756 locale: "-", // only need to load the root file 12757 nonLocale: true, 12758 sync: sync, 12759 loadParams: loadParams, 12760 callback: ilib.bind(this, function(astroData) { 12761 /** 12762 * @type {{ 12763 * _EquinoxpTerms:Array.<number>, 12764 * _JDE0tab1000:Array.<number>, 12765 * _JDE0tab2000:Array.<number>, 12766 * _deltaTtab:Array.<number>, 12767 * _oterms:Array.<number>, 12768 * _nutArgMult:Array.<number>, 12769 * _nutArgCoeff:Array.<number>, 12770 * _nutCoeffA:Array.<number>, 12771 * _nutCoeffB:Array.<number>, 12772 * _coeff19th:Array.<number>, 12773 * _coeff18th:Array.<number>, 12774 * _solarLongCoeff:Array.<number>, 12775 * _solarLongMultipliers:Array.<number>, 12776 * _solarLongAddends:Array.<number>, 12777 * _meanMoonCoeff:Array.<number>, 12778 * _elongationCoeff:Array.<number>, 12779 * _solarAnomalyCoeff:Array.<number>, 12780 * _lunarAnomalyCoeff:Array.<number>, 12781 * _moonFromNodeCoeff:Array.<number>, 12782 * _eCoeff:Array.<number>, 12783 * _lunarElongationLongCoeff:Array.<number>, 12784 * _solarAnomalyLongCoeff:Array.<number>, 12785 * _lunarAnomalyLongCoeff:Array.<number>, 12786 * _moonFromNodeLongCoeff:Array.<number>, 12787 * _sineCoeff:Array.<number>, 12788 * _nmApproxCoeff:Array.<number>, 12789 * _nmCapECoeff:Array.<number>, 12790 * _nmSolarAnomalyCoeff:Array.<number>, 12791 * _nmLunarAnomalyCoeff:Array.<number>, 12792 * _nmMoonArgumentCoeff:Array.<number>, 12793 * _nmCapOmegaCoeff:Array.<number>, 12794 * _nmEFactor:Array.<number>, 12795 * _nmSolarCoeff:Array.<number>, 12796 * _nmLunarCoeff:Array.<number>, 12797 * _nmMoonCoeff:Array.<number>, 12798 * _nmSineCoeff:Array.<number>, 12799 * _nmAddConst:Array.<number>, 12800 * _nmAddCoeff:Array.<number>, 12801 * _nmAddFactor:Array.<number>, 12802 * _nmExtra:Array.<number> 12803 * }} 12804 */ 12805 ilib.data.astro = astroData; 12806 if (callback && typeof(callback) === 'function') { 12807 callback(astroData); 12808 } 12809 }) 12810 }); 12811 } else { 12812 if (callback && typeof(callback) === 'function') { 12813 callback(ilib.data.astro); 12814 } 12815 } 12816 }; 12817 12818 /** 12819 * Convert degrees to radians. 12820 * 12821 * @static 12822 * @protected 12823 * @param {number} d angle in degrees 12824 * @return {number} angle in radians 12825 */ 12826 Astro._dtr = function(d) { 12827 return (d * Math.PI) / 180.0; 12828 }; 12829 12830 /** 12831 * Convert radians to degrees. 12832 * 12833 * @static 12834 * @protected 12835 * @param {number} r angle in radians 12836 * @return {number} angle in degrees 12837 */ 12838 Astro._rtd = function(r) { 12839 return (r * 180.0) / Math.PI; 12840 }; 12841 12842 /** 12843 * Return the cosine of an angle given in degrees. 12844 * @static 12845 * @protected 12846 * @param {number} d angle in degrees 12847 * @return {number} cosine of the angle. 12848 */ 12849 Astro._dcos = function(d) { 12850 return Math.cos(Astro._dtr(d)); 12851 }; 12852 12853 /** 12854 * Return the sine of an angle given in degrees. 12855 * @static 12856 * @protected 12857 * @param {number} d angle in degrees 12858 * @return {number} sine of the angle. 12859 */ 12860 Astro._dsin = function(d) { 12861 return Math.sin(Astro._dtr(d)); 12862 }; 12863 12864 /** 12865 * Return the tan of an angle given in degrees. 12866 * @static 12867 * @protected 12868 * @param {number} d angle in degrees 12869 * @return {number} tan of the angle. 12870 */ 12871 Astro._dtan = function(d) { 12872 return Math.tan(Astro._dtr(d)); 12873 }; 12874 12875 /** 12876 * Range reduce angle in degrees. 12877 * 12878 * @static 12879 * @param {number} a angle to reduce 12880 * @return {number} the reduced angle 12881 */ 12882 Astro._fixangle = function(a) { 12883 return a - 360.0 * (Math.floor(a / 360.0)); 12884 }; 12885 12886 /** 12887 * Range reduce angle in radians. 12888 * 12889 * @static 12890 * @protected 12891 * @param {number} a angle to reduce 12892 * @return {number} the reduced angle 12893 */ 12894 Astro._fixangr = function(a) { 12895 return a - (2 * Math.PI) * (Math.floor(a / (2 * Math.PI))); 12896 }; 12897 12898 /** 12899 * Determine the Julian Ephemeris Day of an equinox or solstice. The "which" 12900 * argument selects the item to be computed: 12901 * 12902 * <ul> 12903 * <li>0 March equinox 12904 * <li>1 June solstice 12905 * <li>2 September equinox 12906 * <li>3 December solstice 12907 * </ul> 12908 * 12909 * @static 12910 * @protected 12911 * @param {number} year Gregorian year to calculate for 12912 * @param {number} which Which equinox or solstice to calculate 12913 */ 12914 Astro._equinox = function(year, which) { 12915 var deltaL, i, j, JDE0, JDE, JDE0tab, S, T, W, Y; 12916 12917 /* Initialize terms for mean equinox and solstices. We 12918 have two sets: one for years prior to 1000 and a second 12919 for subsequent years. */ 12920 12921 if (year < 1000) { 12922 JDE0tab = ilib.data.astro._JDE0tab1000; 12923 Y = year / 1000; 12924 } else { 12925 JDE0tab = ilib.data.astro._JDE0tab2000; 12926 Y = (year - 2000) / 1000; 12927 } 12928 12929 JDE0 = JDE0tab[which][0] + (JDE0tab[which][1] * Y) 12930 + (JDE0tab[which][2] * Y * Y) + (JDE0tab[which][3] * Y * Y * Y) 12931 + (JDE0tab[which][4] * Y * Y * Y * Y); 12932 12933 //document.debug.log.value += "JDE0 = " + JDE0 + "\n"; 12934 12935 T = (JDE0 - 2451545.0) / 36525; 12936 //document.debug.log.value += "T = " + T + "\n"; 12937 W = (35999.373 * T) - 2.47; 12938 //document.debug.log.value += "W = " + W + "\n"; 12939 deltaL = 1 + (0.0334 * Astro._dcos(W)) + (0.0007 * Astro._dcos(2 * W)); 12940 //document.debug.log.value += "deltaL = " + deltaL + "\n"; 12941 12942 // Sum the periodic terms for time T 12943 12944 S = 0; 12945 j = 0; 12946 for (i = 0; i < 24; i++) { 12947 S += ilib.data.astro._EquinoxpTerms[j] 12948 * Astro._dcos(ilib.data.astro._EquinoxpTerms[j + 1] + (ilib.data.astro._EquinoxpTerms[j + 2] * T)); 12949 j += 3; 12950 } 12951 12952 //document.debug.log.value += "S = " + S + "\n"; 12953 //document.debug.log.value += "Corr = " + ((S * 0.00001) / deltaL) + "\n"; 12954 12955 JDE = JDE0 + ((S * 0.00001) / deltaL); 12956 12957 return JDE; 12958 }; 12959 12960 /* 12961 * The table of observed Delta T values at the beginning of 12962 * years from 1620 through 2014 as found in astro.json is taken from 12963 * http://www.staff.science.uu.nl/~gent0113/deltat/deltat.htm 12964 * and 12965 * ftp://maia.usno.navy.mil/ser7/deltat.data 12966 */ 12967 12968 /** 12969 * Determine the difference, in seconds, between dynamical time and universal time. 12970 * 12971 * @static 12972 * @protected 12973 * @param {number} year to calculate the difference for 12974 * @return {number} difference in seconds between dynamical time and universal time 12975 */ 12976 Astro._deltat = function (year) { 12977 var dt, f, i, t; 12978 12979 if ((year >= 1620) && (year <= 2014)) { 12980 i = Math.floor(year - 1620); 12981 f = (year - 1620) - i; /* Fractional part of year */ 12982 dt = ilib.data.astro._deltaTtab[i] + ((ilib.data.astro._deltaTtab[i + 1] - ilib.data.astro._deltaTtab[i]) * f); 12983 } else { 12984 t = (year - 2000) / 100; 12985 if (year < 948) { 12986 dt = 2177 + (497 * t) + (44.1 * t * t); 12987 } else { 12988 dt = 102 + (102 * t) + (25.3 * t * t); 12989 if ((year > 2000) && (year < 2100)) { 12990 dt += 0.37 * (year - 2100); 12991 } 12992 } 12993 } 12994 return dt; 12995 }; 12996 12997 /** 12998 * Calculate the obliquity of the ecliptic for a given 12999 * Julian date. This uses Laskar's tenth-degree 13000 * polynomial fit (J. Laskar, Astronomy and 13001 * Astrophysics, Vol. 157, page 68 [1986]) which is 13002 * accurate to within 0.01 arc second between AD 1000 13003 * and AD 3000, and within a few seconds of arc for 13004 * +/-10000 years around AD 2000. If we're outside the 13005 * range in which this fit is valid (deep time) we 13006 * simply return the J2000 value of the obliquity, which 13007 * happens to be almost precisely the mean. 13008 * 13009 * @static 13010 * @protected 13011 * @param {number} jd Julian Day to calculate the obliquity for 13012 * @return {number} the obliquity 13013 */ 13014 Astro._obliqeq = function (jd) { 13015 var eps, u, v, i; 13016 13017 v = u = (jd - 2451545.0) / 3652500.0; 13018 13019 eps = 23 + (26 / 60.0) + (21.448 / 3600.0); 13020 13021 if (Math.abs(u) < 1.0) { 13022 for (i = 0; i < 10; i++) { 13023 eps += (ilib.data.astro._oterms[i] / 3600.0) * v; 13024 v *= u; 13025 } 13026 } 13027 return eps; 13028 }; 13029 13030 /** 13031 * Return the position of the sun. We return 13032 * intermediate values because they are useful in a 13033 * variety of other contexts. 13034 * @static 13035 * @protected 13036 * @param {number} jd find the position of sun on this Julian Day 13037 * @return {Object} the position of the sun and many intermediate 13038 * values 13039 */ 13040 Astro._sunpos = function(jd) { 13041 var ret = {}, 13042 T, T2, T3, Omega, epsilon, epsilon0; 13043 13044 T = (jd - 2451545.0) / 36525.0; 13045 //document.debug.log.value += "Sunpos. T = " + T + "\n"; 13046 T2 = T * T; 13047 T3 = T * T2; 13048 ret.meanLongitude = Astro._fixangle(280.46646 + 36000.76983 * T + 0.0003032 * T2); 13049 //document.debug.log.value += "ret.meanLongitude = " + ret.meanLongitude + "\n"; 13050 ret.meanAnomaly = Astro._fixangle(357.52911 + (35999.05029 * T) - 0.0001537 * T2 - 0.00000048 * T3); 13051 //document.debug.log.value += "ret.meanAnomaly = " + ret.meanAnomaly + "\n"; 13052 ret.eccentricity = 0.016708634 - 0.000042037 * T - 0.0000001267 * T2; 13053 //document.debug.log.value += "e = " + e + "\n"; 13054 ret.equationOfCenter = ((1.914602 - 0.004817 * T - 0.000014 * T2) * Astro._dsin(ret.meanAnomaly)) 13055 + ((0.019993 - 0.000101 * T) * Astro._dsin(2 * ret.meanAnomaly)) 13056 + (0.000289 * Astro._dsin(3 * ret.meanAnomaly)); 13057 //document.debug.log.value += "ret.equationOfCenter = " + ret.equationOfCenter + "\n"; 13058 ret.sunLongitude = ret.meanLongitude + ret.equationOfCenter; 13059 //document.debug.log.value += "ret.sunLongitude = " + ret.sunLongitude + "\n"; 13060 //ret.sunAnomaly = ret.meanAnomaly + ret.equationOfCenter; 13061 //document.debug.log.value += "ret.sunAnomaly = " + ret.sunAnomaly + "\n"; 13062 // ret.sunRadius = (1.000001018 * (1 - (ret.eccentricity * ret.eccentricity))) / (1 + (ret.eccentricity * Astro._dcos(ret.sunAnomaly))); 13063 //document.debug.log.value += "ret.sunRadius = " + ret.sunRadius + "\n"; 13064 Omega = 125.04 - (1934.136 * T); 13065 //document.debug.log.value += "Omega = " + Omega + "\n"; 13066 ret.apparentLong = ret.sunLongitude + (-0.00569) + (-0.00478 * Astro._dsin(Omega)); 13067 //document.debug.log.value += "ret.apparentLong = " + ret.apparentLong + "\n"; 13068 epsilon0 = Astro._obliqeq(jd); 13069 //document.debug.log.value += "epsilon0 = " + epsilon0 + "\n"; 13070 epsilon = epsilon0 + (0.00256 * Astro._dcos(Omega)); 13071 //document.debug.log.value += "epsilon = " + epsilon + "\n"; 13072 //ret.rightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon0) * Astro._dsin(ret.sunLongitude), Astro._dcos(ret.sunLongitude)))); 13073 //document.debug.log.value += "ret.rightAscension = " + ret.rightAscension + "\n"; 13074 // ret.declination = Astro._rtd(Math.asin(Astro._dsin(epsilon0) * Astro._dsin(ret.sunLongitude))); 13075 ////document.debug.log.value += "ret.declination = " + ret.declination + "\n"; 13076 ret.inclination = Astro._fixangle(23.4392911 - 0.013004167 * T - 0.00000016389 * T2 + 0.0000005036 * T3); 13077 ret.apparentRightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon) * Astro._dsin(ret.apparentLong), Astro._dcos(ret.apparentLong)))); 13078 //document.debug.log.value += "ret.apparentRightAscension = " + ret.apparentRightAscension + "\n"; 13079 //ret.apparentDeclination = Astro._rtd(Math.asin(Astro._dsin(epsilon) * Astro._dsin(ret.apparentLong))); 13080 //document.debug.log.value += "ret.apparentDecliation = " + ret.apparentDecliation + "\n"; 13081 13082 // Angular quantities are expressed in decimal degrees 13083 return ret; 13084 }; 13085 13086 /** 13087 * Calculate the nutation in longitude, deltaPsi, and obliquity, 13088 * deltaEpsilon for a given Julian date jd. Results are returned as an object 13089 * giving deltaPsi and deltaEpsilon in degrees. 13090 * 13091 * @static 13092 * @protected 13093 * @param {number} jd calculate the nutation of this Julian Day 13094 * @return {Object} the deltaPsi and deltaEpsilon of the nutation 13095 */ 13096 Astro._nutation = function(jd) { 13097 var i, j, 13098 t = (jd - 2451545.0) / 36525.0, 13099 t2, t3, to10, 13100 ta = [], 13101 dp = 0, 13102 de = 0, 13103 ang, 13104 ret = {}; 13105 13106 t3 = t * (t2 = t * t); 13107 13108 /* 13109 * Calculate angles. The correspondence between the elements of our array 13110 * and the terms cited in Meeus are: 13111 * 13112 * ta[0] = D ta[0] = M ta[2] = M' ta[3] = F ta[4] = \Omega 13113 * 13114 */ 13115 13116 ta[0] = Astro._dtr(297.850363 + 445267.11148 * t - 0.0019142 * t2 + t3 / 189474.0); 13117 ta[1] = Astro._dtr(357.52772 + 35999.05034 * t - 0.0001603 * t2 - t3 / 300000.0); 13118 ta[2] = Astro._dtr(134.96298 + 477198.867398 * t + 0.0086972 * t2 + t3 / 56250.0); 13119 ta[3] = Astro._dtr(93.27191 + 483202.017538 * t - 0.0036825 * t2 + t3 / 327270); 13120 ta[4] = Astro._dtr(125.04452 - 1934.136261 * t + 0.0020708 * t2 + t3 / 450000.0); 13121 13122 /* 13123 * Range reduce the angles in case the sine and cosine functions don't do it 13124 * as accurately or quickly. 13125 */ 13126 13127 for (i = 0; i < 5; i++) { 13128 ta[i] = Astro._fixangr(ta[i]); 13129 } 13130 13131 to10 = t / 10.0; 13132 for (i = 0; i < 63; i++) { 13133 ang = 0; 13134 for (j = 0; j < 5; j++) { 13135 if (ilib.data.astro._nutArgMult[(i * 5) + j] != 0) { 13136 ang += ilib.data.astro._nutArgMult[(i * 5) + j] * ta[j]; 13137 } 13138 } 13139 dp += (ilib.data.astro._nutArgCoeff[(i * 4) + 0] + ilib.data.astro._nutArgCoeff[(i * 4) + 1] * to10) * Math.sin(ang); 13140 de += (ilib.data.astro._nutArgCoeff[(i * 4) + 2] + ilib.data.astro._nutArgCoeff[(i * 4) + 3] * to10) * Math.cos(ang); 13141 } 13142 13143 /* 13144 * Return the result, converting from ten thousandths of arc seconds to 13145 * radians in the process. 13146 */ 13147 13148 ret.deltaPsi = dp / (3600.0 * 10000.0); 13149 ret.deltaEpsilon = de / (3600.0 * 10000.0); 13150 13151 return ret; 13152 }; 13153 13154 /** 13155 * Returns the equation of time as a fraction of a day. 13156 * 13157 * @static 13158 * @protected 13159 * @param {number} jd the Julian Day of the day to calculate for 13160 * @return {number} the equation of time for the given day 13161 */ 13162 Astro._equationOfTime = function(jd) { 13163 var alpha, deltaPsi, E, epsilon, L0, tau, pos; 13164 13165 // 2451545.0 is the Julian day of J2000 epoch 13166 // 365250.0 is the number of days in a Julian millenium 13167 tau = (jd - 2451545.0) / 365250.0; 13168 //console.log("equationOfTime. tau = " + tau); 13169 L0 = 280.4664567 + (360007.6982779 * tau) + (0.03032028 * tau * tau) 13170 + ((tau * tau * tau) / 49931) 13171 + (-((tau * tau * tau * tau) / 15300)) 13172 + (-((tau * tau * tau * tau * tau) / 2000000)); 13173 //console.log("L0 = " + L0); 13174 L0 = Astro._fixangle(L0); 13175 //console.log("L0 = " + L0); 13176 pos = Astro._sunpos(jd); 13177 alpha = pos.apparentRightAscension; 13178 //console.log("alpha = " + alpha); 13179 var nut = Astro._nutation(jd); 13180 deltaPsi = nut.deltaPsi; 13181 //console.log("deltaPsi = " + deltaPsi); 13182 epsilon = Astro._obliqeq(jd) + nut.deltaEpsilon; 13183 //console.log("epsilon = " + epsilon); 13184 //console.log("L0 - 0.0057183 = " + (L0 - 0.0057183)); 13185 //console.log("L0 - 0.0057183 - alpha = " + (L0 - 0.0057183 - alpha)); 13186 //console.log("deltaPsi * cos(epsilon) = " + deltaPsi * Astro._dcos(epsilon)); 13187 13188 E = L0 - 0.0057183 - alpha + deltaPsi * Astro._dcos(epsilon); 13189 // if alpha and L0 are in different quadrants, then renormalize 13190 // so that the difference between them is in the right range 13191 if (E > 180) { 13192 E -= 360; 13193 } 13194 //console.log("E = " + E); 13195 // E = E - 20.0 * (Math.floor(E / 20.0)); 13196 E = E * 4; 13197 //console.log("Efixed = " + E); 13198 E = E / (24 * 60); 13199 //console.log("Eday = " + E); 13200 13201 return E; 13202 }; 13203 13204 /** 13205 * @private 13206 * @static 13207 */ 13208 Astro._poly = function(x, coefficients) { 13209 var result = coefficients[0]; 13210 var xpow = x; 13211 for (var i = 1; i < coefficients.length; i++) { 13212 result += coefficients[i] * xpow; 13213 xpow *= x; 13214 } 13215 return result; 13216 }; 13217 13218 /** 13219 * Calculate the UTC RD from the local RD given "zone" number of minutes 13220 * worth of offset. 13221 * 13222 * @static 13223 * @protected 13224 * @param {number} local RD of the locale time, given in any calendar 13225 * @param {number} zone number of minutes of offset from UTC for the time zone 13226 * @return {number} the UTC equivalent of the local RD 13227 */ 13228 Astro._universalFromLocal = function(local, zone) { 13229 return local - zone / 1440; 13230 }; 13231 13232 /** 13233 * Calculate the local RD from the UTC RD given "zone" number of minutes 13234 * worth of offset. 13235 * 13236 * @static 13237 * @protected 13238 * @param {number} local RD of the locale time, given in any calendar 13239 * @param {number} zone number of minutes of offset from UTC for the time zone 13240 * @return {number} the UTC equivalent of the local RD 13241 */ 13242 Astro._localFromUniversal = function(local, zone) { 13243 return local + zone / 1440; 13244 }; 13245 13246 /** 13247 * @private 13248 * @static 13249 * @param {number} c julian centuries of the date to calculate 13250 * @return {number} the aberration 13251 */ 13252 Astro._aberration = function(c) { 13253 return 9.74e-05 * Astro._dcos(177.63 + 35999.01847999999 * c) - 0.005575; 13254 }; 13255 13256 /** 13257 * @private 13258 * 13259 ilib.data.astro._nutCoeffA = [124.90, -1934.134, 0.002063]; 13260 ilib.data.astro._nutCoeffB q= [201.11, 72001.5377, 0.00057]; 13261 */ 13262 13263 /** 13264 * @private 13265 * @static 13266 * @param {number} c julian centuries of the date to calculate 13267 * @return {number} the nutation for the given julian century in radians 13268 */ 13269 Astro._nutation2 = function(c) { 13270 var a = Astro._poly(c, ilib.data.astro._nutCoeffA); 13271 var b = Astro._poly(c, ilib.data.astro._nutCoeffB); 13272 // return -0.0000834 * Astro._dsin(a) - 0.0000064 * Astro._dsin(b); 13273 return -0.004778 * Astro._dsin(a) - 0.0003667 * Astro._dsin(b); 13274 }; 13275 13276 /** 13277 * @static 13278 * @private 13279 */ 13280 Astro._ephemerisCorrection = function(jd) { 13281 var year = GregorianDate._calcYear(jd - 1721424.5); 13282 13283 if (1988 <= year && year <= 2019) { 13284 return (year - 1933) / 86400; 13285 } 13286 13287 if (1800 <= year && year <= 1987) { 13288 var jul1 = new GregRataDie({ 13289 year: year, 13290 month: 7, 13291 day: 1, 13292 hour: 0, 13293 minute: 0, 13294 second: 0 13295 }); 13296 // 693596 is the rd of Jan 1, 1900 13297 var theta = (jul1.getRataDie() - 693596) / 36525; 13298 return Astro._poly(theta, (1900 <= year) ? ilib.data.astro._coeff19th : ilib.data.astro._coeff18th); 13299 } 13300 13301 if (1620 <= year && year <= 1799) { 13302 year -= 1600; 13303 return (196.58333 - 4.0675 * year + 0.0219167 * year * year) / 86400; 13304 } 13305 13306 // 660724 is the rd of Jan 1, 1810 13307 var jan1 = new GregRataDie({ 13308 year: year, 13309 month: 1, 13310 day: 1, 13311 hour: 0, 13312 minute: 0, 13313 second: 0 13314 }); 13315 // var x = 0.5 + (jan1.getRataDie() - 660724); 13316 var x = 0.5 + (jan1.getRataDie() - 660724); 13317 13318 return ((x * x / 41048480) - 15) / 86400; 13319 }; 13320 13321 /** 13322 * @static 13323 * @private 13324 */ 13325 Astro._ephemerisFromUniversal = function(jd) { 13326 return jd + Astro._ephemerisCorrection(jd); 13327 }; 13328 13329 /** 13330 * @static 13331 * @private 13332 */ 13333 Astro._universalFromEphemeris = function(jd) { 13334 return jd - Astro._ephemerisCorrection(jd); 13335 }; 13336 13337 /** 13338 * @static 13339 * @private 13340 */ 13341 Astro._julianCenturies = function(jd) { 13342 // 2451545.0 is the Julian day of J2000 epoch 13343 // 730119.5 is the Gregorian RD of J2000 epoch 13344 // 36525.0 is the number of days in a Julian century 13345 return (Astro._ephemerisFromUniversal(jd) - 2451545.0) / 36525.0; 13346 }; 13347 13348 /** 13349 * Calculate the solar longitude 13350 * 13351 * @static 13352 * @protected 13353 * @param {number} jd julian day of the date to calculate the longitude for 13354 * @return {number} the solar longitude in degrees 13355 */ 13356 Astro._solarLongitude = function(jd) { 13357 var c = Astro._julianCenturies(jd), 13358 longitude = 0, 13359 len = ilib.data.astro._solarLongCoeff.length, 13360 row; 13361 13362 for (var i = 0; i < len; i++) { 13363 longitude += ilib.data.astro._solarLongCoeff[i] * 13364 Astro._dsin(ilib.data.astro._solarLongAddends[i] + ilib.data.astro._solarLongMultipliers[i] * c); 13365 } 13366 longitude *= 5.729577951308232e-06; 13367 longitude += 282.77718340000001 + 36000.769537439999 * c; 13368 longitude += Astro._aberration(c) + Astro._nutation2(c); 13369 return Astro._fixangle(longitude); 13370 }; 13371 13372 /** 13373 * @static 13374 * @protected 13375 * @param {number} jd 13376 * @return {number} 13377 */ 13378 Astro._lunarLongitude = function (jd) { 13379 var c = Astro._julianCenturies(jd), 13380 meanMoon = Astro._fixangle(Astro._poly(c, ilib.data.astro._meanMoonCoeff)), 13381 elongation = Astro._fixangle(Astro._poly(c, ilib.data.astro._elongationCoeff)), 13382 solarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._solarAnomalyCoeff)), 13383 lunarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._lunarAnomalyCoeff)), 13384 moonNode = Astro._fixangle(Astro._poly(c, ilib.data.astro._moonFromNodeCoeff)), 13385 e = Astro._poly(c, ilib.data.astro._eCoeff); 13386 13387 var sum = 0; 13388 for (var i = 0; i < ilib.data.astro._lunarElongationLongCoeff.length; i++) { 13389 var x = ilib.data.astro._solarAnomalyLongCoeff[i]; 13390 13391 sum += ilib.data.astro._sineCoeff[i] * Math.pow(e, Math.abs(x)) * 13392 Astro._dsin(ilib.data.astro._lunarElongationLongCoeff[i] * elongation + x * solarAnomaly + 13393 ilib.data.astro._lunarAnomalyLongCoeff[i] * lunarAnomaly + 13394 ilib.data.astro._moonFromNodeLongCoeff[i] * moonNode); 13395 } 13396 var longitude = sum / 1000000; 13397 var venus = 3958.0 / 1000000 * Astro._dsin(119.75 + c * 131.84899999999999); 13398 var jupiter = 318.0 / 1000000 * Astro._dsin(53.090000000000003 + c * 479264.28999999998); 13399 var flatEarth = 1962.0 / 1000000 * Astro._dsin(meanMoon - moonNode); 13400 13401 return Astro._fixangle(meanMoon + longitude + venus + jupiter + flatEarth + Astro._nutation2(c)); 13402 }; 13403 13404 /** 13405 * @static 13406 * @protected 13407 * @param {number} n 13408 * @return {number} julian day of the n'th new moon 13409 */ 13410 Astro._newMoonTime = function(n) { 13411 var k = n - 24724; 13412 var c = k / 1236.8499999999999; 13413 var approx = Astro._poly(c, ilib.data.astro._nmApproxCoeff); 13414 var capE = Astro._poly(c, ilib.data.astro._nmCapECoeff); 13415 var solarAnomaly = Astro._poly(c, ilib.data.astro._nmSolarAnomalyCoeff); 13416 var lunarAnomaly = Astro._poly(c, ilib.data.astro._nmLunarAnomalyCoeff); 13417 var moonArgument = Astro._poly(c, ilib.data.astro._nmMoonArgumentCoeff); 13418 var capOmega = Astro._poly(c, ilib.data.astro._nmCapOmegaCoeff); 13419 var correction = -0.00017 * Astro._dsin(capOmega); 13420 for (var i = 0; i < ilib.data.astro._nmSineCoeff.length; i++) { 13421 correction = correction + ilib.data.astro._nmSineCoeff[i] * Math.pow(capE, ilib.data.astro._nmEFactor[i]) * 13422 Astro._dsin(ilib.data.astro._nmSolarCoeff[i] * solarAnomaly + 13423 ilib.data.astro._nmLunarCoeff[i] * lunarAnomaly + 13424 ilib.data.astro._nmMoonCoeff[i] * moonArgument); 13425 } 13426 var additional = 0; 13427 for (var i = 0; i < ilib.data.astro._nmAddConst.length; i++) { 13428 additional = additional + ilib.data.astro._nmAddFactor[i] * 13429 Astro._dsin(ilib.data.astro._nmAddConst[i] + ilib.data.astro._nmAddCoeff[i] * k); 13430 } 13431 var extra = 0.000325 * Astro._dsin(Astro._poly(c, ilib.data.astro._nmExtra)); 13432 return Astro._universalFromEphemeris(approx + correction + extra + additional + RataDie.gregorianEpoch); 13433 }; 13434 13435 /** 13436 * @static 13437 * @protected 13438 * @param {number} jd 13439 * @return {number} 13440 */ 13441 Astro._lunarSolarAngle = function(jd) { 13442 var lunar = Astro._lunarLongitude(jd); 13443 var solar = Astro._solarLongitude(jd) 13444 return Astro._fixangle(lunar - solar); 13445 }; 13446 13447 /** 13448 * @static 13449 * @protected 13450 * @param {number} jd 13451 * @return {number} 13452 */ 13453 Astro._newMoonBefore = function (jd) { 13454 var phase = Astro._lunarSolarAngle(jd); 13455 // 11.450086114414322 is the julian day of the 0th full moon 13456 // 29.530588853000001 is the average length of a month 13457 var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360) - 1; 13458 var current, last; 13459 current = last = Astro._newMoonTime(guess); 13460 while (current < jd) { 13461 guess++; 13462 last = current; 13463 current = Astro._newMoonTime(guess); 13464 } 13465 return last; 13466 }; 13467 13468 /** 13469 * @static 13470 * @protected 13471 * @param {number} jd 13472 * @return {number} 13473 */ 13474 Astro._newMoonAtOrAfter = function (jd) { 13475 var phase = Astro._lunarSolarAngle(jd); 13476 // 11.450086114414322 is the julian day of the 0th full moon 13477 // 29.530588853000001 is the average length of a month 13478 var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360); 13479 var current; 13480 while ((current = Astro._newMoonTime(guess)) < jd) { 13481 guess++; 13482 } 13483 return current; 13484 }; 13485 13486 /** 13487 * @static 13488 * @protected 13489 * @param {number} jd JD to calculate from 13490 * @param {number} longitude longitude to seek 13491 * @returns {number} the JD of the next time that the solar longitude 13492 * is a multiple of the given longitude 13493 */ 13494 Astro._nextSolarLongitude = function(jd, longitude) { 13495 var rate = 365.242189 / 360.0; 13496 var tau = jd + rate * Astro._fixangle(longitude - Astro._solarLongitude(jd)); 13497 var start = Math.max(jd, tau - 5.0); 13498 var end = tau + 5.0; 13499 13500 return SearchUtils.bisectionSearch(0, start, end, 1e-6, function (l) { 13501 return 180 - Astro._fixangle(Astro._solarLongitude(l) - longitude); 13502 }); 13503 }; 13504 13505 /** 13506 * Floor the julian day to midnight of the current julian day. 13507 * 13508 * @static 13509 * @protected 13510 * @param {number} jd the julian to round 13511 * @return {number} the jd floored to the midnight of the julian day 13512 */ 13513 Astro._floorToJD = function(jd) { 13514 return Math.floor(jd - 0.5) + 0.5; 13515 }; 13516 13517 /** 13518 * Floor the julian day to midnight of the current julian day. 13519 * 13520 * @static 13521 * @protected 13522 * @param {number} jd the julian to round 13523 * @return {number} the jd floored to the midnight of the julian day 13524 */ 13525 Astro._ceilToJD = function(jd) { 13526 return Math.ceil(jd + 0.5) - 0.5; 13527 }; 13528 13529 13530 13531 /*< PersRataDie.js */ 13532 /* 13533 * persratadie.js - Represent a rata die date in the Persian calendar 13534 * 13535 * Copyright © 2014-2015, JEDLSoft 13536 * 13537 * Licensed under the Apache License, Version 2.0 (the "License"); 13538 * you may not use this file except in compliance with the License. 13539 * You may obtain a copy of the License at 13540 * 13541 * http://www.apache.org/licenses/LICENSE-2.0 13542 * 13543 * Unless required by applicable law or agreed to in writing, software 13544 * distributed under the License is distributed on an "AS IS" BASIS, 13545 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13546 * 13547 * See the License for the specific language governing permissions and 13548 * limitations under the License. 13549 */ 13550 13551 /* !depends 13552 ilib.js 13553 MathUtils.js 13554 RataDie.js 13555 Astro.js 13556 GregorianDate.js 13557 */ 13558 13559 13560 13561 13562 /** 13563 * @class 13564 * Construct a new Persian RD date number object. The constructor parameters can 13565 * contain any of the following properties: 13566 * 13567 * <ul> 13568 * <li><i>unixtime<i> - sets the time of this instance according to the given 13569 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 13570 * 13571 * <li><i>julianday</i> - sets the time of this instance according to the given 13572 * Julian Day instance or the Julian Day given as a float 13573 * 13574 * <li><i>year</i> - any integer, including 0 13575 * 13576 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 13577 * 13578 * <li><i>day</i> - 1 to 31 13579 * 13580 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 13581 * is always done with an unambiguous 24 hour representation 13582 * 13583 * <li><i>minute</i> - 0 to 59 13584 * 13585 * <li><i>second</i> - 0 to 59 13586 * 13587 * <li><i>millisecond</i> - 0 to 999 13588 * 13589 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 13590 * </ul> 13591 * 13592 * If the constructor is called with another Persian date instance instead of 13593 * a parameter block, the other instance acts as a parameter block and its 13594 * settings are copied into the current instance.<p> 13595 * 13596 * If the constructor is called with no arguments at all or if none of the 13597 * properties listed above are present, then the RD is calculate based on 13598 * the current date at the time of instantiation. <p> 13599 * 13600 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 13601 * specified in the params, it is assumed that they have the smallest possible 13602 * value in the range for the property (zero or one).<p> 13603 * 13604 * 13605 * @private 13606 * @constructor 13607 * @extends RataDie 13608 * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date 13609 */ 13610 var PersRataDie = function(params) { 13611 this.rd = NaN; 13612 Astro.initAstro( 13613 params && typeof(params.sync) === 'boolean' ? params.sync : true, 13614 params && params.loadParams, 13615 ilib.bind(this, function (x) { 13616 RataDie.call(this, params); 13617 if (params && typeof(params.callback) === 'function') { 13618 params.callback(this); 13619 } 13620 }) 13621 ); 13622 }; 13623 13624 PersRataDie.prototype = new RataDie(); 13625 PersRataDie.prototype.parent = RataDie; 13626 PersRataDie.prototype.constructor = PersRataDie; 13627 13628 /** 13629 * The difference between a zero Julian day and the first Persian date 13630 * @private 13631 * @type number 13632 */ 13633 PersRataDie.prototype.epoch = 1948319.5; 13634 13635 /** 13636 * @protected 13637 */ 13638 PersRataDie.prototype._tehranEquinox = function(year) { 13639 var equJED, equJD, equAPP, equTehran, dtTehran, eot; 13640 13641 // March equinox in dynamical time 13642 equJED = Astro._equinox(year, 0); 13643 13644 // Correct for delta T to obtain Universal time 13645 equJD = equJED - (Astro._deltat(year) / (24 * 60 * 60)); 13646 13647 // Apply the equation of time to yield the apparent time at Greenwich 13648 eot = Astro._equationOfTime(equJED) * 360; 13649 eot = (eot - 20 * Math.floor(eot/20)) / 360; 13650 equAPP = equJD + eot; 13651 13652 /* 13653 * Finally, we must correct for the constant difference between 13654 * the Greenwich meridian and the time zone standard for Iran 13655 * Standard time, 52 degrees 30 minutes to the East. 13656 */ 13657 13658 dtTehran = 52.5 / 360; 13659 equTehran = equAPP + dtTehran; 13660 13661 return equTehran; 13662 }; 13663 13664 /** 13665 * Calculate the year based on the given Julian day. 13666 * @protected 13667 * @param {number} jd the Julian day to get the year for 13668 * @return {{year:number,equinox:number}} the year and the last equinox 13669 */ 13670 PersRataDie.prototype._getYear = function(jd) { 13671 var gd = new GregorianDate({julianday: jd}); 13672 var guess = gd.getYears() - 2, 13673 nexteq, 13674 ret = {}; 13675 13676 //ret.equinox = Math.floor(this._tehranEquinox(guess)); 13677 ret.equinox = this._tehranEquinox(guess); 13678 while (ret.equinox > jd) { 13679 guess--; 13680 // ret.equinox = Math.floor(this._tehranEquinox(guess)); 13681 ret.equinox = this._tehranEquinox(guess); 13682 } 13683 nexteq = ret.equinox - 1; 13684 // if the equinox falls after noon, then the day after that is the start of the 13685 // next year, so truncate the JD to get the noon of the day before the day with 13686 //the equinox on it, then add 0.5 to get the midnight of that day 13687 while (!(Math.floor(ret.equinox) + 0.5 <= jd && jd < Math.floor(nexteq) + 0.5)) { 13688 ret.equinox = nexteq; 13689 guess++; 13690 // nexteq = Math.floor(this._tehranEquinox(guess)); 13691 nexteq = this._tehranEquinox(guess); 13692 } 13693 13694 // Mean solar tropical year is 365.24219878 days 13695 ret.year = Math.round((ret.equinox - this.epoch - 1) / 365.24219878) + 1; 13696 13697 return ret; 13698 }; 13699 13700 /** 13701 * Calculate the Rata Die (fixed day) number of the given date from the 13702 * date components. 13703 * 13704 * @protected 13705 * @param {Object} date the date components to calculate the RD from 13706 */ 13707 PersRataDie.prototype._setDateComponents = function(date) { 13708 var adr, guess, jd; 13709 13710 // Mean solar tropical year is 365.24219878 days 13711 guess = this.epoch + 1 + 365.24219878 * (date.year - 2); 13712 adr = {year: date.year - 1, equinox: 0}; 13713 13714 while (adr.year < date.year) { 13715 adr = this._getYear(guess); 13716 guess = adr.equinox + (365.24219878 + 2); 13717 } 13718 13719 jd = Math.floor(adr.equinox) + 13720 ((date.month <= 7) ? 13721 ((date.month - 1) * 31) : 13722 (((date.month - 1) * 30) + 6) 13723 ) + 13724 (date.day - 1 + 0.5); // add 0.5 so that we convert JDs, which start at noon to RDs which start at midnight 13725 13726 jd += (date.hour * 3600000 + 13727 date.minute * 60000 + 13728 date.second * 1000 + 13729 date.millisecond) / 13730 86400000; 13731 13732 this.rd = jd - this.epoch; 13733 }; 13734 13735 /** 13736 * Return the rd number of the particular day of the week on or before the 13737 * given rd. eg. The Sunday on or before the given rd. 13738 * @private 13739 * @param {number} rd the rata die date of the reference date 13740 * @param {number} dayOfWeek the day of the week that is being sought relative 13741 * to the current date 13742 * @return {number} the rd of the day of the week 13743 */ 13744 PersRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 13745 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); 13746 }; 13747 13748 13749 /*< PersianCal.js */ 13750 /* 13751 * persianastro.js - Represent a Persian astronomical (Hijjri) calendar object. 13752 * 13753 * Copyright © 2014-2015, JEDLSoft 13754 * 13755 * Licensed under the Apache License, Version 2.0 (the "License"); 13756 * you may not use this file except in compliance with the License. 13757 * You may obtain a copy of the License at 13758 * 13759 * http://www.apache.org/licenses/LICENSE-2.0 13760 * 13761 * Unless required by applicable law or agreed to in writing, software 13762 * distributed under the License is distributed on an "AS IS" BASIS, 13763 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13764 * 13765 * See the License for the specific language governing permissions and 13766 * limitations under the License. 13767 */ 13768 13769 13770 /* !depends 13771 Calendar.js 13772 PersRataDie.js 13773 ilib.js 13774 MathUtils.js 13775 */ 13776 13777 13778 13779 13780 /** 13781 * @class 13782 * Construct a new Persian astronomical (Hijjri) calendar object. This class encodes 13783 * information about a Persian calendar. This class differs from the 13784 * Persian calendar in that the leap years are calculated based on the 13785 * astronomical observations of the sun in Teheran, instead of calculating 13786 * the leap years based on a regular cyclical rhythm algorithm.<p> 13787 * 13788 * 13789 * @constructor 13790 * @extends Calendar 13791 */ 13792 var PersianCal = function() { 13793 this.type = "persian"; 13794 }; 13795 13796 /** 13797 * @private 13798 * @const 13799 * @type Array.<number> 13800 * the lengths of each month 13801 */ 13802 PersianCal.monthLengths = [ 13803 31, // Farvardin 13804 31, // Ordibehesht 13805 31, // Khordad 13806 31, // Tir 13807 31, // Mordad 13808 31, // Shahrivar 13809 30, // Mehr 13810 30, // Aban 13811 30, // Azar 13812 30, // Dey 13813 30, // Bahman 13814 29 // Esfand 13815 ]; 13816 13817 /** 13818 * Return the number of months in the given year. The number of months in a year varies 13819 * for some luni-solar calendars because in some years, an extra month is needed to extend the 13820 * days in a year to an entire solar year. The month is represented as a 1-based number 13821 * where 1=first month, 2=second month, etc. 13822 * 13823 * @param {number} year a year for which the number of months is sought 13824 * @return {number} The number of months in the given year 13825 */ 13826 PersianCal.prototype.getNumMonths = function(year) { 13827 return 12; 13828 }; 13829 13830 /** 13831 * Return the number of days in a particular month in a particular year. This function 13832 * can return a different number for a month depending on the year because of things 13833 * like leap years. 13834 * 13835 * @param {number} month the month for which the length is sought 13836 * @param {number} year the year within which that month can be found 13837 * @return {number} the number of days within the given month in the given year 13838 */ 13839 PersianCal.prototype.getMonLength = function(month, year) { 13840 if (month !== 12 || !this.isLeapYear(year)) { 13841 return PersianCal.monthLengths[month-1]; 13842 } else { 13843 // Month 12, Esfand, has 30 days instead of 29 in leap years 13844 return 30; 13845 } 13846 }; 13847 13848 /** 13849 * Return true if the given year is a leap year in the Persian astronomical calendar. 13850 * @param {number} year the year for which the leap year information is being sought 13851 * @return {boolean} true if the given year is a leap year 13852 */ 13853 PersianCal.prototype.isLeapYear = function(year) { 13854 var rdNextYear = new PersRataDie({ 13855 cal: this, 13856 year: year + 1, 13857 month: 1, 13858 day: 1, 13859 hour: 0, 13860 minute: 0, 13861 second: 0, 13862 millisecond: 0 13863 }); 13864 var rdThisYear = new PersRataDie({ 13865 cal: this, 13866 year: year, 13867 month: 1, 13868 day: 1, 13869 hour: 0, 13870 minute: 0, 13871 second: 0, 13872 millisecond: 0 13873 }); 13874 return (rdNextYear.getRataDie() - rdThisYear.getRataDie()) > 365; 13875 }; 13876 13877 /** 13878 * Return the type of this calendar. 13879 * 13880 * @return {string} the name of the type of this calendar 13881 */ 13882 PersianCal.prototype.getType = function() { 13883 return this.type; 13884 }; 13885 13886 /** 13887 * Return a date instance for this calendar type using the given 13888 * options. 13889 * @param {Object} options options controlling the construction of 13890 * the date instance 13891 * @return {IDate} a date appropriate for this calendar type 13892 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 13893 */ 13894 PersianCal.prototype.newDateInstance = function (options) { 13895 return new PersianDate(options); 13896 }; 13897 13898 /* register this calendar for the factory method */ 13899 Calendar._constructors["persian"] = PersianCal; 13900 13901 13902 /*< PersianDate.js */ 13903 /* 13904 * PersianDate.js - Represent a date in the Persian astronomical (Hijjri) calendar 13905 * 13906 * Copyright © 2014-2015, JEDLSoft 13907 * 13908 * Licensed under the Apache License, Version 2.0 (the "License"); 13909 * you may not use this file except in compliance with the License. 13910 * You may obtain a copy of the License at 13911 * 13912 * http://www.apache.org/licenses/LICENSE-2.0 13913 * 13914 * Unless required by applicable law or agreed to in writing, software 13915 * distributed under the License is distributed on an "AS IS" BASIS, 13916 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13917 * 13918 * See the License for the specific language governing permissions and 13919 * limitations under the License. 13920 */ 13921 13922 /* !depends 13923 ilib.js 13924 Locale.js 13925 TimeZone.js 13926 IDate.js 13927 PersRataDie.js 13928 PersianCal.js 13929 SearchUtils.js 13930 MathUtils.js 13931 LocaleInfo.js 13932 Astro.js 13933 */ 13934 13935 // !data astro 13936 13937 13938 13939 13940 /** 13941 * @class 13942 * 13943 * Construct a new Persian astronomical date object. The constructor parameters can 13944 * contain any of the following properties: 13945 * 13946 * <ul> 13947 * <li><i>unixtime<i> - sets the time of this instance according to the given 13948 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 13949 * 13950 * <li><i>julianday</i> - sets the time of this instance according to the given 13951 * Julian Day instance or the Julian Day given as a float 13952 * 13953 * <li><i>year</i> - any integer, including 0 13954 * 13955 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 13956 * 13957 * <li><i>day</i> - 1 to 31 13958 * 13959 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 13960 * is always done with an unambiguous 24 hour representation 13961 * 13962 * <li><i>minute</i> - 0 to 59 13963 * 13964 * <li><i>second</i> - 0 to 59 13965 * 13966 * <li><i>millisecond</i> - 0 to 999 13967 * 13968 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 13969 * of this persian date. The date/time is kept in the local time. The time zone 13970 * is used later if this date is formatted according to a different time zone and 13971 * the difference has to be calculated, or when the date format has a time zone 13972 * component in it. 13973 * 13974 * <li><i>locale</i> - locale for this persian date. If the time zone is not 13975 * given, it can be inferred from this locale. For locales that span multiple 13976 * time zones, the one with the largest population is chosen as the one that 13977 * represents the locale. 13978 * 13979 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 13980 * </ul> 13981 * 13982 * If the constructor is called with another Persian date instance instead of 13983 * a parameter block, the other instance acts as a parameter block and its 13984 * settings are copied into the current instance.<p> 13985 * 13986 * If the constructor is called with no arguments at all or if none of the 13987 * properties listed above 13988 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 13989 * components are 13990 * filled in with the current date at the time of instantiation. Note that if 13991 * you do not give the time zone when defaulting to the current time and the 13992 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 13993 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 13994 * Mean Time").<p> 13995 * 13996 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 13997 * specified in the params, it is assumed that they have the smallest possible 13998 * value in the range for the property (zero or one).<p> 13999 * 14000 * 14001 * @constructor 14002 * @extends IDate 14003 * @param {Object=} params parameters that govern the settings and behaviour of this Persian date 14004 */ 14005 var PersianDate = function(params) { 14006 this.cal = new PersianCal(); 14007 this.timezone = "local"; 14008 14009 if (params) { 14010 if (params.locale) { 14011 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 14012 var li = new LocaleInfo(this.locale); 14013 this.timezone = li.getTimeZone(); 14014 } 14015 if (params.timezone) { 14016 this.timezone = params.timezone; 14017 } 14018 } 14019 14020 Astro.initAstro( 14021 params && typeof(params.sync) === 'boolean' ? params.sync : true, 14022 params && params.loadParams, 14023 ilib.bind(this, function (x) { 14024 if (params && (params.year || params.month || params.day || params.hour || 14025 params.minute || params.second || params.millisecond)) { 14026 /** 14027 * Year in the Persian calendar. 14028 * @type number 14029 */ 14030 this.year = parseInt(params.year, 10) || 0; 14031 14032 /** 14033 * The month number, ranging from 1 to 12 14034 * @type number 14035 */ 14036 this.month = parseInt(params.month, 10) || 1; 14037 14038 /** 14039 * The day of the month. This ranges from 1 to 31. 14040 * @type number 14041 */ 14042 this.day = parseInt(params.day, 10) || 1; 14043 14044 /** 14045 * The hour of the day. This can be a number from 0 to 23, as times are 14046 * stored unambiguously in the 24-hour clock. 14047 * @type number 14048 */ 14049 this.hour = parseInt(params.hour, 10) || 0; 14050 14051 /** 14052 * The minute of the hours. Ranges from 0 to 59. 14053 * @type number 14054 */ 14055 this.minute = parseInt(params.minute, 10) || 0; 14056 14057 /** 14058 * The second of the minute. Ranges from 0 to 59. 14059 * @type number 14060 */ 14061 this.second = parseInt(params.second, 10) || 0; 14062 14063 /** 14064 * The millisecond of the second. Ranges from 0 to 999. 14065 * @type number 14066 */ 14067 this.millisecond = parseInt(params.millisecond, 10) || 0; 14068 14069 /** 14070 * The day of the year. Ranges from 1 to 366. 14071 * @type number 14072 */ 14073 this.dayOfYear = parseInt(params.dayOfYear, 10); 14074 14075 if (typeof(params.dst) === 'boolean') { 14076 this.dst = params.dst; 14077 } 14078 14079 this.rd = this.newRd(this); 14080 14081 // add the time zone offset to the rd to convert to UTC 14082 if (!this.tz) { 14083 this.tz = new TimeZone({id: this.timezone}); 14084 } 14085 // getOffsetMillis requires that this.year, this.rd, and this.dst 14086 // are set in order to figure out which time zone rules apply and 14087 // what the offset is at that point in the year 14088 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 14089 if (this.offset !== 0) { 14090 this.rd = this.newRd({ 14091 rd: this.rd.getRataDie() - this.offset 14092 }); 14093 } 14094 } 14095 14096 if (!this.rd) { 14097 this.rd = this.newRd(params); 14098 this._calcDateComponents(); 14099 } 14100 14101 if (params && typeof(params.onLoad) === 'function') { 14102 params.onLoad(this); 14103 } 14104 }) 14105 ); 14106 }; 14107 14108 PersianDate.prototype = new IDate({noinstance: true}); 14109 PersianDate.prototype.parent = IDate; 14110 PersianDate.prototype.constructor = PersianDate; 14111 14112 /** 14113 * @private 14114 * @const 14115 * @type Array.<number> 14116 * the cumulative lengths of each month, for a non-leap year 14117 */ 14118 PersianDate.cumMonthLengths = [ 14119 0, // Farvardin 14120 31, // Ordibehesht 14121 62, // Khordad 14122 93, // Tir 14123 124, // Mordad 14124 155, // Shahrivar 14125 186, // Mehr 14126 216, // Aban 14127 246, // Azar 14128 276, // Dey 14129 306, // Bahman 14130 336, // Esfand 14131 366 14132 ]; 14133 14134 /** 14135 * Return a new RD for this date type using the given params. 14136 * @protected 14137 * @param {Object=} params the parameters used to create this rata die instance 14138 * @returns {RataDie} the new RD instance for the given params 14139 */ 14140 PersianDate.prototype.newRd = function (params) { 14141 return new PersRataDie(params); 14142 }; 14143 14144 /** 14145 * Return the year for the given RD 14146 * @protected 14147 * @param {number} rd RD to calculate from 14148 * @returns {number} the year for the RD 14149 */ 14150 PersianDate.prototype._calcYear = function(rd) { 14151 var julianday = rd + this.rd.epoch; 14152 return this.rd._getYear(julianday).year; 14153 }; 14154 14155 /** 14156 * @private 14157 * Calculate date components for the given RD date. 14158 */ 14159 PersianDate.prototype._calcDateComponents = function () { 14160 var remainder, 14161 rd = this.rd.getRataDie(); 14162 14163 this.year = this._calcYear(rd); 14164 14165 if (typeof(this.offset) === "undefined") { 14166 // now offset the RD by the time zone, then recalculate in case we were 14167 // near the year boundary 14168 if (!this.tz) { 14169 this.tz = new TimeZone({id: this.timezone}); 14170 } 14171 this.offset = this.tz.getOffsetMillis(this) / 86400000; 14172 } 14173 14174 if (this.offset !== 0) { 14175 rd += this.offset; 14176 this.year = this._calcYear(rd); 14177 } 14178 14179 //console.log("PersDate.calcComponent: calculating for rd " + rd); 14180 //console.log("PersDate.calcComponent: year is " + ret.year); 14181 var yearStart = this.newRd({ 14182 year: this.year, 14183 month: 1, 14184 day: 1, 14185 hour: 0, 14186 minute: 0, 14187 second: 0, 14188 millisecond: 0 14189 }); 14190 remainder = rd - yearStart.getRataDie() + 1; 14191 14192 this.dayOfYear = remainder; 14193 14194 //console.log("PersDate.calcComponent: remainder is " + remainder); 14195 14196 this.month = SearchUtils.bsearch(Math.floor(remainder), PersianDate.cumMonthLengths); 14197 remainder -= PersianDate.cumMonthLengths[this.month-1]; 14198 14199 //console.log("PersDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 14200 14201 this.day = Math.floor(remainder); 14202 remainder -= this.day; 14203 14204 //console.log("PersDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 14205 14206 // now convert to milliseconds for the rest of the calculation 14207 remainder = Math.round(remainder * 86400000); 14208 14209 this.hour = Math.floor(remainder/3600000); 14210 remainder -= this.hour * 3600000; 14211 14212 this.minute = Math.floor(remainder/60000); 14213 remainder -= this.minute * 60000; 14214 14215 this.second = Math.floor(remainder/1000); 14216 remainder -= this.second * 1000; 14217 14218 this.millisecond = remainder; 14219 }; 14220 14221 /** 14222 * Return the day of the week of this date. The day of the week is encoded 14223 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 14224 * 14225 * @return {number} the day of the week 14226 */ 14227 PersianDate.prototype.getDayOfWeek = function() { 14228 var rd = Math.floor(this.getRataDie()); 14229 return MathUtils.mod(rd-3, 7); 14230 }; 14231 14232 /** 14233 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 14234 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 14235 * December 31st is 365 in regular years, or 366 in leap years. 14236 * @return {number} the ordinal day of the year 14237 */ 14238 PersianDate.prototype.getDayOfYear = function() { 14239 return PersianDate.cumMonthLengths[this.month-1] + this.day; 14240 }; 14241 14242 /** 14243 * Return the era for this date as a number. The value for the era for Persian 14244 * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno 14245 * persico or AP). 14246 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, 14247 * there is a year 0, so any years that are negative or zero are BP. 14248 * @return {number} 1 if this date is in the common era, -1 if it is before the 14249 * common era 14250 */ 14251 PersianDate.prototype.getEra = function() { 14252 return (this.year < 1) ? -1 : 1; 14253 }; 14254 14255 /** 14256 * Return the name of the calendar that governs this date. 14257 * 14258 * @return {string} a string giving the name of the calendar 14259 */ 14260 PersianDate.prototype.getCalendar = function() { 14261 return "persian"; 14262 }; 14263 14264 // register with the factory method 14265 IDate._constructors["persian"] = PersianDate; 14266 14267 14268 /*< PersianAlgoCal.js */ 14269 /* 14270 * persian.js - Represent a Persian algorithmic calendar object. 14271 * 14272 * Copyright © 2014-2015, JEDLSoft 14273 * 14274 * Licensed under the Apache License, Version 2.0 (the "License"); 14275 * you may not use this file except in compliance with the License. 14276 * You may obtain a copy of the License at 14277 * 14278 * http://www.apache.org/licenses/LICENSE-2.0 14279 * 14280 * Unless required by applicable law or agreed to in writing, software 14281 * distributed under the License is distributed on an "AS IS" BASIS, 14282 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14283 * 14284 * See the License for the specific language governing permissions and 14285 * limitations under the License. 14286 */ 14287 14288 14289 /* !depends ilib.js Calendar.js MathUtils.js */ 14290 14291 14292 /** 14293 * @class 14294 * Construct a new Persian algorithmic calendar object. This class encodes information about 14295 * a Persian algorithmic calendar.<p> 14296 * 14297 * 14298 * @constructor 14299 * @extends Calendar 14300 */ 14301 var PersianAlgoCal = function() { 14302 this.type = "persian-algo"; 14303 }; 14304 14305 /** 14306 * @private 14307 * @const 14308 * @type Array.<number> 14309 * the lengths of each month 14310 */ 14311 PersianAlgoCal.monthLengths = [ 14312 31, // Farvardin 14313 31, // Ordibehesht 14314 31, // Khordad 14315 31, // Tir 14316 31, // Mordad 14317 31, // Shahrivar 14318 30, // Mehr 14319 30, // Aban 14320 30, // Azar 14321 30, // Dey 14322 30, // Bahman 14323 29 // Esfand 14324 ]; 14325 14326 /** 14327 * Return the number of months in the given year. The number of months in a year varies 14328 * for some luni-solar calendars because in some years, an extra month is needed to extend the 14329 * days in a year to an entire solar year. The month is represented as a 1-based number 14330 * where 1=first month, 2=second month, etc. 14331 * 14332 * @param {number} year a year for which the number of months is sought 14333 * @return {number} The number of months in the given year 14334 */ 14335 PersianAlgoCal.prototype.getNumMonths = function(year) { 14336 return 12; 14337 }; 14338 14339 /** 14340 * Return the number of days in a particular month in a particular year. This function 14341 * can return a different number for a month depending on the year because of things 14342 * like leap years. 14343 * 14344 * @param {number} month the month for which the length is sought 14345 * @param {number} year the year within which that month can be found 14346 * @return {number} the number of days within the given month in the given year 14347 */ 14348 PersianAlgoCal.prototype.getMonLength = function(month, year) { 14349 if (month !== 12 || !this.isLeapYear(year)) { 14350 return PersianAlgoCal.monthLengths[month-1]; 14351 } else { 14352 // Month 12, Esfand, has 30 days instead of 29 in leap years 14353 return 30; 14354 } 14355 }; 14356 14357 /** 14358 * Return the equivalent year in the 2820 year cycle that begins on 14359 * Far 1, 474. This particular cycle obeys the cycle-of-years formula 14360 * whereas the others do not specifically. This cycle can be used as 14361 * a proxy for other years outside of the cycle by shifting them into 14362 * the cycle. 14363 * @param {number} year year to find the equivalent cycle year for 14364 * @returns {number} the equivalent cycle year 14365 */ 14366 PersianAlgoCal.prototype.equivalentCycleYear = function(year) { 14367 var y = year - (year >= 0 ? 474 : 473); 14368 return MathUtils.mod(y, 2820) + 474; 14369 }; 14370 14371 /** 14372 * Return true if the given year is a leap year in the Persian calendar. 14373 * The year parameter may be given as a number, or as a PersAlgoDate object. 14374 * @param {number} year the year for which the leap year information is being sought 14375 * @return {boolean} true if the given year is a leap year 14376 */ 14377 PersianAlgoCal.prototype.isLeapYear = function(year) { 14378 return (MathUtils.mod((this.equivalentCycleYear(year) + 38) * 682, 2816) < 682); 14379 }; 14380 14381 /** 14382 * Return the type of this calendar. 14383 * 14384 * @return {string} the name of the type of this calendar 14385 */ 14386 PersianAlgoCal.prototype.getType = function() { 14387 return this.type; 14388 }; 14389 14390 /** 14391 * Return a date instance for this calendar type using the given 14392 * options. 14393 * @param {Object} options options controlling the construction of 14394 * the date instance 14395 * @return {IDate} a date appropriate for this calendar type 14396 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 14397 */ 14398 PersianAlgoCal.prototype.newDateInstance = function (options) { 14399 return new PersianAlgoDate(options); 14400 }; 14401 14402 /* register this calendar for the factory method */ 14403 Calendar._constructors["persian-algo"] = PersianAlgoCal; 14404 14405 14406 /*< PersAlgoRataDie.js */ 14407 /* 14408 * PersAlsoRataDie.js - Represent an RD date in the Persian algorithmic calendar 14409 * 14410 * Copyright © 2014-2015, JEDLSoft 14411 * 14412 * Licensed under the Apache License, Version 2.0 (the "License"); 14413 * you may not use this file except in compliance with the License. 14414 * You may obtain a copy of the License at 14415 * 14416 * http://www.apache.org/licenses/LICENSE-2.0 14417 * 14418 * Unless required by applicable law or agreed to in writing, software 14419 * distributed under the License is distributed on an "AS IS" BASIS, 14420 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14421 * 14422 * See the License for the specific language governing permissions and 14423 * limitations under the License. 14424 */ 14425 14426 /* !depends 14427 PersianAlgoCal.js 14428 MathUtils.js 14429 RataDie.js 14430 */ 14431 14432 14433 /** 14434 * @class 14435 * Construct a new Persian RD date number object. The constructor parameters can 14436 * contain any of the following properties: 14437 * 14438 * <ul> 14439 * <li><i>unixtime<i> - sets the time of this instance according to the given 14440 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 14441 * 14442 * <li><i>julianday</i> - sets the time of this instance according to the given 14443 * Julian Day instance or the Julian Day given as a float 14444 * 14445 * <li><i>year</i> - any integer, including 0 14446 * 14447 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 14448 * 14449 * <li><i>day</i> - 1 to 31 14450 * 14451 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 14452 * is always done with an unambiguous 24 hour representation 14453 * 14454 * <li><i>minute</i> - 0 to 59 14455 * 14456 * <li><i>second</i> - 0 to 59 14457 * 14458 * <li><i>millisecond</i> - 0 to 999 14459 * 14460 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14461 * </ul> 14462 * 14463 * If the constructor is called with another Persian date instance instead of 14464 * a parameter block, the other instance acts as a parameter block and its 14465 * settings are copied into the current instance.<p> 14466 * 14467 * If the constructor is called with no arguments at all or if none of the 14468 * properties listed above are present, then the RD is calculate based on 14469 * the current date at the time of instantiation. <p> 14470 * 14471 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14472 * specified in the params, it is assumed that they have the smallest possible 14473 * value in the range for the property (zero or one).<p> 14474 * 14475 * 14476 * @private 14477 * @constructor 14478 * @extends RataDie 14479 * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date 14480 */ 14481 var PersAlgoRataDie = function(params) { 14482 this.cal = params && params.cal || new PersianAlgoCal(); 14483 this.rd = NaN; 14484 RataDie.call(this, params); 14485 }; 14486 14487 PersAlgoRataDie.prototype = new RataDie(); 14488 PersAlgoRataDie.prototype.parent = RataDie; 14489 PersAlgoRataDie.prototype.constructor = PersAlgoRataDie; 14490 14491 /** 14492 * The difference between a zero Julian day and the first Persian date 14493 * @private 14494 * @type number 14495 */ 14496 PersAlgoRataDie.prototype.epoch = 1948319.5; 14497 14498 /** 14499 * @private 14500 * @const 14501 * @type Array.<number> 14502 * the cumulative lengths of each month, for a non-leap year 14503 */ 14504 PersAlgoRataDie.cumMonthLengths = [ 14505 0, // Farvardin 14506 31, // Ordibehesht 14507 62, // Khordad 14508 93, // Tir 14509 124, // Mordad 14510 155, // Shahrivar 14511 186, // Mehr 14512 216, // Aban 14513 246, // Azar 14514 276, // Dey 14515 306, // Bahman 14516 336, // Esfand 14517 365 14518 ]; 14519 14520 /** 14521 * Calculate the Rata Die (fixed day) number of the given date from the 14522 * date components. 14523 * 14524 * @protected 14525 * @param {Object} date the date components to calculate the RD from 14526 */ 14527 PersAlgoRataDie.prototype._setDateComponents = function(date) { 14528 var year = this.cal.equivalentCycleYear(date.year); 14529 var y = date.year - (date.year >= 0 ? 474 : 473); 14530 var rdOfYears = 1029983 * Math.floor(y/2820) + 365 * (year - 1) + Math.floor((682 * year - 110) / 2816); 14531 var dayInYear = (date.month > 1 ? PersAlgoRataDie.cumMonthLengths[date.month-1] : 0) + date.day; 14532 var rdtime = (date.hour * 3600000 + 14533 date.minute * 60000 + 14534 date.second * 1000 + 14535 date.millisecond) / 14536 86400000; 14537 14538 /* 14539 // console.log("getRataDie: converting " + JSON.stringify(this)); 14540 console.log("getRataDie: year is " + year); 14541 console.log("getRataDie: rd of years is " + rdOfYears); 14542 console.log("getRataDie: day in year is " + dayInYear); 14543 console.log("getRataDie: rdtime is " + rdtime); 14544 console.log("getRataDie: rd is " + (rdOfYears + dayInYear + rdtime)); 14545 */ 14546 14547 this.rd = rdOfYears + dayInYear + rdtime; 14548 }; 14549 14550 /** 14551 * Return the rd number of the particular day of the week on or before the 14552 * given rd. eg. The Sunday on or before the given rd. 14553 * @private 14554 * @param {number} rd the rata die date of the reference date 14555 * @param {number} dayOfWeek the day of the week that is being sought relative 14556 * to the current date 14557 * @return {number} the rd of the day of the week 14558 */ 14559 PersAlgoRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 14560 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); 14561 }; 14562 14563 14564 /*< PersianAlgoDate.js */ 14565 /* 14566 * PersianAlgoDate.js - Represent a date in the Persian algorithmic calendar 14567 * 14568 * Copyright © 2014-2015, JEDLSoft 14569 * 14570 * Licensed under the Apache License, Version 2.0 (the "License"); 14571 * you may not use this file except in compliance with the License. 14572 * You may obtain a copy of the License at 14573 * 14574 * http://www.apache.org/licenses/LICENSE-2.0 14575 * 14576 * Unless required by applicable law or agreed to in writing, software 14577 * distributed under the License is distributed on an "AS IS" BASIS, 14578 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14579 * 14580 * See the License for the specific language governing permissions and 14581 * limitations under the License. 14582 */ 14583 14584 /* !depends 14585 ilib.js 14586 Locale.js 14587 LocaleInfo.js 14588 TimeZone.js 14589 IDate.js 14590 PersianAlgoCal.js 14591 SearchUtils.js 14592 MathUtils.js 14593 PersAlgoRataDie.js 14594 */ 14595 14596 14597 14598 14599 /** 14600 * @class 14601 * 14602 * Construct a new Persian date object. The constructor parameters can 14603 * contain any of the following properties: 14604 * 14605 * <ul> 14606 * <li><i>unixtime<i> - sets the time of this instance according to the given 14607 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 14608 * 14609 * <li><i>julianday</i> - sets the time of this instance according to the given 14610 * Julian Day instance or the Julian Day given as a float 14611 * 14612 * <li><i>year</i> - any integer, including 0 14613 * 14614 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 14615 * 14616 * <li><i>day</i> - 1 to 31 14617 * 14618 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 14619 * is always done with an unambiguous 24 hour representation 14620 * 14621 * <li><i>minute</i> - 0 to 59 14622 * 14623 * <li><i>second</i> - 0 to 59 14624 * 14625 * <li><i>millisecond</i> - 0 to 999 14626 * 14627 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 14628 * of this persian date. The date/time is kept in the local time. The time zone 14629 * is used later if this date is formatted according to a different time zone and 14630 * the difference has to be calculated, or when the date format has a time zone 14631 * component in it. 14632 * 14633 * <li><i>locale</i> - locale for this persian date. If the time zone is not 14634 * given, it can be inferred from this locale. For locales that span multiple 14635 * time zones, the one with the largest population is chosen as the one that 14636 * represents the locale. 14637 * 14638 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14639 * </ul> 14640 * 14641 * If the constructor is called with another Persian date instance instead of 14642 * a parameter block, the other instance acts as a parameter block and its 14643 * settings are copied into the current instance.<p> 14644 * 14645 * If the constructor is called with no arguments at all or if none of the 14646 * properties listed above 14647 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 14648 * components are 14649 * filled in with the current date at the time of instantiation. Note that if 14650 * you do not give the time zone when defaulting to the current time and the 14651 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 14652 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 14653 * Mean Time").<p> 14654 * 14655 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14656 * specified in the params, it is assumed that they have the smallest possible 14657 * value in the range for the property (zero or one).<p> 14658 * 14659 * 14660 * @constructor 14661 * @extends IDate 14662 * @param {Object=} params parameters that govern the settings and behaviour of this Persian date 14663 */ 14664 var PersianAlgoDate = function(params) { 14665 this.cal = new PersianAlgoCal(); 14666 this.timezone = "local"; 14667 14668 if (params) { 14669 if (params.locale) { 14670 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 14671 var li = new LocaleInfo(this.locale); 14672 this.timezone = li.getTimeZone(); 14673 } 14674 if (params.timezone) { 14675 this.timezone = params.timezone; 14676 } 14677 14678 if (params.year || params.month || params.day || params.hour || 14679 params.minute || params.second || params.millisecond ) { 14680 /** 14681 * Year in the Persian calendar. 14682 * @type number 14683 */ 14684 this.year = parseInt(params.year, 10) || 0; 14685 14686 /** 14687 * The month number, ranging from 1 to 12 14688 * @type number 14689 */ 14690 this.month = parseInt(params.month, 10) || 1; 14691 14692 /** 14693 * The day of the month. This ranges from 1 to 31. 14694 * @type number 14695 */ 14696 this.day = parseInt(params.day, 10) || 1; 14697 14698 /** 14699 * The hour of the day. This can be a number from 0 to 23, as times are 14700 * stored unambiguously in the 24-hour clock. 14701 * @type number 14702 */ 14703 this.hour = parseInt(params.hour, 10) || 0; 14704 14705 /** 14706 * The minute of the hours. Ranges from 0 to 59. 14707 * @type number 14708 */ 14709 this.minute = parseInt(params.minute, 10) || 0; 14710 14711 /** 14712 * The second of the minute. Ranges from 0 to 59. 14713 * @type number 14714 */ 14715 this.second = parseInt(params.second, 10) || 0; 14716 14717 /** 14718 * The millisecond of the second. Ranges from 0 to 999. 14719 * @type number 14720 */ 14721 this.millisecond = parseInt(params.millisecond, 10) || 0; 14722 14723 /** 14724 * The day of the year. Ranges from 1 to 366. 14725 * @type number 14726 */ 14727 this.dayOfYear = parseInt(params.dayOfYear, 10); 14728 14729 if (typeof(params.dst) === 'boolean') { 14730 this.dst = params.dst; 14731 } 14732 14733 this.rd = this.newRd(this); 14734 14735 // add the time zone offset to the rd to convert to UTC 14736 if (!this.tz) { 14737 this.tz = new TimeZone({id: this.timezone}); 14738 } 14739 // getOffsetMillis requires that this.year, this.rd, and this.dst 14740 // are set in order to figure out which time zone rules apply and 14741 // what the offset is at that point in the year 14742 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 14743 if (this.offset !== 0) { 14744 this.rd = this.newRd({ 14745 rd: this.rd.getRataDie() - this.offset 14746 }); 14747 } 14748 } 14749 } 14750 14751 if (!this.rd) { 14752 this.rd = this.newRd(params); 14753 this._calcDateComponents(); 14754 } 14755 }; 14756 14757 PersianAlgoDate.prototype = new IDate({noinstance: true}); 14758 PersianAlgoDate.prototype.parent = IDate; 14759 PersianAlgoDate.prototype.constructor = PersianAlgoDate; 14760 14761 /** 14762 * Return a new RD for this date type using the given params. 14763 * @protected 14764 * @param {Object=} params the parameters used to create this rata die instance 14765 * @returns {RataDie} the new RD instance for the given params 14766 */ 14767 PersianAlgoDate.prototype.newRd = function (params) { 14768 return new PersAlgoRataDie(params); 14769 }; 14770 14771 /** 14772 * Return the year for the given RD 14773 * @protected 14774 * @param {number} rd RD to calculate from 14775 * @returns {number} the year for the RD 14776 */ 14777 PersianAlgoDate.prototype._calcYear = function(rd) { 14778 var shiftedRd = rd - 173126; 14779 var numberOfCycles = Math.floor(shiftedRd / 1029983); 14780 var shiftedDayInCycle = MathUtils.mod(shiftedRd, 1029983); 14781 var yearInCycle = (shiftedDayInCycle === 1029982) ? 2820 : Math.floor((2816 * shiftedDayInCycle + 1031337) / 1028522); 14782 var year = 474 + 2820 * numberOfCycles + yearInCycle; 14783 return (year > 0) ? year : year - 1; 14784 }; 14785 14786 /** 14787 * @private 14788 * Calculate date components for the given RD date. 14789 */ 14790 PersianAlgoDate.prototype._calcDateComponents = function () { 14791 var remainder, 14792 rd = this.rd.getRataDie(); 14793 14794 this.year = this._calcYear(rd); 14795 14796 if (typeof(this.offset) === "undefined") { 14797 // now offset the RD by the time zone, then recalculate in case we were 14798 // near the year boundary 14799 if (!this.tz) { 14800 this.tz = new TimeZone({id: this.timezone}); 14801 } 14802 this.offset = this.tz.getOffsetMillis(this) / 86400000; 14803 } 14804 14805 if (this.offset !== 0) { 14806 rd += this.offset; 14807 this.year = this._calcYear(rd); 14808 } 14809 14810 //console.log("PersAlgoDate.calcComponent: calculating for rd " + rd); 14811 //console.log("PersAlgoDate.calcComponent: year is " + ret.year); 14812 var yearStart = this.newRd({ 14813 year: this.year, 14814 month: 1, 14815 day: 1, 14816 hour: 0, 14817 minute: 0, 14818 second: 0, 14819 millisecond: 0 14820 }); 14821 remainder = rd - yearStart.getRataDie() + 1; 14822 14823 this.dayOfYear = remainder; 14824 14825 //console.log("PersAlgoDate.calcComponent: remainder is " + remainder); 14826 14827 this.month = SearchUtils.bsearch(remainder, PersAlgoRataDie.cumMonthLengths); 14828 remainder -= PersAlgoRataDie.cumMonthLengths[this.month-1]; 14829 14830 //console.log("PersAlgoDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 14831 14832 this.day = Math.floor(remainder); 14833 remainder -= this.day; 14834 14835 //console.log("PersAlgoDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 14836 14837 // now convert to milliseconds for the rest of the calculation 14838 remainder = Math.round(remainder * 86400000); 14839 14840 this.hour = Math.floor(remainder/3600000); 14841 remainder -= this.hour * 3600000; 14842 14843 this.minute = Math.floor(remainder/60000); 14844 remainder -= this.minute * 60000; 14845 14846 this.second = Math.floor(remainder/1000); 14847 remainder -= this.second * 1000; 14848 14849 this.millisecond = remainder; 14850 }; 14851 14852 /** 14853 * Return the day of the week of this date. The day of the week is encoded 14854 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 14855 * 14856 * @return {number} the day of the week 14857 */ 14858 PersianAlgoDate.prototype.getDayOfWeek = function() { 14859 var rd = Math.floor(this.getRataDie()); 14860 return MathUtils.mod(rd-3, 7); 14861 }; 14862 14863 /** 14864 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 14865 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 14866 * December 31st is 365 in regular years, or 366 in leap years. 14867 * @return {number} the ordinal day of the year 14868 */ 14869 PersianAlgoDate.prototype.getDayOfYear = function() { 14870 return PersAlgoRataDie.cumMonthLengths[this.month-1] + this.day; 14871 }; 14872 14873 /** 14874 * Return the era for this date as a number. The value for the era for Persian 14875 * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno 14876 * persico or AP). 14877 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, 14878 * there is a year 0, so any years that are negative or zero are BP. 14879 * @return {number} 1 if this date is in the common era, -1 if it is before the 14880 * common era 14881 */ 14882 PersianAlgoDate.prototype.getEra = function() { 14883 return (this.year < 1) ? -1 : 1; 14884 }; 14885 14886 /** 14887 * Return the name of the calendar that governs this date. 14888 * 14889 * @return {string} a string giving the name of the calendar 14890 */ 14891 PersianAlgoDate.prototype.getCalendar = function() { 14892 return "persian-algo"; 14893 }; 14894 14895 // register with the factory method 14896 IDate._constructors["persian-algo"] = PersianAlgoDate; 14897 14898 14899 /*< HanCal.js */ 14900 /* 14901 * han.js - Represent a Han Chinese Lunar calendar object. 14902 * 14903 * Copyright © 2014-2015, JEDLSoft 14904 * 14905 * Licensed under the Apache License, Version 2.0 (the "License"); 14906 * you may not use this file except in compliance with the License. 14907 * You may obtain a copy of the License at 14908 * 14909 * http://www.apache.org/licenses/LICENSE-2.0 14910 * 14911 * Unless required by applicable law or agreed to in writing, software 14912 * distributed under the License is distributed on an "AS IS" BASIS, 14913 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14914 * 14915 * See the License for the specific language governing permissions and 14916 * limitations under the License. 14917 */ 14918 14919 /* !depends 14920 ilib.js 14921 Calendar.js 14922 MathUtils.js 14923 Astro.js 14924 GregorianDate.js 14925 GregRataDie.js 14926 RataDie.js 14927 */ 14928 14929 14930 14931 14932 /** 14933 * @class 14934 * Construct a new Han algorithmic calendar object. This class encodes information about 14935 * a Han algorithmic calendar.<p> 14936 * 14937 * 14938 * @constructor 14939 * @param {Object=} params optional parameters to load the calendrical data 14940 * @extends Calendar 14941 */ 14942 var HanCal = function(params) { 14943 this.type = "han"; 14944 var sync = params && typeof(params.sync) === 'boolean' ? params.sync : true; 14945 14946 Astro.initAstro(sync, params && params.loadParams, ilib.bind(this, function (x) { 14947 if (params && typeof(params.callback) === 'function') { 14948 params.callback(this); 14949 } 14950 })); 14951 }; 14952 14953 /** 14954 * @protected 14955 * @static 14956 * @param {number} year 14957 * @param {number=} cycle 14958 * @return {number} 14959 */ 14960 HanCal._getElapsedYear = function(year, cycle) { 14961 var elapsedYear = year || 0; 14962 if (typeof(year) !== 'undefined' && year < 61 && typeof(cycle) !== 'undefined') { 14963 elapsedYear = 60 * cycle + year; 14964 } 14965 return elapsedYear; 14966 }; 14967 14968 /** 14969 * @protected 14970 * @static 14971 * @param {number} jd julian day to calculate from 14972 * @param {number} longitude longitude to seek 14973 * @returns {number} the julian day of the next time that the solar longitude 14974 * is a multiple of the given longitude 14975 */ 14976 HanCal._hanNextSolarLongitude = function(jd, longitude) { 14977 var tz = HanCal._chineseTZ(jd); 14978 var uni = Astro._universalFromLocal(jd, tz); 14979 var sol = Astro._nextSolarLongitude(uni, longitude); 14980 return Astro._localFromUniversal(sol, tz); 14981 }; 14982 14983 /** 14984 * @protected 14985 * @static 14986 * @param {number} jd julian day to calculate from 14987 * @returns {number} the major solar term for the julian day 14988 */ 14989 HanCal._majorSTOnOrAfter = function(jd) { 14990 var tz = HanCal._chineseTZ(jd); 14991 var uni = Astro._universalFromLocal(jd, tz); 14992 var next = Astro._fixangle(30 * Math.ceil(Astro._solarLongitude(uni)/30)); 14993 return HanCal._hanNextSolarLongitude(jd, next); 14994 }; 14995 14996 /** 14997 * @protected 14998 * @static 14999 * @param {number} year the year for which the leap year information is being sought 15000 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15001 * cycle is not given, then the year should be given as elapsed years since the beginning 15002 * of the epoch 15003 */ 15004 HanCal._solsticeBefore = function (year, cycle) { 15005 var elapsedYear = HanCal._getElapsedYear(year, cycle); 15006 var gregyear = elapsedYear - 2697; 15007 var rd = new GregRataDie({ 15008 year: gregyear-1, 15009 month: 12, 15010 day: 15, 15011 hour: 0, 15012 minute: 0, 15013 second: 0, 15014 millisecond: 0 15015 }); 15016 return HanCal._majorSTOnOrAfter(rd.getRataDie() + RataDie.gregorianEpoch); 15017 }; 15018 15019 /** 15020 * @protected 15021 * @static 15022 * @param {number} jd julian day to calculate from 15023 * @returns {number} the current major solar term 15024 */ 15025 HanCal._chineseTZ = function(jd) { 15026 var year = GregorianDate._calcYear(jd - RataDie.gregorianEpoch); 15027 return year < 1929 ? 465.6666666666666666 : 480; 15028 }; 15029 15030 /** 15031 * @protected 15032 * @static 15033 * @param {number} jd julian day to calculate from 15034 * @returns {number} the julian day of next new moon on or after the given julian day date 15035 */ 15036 HanCal._newMoonOnOrAfter = function(jd) { 15037 var tz = HanCal._chineseTZ(jd); 15038 var uni = Astro._universalFromLocal(jd, tz); 15039 var moon = Astro._newMoonAtOrAfter(uni); 15040 // floor to the start of the julian day 15041 return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); 15042 }; 15043 15044 /** 15045 * @protected 15046 * @static 15047 * @param {number} jd julian day to calculate from 15048 * @returns {number} the julian day of previous new moon before the given julian day date 15049 */ 15050 HanCal._newMoonBefore = function(jd) { 15051 var tz = HanCal._chineseTZ(jd); 15052 var uni = Astro._universalFromLocal(jd, tz); 15053 var moon = Astro._newMoonBefore(uni); 15054 // floor to the start of the julian day 15055 return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); 15056 }; 15057 15058 /** 15059 * @static 15060 * @protected 15061 * @param {number} year the year for which the leap year information is being sought 15062 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15063 * cycle is not given, then the year should be given as elapsed years since the beginning 15064 * of the epoch 15065 */ 15066 HanCal._leapYearCalc = function(year, cycle) { 15067 var ret = { 15068 elapsedYear: HanCal._getElapsedYear(year, cycle) 15069 }; 15070 ret.solstice1 = HanCal._solsticeBefore(ret.elapsedYear); 15071 ret.solstice2 = HanCal._solsticeBefore(ret.elapsedYear+1); 15072 // ceil to the end of the julian day 15073 ret.m1 = HanCal._newMoonOnOrAfter(Astro._ceilToJD(ret.solstice1)); 15074 ret.m2 = HanCal._newMoonBefore(Astro._ceilToJD(ret.solstice2)); 15075 15076 return ret; 15077 }; 15078 15079 /** 15080 * @protected 15081 * @static 15082 * @param {number} jd julian day to calculate from 15083 * @returns {number} the current major solar term 15084 */ 15085 HanCal._currentMajorST = function(jd) { 15086 var s = Astro._solarLongitude(Astro._universalFromLocal(jd, HanCal._chineseTZ(jd))); 15087 return MathUtils.amod(2 + Math.floor(s/30), 12); 15088 }; 15089 15090 /** 15091 * @protected 15092 * @static 15093 * @param {number} jd julian day to calculate from 15094 * @returns {boolean} true if there is no major solar term in the same year 15095 */ 15096 HanCal._noMajorST = function(jd) { 15097 return HanCal._currentMajorST(jd) === HanCal._currentMajorST(HanCal._newMoonOnOrAfter(jd+1)); 15098 }; 15099 15100 /** 15101 * Return the number of months in the given year. The number of months in a year varies 15102 * for some luni-solar calendars because in some years, an extra month is needed to extend the 15103 * days in a year to an entire solar year. The month is represented as a 1-based number 15104 * where 1=first month, 2=second month, etc. 15105 * 15106 * @param {number} year a year for which the number of months is sought 15107 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15108 * cycle is not given, then the year should be given as elapsed years since the beginning 15109 * of the epoch 15110 * @return {number} The number of months in the given year 15111 */ 15112 HanCal.prototype.getNumMonths = function(year, cycle) { 15113 return this.isLeapYear(year, cycle) ? 13 : 12; 15114 }; 15115 15116 /** 15117 * Return the number of days in a particular month in a particular year. This function 15118 * can return a different number for a month depending on the year because of things 15119 * like leap years. 15120 * 15121 * @param {number} month the elapsed month for which the length is sought 15122 * @param {number} year the elapsed year within which that month can be found 15123 * @return {number} the number of days within the given month in the given year 15124 */ 15125 HanCal.prototype.getMonLength = function(month, year) { 15126 // distance between two new moons in Nanjing China 15127 var calc = HanCal._leapYearCalc(year); 15128 var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + month * 29); 15129 var postNewMoon = HanCal._newMoonOnOrAfter(priorNewMoon + 1); 15130 return postNewMoon - priorNewMoon; 15131 }; 15132 15133 /** 15134 * Return the equivalent year in the 2820 year cycle that begins on 15135 * Far 1, 474. This particular cycle obeys the cycle-of-years formula 15136 * whereas the others do not specifically. This cycle can be used as 15137 * a proxy for other years outside of the cycle by shifting them into 15138 * the cycle. 15139 * @param {number} year year to find the equivalent cycle year for 15140 * @returns {number} the equivalent cycle year 15141 */ 15142 HanCal.prototype.equivalentCycleYear = function(year) { 15143 var y = year - (year >= 0 ? 474 : 473); 15144 return MathUtils.mod(y, 2820) + 474; 15145 }; 15146 15147 /** 15148 * Return true if the given year is a leap year in the Han calendar. 15149 * If the year is given as a year/cycle combination, then the year should be in the 15150 * range [1,60] and the given cycle is the cycle in which the year is located. If 15151 * the year is greater than 60, then 15152 * it represents the total number of years elapsed in the proleptic calendar since 15153 * the beginning of the Chinese epoch in on 15 Feb, -2636 (Gregorian). In this 15154 * case, the cycle parameter is ignored. 15155 * 15156 * @param {number} year the year for which the leap year information is being sought 15157 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15158 * cycle is not given, then the year should be given as elapsed years since the beginning 15159 * of the epoch 15160 * @return {boolean} true if the given year is a leap year 15161 */ 15162 HanCal.prototype.isLeapYear = function(year, cycle) { 15163 var calc = HanCal._leapYearCalc(year, cycle); 15164 return Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15165 }; 15166 15167 /** 15168 * Return the month of the year that is the leap month. If the given year is 15169 * not a leap year, then this method will return -1. 15170 * 15171 * @param {number} year the year for which the leap year information is being sought 15172 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15173 * cycle is not given, then the year should be given as elapsed years since the beginning 15174 * of the epoch 15175 * @return {number} the number of the month that is doubled in this leap year, or -1 15176 * if this is not a leap year 15177 */ 15178 HanCal.prototype.getLeapMonth = function(year, cycle) { 15179 var calc = HanCal._leapYearCalc(year, cycle); 15180 15181 if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) != 12) { 15182 return -1; // no leap month 15183 } 15184 15185 // search between rd1 and rd2 for the first month with no major solar term. That is our leap month. 15186 var month = 0; 15187 var m = HanCal._newMoonOnOrAfter(calc.m1+1); 15188 while (!HanCal._noMajorST(m)) { 15189 month++; 15190 m = HanCal._newMoonOnOrAfter(m+1); 15191 } 15192 15193 // return the number of the month that is doubled 15194 return month; 15195 }; 15196 15197 /** 15198 * Return the date of Chinese New Years in the given calendar year. 15199 * 15200 * @param {number} year the Chinese year for which the new year information is being sought 15201 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15202 * cycle is not given, then the year should be given as elapsed years since the beginning 15203 * of the epoch 15204 * @return {number} the julian day of the beginning of the given year 15205 */ 15206 HanCal.prototype.newYears = function(year, cycle) { 15207 var calc = HanCal._leapYearCalc(year, cycle); 15208 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15209 if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12 && 15210 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { 15211 return HanCal._newMoonOnOrAfter(m2+1); 15212 } 15213 return m2; 15214 }; 15215 15216 /** 15217 * Return the type of this calendar. 15218 * 15219 * @return {string} the name of the type of this calendar 15220 */ 15221 HanCal.prototype.getType = function() { 15222 return this.type; 15223 }; 15224 15225 /** 15226 * Return a date instance for this calendar type using the given 15227 * options. 15228 * @param {Object} options options controlling the construction of 15229 * the date instance 15230 * @return {HanDate} a date appropriate for this calendar type 15231 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 15232 */ 15233 HanCal.prototype.newDateInstance = function (options) { 15234 return new HanDate(options); 15235 }; 15236 15237 /* register this calendar for the factory method */ 15238 Calendar._constructors["han"] = HanCal; 15239 15240 15241 /*< HanRataDie.js */ 15242 /* 15243 * HanDate.js - Represent a date in the Han algorithmic calendar 15244 * 15245 * Copyright © 2014-2015, JEDLSoft 15246 * 15247 * Licensed under the Apache License, Version 2.0 (the "License"); 15248 * you may not use this file except in compliance with the License. 15249 * You may obtain a copy of the License at 15250 * 15251 * http://www.apache.org/licenses/LICENSE-2.0 15252 * 15253 * Unless required by applicable law or agreed to in writing, software 15254 * distributed under the License is distributed on an "AS IS" BASIS, 15255 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15256 * 15257 * See the License for the specific language governing permissions and 15258 * limitations under the License. 15259 */ 15260 15261 /* !depends 15262 ilib.js 15263 HanCal.js 15264 MathUtils.js 15265 RataDie.js 15266 */ 15267 15268 15269 /** 15270 * Construct a new Han RD date number object. The constructor parameters can 15271 * contain any of the following properties: 15272 * 15273 * <ul> 15274 * <li><i>unixtime<i> - sets the time of this instance according to the given 15275 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 15276 * 15277 * <li><i>julianday</i> - sets the time of this instance according to the given 15278 * Julian Day instance or the Julian Day given as a float 15279 * 15280 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 15281 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 15282 * linear count of years since the beginning of the epoch, much like other calendars. This linear 15283 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 15284 * to 60 and treated as if it were a year in the regular 60-year cycle. 15285 * 15286 * <li><i>year</i> - any integer, including 0 15287 * 15288 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 15289 * 15290 * <li><i>day</i> - 1 to 31 15291 * 15292 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15293 * is always done with an unambiguous 24 hour representation 15294 * 15295 * <li><i>minute</i> - 0 to 59 15296 * 15297 * <li><i>second</i> - 0 to 59 15298 * 15299 * <li><i>millisecond</i> - 0 to 999 15300 * 15301 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15302 * </ul> 15303 * 15304 * If the constructor is called with another Han date instance instead of 15305 * a parameter block, the other instance acts as a parameter block and its 15306 * settings are copied into the current instance.<p> 15307 * 15308 * If the constructor is called with no arguments at all or if none of the 15309 * properties listed above are present, then the RD is calculate based on 15310 * the current date at the time of instantiation. <p> 15311 * 15312 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 15313 * specified in the params, it is assumed that they have the smallest possible 15314 * value in the range for the property (zero or one).<p> 15315 * 15316 * 15317 * @private 15318 * @class 15319 * @constructor 15320 * @extends RataDie 15321 * @param {Object=} params parameters that govern the settings and behaviour of this Han RD date 15322 */ 15323 var HanRataDie = function(params) { 15324 this.rd = NaN; 15325 if (params && params.cal) { 15326 this.cal = params.cal; 15327 RataDie.call(this, params); 15328 if (params && typeof(params.callback) === 'function') { 15329 params.callback(this); 15330 } 15331 } else { 15332 new HanCal({ 15333 sync: params && params.sync, 15334 loadParams: params && params.loadParams, 15335 callback: ilib.bind(this, function(c) { 15336 this.cal = c; 15337 RataDie.call(this, params); 15338 if (params && typeof(params.callback) === 'function') { 15339 params.callback(this); 15340 } 15341 }) 15342 }); 15343 } 15344 }; 15345 15346 HanRataDie.prototype = new RataDie(); 15347 HanRataDie.prototype.parent = RataDie; 15348 HanRataDie.prototype.constructor = HanRataDie; 15349 15350 /** 15351 * The difference between a zero Julian day and the first Han date 15352 * which is February 15, -2636 (Gregorian). 15353 * @private 15354 * @type number 15355 */ 15356 HanRataDie.epoch = 758325.5; 15357 15358 /** 15359 * Calculate the Rata Die (fixed day) number of the given date from the 15360 * date components. 15361 * 15362 * @protected 15363 * @param {Object} date the date components to calculate the RD from 15364 */ 15365 HanRataDie.prototype._setDateComponents = function(date) { 15366 var calc = HanCal._leapYearCalc(date.year, date.cycle); 15367 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15368 var newYears; 15369 this.leapYear = (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12); 15370 if (this.leapYear && (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { 15371 newYears = HanCal._newMoonOnOrAfter(m2+1); 15372 } else { 15373 newYears = m2; 15374 } 15375 15376 var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + date.month * 29); // this is a julian day 15377 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(priorNewMoon)); 15378 this.leapMonth = (this.leapYear && HanCal._noMajorST(priorNewMoon) && !this.priorLeapMonth); 15379 15380 var rdtime = (date.hour * 3600000 + 15381 date.minute * 60000 + 15382 date.second * 1000 + 15383 date.millisecond) / 15384 86400000; 15385 15386 /* 15387 console.log("getRataDie: converting " + JSON.stringify(date) + " to an RD"); 15388 console.log("getRataDie: year is " + date.year + " plus cycle " + date.cycle); 15389 console.log("getRataDie: isLeapYear is " + this.leapYear); 15390 console.log("getRataDie: priorNewMoon is " + priorNewMoon); 15391 console.log("getRataDie: day in month is " + date.day); 15392 console.log("getRataDie: rdtime is " + rdtime); 15393 console.log("getRataDie: rd is " + (priorNewMoon + date.day - 1 + rdtime)); 15394 */ 15395 15396 this.rd = priorNewMoon + date.day - 1 + rdtime - RataDie.gregorianEpoch; 15397 }; 15398 15399 /** 15400 * Return the rd number of the particular day of the week on or before the 15401 * given rd. eg. The Sunday on or before the given rd. 15402 * @private 15403 * @param {number} rd the rata die date of the reference date 15404 * @param {number} dayOfWeek the day of the week that is being sought relative 15405 * to the current date 15406 * @return {number} the rd of the day of the week 15407 */ 15408 HanRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 15409 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); 15410 }; 15411 15412 /** 15413 * @protected 15414 * @static 15415 * @param {number} jd1 first julian day 15416 * @param {number} jd2 second julian day 15417 * @returns {boolean} true if there is a leap month earlier in the same year 15418 * as the given months 15419 */ 15420 HanRataDie._priorLeapMonth = function(jd1, jd2) { 15421 return jd2 >= jd1 && 15422 (HanRataDie._priorLeapMonth(jd1, HanCal._newMoonBefore(jd2)) || 15423 HanCal._noMajorST(jd2)); 15424 }; 15425 15426 15427 15428 /*< HanDate.js */ 15429 /* 15430 * HanDate.js - Represent a date in the Han algorithmic calendar 15431 * 15432 * Copyright © 2014-2015, JEDLSoft 15433 * 15434 * Licensed under the Apache License, Version 2.0 (the "License"); 15435 * you may not use this file except in compliance with the License. 15436 * You may obtain a copy of the License at 15437 * 15438 * http://www.apache.org/licenses/LICENSE-2.0 15439 * 15440 * Unless required by applicable law or agreed to in writing, software 15441 * distributed under the License is distributed on an "AS IS" BASIS, 15442 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15443 * 15444 * See the License for the specific language governing permissions and 15445 * limitations under the License. 15446 */ 15447 15448 /* !depends 15449 ilib.js 15450 IDate.js 15451 GregorianDate.js 15452 HanCal.js 15453 Astro.js 15454 JSUtils.js 15455 MathUtils.js 15456 LocaleInfo.js 15457 Locale.js 15458 TimeZone.js 15459 HanRataDie.js 15460 RataDie.js 15461 */ 15462 15463 15464 15465 15466 /** 15467 * @class 15468 * 15469 * Construct a new Han date object. The constructor parameters can 15470 * contain any of the following properties: 15471 * 15472 * <ul> 15473 * <li><i>unixtime<i> - sets the time of this instance according to the given 15474 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 15475 * 15476 * <li><i>julianday</i> - sets the time of this instance according to the given 15477 * Julian Day instance or the Julian Day given as a float 15478 * 15479 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 15480 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 15481 * linear count of years since the beginning of the epoch, much like other calendars. This linear 15482 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 15483 * to 60 and treated as if it were a year in the regular 60-year cycle. 15484 * 15485 * <li><i>year</i> - any integer, including 0 15486 * 15487 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 15488 * 15489 * <li><i>day</i> - 1 to 31 15490 * 15491 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15492 * is always done with an unambiguous 24 hour representation 15493 * 15494 * <li><i>minute</i> - 0 to 59 15495 * 15496 * <li><i>second</i> - 0 to 59 15497 * 15498 * <li><i>millisecond</i> - 0 to 999 15499 * 15500 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 15501 * of this han date. The date/time is kept in the local time. The time zone 15502 * is used later if this date is formatted according to a different time zone and 15503 * the difference has to be calculated, or when the date format has a time zone 15504 * component in it. 15505 * 15506 * <li><i>locale</i> - locale for this han date. If the time zone is not 15507 * given, it can be inferred from this locale. For locales that span multiple 15508 * time zones, the one with the largest population is chosen as the one that 15509 * represents the locale. 15510 * 15511 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15512 * </ul> 15513 * 15514 * If the constructor is called with another Han date instance instead of 15515 * a parameter block, the other instance acts as a parameter block and its 15516 * settings are copied into the current instance.<p> 15517 * 15518 * If the constructor is called with no arguments at all or if none of the 15519 * properties listed above 15520 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 15521 * components are 15522 * filled in with the current date at the time of instantiation. Note that if 15523 * you do not give the time zone when defaulting to the current time and the 15524 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 15525 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 15526 * Mean Time").<p> 15527 * 15528 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 15529 * specified in the params, it is assumed that they have the smallest possible 15530 * value in the range for the property (zero or one).<p> 15531 * 15532 * 15533 * @constructor 15534 * @extends Date 15535 * @param {Object=} params parameters that govern the settings and behaviour of this Han date 15536 */ 15537 var HanDate = function(params) { 15538 this.timezone = "local"; 15539 if (params) { 15540 if (params.locale) { 15541 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 15542 var li = new LocaleInfo(this.locale); 15543 this.timezone = li.getTimeZone(); 15544 } 15545 if (params.timezone) { 15546 this.timezone = params.timezone; 15547 } 15548 } 15549 15550 new HanCal({ 15551 sync: params && typeof(params) === 'boolean' ? params.sync : true, 15552 loadParams: params && params.loadParams, 15553 callback: ilib.bind(this, function (cal) { 15554 this.cal = cal; 15555 15556 if (params && (params.year || params.month || params.day || params.hour || 15557 params.minute || params.second || params.millisecond || params.cycle || params.cycleYear)) { 15558 if (typeof(params.cycle) !== 'undefined') { 15559 /** 15560 * Cycle number in the Han calendar. 15561 * @type number 15562 */ 15563 this.cycle = parseInt(params.cycle, 10) || 0; 15564 15565 var year = (typeof(params.year) !== 'undefined' ? parseInt(params.year, 10) : parseInt(params.cycleYear, 10)) || 0; 15566 15567 /** 15568 * Year in the Han calendar. 15569 * @type number 15570 */ 15571 this.year = HanCal._getElapsedYear(year, this.cycle); 15572 } else { 15573 if (typeof(params.year) !== 'undefined') { 15574 this.year = parseInt(params.year, 10) || 0; 15575 this.cycle = Math.floor((this.year - 1) / 60); 15576 } else { 15577 this.year = this.cycle = 0; 15578 } 15579 } 15580 15581 /** 15582 * The month number, ranging from 1 to 13 15583 * @type number 15584 */ 15585 this.month = parseInt(params.month, 10) || 1; 15586 15587 /** 15588 * The day of the month. This ranges from 1 to 30. 15589 * @type number 15590 */ 15591 this.day = parseInt(params.day, 10) || 1; 15592 15593 /** 15594 * The hour of the day. This can be a number from 0 to 23, as times are 15595 * stored unambiguously in the 24-hour clock. 15596 * @type number 15597 */ 15598 this.hour = parseInt(params.hour, 10) || 0; 15599 15600 /** 15601 * The minute of the hours. Ranges from 0 to 59. 15602 * @type number 15603 */ 15604 this.minute = parseInt(params.minute, 10) || 0; 15605 15606 /** 15607 * The second of the minute. Ranges from 0 to 59. 15608 * @type number 15609 */ 15610 this.second = parseInt(params.second, 10) || 0; 15611 15612 /** 15613 * The millisecond of the second. Ranges from 0 to 999. 15614 * @type number 15615 */ 15616 this.millisecond = parseInt(params.millisecond, 10) || 0; 15617 15618 // derived properties 15619 15620 /** 15621 * Year in the cycle of the Han calendar 15622 * @type number 15623 */ 15624 this.cycleYear = MathUtils.amod(this.year, 60); 15625 15626 /** 15627 * The day of the year. Ranges from 1 to 384. 15628 * @type number 15629 */ 15630 this.dayOfYear = parseInt(params.dayOfYear, 10); 15631 15632 if (typeof(params.dst) === 'boolean') { 15633 this.dst = params.dst; 15634 } 15635 15636 this.newRd({ 15637 cal: this.cal, 15638 cycle: this.cycle, 15639 year: this.year, 15640 month: this.month, 15641 day: this.day, 15642 hour: this.hour, 15643 minute: this.minute, 15644 second: this.second, 15645 millisecond: this.millisecond, 15646 sync: params && typeof(params.sync) === 'boolean' ? params.sync : true, 15647 loadParams: params && params.loadParams, 15648 callback: ilib.bind(this, function (rd) { 15649 if (rd) { 15650 this.rd = rd; 15651 15652 // add the time zone offset to the rd to convert to UTC 15653 if (!this.tz) { 15654 this.tz = new TimeZone({id: this.timezone}); 15655 } 15656 // getOffsetMillis requires that this.year, this.rd, and this.dst 15657 // are set in order to figure out which time zone rules apply and 15658 // what the offset is at that point in the year 15659 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 15660 if (this.offset !== 0) { 15661 this.rd = this.newRd({ 15662 cal: this.cal, 15663 rd: this.rd.getRataDie() - this.offset 15664 }); 15665 this._calcLeap(); 15666 } else { 15667 // re-use the derived properties from the RD calculations 15668 this.leapMonth = this.rd.leapMonth; 15669 this.priorLeapMonth = this.rd.priorLeapMonth; 15670 this.leapYear = this.rd.leapYear; 15671 } 15672 } 15673 15674 if (!this.rd) { 15675 this.rd = this.newRd(JSUtils.merge(params || {}, { 15676 cal: this.cal 15677 })); 15678 this._calcDateComponents(); 15679 } 15680 15681 if (params && typeof(params.onLoad) === 'function') { 15682 params.onLoad(this); 15683 } 15684 }) 15685 }); 15686 } else { 15687 if (!this.rd) { 15688 this.rd = this.newRd(JSUtils.merge(params || {}, { 15689 cal: this.cal 15690 })); 15691 this._calcDateComponents(); 15692 } 15693 15694 if (params && typeof(params.onLoad) === 'function') { 15695 params.onLoad(this); 15696 } 15697 } 15698 }) 15699 }); 15700 15701 }; 15702 15703 HanDate.prototype = new IDate({noinstance: true}); 15704 HanDate.prototype.parent = IDate; 15705 HanDate.prototype.constructor = HanDate; 15706 15707 /** 15708 * Return a new RD for this date type using the given params. 15709 * @protected 15710 * @param {Object=} params the parameters used to create this rata die instance 15711 * @returns {RataDie} the new RD instance for the given params 15712 */ 15713 HanDate.prototype.newRd = function (params) { 15714 return new HanRataDie(params); 15715 }; 15716 15717 /** 15718 * Return the year for the given RD 15719 * @protected 15720 * @param {number} rd RD to calculate from 15721 * @returns {number} the year for the RD 15722 */ 15723 HanDate.prototype._calcYear = function(rd) { 15724 var gregdate = new GregorianDate({ 15725 rd: rd, 15726 timezone: this.timezone 15727 }); 15728 var hanyear = gregdate.year + 2697; 15729 var newYears = this.cal.newYears(hanyear); 15730 return hanyear - ((rd + RataDie.gregorianEpoch < newYears) ? 1 : 0); 15731 }; 15732 15733 /** 15734 * @private 15735 * Calculate the leap year and months from the RD. 15736 */ 15737 HanDate.prototype._calcLeap = function() { 15738 var jd = this.rd.getRataDie() + RataDie.gregorianEpoch; 15739 15740 var calc = HanCal._leapYearCalc(this.year); 15741 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15742 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15743 15744 var newYears = (this.leapYear && 15745 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 15746 HanCal._newMoonOnOrAfter(m2+1) : m2; 15747 15748 var m = HanCal._newMoonBefore(jd + 1); 15749 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); 15750 this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); 15751 }; 15752 15753 /** 15754 * @private 15755 * Calculate date components for the given RD date. 15756 */ 15757 HanDate.prototype._calcDateComponents = function () { 15758 var remainder, 15759 jd = this.rd.getRataDie() + RataDie.gregorianEpoch; 15760 15761 // console.log("HanDate._calcDateComponents: calculating for jd " + jd); 15762 15763 if (typeof(this.offset) === "undefined") { 15764 // now offset the jd by the time zone, then recalculate in case we were 15765 // near the year boundary 15766 if (!this.tz) { 15767 this.tz = new TimeZone({id: this.timezone}); 15768 } 15769 this.offset = this.tz.getOffsetMillis(this) / 86400000; 15770 } 15771 15772 if (this.offset !== 0) { 15773 jd += this.offset; 15774 } 15775 15776 // use the Gregorian calendar objects as a convenient way to short-cut some 15777 // of the date calculations 15778 15779 var gregyear = GregorianDate._calcYear(this.rd.getRataDie()); 15780 this.year = gregyear + 2697; 15781 var calc = HanCal._leapYearCalc(this.year); 15782 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15783 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15784 var newYears = (this.leapYear && 15785 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 15786 HanCal._newMoonOnOrAfter(m2+1) : m2; 15787 15788 // See if it's between Jan 1 and the Chinese new years of that Gregorian year. If 15789 // so, then the Han year is actually the previous one 15790 if (jd < newYears) { 15791 this.year--; 15792 calc = HanCal._leapYearCalc(this.year); 15793 m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15794 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15795 newYears = (this.leapYear && 15796 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 15797 HanCal._newMoonOnOrAfter(m2+1) : m2; 15798 } 15799 // month is elapsed month, not the month number + leap month boolean 15800 var m = HanCal._newMoonBefore(jd + 1); 15801 this.month = Math.round((m - calc.m1) / 29.530588853000001); 15802 15803 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); 15804 this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); 15805 15806 this.cycle = Math.floor((this.year - 1) / 60); 15807 this.cycleYear = MathUtils.amod(this.year, 60); 15808 this.day = Astro._floorToJD(jd) - m + 1; 15809 15810 /* 15811 console.log("HanDate._calcDateComponents: year is " + this.year); 15812 console.log("HanDate._calcDateComponents: isLeapYear is " + this.leapYear); 15813 console.log("HanDate._calcDateComponents: cycle is " + this.cycle); 15814 console.log("HanDate._calcDateComponents: cycleYear is " + this.cycleYear); 15815 console.log("HanDate._calcDateComponents: month is " + this.month); 15816 console.log("HanDate._calcDateComponents: isLeapMonth is " + this.leapMonth); 15817 console.log("HanDate._calcDateComponents: day is " + this.day); 15818 */ 15819 15820 // floor to the start of the julian day 15821 remainder = jd - Astro._floorToJD(jd); 15822 15823 // console.log("HanDate._calcDateComponents: time remainder is " + remainder); 15824 15825 // now convert to milliseconds for the rest of the calculation 15826 remainder = Math.round(remainder * 86400000); 15827 15828 this.hour = Math.floor(remainder/3600000); 15829 remainder -= this.hour * 3600000; 15830 15831 this.minute = Math.floor(remainder/60000); 15832 remainder -= this.minute * 60000; 15833 15834 this.second = Math.floor(remainder/1000); 15835 remainder -= this.second * 1000; 15836 15837 this.millisecond = remainder; 15838 }; 15839 15840 /** 15841 * Return the year within the Chinese cycle of this date. Cycles are 60 15842 * years long, and the value returned from this method is the number of the year 15843 * within this cycle. The year returned from getYear() is the total elapsed 15844 * years since the beginning of the Chinese epoch and does not include 15845 * the cycles. 15846 * 15847 * @return {number} the year within the current Chinese cycle 15848 */ 15849 HanDate.prototype.getCycleYears = function() { 15850 return this.cycleYear; 15851 }; 15852 15853 /** 15854 * Return the Chinese cycle number of this date. Cycles are 60 years long, 15855 * and the value returned from getCycleYear() is the number of the year 15856 * within this cycle. The year returned from getYear() is the total elapsed 15857 * years since the beginning of the Chinese epoch and does not include 15858 * the cycles. 15859 * 15860 * @return {number} the current Chinese cycle 15861 */ 15862 HanDate.prototype.getCycles = function() { 15863 return this.cycle; 15864 }; 15865 15866 /** 15867 * Return whether the year of this date is a leap year in the Chinese Han 15868 * calendar. 15869 * 15870 * @return {boolean} true if the year of this date is a leap year in the 15871 * Chinese Han calendar. 15872 */ 15873 HanDate.prototype.isLeapYear = function() { 15874 return this.leapYear; 15875 }; 15876 15877 /** 15878 * Return whether the month of this date is a leap month in the Chinese Han 15879 * calendar. 15880 * 15881 * @return {boolean} true if the month of this date is a leap month in the 15882 * Chinese Han calendar. 15883 */ 15884 HanDate.prototype.isLeapMonth = function() { 15885 return this.leapMonth; 15886 }; 15887 15888 /** 15889 * Return the day of the week of this date. The day of the week is encoded 15890 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 15891 * 15892 * @return {number} the day of the week 15893 */ 15894 HanDate.prototype.getDayOfWeek = function() { 15895 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 15896 return MathUtils.mod(rd, 7); 15897 }; 15898 15899 /** 15900 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 15901 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 15902 * December 31st is 365 in regular years, or 366 in leap years. 15903 * @return {number} the ordinal day of the year 15904 */ 15905 HanDate.prototype.getDayOfYear = function() { 15906 var newYears = this.cal.newYears(this.year); 15907 var priorNewMoon = HanCal._newMoonOnOrAfter(newYears + (this.month -1) * 29); 15908 return priorNewMoon - newYears + this.day; 15909 }; 15910 15911 /** 15912 * Return the era for this date as a number. The value for the era for Han 15913 * calendars is -1 for "before the han era" (BP) and 1 for "the han era" (anno 15914 * persico or AP). 15915 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Han calendar, 15916 * there is a year 0, so any years that are negative or zero are BP. 15917 * @return {number} 1 if this date is in the common era, -1 if it is before the 15918 * common era 15919 */ 15920 HanDate.prototype.getEra = function() { 15921 return (this.year < 1) ? -1 : 1; 15922 }; 15923 15924 /** 15925 * Return the name of the calendar that governs this date. 15926 * 15927 * @return {string} a string giving the name of the calendar 15928 */ 15929 HanDate.prototype.getCalendar = function() { 15930 return "han"; 15931 }; 15932 15933 // register with the factory method 15934 IDate._constructors["han"] = HanDate; 15935 15936 15937 /*< EthiopicCal.js */ 15938 /* 15939 * ethiopic.js - Represent a Ethiopic calendar object. 15940 * 15941 * Copyright © 2015, JEDLSoft 15942 * 15943 * Licensed under the Apache License, Version 2.0 (the "License"); 15944 * you may not use this file except in compliance with the License. 15945 * You may obtain a copy of the License at 15946 * 15947 * http://www.apache.org/licenses/LICENSE-2.0 15948 * 15949 * Unless required by applicable law or agreed to in writing, software 15950 * distributed under the License is distributed on an "AS IS" BASIS, 15951 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15952 * 15953 * See the License for the specific language governing permissions and 15954 * limitations under the License. 15955 */ 15956 15957 /* !depends ilib.js Calendar.js Utils.js MathUtils.js */ 15958 15959 15960 15961 /** 15962 * @class 15963 * Construct a new Ethiopic calendar object. This class encodes information about 15964 * a Ethiopic calendar.<p> 15965 * 15966 * 15967 * @constructor 15968 * @extends Calendar 15969 */ 15970 var EthiopicCal = function() { 15971 this.type = "ethiopic"; 15972 }; 15973 15974 /** 15975 * Return the number of months in the given year. The number of months in a year varies 15976 * for lunar calendars because in some years, an extra month is needed to extend the 15977 * days in a year to an entire solar year. The month is represented as a 1-based number 15978 * where 1=Maskaram, 2=Teqemt, etc. until 13=Paguemen. 15979 * 15980 * @param {number} year a year for which the number of months is sought 15981 */ 15982 EthiopicCal.prototype.getNumMonths = function(year) { 15983 return 13; 15984 }; 15985 15986 /** 15987 * Return the number of days in a particular month in a particular year. This function 15988 * can return a different number for a month depending on the year because of things 15989 * like leap years. 15990 * 15991 * @param {number|string} month the month for which the length is sought 15992 * @param {number} year the year within which that month can be found 15993 * @return {number} the number of days within the given month in the given year 15994 */ 15995 EthiopicCal.prototype.getMonLength = function(month, year) { 15996 var m = month; 15997 switch (typeof(m)) { 15998 case "string": 15999 m = parseInt(m, 10); 16000 break; 16001 case "function": 16002 case "object": 16003 case "undefined": 16004 return 30; 16005 break; 16006 } 16007 if (m < 13) { 16008 return 30; 16009 } else { 16010 return this.isLeapYear(year) ? 6 : 5; 16011 } 16012 }; 16013 16014 /** 16015 * Return true if the given year is a leap year in the Ethiopic calendar. 16016 * The year parameter may be given as a number, or as a JulDate object. 16017 * @param {number|EthiopicDate|string} year the year for which the leap year information is being sought 16018 * @return {boolean} true if the given year is a leap year 16019 */ 16020 EthiopicCal.prototype.isLeapYear = function(year) { 16021 var y = year; 16022 switch (typeof(y)) { 16023 case "string": 16024 y = parseInt(y, 10); 16025 break; 16026 case "object": 16027 if (typeof(y.year) !== "number") { // in case it is an ilib.Date object 16028 return false; 16029 } 16030 y = y.year; 16031 break; 16032 case "function": 16033 case "undefined": 16034 return false; 16035 break; 16036 } 16037 return MathUtils.mod(y, 4) === 3; 16038 }; 16039 16040 /** 16041 * Return the type of this calendar. 16042 * 16043 * @return {string} the name of the type of this calendar 16044 */ 16045 EthiopicCal.prototype.getType = function() { 16046 return this.type; 16047 }; 16048 16049 /** 16050 * Return a date instance for this calendar type using the given 16051 * options. 16052 * @param {Object} options options controlling the construction of 16053 * the date instance 16054 * @return {IDate} a date appropriate for this calendar type 16055 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 16056 */ 16057 EthiopicCal.prototype.newDateInstance = function (options) { 16058 return new EthiopicDate(options); 16059 }; 16060 16061 /* register this calendar for the factory method */ 16062 Calendar._constructors["ethiopic"] = EthiopicCal; 16063 16064 16065 /*< EthiopicRataDie.js */ 16066 /* 16067 * EthiopicRataDie.js - Represent an RD date in the Ethiopic calendar 16068 * 16069 * Copyright © 2015, JEDLSoft 16070 * 16071 * Licensed under the Apache License, Version 2.0 (the "License"); 16072 * you may not use this file except in compliance with the License. 16073 * You may obtain a copy of the License at 16074 * 16075 * http://www.apache.org/licenses/LICENSE-2.0 16076 * 16077 * Unless required by applicable law or agreed to in writing, software 16078 * distributed under the License is distributed on an "AS IS" BASIS, 16079 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16080 * 16081 * See the License for the specific language governing permissions and 16082 * limitations under the License. 16083 */ 16084 16085 /* !depends 16086 ilib.js 16087 EthiopicCal.js 16088 RataDie.js 16089 */ 16090 16091 16092 /** 16093 * @class 16094 * Construct a new Ethiopic RD date number object. The constructor parameters can 16095 * contain any of the following properties: 16096 * 16097 * <ul> 16098 * <li><i>unixtime<i> - sets the time of this instance according to the given 16099 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 16100 * 16101 * <li><i>julianday</i> - sets the time of this instance according to the given 16102 * Julian Day instance or the Julian Day given as a float 16103 * 16104 * <li><i>year</i> - any integer, including 0 16105 * 16106 * <li><i>month</i> - 1 to 12, where 1 means Maskaram, 2 means Teqemt, etc., and 13 means Paguemen 16107 * 16108 * <li><i>day</i> - 1 to 30 16109 * 16110 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16111 * is always done with an unambiguous 24 hour representation 16112 * 16113 * <li><i>minute</i> - 0 to 59 16114 * 16115 * <li><i>second</i> - 0 to 59 16116 * 16117 * <li><i>millisecond</i> - 0 to 999 16118 * 16119 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16120 * </ul> 16121 * 16122 * If the constructor is called with another Ethiopic date instance instead of 16123 * a parameter block, the other instance acts as a parameter block and its 16124 * settings are copied into the current instance.<p> 16125 * 16126 * If the constructor is called with no arguments at all or if none of the 16127 * properties listed above are present, then the RD is calculate based on 16128 * the current date at the time of instantiation. <p> 16129 * 16130 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 16131 * specified in the params, it is assumed that they have the smallest possible 16132 * value in the range for the property (zero or one).<p> 16133 * 16134 * 16135 * @private 16136 * @constructor 16137 * @extends RataDie 16138 * @param {Object=} params parameters that govern the settings and behaviour of this Ethiopic RD date 16139 */ 16140 var EthiopicRataDie = function(params) { 16141 this.cal = params && params.cal || new EthiopicCal(); 16142 this.rd = NaN; 16143 RataDie.call(this, params); 16144 }; 16145 16146 EthiopicRataDie.prototype = new RataDie(); 16147 EthiopicRataDie.prototype.parent = RataDie; 16148 EthiopicRataDie.prototype.constructor = EthiopicRataDie; 16149 16150 /** 16151 * The difference between the zero Julian day and the first Ethiopic date 16152 * of Friday, August 29, 8 CE Julian at 6:00am UTC.<p> 16153 * 16154 * See <a href="http://us.wow.com/wiki/Time_in_Ethiopia?s_chn=90&s_pt=aolsem&v_t=aolsem" 16155 * Time in Ethiopia</a> for information about how time is handled in Ethiopia. 16156 * 16157 * @protected 16158 * @type number 16159 */ 16160 EthiopicRataDie.prototype.epoch = 1724219.75; 16161 16162 /** 16163 * Calculate the Rata Die (fixed day) number of the given date from the 16164 * date components. 16165 * 16166 * @protected 16167 * @param {Object} date the date components to calculate the RD from 16168 */ 16169 EthiopicRataDie.prototype._setDateComponents = function(date) { 16170 var year = date.year; 16171 var years = 365 * (year - 1) + Math.floor(year/4); 16172 var dayInYear = (date.month-1) * 30 + date.day; 16173 var rdtime = (date.hour * 3600000 + 16174 date.minute * 60000 + 16175 date.second * 1000 + 16176 date.millisecond) / 16177 86400000; 16178 16179 /* 16180 console.log("calcRataDie: converting " + JSON.stringify(parts)); 16181 console.log("getRataDie: year is " + years); 16182 console.log("getRataDie: day in year is " + dayInYear); 16183 console.log("getRataDie: rdtime is " + rdtime); 16184 console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); 16185 */ 16186 16187 this.rd = years + dayInYear + rdtime; 16188 }; 16189 16190 16191 16192 /*< EthiopicDate.js */ 16193 /* 16194 * EthiopicDate.js - Represent a date in the Ethiopic calendar 16195 * 16196 * Copyright © 2015, JEDLSoft 16197 * 16198 * Licensed under the Apache License, Version 2.0 (the "License"); 16199 * you may not use this file except in compliance with the License. 16200 * You may obtain a copy of the License at 16201 * 16202 * http://www.apache.org/licenses/LICENSE-2.0 16203 * 16204 * Unless required by applicable law or agreed to in writing, software 16205 * distributed under the License is distributed on an "AS IS" BASIS, 16206 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16207 * 16208 * See the License for the specific language governing permissions and 16209 * limitations under the License. 16210 */ 16211 16212 /* !depends 16213 ilib.js 16214 IDate.js 16215 EthiopicCal.js 16216 MathUtils.js 16217 Locale.js 16218 LocaleInfo.js 16219 TimeZone.js 16220 EthiopicRataDie.js 16221 */ 16222 16223 16224 16225 /** 16226 * @class 16227 * Construct a new date object for the Ethiopic Calendar. The constructor can be called 16228 * with a parameter object that contains any of the following properties: 16229 * 16230 * <ul> 16231 * <li><i>unixtime<i> - sets the time of this instance according to the given 16232 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 16233 * <li><i>julianday</i> - the Julian Day to set into this date 16234 * <li><i>year</i> - any integer 16235 * <li><i>month</i> - 1 to 13, where 1 means Maskaram, 2 means Teqemt, etc., and 13 means Paguemen 16236 * <li><i>day</i> - 1 to 30 16237 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16238 * is always done with an unambiguous 24 hour representation 16239 * <li><i>minute</i> - 0 to 59 16240 * <li><i>second</i> - 0 to 59 16241 * <li><i>millisecond<i> - 0 to 999 16242 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 16243 * of this ethiopic date. The date/time is kept in the local time. The time zone 16244 * is used later if this date is formatted according to a different time zone and 16245 * the difference has to be calculated, or when the date format has a time zone 16246 * component in it. 16247 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 16248 * given, it can be inferred from this locale. For locales that span multiple 16249 * time zones, the one with the largest population is chosen as the one that 16250 * represents the locale. 16251 * 16252 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16253 * </ul> 16254 * 16255 * If called with another Ethiopic date argument, the date components of the given 16256 * date are copied into the current one.<p> 16257 * 16258 * If the constructor is called with no arguments at all or if none of the 16259 * properties listed above 16260 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 16261 * components are 16262 * filled in with the current date at the time of instantiation. Note that if 16263 * you do not give the time zone when defaulting to the current time and the 16264 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 16265 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 16266 * Mean Time").<p> 16267 * 16268 * 16269 * @constructor 16270 * @extends IDate 16271 * @param {Object=} params parameters that govern the settings and behaviour of this Ethiopic date 16272 */ 16273 var EthiopicDate = function(params) { 16274 this.cal = new EthiopicCal(); 16275 16276 if (params) { 16277 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 16278 // for doing inheritance, so don't need to fill in the data. The inheriting class only wants the methods. 16279 return; 16280 } 16281 if (params.locale) { 16282 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 16283 var li = new LocaleInfo(this.locale); 16284 this.timezone = li.getTimeZone(); 16285 } 16286 if (params.timezone) { 16287 this.timezone = params.timezone; 16288 } 16289 16290 if (params.year || params.month || params.day || params.hour || 16291 params.minute || params.second || params.millisecond ) { 16292 /** 16293 * Year in the Ethiopic calendar. 16294 * @type number 16295 */ 16296 this.year = parseInt(params.year, 10) || 0; 16297 /** 16298 * The month number, ranging from 1 (Maskaram) to 13 (Paguemen). 16299 * @type number 16300 */ 16301 this.month = parseInt(params.month, 10) || 1; 16302 /** 16303 * The day of the month. This ranges from 1 to 30. 16304 * @type number 16305 */ 16306 this.day = parseInt(params.day, 10) || 1; 16307 /** 16308 * The hour of the day. This can be a number from 0 to 23, as times are 16309 * stored unambiguously in the 24-hour clock. 16310 * @type number 16311 */ 16312 this.hour = parseInt(params.hour, 10) || 0; 16313 /** 16314 * The minute of the hours. Ranges from 0 to 59. 16315 * @type number 16316 */ 16317 this.minute = parseInt(params.minute, 10) || 0; 16318 /** 16319 * The second of the minute. Ranges from 0 to 59. 16320 * @type number 16321 */ 16322 this.second = parseInt(params.second, 10) || 0; 16323 /** 16324 * The millisecond of the second. Ranges from 0 to 999. 16325 * @type number 16326 */ 16327 this.millisecond = parseInt(params.millisecond, 10) || 0; 16328 16329 /** 16330 * The day of the year. Ranges from 1 to 366. 16331 * @type number 16332 */ 16333 this.dayOfYear = parseInt(params.dayOfYear, 10); 16334 16335 if (typeof(params.dst) === 'boolean') { 16336 this.dst = params.dst; 16337 } 16338 16339 this.rd = this.newRd(this); 16340 16341 // add the time zone offset to the rd to convert to UTC 16342 if (!this.tz) { 16343 this.tz = new TimeZone({id: this.timezone}); 16344 } 16345 // getOffsetMillis requires that this.year, this.rd, and this.dst 16346 // are set in order to figure out which time zone rules apply and 16347 // what the offset is at that point in the year 16348 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 16349 if (this.offset !== 0) { 16350 this.rd = this.newRd({ 16351 rd: this.rd.getRataDie() - this.offset 16352 }); 16353 } 16354 } 16355 } 16356 16357 if (!this.rd) { 16358 this.rd = this.newRd(params); 16359 this._calcDateComponents(); 16360 } 16361 }; 16362 16363 EthiopicDate.prototype = new IDate({ noinstance: true }); 16364 EthiopicDate.prototype.parent = IDate; 16365 EthiopicDate.prototype.constructor = EthiopicDate; 16366 16367 /** 16368 * Return a new RD for this date type using the given params. 16369 * @protected 16370 * @param {Object=} params the parameters used to create this rata die instance 16371 * @returns {RataDie} the new RD instance for the given params 16372 */ 16373 EthiopicDate.prototype.newRd = function (params) { 16374 return new EthiopicRataDie(params); 16375 }; 16376 16377 /** 16378 * Return the year for the given RD 16379 * @protected 16380 * @param {number} rd RD to calculate from 16381 * @returns {number} the year for the RD 16382 */ 16383 EthiopicDate.prototype._calcYear = function(rd) { 16384 var year = Math.floor((4*(Math.floor(rd)-1) + 1463)/1461); 16385 16386 return year; 16387 }; 16388 16389 /** 16390 * Calculate date components for the given RD date. 16391 * @protected 16392 */ 16393 EthiopicDate.prototype._calcDateComponents = function () { 16394 var remainder, 16395 cumulative, 16396 rd = this.rd.getRataDie(); 16397 16398 this.year = this._calcYear(rd); 16399 16400 if (typeof(this.offset) === "undefined") { 16401 this.year = this._calcYear(rd); 16402 16403 // now offset the RD by the time zone, then recalculate in case we were 16404 // near the year boundary 16405 if (!this.tz) { 16406 this.tz = new TimeZone({id: this.timezone}); 16407 } 16408 this.offset = this.tz.getOffsetMillis(this) / 86400000; 16409 } 16410 16411 if (this.offset !== 0) { 16412 rd += this.offset; 16413 this.year = this._calcYear(rd); 16414 } 16415 16416 var jan1 = this.newRd({ 16417 year: this.year, 16418 month: 1, 16419 day: 1, 16420 hour: 0, 16421 minute: 0, 16422 second: 0, 16423 millisecond: 0 16424 }); 16425 remainder = rd + 1 - jan1.getRataDie(); 16426 16427 this.month = Math.floor((remainder-1)/30) + 1; 16428 remainder = remainder - (this.month-1) * 30; 16429 16430 this.day = Math.floor(remainder); 16431 remainder -= this.day; 16432 // now convert to milliseconds for the rest of the calculation 16433 remainder = Math.round(remainder * 86400000); 16434 16435 this.hour = Math.floor(remainder/3600000); 16436 remainder -= this.hour * 3600000; 16437 16438 this.minute = Math.floor(remainder/60000); 16439 remainder -= this.minute * 60000; 16440 16441 this.second = Math.floor(remainder/1000); 16442 remainder -= this.second * 1000; 16443 16444 this.millisecond = remainder; 16445 }; 16446 16447 /** 16448 * Return the day of the week of this date. The day of the week is encoded 16449 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 16450 * 16451 * @return {number} the day of the week 16452 */ 16453 EthiopicDate.prototype.getDayOfWeek = function() { 16454 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 16455 return MathUtils.mod(rd-4, 7); 16456 }; 16457 16458 /** 16459 * Return the name of the calendar that governs this date. 16460 * 16461 * @return {string} a string giving the name of the calendar 16462 */ 16463 EthiopicDate.prototype.getCalendar = function() { 16464 return "ethiopic"; 16465 }; 16466 16467 //register with the factory method 16468 IDate._constructors["ethiopic"] = EthiopicDate; 16469 16470 16471 16472 /*< CopticCal.js */ 16473 /* 16474 * coptic.js - Represent a Coptic calendar object. 16475 * 16476 * Copyright © 2015, JEDLSoft 16477 * 16478 * Licensed under the Apache License, Version 2.0 (the "License"); 16479 * you may not use this file except in compliance with the License. 16480 * You may obtain a copy of the License at 16481 * 16482 * http://www.apache.org/licenses/LICENSE-2.0 16483 * 16484 * Unless required by applicable law or agreed to in writing, software 16485 * distributed under the License is distributed on an "AS IS" BASIS, 16486 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16487 * 16488 * See the License for the specific language governing permissions and 16489 * limitations under the License. 16490 */ 16491 16492 16493 /* !depends ilib.js Calendar.js Locale.js Utils.js EthiopicCal.js */ 16494 16495 16496 /** 16497 * @class 16498 * Construct a new Coptic calendar object. This class encodes information about 16499 * a Coptic calendar.<p> 16500 * 16501 * 16502 * @constructor 16503 * @extends EthiopicCal 16504 */ 16505 var CopticCal = function() { 16506 this.type = "coptic"; 16507 }; 16508 16509 CopticCal.prototype = new EthiopicCal(); 16510 CopticCal.prototype.parent = EthiopicCal; 16511 CopticCal.prototype.constructor = CopticCal; 16512 16513 /** 16514 * Return a date instance for this calendar type using the given 16515 * options. 16516 * @param {Object} options options controlling the construction of 16517 * the date instance 16518 * @return {IDate} a date appropriate for this calendar type 16519 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 16520 */ 16521 CopticCal.prototype.newDateInstance = function (options) { 16522 return new CopticDate(options); 16523 }; 16524 16525 /* register this calendar for the factory method */ 16526 Calendar._constructors["coptic"] = CopticCal; 16527 16528 16529 /*< CopticRataDie.js */ 16530 /* 16531 * CopticRataDie.js - Represent an RD date in the Coptic calendar 16532 * 16533 * Copyright © 2015, JEDLSoft 16534 * 16535 * Licensed under the Apache License, Version 2.0 (the "License"); 16536 * you may not use this file except in compliance with the License. 16537 * You may obtain a copy of the License at 16538 * 16539 * http://www.apache.org/licenses/LICENSE-2.0 16540 * 16541 * Unless required by applicable law or agreed to in writing, software 16542 * distributed under the License is distributed on an "AS IS" BASIS, 16543 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16544 * 16545 * See the License for the specific language governing permissions and 16546 * limitations under the License. 16547 */ 16548 16549 /* !depends 16550 ilib.js 16551 CopticCal.js 16552 JSUtils.js 16553 EthiopicRataDie.js 16554 */ 16555 16556 16557 /** 16558 * @class 16559 * Construct a new Coptic RD date number object. The constructor parameters can 16560 * contain any of the following properties: 16561 * 16562 * <ul> 16563 * <li><i>unixtime<i> - sets the time of this instance according to the given 16564 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 16565 * 16566 * <li><i>julianday</i> - sets the time of this instance according to the given 16567 * Julian Day instance or the Julian Day given as a float 16568 * 16569 * <li><i>year</i> - any integer, including 0 16570 * 16571 * <li><i>month</i> - 1 to 13, where 1 means Thoout, 2 means Paope, etc., and 13 means Epagomene 16572 * 16573 * <li><i>day</i> - 1 to 30 16574 * 16575 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16576 * is always done with an unambiguous 24 hour representation 16577 * 16578 * <li><i>minute</i> - 0 to 59 16579 * 16580 * <li><i>second</i> - 0 to 59 16581 * 16582 * <li><i>millisecond</i> - 0 to 999 16583 * 16584 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16585 * </ul> 16586 * 16587 * If the constructor is called with another Coptic date instance instead of 16588 * a parameter block, the other instance acts as a parameter block and its 16589 * settings are copied into the current instance.<p> 16590 * 16591 * If the constructor is called with no arguments at all or if none of the 16592 * properties listed above are present, then the RD is calculate based on 16593 * the current date at the time of instantiation. <p> 16594 * 16595 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 16596 * specified in the params, it is assumed that they have the smallest possible 16597 * value in the range for the property (zero or one).<p> 16598 * 16599 * 16600 * @private 16601 * @constructor 16602 * @extends EthiopicRataDie 16603 * @param {Object=} params parameters that govern the settings and behaviour of this Coptic RD date 16604 */ 16605 var CopticRataDie = function(params) { 16606 this.cal = params && params.cal || new CopticCal(); 16607 this.rd = NaN; 16608 /** 16609 * The difference between the zero Julian day and the first Coptic date 16610 * of Friday, August 29, 284 CE Julian at 7:00am UTC. 16611 * @private 16612 * @type number 16613 */ 16614 this.epoch = 1825028.5; 16615 16616 var tmp = {}; 16617 if (params) { 16618 JSUtils.shallowCopy(params, tmp); 16619 } 16620 tmp.cal = this.cal; // override the cal parameter that may be passed in 16621 EthiopicRataDie.call(this, tmp); 16622 }; 16623 16624 CopticRataDie.prototype = new EthiopicRataDie(); 16625 CopticRataDie.prototype.parent = EthiopicRataDie; 16626 CopticRataDie.prototype.constructor = CopticRataDie; 16627 16628 16629 /*< CopticDate.js */ 16630 /* 16631 * CopticDate.js - Represent a date in the Coptic calendar 16632 * 16633 * Copyright © 2015, JEDLSoft 16634 * 16635 * Licensed under the Apache License, Version 2.0 (the "License"); 16636 * you may not use this file except in compliance with the License. 16637 * You may obtain a copy of the License at 16638 * 16639 * http://www.apache.org/licenses/LICENSE-2.0 16640 * 16641 * Unless required by applicable law or agreed to in writing, software 16642 * distributed under the License is distributed on an "AS IS" BASIS, 16643 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16644 * 16645 * See the License for the specific language governing permissions and 16646 * limitations under the License. 16647 */ 16648 16649 /* !depends 16650 ilib.js 16651 IDate.js 16652 CopticCal.js 16653 MathUtils.js 16654 JSUtils.js 16655 Locale.js 16656 LocaleInfo.js 16657 TimeZone.js 16658 EthiopicDate.js 16659 CopticRataDie.js 16660 */ 16661 16662 16663 16664 16665 /** 16666 * @class 16667 * Construct a new date object for the Coptic Calendar. The constructor can be called 16668 * with a parameter object that contains any of the following properties: 16669 * 16670 * <ul> 16671 * <li><i>unixtime<i> - sets the time of this instance according to the given 16672 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 16673 * <li><i>julianday</i> - the Julian Day to set into this date 16674 * <li><i>year</i> - any integer 16675 * <li><i>month</i> - 1 to 13, where 1 means Thoout, 2 means Paope, etc., and 13 means Epagomene 16676 * <li><i>day</i> - 1 to 30 16677 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16678 * is always done with an unambiguous 24 hour representation 16679 * <li><i>minute</i> - 0 to 59 16680 * <li><i>second</i> - 0 to 59 16681 * <li><i>millisecond<i> - 0 to 999 16682 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 16683 * of this coptic date. The date/time is kept in the local time. The time zone 16684 * is used later if this date is formatted according to a different time zone and 16685 * the difference has to be calculated, or when the date format has a time zone 16686 * component in it. 16687 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 16688 * given, it can be inferred from this locale. For locales that span multiple 16689 * time zones, the one with the largest population is chosen as the one that 16690 * represents the locale. 16691 * 16692 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16693 * </ul> 16694 * 16695 * If called with another Coptic date argument, the date components of the given 16696 * date are copied into the current one.<p> 16697 * 16698 * If the constructor is called with no arguments at all or if none of the 16699 * properties listed above 16700 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 16701 * components are 16702 * filled in with the current date at the time of instantiation. Note that if 16703 * you do not give the time zone when defaulting to the current time and the 16704 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 16705 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 16706 * Mean Time").<p> 16707 * 16708 * 16709 * @constructor 16710 * @extends EthiopicDate 16711 * @param {Object=} params parameters that govern the settings and behaviour of this Coptic date 16712 */ 16713 var CopticDate = function(params) { 16714 this.rd = NaN; // clear these out so that the EthiopicDate constructor can set it 16715 EthiopicDate.call(this, params); 16716 this.cal = new CopticCal(); 16717 }; 16718 16719 CopticDate.prototype = new EthiopicDate({noinstance: true}); 16720 CopticDate.prototype.parent = EthiopicDate.prototype; 16721 CopticDate.prototype.constructor = CopticDate; 16722 16723 /** 16724 * Return a new RD for this date type using the given params. 16725 * @protected 16726 * @param {Object=} params the parameters used to create this rata die instance 16727 * @returns {RataDie} the new RD instance for the given params 16728 */ 16729 CopticDate.prototype.newRd = function (params) { 16730 return new CopticRataDie(params); 16731 }; 16732 16733 /** 16734 * Return the day of the week of this date. The day of the week is encoded 16735 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 16736 * 16737 * @return {number} the day of the week 16738 */ 16739 CopticDate.prototype.getDayOfWeek = function() { 16740 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 16741 return MathUtils.mod(rd-3, 7); 16742 }; 16743 16744 /** 16745 * Return the name of the calendar that governs this date. 16746 * 16747 * @return {string} a string giving the name of the calendar 16748 */ 16749 CopticDate.prototype.getCalendar = function() { 16750 return "coptic"; 16751 }; 16752 16753 //register with the factory method 16754 IDate._constructors["coptic"] = CopticDate; 16755 16756 16757 /*< CType.js */ 16758 /* 16759 * CType.js - Character type definitions 16760 * 16761 * Copyright © 2012-2015, JEDLSoft 16762 * 16763 * Licensed under the Apache License, Version 2.0 (the "License"); 16764 * you may not use this file except in compliance with the License. 16765 * You may obtain a copy of the License at 16766 * 16767 * http://www.apache.org/licenses/LICENSE-2.0 16768 * 16769 * Unless required by applicable law or agreed to in writing, software 16770 * distributed under the License is distributed on an "AS IS" BASIS, 16771 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16772 * 16773 * See the License for the specific language governing permissions and 16774 * limitations under the License. 16775 */ 16776 16777 // !depends ilib.js Locale.js SearchUtils.js Utils.js IString.js 16778 16779 // !data ctype 16780 16781 16782 /** 16783 * Provides a set of static routines that return information about characters. 16784 * These routines emulate the C-library ctype functions. The characters must be 16785 * encoded in utf-16, as no other charsets are currently supported. Only the first 16786 * character of the given string is tested. 16787 * @namespace 16788 */ 16789 var CType = {}; 16790 16791 16792 /** 16793 * Actual implementation for withinRange. Searches the given object for ranges. 16794 * The range names are taken from the Unicode range names in 16795 * http://www.unicode.org/Public/UNIDATA/extracted/DerivedGeneralCategory.txt 16796 * 16797 * <ul> 16798 * <li>Cn - Unassigned 16799 * <li>Lu - Uppercase_Letter 16800 * <li>Ll - Lowercase_Letter 16801 * <li>Lt - Titlecase_Letter 16802 * <li>Lm - Modifier_Letter 16803 * <li>Lo - Other_Letter 16804 * <li>Mn - Nonspacing_Mark 16805 * <li>Me - Enclosing_Mark 16806 * <li>Mc - Spacing_Mark 16807 * <li>Nd - Decimal_Number 16808 * <li>Nl - Letter_Number 16809 * <li>No - Other_Number 16810 * <li>Zs - Space_Separator 16811 * <li>Zl - Line_Separator 16812 * <li>Zp - Paragraph_Separator 16813 * <li>Cc - Control 16814 * <li>Cf - Format 16815 * <li>Co - Private_Use 16816 * <li>Cs - Surrogate 16817 * <li>Pd - Dash_Punctuation 16818 * <li>Ps - Open_Punctuation 16819 * <li>Pe - Close_Punctuation 16820 * <li>Pc - Connector_Punctuation 16821 * <li>Po - Other_Punctuation 16822 * <li>Sm - Math_Symbol 16823 * <li>Sc - Currency_Symbol 16824 * <li>Sk - Modifier_Symbol 16825 * <li>So - Other_Symbol 16826 * <li>Pi - Initial_Punctuation 16827 * <li>Pf - Final_Punctuation 16828 * </ul> 16829 * 16830 * @protected 16831 * @param {number} num code point of the character to examine 16832 * @param {string} rangeName the name of the range to check 16833 * @param {Object} obj object containing the character range data 16834 * @return {boolean} true if the first character is within the named 16835 * range 16836 */ 16837 CType._inRange = function(num, rangeName, obj) { 16838 var range, i; 16839 if (num < 0 || !rangeName || !obj) { 16840 return false; 16841 } 16842 16843 range = obj[rangeName]; 16844 if (!range) { 16845 return false; 16846 } 16847 16848 var compare = function(singlerange, target) { 16849 if (singlerange.length === 1) { 16850 return singlerange[0] - target; 16851 } else { 16852 return target < singlerange[0] ? singlerange[0] - target : 16853 (target > singlerange[1] ? singlerange[1] - target : 0); 16854 } 16855 }; 16856 var result = SearchUtils.bsearch(num, range, compare); 16857 return result < range.length && compare(range[result], num) === 0; 16858 }; 16859 16860 /** 16861 * Return whether or not the first character is within the named range 16862 * of Unicode characters. The valid list of range names are taken from 16863 * the Unicode 6.0 spec. Characters in all ranges of Unicode are supported, 16864 * including those supported in Javascript via UTF-16. Currently, this method 16865 * supports the following range names: 16866 * 16867 * <ul> 16868 * <li><i>ascii</i> - basic ASCII 16869 * <li><i>latin</i> - Latin, Latin Extended Additional, Latin-1 supplement, Latin Extended-C, Latin Extended-D, Latin Extended-E 16870 * <li><i>armenian</i> 16871 * <li><i>greek</i> - Greek, Greek Extended 16872 * <li><i>cyrillic</i> - Cyrillic, Cyrillic Extended-A, Cyrillic Extended-B, Cyrillic Supplement 16873 * <li><i>georgian</i> - Georgian, Georgian Supplement 16874 * <li><i>glagolitic</i> 16875 * <li><i>gothic</i> 16876 * <li><i>ogham</i> 16877 * <li><i>oldpersian</i> 16878 * <li><i>runic</i> 16879 * <li><i>ipa</i> - IPA, Phonetic Extensions, Phonetic Extensions Supplement 16880 * <li><i>phonetic</i> 16881 * <li><i>modifiertone</i> - Modifier Tone Letters 16882 * <li><i>spacing</i> 16883 * <li><i>diacritics</i> 16884 * <li><i>halfmarks</i> - Combining Half Marks 16885 * <li><i>small</i> - Small Form Variants 16886 * <li><i>bamum</i> - Bamum, Bamum Supplement 16887 * <li><i>ethiopic</i> - Ethiopic, Ethiopic Extended, Ethiopic Extended-A 16888 * <li><i>nko</i> 16889 * <li><i>osmanya</i> 16890 * <li><i>tifinagh</i> 16891 * <li><i>val</i> 16892 * <li><i>arabic</i> - Arabic, Arabic Supplement, Arabic Presentation Forms-A, 16893 * Arabic Presentation Forms-B, Arabic Mathematical Alphabetic Symbols 16894 * <li><i>carlan</i> 16895 * <li><i>hebrew</i> 16896 * <li><i>mandaic</i> 16897 * <li><i>samaritan</i> 16898 * <li><i>syriac</i> 16899 * <li><i>mongolian</i> 16900 * <li><i>phagspa</i> 16901 * <li><i>tibetan</i> 16902 * <li><i>bengali</i> 16903 * <li><i>devanagari</i> - Devanagari, Devanagari Extended 16904 * <li><i>gujarati</i> 16905 * <li><i>gurmukhi</i> 16906 * <li><i>kannada</i> 16907 * <li><i>lepcha</i> 16908 * <li><i>limbu</i> 16909 * <li><i>malayalam</i> 16910 * <li><i>meetaimayek</i> 16911 * <li><i>olchiki</i> 16912 * <li><i>oriya</i> 16913 * <li><i>saurashtra</i> 16914 * <li><i>sinhala</i> 16915 * <li><i>sylotinagri</i> - Syloti Nagri 16916 * <li><i>tamil</i> 16917 * <li><i>telugu</i> 16918 * <li><i>thaana</i> 16919 * <li><i>vedic</i> 16920 * <li><i>batak</i> 16921 * <li><i>balinese</i> 16922 * <li><i>buginese</i> 16923 * <li><i>cham</i> 16924 * <li><i>javanese</i> 16925 * <li><i>kayahli</i> 16926 * <li><i>khmer</i> 16927 * <li><i>lao</i> 16928 * <li><i>myanmar</i> - Myanmar, Myanmar Extended-A, Myanmar Extended-B 16929 * <li><i>newtailue</i> 16930 * <li><i>rejang</i> 16931 * <li><i>sundanese</i> - Sundanese, Sundanese Supplement 16932 * <li><i>taile</i> 16933 * <li><i>taitham</i> 16934 * <li><i>taiviet</i> 16935 * <li><i>thai</i> 16936 * <li><i>buhld</i> 16937 * <li><i>hanunoo</i> 16938 * <li><i>tagalog</i> 16939 * <li><i>tagbanwa</i> 16940 * <li><i>bopomofo</i> - Bopomofo, Bopomofo Extended 16941 * <li><i>cjk</i> - the CJK unified ideographs (Han), CJK Unified Ideographs 16942 * Extension A, CJK Unified Ideographs Extension B, CJK Unified Ideographs 16943 * Extension C, CJK Unified Ideographs Extension D, Ideographic Description 16944 * Characters (=isIdeo()) 16945 * <li><i>cjkcompatibility</i> - CJK Compatibility, CJK Compatibility 16946 * Ideographs, CJK Compatibility Forms, CJK Compatibility Ideographs Supplement 16947 * <li><i>cjkradicals</i> - the CJK radicals, KangXi radicals 16948 * <li><i>hangul</i> - Hangul Jamo, Hangul Syllables, Hangul Jamo Extended-A, 16949 * Hangul Jamo Extended-B, Hangul Compatibility Jamo 16950 * <li><i>cjkpunct</i> - CJK symbols and punctuation 16951 * <li><i>cjkstrokes</i> - CJK strokes 16952 * <li><i>hiragana</i> 16953 * <li><i>katakana</i> - Katakana, Katakana Phonetic Extensions, Kana Supplement 16954 * <li><i>kanbun</i> 16955 * <li><i>lisu</i> 16956 * <li><i>yi</i> - Yi Syllables, Yi Radicals 16957 * <li><i>cherokee</i> 16958 * <li><i>canadian</i> - Unified Canadian Aboriginal Syllabics, Unified Canadian 16959 * Aboriginal Syllabics Extended 16960 * <li><i>presentation</i> - Alphabetic presentation forms 16961 * <li><i>vertical</i> - Vertical Forms 16962 * <li><i>width</i> - Halfwidth and Fullwidth Forms 16963 * <li><i>punctuation</i> - General punctuation, Supplemental Punctuation 16964 * <li><i>box</i> - Box Drawing 16965 * <li><i>block</i> - Block Elements 16966 * <li><i>letterlike</i> - Letterlike symbols 16967 * <li><i>mathematical</i> - Mathematical alphanumeric symbols, Miscellaneous 16968 * Mathematical Symbols-A, Miscellaneous Mathematical Symbols-B 16969 * <li><i>enclosedalpha</i> - Enclosed alphanumerics, Enclosed Alphanumeric Supplement 16970 * <li><i>enclosedcjk</i> - Enclosed CJK letters and months, Enclosed Ideographic Supplement 16971 * <li><i>cjkcompatibility</i> - CJK compatibility 16972 * <li><i>apl</i> - APL symbols 16973 * <li><i>controlpictures</i> - Control pictures 16974 * <li><i>misc</i> - Miscellaneous technical 16975 * <li><i>ocr</i> - Optical character recognition (OCR) 16976 * <li><i>combining</i> - Combining Diacritical Marks, Combining Diacritical Marks 16977 * for Symbols, Combining Diacritical Marks Supplement, Combining Diacritical Marks Extended 16978 * <li><i>digits</i> - ASCII digits (=isDigit()) 16979 * <li><i>indicnumber</i> - Common Indic Number Forms 16980 * <li><i>numbers</i> - Number forms 16981 * <li><i>supersub</i> - Superscripts and Subscripts 16982 * <li><i>arrows</i> - Arrows, Miscellaneous Symbols and Arrows, Supplemental Arrows-A, 16983 * Supplemental Arrows-B, Supplemental Arrows-C 16984 * <li><i>operators</i> - Mathematical operators, supplemental 16985 * mathematical operators 16986 * <li><i>geometric</i> - Geometric shapes, Geometric shapes extended 16987 * <li><i>ancient</i> - Ancient symbols 16988 * <li><i>braille</i> - Braille patterns 16989 * <li><i>currency</i> - Currency symbols 16990 * <li><i>dingbats</i> 16991 * <li><i>gamesymbols</i> 16992 * <li><i>yijing</i> - Yijing Hexagram Symbols 16993 * <li><i>specials</i> 16994 * <li><i>variations</i> - Variation Selectors, Variation Selectors Supplement 16995 * <li><i>privateuse</i> - Private Use Area, Supplementary Private Use Area-A, 16996 * Supplementary Private Use Area-B 16997 * <li><i>supplementarya</i> - Supplementary private use area-A 16998 * <li><i>supplementaryb</i> - Supplementary private use area-B 16999 * <li><i>highsurrogates</i> - High Surrogates, High Private Use Surrogates 17000 * <li><i>lowsurrogates</i> 17001 * <li><i>reserved</i> 17002 * <li><i>noncharacters</i> 17003 * <li><i>copticnumber</i> - coptic epact numbers 17004 * <li><i>oldpermic</i> - old permic 17005 * <li><i>albanian</i> - albanian 17006 * <li><i>lineara</i> - linear a 17007 * <li><i>meroitic</i> - meroitic cursive 17008 * <li><i>oldnortharabian</i> - old north arabian 17009 * <li><i>oldhungarian</i> - Supplementary private use area-A 17010 * <li><i>sorasompeng</i> - sora sompeng 17011 * <li><i>warangciti</i> - warang citi 17012 * <li><i>paucinhau</i> - pau cin hau 17013 * <li><i>bassavah</i> - bassa vah 17014 * <li><i>pahawhhmong</i> - pahawh hmong 17015 * <li><i>shorthandformat</i> - shorthand format controls 17016 * <li><i>suttonsignwriting</i> - sutton signwriting 17017 * <li><i>pictographs</i> - miscellaneous symbols and pictographs, supplemental symbols and pictographs 17018 * <li><i>ornamentaldingbats</i> - ornamental dingbats 17019 * </ul><p> 17020 * 17021 * 17022 * @protected 17023 * @param {string|IString|number} ch character or code point to examine 17024 * @param {string} rangeName the name of the range to check 17025 * @return {boolean} true if the first character is within the named 17026 * range 17027 */ 17028 CType.withinRange = function(ch, rangeName) { 17029 if (!rangeName) { 17030 return false; 17031 } 17032 var num; 17033 switch (typeof(ch)) { 17034 case 'number': 17035 num = ch; 17036 break; 17037 case 'string': 17038 num = IString.toCodePoint(ch, 0); 17039 break; 17040 case 'undefined': 17041 return false; 17042 default: 17043 num = ch._toCodePoint(0); 17044 break; 17045 } 17046 17047 return CType._inRange(num, rangeName.toLowerCase(), ilib.data.ctype); 17048 }; 17049 17050 /** 17051 * @protected 17052 * @param {boolean} sync 17053 * @param {Object|undefined} loadParams 17054 * @param {function(*)|undefined} onLoad 17055 */ 17056 CType._init = function(sync, loadParams, onLoad) { 17057 CType._load("ctype", sync, loadParams, onLoad); 17058 }; 17059 17060 /** 17061 * @protected 17062 * @param {string} name 17063 * @param {boolean} sync 17064 * @param {Object|undefined} loadParams 17065 * @param {function(*)|undefined} onLoad 17066 */ 17067 CType._load = function (name, sync, loadParams, onLoad) { 17068 if (!ilib.data[name]) { 17069 var loadName = name ? name + ".json" : "CType.json"; 17070 Utils.loadData({ 17071 name: loadName, 17072 locale: "-", 17073 nonlocale: true, 17074 sync: sync, 17075 loadParams: loadParams, 17076 callback: ilib.bind(this, function(ct) { 17077 ilib.data[name] = ct; 17078 if (onLoad && typeof(onLoad) === 'function') { 17079 onLoad(ilib.data[name]); 17080 } 17081 }) 17082 }); 17083 } else { 17084 if (onLoad && typeof(onLoad) === 'function') { 17085 onLoad(ilib.data[name]); 17086 } 17087 } 17088 }; 17089 17090 17091 17092 /*< isDigit.js */ 17093 /* 17094 * isDigit.js - Character type is digit 17095 * 17096 * Copyright © 2012-2015, JEDLSoft 17097 * 17098 * Licensed under the Apache License, Version 2.0 (the "License"); 17099 * you may not use this file except in compliance with the License. 17100 * You may obtain a copy of the License at 17101 * 17102 * http://www.apache.org/licenses/LICENSE-2.0 17103 * 17104 * Unless required by applicable law or agreed to in writing, software 17105 * distributed under the License is distributed on an "AS IS" BASIS, 17106 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17107 * 17108 * See the License for the specific language governing permissions and 17109 * limitations under the License. 17110 */ 17111 17112 // !depends CType.js IString.js ilib.js 17113 17114 // !data ctype 17115 17116 17117 /** 17118 * Return whether or not the first character is a digit character in the 17119 * Latin script.<p> 17120 * 17121 * @static 17122 * @param {string|IString|number} ch character or code point to examine 17123 * @return {boolean} true if the first character is a digit character in the 17124 * Latin script. 17125 */ 17126 var isDigit = function (ch) { 17127 var num; 17128 switch (typeof(ch)) { 17129 case 'number': 17130 num = ch; 17131 break; 17132 case 'string': 17133 num = IString.toCodePoint(ch, 0); 17134 break; 17135 case 'undefined': 17136 return false; 17137 default: 17138 num = ch._toCodePoint(0); 17139 break; 17140 } 17141 return CType._inRange(num, 'digit', ilib.data.ctype); 17142 }; 17143 17144 /** 17145 * @protected 17146 * @param {boolean} sync 17147 * @param {Object|undefined} loadParams 17148 * @param {function(*)|undefined} onLoad 17149 */ 17150 isDigit._init = function (sync, loadParams, onLoad) { 17151 CType._init(sync, loadParams, onLoad); 17152 }; 17153 17154 17155 17156 /*< isSpace.js */ 17157 /* 17158 * isSpace.js - Character type is space char 17159 * 17160 * Copyright © 2012-2015, JEDLSoft 17161 * 17162 * Licensed under the Apache License, Version 2.0 (the "License"); 17163 * you may not use this file except in compliance with the License. 17164 * You may obtain a copy of the License at 17165 * 17166 * http://www.apache.org/licenses/LICENSE-2.0 17167 * 17168 * Unless required by applicable law or agreed to in writing, software 17169 * distributed under the License is distributed on an "AS IS" BASIS, 17170 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17171 * 17172 * See the License for the specific language governing permissions and 17173 * limitations under the License. 17174 */ 17175 17176 // !depends CType.js IString.js 17177 17178 // !data ctype ctype_z 17179 17180 17181 17182 /** 17183 * Return whether or not the first character is a whitespace character.<p> 17184 * 17185 * @static 17186 * @param {string|IString|number} ch character or code point to examine 17187 * @return {boolean} true if the first character is a whitespace character. 17188 */ 17189 var isSpace = function (ch) { 17190 var num; 17191 switch (typeof(ch)) { 17192 case 'number': 17193 num = ch; 17194 break; 17195 case 'string': 17196 num = IString.toCodePoint(ch, 0); 17197 break; 17198 case 'undefined': 17199 return false; 17200 default: 17201 num = ch._toCodePoint(0); 17202 break; 17203 } 17204 17205 return CType._inRange(num, 'space', ilib.data.ctype) || 17206 CType._inRange(num, 'Zs', ilib.data.ctype_z) || 17207 CType._inRange(num, 'Zl', ilib.data.ctype_z) || 17208 CType._inRange(num, 'Zp', ilib.data.ctype_z); 17209 }; 17210 17211 /** 17212 * @protected 17213 * @param {boolean} sync 17214 * @param {Object|undefined} loadParams 17215 * @param {function(*)|undefined} onLoad 17216 */ 17217 isSpace._init = function (sync, loadParams, onLoad) { 17218 CType._load("ctype_z", sync, loadParams, function () { 17219 CType._init(sync, loadParams, onLoad); 17220 }); 17221 }; 17222 17223 17224 /*< Currency.js */ 17225 /* 17226 * Currency.js - Currency definition 17227 * 17228 * Copyright © 2012-2015, JEDLSoft 17229 * 17230 * Licensed under the Apache License, Version 2.0 (the "License"); 17231 * you may not use this file except in compliance with the License. 17232 * You may obtain a copy of the License at 17233 * 17234 * http://www.apache.org/licenses/LICENSE-2.0 17235 * 17236 * Unless required by applicable law or agreed to in writing, software 17237 * distributed under the License is distributed on an "AS IS" BASIS, 17238 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17239 * 17240 * See the License for the specific language governing permissions and 17241 * limitations under the License. 17242 */ 17243 17244 // !depends ilib.js Utils.js Locale.js LocaleInfo.js 17245 17246 // !data currency 17247 17248 17249 /** 17250 * @class 17251 * Create a new currency information instance. Instances of this class encode 17252 * information about a particular currency.<p> 17253 * 17254 * Note: that if you are looking to format currency for display, please see 17255 * the number formatting class {NumFmt}. This class only gives information 17256 * about currencies.<p> 17257 * 17258 * The options can contain any of the following properties: 17259 * 17260 * <ul> 17261 * <li><i>locale</i> - specify the locale for this instance 17262 * <li><i>code</i> - find info on a specific currency with the given ISO 4217 code 17263 * <li><i>sign</i> - search for a currency that uses this sign 17264 * <li><i>onLoad</i> - a callback function to call when the currency data is fully 17265 * loaded. When the onLoad option is given, this class will attempt to 17266 * load any missing locale data using the ilib loader callback. 17267 * When the constructor is done (even if the data is already preassembled), the 17268 * onLoad function is called with the current instance as a parameter, so this 17269 * callback can be used with preassembled or dynamic loading or a mix of the two. 17270 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 17271 * asynchronously. If this option is given as "false", then the "onLoad" 17272 * callback must be given, as the instance returned from this constructor will 17273 * not be usable for a while. 17274 * <li><i>loadParams</i> - an object containing parameters to pass to the 17275 * loader callback function when locale data is missing. The parameters are not 17276 * interpretted or modified in any way. They are simply passed along. The object 17277 * may contain any property/value pairs as long as the calling code is in 17278 * agreement with the loader callback function as to what those parameters mean. 17279 * </ul> 17280 * 17281 * When searching for a currency by its sign, this class cannot guarantee 17282 * that it will return info about a specific currency. The reason is that currency 17283 * signs are sometimes shared between different currencies and the sign is 17284 * therefore ambiguous. If you need a 17285 * guarantee, find the currency using the code instead.<p> 17286 * 17287 * The way this class finds a currency by sign is the following. If the sign is 17288 * unambiguous, then 17289 * the currency is returned. If there are multiple currencies that use the same 17290 * sign, and the current locale uses that sign, then the default currency for 17291 * the current locale is returned. If there are multiple, but the current locale 17292 * does not use that sign, then the currency with the largest circulation is 17293 * returned. For example, if you are in the en-GB locale, and the sign is "$", 17294 * then this class will notice that there are multiple currencies with that 17295 * sign (USD, CAD, AUD, HKD, MXP, etc.) Since "$" is not used in en-GB, it will 17296 * pick the one with the largest circulation, which in this case is the US Dollar 17297 * (USD).<p> 17298 * 17299 * If neither the code or sign property is set, the currency that is most common 17300 * for the locale 17301 * will be used instead. If the locale is not set, the default locale will be used. 17302 * If the code is given, but it is not found in the list of known currencies, this 17303 * constructor will throw an exception. If the sign is given, but it is not found, 17304 * this constructor will default to the currency for the current locale. If both 17305 * the code and sign properties are given, then the sign property will be ignored 17306 * and only the code property used. If the locale is given, but it is not a known 17307 * locale, this class will default to the default locale instead.<p> 17308 * 17309 * 17310 * @constructor 17311 * @param options {Object} a set of properties to govern how this instance is constructed. 17312 * @throws "currency xxx is unknown" when the given currency code is not in the list of 17313 * known currencies. xxx is replaced with the requested code. 17314 */ 17315 var Currency = function (options) { 17316 this.sync = true; 17317 17318 if (options) { 17319 if (options.code) { 17320 this.code = options.code; 17321 } 17322 if (options.locale) { 17323 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 17324 } 17325 if (options.sign) { 17326 this.sign = options.sign; 17327 } 17328 if (typeof(options.sync) !== 'undefined') { 17329 this.sync = options.sync; 17330 } 17331 if (options.loadParams) { 17332 this.loadParams = options.loadParams; 17333 } 17334 } 17335 17336 this.locale = this.locale || new Locale(); 17337 if (typeof(ilib.data.currency) === 'undefined') { 17338 Utils.loadData({ 17339 name: "currency.json", 17340 object: Currency, 17341 locale: "-", 17342 sync: this.sync, 17343 loadParams: this.loadParams, 17344 callback: ilib.bind(this, function(currency) { 17345 ilib.data.currency = currency; 17346 this._loadLocinfo(options && options.onLoad); 17347 }) 17348 }); 17349 } else { 17350 this._loadLocinfo(options && options.onLoad); 17351 } 17352 }; 17353 17354 /** 17355 * Return an array of the ids for all ISO 4217 currencies that 17356 * this copy of ilib knows about. 17357 * 17358 * @static 17359 * @return {Array.<string>} an array of currency ids that this copy of ilib knows about. 17360 */ 17361 Currency.getAvailableCurrencies = function() { 17362 var ret = [], 17363 cur, 17364 currencies = new ResBundle({ 17365 name: "currency" 17366 }).getResObj(); 17367 17368 for (cur in currencies) { 17369 if (cur && currencies[cur]) { 17370 ret.push(cur); 17371 } 17372 } 17373 17374 return ret; 17375 }; 17376 17377 Currency.prototype = { 17378 /** 17379 * @private 17380 */ 17381 _loadLocinfo: function(onLoad) { 17382 new LocaleInfo(this.locale, { 17383 onLoad: ilib.bind(this, function (li) { 17384 var currInfo; 17385 17386 this.locinfo = li; 17387 if (this.code) { 17388 currInfo = ilib.data.currency[this.code]; 17389 if (!currInfo) { 17390 throw "currency " + this.code + " is unknown"; 17391 } 17392 } else if (this.sign) { 17393 currInfo = ilib.data.currency[this.sign]; // maybe it is really a code... 17394 if (typeof(currInfo) !== 'undefined') { 17395 this.code = this.sign; 17396 } else { 17397 this.code = this.locinfo.getCurrency(); 17398 currInfo = ilib.data.currency[this.code]; 17399 if (currInfo.sign !== this.sign) { 17400 // current locale does not use the sign, so search for it 17401 for (var cur in ilib.data.currency) { 17402 if (cur && ilib.data.currency[cur]) { 17403 currInfo = ilib.data.currency[cur]; 17404 if (currInfo.sign === this.sign) { 17405 // currency data is already ordered so that the currency with the 17406 // largest circulation is at the beginning, so all we have to do 17407 // is take the first one in the list that matches 17408 this.code = cur; 17409 break; 17410 } 17411 } 17412 } 17413 } 17414 } 17415 } 17416 17417 if (!currInfo || !this.code) { 17418 this.code = this.locinfo.getCurrency(); 17419 currInfo = ilib.data.currency[this.code]; 17420 } 17421 17422 this.name = currInfo.name; 17423 this.fractionDigits = currInfo.decimals; 17424 this.sign = currInfo.sign; 17425 17426 if (typeof(onLoad) === 'function') { 17427 onLoad(this); 17428 } 17429 }) 17430 }); 17431 }, 17432 17433 /** 17434 * Return the ISO 4217 currency code for this instance. 17435 * @return {string} the ISO 4217 currency code for this instance 17436 */ 17437 getCode: function () { 17438 return this.code; 17439 }, 17440 17441 /** 17442 * Return the default number of fraction digits that is typically used 17443 * with this type of currency. 17444 * @return {number} the number of fraction digits for this currency 17445 */ 17446 getFractionDigits: function () { 17447 return this.fractionDigits; 17448 }, 17449 17450 /** 17451 * Return the sign commonly used to represent this currency. 17452 * @return {string} the sign commonly used to represent this currency 17453 */ 17454 getSign: function () { 17455 return this.sign; 17456 }, 17457 17458 /** 17459 * Return the name of the currency in English. 17460 * @return {string} the name of the currency in English 17461 */ 17462 getName: function () { 17463 return this.name; 17464 }, 17465 17466 /** 17467 * Return the locale for this currency. If the options to the constructor 17468 * included a locale property in order to find the currency that is appropriate 17469 * for that locale, then the locale is returned here. If the options did not 17470 * include a locale, then this method returns undefined. 17471 * @return {Locale} the locale used in the constructor of this instance, 17472 * or undefined if no locale was given in the constructor 17473 */ 17474 getLocale: function () { 17475 return this.locale; 17476 } 17477 }; 17478 17479 17480 17481 /*< INumber.js */ 17482 /* 17483 * INumber.js - Parse a number in any locale 17484 * 17485 * Copyright © 2012-2015, JEDLSoft 17486 * 17487 * Licensed under the Apache License, Version 2.0 (the "License"); 17488 * you may not use this file except in compliance with the License. 17489 * You may obtain a copy of the License at 17490 * 17491 * http://www.apache.org/licenses/LICENSE-2.0 17492 * 17493 * Unless required by applicable law or agreed to in writing, software 17494 * distributed under the License is distributed on an "AS IS" BASIS, 17495 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17496 * 17497 * See the License for the specific language governing permissions and 17498 * limitations under the License. 17499 */ 17500 17501 /* 17502 !depends 17503 ilib.js 17504 Locale.js 17505 isDigit.js 17506 isSpace.js 17507 LocaleInfo.js 17508 Utils.js 17509 Currency.js 17510 */ 17511 17512 17513 17514 17515 17516 17517 /** 17518 * @class 17519 * Parse a string as a number, ignoring all locale-specific formatting.<p> 17520 * 17521 * This class is different from the standard Javascript parseInt() and parseFloat() 17522 * functions in that the number to be parsed can have formatting characters in it 17523 * that are not supported by those two 17524 * functions, and it handles numbers written in other locales properly. For example, 17525 * if you pass the string "203,231.23" to the parseFloat() function in Javascript, it 17526 * will return you the number 203. The INumber class will parse it correctly and 17527 * the value() function will return the number 203231.23. If you pass parseFloat() the 17528 * string "203.231,23" with the locale set to de-DE, it will return you 203 again. This 17529 * class will return the correct number 203231.23 again.<p> 17530 * 17531 * The options object may contain any of the following properties: 17532 * 17533 * <ul> 17534 * <li><i>locale</i> - specify the locale of the string to parse. This is used to 17535 * figure out what the decimal point character is. If not specified, the default locale 17536 * for the app or browser is used. 17537 * <li><i>type</i> - specify whether this string should be interpretted as a number, 17538 * currency, or percentage amount. When the number is interpretted as a currency 17539 * amount, the getCurrency() method will return something useful, otherwise it will 17540 * return undefined. If 17541 * the number is to be interpretted as percentage amount and there is a percentage sign 17542 * in the string, then the number will be returned 17543 * as a fraction from the valueOf() method. If there is no percentage sign, then the 17544 * number will be returned as a regular number. That is "58.3%" will be returned as the 17545 * number 0.583 but "58.3" will be returned as 58.3. Valid values for this property 17546 * are "number", "currency", and "percentage". Default if this is not specified is 17547 * "number". 17548 * <li><i>onLoad</i> - a callback function to call when the locale data is fully 17549 * loaded. When the onLoad option is given, this class will attempt to 17550 * load any missing locale data using the ilib loader callback. 17551 * When the constructor is done (even if the data is already preassembled), the 17552 * onLoad function is called with the current instance as a parameter, so this 17553 * callback can be used with preassembled or dynamic loading or a mix of the two. 17554 * 17555 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 17556 * asynchronously. If this option is given as "false", then the "onLoad" 17557 * callback must be given, as the instance returned from this constructor will 17558 * not be usable for a while. 17559 * 17560 * <li><i>loadParams</i> - an object containing parameters to pass to the 17561 * loader callback function when locale data is missing. The parameters are not 17562 * interpretted or modified in any way. They are simply passed along. The object 17563 * may contain any property/value pairs as long as the calling code is in 17564 * agreement with the loader callback function as to what those parameters mean. 17565 * </ul> 17566 * <p> 17567 * 17568 * This class is named INumber ("ilib number") so as not to conflict with the 17569 * built-in Javascript Number class. 17570 * 17571 * @constructor 17572 * @param {string|number|INumber|Number|undefined} str a string to parse as a number, or a number value 17573 * @param {Object=} options Options controlling how the instance should be created 17574 */ 17575 var INumber = function (str, options) { 17576 var i, stripped = "", 17577 sync = true, 17578 loadParams, 17579 onLoad; 17580 17581 this.locale = new Locale(); 17582 this.type = "number"; 17583 17584 if (options) { 17585 if (options.locale) { 17586 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 17587 } 17588 if (options.type) { 17589 switch (options.type) { 17590 case "number": 17591 case "currency": 17592 case "percentage": 17593 this.type = options.type; 17594 break; 17595 default: 17596 break; 17597 } 17598 } 17599 if (typeof(options.sync) !== 'undefined') { 17600 sync = (options.sync == true); 17601 } 17602 loadParams = options.loadParams; 17603 onLoad = options.onLoad; 17604 } 17605 17606 isDigit._init(sync, loadParams, ilib.bind(this, function() { 17607 isSpace._init(sync, loadParams, ilib.bind(this, function() { 17608 new LocaleInfo(this.locale, { 17609 sync: sync, 17610 onLoad: ilib.bind(this, function (li) { 17611 this.decimal = li.getDecimalSeparator(); 17612 17613 switch (typeof(str)) { 17614 case 'string': 17615 // stripping should work for all locales, because you just ignore all the 17616 // formatting except the decimal char 17617 var unary = true; // looking for the unary minus still? 17618 var lastNumericChar = 0; 17619 this.str = str || "0"; 17620 i = 0; 17621 for (i = 0; i < this.str.length; i++) { 17622 if (unary && this.str.charAt(i) === '-') { 17623 unary = false; 17624 stripped += this.str.charAt(i); 17625 lastNumericChar = i; 17626 } else if (isDigit(this.str.charAt(i))) { 17627 stripped += this.str.charAt(i); 17628 unary = false; 17629 lastNumericChar = i; 17630 } else if (this.str.charAt(i) === this.decimal) { 17631 stripped += "."; // always convert to period 17632 unary = false; 17633 lastNumericChar = i; 17634 } // else ignore 17635 } 17636 // record what we actually parsed 17637 this.parsed = this.str.substring(0, lastNumericChar+1); 17638 /** @type {number} */ 17639 this.value = parseFloat(stripped); 17640 break; 17641 case 'number': 17642 this.str = "" + str; 17643 this.value = str; 17644 break; 17645 17646 case 'object': 17647 // call parseFloat to coerse the type to number 17648 this.value = parseFloat(str.valueOf()); 17649 this.str = "" + this.value; 17650 break; 17651 17652 case 'undefined': 17653 this.value = 0; 17654 this.str = "0"; 17655 break; 17656 } 17657 17658 switch (this.type) { 17659 default: 17660 // don't need to do anything special for other types 17661 break; 17662 case "percentage": 17663 if (this.str.indexOf(li.getPercentageSymbol()) !== -1) { 17664 this.value /= 100; 17665 } 17666 break; 17667 case "currency": 17668 stripped = ""; 17669 i = 0; 17670 while (i < this.str.length && 17671 !isDigit(this.str.charAt(i)) && 17672 !isSpace(this.str.charAt(i))) { 17673 stripped += this.str.charAt(i++); 17674 } 17675 if (stripped.length === 0) { 17676 while (i < this.str.length && 17677 isDigit(this.str.charAt(i)) || 17678 isSpace(this.str.charAt(i)) || 17679 this.str.charAt(i) === '.' || 17680 this.str.charAt(i) === ',' ) { 17681 i++; 17682 } 17683 while (i < this.str.length && 17684 !isDigit(this.str.charAt(i)) && 17685 !isSpace(this.str.charAt(i))) { 17686 stripped += this.str.charAt(i++); 17687 } 17688 } 17689 new Currency({ 17690 locale: this.locale, 17691 sign: stripped, 17692 sync: sync, 17693 onLoad: ilib.bind(this, function (cur) { 17694 this.currency = cur; 17695 if (options && typeof(options.onLoad) === 'function') { 17696 options.onLoad(this); 17697 } 17698 }) 17699 }); 17700 return; 17701 } 17702 17703 if (options && typeof(options.onLoad) === 'function') { 17704 options.onLoad(this); 17705 } 17706 }) 17707 }); 17708 })); 17709 })); 17710 }; 17711 17712 INumber.prototype = { 17713 /** 17714 * Return the locale for this formatter instance. 17715 * @return {Locale} the locale instance for this formatter 17716 */ 17717 getLocale: function () { 17718 return this.locale; 17719 }, 17720 17721 /** 17722 * Return the original string that this number instance was created with. 17723 * @return {string} the original string 17724 */ 17725 toString: function () { 17726 return this.str; 17727 }, 17728 17729 /** 17730 * If the type of this INumber instance is "currency", then the parser will attempt 17731 * to figure out which currency this amount represents. The amount can be written 17732 * with any of the currency signs or ISO 4217 codes that are currently 17733 * recognized by ilib, and the currency signs may occur before or after the 17734 * numeric portion of the string. If no currency can be recognized, then the 17735 * default currency for the locale is returned. If multiple currencies can be 17736 * recognized (for example if the currency sign is "$"), then this method 17737 * will prefer the one for the current locale. If multiple currencies can be 17738 * recognized, but none are used in the current locale, then the first currency 17739 * encountered will be used. This may produce random results, though the larger 17740 * currencies occur earlier in the list. For example, if the sign found in the 17741 * string is "$" and that is not the sign of the currency of the current locale 17742 * then the US dollar will be recognized, as it is the largest currency that uses 17743 * the "$" as its sign. 17744 * 17745 * @return {Currency|undefined} the currency instance for this amount, or 17746 * undefined if this INumber object is not of type currency 17747 */ 17748 getCurrency: function () { 17749 return this.currency; 17750 }, 17751 17752 /** 17753 * Return the value of this INumber object as a primitive number instance. 17754 * @return {number} the value of this number instance 17755 */ 17756 valueOf: function () { 17757 return this.value; 17758 } 17759 }; 17760 17761 17762 /*< NumFmt.js */ 17763 /* 17764 * NumFmt.js - Number formatter definition 17765 * 17766 * Copyright © 2012-2015, JEDLSoft 17767 * 17768 * Licensed under the Apache License, Version 2.0 (the "License"); 17769 * you may not use this file except in compliance with the License. 17770 * You may obtain a copy of the License at 17771 * 17772 * http://www.apache.org/licenses/LICENSE-2.0 17773 * 17774 * Unless required by applicable law or agreed to in writing, software 17775 * distributed under the License is distributed on an "AS IS" BASIS, 17776 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17777 * 17778 * See the License for the specific language governing permissions and 17779 * limitations under the License. 17780 */ 17781 17782 /* 17783 !depends 17784 ilib.js 17785 Locale.js 17786 LocaleInfo.js 17787 Utils.js 17788 MathUtils.js 17789 Currency.js 17790 IString.js 17791 JSUtils.js 17792 INumber.js 17793 */ 17794 17795 // !data localeinfo currency 17796 17797 17798 17799 /** 17800 * @class 17801 * Create a new number formatter instance. Locales differ in the way that digits 17802 * in a formatted number are grouped, in the way the decimal character is represented, 17803 * etc. Use this formatter to get it right for any locale.<p> 17804 * 17805 * This formatter can format plain numbers, currency amounts, and percentage amounts.<p> 17806 * 17807 * As with all formatters, the recommended 17808 * practice is to create one formatter and use it multiple times to format various 17809 * numbers.<p> 17810 * 17811 * The options can contain any of the following properties: 17812 * 17813 * <ul> 17814 * <li><i>locale</i> - use the conventions of the specified locale when figuring out how to 17815 * format a number. 17816 * <li><i>type</i> - the type of this formatter. Valid values are "number", "currency", or 17817 * "percentage". If this property is not specified, the default is "number". 17818 * <li><i>currency</i> - the ISO 4217 3-letter currency code to use when the formatter type 17819 * is "currency". This property is required for currency formatting. If the type property 17820 * is "currency" and the currency property is not specified, the constructor will throw a 17821 * an exception. 17822 * <li><i>maxFractionDigits</i> - the maximum number of digits that should appear in the 17823 * formatted output after the decimal. A value of -1 means unlimited, and 0 means only print 17824 * the integral part of the number. 17825 * <li><i>minFractionDigits</i> - the minimum number of fractional digits that should 17826 * appear in the formatted output. If the number does not have enough fractional digits 17827 * to reach this minimum, the number will be zero-padded at the end to get to the limit. 17828 * If the type of the formatter is "currency" and this 17829 * property is not specified, then the minimum fraction digits is set to the normal number 17830 * of digits used with that currency, which is almost always 0, 2, or 3 digits. 17831 * <li><i>useNative</i> - the flag used to determaine whether to use the native script settings 17832 * for formatting the numbers . 17833 * <li><i>roundingMode</i> - When the maxFractionDigits or maxIntegerDigits is specified, 17834 * this property governs how the least significant digits are rounded to conform to that 17835 * maximum. The value of this property is a string with one of the following values: 17836 * <ul> 17837 * <li><i>up</i> - round away from zero 17838 * <li><i>down</i> - round towards zero. This has the effect of truncating the number 17839 * <li><i>ceiling</i> - round towards positive infinity 17840 * <li><i>floor</i> - round towards negative infinity 17841 * <li><i>halfup</i> - round towards nearest neighbour. If equidistant, round up. 17842 * <li><i>halfdown</i> - round towards nearest neighbour. If equidistant, round down. 17843 * <li><i>halfeven</i> - round towards nearest neighbour. If equidistant, round towards the even neighbour 17844 * <li><i>halfodd</i> - round towards nearest neighbour. If equidistant, round towards the odd neighbour 17845 * </ul> 17846 * When the type of the formatter is "currency" and the <i>roundingMode</i> property is not 17847 * set, then the standard legal rounding rules for the locale are followed. If the type 17848 * is "number" or "percentage" and the <i>roundingMode</i> property is not set, then the 17849 * default mode is "halfdown".</i>. 17850 * 17851 * <li><i>style</i> - When the type of this formatter is "currency", the currency amount 17852 * can be formatted in the following styles: "common" and "iso". The common style is the 17853 * one commonly used in every day writing where the currency unit is represented using a 17854 * symbol. eg. "$57.35" for fifty-seven dollars and thirty five cents. The iso style is 17855 * the international style where the currency unit is represented using the ISO 4217 code. 17856 * eg. "USD 57.35" for the same amount. The default is "common" style if the style is 17857 * not specified.<p> 17858 * 17859 * When the type of this formatter is "number", the style can be one of the following: 17860 * <ul> 17861 * <li><i>standard - format a fully specified floating point number properly for the locale 17862 * <li><i>scientific</i> - use scientific notation for all numbers. That is, 1 integral 17863 * digit, followed by a number of fractional digits, followed by an "e" which denotes 17864 * exponentiation, followed digits which give the power of 10 in the exponent. 17865 * <li><i>native</i> - format a floating point number using the native digits and 17866 * formatting symbols for the script of the locale. 17867 * <li><i>nogrouping</i> - format a floating point number without grouping digits for 17868 * the integral portion of the number 17869 * </ul> 17870 * Note that if you specify a maximum number 17871 * of integral digits, the formatter with a standard style will give you standard 17872 * formatting for smaller numbers and scientific notation for larger numbers. The default 17873 * is standard style if this is not specified. 17874 * 17875 * <li><i>onLoad</i> - a callback function to call when the format data is fully 17876 * loaded. When the onLoad option is given, this class will attempt to 17877 * load any missing locale data using the ilib loader callback. 17878 * When the constructor is done (even if the data is already preassembled), the 17879 * onLoad function is called with the current instance as a parameter, so this 17880 * callback can be used with preassembled or dynamic loading or a mix of the two. 17881 * 17882 * <li>sync - tell whether to load any missing locale data synchronously or 17883 * asynchronously. If this option is given as "false", then the "onLoad" 17884 * callback must be given, as the instance returned from this constructor will 17885 * not be usable for a while. 17886 * 17887 * <li><i>loadParams</i> - an object containing parameters to pass to the 17888 * loader callback function when locale data is missing. The parameters are not 17889 * interpretted or modified in any way. They are simply passed along. The object 17890 * may contain any property/value pairs as long as the calling code is in 17891 * agreement with the loader callback function as to what those parameters mean. 17892 * </ul> 17893 * <p> 17894 * 17895 * 17896 * @constructor 17897 * @param {Object.<string,*>} options A set of options that govern how the formatter will behave 17898 */ 17899 var NumFmt = function (options) { 17900 var sync = true; 17901 this.locale = new Locale(); 17902 /** 17903 * @private 17904 * @type {string} 17905 */ 17906 this.type = "number"; 17907 var loadParams = undefined; 17908 17909 if (options) { 17910 if (options.locale) { 17911 this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; 17912 } 17913 17914 if (options.type) { 17915 if (options.type === 'number' || 17916 options.type === 'currency' || 17917 options.type === 'percentage') { 17918 this.type = options.type; 17919 } 17920 } 17921 17922 if (options.currency) { 17923 /** 17924 * @private 17925 * @type {string} 17926 */ 17927 this.currency = options.currency; 17928 } 17929 17930 if (typeof (options.maxFractionDigits) === 'number') { 17931 /** 17932 * @private 17933 * @type {number|undefined} 17934 */ 17935 this.maxFractionDigits = this._toPrimitive(options.maxFractionDigits); 17936 } 17937 if (typeof (options.minFractionDigits) === 'number') { 17938 /** 17939 * @private 17940 * @type {number|undefined} 17941 */ 17942 this.minFractionDigits = this._toPrimitive(options.minFractionDigits); 17943 // enforce the limits to avoid JS exceptions 17944 if (this.minFractionDigits < 0) { 17945 this.minFractionDigits = 0; 17946 } 17947 if (this.minFractionDigits > 20) { 17948 this.minFractionDigits = 20; 17949 } 17950 } 17951 if (options.style) { 17952 /** 17953 * @private 17954 * @type {string} 17955 */ 17956 this.style = options.style; 17957 } 17958 if (typeof(options.useNative) === 'boolean') { 17959 /** 17960 * @private 17961 * @type {boolean} 17962 * */ 17963 this.useNative = options.useNative; 17964 } 17965 /** 17966 * @private 17967 * @type {string} 17968 */ 17969 this.roundingMode = options.roundingMode; 17970 17971 if (typeof(options.sync) === 'boolean') { 17972 sync = options.sync; 17973 } 17974 17975 loadParams = options.loadParams; 17976 } 17977 17978 /** 17979 * @private 17980 * @type {LocaleInfo|undefined} 17981 */ 17982 this.localeInfo = undefined; 17983 17984 new LocaleInfo(this.locale, { 17985 sync: sync, 17986 loadParams: loadParams, 17987 onLoad: ilib.bind(this, function (li) { 17988 /** 17989 * @private 17990 * @type {LocaleInfo|undefined} 17991 */ 17992 this.localeInfo = li; 17993 17994 if (this.type === "number") { 17995 this.templateNegative = new IString(this.localeInfo.getNegativeNumberFormat() || "-{n}"); 17996 } else if (this.type === "currency") { 17997 var templates; 17998 17999 if (!this.currency || typeof (this.currency) != 'string') { 18000 throw "A currency property is required in the options to the number formatter constructor when the type property is set to currency."; 18001 } 18002 18003 new Currency({ 18004 locale: this.locale, 18005 code: this.currency, 18006 sync: sync, 18007 loadParams: loadParams, 18008 onLoad: ilib.bind(this, function (cur) { 18009 this.currencyInfo = cur; 18010 if (this.style !== "common" && this.style !== "iso") { 18011 this.style = "common"; 18012 } 18013 18014 if (typeof(this.maxFractionDigits) !== 'number' && typeof(this.minFractionDigits) !== 'number') { 18015 this.minFractionDigits = this.maxFractionDigits = this.currencyInfo.getFractionDigits(); 18016 } 18017 18018 templates = this.localeInfo.getCurrencyFormats(); 18019 this.template = new IString(templates[this.style] || templates.common); 18020 this.templateNegative = new IString(templates[this.style + "Negative"] || templates["commonNegative"]); 18021 this.sign = (this.style === "iso") ? this.currencyInfo.getCode() : this.currencyInfo.getSign(); 18022 18023 if (!this.roundingMode) { 18024 this.roundingMode = this.currencyInfo && this.currencyInfo.roundingMode; 18025 } 18026 18027 this._init(); 18028 18029 if (options && typeof (options.onLoad) === 'function') { 18030 options.onLoad(this); 18031 } 18032 }) 18033 }); 18034 return; 18035 } else if (this.type === "percentage") { 18036 this.template = new IString(this.localeInfo.getPercentageFormat() || "{n}%"); 18037 this.templateNegative = new IString(this.localeInfo.getNegativePercentageFormat() || this.localeInfo.getNegativeNumberFormat() + "%"); 18038 } 18039 18040 this._init(); 18041 18042 if (options && typeof (options.onLoad) === 'function') { 18043 options.onLoad(this); 18044 } 18045 }) 18046 }); 18047 }; 18048 18049 /** 18050 * Return an array of available locales that this formatter can format 18051 * @static 18052 * @return {Array.<Locale>|undefined} an array of available locales 18053 */ 18054 NumFmt.getAvailableLocales = function () { 18055 return undefined; 18056 }; 18057 18058 /** 18059 * @private 18060 * @const 18061 * @type string 18062 */ 18063 NumFmt.zeros = "0000000000000000000000000000000000000000000000000000000000000000000000"; 18064 18065 NumFmt.prototype = { 18066 /** 18067 * Return true if this formatter uses native digits to format the number. If the useNative 18068 * option is given to the constructor, then this flag will be honoured. If the useNative 18069 * option is not given to the constructor, this this formatter will use native digits if 18070 * the locale typically uses native digits. 18071 * 18072 * @return {boolean} true if this formatter will format with native digits, false otherwise 18073 */ 18074 getUseNative: function() { 18075 if (typeof(this.useNative) === "boolean") { 18076 return this.useNative; 18077 } 18078 return (this.localeInfo.getDigitsStyle() === "native"); 18079 }, 18080 18081 /** 18082 * @private 18083 */ 18084 _init: function () { 18085 if (this.maxFractionDigits < this.minFractionDigits) { 18086 this.minFractionDigits = this.maxFractionDigits; 18087 } 18088 18089 if (!this.roundingMode) { 18090 this.roundingMode = this.localeInfo.getRoundingMode(); 18091 } 18092 18093 if (!this.roundingMode) { 18094 this.roundingMode = "halfdown"; 18095 } 18096 18097 // set up the function, so we only have to figure it out once 18098 // and not every time we do format() 18099 this.round = MathUtils[this.roundingMode]; 18100 if (!this.round) { 18101 this.roundingMode = "halfdown"; 18102 this.round = MathUtils[this.roundingMode]; 18103 } 18104 18105 if (this.style === "nogrouping") { 18106 this.prigroupSize = this.secgroupSize = 0; 18107 } else { 18108 this.prigroupSize = this.localeInfo.getPrimaryGroupingDigits(); 18109 this.secgroupSize = this.localeInfo.getSecondaryGroupingDigits(); 18110 this.groupingSeparator = this.getUseNative() ? this.localeInfo.getNativeGroupingSeparator() : this.localeInfo.getGroupingSeparator(); 18111 } 18112 this.decimalSeparator = this.getUseNative() ? this.localeInfo.getNativeDecimalSeparator() : this.localeInfo.getDecimalSeparator(); 18113 18114 if (this.getUseNative()) { 18115 var nd = this.localeInfo.getNativeDigits() || this.localeInfo.getDigits(); 18116 if (nd) { 18117 this.digits = nd.split(""); 18118 } 18119 } 18120 18121 this.exponentSymbol = this.localeInfo.getExponential() || "e"; 18122 }, 18123 18124 /** 18125 * @private 18126 * @param {INumber|Number|string|number} num object, string, or number to convert to a primitive number 18127 * @return {number} the primitive number equivalent of the argument 18128 */ 18129 _toPrimitive: function (num) { 18130 var n = 0; 18131 18132 switch (typeof (num)) { 18133 case 'number': 18134 n = num; 18135 break; 18136 case 'string': 18137 n = parseFloat(num); 18138 break; 18139 case 'object': 18140 // call parseFloat to coerse the type to number 18141 n = parseFloat(num.valueOf()); 18142 break; 18143 } 18144 18145 return n; 18146 }, 18147 18148 /** 18149 * Format the number using scientific notation as a positive number. Negative 18150 * formatting to be applied later. 18151 * @private 18152 * @param {number} num the number to format 18153 * @return {string} the formatted number 18154 */ 18155 _formatScientific: function (num) { 18156 var n = new Number(num); 18157 var formatted; 18158 18159 var factor, 18160 str = n.toExponential(), 18161 parts = str.split("e"), 18162 significant = parts[0], 18163 exponent = parts[1], 18164 numparts, 18165 integral, 18166 fraction; 18167 18168 if (this.maxFractionDigits > 0) { 18169 // if there is a max fraction digits setting, round the fraction to 18170 // the right length first by dividing or multiplying by powers of 10. 18171 // manipulate the fraction digits so as to 18172 // avoid the rounding errors of floating point numbers 18173 factor = Math.pow(10, this.maxFractionDigits); 18174 significant = this.round(significant * factor) / factor; 18175 } 18176 numparts = ("" + significant).split("."); 18177 integral = numparts[0]; 18178 fraction = numparts[1]; 18179 18180 if (typeof(this.maxFractionDigits) !== 'undefined') { 18181 fraction = fraction.substring(0, this.maxFractionDigits); 18182 } 18183 if (typeof(this.minFractionDigits) !== 'undefined') { 18184 fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); 18185 } 18186 formatted = integral; 18187 if (fraction.length) { 18188 formatted += this.decimalSeparator + fraction; 18189 } 18190 formatted += this.exponentSymbol + exponent; 18191 return formatted; 18192 }, 18193 18194 /** 18195 * Formats the number as a positive number. Negative formatting to be applied later. 18196 * @private 18197 * @param {number} num the number to format 18198 * @return {string} the formatted number 18199 */ 18200 _formatStandard: function (num) { 18201 var i; 18202 var k; 18203 18204 if (typeof(this.maxFractionDigits) !== 'undefined' && this.maxFractionDigits > -1) { 18205 var factor = Math.pow(10, this.maxFractionDigits); 18206 num = this.round(num * factor) / factor; 18207 } 18208 18209 num = Math.abs(num); 18210 18211 var parts = ("" + num).split("."), 18212 integral = parts[0], 18213 fraction = parts[1], 18214 cycle, 18215 formatted; 18216 18217 integral = integral.toString(); 18218 18219 if (this.minFractionDigits > 0) { 18220 fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); 18221 } 18222 18223 if (this.secgroupSize > 0) { 18224 if (integral.length > this.prigroupSize) { 18225 var size1 = this.prigroupSize; 18226 var size2 = integral.length; 18227 var size3 = size2 - size1; 18228 integral = integral.slice(0, size3) + this.groupingSeparator + integral.slice(size3); 18229 var num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); 18230 k = num_sec.length; 18231 while (k > this.secgroupSize) { 18232 var secsize1 = this.secgroupSize; 18233 var secsize2 = num_sec.length; 18234 var secsize3 = secsize2 - secsize1; 18235 integral = integral.slice(0, secsize3) + this.groupingSeparator + integral.slice(secsize3); 18236 num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); 18237 k = num_sec.length; 18238 } 18239 } 18240 18241 formatted = integral; 18242 } else if (this.prigroupSize !== 0) { 18243 cycle = MathUtils.mod(integral.length - 1, this.prigroupSize); 18244 18245 formatted = ""; 18246 18247 for (i = 0; i < integral.length - 1; i++) { 18248 formatted += integral.charAt(i); 18249 if (cycle === 0) { 18250 formatted += this.groupingSeparator; 18251 } 18252 cycle = MathUtils.mod(cycle - 1, this.prigroupSize); 18253 } 18254 formatted += integral.charAt(integral.length - 1); 18255 } else { 18256 formatted = integral; 18257 } 18258 18259 if (fraction && (typeof(this.maxFractionDigits) === 'undefined' || this.maxFractionDigits > 0)) { 18260 formatted += this.decimalSeparator; 18261 formatted += fraction; 18262 } 18263 18264 if (this.digits) { 18265 formatted = JSUtils.mapString(formatted, this.digits); 18266 } 18267 18268 return formatted; 18269 }, 18270 18271 /** 18272 * Format a number according to the settings of this number formatter instance. 18273 * @param num {number|string|INumber|Number} a floating point number to format 18274 * @return {string} a string containing the formatted number 18275 */ 18276 format: function (num) { 18277 var formatted, n; 18278 18279 if (typeof (num) === 'undefined') { 18280 return ""; 18281 } 18282 18283 // convert to a real primitive number type 18284 n = this._toPrimitive(num); 18285 18286 if (this.type === "number") { 18287 formatted = (this.style === "scientific") ? 18288 this._formatScientific(n) : 18289 this._formatStandard(n); 18290 18291 if (num < 0) { 18292 formatted = this.templateNegative.format({n: formatted}); 18293 } 18294 } else { 18295 formatted = this._formatStandard(n); 18296 var template = (n < 0) ? this.templateNegative : this.template; 18297 formatted = template.format({ 18298 n: formatted, 18299 s: this.sign 18300 }); 18301 } 18302 18303 return formatted; 18304 }, 18305 18306 /** 18307 * Return the type of formatter. Valid values are "number", "currency", and 18308 * "percentage". 18309 * 18310 * @return {string} the type of formatter 18311 */ 18312 getType: function () { 18313 return this.type; 18314 }, 18315 18316 /** 18317 * Return the locale for this formatter instance. 18318 * @return {Locale} the locale instance for this formatter 18319 */ 18320 getLocale: function () { 18321 return this.locale; 18322 }, 18323 18324 /** 18325 * Returns true if this formatter groups together digits in the integral 18326 * portion of a number, based on the options set up in the constructor. In 18327 * most western European cultures, this means separating every 3 digits 18328 * of the integral portion of a number with a particular character. 18329 * 18330 * @return {boolean} true if this formatter groups digits in the integral 18331 * portion of the number 18332 */ 18333 isGroupingUsed: function () { 18334 return (this.groupingSeparator !== 'undefined' && this.groupingSeparator.length > 0); 18335 }, 18336 18337 /** 18338 * Returns the maximum fraction digits set up in the constructor. 18339 * 18340 * @return {number} the maximum number of fractional digits this 18341 * formatter will format, or -1 for no maximum 18342 */ 18343 getMaxFractionDigits: function () { 18344 return typeof (this.maxFractionDigits) !== 'undefined' ? this.maxFractionDigits : -1; 18345 }, 18346 18347 /** 18348 * Returns the minimum fraction digits set up in the constructor. If 18349 * the formatter has the type "currency", then the minimum fraction 18350 * digits is the amount of digits that is standard for the currency 18351 * in question unless overridden in the options to the constructor. 18352 * 18353 * @return {number} the minimum number of fractional digits this 18354 * formatter will format, or -1 for no minimum 18355 */ 18356 getMinFractionDigits: function () { 18357 return typeof (this.minFractionDigits) !== 'undefined' ? this.minFractionDigits : -1; 18358 }, 18359 18360 /** 18361 * Returns the ISO 4217 code for the currency that this formatter formats. 18362 * IF the typeof this formatter is not "currency", then this method will 18363 * return undefined. 18364 * 18365 * @return {string} the ISO 4217 code for the currency that this formatter 18366 * formats, or undefined if this not a currency formatter 18367 */ 18368 getCurrency: function () { 18369 return this.currencyInfo && this.currencyInfo.getCode(); 18370 }, 18371 18372 /** 18373 * Returns the rounding mode set up in the constructor. The rounding mode 18374 * controls how numbers are rounded when the integral or fraction digits 18375 * of a number are limited. 18376 * 18377 * @return {string} the name of the rounding mode used in this formatter 18378 */ 18379 getRoundingMode: function () { 18380 return this.roundingMode; 18381 }, 18382 18383 /** 18384 * If this formatter is a currency formatter, then the style determines how the 18385 * currency is denoted in the formatted output. This method returns the style 18386 * that this formatter will produce. (See the constructor comment for more about 18387 * the styles.) 18388 * @return {string} the name of the style this formatter will use to format 18389 * currency amounts, or "undefined" if this formatter is not a currency formatter 18390 */ 18391 getStyle: function () { 18392 return this.style; 18393 } 18394 }; 18395 18396 18397 /*< DurationFmt.js */ 18398 /* 18399 * DurFmt.js - Date formatter definition 18400 * 18401 * Copyright © 2012-2015, JEDLSoft 18402 * 18403 * Licensed under the Apache License, Version 2.0 (the "License"); 18404 * you may not use this file except in compliance with the License. 18405 * You may obtain a copy of the License at 18406 * 18407 * http://www.apache.org/licenses/LICENSE-2.0 18408 * 18409 * Unless required by applicable law or agreed to in writing, software 18410 * distributed under the License is distributed on an "AS IS" BASIS, 18411 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18412 * 18413 * See the License for the specific language governing permissions and 18414 * limitations under the License. 18415 */ 18416 18417 /* 18418 !depends 18419 ilib.js 18420 Locale.js 18421 DateFmt.js 18422 IString.js 18423 ResBundle.js 18424 LocaleInfo.js 18425 JSUtils.js 18426 Utils.js 18427 */ 18428 18429 // !data dateformats sysres 18430 // !resbundle sysres 18431 18432 18433 /** 18434 * @class 18435 * Create a new duration formatter instance. The duration formatter is immutable once 18436 * it is created, but can format as many different durations as needed with the same 18437 * options. Create different duration formatter instances for different purposes 18438 * and then keep them cached for use later if you have more than one duration to 18439 * format.<p> 18440 * 18441 * Duration formatters format lengths of time. The duration formatter is meant to format 18442 * durations of such things as the length of a song or a movie or a meeting, or the 18443 * current position in that song or movie while playing it. If you wish to format a 18444 * period of time that has a specific start and end date/time, then use a 18445 * [DateRngFmt] instance instead and call its format method.<p> 18446 * 18447 * The options may contain any of the following properties: 18448 * 18449 * <ul> 18450 * <li><i>locale</i> - locale to use when formatting the duration. If the locale is 18451 * not specified, then the default locale of the app or web page will be used. 18452 * 18453 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 18454 * formatted string. 18455 * 18456 * <ul> 18457 * <li><i>short</i> - use a short representation of the duration. This is the most compact format possible for the locale. eg. 1y 1m 1w 1d 1:01:01 18458 * <li><i>medium</i> - use a medium length representation of the duration. This is a slightly longer format. eg. 1 yr 1 mo 1 wk 1 dy 1 hr 1 mi 1 se 18459 * <li><i>long</i> - use a long representation of the duration. This is a fully specified format, but some of the textual 18460 * parts may still be abbreviated. eg. 1 yr 1 mo 1 wk 1 day 1 hr 1 min 1 sec 18461 * <li><i>full</i> - use a full representation of the duration. This is a fully specified format where all the textual 18462 * parts are spelled out completely. eg. 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute and 1 second 18463 * </ul> 18464 * 18465 * <li><i>style<i> - whether hours, minutes, and seconds should be formatted as a text string 18466 * or as a regular time as on a clock. eg. text is "1 hour, 15 minutes", whereas clock is "1:15:00". Valid 18467 * values for this property are "text" or "clock". Default if this property is not specified 18468 * is "text". 18469 * 18470 *<li><i>useNative</i> - the flag used to determaine whether to use the native script settings 18471 * for formatting the numbers . 18472 * 18473 * <li><i>onLoad</i> - a callback function to call when the format data is fully 18474 * loaded. When the onLoad option is given, this class will attempt to 18475 * load any missing locale data using the ilib loader callback. 18476 * When the constructor is done (even if the data is already preassembled), the 18477 * onLoad function is called with the current instance as a parameter, so this 18478 * callback can be used with preassembled or dynamic loading or a mix of the two. 18479 * 18480 * <li>sync - tell whether to load any missing locale data synchronously or 18481 * asynchronously. If this option is given as "false", then the "onLoad" 18482 * callback must be given, as the instance returned from this constructor will 18483 * not be usable for a while. 18484 * 18485 * <li><i>loadParams</i> - an object containing parameters to pass to the 18486 * loader callback function when locale data is missing. The parameters are not 18487 * interpretted or modified in any way. They are simply passed along. The object 18488 * may contain any property/value pairs as long as the calling code is in 18489 * agreement with the loader callback function as to what those parameters mean. 18490 * </ul> 18491 * <p> 18492 * 18493 * 18494 * @constructor 18495 * @param {?Object} options options governing the way this date formatter instance works 18496 */ 18497 var DurationFmt = function(options) { 18498 var sync = true; 18499 var loadParams = undefined; 18500 18501 this.locale = new Locale(); 18502 this.length = "short"; 18503 this.style = "text"; 18504 18505 if (options) { 18506 if (options.locale) { 18507 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 18508 } 18509 18510 if (options.length) { 18511 if (options.length === 'short' || 18512 options.length === 'medium' || 18513 options.length === 'long' || 18514 options.length === 'full') { 18515 this.length = options.length; 18516 } 18517 } 18518 18519 if (options.style) { 18520 if (options.style === 'text' || options.style === 'clock') { 18521 this.style = options.style; 18522 } 18523 } 18524 18525 if (typeof(options.sync) !== 'undefined') { 18526 sync = (options.sync == true); 18527 } 18528 18529 if (typeof(options.useNative) === 'boolean') { 18530 this.useNative = options.useNative; 18531 } 18532 18533 loadParams = options.loadParams; 18534 } 18535 18536 new ResBundle({ 18537 locale: this.locale, 18538 name: "sysres", 18539 sync: sync, 18540 loadParams: loadParams, 18541 onLoad: ilib.bind(this, function (sysres) { 18542 switch (this.length) { 18543 case 'short': 18544 this.components = { 18545 year: sysres.getString("#{num}y"), 18546 month: sysres.getString("#{num}m", "durationShortMonths"), 18547 week: sysres.getString("#{num}w"), 18548 day: sysres.getString("#{num}d"), 18549 hour: sysres.getString("#{num}h"), 18550 minute: sysres.getString("#{num}m", "durationShortMinutes"), 18551 second: sysres.getString("#{num}s"), 18552 millisecond: sysres.getString("#{num}m", "durationShortMillis"), 18553 separator: sysres.getString(" ", "separatorShort"), 18554 finalSeparator: "" // not used at this length 18555 }; 18556 break; 18557 18558 case 'medium': 18559 this.components = { 18560 year: sysres.getString("1#1 yr|#{num} yrs", "durationMediumYears"), 18561 month: sysres.getString("1#1 mo|#{num} mos"), 18562 week: sysres.getString("1#1 wk|#{num} wks", "durationMediumWeeks"), 18563 day: sysres.getString("1#1 dy|#{num} dys"), 18564 hour: sysres.getString("1#1 hr|#{num} hrs", "durationMediumHours"), 18565 minute: sysres.getString("1#1 mi|#{num} min"), 18566 second: sysres.getString("1#1 se|#{num} sec"), 18567 millisecond: sysres.getString("#{num} ms", "durationMediumMillis"), 18568 separator: sysres.getString(" ", "separatorMedium"), 18569 finalSeparator: "" // not used at this length 18570 }; 18571 break; 18572 18573 case 'long': 18574 this.components = { 18575 year: sysres.getString("1#1 yr|#{num} yrs"), 18576 month: sysres.getString("1#1 mon|#{num} mons"), 18577 week: sysres.getString("1#1 wk|#{num} wks"), 18578 day: sysres.getString("1#1 day|#{num} days", "durationLongDays"), 18579 hour: sysres.getString("1#1 hr|#{num} hrs"), 18580 minute: sysres.getString("1#1 min|#{num} min"), 18581 second: sysres.getString("1#1 sec|#{num} sec"), 18582 millisecond: sysres.getString("#{num} ms"), 18583 separator: sysres.getString(", ", "separatorLong"), 18584 finalSeparator: "" // not used at this length 18585 }; 18586 break; 18587 18588 case 'full': 18589 this.components = { 18590 year: sysres.getString("1#1 year|#{num} years"), 18591 month: sysres.getString("1#1 month|#{num} months"), 18592 week: sysres.getString("1#1 week|#{num} weeks"), 18593 day: sysres.getString("1#1 day|#{num} days"), 18594 hour: sysres.getString("1#1 hour|#{num} hours"), 18595 minute: sysres.getString("1#1 minute|#{num} minutes"), 18596 second: sysres.getString("1#1 second|#{num} seconds"), 18597 millisecond: sysres.getString("1#1 millisecond|#{num} milliseconds"), 18598 separator: sysres.getString(", ", "separatorFull"), 18599 finalSeparator: sysres.getString(" and ", "finalSeparatorFull") 18600 }; 18601 break; 18602 } 18603 18604 if (this.style === 'clock') { 18605 new DateFmt({ 18606 locale: this.locale, 18607 calendar: "gregorian", 18608 type: "time", 18609 time: "ms", 18610 sync: sync, 18611 loadParams: loadParams, 18612 useNative: this.useNative, 18613 onLoad: ilib.bind(this, function (fmtMS) { 18614 this.timeFmtMS = fmtMS; 18615 new DateFmt({ 18616 locale: this.locale, 18617 calendar: "gregorian", 18618 type: "time", 18619 time: "hm", 18620 sync: sync, 18621 loadParams: loadParams, 18622 useNative: this.useNative, 18623 onLoad: ilib.bind(this, function (fmtHM) { 18624 this.timeFmtHM = fmtHM; 18625 new DateFmt({ 18626 locale: this.locale, 18627 calendar: "gregorian", 18628 type: "time", 18629 time: "hms", 18630 sync: sync, 18631 loadParams: loadParams, 18632 useNative: this.useNative, 18633 onLoad: ilib.bind(this, function (fmtHMS) { 18634 this.timeFmtHMS = fmtHMS; 18635 18636 // munge with the template to make sure that the hours are not formatted mod 12 18637 this.timeFmtHM.template = this.timeFmtHM.template.replace(/hh?/, 'H'); 18638 this.timeFmtHM.templateArr = this.timeFmtHM._tokenize(this.timeFmtHM.template); 18639 this.timeFmtHMS.template = this.timeFmtHMS.template.replace(/hh?/, 'H'); 18640 this.timeFmtHMS.templateArr = this.timeFmtHMS._tokenize(this.timeFmtHMS.template); 18641 18642 this._init(this.timeFmtHM.locinfo, options && options.onLoad); 18643 }) 18644 }); 18645 }) 18646 }); 18647 }) 18648 }); 18649 return; 18650 } 18651 18652 new LocaleInfo(this.locale, { 18653 sync: sync, 18654 loadParams: loadParams, 18655 onLoad: ilib.bind(this, function (li) { 18656 this._init(li, options && options.onLoad); 18657 }) 18658 }); 18659 }) 18660 }); 18661 }; 18662 18663 /** 18664 * @private 18665 * @static 18666 */ 18667 DurationFmt.complist = { 18668 "text": ["year", "month", "week", "day", "hour", "minute", "second", "millisecond"], 18669 "clock": ["year", "month", "week", "day"] 18670 }; 18671 18672 /** 18673 * @private 18674 */ 18675 DurationFmt.prototype._mapDigits = function(str) { 18676 if (this.useNative && this.digits) { 18677 return JSUtils.mapString(str.toString(), this.digits); 18678 } 18679 return str; 18680 }; 18681 18682 /** 18683 * @private 18684 * @param {LocaleInfo} locinfo 18685 * @param {function(DurationFmt)|undefined} onLoad 18686 */ 18687 DurationFmt.prototype._init = function(locinfo, onLoad) { 18688 var digits; 18689 var scriptInfo = new ScriptInfo(locinfo.getScript()); 18690 this.scriptDirection = scriptInfo.getScriptDirection(); 18691 18692 if (typeof(this.useNative) === 'boolean') { 18693 // if the caller explicitly said to use native or not, honour that despite what the locale data says... 18694 if (this.useNative) { 18695 digits = locinfo.getNativeDigits(); 18696 if (digits) { 18697 this.digits = digits; 18698 } 18699 } 18700 } else if (locinfo.getDigitsStyle() === "native") { 18701 // else if the locale usually uses native digits, then use them 18702 digits = locinfo.getNativeDigits(); 18703 if (digits) { 18704 this.useNative = true; 18705 this.digits = digits; 18706 } 18707 } // else use western digits always 18708 18709 if (typeof(onLoad) === 'function') { 18710 onLoad(this); 18711 } 18712 }; 18713 18714 /** 18715 * Format a duration according to the format template of this formatter instance.<p> 18716 * 18717 * The components parameter should be an object that contains any or all of these 18718 * numeric properties: 18719 * 18720 * <ul> 18721 * <li>year 18722 * <li>month 18723 * <li>week 18724 * <li>day 18725 * <li>hour 18726 * <li>minute 18727 * <li>second 18728 * </ul> 18729 * <p> 18730 * 18731 * When a property is left out of the components parameter or has a value of 0, it will not 18732 * be formatted into the output string, except for times that include 0 minutes and 0 seconds. 18733 * 18734 * This formatter will not ensure that numbers for each component property is within the 18735 * valid range for that component. This allows you to format durations that are longer 18736 * than normal range. For example, you could format a duration has being "33 hours" rather 18737 * than "1 day, 9 hours". 18738 * 18739 * @param {Object} components date/time components to be formatted into a duration string 18740 * @return {IString} a string with the duration formatted according to the style and 18741 * locale set up for this formatter instance. If the components parameter is empty or 18742 * undefined, an empty string is returned. 18743 */ 18744 DurationFmt.prototype.format = function (components) { 18745 var i, list, temp, fmt, secondlast = true, str = ""; 18746 18747 list = DurationFmt.complist[this.style]; 18748 //for (i = 0; i < list.length; i++) { 18749 for (i = list.length-1; i >= 0; i--) { 18750 //console.log("Now dealing with " + list[i]); 18751 if (typeof(components[list[i]]) !== 'undefined' && components[list[i]] != 0) { 18752 if (str.length > 0) { 18753 str = ((this.length === 'full' && secondlast) ? this.components.finalSeparator : this.components.separator) + str; 18754 secondlast = false; 18755 } 18756 str = this.components[list[i]].formatChoice(components[list[i]], {num: this._mapDigits(components[list[i]])}) + str; 18757 } 18758 } 18759 18760 if (this.style === 'clock') { 18761 if (typeof(components.hour) !== 'undefined') { 18762 fmt = (typeof(components.second) !== 'undefined') ? this.timeFmtHMS : this.timeFmtHM; 18763 } else { 18764 fmt = this.timeFmtMS; 18765 } 18766 18767 if (str.length > 0) { 18768 str += this.components.separator; 18769 } 18770 str += fmt._formatTemplate(components, fmt.templateArr); 18771 } 18772 18773 if (this.scriptDirection === 'rtl') { 18774 str = "\u200F" + str; 18775 } 18776 return new IString(str); 18777 }; 18778 18779 /** 18780 * Return the locale that was used to construct this duration formatter object. If the 18781 * locale was not given as parameter to the constructor, this method returns the default 18782 * locale of the system. 18783 * 18784 * @return {Locale} locale that this duration formatter was constructed with 18785 */ 18786 DurationFmt.prototype.getLocale = function () { 18787 return this.locale; 18788 }; 18789 18790 /** 18791 * Return the length that was used to construct this duration formatter object. If the 18792 * length was not given as parameter to the constructor, this method returns the default 18793 * length. Valid values are "short", "medium", "long", and "full". 18794 * 18795 * @return {string} length that this duration formatter was constructed with 18796 */ 18797 DurationFmt.prototype.getLength = function () { 18798 return this.length; 18799 }; 18800 18801 /** 18802 * Return the style that was used to construct this duration formatter object. Returns 18803 * one of "text" or "clock". 18804 * 18805 * @return {string} style that this duration formatter was constructed with 18806 */ 18807 DurationFmt.prototype.getStyle = function () { 18808 return this.style; 18809 }; 18810 18811 18812 18813 /*< isAlpha.js */ 18814 /* 18815 * ctype.islpha.js - Character type is alphabetic 18816 * 18817 * Copyright © 2012-2015, JEDLSoft 18818 * 18819 * Licensed under the Apache License, Version 2.0 (the "License"); 18820 * you may not use this file except in compliance with the License. 18821 * You may obtain a copy of the License at 18822 * 18823 * http://www.apache.org/licenses/LICENSE-2.0 18824 * 18825 * Unless required by applicable law or agreed to in writing, software 18826 * distributed under the License is distributed on an "AS IS" BASIS, 18827 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18828 * 18829 * See the License for the specific language governing permissions and 18830 * limitations under the License. 18831 */ 18832 18833 // !depends CType.js IString.js ilib.js 18834 18835 // !data ctype_l 18836 18837 18838 /** 18839 * Return whether or not the first character is alphabetic.<p> 18840 * 18841 * @static 18842 * @param {string|IString|number} ch character or code point to examine 18843 * @return {boolean} true if the first character is alphabetic. 18844 */ 18845 var isAlpha = function (ch) { 18846 var num; 18847 switch (typeof(ch)) { 18848 case 'number': 18849 num = ch; 18850 break; 18851 case 'string': 18852 num = IString.toCodePoint(ch, 0); 18853 break; 18854 case 'undefined': 18855 return false; 18856 default: 18857 num = ch._toCodePoint(0); 18858 break; 18859 } 18860 return CType._inRange(num, 'Lu', ilib.data.ctype_l) || 18861 CType._inRange(num, 'Ll', ilib.data.ctype_l) || 18862 CType._inRange(num, 'Lt', ilib.data.ctype_l) || 18863 CType._inRange(num, 'Lm', ilib.data.ctype_l) || 18864 CType._inRange(num, 'Lo', ilib.data.ctype_l); 18865 }; 18866 18867 /** 18868 * @protected 18869 * @param {boolean} sync 18870 * @param {Object|undefined} loadParams 18871 * @param {function(*)|undefined} onLoad 18872 */ 18873 isAlpha._init = function (sync, loadParams, onLoad) { 18874 CType._load("ctype_l", sync, loadParams, onLoad); 18875 }; 18876 18877 18878 /*< isAlnum.js */ 18879 /* 18880 * isAlnum.js - Character type is alphanumeric 18881 * 18882 * Copyright © 2012-2015, JEDLSoft 18883 * 18884 * Licensed under the Apache License, Version 2.0 (the "License"); 18885 * you may not use this file except in compliance with the License. 18886 * You may obtain a copy of the License at 18887 * 18888 * http://www.apache.org/licenses/LICENSE-2.0 18889 * 18890 * Unless required by applicable law or agreed to in writing, software 18891 * distributed under the License is distributed on an "AS IS" BASIS, 18892 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18893 * 18894 * See the License for the specific language governing permissions and 18895 * limitations under the License. 18896 */ 18897 18898 // !depends CType.js IString.js isAlpha.js isDigit.js 18899 18900 18901 /** 18902 * Return whether or not the first character is alphabetic or numeric.<p> 18903 * 18904 * @static 18905 * @param {string|IString|number} ch character or code point to examine 18906 * @return {boolean} true if the first character is alphabetic or numeric 18907 */ 18908 var isAlnum = function (ch) { 18909 var num; 18910 switch (typeof(ch)) { 18911 case 'number': 18912 num = ch; 18913 break; 18914 case 'string': 18915 num = IString.toCodePoint(ch, 0); 18916 break; 18917 case 'undefined': 18918 return false; 18919 default: 18920 num = ch._toCodePoint(0); 18921 break; 18922 } 18923 return isAlpha(num) || isDigit(num); 18924 }; 18925 18926 /** 18927 * @protected 18928 * @param {boolean} sync 18929 * @param {Object|undefined} loadParams 18930 * @param {function(*)|undefined} onLoad 18931 */ 18932 isAlnum._init = function (sync, loadParams, onLoad) { 18933 isAlpha._init(sync, loadParams, function () { 18934 isDigit._init(sync, loadParams, onLoad); 18935 }); 18936 }; 18937 18938 18939 18940 /*< isAscii.js */ 18941 /* 18942 * isAscii.js - Character type is ASCII 18943 * 18944 * Copyright © 2012-2015, JEDLSoft 18945 * 18946 * Licensed under the Apache License, Version 2.0 (the "License"); 18947 * you may not use this file except in compliance with the License. 18948 * You may obtain a copy of the License at 18949 * 18950 * http://www.apache.org/licenses/LICENSE-2.0 18951 * 18952 * Unless required by applicable law or agreed to in writing, software 18953 * distributed under the License is distributed on an "AS IS" BASIS, 18954 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18955 * 18956 * See the License for the specific language governing permissions and 18957 * limitations under the License. 18958 */ 18959 18960 // !depends CType.js IString.js ilib.js 18961 18962 // !data ctype 18963 18964 18965 /** 18966 * Return whether or not the first character is in the ASCII range.<p> 18967 * 18968 * @static 18969 * @param {string|IString|number} ch character or code point to examine 18970 * @return {boolean} true if the first character is in the ASCII range. 18971 */ 18972 var isAscii = function (ch) { 18973 var num; 18974 switch (typeof(ch)) { 18975 case 'number': 18976 num = ch; 18977 break; 18978 case 'string': 18979 num = IString.toCodePoint(ch, 0); 18980 break; 18981 case 'undefined': 18982 return false; 18983 default: 18984 num = ch._toCodePoint(0); 18985 break; 18986 } 18987 return CType._inRange(num, 'ascii', ilib.data.ctype); 18988 }; 18989 18990 /** 18991 * @protected 18992 * @param {boolean} sync 18993 * @param {Object|undefined} loadParams 18994 * @param {function(*)|undefined} onLoad 18995 */ 18996 isAscii._init = function (sync, loadParams, onLoad) { 18997 CType._init(sync, loadParams, onLoad); 18998 }; 18999 19000 19001 /*< isBlank.js */ 19002 /* 19003 * isBlank.js - Character type is blank 19004 * 19005 * Copyright © 2012-2015, JEDLSoft 19006 * 19007 * Licensed under the Apache License, Version 2.0 (the "License"); 19008 * you may not use this file except in compliance with the License. 19009 * You may obtain a copy of the License at 19010 * 19011 * http://www.apache.org/licenses/LICENSE-2.0 19012 * 19013 * Unless required by applicable law or agreed to in writing, software 19014 * distributed under the License is distributed on an "AS IS" BASIS, 19015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19016 * 19017 * See the License for the specific language governing permissions and 19018 * limitations under the License. 19019 */ 19020 19021 // !depends CType.js IString.js ilib.js 19022 19023 // !data ctype 19024 19025 19026 /** 19027 * Return whether or not the first character is a blank character.<p> 19028 * 19029 * @static 19030 * ie. a space or a tab. 19031 * @param {string|IString|number} ch character or code point to examine 19032 * @return {boolean} true if the first character is a blank character. 19033 */ 19034 var isBlank = function (ch) { 19035 var num; 19036 switch (typeof(ch)) { 19037 case 'number': 19038 num = ch; 19039 break; 19040 case 'string': 19041 num = IString.toCodePoint(ch, 0); 19042 break; 19043 case 'undefined': 19044 return false; 19045 default: 19046 num = ch._toCodePoint(0); 19047 break; 19048 } 19049 return CType._inRange(num, 'blank', ilib.data.ctype); 19050 }; 19051 19052 /** 19053 * @protected 19054 * @param {boolean} sync 19055 * @param {Object|undefined} loadParams 19056 * @param {function(*)|undefined} onLoad 19057 */ 19058 isBlank._init = function (sync, loadParams, onLoad) { 19059 CType._init(sync, loadParams, onLoad); 19060 }; 19061 19062 19063 /*< isCntrl.js */ 19064 /* 19065 * isCntrl.js - Character type is control character 19066 * 19067 * Copyright © 2012-2015, JEDLSoft 19068 * 19069 * Licensed under the Apache License, Version 2.0 (the "License"); 19070 * you may not use this file except in compliance with the License. 19071 * You may obtain a copy of the License at 19072 * 19073 * http://www.apache.org/licenses/LICENSE-2.0 19074 * 19075 * Unless required by applicable law or agreed to in writing, software 19076 * distributed under the License is distributed on an "AS IS" BASIS, 19077 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19078 * 19079 * See the License for the specific language governing permissions and 19080 * limitations under the License. 19081 */ 19082 19083 // !depends CType.js IString.js ilib.js 19084 19085 // !data ctype_c 19086 19087 19088 /** 19089 * Return whether or not the first character is a control character.<p> 19090 * 19091 * @static 19092 * @param {string|IString|number} ch character or code point to examine 19093 * @return {boolean} true if the first character is a control character. 19094 */ 19095 var isCntrl = function (ch) { 19096 var num; 19097 switch (typeof(ch)) { 19098 case 'number': 19099 num = ch; 19100 break; 19101 case 'string': 19102 num = IString.toCodePoint(ch, 0); 19103 break; 19104 case 'undefined': 19105 return false; 19106 default: 19107 num = ch._toCodePoint(0); 19108 break; 19109 } 19110 return CType._inRange(num, 'Cc', ilib.data.ctype_c); 19111 }; 19112 19113 /** 19114 * @protected 19115 * @param {boolean} sync 19116 * @param {Object|undefined} loadParams 19117 * @param {function(*)|undefined} onLoad 19118 */ 19119 isCntrl._init = function (sync, loadParams, onLoad) { 19120 CType._load("ctype_c", sync, loadParams, onLoad); 19121 }; 19122 19123 19124 /*< isGraph.js */ 19125 /* 19126 * isGraph.js - Character type is graph char 19127 * 19128 * Copyright © 2012-2015, JEDLSoft 19129 * 19130 * Licensed under the Apache License, Version 2.0 (the "License"); 19131 * you may not use this file except in compliance with the License. 19132 * You may obtain a copy of the License at 19133 * 19134 * http://www.apache.org/licenses/LICENSE-2.0 19135 * 19136 * Unless required by applicable law or agreed to in writing, software 19137 * distributed under the License is distributed on an "AS IS" BASIS, 19138 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19139 * 19140 * See the License for the specific language governing permissions and 19141 * limitations under the License. 19142 */ 19143 19144 // !depends IString.js isSpace.js isCntrl.js ilib.js 19145 19146 19147 /** 19148 * Return whether or not the first character is any printable character 19149 * other than space.<p> 19150 * 19151 * @static 19152 * @param {string|IString|number} ch character or code point to examine 19153 * @return {boolean} true if the first character is any printable character 19154 * other than space. 19155 */ 19156 var isGraph = function (ch) { 19157 var num; 19158 switch (typeof(ch)) { 19159 case 'number': 19160 num = ch; 19161 break; 19162 case 'string': 19163 num = IString.toCodePoint(ch, 0); 19164 break; 19165 case 'undefined': 19166 return false; 19167 default: 19168 num = ch._toCodePoint(0); 19169 break; 19170 } 19171 return typeof(ch) !== 'undefined' && ch.length > 0 && !isSpace(num) && !isCntrl(num); 19172 }; 19173 19174 /** 19175 * @protected 19176 * @param {boolean} sync 19177 * @param {Object|undefined} loadParams 19178 * @param {function(*)|undefined} onLoad 19179 */ 19180 isGraph._init = function (sync, loadParams, onLoad) { 19181 isSpace._init(sync, loadParams, function () { 19182 isCntrl._init(sync, loadParams, onLoad); 19183 }); 19184 }; 19185 19186 19187 19188 /*< isIdeo.js */ 19189 /* 19190 * CType.js - Character type definitions 19191 * 19192 * Copyright © 2012-2015, JEDLSoft 19193 * 19194 * Licensed under the Apache License, Version 2.0 (the "License"); 19195 * you may not use this file except in compliance with the License. 19196 * You may obtain a copy of the License at 19197 * 19198 * http://www.apache.org/licenses/LICENSE-2.0 19199 * 19200 * Unless required by applicable law or agreed to in writing, software 19201 * distributed under the License is distributed on an "AS IS" BASIS, 19202 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19203 * 19204 * See the License for the specific language governing permissions and 19205 * limitations under the License. 19206 */ 19207 19208 // !depends CType.js IString.js 19209 19210 // !data ctype 19211 19212 19213 /** 19214 * Return whether or not the first character is an ideographic character.<p> 19215 * 19216 * @static 19217 * @param {string|IString|number} ch character or code point to examine 19218 * @return {boolean} true if the first character is an ideographic character. 19219 */ 19220 var isIdeo = function (ch) { 19221 var num; 19222 switch (typeof(ch)) { 19223 case 'number': 19224 num = ch; 19225 break; 19226 case 'string': 19227 num = IString.toCodePoint(ch, 0); 19228 break; 19229 case 'undefined': 19230 return false; 19231 default: 19232 num = ch._toCodePoint(0); 19233 break; 19234 } 19235 19236 return CType._inRange(num, 'cjk', ilib.data.ctype) || 19237 CType._inRange(num, 'cjkradicals', ilib.data.ctype) || 19238 CType._inRange(num, 'enclosedcjk', ilib.data.ctype) || 19239 CType._inRange(num, 'cjkpunct', ilib.data.ctype) || 19240 CType._inRange(num, 'cjkcompatibility', ilib.data.ctype); 19241 }; 19242 19243 /** 19244 * @protected 19245 * @param {boolean} sync 19246 * @param {Object|undefined} loadParams 19247 * @param {function(*)|undefined} onLoad 19248 */ 19249 isIdeo._init = function (sync, loadParams, onLoad) { 19250 CType._init(sync, loadParams, onLoad); 19251 }; 19252 19253 19254 /*< isLower.js */ 19255 /* 19256 * isLower.js - Character type is lower case letter 19257 * 19258 * Copyright © 2012-2015, JEDLSoft 19259 * 19260 * Licensed under the Apache License, Version 2.0 (the "License"); 19261 * you may not use this file except in compliance with the License. 19262 * You may obtain a copy of the License at 19263 * 19264 * http://www.apache.org/licenses/LICENSE-2.0 19265 * 19266 * Unless required by applicable law or agreed to in writing, software 19267 * distributed under the License is distributed on an "AS IS" BASIS, 19268 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19269 * 19270 * See the License for the specific language governing permissions and 19271 * limitations under the License. 19272 */ 19273 19274 // !depends CType.js IString.js 19275 19276 // !data ctype_l 19277 19278 19279 19280 /** 19281 * Return whether or not the first character is lower-case. For alphabetic 19282 * characters in scripts that do not make a distinction between upper- and 19283 * lower-case, this function always returns true.<p> 19284 * 19285 * @static 19286 * @param {string|IString|number} ch character or code point to examine 19287 * @return {boolean} true if the first character is lower-case. 19288 */ 19289 var isLower = function (ch) { 19290 var num; 19291 switch (typeof(ch)) { 19292 case 'number': 19293 num = ch; 19294 break; 19295 case 'string': 19296 num = IString.toCodePoint(ch, 0); 19297 break; 19298 case 'undefined': 19299 return false; 19300 default: 19301 num = ch._toCodePoint(0); 19302 break; 19303 } 19304 19305 return CType._inRange(num, 'Ll', ilib.data.ctype_l); 19306 }; 19307 19308 /** 19309 * @protected 19310 * @param {boolean} sync 19311 * @param {Object|undefined} loadParams 19312 * @param {function(*)|undefined} onLoad 19313 */ 19314 isLower._init = function (sync, loadParams, onLoad) { 19315 CType._load("ctype_l", sync, loadParams, onLoad); 19316 }; 19317 19318 19319 /*< isPrint.js */ 19320 /* 19321 * isPrint.js - Character type is printable char 19322 * 19323 * Copyright © 2012-2015, JEDLSoft 19324 * 19325 * Licensed under the Apache License, Version 2.0 (the "License"); 19326 * you may not use this file except in compliance with the License. 19327 * You may obtain a copy of the License at 19328 * 19329 * http://www.apache.org/licenses/LICENSE-2.0 19330 * 19331 * Unless required by applicable law or agreed to in writing, software 19332 * distributed under the License is distributed on an "AS IS" BASIS, 19333 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19334 * 19335 * See the License for the specific language governing permissions and 19336 * limitations under the License. 19337 */ 19338 19339 // !depends CType.js isCntrl.js 19340 19341 19342 /** 19343 * Return whether or not the first character is any printable character, 19344 * including space.<p> 19345 * 19346 * @static 19347 * @param {string|IString|number} ch character or code point to examine 19348 * @return {boolean} true if the first character is printable. 19349 */ 19350 var isPrint = function (ch) { 19351 return typeof(ch) !== 'undefined' && ch.length > 0 && !isCntrl(ch); 19352 }; 19353 19354 /** 19355 * @protected 19356 * @param {boolean} sync 19357 * @param {Object|undefined} loadParams 19358 * @param {function(*)|undefined} onLoad 19359 */ 19360 isPrint._init = function (sync, loadParams, onLoad) { 19361 isCntrl._init(sync, loadParams, onLoad); 19362 }; 19363 19364 19365 /*< isPunct.js */ 19366 /* 19367 * isPunct.js - Character type is punctuation 19368 * 19369 * Copyright © 2012-2015, JEDLSoft 19370 * 19371 * Licensed under the Apache License, Version 2.0 (the "License"); 19372 * you may not use this file except in compliance with the License. 19373 * You may obtain a copy of the License at 19374 * 19375 * http://www.apache.org/licenses/LICENSE-2.0 19376 * 19377 * Unless required by applicable law or agreed to in writing, software 19378 * distributed under the License is distributed on an "AS IS" BASIS, 19379 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19380 * 19381 * See the License for the specific language governing permissions and 19382 * limitations under the License. 19383 */ 19384 19385 // !depends CType.js IString.js 19386 19387 // !data ctype_p 19388 19389 19390 19391 /** 19392 * Return whether or not the first character is punctuation.<p> 19393 * 19394 * @static 19395 * @param {string|IString|number} ch character or code point to examine 19396 * @return {boolean} true if the first character is punctuation. 19397 */ 19398 var isPunct = function (ch) { 19399 var num; 19400 switch (typeof(ch)) { 19401 case 'number': 19402 num = ch; 19403 break; 19404 case 'string': 19405 num = IString.toCodePoint(ch, 0); 19406 break; 19407 case 'undefined': 19408 return false; 19409 default: 19410 num = ch._toCodePoint(0); 19411 break; 19412 } 19413 19414 return CType._inRange(num, 'Pd', ilib.data.ctype_p) || 19415 CType._inRange(num, 'Ps', ilib.data.ctype_p) || 19416 CType._inRange(num, 'Pe', ilib.data.ctype_p) || 19417 CType._inRange(num, 'Pc', ilib.data.ctype_p) || 19418 CType._inRange(num, 'Po', ilib.data.ctype_p) || 19419 CType._inRange(num, 'Pi', ilib.data.ctype_p) || 19420 CType._inRange(num, 'Pf', ilib.data.ctype_p); 19421 }; 19422 19423 /** 19424 * @protected 19425 * @param {boolean} sync 19426 * @param {Object|undefined} loadParams 19427 * @param {function(*)|undefined} onLoad 19428 */ 19429 isPunct._init = function (sync, loadParams, onLoad) { 19430 CType._load("ctype_p", sync, loadParams, onLoad); 19431 }; 19432 19433 19434 19435 /*< isUpper.js */ 19436 /* 19437 * isUpper.js - Character type is upper-case letter 19438 * 19439 * Copyright © 2012-2015, JEDLSoft 19440 * 19441 * Licensed under the Apache License, Version 2.0 (the "License"); 19442 * you may not use this file except in compliance with the License. 19443 * You may obtain a copy of the License at 19444 * 19445 * http://www.apache.org/licenses/LICENSE-2.0 19446 * 19447 * Unless required by applicable law or agreed to in writing, software 19448 * distributed under the License is distributed on an "AS IS" BASIS, 19449 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19450 * 19451 * See the License for the specific language governing permissions and 19452 * limitations under the License. 19453 */ 19454 19455 // !depends CType.js IString.js 19456 19457 // !data ctype_l 19458 19459 19460 19461 /** 19462 * Return whether or not the first character is upper-case. For alphabetic 19463 * characters in scripts that do not make a distinction between upper- and 19464 * lower-case, this function always returns true.<p> 19465 * 19466 * @static 19467 * @param {string|IString|number} ch character or code point to examine 19468 * @return {boolean} true if the first character is upper-case. 19469 */ 19470 var isUpper = function (ch) { 19471 var num; 19472 switch (typeof(ch)) { 19473 case 'number': 19474 num = ch; 19475 break; 19476 case 'string': 19477 num = IString.toCodePoint(ch, 0); 19478 break; 19479 case 'undefined': 19480 return false; 19481 default: 19482 num = ch._toCodePoint(0); 19483 break; 19484 } 19485 19486 return CType._inRange(num, 'Lu', ilib.data.ctype_l); 19487 }; 19488 19489 /** 19490 * @protected 19491 * @param {boolean} sync 19492 * @param {Object|undefined} loadParams 19493 * @param {function(*)|undefined} onLoad 19494 */ 19495 isUpper._init = function (sync, loadParams, onLoad) { 19496 CType._load("ctype_l", sync, loadParams, onLoad); 19497 }; 19498 19499 19500 /*< isXdigit.js */ 19501 /* 19502 * isXdigit.js - Character type is hex digit 19503 * 19504 * Copyright © 2012-2015, JEDLSoft 19505 * 19506 * Licensed under the Apache License, Version 2.0 (the "License"); 19507 * you may not use this file except in compliance with the License. 19508 * You may obtain a copy of the License at 19509 * 19510 * http://www.apache.org/licenses/LICENSE-2.0 19511 * 19512 * Unless required by applicable law or agreed to in writing, software 19513 * distributed under the License is distributed on an "AS IS" BASIS, 19514 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19515 * 19516 * See the License for the specific language governing permissions and 19517 * limitations under the License. 19518 */ 19519 19520 // !depends CType.js IString.js 19521 19522 // !data ctype 19523 19524 19525 19526 /** 19527 * Return whether or not the first character is a hexadecimal digit written 19528 * in the Latin script. (0-9 or A-F)<p> 19529 * 19530 * @static 19531 * @param {string|IString|number} ch character or code point to examine 19532 * @return {boolean} true if the first character is a hexadecimal digit written 19533 * in the Latin script. 19534 */ 19535 var isXdigit = function (ch) { 19536 var num; 19537 switch (typeof(ch)) { 19538 case 'number': 19539 num = ch; 19540 break; 19541 case 'string': 19542 num = IString.toCodePoint(ch, 0); 19543 break; 19544 case 'undefined': 19545 return false; 19546 default: 19547 num = ch._toCodePoint(0); 19548 break; 19549 } 19550 19551 return CType._inRange(num, 'xdigit', ilib.data.ctype); 19552 }; 19553 19554 /** 19555 * @protected 19556 * @param {boolean} sync 19557 * @param {Object|undefined} loadParams 19558 * @param {function(*)|undefined} onLoad 19559 */ 19560 isXdigit._init = function (sync, loadParams, onLoad) { 19561 CType._init(sync, loadParams, onLoad); 19562 }; 19563 19564 19565 /*< isScript.js */ 19566 /* 19567 * isScript.js - Character type is script 19568 * 19569 * Copyright © 2012-2015, JEDLSoft 19570 * 19571 * Licensed under the Apache License, Version 2.0 (the "License"); 19572 * you may not use this file except in compliance with the License. 19573 * You may obtain a copy of the License at 19574 * 19575 * http://www.apache.org/licenses/LICENSE-2.0 19576 * 19577 * Unless required by applicable law or agreed to in writing, software 19578 * distributed under the License is distributed on an "AS IS" BASIS, 19579 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19580 * 19581 * See the License for the specific language governing permissions and 19582 * limitations under the License. 19583 */ 19584 19585 // !depends CType.js IString.js 19586 19587 // !data scriptToRange 19588 19589 19590 19591 /** 19592 * Return whether or not the first character in the given string is 19593 * in the given script. The script is given as the 4-letter ISO 19594 * 15924 script code.<p> 19595 * 19596 * @static 19597 * @param {string|IString|number} ch character or code point to examine 19598 * @param {string} script the 4-letter ISO 15924 to query against 19599 * @return {boolean} true if the first character is in the given script, and 19600 * false otherwise 19601 */ 19602 var isScript = function (ch, script) { 19603 var num; 19604 switch (typeof(ch)) { 19605 case 'number': 19606 num = ch; 19607 break; 19608 case 'string': 19609 num = IString.toCodePoint(ch, 0); 19610 break; 19611 case 'undefined': 19612 return false; 19613 default: 19614 num = ch._toCodePoint(0); 19615 break; 19616 } 19617 19618 return CType._inRange(num, script, ilib.data.scriptToRange); 19619 }; 19620 19621 /** 19622 * @protected 19623 * @param {boolean} sync 19624 * @param {Object|undefined} loadParams 19625 * @param {function(*)|undefined} onLoad 19626 */ 19627 isScript._init = function (sync, loadParams, onLoad) { 19628 CType._load("scriptToRange", sync, loadParams, onLoad); 19629 }; 19630 19631 19632 /*< ScriptInfo.js */ 19633 /* 19634 * ScriptInfo.js - information about scripts 19635 * 19636 * Copyright © 2012-2017, JEDLSoft 19637 * 19638 * Licensed under the Apache License, Version 2.0 (the "License"); 19639 * you may not use this file except in compliance with the License. 19640 * You may obtain a copy of the License at 19641 * 19642 * http://www.apache.org/licenses/LICENSE-2.0 19643 * 19644 * Unless required by applicable law or agreed to in writing, software 19645 * distributed under the License is distributed on an "AS IS" BASIS, 19646 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19647 * 19648 * See the License for the specific language governing permissions and 19649 * limitations under the License. 19650 */ 19651 19652 // !depends ilib.js Utils.js 19653 19654 // !data scripts 19655 19656 19657 /** 19658 * @class 19659 * Create a new script info instance. This class encodes information about 19660 * scripts, which are sets of characters used in a writing system.<p> 19661 * 19662 * The options object may contain any of the following properties: 19663 * 19664 * <ul> 19665 * <li><i>onLoad</i> - a callback function to call when the script info object is fully 19666 * loaded. When the onLoad option is given, the script info object will attempt to 19667 * load any missing locale data using the ilib loader callback. 19668 * When the constructor is done (even if the data is already preassembled), the 19669 * onLoad function is called with the current instance as a parameter, so this 19670 * callback can be used with preassembled or dynamic loading or a mix of the two. 19671 * 19672 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 19673 * asynchronously. If this option is given as "false", then the "onLoad" 19674 * callback must be given, as the instance returned from this constructor will 19675 * not be usable for a while. 19676 * 19677 * <li><i>loadParams</i> - an object containing parameters to pass to the 19678 * loader callback function when locale data is missing. The parameters are not 19679 * interpretted or modified in any way. They are simply passed along. The object 19680 * may contain any property/value pairs as long as the calling code is in 19681 * agreement with the loader callback function as to what those parameters mean. 19682 * </ul> 19683 * 19684 * 19685 * @constructor 19686 * @param {string} script The ISO 15924 4-letter identifier for the script 19687 * @param {Object=} options parameters to initialize this instance 19688 */ 19689 var ScriptInfo = function(script, options) { 19690 var sync = true, 19691 loadParams = undefined; 19692 19693 this.script = script; 19694 19695 if (options) { 19696 if (typeof(options.sync) !== 'undefined') { 19697 sync = (options.sync == true); 19698 } 19699 19700 if (typeof(options.loadParams) !== 'undefined') { 19701 loadParams = options.loadParams; 19702 } 19703 } 19704 19705 if (!ScriptInfo.cache) { 19706 ScriptInfo.cache = {}; 19707 } 19708 19709 if (!ilib.data.scripts) { 19710 Utils.loadData({ 19711 object: ScriptInfo, 19712 locale: "-", 19713 name: "scripts.json", 19714 sync: sync, 19715 loadParams: loadParams, 19716 callback: ilib.bind(this, function (info) { 19717 if (!info) { 19718 info = {"Latn":{"nb":215,"nm":"Latin","lid":"Latin","rtl":false,"ime":false,"casing":true}}; 19719 var spec = this.locale.getSpec().replace(/-/g, "_"); 19720 ScriptInfo.cache[spec] = info; 19721 } 19722 ilib.data.scripts = info; 19723 this.info = script && ilib.data.scripts[script]; 19724 if (options && typeof(options.onLoad) === 'function') { 19725 options.onLoad(this); 19726 } 19727 }) 19728 }); 19729 } else { 19730 this.info = ilib.data.scripts[script]; 19731 } 19732 19733 }; 19734 19735 /** 19736 * @private 19737 */ 19738 ScriptInfo._getScriptsArray = function() { 19739 var ret = [], 19740 script = undefined, 19741 scripts = ilib.data.scripts; 19742 19743 for (script in scripts) { 19744 if (script && scripts[script]) { 19745 ret.push(script); 19746 } 19747 } 19748 19749 return ret; 19750 }; 19751 19752 /** 19753 * Return an array of all ISO 15924 4-letter identifier script identifiers that 19754 * this copy of ilib knows about. 19755 * @static 19756 * @param {boolean} sync whether to find the available ids synchronously (true) or asynchronously (false) 19757 * @param {Object} loadParams arbitrary object full of properties to pass to the loader 19758 * @param {function(Array.<string>)} onLoad callback function to call when the data is finished loading 19759 * @return {Array.<string>} an array of all script identifiers that this copy of 19760 * ilib knows about 19761 */ 19762 ScriptInfo.getAllScripts = function(sync, loadParams, onLoad) { 19763 if (!ilib.data.scripts) { 19764 Utils.loadData({ 19765 object: ScriptInfo, 19766 locale: "-", 19767 name: "scripts.json", 19768 sync: sync, 19769 loadParams: loadParams, 19770 callback: ilib.bind(this, function (info) { 19771 ilib.data.scripts = info; 19772 19773 if (typeof(onLoad) === 'function') { 19774 onLoad(ScriptInfo._getScriptsArray()); 19775 } 19776 }) 19777 }); 19778 } 19779 19780 return ScriptInfo._getScriptsArray(); 19781 }; 19782 19783 ScriptInfo.prototype = { 19784 /** 19785 * Return the 4-letter ISO 15924 identifier associated 19786 * with this script. 19787 * @return {string} the 4-letter ISO code for this script 19788 */ 19789 getCode: function () { 19790 return this.info && this.script; 19791 }, 19792 19793 /** 19794 * Get the ISO 15924 code number associated with this 19795 * script. 19796 * 19797 * @return {number} the ISO 15924 code number 19798 */ 19799 getCodeNumber: function () { 19800 return this.info && this.info.nb || 0; 19801 }, 19802 19803 /** 19804 * Get the name of this script in English. 19805 * 19806 * @return {string} the name of this script in English 19807 */ 19808 getName: function () { 19809 return this.info && this.info.nm; 19810 }, 19811 19812 /** 19813 * Get the long identifier assciated with this script. 19814 * 19815 * @return {string} the long identifier of this script 19816 */ 19817 getLongCode: function () { 19818 return this.info && this.info.lid; 19819 }, 19820 19821 /** 19822 * Return the usual direction that text in this script is written 19823 * in. Possible return values are "rtl" for right-to-left, 19824 * "ltr" for left-to-right, and "ttb" for top-to-bottom. 19825 * 19826 * @return {string} the usual direction that text in this script is 19827 * written in 19828 */ 19829 getScriptDirection: function() { 19830 return (this.info && typeof(this.info.rtl) !== 'undefined' && this.info.rtl) ? "rtl" : "ltr"; 19831 }, 19832 19833 /** 19834 * Return true if this script typically requires an input method engine 19835 * to enter its characters. 19836 * 19837 * @return {boolean} true if this script typically requires an IME 19838 */ 19839 getNeedsIME: function () { 19840 return this.info && this.info.ime ? true : false; // converts undefined to false 19841 }, 19842 19843 /** 19844 * Return true if this script uses lower- and upper-case characters. 19845 * 19846 * @return {boolean} true if this script uses letter case 19847 */ 19848 getCasing: function () { 19849 return this.info && this.info.casing ? true : false; // converts undefined to false 19850 } 19851 }; 19852 19853 19854 /*< Name.js */ 19855 /* 19856 * Name.js - Person name parser 19857 * 19858 * Copyright © 2013-2015, JEDLSoft 19859 * 19860 * Licensed under the Apache License, Version 2.0 (the "License"); 19861 * you may not use this file except in compliance with the License. 19862 * You may obtain a copy of the License at 19863 * 19864 * http://www.apache.org/licenses/LICENSE-2.0 19865 * 19866 * Unless required by applicable law or agreed to in writing, software 19867 * distributed under the License is distributed on an "AS IS" BASIS, 19868 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19869 * 19870 * See the License for the specific language governing permissions and 19871 * limitations under the License. 19872 */ 19873 19874 /* !depends 19875 ilib.js 19876 Locale.js 19877 Utils.js 19878 isAlpha.js 19879 isIdeo.js 19880 isPunct.js 19881 isSpace.js 19882 JSUtils.js 19883 IString.js 19884 */ 19885 19886 // !data name 19887 19888 // notes: 19889 // icelandic given names: http://en.wiktionary.org/wiki/Appendix:Icelandic_given_names 19890 // danish approved given names: http://www.familiestyrelsen.dk/samliv/navne/ 19891 // http://www.mentalfloss.com/blogs/archives/59277 19892 // other countries with first name restrictions: Norway, China, New Zealand, Japan, Sweden, Germany, Hungary 19893 19894 19895 19896 /** 19897 * @class 19898 * A class to parse names of people. Different locales have different conventions when it 19899 * comes to naming people.<p> 19900 * 19901 * The options can contain any of the following properties: 19902 * 19903 * <ul> 19904 * <li><i>locale</i> - use the rules and conventions of the given locale in order to parse 19905 * the name 19906 * <li><i>style</i> - explicitly use the named style to parse the name. Valid values so 19907 * far are "western" and "asian". If this property is not specified, then the style will 19908 * be gleaned from the name itself. This class will count the total number of Latin or Asian 19909 * characters. If the majority of the characters are in one style, that style will be 19910 * used to parse the whole name. 19911 * <li><i>order</i> - explicitly use the given order for names. In some locales, such 19912 * as Russian, names may be written equally validly as "givenName familyName" or "familyName 19913 * givenName". This option tells the parser which order to prefer, and overrides the 19914 * default order for the locale. Valid values are "gf" (given-family) or "fg" (family-given). 19915 * <li><i>useSpaces</i> - explicitly specifies whether to use spaces or not between the given name , middle name 19916 * and family name. 19917 * <li>onLoad - a callback function to call when the name info is fully 19918 * loaded and the name has been parsed. When the onLoad option is given, the name object 19919 * will attempt to load any missing locale data using the ilib loader callback. 19920 * When the constructor is done (even if the data is already preassembled), the 19921 * onLoad function is called with the current instance as a parameter, so this 19922 * callback can be used with preassembled or dynamic loading or a mix of the two. 19923 * 19924 * <li>sync - tell whether to load any missing locale data synchronously or 19925 * asynchronously. If this option is given as "false", then the "onLoad" 19926 * callback must be given, as the instance returned from this constructor will 19927 * not be usable for a while. 19928 * 19929 * <li><i>loadParams</i> - an object containing parameters to pass to the 19930 * loader callback function when locale data is missing. The parameters are not 19931 * interpretted or modified in any way. They are simply passed along. The object 19932 * may contain any property/value pairs as long as the calling code is in 19933 * agreement with the loader callback function as to what those parameters mean. 19934 * </ul> 19935 * 19936 * When the parser has completed its parsing, it fills in the fields listed below.<p> 19937 * 19938 * For names that include auxilliary words, such as the family name "van der Heijden", all 19939 * of the auxilliary words ("van der") will be included in the field.<p> 19940 * 19941 * For names in Spanish locales, it is assumed that the family name is doubled. That is, 19942 * a person may have a paternal family name followed by a maternal family name. All 19943 * family names will be listed in the familyName field as normal, separated by spaces. 19944 * When formatting the short version of such names, only the paternal family name will 19945 * be used. 19946 * 19947 * 19948 * @constructor 19949 * @param {string|Name=} name the name to parse 19950 * @param {Object=} options Options governing the construction of this name instance 19951 */ 19952 var Name = function (name, options) { 19953 var sync = true; 19954 19955 if (!name || name.length === 0) { 19956 return; 19957 } 19958 19959 this.loadParams = {}; 19960 19961 if (options) { 19962 if (options.locale) { 19963 this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; 19964 } 19965 19966 if (options.style && (options.style === "asian" || options.style === "western")) { 19967 this.style = options.style; 19968 } 19969 19970 if (options.order && (options.order === "gmf" || options.order === "fmg" || options.order === "fgm")) { 19971 this.order = options.order; 19972 } 19973 19974 if (typeof (options.sync) !== 'undefined') { 19975 sync = (options.sync == true); 19976 } 19977 19978 if (typeof (options.loadParams) !== 'undefined') { 19979 this.loadParams = options.loadParams; 19980 } 19981 } 19982 19983 if (!Name.cache) { 19984 Name.cache = {}; 19985 } 19986 19987 this.locale = this.locale || new Locale(); 19988 19989 isAlpha._init(sync, this.loadParams, ilib.bind(this, function() { 19990 isIdeo._init(sync, this.loadParams, ilib.bind(this, function() { 19991 isPunct._init(sync, this.loadParams, ilib.bind(this, function() { 19992 isSpace._init(sync, this.loadParams, ilib.bind(this, function() { 19993 Utils.loadData({ 19994 object: Name, 19995 locale: this.locale, 19996 name: "name.json", 19997 sync: sync, 19998 loadParams: this.loadParams, 19999 callback: ilib.bind(this, function (info) { 20000 if (!info) { 20001 info = Name.defaultInfo; 20002 var spec = this.locale.getSpec().replace(/-/g, "_"); 20003 Name.cache[spec] = info; 20004 } 20005 if (typeof (name) === 'object') { 20006 // copy constructor 20007 /** 20008 * The prefixes for this name 20009 * @type {string|Array.<string>} 20010 */ 20011 this.prefix = name.prefix; 20012 /** 20013 * The given (personal) name in this name. 20014 * @type {string|Array.<string>} 20015 */ 20016 this.givenName = name.givenName; 20017 /** 20018 * The middle names used in this name. If there are multiple middle names, they all 20019 * appear in this field separated by spaces. 20020 * @type {string|Array.<string>} 20021 */ 20022 this.middleName = name.middleName; 20023 /** 20024 * The family names in this name. If there are multiple family names, they all 20025 * appear in this field separated by spaces. 20026 * @type {string|Array.<string>} 20027 */ 20028 this.familyName = name.familyName; 20029 /** 20030 * The suffixes for this name. If there are multiple suffixes, they all 20031 * appear in this field separated by spaces. 20032 * @type {string|Array.<string>} 20033 */ 20034 this.suffix = name.suffix; 20035 20036 // private properties 20037 this.locale = name.locale; 20038 this.style = name.style; 20039 this.order = name.order; 20040 this.useSpaces = name.useSpaces; 20041 this.isAsianName = name.isAsianName; 20042 return; 20043 } 20044 /** 20045 * @type {{ 20046 * nameStyle:string, 20047 * order:string, 20048 * prefixes:Array.<string>, 20049 * suffixes:Array.<string>, 20050 * auxillaries:Array.<string>, 20051 * honorifics:Array.<string>, 20052 * knownFamilyNames:Array.<string>, 20053 * noCompoundFamilyNames:boolean, 20054 * sortByHeadWord:boolean 20055 * }} */ 20056 this.info = info; 20057 this._init(name); 20058 if (options && typeof(options.onLoad) === 'function') { 20059 options.onLoad(this); 20060 } 20061 }) 20062 }); 20063 })); 20064 })); 20065 })); 20066 })); 20067 }; 20068 20069 Name.defaultInfo = ilib.data.name || { 20070 "components": { 20071 "short": { 20072 "g": 1, 20073 "f": 1 20074 }, 20075 "medium": { 20076 "g": 1, 20077 "m": 1, 20078 "f": 1 20079 }, 20080 "long": { 20081 "p": 1, 20082 "g": 1, 20083 "m": 1, 20084 "f": 1 20085 }, 20086 "full": { 20087 "p": 1, 20088 "g": 1, 20089 "m": 1, 20090 "f": 1, 20091 "s": 1 20092 } 20093 }, 20094 "format": "{prefix} {givenName} {middleName} {familyName}{suffix}", 20095 "sortByHeadWord": false, 20096 "nameStyle": "western", 20097 "conjunctions": { 20098 "and1": "and", 20099 "and2": "and", 20100 "or1": "or", 20101 "or2": "or" 20102 }, 20103 "auxillaries": { 20104 "von": 1, 20105 "von der": 1, 20106 "von den": 1, 20107 "van": 1, 20108 "van der": 1, 20109 "van de": 1, 20110 "van den": 1, 20111 "de": 1, 20112 "di": 1, 20113 "la": 1, 20114 "lo": 1, 20115 "des": 1, 20116 "le": 1, 20117 "les": 1, 20118 "du": 1, 20119 "de la": 1, 20120 "del": 1, 20121 "de los": 1, 20122 "de las": 1 20123 }, 20124 "prefixes": [ 20125 "doctor", 20126 "dr", 20127 "mr", 20128 "mrs", 20129 "ms", 20130 "mister", 20131 "madame", 20132 "madamoiselle", 20133 "miss", 20134 "monsieur", 20135 "señor", 20136 "señora", 20137 "señorita" 20138 ], 20139 "suffixes": [ 20140 ",", 20141 "junior", 20142 "jr", 20143 "senior", 20144 "sr", 20145 "i", 20146 "ii", 20147 "iii", 20148 "esq", 20149 "phd", 20150 "md" 20151 ], 20152 "patronymicName":[ ], 20153 "familyNames":[ ] 20154 }; 20155 20156 /** 20157 * Return true if the given character is in the range of the Han, Hangul, or kana 20158 * scripts. 20159 * @static 20160 * @protected 20161 */ 20162 Name._isAsianChar = function(c) { 20163 return isIdeo(c) || 20164 CType.withinRange(c, "hangul") || 20165 CType.withinRange(c, "hiragana") || 20166 CType.withinRange(c, "katakana"); 20167 }; 20168 20169 20170 /** 20171 * @static 20172 * @protected 20173 */ 20174 Name._isAsianName = function (name, language) { 20175 // the idea is to count the number of asian chars and the number 20176 // of latin chars. If one is greater than the other, choose 20177 // that style. 20178 var asian = 0, 20179 latin = 0, 20180 i; 20181 20182 if (name && name.length > 0) { 20183 for (i = 0; i < name.length; i++) { 20184 var c = name.charAt(i); 20185 20186 if (Name._isAsianChar(c)) { 20187 if (language =="ko" || language =="ja" || language =="zh") { 20188 return true; 20189 } 20190 asian++; 20191 } else if (isAlpha(c)) { 20192 if (!language =="ko" || !language =="ja" || !language =="zh") { 20193 return false; 20194 } 20195 latin++; 20196 } 20197 } 20198 20199 return latin < asian; 20200 } 20201 20202 return false; 20203 }; 20204 20205 /** 20206 * Return true if any Latin letters are found in the string. Return 20207 * false if all the characters are non-Latin. 20208 * @static 20209 * @protected 20210 */ 20211 Name._isEuroName = function (name, language) { 20212 var c, 20213 n = new IString(name), 20214 it = n.charIterator(); 20215 20216 while (it.hasNext()) { 20217 c = it.next(); 20218 20219 if (!Name._isAsianChar(c) && !isPunct(c) && !isSpace(c)) { 20220 return true; 20221 } else if (Name._isAsianChar(c) && (language =="ko" || language =="ja" || language =="zh")) { 20222 return false; 20223 } 20224 } 20225 return false; 20226 }; 20227 20228 Name.prototype = { 20229 /** 20230 * @protected 20231 */ 20232 _init: function (name) { 20233 var parts, prefixArray, prefix, prefixLower, 20234 suffixArray, suffix, suffixLower, 20235 i, info, hpSuffix; 20236 var currentLanguage = this.locale.getLanguage(); 20237 20238 if (name) { 20239 // for DFISH-12905, pick off the part that the LDAP server automatically adds to our names in HP emails 20240 i = name.search(/\s*[,\/\(\[\{<]/); 20241 if (i !== -1) { 20242 hpSuffix = name.substring(i); 20243 hpSuffix = hpSuffix.replace(/\s+/g, ' '); // compress multiple whitespaces 20244 suffixArray = hpSuffix.split(" "); 20245 var conjunctionIndex = this._findLastConjunction(suffixArray); 20246 if (conjunctionIndex > -1) { 20247 // it's got conjunctions in it, so this is not really a suffix 20248 hpSuffix = undefined; 20249 } else { 20250 name = name.substring(0, i); 20251 } 20252 } 20253 20254 this.isAsianName = Name._isAsianName(name, currentLanguage); 20255 if (this.info.nameStyle === "asian" || this.info.order === "fmg" || this.info.order === "fgm") { 20256 info = this.isAsianName ? this.info : ilib.data.name; 20257 } else { 20258 info = this.isAsianName ? ilib.data.name : this.info; 20259 } 20260 20261 if (this.isAsianName) { 20262 // all-asian names 20263 if (this.useSpaces == false) { 20264 name = name.replace(/\s+/g, ''); // eliminate all whitespaces 20265 } 20266 parts = name.trim().split(''); 20267 } 20268 //} 20269 else { 20270 name = name.replace(/, /g, ' , '); 20271 name = name.replace(/\s+/g, ' '); // compress multiple whitespaces 20272 parts = name.trim().split(' '); 20273 } 20274 20275 // check for prefixes 20276 if (parts.length > 1) { 20277 for (i = parts.length; i > 0; i--) { 20278 prefixArray = parts.slice(0, i); 20279 prefix = prefixArray.join(this.isAsianName ? '' : ' '); 20280 prefixLower = prefix.toLowerCase(); 20281 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 20282 if (ilib.isArray(this.info.prefixes) && 20283 (JSUtils.indexOf(this.info.prefixes, prefixLower) > -1 || this._isConjunction(prefixLower))) { 20284 if (this.prefix) { 20285 if (!this.isAsianName) { 20286 this.prefix += ' '; 20287 } 20288 this.prefix += prefix; 20289 } else { 20290 this.prefix = prefix; 20291 } 20292 parts = parts.slice(i); 20293 i = parts.length; 20294 } 20295 } 20296 } 20297 // check for suffixes 20298 if (parts.length > 1) { 20299 for (i = parts.length; i > 0; i--) { 20300 suffixArray = parts.slice(-i); 20301 suffix = suffixArray.join(this.isAsianName ? '' : ' '); 20302 suffixLower = suffix.toLowerCase(); 20303 suffixLower = suffixLower.replace(/[\.]/g, ''); // ignore periods 20304 if (ilib.isArray(this.info.suffixes) && JSUtils.indexOf(this.info.suffixes, suffixLower) > -1) { 20305 if (this.suffix) { 20306 if (!this.isAsianName && !isPunct(this.suffix.charAt(0))) { 20307 this.suffix = ' ' + this.suffix; 20308 } 20309 this.suffix = suffix + this.suffix; 20310 } else { 20311 this.suffix = suffix; 20312 } 20313 parts = parts.slice(0, parts.length - i); 20314 i = parts.length; 20315 } 20316 } 20317 } 20318 20319 if (hpSuffix) { 20320 this.suffix = (this.suffix && this.suffix + hpSuffix) || hpSuffix; 20321 } 20322 20323 // adjoin auxillary words to their headwords 20324 if (parts.length > 1 && !this.isAsianName) { 20325 parts = this._joinAuxillaries(parts, this.isAsianName); 20326 } 20327 20328 if (this.isAsianName) { 20329 this._parseAsianName(parts, currentLanguage); 20330 } else { 20331 this._parseWesternName(parts); 20332 } 20333 20334 this._joinNameArrays(); 20335 } 20336 }, 20337 20338 /** 20339 * @return {number} 20340 * 20341 _findSequence: function(parts, hash, isAsian) { 20342 var sequence, sequenceLower, sequenceArray, aux = [], i, ret = {}; 20343 20344 if (parts.length > 0 && hash) { 20345 //console.info("_findSequence: finding sequences"); 20346 for (var start = 0; start < parts.length-1; start++) { 20347 for ( i = parts.length; i > start; i-- ) { 20348 sequenceArray = parts.slice(start, i); 20349 sequence = sequenceArray.join(isAsian ? '' : ' '); 20350 sequenceLower = sequence.toLowerCase(); 20351 sequenceLower = sequenceLower.replace(/[,\.]/g, ''); // ignore commas and periods 20352 20353 //console.info("_findSequence: checking sequence: '" + sequenceLower + "'"); 20354 20355 if ( sequenceLower in hash ) { 20356 ret.match = sequenceArray; 20357 ret.start = start; 20358 ret.end = i; 20359 return ret; 20360 //console.info("_findSequence: Found sequence '" + sequence + "' New parts list is " + JSON.stringify(parts)); 20361 } 20362 } 20363 } 20364 } 20365 20366 return undefined; 20367 }, 20368 */ 20369 20370 /** 20371 * @protected 20372 * @param {Array} parts 20373 * @param {Array} names 20374 * @param {boolean} isAsian 20375 * @param {boolean=} noCompoundPrefix 20376 */ 20377 _findPrefix: function (parts, names, isAsian, noCompoundPrefix) { 20378 var i, prefix, prefixLower, prefixArray, aux = []; 20379 20380 if (parts.length > 0 && names) { 20381 for (i = parts.length; i > 0; i--) { 20382 prefixArray = parts.slice(0, i); 20383 prefix = prefixArray.join(isAsian ? '' : ' '); 20384 prefixLower = prefix.toLowerCase(); 20385 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 20386 20387 if (prefixLower in names) { 20388 aux = aux.concat(isAsian ? prefix : prefixArray); 20389 if (noCompoundPrefix) { 20390 // don't need to parse further. Just return it as is. 20391 return aux; 20392 } 20393 parts = parts.slice(i); 20394 i = parts.length + 1; 20395 } 20396 } 20397 } 20398 20399 return aux; 20400 }, 20401 20402 /** 20403 * @protected 20404 */ 20405 _findSuffix: function (parts, names, isAsian) { 20406 var i, j, seq = ""; 20407 20408 for (i = 0; i < names.length; i++) { 20409 if (parts.length >= names[i].length) { 20410 j = 0; 20411 while (j < names[i].length && parts[parts.length - j] === names[i][names[i].length - j]) { 20412 j++; 20413 } 20414 if (j >= names[i].length) { 20415 seq = parts.slice(parts.length - j).join(isAsian ? "" : " ") + (isAsian ? "" : " ") + seq; 20416 parts = parts.slice(0, parts.length - j); 20417 i = -1; // restart the search 20418 } 20419 } 20420 } 20421 20422 this.suffix = seq; 20423 return parts; 20424 }, 20425 20426 /** 20427 * @protected 20428 * Tell whether or not the given word is a conjunction in this language. 20429 * @param {string} word the word to test 20430 * @return {boolean} true if the word is a conjunction 20431 */ 20432 _isConjunction: function _isConjunction(word) { 20433 return (this.info.conjunctions.and1 === word || 20434 this.info.conjunctions.and2 === word || 20435 this.info.conjunctions.or1 === word || 20436 this.info.conjunctions.or2 === word || 20437 ("&" === word) || 20438 ("+" === word)); 20439 }, 20440 20441 /** 20442 * Find the last instance of 'and' in the name 20443 * @protected 20444 * @param {Array.<string>} parts 20445 * @return {number} 20446 */ 20447 _findLastConjunction: function _findLastConjunction(parts) { 20448 var conjunctionIndex = -1, 20449 index, part; 20450 20451 for (index = 0; index < parts.length; index++) { 20452 part = parts[index]; 20453 if (typeof (part) === 'string') { 20454 part = part.toLowerCase(); 20455 // also recognize English 20456 if ("and" === part || "or" === part || "&" === part || "+" === part) { 20457 conjunctionIndex = index; 20458 } 20459 if (this._isConjunction(part)) { 20460 conjunctionIndex = index; 20461 } 20462 } 20463 } 20464 return conjunctionIndex; 20465 }, 20466 20467 /** 20468 * @protected 20469 * @param {Array.<string>} parts the current array of name parts 20470 * @param {boolean} isAsian true if the name is being parsed as an Asian name 20471 * @return {Array.<string>} the remaining parts after the prefixes have been removed 20472 */ 20473 _extractPrefixes: function (parts, isAsian) { 20474 var i = this._findPrefix(parts, this.info.prefixes, isAsian); 20475 if (i > 0) { 20476 this.prefix = parts.slice(0, i).join(isAsian ? "" : " "); 20477 return parts.slice(i); 20478 } 20479 // prefixes not found, so just return the array unmodified 20480 return parts; 20481 }, 20482 20483 /** 20484 * @protected 20485 * @param {Array.<string>} parts the current array of name parts 20486 * @param {boolean} isAsian true if the name is being parsed as an Asian name 20487 * @return {Array.<string>} the remaining parts after the suffices have been removed 20488 */ 20489 _extractSuffixes: function (parts, isAsian) { 20490 var i = this._findSuffix(parts, this.info.suffixes, isAsian); 20491 if (i > 0) { 20492 this.suffix = parts.slice(i).join(isAsian ? "" : " "); 20493 return parts.slice(0, i); 20494 } 20495 // suffices not found, so just return the array unmodified 20496 return parts; 20497 }, 20498 20499 /** 20500 * Adjoin auxillary words to their head words. 20501 * @protected 20502 * @param {Array.<string>} parts the current array of name parts 20503 * @param {boolean} isAsian true if the name is being parsed as an Asian name 20504 * @return {Array.<string>} the parts after the auxillary words have been plucked onto their head word 20505 */ 20506 _joinAuxillaries: function (parts, isAsian) { 20507 var start, i, prefixArray, prefix, prefixLower; 20508 20509 if (this.info.auxillaries && (parts.length > 2 || this.prefix)) { 20510 for (start = 0; start < parts.length - 1; start++) { 20511 for (i = parts.length; i > start; i--) { 20512 prefixArray = parts.slice(start, i); 20513 prefix = prefixArray.join(' '); 20514 prefixLower = prefix.toLowerCase(); 20515 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 20516 20517 if (prefixLower in this.info.auxillaries) { 20518 parts.splice(start, i + 1 - start, prefixArray.concat(parts[i])); 20519 i = start; 20520 } 20521 } 20522 } 20523 } 20524 20525 return parts; 20526 }, 20527 20528 /** 20529 * Recursively join an array or string into a long string. 20530 * @protected 20531 */ 20532 _joinArrayOrString: function _joinArrayOrString(part) { 20533 var i; 20534 if (typeof (part) === 'object') { 20535 for (i = 0; i < part.length; i++) { 20536 part[i] = this._joinArrayOrString(part[i]); 20537 } 20538 var ret = ""; 20539 part.forEach(function (segment) { 20540 if (ret.length > 0 && !isPunct(segment.charAt(0))) { 20541 ret += ' '; 20542 } 20543 ret += segment; 20544 }); 20545 20546 return ret; 20547 } 20548 20549 return part; 20550 }, 20551 20552 /** 20553 * @protected 20554 */ 20555 _joinNameArrays: function _joinNameArrays() { 20556 var prop; 20557 for (prop in this) { 20558 20559 if (this[prop] !== undefined && typeof (this[prop]) === 'object' && ilib.isArray(this[prop])) { 20560 20561 this[prop] = this._joinArrayOrString(this[prop]); 20562 } 20563 } 20564 }, 20565 20566 /** 20567 * @protected 20568 */ 20569 _parseAsianName: function (parts, language) { 20570 var familyNameArray = this._findPrefix(parts, this.info.knownFamilyNames, true, this.info.noCompoundFamilyNames); 20571 var tempFullName = parts.join(''); 20572 20573 if (familyNameArray && familyNameArray.length > 0) { 20574 this.familyName = familyNameArray.join(''); 20575 this.givenName = parts.slice(this.familyName.length).join(''); 20576 20577 //Overide parsing rules if spaces are found in korean 20578 if (language === "ko" && tempFullName.search(/\s*[/\s]/) > -1 && !this.suffix) { 20579 this._parseKoreanName(tempFullName); 20580 } 20581 } else if (this.locale.getLanguage() === "ja") { 20582 this._parseJapaneseName(parts); 20583 } else if (this.suffix || this.prefix) { 20584 this.familyName = parts.join(''); 20585 } else { 20586 this.givenName = parts.join(''); 20587 } 20588 }, 20589 20590 /** 20591 * @protected 20592 */ 20593 _parseKoreanName: function (name) { 20594 var tempName = name; 20595 20596 var spaceSplit = tempName.split(" "); 20597 var spceCount = spaceSplit.length; 20598 var fistSpaceIndex = tempName.indexOf(" "); 20599 var lastSpaceIndex = tempName.lastIndexOf(" "); 20600 20601 if (spceCount === 2) { 20602 this.familyName = spaceSplit[0]; 20603 this.givenName = tempName.slice(fistSpaceIndex, tempName.length); 20604 } else { 20605 this.familyName = spaceSplit[0]; 20606 this.middleName = tempName.slice(fistSpaceIndex, lastSpaceIndex); 20607 this.givenName = tempName.slice(lastSpaceIndex, tempName.length); 20608 } 20609 20610 }, 20611 20612 /** 20613 * @protected 20614 */ 20615 _parseJapaneseName: function (parts) { 20616 if (this.suffix && this.suffix.length > 1 && this.info.honorifics.indexOf(this.suffix)>-1) { 20617 if (parts.length === 1) { 20618 if (CType.withinRange(parts[0], "cjk")) { 20619 this.familyName = parts[0]; 20620 } else { 20621 this.givenName = parts[0]; 20622 } 20623 return; 20624 } else if (parts.length === 2) { 20625 this.familyName = parts.slice(0,parts.length).join("") 20626 return; 20627 } 20628 } 20629 if (parts.length > 1) { 20630 var fn = ""; 20631 for (var i = 0; i < parts.length; i++) { 20632 if (CType.withinRange(parts[i], "cjk")) { 20633 fn += parts[i]; 20634 } else if (fn.length > 1 && CType.withinRange(parts[i], "hiragana")) { 20635 this.familyName = fn; 20636 this.givenName = parts.slice(i,parts.length).join(""); 20637 return; 20638 } else { 20639 break; 20640 } 20641 } 20642 } 20643 if (parts.length === 1) { 20644 this.familyName = parts[0]; 20645 } else if (parts.length === 2) { 20646 this.familyName = parts[0]; 20647 this.givenName = parts[1]; 20648 } else if (parts.length === 3) { 20649 this.familyName = parts[0]; 20650 this.givenName = parts.slice(1,parts.length).join(""); 20651 } else if (parts.length > 3) { 20652 this.familyName = parts.slice(0,2).join("") 20653 this.givenName = parts.slice(2,parts.length).join(""); 20654 } 20655 }, 20656 20657 /** 20658 * @protected 20659 */ 20660 _parseSpanishName: function (parts) { 20661 var conjunctionIndex; 20662 20663 if (parts.length === 1) { 20664 if (this.prefix || typeof (parts[0]) === 'object') { 20665 this.familyName = parts[0]; 20666 } else { 20667 this.givenName = parts[0]; 20668 } 20669 } else if (parts.length === 2) { 20670 // we do G F 20671 this.givenName = parts[0]; 20672 this.familyName = parts[1]; 20673 } else if (parts.length === 3) { 20674 conjunctionIndex = this._findLastConjunction(parts); 20675 // if there's an 'and' in the middle spot, put everything in the first name 20676 if (conjunctionIndex === 1) { 20677 this.givenName = parts; 20678 } else { 20679 // else, do G F F 20680 this.givenName = parts[0]; 20681 this.familyName = parts.slice(1); 20682 } 20683 } else if (parts.length > 3) { 20684 //there are at least 4 parts to this name 20685 20686 conjunctionIndex = this._findLastConjunction(parts); 20687 ////console.log("@@@@@@@@@@@@@@@@"+conjunctionIndex) 20688 if (conjunctionIndex > 0) { 20689 // if there's a conjunction that's not the first token, put everything up to and 20690 // including the token after it into the first name, the last 2 tokens into 20691 // the family name (if they exist) and everything else in to the middle name 20692 // 0 1 2 3 4 5 20693 // G A G 20694 // G A G F 20695 // G G A G 20696 // G A G F F 20697 // G G A G F 20698 // G G G A G 20699 // G A G M F F 20700 // G G A G F F 20701 // G G G A G F 20702 // G G G G A G 20703 this.givenName = parts.splice(0, conjunctionIndex + 2); 20704 if (parts.length > 1) { 20705 this.familyName = parts.splice(parts.length - 2, 2); 20706 if (parts.length > 0) { 20707 this.middleName = parts; 20708 } 20709 } else if (parts.length === 1) { 20710 this.familyName = parts[0]; 20711 } 20712 } else { 20713 this.givenName = parts.splice(0, 1); 20714 this.familyName = parts.splice(parts.length - 2, 2); 20715 this.middleName = parts; 20716 } 20717 } 20718 }, 20719 20720 /** 20721 * @protected 20722 */ 20723 _parseIndonesianName: function (parts) { 20724 var conjunctionIndex; 20725 20726 if (parts.length === 1) { 20727 //if (this.prefix || typeof(parts[0]) === 'object') { 20728 //this.familyName = parts[0]; 20729 //} else { 20730 this.givenName = parts[0]; 20731 //} 20732 //} else if (parts.length === 2) { 20733 // we do G F 20734 //this.givenName = parts[0]; 20735 //this.familyName = parts[1]; 20736 } else if (parts.length >= 2) { 20737 //there are at least 3 parts to this name 20738 20739 conjunctionIndex = this._findLastConjunction(parts); 20740 if (conjunctionIndex > 0) { 20741 // if there's a conjunction that's not the first token, put everything up to and 20742 // including the token after it into the first name, the last 2 tokens into 20743 // the family name (if they exist) and everything else in to the middle name 20744 // 0 1 2 3 4 5 20745 // G A G 20746 // G A G F 20747 // G G A G 20748 // G A G F F 20749 // G G A G F 20750 // G G G A G 20751 // G A G M F F 20752 // G G A G F F 20753 // G G G A G F 20754 // G G G G A G 20755 this.givenName = parts.splice(0, conjunctionIndex + 2); 20756 if (parts.length > 1) { 20757 //this.familyName = parts.splice(parts.length-2, 2); 20758 //if ( parts.length > 0 ) { 20759 this.middleName = parts; 20760 } 20761 //} else if (parts.length === 1) { 20762 // this.familyName = parts[0]; 20763 //} 20764 } else { 20765 this.givenName = parts.splice(0, 1); 20766 //this.familyName = parts.splice(parts.length-2, 2); 20767 this.middleName = parts; 20768 } 20769 } 20770 }, 20771 20772 /** 20773 * @protected 20774 */ 20775 _parseGenericWesternName: function (parts) { 20776 /* Western names are parsed as follows, and rules are applied in this 20777 * order: 20778 * 20779 * G 20780 * G F 20781 * G M F 20782 * G M M F 20783 * P F 20784 * P G F 20785 */ 20786 var conjunctionIndex; 20787 20788 if (parts.length === 1) { 20789 if (this.prefix || typeof (parts[0]) === 'object') { 20790 // already has a prefix, so assume it goes with the family name like "Dr. Roberts" or 20791 // it is a name with auxillaries, which is almost always a family name 20792 this.familyName = parts[0]; 20793 } else { 20794 this.givenName = parts[0]; 20795 } 20796 } else if (parts.length === 2) { 20797 // we do G F 20798 if (this.info.order == 'fgm') { 20799 this.givenName = parts[1]; 20800 this.familyName = parts[0]; 20801 } else if (this.info.order == "gmf" || typeof (this.info.order) == 'undefined') { 20802 this.givenName = parts[0]; 20803 this.familyName = parts[1]; 20804 } 20805 } else if (parts.length >= 3) { 20806 //find the first instance of 'and' in the name 20807 conjunctionIndex = this._findLastConjunction(parts); 20808 20809 if (conjunctionIndex > 0) { 20810 // if there's a conjunction that's not the first token, put everything up to and 20811 // including the token after it into the first name, the last token into 20812 // the family name (if it exists) and everything else in to the middle name 20813 // 0 1 2 3 4 5 20814 // G A G M M F 20815 // G G A G M F 20816 // G G G A G F 20817 // G G G G A G 20818 //if(this.order == "gmf") { 20819 this.givenName = parts.slice(0, conjunctionIndex + 2); 20820 20821 if (conjunctionIndex + 1 < parts.length - 1) { 20822 this.familyName = parts.splice(parts.length - 1, 1); 20823 ////console.log(this.familyName); 20824 if (conjunctionIndex + 2 < parts.length - 1) { 20825 this.middleName = parts.slice(conjunctionIndex + 2, parts.length - conjunctionIndex - 3); 20826 } 20827 } else if (this.order == "fgm") { 20828 this.familyName = parts.slice(0, conjunctionIndex + 2); 20829 if (conjunctionIndex + 1 < parts.length - 1) { 20830 this.middleName = parts.splice(parts.length - 1, 1); 20831 if (conjunctionIndex + 2 < parts.length - 1) { 20832 this.givenName = parts.slice(conjunctionIndex + 2, parts.length - conjunctionIndex - 3); 20833 } 20834 } 20835 } 20836 } else { 20837 this.givenName = parts[0]; 20838 20839 this.middleName = parts.slice(1, parts.length - 1); 20840 20841 this.familyName = parts[parts.length - 1]; 20842 } 20843 } 20844 }, 20845 20846 /** 20847 * parse patrinomic name from the russian names 20848 * @protected 20849 * @param {Array.<string>} parts the current array of name parts 20850 * @return number index of the part which contains patronymic name 20851 */ 20852 _findPatronymicName: function(parts) { 20853 var index, part; 20854 for (index = 0; index < parts.length; index++) { 20855 part = parts[index]; 20856 if (typeof (part) === 'string') { 20857 part = part.toLowerCase(); 20858 20859 var subLength = this.info.patronymicName.length; 20860 while(subLength--) { 20861 if(part.indexOf(this.info.patronymicName[subLength])!== -1 ) 20862 return index; 20863 } 20864 } 20865 } 20866 return -1; 20867 }, 20868 20869 /** 20870 * find if the given part is patronymic name 20871 * 20872 * @protected 20873 * @param {string} part string from name parts @ 20874 * @return number index of the part which contains familyName 20875 */ 20876 _isPatronymicName: function(part) { 20877 var pName; 20878 if ( typeof (part) === 'string') { 20879 pName = part.toLowerCase(); 20880 20881 var subLength = this.info.patronymicName.length; 20882 while (subLength--) { 20883 if (pName.indexOf(this.info.patronymicName[subLength]) !== -1) 20884 return true; 20885 } 20886 } 20887 return false; 20888 }, 20889 20890 /** 20891 * find family name from the russian name 20892 * 20893 * @protected 20894 * @param {Array.<string>} parts the current array of name parts 20895 * @return boolean true if patronymic, false otherwise 20896 */ 20897 _findFamilyName: function(parts) { 20898 var index, part, substring; 20899 for (index = 0; index < parts.length; index++) { 20900 part = parts[index]; 20901 20902 if ( typeof (part) === 'string') { 20903 part = part.toLowerCase(); 20904 var length = part.length - 1; 20905 20906 if (this.info.familyName.indexOf(part) !== -1) { 20907 return index; 20908 } else if (part[length] === 'в' || part[length] === 'н' || 20909 part[length] === 'й') { 20910 substring = part.slice(0, -1); 20911 if (this.info.familyName.indexOf(substring) !== -1) { 20912 return index; 20913 } 20914 } else if ((part[length - 1] === 'в' && part[length] === 'а') || 20915 (part[length - 1] === 'н' && part[length] === 'а') || 20916 (part[length - 1] === 'а' && part[length] === 'я')) { 20917 substring = part.slice(0, -2); 20918 if (this.info.familyName.indexOf(substring) !== -1) { 20919 return index; 20920 } 20921 } 20922 } 20923 } 20924 return -1; 20925 }, 20926 20927 /** 20928 * parse russian name 20929 * 20930 * @protected 20931 * @param {Array.<string>} parts the current array of name parts 20932 * @return 20933 */ 20934 _parseRussianName: function(parts) { 20935 var conjunctionIndex, familyIndex = -1; 20936 20937 if (parts.length === 1) { 20938 if (this.prefix || typeof (parts[0]) === 'object') { 20939 // already has a prefix, so assume it goes with the family name 20940 // like "Dr. Roberts" or 20941 // it is a name with auxillaries, which is almost always a 20942 // family name 20943 this.familyName = parts[0]; 20944 } else { 20945 this.givenName = parts[0]; 20946 } 20947 } else if (parts.length === 2) { 20948 // we do G F 20949 if (this.info.order === 'fgm') { 20950 this.givenName = parts[1]; 20951 this.familyName = parts[0]; 20952 } else if (this.info.order === "gmf") { 20953 this.givenName = parts[0]; 20954 this.familyName = parts[1]; 20955 } else if ( typeof (this.info.order) === 'undefined') { 20956 if (this._isPatronymicName(parts[1]) === true) { 20957 this.middleName = parts[1]; 20958 this.givenName = parts[0]; 20959 } else if ((familyIndex = this._findFamilyName(parts)) !== -1) { 20960 if (familyIndex === 1) { 20961 this.givenName = parts[0]; 20962 this.familyName = parts[1]; 20963 } else { 20964 this.familyName = parts[0]; 20965 this.givenName = parts[1]; 20966 } 20967 20968 } else { 20969 this.givenName = parts[0]; 20970 this.familyName = parts[1]; 20971 } 20972 20973 } 20974 } else if (parts.length >= 3) { 20975 // find the first instance of 'and' in the name 20976 conjunctionIndex = this._findLastConjunction(parts); 20977 var patronymicNameIndex = this._findPatronymicName(parts); 20978 if (conjunctionIndex > 0) { 20979 // if there's a conjunction that's not the first token, put 20980 // everything up to and 20981 // including the token after it into the first name, the last 20982 // token into 20983 // the family name (if it exists) and everything else in to the 20984 // middle name 20985 // 0 1 2 3 4 5 20986 // G A G M M F 20987 // G G A G M F 20988 // G G G A G F 20989 // G G G G A G 20990 // if(this.order == "gmf") { 20991 this.givenName = parts.slice(0, conjunctionIndex + 2); 20992 20993 if (conjunctionIndex + 1 < parts.length - 1) { 20994 this.familyName = parts.splice(parts.length - 1, 1); 20995 // //console.log(this.familyName); 20996 if (conjunctionIndex + 2 < parts.length - 1) { 20997 this.middleName = parts.slice(conjunctionIndex + 2, 20998 parts.length - conjunctionIndex - 3); 20999 } 21000 } else if (this.order == "fgm") { 21001 this.familyName = parts.slice(0, conjunctionIndex + 2); 21002 if (conjunctionIndex + 1 < parts.length - 1) { 21003 this.middleName = parts.splice(parts.length - 1, 1); 21004 if (conjunctionIndex + 2 < parts.length - 1) { 21005 this.givenName = parts.slice(conjunctionIndex + 2, 21006 parts.length - conjunctionIndex - 3); 21007 } 21008 } 21009 } 21010 } else if (patronymicNameIndex !== -1) { 21011 this.middleName = parts[patronymicNameIndex]; 21012 21013 if (patronymicNameIndex === (parts.length - 1)) { 21014 this.familyName = parts[0]; 21015 this.givenName = parts.slice(1, patronymicNameIndex); 21016 } else { 21017 this.givenName = parts.slice(0, patronymicNameIndex); 21018 21019 this.familyName = parts[parts.length - 1]; 21020 } 21021 } else { 21022 this.givenName = parts[0]; 21023 21024 this.middleName = parts.slice(1, parts.length - 1); 21025 21026 this.familyName = parts[parts.length - 1]; 21027 } 21028 } 21029 }, 21030 21031 21032 /** 21033 * @protected 21034 */ 21035 _parseWesternName: function (parts) { 21036 21037 if (this.locale.getLanguage() === "es" || this.locale.getLanguage() === "pt") { 21038 // in spain and mexico and portugal, we parse names differently than in the rest of the world 21039 // because of the double family names 21040 this._parseSpanishName(parts); 21041 } else if (this.locale.getLanguage() === "ru") { 21042 /* 21043 * In Russian, names can be given equally validly as given-family 21044 * or family-given. Use the value of the "order" property of the 21045 * constructor options to give the default when the order is ambiguous. 21046 */ 21047 this._parseRussianName(parts); 21048 } else if (this.locale.getLanguage() === "id") { 21049 // in indonesia, we parse names differently than in the rest of the world 21050 // because names don't have family names usually. 21051 this._parseIndonesianName(parts); 21052 } else { 21053 this._parseGenericWesternName(parts); 21054 } 21055 }, 21056 21057 /** 21058 * When sorting names with auxiliary words (like "van der" or "de los"), determine 21059 * which is the "head word" and return a string that can be easily sorted by head 21060 * word. In English, names are always sorted by initial characters. In places like 21061 * the Netherlands or Germany, family names are sorted by the head word of a list 21062 * of names rather than the first element of that name. 21063 * @return {string|undefined} a string containing the family name[s] to be used for sorting 21064 * in the current locale, or undefined if there is no family name in this object 21065 */ 21066 getSortFamilyName: function () { 21067 var name, 21068 auxillaries, 21069 auxString, 21070 parts, 21071 i; 21072 21073 // no name to sort by 21074 if (!this.familyName) { 21075 return undefined; 21076 } 21077 21078 // first break the name into parts 21079 if (this.info) { 21080 if (this.info.sortByHeadWord) { 21081 if (typeof (this.familyName) === 'string') { 21082 name = this.familyName.replace(/\s+/g, ' '); // compress multiple whitespaces 21083 parts = name.trim().split(' '); 21084 } else { 21085 // already split 21086 parts = this.familyName; 21087 } 21088 21089 auxillaries = this._findPrefix(parts, this.info.auxillaries, false); 21090 if (auxillaries && auxillaries.length > 0) { 21091 if (typeof (this.familyName) === 'string') { 21092 auxString = auxillaries.join(' '); 21093 name = this.familyName.substring(auxString.length + 1) + ', ' + auxString; 21094 } else { 21095 name = parts.slice(auxillaries.length).join(' ') + 21096 ', ' + 21097 parts.slice(0, auxillaries.length).join(' '); 21098 } 21099 } 21100 } else if (this.info.knownFamilyNames && this.familyName) { 21101 parts = this.familyName.split(''); 21102 var familyNameArray = this._findPrefix(parts, this.info.knownFamilyNames, true, this.info.noCompoundFamilyNames); 21103 name = ""; 21104 for (i = 0; i < familyNameArray.length; i++) { 21105 name += (this.info.knownFamilyNames[familyNameArray[i]] || ""); 21106 } 21107 } 21108 } 21109 21110 return name || this.familyName; 21111 }, 21112 21113 getHeadFamilyName: function () {}, 21114 21115 /** 21116 * @protected 21117 * Return a shallow copy of the current instance. 21118 */ 21119 clone: function () { 21120 return new Name(this); 21121 } 21122 }; 21123 21124 21125 /*< NameFmt.js */ 21126 /* 21127 * NameFmt.js - Format person names for display 21128 * 21129 * Copyright © 2013-2015, JEDLSoft 21130 * 21131 * Licensed under the Apache License, Version 2.0 (the "License"); 21132 * you may not use this file except in compliance with the License. 21133 * You may obtain a copy of the License at 21134 * 21135 * http://www.apache.org/licenses/LICENSE-2.0 21136 * 21137 * Unless required by applicable law or agreed to in writing, software 21138 * distributed under the License is distributed on an "AS IS" BASIS, 21139 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21140 * 21141 * See the License for the specific language governing permissions and 21142 * limitations under the License. 21143 */ 21144 21145 /* !depends 21146 ilib.js 21147 Locale.js 21148 IString.js 21149 Name.js 21150 isPunct.js 21151 Utils.js 21152 */ 21153 21154 // !data name 21155 21156 21157 21158 21159 /** 21160 * @class 21161 * Creates a formatter that can format person name instances (Name) for display to 21162 * a user. The options may contain the following properties: 21163 * 21164 * <ul> 21165 * <li><i>locale</i> - Use the conventions of the given locale to construct the name format. 21166 * <li><i>style</i> - Format the name with the given style. The value of this property 21167 * should be one of the following strings: 21168 * <ul> 21169 * <li><i>short</i> - Format a short name with just the given and family names. 21170 * <li><i>medium</i> - Format a medium-length name with the given, middle, and family names. 21171 * <li><i>long</i> - Format a long name with all names available in the given name object, including 21172 * prefixes. 21173 * <li><i>full</i> - Format a long name with all names available in the given name object, including 21174 * prefixes and suffixes. 21175 * </ul> 21176 * <li><i>components</i> - Format the name with the given components in the correct 21177 * order for those components. Components are encoded as a string of letters representing 21178 * the desired components: 21179 * <ul> 21180 * <li><i>p</i> - prefixes 21181 * <li><i>g</i> - given name 21182 * <li><i>m</i> - middle names 21183 * <li><i>f</i> - family name 21184 * <li><i>s</i> - suffixes 21185 * </ul> 21186 * <p> 21187 * 21188 * For example, the string "pf" would mean to only format any prefixes and family names 21189 * together and leave out all the other parts of the name.<p> 21190 * 21191 * The components can be listed in any order in the string. The <i>components</i> option 21192 * overrides the <i>style</i> option if both are specified. 21193 * 21194 * <li>onLoad - a callback function to call when the locale info object is fully 21195 * loaded. When the onLoad option is given, the localeinfo object will attempt to 21196 * load any missing locale data using the ilib loader callback. 21197 * When the constructor is done (even if the data is already preassembled), the 21198 * onLoad function is called with the current instance as a parameter, so this 21199 * callback can be used with preassembled or dynamic loading or a mix of the two. 21200 * 21201 * <li>sync - tell whether to load any missing locale data synchronously or 21202 * asynchronously. If this option is given as "false", then the "onLoad" 21203 * callback must be given, as the instance returned from this constructor will 21204 * not be usable for a while. 21205 * 21206 * <li><i>loadParams</i> - an object containing parameters to pass to the 21207 * loader callback function when locale data is missing. The parameters are not 21208 * interpretted or modified in any way. They are simply passed along. The object 21209 * may contain any property/value pairs as long as the calling code is in 21210 * agreement with the loader callback function as to what those parameters mean. 21211 * </ul> 21212 * 21213 * Formatting names is a locale-dependent function, as the order of the components 21214 * depends on the locale. The following explains some of the details:<p> 21215 * 21216 * <ul> 21217 * <li>In Western countries, the given name comes first, followed by a space, followed 21218 * by the family name. In Asian countries, the family name comes first, followed immediately 21219 * by the given name with no space. But, that format is only used with Asian names written 21220 * in ideographic characters. In Asian countries, especially ones where both an Asian and 21221 * a Western language are used (Hong Kong, Singapore, etc.), the convention is often to 21222 * follow the language of the name. That is, Asian names are written in Asian style, and 21223 * Western names are written in Western style. This class follows that convention as 21224 * well. 21225 * <li>In other Asian countries, Asian names 21226 * written in Latin script are written with Asian ordering. eg. "Xu Ping-an" instead 21227 * of the more Western order "Ping-an Xu", as the order is thought to go with the style 21228 * that is appropriate for the name rather than the style for the language being written. 21229 * <li>In some Spanish speaking countries, people often take both their maternal and 21230 * paternal last names as their own family name. When formatting a short or medium style 21231 * of that family name, only the paternal name is used. In the long style, all the names 21232 * are used. eg. "Juan Julio Raul Lopez Ortiz" took the name "Lopez" from his father and 21233 * the name "Ortiz" from his mother. His family name would be "Lopez Ortiz". The formatted 21234 * short style of his name would be simply "Juan Lopez" which only uses his paternal 21235 * family name of "Lopez". 21236 * <li>In many Western languages, it is common to use auxillary words in family names. For 21237 * example, the family name of "Ludwig von Beethoven" in German is "von Beethoven", not 21238 * "Beethoven". This class ensures that the family name is formatted correctly with 21239 * all auxillary words. 21240 * </ul> 21241 * 21242 * 21243 * @constructor 21244 * @param {Object} options A set of options that govern how the formatter will behave 21245 */ 21246 var NameFmt = function(options) { 21247 var sync = true; 21248 21249 this.style = "short"; 21250 this.loadParams = {}; 21251 21252 if (options) { 21253 if (options.locale) { 21254 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 21255 } 21256 21257 if (options.style) { 21258 this.style = options.style; 21259 } 21260 21261 if (options.components) { 21262 this.components = options.components; 21263 } 21264 21265 if (typeof(options.sync) !== 'undefined') { 21266 sync = (options.sync == true); 21267 } 21268 21269 if (typeof(options.loadParams) !== 'undefined') { 21270 this.loadParams = options.loadParams; 21271 } 21272 } 21273 21274 // set up defaults in case we need them 21275 this.defaultEuroTemplate = new IString("{prefix} {givenName} {middleName} {familyName}{suffix}"); 21276 this.defaultAsianTemplate = new IString("{prefix}{familyName}{givenName}{middleName}{suffix}"); 21277 this.useFirstFamilyName = false; 21278 21279 switch (this.style) { 21280 default: 21281 case "s": 21282 case "short": 21283 this.style = "short"; 21284 break; 21285 case "m": 21286 case "medium": 21287 this.style = "medium"; 21288 break; 21289 case "l": 21290 case "long": 21291 this.style = "long"; 21292 break; 21293 case "f": 21294 case "full": 21295 this.style = "full"; 21296 break; 21297 } 21298 21299 if (!Name.cache) { 21300 Name.cache = {}; 21301 } 21302 21303 this.locale = this.locale || new Locale(); 21304 21305 isPunct._init(sync, this.loadParams, ilib.bind(this, function() { 21306 Utils.loadData({ 21307 object: Name, 21308 locale: this.locale, 21309 name: "name.json", 21310 sync: sync, 21311 loadParams: this.loadParams, 21312 callback: ilib.bind(this, function (info) { 21313 if (!info) { 21314 info = Name.defaultInfo; 21315 var spec = this.locale.getSpec().replace(/-/g, "_"); 21316 Name.cache[spec] = info; 21317 } 21318 this.info = info; 21319 this._init(); 21320 if (options && typeof(options.onLoad) === 'function') { 21321 options.onLoad(this); 21322 } 21323 }) 21324 }); 21325 })); 21326 }; 21327 21328 NameFmt.prototype = { 21329 /** 21330 * @protected 21331 */ 21332 _init: function() { 21333 if (this.components) { 21334 var valids = {"p":1,"g":1,"m":1,"f":1,"s":1}, 21335 arr = this.components.split(""); 21336 this.comps = {}; 21337 for (var i = 0; i < arr.length; i++) { 21338 if (valids[arr[i].toLowerCase()]) { 21339 this.comps[arr[i].toLowerCase()] = true; 21340 } 21341 } 21342 } else { 21343 this.comps = this.info.components[this.style]; 21344 } 21345 21346 this.template = new IString(this.info.format); 21347 21348 if (this.locale.language === "es" && (this.style !== "long" && this.style !== "full")) { 21349 this.useFirstFamilyName = true; // in spanish, they have 2 family names, the maternal and paternal 21350 } 21351 21352 this.isAsianLocale = (this.info.nameStyle === "asian"); 21353 }, 21354 21355 /** 21356 * adjoin auxillary words to their head words 21357 * @protected 21358 */ 21359 _adjoinAuxillaries: function (parts, namePrefix) { 21360 var start, i, prefixArray, prefix, prefixLower; 21361 21362 //console.info("_adjoinAuxillaries: finding and adjoining aux words in " + parts.join(' ')); 21363 21364 if ( this.info.auxillaries && (parts.length > 2 || namePrefix) ) { 21365 for ( start = 0; start < parts.length-1; start++ ) { 21366 for ( i = parts.length; i > start; i-- ) { 21367 prefixArray = parts.slice(start, i); 21368 prefix = prefixArray.join(' '); 21369 prefixLower = prefix.toLowerCase(); 21370 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 21371 21372 //console.info("_adjoinAuxillaries: checking aux prefix: '" + prefixLower + "' which is " + start + " to " + i); 21373 21374 if ( prefixLower in this.info.auxillaries ) { 21375 //console.info("Found! Old parts list is " + JSON.stringify(parts)); 21376 parts.splice(start, i+1-start, prefixArray.concat(parts[i])); 21377 //console.info("_adjoinAuxillaries: Found! New parts list is " + JSON.stringify(parts)); 21378 i = start; 21379 } 21380 } 21381 } 21382 } 21383 21384 //console.info("_adjoinAuxillaries: done. Result is " + JSON.stringify(parts)); 21385 21386 return parts; 21387 }, 21388 21389 /** 21390 * Return the locale for this formatter instance. 21391 * @return {Locale} the locale instance for this formatter 21392 */ 21393 getLocale: function () { 21394 return this.locale; 21395 }, 21396 21397 /** 21398 * Return the style of names returned by this formatter 21399 * @return {string} the style of names returned by this formatter 21400 */ 21401 getStyle: function () { 21402 return this.style; 21403 }, 21404 21405 /** 21406 * Return the list of components used to format names in this formatter 21407 * @return {string} the list of components 21408 */ 21409 getComponents: function () { 21410 return this.components; 21411 }, 21412 21413 /** 21414 * Format the name for display in the current locale with the options set up 21415 * in the constructor of this formatter instance.<p> 21416 * 21417 * If the name does not contain all the parts required for the style, those parts 21418 * will be left blank.<p> 21419 * 21420 * There are two basic styles of formatting: European, and Asian. If this formatter object 21421 * is set for European style, but an Asian name is passed to the format method, then this 21422 * method will format the Asian name with a generic Asian template. Similarly, if the 21423 * formatter is set for an Asian style, and a European name is passed to the format method, 21424 * the formatter will use a generic European template.<p> 21425 * 21426 * This means it is always safe to format any name with a formatter for any locale. You should 21427 * always get something at least reasonable as output.<p> 21428 * 21429 * @param {Name} name the name to format 21430 * @return {string|undefined} the name formatted according to the style of this formatter instance 21431 */ 21432 format: function(name) { 21433 var formatted, temp, modified, isAsianName; 21434 var currentLanguage = this.locale.getLanguage(); 21435 21436 if (!name || typeof(name) !== 'object') { 21437 return undefined; 21438 } 21439 21440 if ((typeof(name.isAsianName) === 'boolean' && !name.isAsianName) || 21441 Name._isEuroName([name.givenName, name.middleName, name.familyName].join(""), currentLanguage)) { 21442 isAsianName = false; // this is a euro name, even if the locale is asian 21443 modified = name.clone(); 21444 21445 // handle the case where there is no space if there is punctuation in the suffix like ", Phd". 21446 // Otherwise, put a space in to transform "PhD" to " PhD" 21447 /* 21448 console.log("suffix is " + modified.suffix); 21449 if ( modified.suffix ) { 21450 console.log("first char is " + modified.suffix.charAt(0)); 21451 console.log("isPunct(modified.suffix.charAt(0)) is " + isPunct(modified.suffix.charAt(0))); 21452 } 21453 */ 21454 if (modified.suffix && isPunct(modified.suffix.charAt(0)) === false) { 21455 modified.suffix = ' ' + modified.suffix; 21456 } 21457 21458 if (this.useFirstFamilyName && name.familyName) { 21459 var familyNameParts = modified.familyName.trim().split(' '); 21460 if (familyNameParts.length > 1) { 21461 familyNameParts = this._adjoinAuxillaries(familyNameParts, name.prefix); 21462 } //in spain and mexico, we parse names differently than in the rest of the world 21463 21464 modified.familyName = familyNameParts[0]; 21465 } 21466 21467 modified._joinNameArrays(); 21468 } else { 21469 isAsianName = true; 21470 modified = name; 21471 if (modified.suffix && currentLanguage === "ko" && this.info.honorifics.indexOf(name.suffix) == -1) { 21472 modified.suffix = ' ' + modified.suffix; 21473 } 21474 } 21475 21476 if (!this.template || isAsianName !== this.isAsianLocale) { 21477 temp = isAsianName ? this.defaultAsianTemplate : this.defaultEuroTemplate; 21478 } else { 21479 temp = this.template; 21480 } 21481 21482 var parts = { 21483 prefix: this.comps["p"] && modified.prefix || "", 21484 givenName: this.comps["g"] && modified.givenName || "", 21485 middleName: this.comps["m"] && modified.middleName || "", 21486 familyName: this.comps["f"] && modified.familyName || "", 21487 suffix: this.comps["s"] && modified.suffix || "" 21488 }; 21489 21490 formatted = temp.format(parts); 21491 return formatted.replace(/\s+/g, ' ').trim(); 21492 } 21493 }; 21494 21495 21496 /*< Address.js */ 21497 /* 21498 * Address.js - Represent a mailing address 21499 * 21500 * Copyright © 2013-2015, JEDLSoft 21501 * 21502 * Licensed under the Apache License, Version 2.0 (the "License"); 21503 * you may not use this file except in compliance with the License. 21504 * You may obtain a copy of the License at 21505 * 21506 * http://www.apache.org/licenses/LICENSE-2.0 21507 * 21508 * Unless required by applicable law or agreed to in writing, software 21509 * distributed under the License is distributed on an "AS IS" BASIS, 21510 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21511 * 21512 * See the License for the specific language governing permissions and 21513 * limitations under the License. 21514 */ 21515 21516 /*globals console RegExp */ 21517 21518 /* !depends 21519 ilib.js 21520 Utils.js 21521 JSUtils.js 21522 Locale.js 21523 isIdeo.js 21524 isAscii.js 21525 isDigit.js 21526 IString.js 21527 */ 21528 21529 // !data address countries nativecountries ctrynames 21530 21531 21532 /** 21533 * @class 21534 * Create a new Address instance and parse a physical address.<p> 21535 * 21536 * This function parses a physical address written in a free-form string. 21537 * It returns an object with a number of properties from the list below 21538 * that it may have extracted from that address.<p> 21539 * 21540 * The following is a list of properties that the algorithm will return:<p> 21541 * 21542 * <ul> 21543 * <li><i>streetAddress</i>: The street address, including house numbers and all. 21544 * <li><i>locality</i>: The locality of this address (usually a city or town). 21545 * <li><i>region</i>: The region where the locality is located. In the US, this 21546 * corresponds to states. In other countries, this may be provinces, 21547 * cantons, prefectures, etc. In some smaller countries, there are no 21548 * such divisions. 21549 * <li><i>postalCode</i>: Country-specific code for expediting mail. In the US, 21550 * this is the zip code. 21551 * <li><i>country</i>: The country of the address. 21552 * <li><i>countryCode</i>: The ISO 3166 2-letter region code for the destination 21553 * country in this address. 21554 * </ul> 21555 * 21556 * The above properties will not necessarily appear in the instance. For 21557 * any individual property, if the free-form address does not contain 21558 * that property or it cannot be parsed out, the it is left out.<p> 21559 * 21560 * The options parameter may contain any of the following properties: 21561 * 21562 * <ul> 21563 * <li><i>locale</i> - locale or localeSpec to use to parse the address. If not 21564 * specified, this function will use the current ilib locale 21565 * 21566 * <li><i>onLoad</i> - a callback function to call when the address info for the 21567 * locale is fully loaded and the address has been parsed. When the onLoad 21568 * option is given, the address object 21569 * will attempt to load any missing locale data using the ilib loader callback. 21570 * When the constructor is done (even if the data is already preassembled), the 21571 * onLoad function is called with the current instance as a parameter, so this 21572 * callback can be used with preassembled or dynamic loading or a mix of the two. 21573 * 21574 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 21575 * asynchronously. If this option is given as "false", then the "onLoad" 21576 * callback must be given, as the instance returned from this constructor will 21577 * not be usable for a while. 21578 * 21579 * <li><i>loadParams</i> - an object containing parameters to pass to the 21580 * loader callback function when locale data is missing. The parameters are not 21581 * interpretted or modified in any way. They are simply passed along. The object 21582 * may contain any property/value pairs as long as the calling code is in 21583 * agreement with the loader callback function as to what those parameters mean. 21584 * </ul> 21585 * 21586 * When an address cannot be parsed properly, the entire address will be placed 21587 * into the streetAddress property.<p> 21588 * 21589 * When the freeformAddress is another Address, this will act like a copy 21590 * constructor.<p> 21591 * 21592 * 21593 * @constructor 21594 * @param {string|Address} freeformAddress free-form address to parse, or a 21595 * javascript object containing the fields 21596 * @param {Object} options options to the parser 21597 */ 21598 var Address = function (freeformAddress, options) { 21599 var address; 21600 21601 if (!freeformAddress) { 21602 return undefined; 21603 } 21604 21605 this.sync = true; 21606 this.loadParams = {}; 21607 21608 if (options) { 21609 if (options.locale) { 21610 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 21611 } 21612 21613 if (typeof(options.sync) !== 'undefined') { 21614 this.sync = (options.sync == true); 21615 } 21616 21617 if (options.loadParams) { 21618 this.loadParams = options.loadParams; 21619 } 21620 } 21621 21622 this.locale = this.locale || new Locale(); 21623 // initialize from an already parsed object 21624 if (typeof(freeformAddress) === 'object') { 21625 /** 21626 * The street address, including house numbers and all. 21627 * @type {string|undefined} 21628 */ 21629 this.streetAddress = freeformAddress.streetAddress; 21630 /** 21631 * The locality of this address (usually a city or town). 21632 * @type {string|undefined} 21633 */ 21634 this.locality = freeformAddress.locality; 21635 /** 21636 * The region (province, canton, prefecture, state, etc.) where the address is located. 21637 * @type {string|undefined} 21638 */ 21639 this.region = freeformAddress.region; 21640 /** 21641 * Country-specific code for expediting mail. In the US, this is the zip code. 21642 * @type {string|undefined} 21643 */ 21644 this.postalCode = freeformAddress.postalCode; 21645 /** 21646 * Optional city-specific code for a particular post office, used to expidite 21647 * delivery. 21648 * @type {string|undefined} 21649 */ 21650 this.postOffice = freeformAddress.postOffice; 21651 /** 21652 * The country of the address. 21653 * @type {string|undefined} 21654 */ 21655 this.country = freeformAddress.country; 21656 if (freeformAddress.countryCode) { 21657 /** 21658 * The 2 or 3 letter ISO 3166 region code for the destination country in this address. 21659 * @type {string} 21660 */ 21661 this.countryCode = freeformAddress.countryCode; 21662 } 21663 if (freeformAddress.format) { 21664 /** 21665 * private 21666 * @type {string} 21667 */ 21668 this.format = freeformAddress.format; 21669 } 21670 return this; 21671 } 21672 21673 address = freeformAddress.replace(/[ \t\r]+/g, " ").trim(); 21674 address = address.replace(/[\s\n]+$/, ""); 21675 address = address.replace(/^[\s\n]+/, ""); 21676 //console.log("\n\n-------------\nAddress is '" + address + "'"); 21677 21678 this.lines = address.split(/[,,\n]/g); 21679 this.removeEmptyLines(this.lines); 21680 21681 isAscii._init(this.sync, this.loadParams, ilib.bind(this, function() { 21682 isIdeo._init(this.sync, this.loadParams, ilib.bind(this, function() { 21683 isDigit._init(this.sync, this.loadParams, ilib.bind(this, function() { 21684 if (typeof(ilib.data.nativecountries) === 'undefined') { 21685 Utils.loadData({ 21686 object: Address, 21687 name: "nativecountries.json", // countries in their own language 21688 locale: "-", // only need to load the root file 21689 nonlocale: true, 21690 sync: this.sync, 21691 loadParams: this.loadParams, 21692 callback: ilib.bind(this, function(nativecountries) { 21693 ilib.data.nativecountries = nativecountries; 21694 this._loadCountries(options && options.onLoad); 21695 }) 21696 }); 21697 } else { 21698 this._loadCountries(options && options.onLoad); 21699 } 21700 })); 21701 })); 21702 })); 21703 }; 21704 21705 /** @protected */ 21706 Address.prototype = { 21707 /** 21708 * @private 21709 */ 21710 _loadCountries: function(onLoad) { 21711 if (typeof(ilib.data.countries) === 'undefined') { 21712 Utils.loadData({ 21713 object: Address, 21714 name: "countries.json", // countries in English 21715 locale: "-", // only need to load the root file 21716 nonlocale: true, 21717 sync: this.sync, 21718 loadParams: this.loadParams, 21719 callback: ilib.bind(this, function(countries) { 21720 ilib.data.countries = countries; 21721 this._loadCtrynames(onLoad); 21722 }) 21723 }); 21724 } else { 21725 this._loadCtrynames(onLoad); 21726 } 21727 }, 21728 21729 /** 21730 * @private 21731 */ 21732 _loadCtrynames: function(onLoad) { 21733 Utils.loadData({ 21734 name: "ctrynames.json", 21735 object: Address, 21736 locale: this.locale, 21737 sync: this.sync, 21738 loadParams: this.loadParams, 21739 callback: ilib.bind(this, function(ctrynames) { 21740 this._determineDest(ctrynames, onLoad); 21741 }) 21742 }); 21743 }, 21744 21745 /** 21746 * @private 21747 * @param {Object?} ctrynames 21748 */ 21749 _findDest: function (ctrynames) { 21750 var match; 21751 21752 for (var countryName in ctrynames) { 21753 if (countryName && countryName !== "generated") { 21754 // find the longest match in the current table 21755 // ctrynames contains the country names mapped to region code 21756 // for efficiency, only test for things longer than the current match 21757 if (!match || match.text.length < countryName.length) { 21758 var temp = this._findCountry(countryName); 21759 if (temp) { 21760 match = temp; 21761 this.country = match.text; 21762 this.countryCode = ctrynames[countryName]; 21763 } 21764 } 21765 } 21766 } 21767 return match; 21768 }, 21769 21770 /** 21771 * @private 21772 * @param {Object?} localizedCountries 21773 * @param {function(Address):undefined} callback 21774 */ 21775 _determineDest: function (localizedCountries, callback) { 21776 var match; 21777 21778 /* 21779 * First, find the name of the destination country, as that determines how to parse 21780 * the rest of the address. For any address, there are three possible ways 21781 * that the name of the country could be written: 21782 * 1. In the current language 21783 * 2. In its own native language 21784 * 3. In English 21785 * We'll try all three. 21786 */ 21787 var tables = []; 21788 if (localizedCountries) { 21789 tables.push(localizedCountries); 21790 } 21791 tables.push(ilib.data.nativecountries); 21792 tables.push(ilib.data.countries); 21793 21794 for (var i = 0; i < tables.length; i++) { 21795 match = this._findDest(tables[i]); 21796 21797 if (match) { 21798 this.lines[match.line] = this.lines[match.line].substring(0, match.start) + this.lines[match.line].substring(match.start + match.text.length); 21799 21800 this._init(callback); 21801 return; 21802 } 21803 } 21804 21805 // no country, so try parsing it as if we were in the same country 21806 this.country = undefined; 21807 this.countryCode = this.locale.getRegion(); 21808 this._init(callback); 21809 }, 21810 21811 /** 21812 * @private 21813 * @param {function(Address):undefined} callback 21814 */ 21815 _init: function(callback) { 21816 Utils.loadData({ 21817 object: Address, 21818 locale: new Locale(this.countryCode), 21819 name: "address.json", 21820 sync: this.sync, 21821 loadParams: this.loadParams, 21822 callback: ilib.bind(this, function(info) { 21823 if (!info || JSUtils.isEmpty(info)) { 21824 // load the "unknown" locale instead 21825 Utils.loadData({ 21826 object: Address, 21827 locale: new Locale("XX"), 21828 name: "address.json", 21829 sync: this.sync, 21830 loadParams: this.loadParams, 21831 callback: ilib.bind(this, function(info) { 21832 this.info = info; 21833 this._parseAddress(); 21834 if (typeof(callback) === 'function') { 21835 callback(this); 21836 } 21837 }) 21838 }); 21839 } else { 21840 this.info = info; 21841 this._parseAddress(); 21842 if (typeof(callback) === 'function') { 21843 callback(this); 21844 } 21845 } 21846 }) 21847 }); 21848 }, 21849 21850 /** 21851 * @private 21852 */ 21853 _parseAddress: function() { 21854 // clean it up first 21855 var i, 21856 asianChars = 0, 21857 latinChars = 0, 21858 startAt, 21859 infoFields, 21860 field, 21861 pattern, 21862 matchFunction, 21863 match, 21864 fieldNumber; 21865 21866 // for locales that support both latin and asian character addresses, 21867 // decide if we are parsing an asian or latin script address 21868 if (this.info && this.info.multiformat) { 21869 for (var j = 0; j < this.lines.length; j++) { 21870 var line = new IString(this.lines[j]); 21871 var it = line.charIterator(); 21872 while (it.hasNext()) { 21873 var c = it.next(); 21874 if (isIdeo(c) || CType.withinRange(c, "Hangul")) { 21875 asianChars++; 21876 } else if (isAscii(c) && !isDigit(c)) { 21877 latinChars++; 21878 } 21879 } 21880 } 21881 21882 this.format = (asianChars >= latinChars) ? "asian" : "latin"; 21883 startAt = this.info.startAt[this.format]; 21884 infoFields = this.info.fields[this.format]; 21885 // //console.log("multiformat locale: format is now " + this.format); 21886 } else { 21887 startAt = (this.info && this.info.startAt) || "end"; 21888 infoFields = this.info.fields; 21889 } 21890 this.compare = (startAt === "end") ? this.endsWith : this.startsWith; 21891 21892 //console.log("this.lines is: " + JSON.stringify(this.lines)); 21893 21894 for (i = 0; i < infoFields.length && this.lines.length > 0; i++) { 21895 field = infoFields[i]; 21896 this.removeEmptyLines(this.lines); 21897 //console.log("Searching for field " + field.name); 21898 if (field.pattern) { 21899 if (typeof(field.pattern) === 'string') { 21900 pattern = new RegExp(field.pattern, "img"); 21901 matchFunction = this.matchRegExp; 21902 } else { 21903 pattern = field.pattern; 21904 matchFunction = this.matchPattern; 21905 } 21906 21907 switch (field.line) { 21908 case 'startAtFirst': 21909 for (fieldNumber = 0; fieldNumber < this.lines.length; fieldNumber++) { 21910 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21911 if (match) { 21912 break; 21913 } 21914 } 21915 break; 21916 case 'startAtLast': 21917 for (fieldNumber = this.lines.length-1; fieldNumber >= 0; fieldNumber--) { 21918 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21919 if (match) { 21920 break; 21921 } 21922 } 21923 break; 21924 case 'first': 21925 fieldNumber = 0; 21926 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21927 break; 21928 case 'last': 21929 default: 21930 fieldNumber = this.lines.length - 1; 21931 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21932 break; 21933 } 21934 if (match) { 21935 // //console.log("found match for " + field.name + ": " + JSON.stringify(match)); 21936 // //console.log("remaining line is " + match.line); 21937 this.lines[fieldNumber] = match.line; 21938 this[field.name] = match.match; 21939 } 21940 } else { 21941 // if nothing is given, default to taking the whole field 21942 this[field.name] = this.lines.splice(fieldNumber,1)[0].trim(); 21943 //console.log("typeof(this[field.name]) is " + typeof(this[field.name]) + " and value is " + JSON.stringify(this[field.name])); 21944 } 21945 } 21946 21947 // all the left overs go in the street address field 21948 this.removeEmptyLines(this.lines); 21949 if (this.lines.length > 0) { 21950 //console.log("this.lines is " + JSON.stringify(this.lines) + " and splicing to get streetAddress"); 21951 // Korea uses spaces between words, despite being an "asian" locale 21952 var joinString = (this.info.joinString && this.info.joinString[this.format]) || ((this.format && this.format === "asian") ? "" : ", "); 21953 this.streetAddress = this.lines.join(joinString).trim(); 21954 } 21955 21956 this.lines = undefined; 21957 //console.log("final result is " + JSON.stringify(this)); 21958 }, 21959 21960 /** 21961 * @protected 21962 * Find the named country either at the end or the beginning of the address. 21963 */ 21964 _findCountry: function(name) { 21965 var start = -1, match, line = 0; 21966 21967 if (this.lines.length > 0) { 21968 start = this.startsWith(this.lines[line], name); 21969 if (start === -1) { 21970 line = this.lines.length-1; 21971 start = this.endsWith(this.lines[line], name); 21972 } 21973 if (start !== -1) { 21974 match = { 21975 text: this.lines[line].substring(start, start + name.length), 21976 line: line, 21977 start: start 21978 }; 21979 } 21980 } 21981 21982 return match; 21983 }, 21984 21985 endsWith: function (subject, query) { 21986 var start = subject.length-query.length, 21987 i, 21988 pat; 21989 //console.log("endsWith: checking " + query + " against " + subject); 21990 for (i = 0; i < query.length; i++) { 21991 // TODO: use case mapper instead of toLowerCase() 21992 if (subject.charAt(start+i).toLowerCase() !== query.charAt(i).toLowerCase()) { 21993 return -1; 21994 } 21995 } 21996 if (start > 0) { 21997 pat = /\s/; 21998 if (!pat.test(subject.charAt(start-1))) { 21999 // make sure if we are not at the beginning of the string, that the match is 22000 // not the end of some other word 22001 return -1; 22002 } 22003 } 22004 return start; 22005 }, 22006 22007 startsWith: function (subject, query) { 22008 var i; 22009 // //console.log("startsWith: checking " + query + " against " + subject); 22010 for (i = 0; i < query.length; i++) { 22011 // TODO: use case mapper instead of toLowerCase() 22012 if (subject.charAt(i).toLowerCase() !== query.charAt(i).toLowerCase()) { 22013 return -1; 22014 } 22015 } 22016 return 0; 22017 }, 22018 22019 removeEmptyLines: function (arr) { 22020 var i = 0; 22021 22022 while (i < arr.length) { 22023 if (arr[i]) { 22024 arr[i] = arr[i].trim(); 22025 if (arr[i].length === 0) { 22026 arr.splice(i,1); 22027 } else { 22028 i++; 22029 } 22030 } else { 22031 arr.splice(i,1); 22032 } 22033 } 22034 }, 22035 22036 matchRegExp: function(address, line, expression, matchGroup, startAt) { 22037 var lastMatch, 22038 match, 22039 ret = {}, 22040 last; 22041 22042 //console.log("searching for regexp " + expression.source + " in line " + line); 22043 22044 match = expression.exec(line); 22045 if (startAt === 'end') { 22046 while (match !== null && match.length > 0) { 22047 //console.log("found matches " + JSON.stringify(match)); 22048 lastMatch = match; 22049 match = expression.exec(line); 22050 } 22051 match = lastMatch; 22052 } 22053 22054 if (match && match !== null) { 22055 //console.log("found matches " + JSON.stringify(match)); 22056 matchGroup = matchGroup || 0; 22057 if (match[matchGroup] !== undefined) { 22058 ret.match = match[matchGroup].trim(); 22059 ret.match = ret.match.replace(/^\-|\-+$/, ''); 22060 ret.match = ret.match.replace(/\s+$/, ''); 22061 last = (startAt === 'end') ? line.lastIndexOf(match[matchGroup]) : line.indexOf(match[matchGroup]); 22062 //console.log("last is " + last); 22063 ret.line = line.slice(0,last); 22064 if (address.format !== "asian") { 22065 ret.line += " "; 22066 } 22067 ret.line += line.slice(last+match[matchGroup].length); 22068 ret.line = ret.line.trim(); 22069 //console.log("found match " + ret.match + " from matchgroup " + matchGroup + " and rest of line is " + ret.line); 22070 return ret; 22071 } 22072 //} else { 22073 //console.log("no match"); 22074 } 22075 22076 return undefined; 22077 }, 22078 22079 matchPattern: function(address, line, pattern, matchGroup) { 22080 var start, 22081 j, 22082 ret = {}; 22083 22084 //console.log("searching in line " + line); 22085 22086 // search an array of possible fixed strings 22087 //console.log("Using fixed set of strings."); 22088 for (j = 0; j < pattern.length; j++) { 22089 start = address.compare(line, pattern[j]); 22090 if (start !== -1) { 22091 ret.match = line.substring(start, start+pattern[j].length); 22092 if (start !== 0) { 22093 ret.line = line.substring(0,start).trim(); 22094 } else { 22095 ret.line = line.substring(pattern[j].length).trim(); 22096 } 22097 //console.log("found match " + ret.match + " and rest of line is " + ret.line); 22098 return ret; 22099 } 22100 } 22101 22102 return undefined; 22103 } 22104 }; 22105 22106 22107 22108 /*< AddressFmt.js */ 22109 /* 22110 * AddressFmt.js - Format an address 22111 * 22112 * Copyright © 2013-2015, JEDLSoft 22113 * 22114 * Licensed under the Apache License, Version 2.0 (the "License"); 22115 * you may not use this file except in compliance with the License. 22116 * You may obtain a copy of the License at 22117 * 22118 * http://www.apache.org/licenses/LICENSE-2.0 22119 * 22120 * Unless required by applicable law or agreed to in writing, software 22121 * distributed under the License is distributed on an "AS IS" BASIS, 22122 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22123 * 22124 * See the License for the specific language governing permissions and 22125 * limitations under the License. 22126 */ 22127 22128 /* !depends 22129 ilib.js 22130 Locale.js 22131 Address.js 22132 IString.js 22133 Utils.js 22134 JSUtils.js 22135 */ 22136 22137 // !data address 22138 22139 22140 22141 /** 22142 * @class 22143 * Create a new formatter object to format physical addresses in a particular way. 22144 * 22145 * The options object may contain the following properties, both of which are optional: 22146 * 22147 * <ul> 22148 * <li><i>locale</i> - the locale to use to format this address. If not specified, it uses the default locale 22149 * 22150 * <li><i>style</i> - the style of this address. The default style for each country usually includes all valid 22151 * fields for that country. 22152 * 22153 * <li><i>onLoad</i> - a callback function to call when the address info for the 22154 * locale is fully loaded and the address has been parsed. When the onLoad 22155 * option is given, the address formatter object 22156 * will attempt to load any missing locale data using the ilib loader callback. 22157 * When the constructor is done (even if the data is already preassembled), the 22158 * onLoad function is called with the current instance as a parameter, so this 22159 * callback can be used with preassembled or dynamic loading or a mix of the two. 22160 * 22161 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 22162 * asynchronously. If this option is given as "false", then the "onLoad" 22163 * callback must be given, as the instance returned from this constructor will 22164 * not be usable for a while. 22165 * 22166 * <li><i>loadParams</i> - an object containing parameters to pass to the 22167 * loader callback function when locale data is missing. The parameters are not 22168 * interpretted or modified in any way. They are simply passed along. The object 22169 * may contain any property/value pairs as long as the calling code is in 22170 * agreement with the loader callback function as to what those parameters mean. 22171 * </ul> 22172 * 22173 * 22174 * @constructor 22175 * @param {Object} options options that configure how this formatter should work 22176 * Returns a formatter instance that can format multiple addresses. 22177 */ 22178 var AddressFmt = function(options) { 22179 this.sync = true; 22180 this.styleName = 'default'; 22181 this.loadParams = {}; 22182 this.locale = new Locale(); 22183 22184 if (options) { 22185 if (options.locale) { 22186 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 22187 } 22188 22189 if (typeof(options.sync) !== 'undefined') { 22190 this.sync = (options.sync == true); 22191 } 22192 22193 if (options.style) { 22194 this.styleName = options.style; 22195 } 22196 22197 if (options.loadParams) { 22198 this.loadParams = options.loadParams; 22199 } 22200 } 22201 22202 // console.log("Creating formatter for region: " + this.locale.region); 22203 Utils.loadData({ 22204 name: "address.json", 22205 object: AddressFmt, 22206 locale: this.locale, 22207 sync: this.sync, 22208 loadParams: this.loadParams, 22209 callback: ilib.bind(this, function(info) { 22210 if (!info || JSUtils.isEmpty(info)) { 22211 // load the "unknown" locale instead 22212 Utils.loadData({ 22213 name: "address.json", 22214 object: AddressFmt, 22215 locale: new Locale("XX"), 22216 sync: this.sync, 22217 loadParams: this.loadParams, 22218 callback: ilib.bind(this, function(info) { 22219 this.info = info; 22220 this._init(); 22221 if (options && typeof(options.onLoad) === 'function') { 22222 options.onLoad(this); 22223 } 22224 }) 22225 }); 22226 } else { 22227 this.info = info; 22228 this._init(); 22229 if (options && typeof(options.onLoad) === 'function') { 22230 options.onLoad(this); 22231 } 22232 } 22233 }) 22234 }); 22235 }; 22236 22237 /** 22238 * @private 22239 */ 22240 AddressFmt.prototype._init = function () { 22241 this.style = this.info && this.info.formats && this.info.formats[this.styleName]; 22242 22243 // use generic default -- should not happen, but just in case... 22244 this.style = this.style || (this.info && this.info.formats["default"]) || "{streetAddress}\n{locality} {region} {postalCode}\n{country}"; 22245 }; 22246 22247 /** 22248 * This function formats a physical address (Address instance) for display. 22249 * Whitespace is trimmed from the beginning and end of final resulting string, and 22250 * multiple consecutive whitespace characters in the middle of the string are 22251 * compressed down to 1 space character. 22252 * 22253 * If the Address instance is for a locale that is different than the locale for this 22254 * formatter, then a hybrid address is produced. The country name is located in the 22255 * correct spot for the current formatter's locale, but the rest of the fields are 22256 * formatted according to the default style of the locale of the actual address. 22257 * 22258 * Example: a mailing address in China, but formatted for the US might produce the words 22259 * "People's Republic of China" in English at the last line of the address, and the 22260 * Chinese-style address will appear in the first line of the address. In the US, the 22261 * country is on the last line, but in China the country is usually on the first line. 22262 * 22263 * @param {Address} address Address to format 22264 * @returns {string} Returns a string containing the formatted address 22265 */ 22266 AddressFmt.prototype.format = function (address) { 22267 var ret, template, other, format; 22268 22269 if (!address) { 22270 return ""; 22271 } 22272 // console.log("formatting address: " + JSON.stringify(address)); 22273 if (address.countryCode && 22274 address.countryCode !== this.locale.region && 22275 Locale._isRegionCode(this.locale.region) && 22276 this.locale.region !== "XX") { 22277 // we are formatting an address that is sent from this country to another country, 22278 // so only the country should be in this locale, and the rest should be in the other 22279 // locale 22280 // console.log("formatting for another locale. Loading in its settings: " + address.countryCode); 22281 other = new AddressFmt({ 22282 locale: new Locale(address.countryCode), 22283 style: this.styleName 22284 }); 22285 return other.format(address); 22286 } 22287 22288 if (typeof(this.style) === 'object') { 22289 format = this.style[address.format || "latin"]; 22290 } else { 22291 format = this.style; 22292 } 22293 22294 // console.log("Using format: " + format); 22295 // make sure we have a blank string for any missing parts so that 22296 // those template parts get blanked out 22297 var params = { 22298 country: address.country || "", 22299 region: address.region || "", 22300 locality: address.locality || "", 22301 streetAddress: address.streetAddress || "", 22302 postalCode: address.postalCode || "", 22303 postOffice: address.postOffice || "" 22304 }; 22305 template = new IString(format); 22306 ret = template.format(params); 22307 ret = ret.replace(/[ \t]+/g, ' '); 22308 ret = ret.replace("\n ", "\n"); 22309 ret = ret.replace(" \n", "\n"); 22310 return ret.replace(/\n+/g, '\n').trim(); 22311 }; 22312 22313 22314 22315 /*< GlyphString.js */ 22316 /* 22317 * GlyphString.js - ilib string subclass that allows you to access 22318 * whole glyphs at a time 22319 * 22320 * Copyright © 2015-2017, JEDLSoft 22321 * 22322 * Licensed under the Apache License, Version 2.0 (the "License"); 22323 * you may not use this file except in compliance with the License. 22324 * You may obtain a copy of the License at 22325 * 22326 * http://www.apache.org/licenses/LICENSE-2.0 22327 * 22328 * Unless required by applicable law or agreed to in writing, software 22329 * distributed under the License is distributed on an "AS IS" BASIS, 22330 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22331 * 22332 * See the License for the specific language governing permissions and 22333 * limitations under the License. 22334 */ 22335 22336 // !depends IString.js CType.js Utils.js JSUtils.js 22337 // !data normdata ctype_m 22338 22339 22340 22341 /** 22342 * @class 22343 * Create a new glyph string instance. This string inherits from 22344 * the IString class, and adds methods that allow you to access 22345 * whole glyphs at a time. <p> 22346 * 22347 * In Unicode, various accented characters can be created by using 22348 * a base character and one or more combining characters following 22349 * it. These appear on the screen to the user as a single glyph. 22350 * For example, the Latin character "a" (U+0061) followed by the 22351 * combining diaresis character "¨" (U+0308) combine together to 22352 * form the "a with diaresis" glyph "ä", which looks like a single 22353 * character on the screen.<p> 22354 * 22355 * The big problem with combining characters for web developers is 22356 * that many CSS engines do not ellipsize text between glyphs. They 22357 * only deal with single Unicode characters. So if a particular space 22358 * only allows for 4 characters, the CSS engine will truncate a 22359 * string at 4 Unicode characters and then add the ellipsis (...) 22360 * character. What if the fourth Unicode character is the "a" and 22361 * the fifth one is the diaresis? Then a string like "xxxäxxx" that 22362 * is ellipsized at 4 characters will appear as "xxxa..." on the 22363 * screen instead of "xxxä...".<p> 22364 * 22365 * In the Latin script as it is commonly used, it is not so common 22366 * to form accented characters using combining accents, so the above 22367 * example is mostly for illustrative purposes. It is not unheard of 22368 * however. The situation is much, much worse in scripts such as Thai and 22369 * Devanagari that normally make very heavy use of combining characters. 22370 * These scripts do so because Unicode does not include pre-composed 22371 * versions of the accented characters like it does for Latin, so 22372 * combining accents are the only way to create these accented and 22373 * combined versions of the characters.<p> 22374 * 22375 * The solution to this problem is not to use the the CSS property 22376 * "text-overflow: ellipsis" in your web site, ever. Instead, use 22377 * a glyph string to truncate text between glyphs dynamically, 22378 * rather than truncating between Unicode characters using CSS.<p> 22379 * 22380 * Glyph strings are also useful for truncation, hyphenation, and 22381 * line wrapping, as all of these should be done between glyphs instead 22382 * of between characters.<p> 22383 * 22384 * The options parameter is optional, and may contain any combination 22385 * of the following properties:<p> 22386 * 22387 * <ul> 22388 * <li><i>onLoad</i> - a callback function to call when the locale data are 22389 * fully loaded. When the onLoad option is given, this object will attempt to 22390 * load any missing locale data using the ilib loader callback. 22391 * When the constructor is done (even if the data is already preassembled), the 22392 * onLoad function is called with the current instance as a parameter, so this 22393 * callback can be used with preassembled or dynamic loading or a mix of the two. 22394 * 22395 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 22396 * asynchronously. If this option is given as "false", then the "onLoad" 22397 * callback must be given, as the instance returned from this constructor will 22398 * not be usable for a while. 22399 * 22400 * <li><i>loadParams</i> - an object containing parameters to pass to the 22401 * loader callback function when locale data is missing. The parameters are not 22402 * interpretted or modified in any way. They are simply passed along. The object 22403 * may contain any property/value pairs as long as the calling code is in 22404 * agreement with the loader callback function as to what those parameters mean. 22405 * </ul> 22406 * 22407 * @constructor 22408 * @extends IString 22409 * @param {string|IString=} str initialize this instance with this string 22410 * @param {Object=} options options governing the way this instance works 22411 */ 22412 var GlyphString = function (str, options) { 22413 if (options && options.noinstance) { 22414 return; 22415 } 22416 22417 IString.call(this, str); 22418 22419 var sync = true; 22420 var loadParams = {}; 22421 if (options) { 22422 if (typeof(options.sync) === 'boolean') { 22423 sync = options.sync; 22424 } 22425 if (options.loadParams) { 22426 loadParams = options.loadParams; 22427 } 22428 } 22429 22430 CType._load("ctype_m", sync, loadParams, function() { 22431 if (!ilib.data.norm || JSUtils.isEmpty(ilib.data.norm.ccc)) { 22432 Utils.loadData({ 22433 object: GlyphString, 22434 locale: "-", 22435 name: "normdata.json", 22436 nonlocale: true, 22437 sync: sync, 22438 loadParams: loadParams, 22439 callback: ilib.bind(this, function (norm) { 22440 ilib.extend(ilib.data.norm, norm); 22441 if (options && typeof(options.onLoad) === 'function') { 22442 options.onLoad(this); 22443 } 22444 }) 22445 }); 22446 } else { 22447 if (options && typeof(options.onLoad) === 'function') { 22448 options.onLoad(this); 22449 } 22450 } 22451 }); 22452 }; 22453 22454 GlyphString.prototype = new IString(undefined); 22455 GlyphString.prototype.parent = IString; 22456 GlyphString.prototype.constructor = GlyphString; 22457 22458 /** 22459 * Return true if the given character is a leading Jamo (Choseong) character. 22460 * 22461 * @private 22462 * @static 22463 * @param {number} n code point to check 22464 * @return {boolean} true if the character is a leading Jamo character, 22465 * false otherwise 22466 */ 22467 GlyphString._isJamoL = function (n) { 22468 return (n >= 0x1100 && n <= 0x1112); 22469 }; 22470 22471 /** 22472 * Return true if the given character is a vowel Jamo (Jungseong) character. 22473 * 22474 * @private 22475 * @static 22476 * @param {number} n code point to check 22477 * @return {boolean} true if the character is a vowel Jamo character, 22478 * false otherwise 22479 */ 22480 GlyphString._isJamoV = function (n) { 22481 return (n >= 0x1161 && n <= 0x1175); 22482 }; 22483 22484 /** 22485 * Return true if the given character is a trailing Jamo (Jongseong) character. 22486 * 22487 * @private 22488 * @static 22489 * @param {number} n code point to check 22490 * @return {boolean} true if the character is a trailing Jamo character, 22491 * false otherwise 22492 */ 22493 GlyphString._isJamoT = function (n) { 22494 return (n >= 0x11A8 && n <= 0x11C2); 22495 }; 22496 22497 /** 22498 * Return true if the given character is a LV Jamo character. 22499 * LV Jamo character is a precomposed Hangul character with LV sequence. 22500 * 22501 * @private 22502 * @static 22503 * @param {number} n code point to check 22504 * @return {boolean} true if the character is a LV Jamo character, 22505 * false otherwise 22506 */ 22507 GlyphString._isJamoLV = function (n) { 22508 var syllableBase = 0xAC00; 22509 var leadingJamoCount = 19; 22510 var vowelJamoCount = 21; 22511 var trailingJamoCount = 28; 22512 var syllableCount = leadingJamoCount * vowelJamoCount * trailingJamoCount; 22513 var syllableIndex = n - syllableBase; 22514 // Check if n is a precomposed Hangul 22515 if (0 <= syllableIndex && syllableIndex < syllableCount) { 22516 // Check if n is a LV Jamo character 22517 if((syllableIndex % trailingJamoCount) == 0) { 22518 return true; 22519 } 22520 } 22521 return false; 22522 }; 22523 22524 /** 22525 * Return true if the given character is a precomposed Hangul character. 22526 * The precomposed Hangul character may be a LV Jamo character or a LVT Jamo Character. 22527 * 22528 * @private 22529 * @static 22530 * @param {number} n code point to check 22531 * @return {boolean} true if the character is a precomposed Hangul character, 22532 * false otherwise 22533 */ 22534 GlyphString._isHangul = function (n) { 22535 return (n >= 0xAC00 && n <= 0xD7A3); 22536 }; 22537 22538 /** 22539 * Algorithmically compose an L and a V combining Jamo characters into 22540 * a precomposed Korean syllabic Hangul character. Both should already 22541 * be in the proper ranges for L and V characters. 22542 * 22543 * @private 22544 * @static 22545 * @param {number} lead the code point of the lead Jamo character to compose 22546 * @param {number} trail the code point of the trailing Jamo character to compose 22547 * @return {string} the composed Hangul character 22548 */ 22549 GlyphString._composeJamoLV = function (lead, trail) { 22550 var lindex = lead - 0x1100; 22551 var vindex = trail - 0x1161; 22552 return IString.fromCodePoint(0xAC00 + (lindex * 21 + vindex) * 28); 22553 }; 22554 22555 /** 22556 * Algorithmically compose a Hangul LV and a combining Jamo T character 22557 * into a precomposed Korean syllabic Hangul character. 22558 * 22559 * @private 22560 * @static 22561 * @param {number} lead the code point of the lead Hangul character to compose 22562 * @param {number} trail the code point of the trailing Jamo T character to compose 22563 * @return {string} the composed Hangul character 22564 */ 22565 GlyphString._composeJamoLVT = function (lead, trail) { 22566 return IString.fromCodePoint(lead + (trail - 0x11A7)); 22567 }; 22568 22569 /** 22570 * Compose one character out of a leading character and a 22571 * trailing character. If the characters are Korean Jamo, they 22572 * will be composed algorithmically. If they are any other 22573 * characters, they will be looked up in the nfc tables. 22574 * 22575 * @private 22576 * @static 22577 * @param {string} lead leading character to compose 22578 * @param {string} trail the trailing character to compose 22579 * @return {string|null} the fully composed character, or undefined if 22580 * there is no composition for those two characters 22581 */ 22582 GlyphString._compose = function (lead, trail) { 22583 var first = lead.charCodeAt(0); 22584 var last = trail.charCodeAt(0); 22585 if (GlyphString._isJamoLV(first) && GlyphString._isJamoT(last)) { 22586 return GlyphString._composeJamoLVT(first, last); 22587 } else if (GlyphString._isJamoL(first) && GlyphString._isJamoV(last)) { 22588 return GlyphString._composeJamoLV(first, last); 22589 } 22590 22591 var c = lead + trail; 22592 return (ilib.data.norm.nfc && ilib.data.norm.nfc[c]); 22593 }; 22594 22595 /** 22596 * Return an iterator that will step through all of the characters 22597 * in the string one at a time, taking care to step through decomposed 22598 * characters and through surrogate pairs in the UTF-16 encoding 22599 * as single characters. <p> 22600 * 22601 * The GlyphString class will return decomposed Unicode characters 22602 * as a single unit that a user might see on the screen as a single 22603 * glyph. If the 22604 * next character in the iteration is a base character and it is 22605 * followed by combining characters, the base and all its following 22606 * combining characters are returned as a single unit.<p> 22607 * 22608 * The standard Javascript String's charAt() method only 22609 * returns information about a particular 16-bit character in the 22610 * UTF-16 encoding scheme. 22611 * If the index is pointing to a low- or high-surrogate character, 22612 * it will return that surrogate character rather 22613 * than the surrogate pair which represents a character 22614 * in the supplementary planes.<p> 22615 * 22616 * The iterator instance returned has two methods, hasNext() which 22617 * returns true if the iterator has more characters to iterate through, 22618 * and next() which returns the next character.<p> 22619 * 22620 * @override 22621 * @return {Object} an iterator 22622 * that iterates through all the characters in the string 22623 */ 22624 GlyphString.prototype.charIterator = function() { 22625 var it = IString.prototype.charIterator.call(this); 22626 22627 /** 22628 * @constructor 22629 */ 22630 function _chiterator (istring) { 22631 this.index = 0; 22632 this.spacingCombining = false; 22633 this.hasNext = function () { 22634 return !!this.nextChar || it.hasNext(); 22635 }; 22636 this.next = function () { 22637 var ch = this.nextChar || it.next(), 22638 prevCcc = ilib.data.norm.ccc[ch], 22639 nextCcc, 22640 composed = ch; 22641 22642 this.nextChar = undefined; 22643 this.spacingCombining = false; 22644 22645 if (ilib.data.norm.ccc && 22646 (typeof(ilib.data.norm.ccc[ch]) === 'undefined' || ilib.data.norm.ccc[ch] === 0)) { 22647 // found a starter... find all the non-starters until the next starter. Must include 22648 // the next starter because under some odd circumstances, two starters sometimes recompose 22649 // together to form another character 22650 var notdone = true; 22651 while (it.hasNext() && notdone) { 22652 this.nextChar = it.next(); 22653 nextCcc = ilib.data.norm.ccc[this.nextChar]; 22654 var codePoint = IString.toCodePoint(this.nextChar, 0); 22655 // Mn characters are Marks that are non-spacing. These do not take more room than an accent, so they should be 22656 // considered part of the on-screen glyph, even if they are non-combining. Mc are marks that are spacing 22657 // and combining, which means they are part of the glyph, but they cause the glyph to use up more space than 22658 // just the base character alone. 22659 var isMn = CType._inRange(codePoint, "Mn", ilib.data.ctype_m); 22660 var isMc = CType._inRange(codePoint, "Mc", ilib.data.ctype_m); 22661 if (isMn || isMc || (typeof(nextCcc) !== 'undefined' && nextCcc !== 0)) { 22662 if (isMc) { 22663 this.spacingCombining = true; 22664 } 22665 ch += this.nextChar; 22666 this.nextChar = undefined; 22667 } else { 22668 // found the next starter. See if this can be composed with the previous starter 22669 var testChar = GlyphString._compose(composed, this.nextChar); 22670 if (prevCcc === 0 && typeof(testChar) !== 'undefined') { 22671 // not blocked and there is a mapping 22672 composed = testChar; 22673 ch += this.nextChar; 22674 this.nextChar = undefined; 22675 } else { 22676 // finished iterating, leave this.nextChar for the next next() call 22677 notdone = false; 22678 } 22679 } 22680 prevCcc = nextCcc; 22681 } 22682 } 22683 return ch; 22684 }; 22685 // Returns true if the last character returned by the "next" method included 22686 // spacing combining characters. If it does, then the character was wider than 22687 // just the base character alone, and the truncation code will not add it. 22688 this.wasSpacingCombining = function() { 22689 return this.spacingCombining; 22690 }; 22691 }; 22692 return new _chiterator(this); 22693 }; 22694 22695 /** 22696 * Truncate the current string at the given number of whole glyphs and return 22697 * the resulting string. 22698 * 22699 * @param {number} length the number of whole glyphs to keep in the string 22700 * @return {string} a string truncated to the requested number of glyphs 22701 */ 22702 GlyphString.prototype.truncate = function(length) { 22703 var it = this.charIterator(); 22704 var tr = ""; 22705 for (var i = 0; i < length-1 && it.hasNext(); i++) { 22706 tr += it.next(); 22707 } 22708 22709 /* 22710 * handle the last character separately. If it contains spacing combining 22711 * accents, then we must assume that it uses up more horizontal space on 22712 * the screen than just the base character by itself, and therefore this 22713 * method will not truncate enough characters to fit in the given length. 22714 * In this case, we have to chop off not only the combining characters, 22715 * but also the base character as well because the base without the 22716 * combining accents is considered a different character. 22717 */ 22718 if (i < length && it.hasNext()) { 22719 var c = it.next(); 22720 if (!it.wasSpacingCombining()) { 22721 tr += c; 22722 } 22723 } 22724 return tr; 22725 }; 22726 22727 /** 22728 * Truncate the current string at the given number of glyphs and add an ellipsis 22729 * to indicate that is more to the string. The ellipsis forms the last character 22730 * in the string, so the string is actually truncated at length-1 glyphs. 22731 * 22732 * @param {number} length the number of whole glyphs to keep in the string 22733 * including the ellipsis 22734 * @return {string} a string truncated to the requested number of glyphs 22735 * with an ellipsis 22736 */ 22737 GlyphString.prototype.ellipsize = function(length) { 22738 return this.truncate(length > 0 ? length-1 : 0) + "…"; 22739 }; 22740 22741 22742 22743 /*< NormString.js */ 22744 /* 22745 * NormString.js - ilib normalized string subclass definition 22746 * 22747 * Copyright © 2013-2015, JEDLSoft 22748 * 22749 * Licensed under the Apache License, Version 2.0 (the "License"); 22750 * you may not use this file except in compliance with the License. 22751 * You may obtain a copy of the License at 22752 * 22753 * http://www.apache.org/licenses/LICENSE-2.0 22754 * 22755 * Unless required by applicable law or agreed to in writing, software 22756 * distributed under the License is distributed on an "AS IS" BASIS, 22757 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22758 * 22759 * See the License for the specific language governing permissions and 22760 * limitations under the License. 22761 */ 22762 22763 // !depends IString.js GlyphString.js Utils.js 22764 22765 22766 22767 /** 22768 * @class 22769 * Create a new normalized string instance. This string inherits from 22770 * the GlyphString class, and adds the normalize method. It can be 22771 * used anywhere that a normal Javascript string is used. <p> 22772 * 22773 * 22774 * @constructor 22775 * @extends GlyphString 22776 * @param {string|IString=} str initialize this instance with this string 22777 */ 22778 var NormString = function (str) { 22779 GlyphString.call(this, str); 22780 }; 22781 22782 NormString.prototype = new GlyphString("", {noinstance:true}); 22783 NormString.prototype.parent = GlyphString; 22784 NormString.prototype.constructor = NormString; 22785 22786 /** 22787 * Initialize the normalized string routines statically. This 22788 * is intended to be called in a dynamic-load version of ilib 22789 * to load the data need to normalize strings before any instances 22790 * of NormString are created.<p> 22791 * 22792 * The options parameter may contain any of the following properties: 22793 * 22794 * <ul> 22795 * <li><i>form</i> - {string} the normalization form to load 22796 * <li><i>script</i> - {string} load the normalization for this script. If the 22797 * script is given as "all" then the normalization data for all scripts 22798 * is loaded at the same time 22799 * <li><i>sync</i> - {boolean} whether to load the files synchronously or not 22800 * <li><i>loadParams</i> - {Object} parameters to the loader function 22801 * <li><i>onLoad</i> - {function()} a function to call when the 22802 * files are done being loaded 22803 * </ul> 22804 * 22805 * @param {Object} options an object containing properties that govern 22806 * how to initialize the data 22807 */ 22808 NormString.init = function(options) { 22809 if (!ilib._load || (typeof(ilib._load) !== 'function' && typeof(ilib._load.loadFiles) !== 'function')) { 22810 // can't do anything 22811 return; 22812 } 22813 var form = "nfkc"; 22814 var script = "all"; 22815 var sync = true; 22816 var onLoad = undefined; 22817 var loadParams = undefined; 22818 if (options) { 22819 form = options.form || "nfkc"; 22820 script = options.script || "all"; 22821 sync = typeof(options.sync) !== 'undefined' ? options.sync : true; 22822 onLoad = typeof(options.onLoad) === 'function' ? options.onLoad : undefined; 22823 if (options.loadParams) { 22824 loadParams = options.loadParams; 22825 } 22826 } 22827 var formDependencies = { 22828 "nfd": ["nfd"], 22829 "nfc": ["nfd"], 22830 "nfkd": ["nfkd", "nfd"], 22831 "nfkc": ["nfkd", "nfd"] 22832 }; 22833 var files = ["normdata.json"]; 22834 var forms = formDependencies[form]; 22835 for (var f in forms) { 22836 files.push(forms[f] + "/" + script + ".json"); 22837 } 22838 22839 if (JSUtils.isEmpty(ilib.data.norm.ccc) || JSUtils.isEmpty(ilib.data.norm.nfd) || JSUtils.isEmpty(ilib.data.norm.nfkd)) { 22840 //console.log("loading files " + JSON.stringify(files)); 22841 Utils._callLoadData(files, sync, loadParams, function(arr) { 22842 ilib.extend(ilib.data.norm, arr[0]); 22843 for (var i = 1; i < arr.length; i++) { 22844 if (typeof(arr[i]) !== 'undefined') { 22845 ilib.extend(ilib.data.norm[forms[i-1]], arr[i]); 22846 } 22847 } 22848 22849 if (onLoad) { 22850 onLoad(arr); 22851 } 22852 }); 22853 } 22854 }; 22855 22856 /** 22857 * Algorithmically decompose a precomposed Korean syllabic Hangul 22858 * character into its individual combining Jamo characters. The given 22859 * character must be in the range of Hangul characters U+AC00 to U+D7A3. 22860 * 22861 * @private 22862 * @static 22863 * @param {number} cp code point of a Korean Hangul character to decompose 22864 * @return {string} the decomposed string of Jamo characters 22865 */ 22866 NormString._decomposeHangul = function (cp) { 22867 var sindex = cp - 0xAC00; 22868 var result = String.fromCharCode(0x1100 + sindex / 588) + 22869 String.fromCharCode(0x1161 + (sindex % 588) / 28); 22870 var t = sindex % 28; 22871 if (t !== 0) { 22872 result += String.fromCharCode(0x11A7 + t); 22873 } 22874 return result; 22875 }; 22876 22877 /** 22878 * Expand one character according to the given canonical and 22879 * compatibility mappings. 22880 * 22881 * @private 22882 * @static 22883 * @param {string} ch character to map 22884 * @param {Object} canon the canonical mappings to apply 22885 * @param {Object=} compat the compatibility mappings to apply, or undefined 22886 * if only the canonical mappings are needed 22887 * @return {string} the mapped character 22888 */ 22889 NormString._expand = function (ch, canon, compat) { 22890 var i, 22891 expansion = "", 22892 n = ch.charCodeAt(0); 22893 if (GlyphString._isHangul(n)) { 22894 expansion = NormString._decomposeHangul(n); 22895 } else { 22896 var result = canon[ch]; 22897 if (!result && compat) { 22898 result = compat[ch]; 22899 } 22900 if (result && result !== ch) { 22901 for (i = 0; i < result.length; i++) { 22902 expansion += NormString._expand(result[i], canon, compat); 22903 } 22904 } else { 22905 expansion = ch; 22906 } 22907 } 22908 return expansion; 22909 }; 22910 22911 /** 22912 * Perform the Unicode Normalization Algorithm upon the string and return 22913 * the resulting new string. The current string is not modified. 22914 * 22915 * <h2>Forms</h2> 22916 * 22917 * The forms of possible normalizations are defined by the <a 22918 * href="http://www.unicode.org/reports/tr15/">Unicode Standard 22919 * Annex (UAX) 15</a>. The form parameter is a string that may have one 22920 * of the following values: 22921 * 22922 * <ul> 22923 * <li>nfd - Canonical decomposition. This decomposes characters into 22924 * their exactly equivalent forms. For example, "ü" would decompose 22925 * into a "u" followed by the combining diaeresis character. 22926 * <li>nfc - Canonical decomposition followed by canonical composition. 22927 * This decomposes and then recomposes character into their shortest 22928 * exactly equivalent forms by recomposing as many combining characters 22929 * as possible. For example, "ü" followed by a combining 22930 * macron character would decompose into a "u" followed by the combining 22931 * macron characters the combining diaeresis character, and then be recomposed into 22932 * the u with macron and diaeresis "ṻ" character. The reason that 22933 * the "nfc" form decomposes and then recomposes is that combining characters 22934 * have a specific order under the Unicode Normalization Algorithm, and 22935 * partly composed characters such as the "ü" followed by combining 22936 * marks may change the order of the combining marks when decomposed and 22937 * recomposed. 22938 * <li>nfkd - Compatibility decomposition. This decomposes characters 22939 * into compatible forms that may not be exactly equivalent semantically, 22940 * as well as performing canonical decomposition as well. 22941 * For example, the "œ" ligature character decomposes to the two 22942 * characters "oe" because they are compatible even though they are not 22943 * exactly the same semantically. 22944 * <li>nfkc - Compatibility decomposition followed by canonical composition. 22945 * This decomposes characters into compatible forms, then recomposes 22946 * characters using the canonical composition. That is, it breaks down 22947 * characters into the compatible forms, and then recombines all combining 22948 * marks it can with their base characters. For example, the character 22949 * "ǽ" would be normalized to "aé" by first decomposing 22950 * the character into "a" followed by "e" followed by the combining acute accent 22951 * combining mark, and then recomposed to an "a" followed by the "e" 22952 * with acute accent. 22953 * </ul> 22954 * 22955 * <h2>Operation</h2> 22956 * 22957 * Two strings a and b can be said to be canonically equivalent if 22958 * normalize(a) = normalize(b) 22959 * under the nfc normalization form. Two strings can be said to be compatible if 22960 * normalize(a) = normalize(b) under the nfkc normalization form.<p> 22961 * 22962 * The canonical normalization is often used to see if strings are 22963 * equivalent to each other, and thus is useful when implementing parsing 22964 * algorithms or exact matching algorithms. It can also be used to ensure 22965 * that any string output produces a predictable sequence of characters.<p> 22966 * 22967 * Compatibility normalization 22968 * does not always preserve the semantic meaning of all the characters, 22969 * although this is sometimes the behaviour that you are after. It is useful, 22970 * for example, when doing searches of user-input against text in documents 22971 * where the matches are supposed to "fuzzy". In this case, both the query 22972 * string and the document string would be mapped to their compatibility 22973 * normalized forms, and then compared.<p> 22974 * 22975 * Compatibility normalization also does not guarantee round-trip conversion 22976 * to and from legacy character sets as the normalization is "lossy". It is 22977 * akin to doing a lower- or upper-case conversion on text -- after casing, 22978 * you cannot tell what case each character is in the original string. It is 22979 * good for matching and searching, but it rarely good for output because some 22980 * distinctions or meanings in the original text have been lost.<p> 22981 * 22982 * Note that W3C normalization for HTML also escapes and unescapes 22983 * HTML character entities such as "ü" for u with diaeresis. This 22984 * method does not do such escaping or unescaping. If normalization is required 22985 * for HTML strings with entities, unescaping should be performed on the string 22986 * prior to calling this method.<p> 22987 * 22988 * <h2>Data</h2> 22989 * 22990 * Normalization requires a fair amount of mapping data, much of which you may 22991 * not need for the characters expected in your texts. It is possible to assemble 22992 * a copy of ilib that saves space by only including normalization data for 22993 * those scripts that you expect to encounter in your data.<p> 22994 * 22995 * The normalization data is organized by normalization form and within there 22996 * by script. To include the normalization data for a particular script with 22997 * a particular normalization form, use the directive: 22998 * 22999 * <pre><code> 23000 * !depends <form>/<script>.js 23001 * </code></pre> 23002 * 23003 * Where <form> is the normalization form ("nfd", "nfc", "nfkd", or "nfkc"), and 23004 * <script> is the ISO 15924 code for the script you would like to 23005 * support. Example: to load in the NFC data for Cyrillic, you would use: 23006 * 23007 * <pre><code> 23008 * !depends nfc/Cyrl.js 23009 * </code></pre> 23010 * 23011 * Note that because certain normalization forms include others in their algorithm, 23012 * their data also depends on the data for the other forms. For example, if you 23013 * include the "nfc" data for a script, you will automatically get the "nfd" data 23014 * for that same script as well because the NFC algorithm does NFD normalization 23015 * first. Here are the dependencies:<p> 23016 * 23017 * <ul> 23018 * <li>NFD -> no dependencies 23019 * <li>NFC -> NFD 23020 * <li>NFKD -> NFD 23021 * <li>NFKC -> NFKD, NFD, NFC 23022 * </ul> 23023 * 23024 * A special value for the script dependency is "all" which will cause the data for 23025 * all scripts 23026 * to be loaded for that normalization form. This would be useful if you know that 23027 * you are going to normalize a lot of multilingual text or cannot predict which scripts 23028 * will appear in the input. Because the NFKC form depends on all others, you can 23029 * get all of the data for all forms automatically by depending on "nfkc/all.js". 23030 * Note that the normalization data for practically all script automatically depend 23031 * on data for the Common script (code "Zyyy") which contains all of the characters 23032 * that are commonly used in many different scripts. Examples of characters in the 23033 * Common script are the ASCII punctuation characters, or the ASCII Arabic 23034 * numerals "0" through "9".<p> 23035 * 23036 * By default, none of the data for normalization is automatically 23037 * included in the preassembled iliball.js file. 23038 * If you would like to normalize strings, you must assemble 23039 * your own copy of ilib and explicitly include the normalization data 23040 * for those scripts as per the instructions above. This normalization method will 23041 * produce output, even without the normalization data. However, the output will be 23042 * simply the same thing as its input for all scripts 23043 * except Korean Hangul and Jamo, which are decomposed and recomposed 23044 * algorithmically and therefore do not rely on data.<p> 23045 * 23046 * If characters are encountered for which there are no normalization data, they 23047 * will be passed through to the output string unmodified. 23048 * 23049 * @param {string} form The normalization form requested 23050 * @return {IString} a new instance of an IString that has been normalized 23051 * according to the requested form. The current instance is not modified. 23052 */ 23053 NormString.prototype.normalize = function (form) { 23054 var i; 23055 23056 if (typeof(form) !== 'string' || this.str.length === 0) { 23057 return new IString(this.str); 23058 } 23059 23060 var nfc = false, 23061 nfkd = false; 23062 23063 switch (form) { 23064 default: 23065 break; 23066 23067 case "nfc": 23068 nfc = true; 23069 break; 23070 23071 case "nfkd": 23072 nfkd = true; 23073 break; 23074 23075 case "nfkc": 23076 nfkd = true; 23077 nfc = true; 23078 break; 23079 } 23080 23081 // decompose 23082 var decomp = ""; 23083 23084 if (nfkd) { 23085 var ch, it = IString.prototype.charIterator.call(this); 23086 while (it.hasNext()) { 23087 ch = it.next(); 23088 decomp += NormString._expand(ch, ilib.data.norm.nfd, ilib.data.norm.nfkd); 23089 } 23090 } else { 23091 var ch, it = IString.prototype.charIterator.call(this); 23092 while (it.hasNext()) { 23093 ch = it.next(); 23094 decomp += NormString._expand(ch, ilib.data.norm.nfd); 23095 } 23096 } 23097 23098 // now put the combining marks in a fixed order by 23099 // sorting on the combining class 23100 function compareByCCC(left, right) { 23101 return ilib.data.norm.ccc[left] - ilib.data.norm.ccc[right]; 23102 } 23103 23104 function ccc(c) { 23105 return ilib.data.norm.ccc[c] || 0; 23106 } 23107 23108 function sortChars(arr, comp) { 23109 // qt/qml's Javascript engine re-arranges entries that are equal to 23110 // each other. Technically, that is a correct behaviour, but it is 23111 // not desirable. All the other engines leave equivalent entries 23112 // where they are. This bubblesort emulates what the other engines 23113 // do. Fortunately, the arrays we are sorting are a max of 5 or 6 23114 // entries, so performance is not a big deal here. 23115 if (ilib._getPlatform() === "qt") { 23116 var tmp; 23117 for (var i = arr.length-1; i > 0; i--) { 23118 for (var j = 0; j < i; j++) { 23119 if (comp(arr[j], arr[j+1]) > 0) { 23120 tmp = arr[j]; 23121 arr[j] = arr[j+1]; 23122 arr[j+1] = tmp; 23123 } 23124 } 23125 } 23126 return arr; 23127 } else { 23128 return arr.sort(comp); 23129 } 23130 } 23131 23132 var dstr = new IString(decomp); 23133 var it = dstr.charIterator(); 23134 var cpArray = []; 23135 23136 // easier to deal with as an array of chars 23137 while (it.hasNext()) { 23138 cpArray.push(it.next()); 23139 } 23140 23141 i = 0; 23142 while (i < cpArray.length) { 23143 if (typeof(ilib.data.norm.ccc[cpArray[i]]) !== 'undefined' && ccc(cpArray[i]) !== 0) { 23144 // found a non-starter... rearrange all the non-starters until the next starter 23145 var end = i+1; 23146 while (end < cpArray.length && 23147 typeof(ilib.data.norm.ccc[cpArray[end]]) !== 'undefined' && 23148 ccc(cpArray[end]) !== 0) { 23149 end++; 23150 } 23151 23152 // simple sort of the non-starter chars 23153 if (end - i > 1) { 23154 cpArray = cpArray.slice(0,i).concat(sortChars(cpArray.slice(i, end), compareByCCC), cpArray.slice(end)); 23155 } 23156 } 23157 i++; 23158 } 23159 23160 if (nfc) { 23161 i = 0; 23162 while (i < cpArray.length) { 23163 if (typeof(ilib.data.norm.ccc[cpArray[i]]) === 'undefined' || ilib.data.norm.ccc[cpArray[i]] === 0) { 23164 // found a starter... find all the non-starters until the next starter. Must include 23165 // the next starter because under some odd circumstances, two starters sometimes recompose 23166 // together to form another character 23167 var end = i+1; 23168 var notdone = true; 23169 while (end < cpArray.length && notdone) { 23170 if (typeof(ilib.data.norm.ccc[cpArray[end]]) !== 'undefined' && 23171 ilib.data.norm.ccc[cpArray[end]] !== 0) { 23172 if (ccc(cpArray[end-1]) < ccc(cpArray[end])) { 23173 // not blocked 23174 var testChar = GlyphString._compose(cpArray[i], cpArray[end]); 23175 if (typeof(testChar) !== 'undefined') { 23176 cpArray[i] = testChar; 23177 23178 // delete the combining char 23179 cpArray.splice(end,1); 23180 23181 // restart the iteration, just in case there is more to recompose with the new char 23182 end = i; 23183 } 23184 } 23185 end++; 23186 } else { 23187 // found the next starter. See if this can be composed with the previous starter 23188 var testChar = GlyphString._compose(cpArray[i], cpArray[end]); 23189 if (ccc(cpArray[end-1]) === 0 && typeof(testChar) !== 'undefined') { 23190 // not blocked and there is a mapping 23191 cpArray[i] = testChar; 23192 23193 // delete the combining char 23194 cpArray.splice(end,1); 23195 23196 // restart the iteration, just in case there is more to recompose with the new char 23197 end = i+1; 23198 } else { 23199 // finished iterating 23200 notdone = false; 23201 } 23202 } 23203 } 23204 } 23205 i++; 23206 } 23207 } 23208 23209 return new IString(cpArray.length > 0 ? cpArray.join("") : ""); 23210 }; 23211 23212 /** 23213 * @override 23214 * Return an iterator that will step through all of the characters 23215 * in the string one at a time, taking care to step through decomposed 23216 * characters and through surrogate pairs in UTF-16 encoding 23217 * properly. <p> 23218 * 23219 * The NormString class will return decomposed Unicode characters 23220 * as a single unit that a user might see on the screen. If the 23221 * next character in the iteration is a base character and it is 23222 * followed by combining characters, the base and all its following 23223 * combining characters are returned as a single unit.<p> 23224 * 23225 * The standard Javascript String's charAt() method only 23226 * returns information about a particular 16-bit character in the 23227 * UTF-16 encoding scheme. 23228 * If the index is pointing to a low- or high-surrogate character, 23229 * it will return that surrogate character rather 23230 * than the surrogate pair which represents a character 23231 * in the supplementary planes.<p> 23232 * 23233 * The iterator instance returned has two methods, hasNext() which 23234 * returns true if the iterator has more characters to iterate through, 23235 * and next() which returns the next character.<p> 23236 * 23237 * @return {Object} an iterator 23238 * that iterates through all the characters in the string 23239 */ 23240 NormString.prototype.charIterator = function() { 23241 var it = IString.prototype.charIterator.call(this); 23242 23243 /** 23244 * @constructor 23245 */ 23246 function _chiterator (istring) { 23247 /** 23248 * @private 23249 */ 23250 var ccc = function(c) { 23251 return ilib.data.norm.ccc[c] || 0; 23252 }; 23253 23254 this.index = 0; 23255 this.hasNext = function () { 23256 return !!this.nextChar || it.hasNext(); 23257 }; 23258 this.next = function () { 23259 var ch = this.nextChar || it.next(), 23260 prevCcc = ccc(ch), 23261 nextCcc, 23262 composed = ch; 23263 23264 this.nextChar = undefined; 23265 23266 if (ilib.data.norm.ccc && 23267 (typeof(ilib.data.norm.ccc[ch]) === 'undefined' || ccc(ch) === 0)) { 23268 // found a starter... find all the non-starters until the next starter. Must include 23269 // the next starter because under some odd circumstances, two starters sometimes recompose 23270 // together to form another character 23271 var notdone = true; 23272 while (it.hasNext() && notdone) { 23273 this.nextChar = it.next(); 23274 nextCcc = ccc(this.nextChar); 23275 if (typeof(ilib.data.norm.ccc[this.nextChar]) !== 'undefined' && nextCcc !== 0) { 23276 ch += this.nextChar; 23277 this.nextChar = undefined; 23278 } else { 23279 // found the next starter. See if this can be composed with the previous starter 23280 var testChar = GlyphString._compose(composed, this.nextChar); 23281 if (prevCcc === 0 && typeof(testChar) !== 'undefined') { 23282 // not blocked and there is a mapping 23283 composed = testChar; 23284 ch += this.nextChar; 23285 this.nextChar = undefined; 23286 } else { 23287 // finished iterating, leave this.nextChar for the next next() call 23288 notdone = false; 23289 } 23290 } 23291 prevCcc = nextCcc; 23292 } 23293 } 23294 return ch; 23295 }; 23296 }; 23297 return new _chiterator(this); 23298 }; 23299 23300 23301 /*< CodePointSource.js */ 23302 /* 23303 * CodePointSource.js - Source of code points from a string 23304 * 23305 * Copyright © 2013-2015, JEDLSoft 23306 * 23307 * Licensed under the Apache License, Version 2.0 (the "License"); 23308 * you may not use this file except in compliance with the License. 23309 * You may obtain a copy of the License at 23310 * 23311 * http://www.apache.org/licenses/LICENSE-2.0 23312 * 23313 * Unless required by applicable law or agreed to in writing, software 23314 * distributed under the License is distributed on an "AS IS" BASIS, 23315 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23316 * 23317 * See the License for the specific language governing permissions and 23318 * limitations under the License. 23319 */ 23320 23321 // !depends isPunct.js NormString.js 23322 23323 23324 /** 23325 * @class 23326 * Represents a buffered source of code points. The input string is first 23327 * normalized so that combining characters come out in a standardized order. 23328 * If the "ignorePunctuation" flag is turned on, then punctuation 23329 * characters are skipped. 23330 * 23331 * @constructor 23332 * @private 23333 * @param {NormString|string} str a string to get code points from 23334 * @param {boolean} ignorePunctuation whether or not to ignore punctuation 23335 * characters 23336 */ 23337 var CodePointSource = function(str, ignorePunctuation) { 23338 this.chars = []; 23339 // first convert the string to a normalized sequence of characters 23340 var s = (typeof(str) === "string") ? new NormString(str) : str; 23341 this.it = s.charIterator(); 23342 this.ignorePunctuation = typeof(ignorePunctuation) === "boolean" && ignorePunctuation; 23343 }; 23344 23345 /** 23346 * Return the first num code points in the source without advancing the 23347 * source pointer. If there are not enough code points left in the 23348 * string to satisfy the request, this method will return undefined. 23349 * 23350 * @param {number} num the number of characters to peek ahead 23351 * @return {string|undefined} a string formed out of up to num code points from 23352 * the start of the string, or undefined if there are not enough character left 23353 * in the source to complete the request 23354 */ 23355 CodePointSource.prototype.peek = function(num) { 23356 if (num < 1) { 23357 return undefined; 23358 } 23359 if (this.chars.length < num && this.it.hasNext()) { 23360 for (var i = 0; this.chars.length < 4 && this.it.hasNext(); i++) { 23361 var c = this.it.next(); 23362 if (c && !this.ignorePunctuation || !isPunct(c)) { 23363 this.chars.push(c); 23364 } 23365 } 23366 } 23367 if (this.chars.length < num) { 23368 return undefined; 23369 } 23370 return this.chars.slice(0, num).join(""); 23371 }; 23372 /** 23373 * Advance the source pointer by the given number of code points. 23374 * @param {number} num number of code points to advance 23375 */ 23376 CodePointSource.prototype.consume = function(num) { 23377 if (num > 0) { 23378 this.peek(num); // for the iterator to go forward if needed 23379 if (num < this.chars.length) { 23380 this.chars = this.chars.slice(num); 23381 } else { 23382 this.chars = []; 23383 } 23384 } 23385 }; 23386 23387 23388 23389 /*< ElementIterator.js */ 23390 /* 23391 * ElementIterator.js - Iterate through a list of collation elements 23392 * 23393 * Copyright © 2013-2015, JEDLSoft 23394 * 23395 * Licensed under the Apache License, Version 2.0 (the "License"); 23396 * you may not use this file except in compliance with the License. 23397 * You may obtain a copy of the License at 23398 * 23399 * http://www.apache.org/licenses/LICENSE-2.0 23400 * 23401 * Unless required by applicable law or agreed to in writing, software 23402 * distributed under the License is distributed on an "AS IS" BASIS, 23403 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23404 * 23405 * See the License for the specific language governing permissions and 23406 * limitations under the License. 23407 */ 23408 23409 /** 23410 * @class 23411 * An iterator through a sequence of collation elements. This 23412 * iterator takes a source of code points, converts them into 23413 * collation elements, and allows the caller to get single 23414 * elements at a time. 23415 * 23416 * @constructor 23417 * @private 23418 * @param {CodePointSource} source source of code points to 23419 * convert to collation elements 23420 * @param {Object} map mapping from sequences of code points to 23421 * collation elements 23422 * @param {number} keysize size in bits of the collation elements 23423 */ 23424 var ElementIterator = function (source, map, keysize) { 23425 this.elements = []; 23426 this.source = source; 23427 this.map = map; 23428 this.keysize = keysize; 23429 }; 23430 23431 /** 23432 * @private 23433 */ 23434 ElementIterator.prototype._fillBuffer = function () { 23435 var str = undefined; 23436 23437 // peek ahead by up to 4 characters, which may combine 23438 // into 1 or more collation elements 23439 for (var i = 4; i > 0; i--) { 23440 str = this.source.peek(i); 23441 if (str && this.map[str]) { 23442 this.elements = this.elements.concat(this.map[str]); 23443 this.source.consume(i); 23444 return; 23445 } 23446 } 23447 23448 if (str) { 23449 // no mappings for the first code point, so just use its 23450 // Unicode code point as a proxy for its sort order. Shift 23451 // it by the key size so that everything unknown sorts 23452 // after things that have mappings 23453 this.elements.push(str.charCodeAt(0) << this.keysize); 23454 this.source.consume(1); 23455 } else { 23456 // end of the string 23457 return undefined; 23458 } 23459 }; 23460 23461 /** 23462 * Return true if there are more collation elements left to 23463 * iterate through. 23464 * @returns {boolean} true if there are more elements left to 23465 * iterate through, and false otherwise 23466 */ 23467 ElementIterator.prototype.hasNext = function () { 23468 if (this.elements.length < 1) { 23469 this._fillBuffer(); 23470 } 23471 return !!this.elements.length; 23472 }; 23473 23474 /** 23475 * Return the next collation element. If more than one collation 23476 * element is generated from a sequence of code points 23477 * (ie. an "expansion"), then this class will buffer the 23478 * other elements and return them on subsequent calls to 23479 * this method. 23480 * 23481 * @returns {number|undefined} the next collation element or 23482 * undefined for no more collation elements 23483 */ 23484 ElementIterator.prototype.next = function () { 23485 if (this.elements.length < 1) { 23486 this._fillBuffer(); 23487 } 23488 var ret = this.elements[0]; 23489 this.elements = this.elements.slice(1); 23490 return ret; 23491 }; 23492 23493 23494 23495 /*< Collator.js */ 23496 /* 23497 * Collator.js - Collation routines 23498 * 23499 * Copyright © 2013-2015, JEDLSoft 23500 * 23501 * Licensed under the Apache License, Version 2.0 (the "License"); 23502 * you may not use this file except in compliance with the License. 23503 * You may obtain a copy of the License at 23504 * 23505 * http://www.apache.org/licenses/LICENSE-2.0 23506 * 23507 * Unless required by applicable law or agreed to in writing, software 23508 * distributed under the License is distributed on an "AS IS" BASIS, 23509 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23510 * 23511 * See the License for the specific language governing permissions and 23512 * limitations under the License. 23513 */ 23514 23515 /* !depends 23516 Locale.js 23517 ilib.js 23518 INumber.js 23519 isPunct.js 23520 NormString.js 23521 MathUtils.js 23522 Utils.js 23523 JSUtils.js 23524 LocaleInfo.js 23525 CodePointSource.js 23526 ElementIterator.js 23527 */ 23528 23529 // !data collation 23530 23531 23532 /** 23533 * @class 23534 * A class that implements a locale-sensitive comparator function 23535 * for use with sorting function. The comparator function 23536 * assumes that the strings it is comparing contain Unicode characters 23537 * encoded in UTF-16.<p> 23538 * 23539 * Collations usually depend only on the language, because most collation orders 23540 * are shared between locales that speak the same language. There are, however, a 23541 * number of instances where a locale collates differently than other locales 23542 * that share the same language. There are also a number of instances where a 23543 * locale collates differently based on the script used. This object can handle 23544 * these cases automatically if a full locale is specified in the options rather 23545 * than just a language code.<p> 23546 * 23547 * <h2>Options</h2> 23548 * 23549 * The options parameter can contain any of the following properties: 23550 * 23551 * <ul> 23552 * <li><i>locale</i> - String|Locale. The locale which the comparator function 23553 * will collate with. Default: the current iLib locale. 23554 * 23555 * <li><i>sensitivity</i> - String. Sensitivity or strength of collator. This is one of 23556 * "primary", "base", "secondary", "accent", "tertiary", "case", "quaternary", or 23557 * "variant". Default: "primary" 23558 * <ol> 23559 * <li>base or primary - Only the primary distinctions between characters are significant. 23560 * Another way of saying that is that the collator will be case-, accent-, and 23561 * variation-insensitive, and only distinguish between the base characters 23562 * <li>case or secondary - Both the primary and secondary distinctions between characters 23563 * are significant. That is, the collator will be accent- and variation-insensitive 23564 * and will distinguish between base characters and character case. 23565 * <li>accent or tertiary - The primary, secondary, and tertiary distinctions between 23566 * characters are all significant. That is, the collator will be 23567 * variation-insensitive, but accent-, case-, and base-character-sensitive. 23568 * <li>variant or quaternary - All distinctions between characters are significant. That is, 23569 * the algorithm is base character-, case-, accent-, and variation-sensitive. 23570 * </ol> 23571 * 23572 * <li><i>upperFirst</i> - boolean. When collating case-sensitively in a script that 23573 * has the concept of case, put upper-case 23574 * characters first, otherwise lower-case will come first. Warning: some browsers do 23575 * not implement this feature or at least do not implement it properly, so if you are 23576 * using the native collator with this option, you may get different results in different 23577 * browsers. To guarantee the same results, set useNative to false to use the ilib 23578 * collator implementation. This of course will be somewhat slower, but more 23579 * predictable. Default: true 23580 * 23581 * <li><i>reverse</i> - boolean. Return the list sorted in reverse order. When the 23582 * upperFirst option is also set to true, upper-case characters would then come at 23583 * the end of the list. Default: false. 23584 * 23585 * <li><i>scriptOrder</i> - string. When collating strings in multiple scripts, 23586 * this property specifies what order those scripts should be sorted. The default 23587 * Unicode Collation Algorithm (UCA) already has a default order for scripts, but 23588 * this can be tailored via this property. The value of this option is a 23589 * space-separated list of ISO 15924 scripts codes. If a code is specified in this 23590 * property, its default data must be included using the JS assembly tool. If the 23591 * data is not included, the ordering for the script will be ignored. Default: 23592 * the default order defined by the UCA. 23593 * 23594 * <li><i>style</i> - The value of the style parameter is dependent on the locale. 23595 * For some locales, there are different styles of collating strings depending 23596 * on what kind of strings are being collated or what the preference of the user 23597 * is. For example, in German, there is a phonebook order and a dictionary ordering 23598 * that sort the same array of strings slightly differently. 23599 * The static method {@link Collator#getAvailableStyles} will return a list of styles that ilib 23600 * currently knows about for any given locale. If the value of the style option is 23601 * not recognized for a locale, it will be ignored. Default style is "standard".<p> 23602 * 23603 * <li><i>usage</i> - Whether this collator will be used for searching or sorting. 23604 * Valid values are simply the strings "sort" or "search". When used for sorting, 23605 * it is good idea if a collator produces a stable sort. That is, the order of the 23606 * sorted array of strings should not depend on the order of the strings in the 23607 * input array. As such, when a collator is supposed to act case insensitively, 23608 * it nonetheless still distinguishes between case after all other criteria 23609 * are satisfied so that strings that are distinguished only by case do not sort 23610 * randomly. For searching, we would like to match two strings that different only 23611 * by case, so the collator must return equals in that situation instead of 23612 * further distinguishing by case. Default is "sort". 23613 * 23614 * <li><i>numeric</i> - Treat the left and right strings as if they started with 23615 * numbers and sort them numerically rather than lexically. 23616 * 23617 * <li><i>ignorePunctuation</i> - Skip punctuation characters when comparing the 23618 * strings. 23619 * 23620 * <li>onLoad - a callback function to call when the collator object is fully 23621 * loaded. When the onLoad option is given, the collator object will attempt to 23622 * load any missing locale data using the ilib loader callback. 23623 * When the constructor is done (even if the data is already preassembled), the 23624 * onLoad function is called with the current instance as a parameter, so this 23625 * callback can be used with preassembled or dynamic loading or a mix of the two. 23626 * 23627 * <li>sync - tell whether to load any missing locale data synchronously or 23628 * asynchronously. If this option is given as "false", then the "onLoad" 23629 * callback must be given, as the instance returned from this constructor will 23630 * not be usable for a while. 23631 * 23632 * <li><i>loadParams</i> - an object containing parameters to pass to the 23633 * loader callback function when locale data is missing. The parameters are not 23634 * interpretted or modified in any way. They are simply passed along. The object 23635 * may contain any property/value pairs as long as the calling code is in 23636 * agreement with the loader callback function as to what those parameters mean. 23637 * 23638 * <li><i>useNative</i> - when this option is true, use the native Intl object 23639 * provided by the Javascript engine, if it exists, to implement this class. If 23640 * it doesn't exist, or if this parameter is false, then this class uses a pure 23641 * Javascript implementation, which is slower and uses a lot more memory, but 23642 * works everywhere that ilib works. Default is "true". 23643 * </ul> 23644 * 23645 * <h2>Operation</h2> 23646 * 23647 * The Collator constructor returns a collator object tailored with the above 23648 * options. The object contains an internal compare() method which compares two 23649 * strings according to those options. This can be used directly to compare 23650 * two strings, but is not useful for passing to the javascript sort function 23651 * because then it will not have its collation data available. Instead, use the 23652 * getComparator() method to retrieve a function that is bound to the collator 23653 * object. (You could also bind it yourself using ilib.bind()). The bound function 23654 * can be used with the standard Javascript array sorting algorithm, or as a 23655 * comparator with your own sorting algorithm.<p> 23656 * 23657 * Example using the standard Javascript array sorting call with the bound 23658 * function:<p> 23659 * 23660 * <code> 23661 * <pre> 23662 * var arr = ["ö", "oe", "ü", "o", "a", "ae", "u", "ß", "ä"]; 23663 * var collator = new Collator({locale: 'de-DE', style: "dictionary"}); 23664 * arr.sort(collator.getComparator()); 23665 * console.log(JSON.stringify(arr)); 23666 * </pre> 23667 * </code> 23668 * <p> 23669 * 23670 * Would give the output:<p> 23671 * 23672 * <code> 23673 * <pre> 23674 * ["a", "ae", "ä", "o", "oe", "ö", "ß", "u", "ü"] 23675 * </pre> 23676 * </code> 23677 * 23678 * When sorting an array of Javascript objects according to one of the 23679 * string properties of the objects, wrap the collator's compare function 23680 * in your own comparator function that knows the structure of the objects 23681 * being sorted:<p> 23682 * 23683 * <code> 23684 * <pre> 23685 * var collator = new Collator({locale: 'de-DE'}); 23686 * var myComparator = function (collator) { 23687 * var comparator = collator.getComparator(); 23688 * // left and right are your own objects 23689 * return function (left, right) { 23690 * return comparator(left.x.y.textProperty, right.x.y.textProperty); 23691 * }; 23692 * }; 23693 * arr.sort(myComparator(collator)); 23694 * </pre> 23695 * </code> 23696 * <p> 23697 * 23698 * <h2>Sort Keys</h2> 23699 * 23700 * The collator class also has a method to retrieve the sort key for a 23701 * string. The sort key is an array of values that represent how each 23702 * character in the string should be collated according to the characteristics 23703 * of the collation algorithm and the given options. Thus, sort keys can be 23704 * compared directly value-for-value with other sort keys that were generated 23705 * by the same collator, and the resulting ordering is guaranteed to be the 23706 * same as if the original strings were compared by the collator. 23707 * Sort keys generated by different collators are not guaranteed to give 23708 * any reasonable results when compared together unless the two collators 23709 * were constructed with 23710 * exactly the same options and therefore end up representing the exact same 23711 * collation sequence.<p> 23712 * 23713 * A good rule of thumb is that you would use a sort key if you had 10 or more 23714 * items to sort or if your array might be resorted arbitrarily. For example, if your 23715 * user interface was displaying a table with 100 rows in it, and each row had 23716 * 4 sortable text columns which could be sorted in acending or descending order, 23717 * the recommended practice would be to generate a sort key for each of the 4 23718 * sortable fields in each row and store that in the Javascript representation of the 23719 * table data. Then, when the user clicks on a column header to resort the 23720 * table according to that column, the resorting would be relatively quick 23721 * because it would only be comparing arrays of values, and not recalculating 23722 * the collation values for each character in each string for every comparison.<p> 23723 * 23724 * For tables that are large, it is usually a better idea to do the sorting 23725 * on the server side, especially if the table is the result of a database 23726 * query. In this case, the table is usually a view of the cursor of a large 23727 * results set, and only a few entries are sent to the front end at a time. 23728 * In order to sort the set efficiently, it should be done on the database 23729 * level instead. 23730 * 23731 * <h2>Data</h2> 23732 * 23733 * Doing correct collation entails a huge amount of mapping data, much of which is 23734 * not necessary when collating in one language with one script, which is the most 23735 * common case. Thus, ilib implements a number of ways to include the data you 23736 * need or leave out the data you don't need using the JS assembly tool: 23737 * 23738 * <ol> 23739 * <li>Full multilingual data - if you are sorting multilingual data and need to collate 23740 * text written in multiple scripts, you can use the directive "!data collation/ducet" to 23741 * load in the full collation data. This allows the collator to perform the entire 23742 * Unicode Collation Algorithm (UCA) based on the Default Unicode Collation Element 23743 * Table (DUCET). The data is very large, on the order of multiple megabytes, but 23744 * sometimes it is necessary. 23745 * <li>A few scripts - if you are sorting text written in only a few scripts, you may 23746 * want to include only the data for those scripts. Each ISO 15924 script code has its 23747 * own data available in a separate file, so you can use the data directive to include 23748 * only the data for the scripts you need. For example, use 23749 * "!data collation/Latn" to retrieve the collation information for the Latin script. 23750 * Because the "ducet" table mentioned in the previous point is a superset of the 23751 * tables for all other scripts, you do not need to include explicitly the data for 23752 * any particular script when using "ducet". That is, you either include "ducet" or 23753 * you include a specific list of scripts. 23754 * <li>Only one script - if you are sorting text written only in one script, you can 23755 * either include the data directly as in the previous point, or you can rely on the 23756 * locale to include the correct data for you. In this case, you can use the directive 23757 * "!data collate" to load in the locale's collation data for its most common script. 23758 * </ol> 23759 * 23760 * With any of the above ways of including the data, the collator will only perform the 23761 * correct language-sensitive sorting for the given locale. All other scripts will be 23762 * sorted in the default manner according to the UCA. For example, if you include the 23763 * "ducet" data and pass in "de-DE" (German for Germany) as the locale spec, then 23764 * only the Latin script (the default script for German) will be sorted according to 23765 * German rules. All other scripts in the DUCET, such as Japanese or Arabic, will use 23766 * the default UCA collation rules.<p> 23767 * 23768 * If this collator encounters a character for which it has no collation data, it will 23769 * sort those characters by pure Unicode value after all characters for which it does have 23770 * collation data. For example, if you only loaded in the German collation data (ie. the 23771 * data for the Latin script tailored to German) to sort a list of person names, but that 23772 * list happens to include the names of a few Japanese people written in Japanese 23773 * characters, the Japanese names will sort at the end of the list after all German names, 23774 * and will sort according to the Unicode values of the characters. 23775 * 23776 * @constructor 23777 * @param {Object} options options governing how the resulting comparator 23778 * function will operate 23779 */ 23780 var Collator = function(options) { 23781 var sync = true, 23782 loadParams = undefined, 23783 useNative = true; 23784 23785 // defaults 23786 /** 23787 * @private 23788 * @type {Locale} 23789 */ 23790 this.locale = new Locale(ilib.getLocale()); 23791 23792 /** @private */ 23793 this.caseFirst = "upper"; 23794 /** @private */ 23795 this.sensitivity = "variant"; 23796 /** @private */ 23797 this.level = 4; 23798 /** @private */ 23799 this.usage = "sort"; 23800 /** @private */ 23801 this.reverse = false; 23802 /** @private */ 23803 this.numeric = false; 23804 /** @private */ 23805 this.style = "default"; 23806 /** @private */ 23807 this.ignorePunctuation = false; 23808 23809 if (options) { 23810 if (options.locale) { 23811 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 23812 } 23813 if (options.sensitivity) { 23814 switch (options.sensitivity) { 23815 case 'primary': 23816 case 'base': 23817 this.sensitivity = "base"; 23818 this.level = 1; 23819 break; 23820 case 'secondary': 23821 case 'accent': 23822 this.sensitivity = "accent"; 23823 this.level = 2; 23824 break; 23825 case 'tertiary': 23826 case 'case': 23827 this.sensitivity = "case"; 23828 this.level = 3; 23829 break; 23830 case 'quaternary': 23831 case 'variant': 23832 this.sensitivity = "variant"; 23833 this.level = 4; 23834 break; 23835 } 23836 } 23837 if (typeof(options.upperFirst) !== 'undefined') { 23838 this.caseFirst = options.upperFirst ? "upper" : "lower"; 23839 } 23840 23841 if (typeof(options.ignorePunctuation) !== 'undefined') { 23842 this.ignorePunctuation = options.ignorePunctuation; 23843 } 23844 if (typeof(options.sync) !== 'undefined') { 23845 sync = (options.sync == true); 23846 } 23847 23848 loadParams = options.loadParams; 23849 if (typeof(options.useNative) !== 'undefined') { 23850 useNative = options.useNative; 23851 } 23852 23853 if (options.usage === "sort" || options.usage === "search") { 23854 this.usage = options.usage; 23855 } 23856 23857 if (typeof(options.reverse) === 'boolean') { 23858 this.reverse = options.reverse; 23859 } 23860 23861 if (typeof(options.numeric) === 'boolean') { 23862 this.numeric = options.numeric; 23863 } 23864 23865 if (typeof(options.style) === 'string') { 23866 this.style = options.style; 23867 } 23868 } 23869 23870 if (this.usage === "sort") { 23871 // produces a stable sort 23872 this.level = 4; 23873 } 23874 23875 if (useNative && typeof(Intl) !== 'undefined' && Intl) { 23876 // this engine is modern and supports the new Intl object! 23877 //console.log("implemented natively"); 23878 /** 23879 * @private 23880 * @type {{compare:function(string,string)}} 23881 */ 23882 this.collator = new Intl.Collator(this.locale.getSpec(), { 23883 sensitivity: this.sensitivity, 23884 caseFirst: this.caseFirst, 23885 ignorePunctuation: this.ignorePunctuation, 23886 numeric: this.numeric, 23887 usage: this.usage 23888 }); 23889 23890 if (options && typeof(options.onLoad) === 'function') { 23891 options.onLoad(this); 23892 } 23893 } else { 23894 //console.log("implemented in pure JS"); 23895 if (!Collator.cache) { 23896 Collator.cache = {}; 23897 } 23898 23899 // else implement in pure Javascript 23900 Utils.loadData({ 23901 object: Collator, 23902 locale: this.locale, 23903 name: "collation.json", 23904 sync: sync, 23905 loadParams: loadParams, 23906 callback: ilib.bind(this, function (collation) { 23907 if (!collation) { 23908 collation = ilib.data.collation; 23909 var spec = this.locale.getSpec().replace(/-/g, '_'); 23910 Collator.cache[spec] = collation; 23911 } 23912 this._init(collation); 23913 new LocaleInfo(this.locale, { 23914 sync: sync, 23915 loadParams: loadParams, 23916 onLoad: ilib.bind(this, function(li) { 23917 this.li = li; 23918 if (this.ignorePunctuation) { 23919 isPunct._init(sync, loadParams, ilib.bind(this, function() { 23920 if (options && typeof(options.onLoad) === 'function') { 23921 options.onLoad(this); 23922 } 23923 })); 23924 } else { 23925 if (options && typeof(options.onLoad) === 'function') { 23926 options.onLoad(this); 23927 } 23928 } 23929 }) 23930 }); 23931 }) 23932 }); 23933 } 23934 }; 23935 23936 Collator.prototype = { 23937 /** 23938 * @private 23939 * Bit pack an array of values into a single number 23940 * @param {number|null|Array.<number>} arr array of values to bit pack 23941 * @param {number} offset offset for the start of this map 23942 */ 23943 _pack: function (arr, offset) { 23944 var value = 0; 23945 if (arr) { 23946 if (typeof(arr) === 'number') { 23947 arr = [ arr ]; 23948 } 23949 for (var i = 0; i < this.level; i++) { 23950 var thisLevel = (typeof(arr[i]) !== "undefined" ? arr[i] : 0); 23951 if (i === 0) { 23952 thisLevel += offset; 23953 } 23954 if (i > 0) { 23955 value <<= this.collation.bits[i]; 23956 } 23957 if (i === 2 && this.caseFirst === "lower") { 23958 // sort the lower case first instead of upper 23959 value = value | (1 - thisLevel); 23960 } else { 23961 value = value | thisLevel; 23962 } 23963 } 23964 } 23965 return value; 23966 }, 23967 23968 /** 23969 * @private 23970 * Return the rule packed into an array of collation elements. 23971 * @param {Array.<number|null|Array.<number>>} rule 23972 * @param {number} offset 23973 * @return {Array.<number>} a bit-packed array of numbers 23974 */ 23975 _packRule: function(rule, offset) { 23976 if (ilib.isArray(rule[0])) { 23977 var ret = []; 23978 for (var i = 0; i < rule.length; i++) { 23979 ret.push(this._pack(rule[i], offset)); 23980 } 23981 return ret; 23982 } else { 23983 return [ this._pack(rule, offset) ]; 23984 } 23985 }, 23986 23987 /** 23988 * @private 23989 */ 23990 _addChars: function (str, offset) { 23991 var gs = new GlyphString(str); 23992 var it = gs.charIterator(); 23993 var c; 23994 23995 while (it.hasNext()) { 23996 c = it.next(); 23997 if (c === "'") { 23998 // escape a sequence of chars as one collation element 23999 c = ""; 24000 var x = ""; 24001 while (it.hasNext() && x !== "'") { 24002 c += x; 24003 x = it.next(); 24004 } 24005 } 24006 this.lastMap++; 24007 this.map[c] = this._packRule([this.lastMap], offset); 24008 } 24009 }, 24010 24011 /** 24012 * @private 24013 */ 24014 _addRules: function(rules, start) { 24015 var p; 24016 for (var r in rules.map) { 24017 if (r) { 24018 this.map[r] = this._packRule(rules.map[r], start); 24019 p = typeof(rules.map[r][0]) === 'number' ? rules.map[r][0] : rules.map[r][0][0]; 24020 this.lastMap = Math.max(p + start, this.lastMap); 24021 } 24022 } 24023 24024 if (typeof(rules.ranges) !== 'undefined') { 24025 // for each range, everything in the range goes in primary sequence from the start 24026 for (var i = 0; i < rules.ranges.length; i++) { 24027 var range = rules.ranges[i]; 24028 24029 this.lastMap = range.start; 24030 if (typeof(range.chars) === "string") { 24031 this._addChars(range.chars, start); 24032 } else { 24033 for (var k = 0; k < range.chars.length; k++) { 24034 this._addChars(range.chars[k], start); 24035 } 24036 } 24037 } 24038 } 24039 }, 24040 24041 /** 24042 * @private 24043 */ 24044 _init: function(rules) { 24045 var rule = this.style; 24046 while (typeof(rule) === 'string') { 24047 rule = rules[rule]; 24048 } 24049 if (!rule) { 24050 rule = "default"; 24051 while (typeof(rule) === 'string') { 24052 rule = rules[rule]; 24053 } 24054 } 24055 if (!rule) { 24056 this.map = {}; 24057 return; 24058 } 24059 24060 /** 24061 * @private 24062 * @type {{scripts:Array.<string>,bits:Array.<number>,maxes:Array.<number>,bases:Array.<number>,map:Object.<string,Array.<number|null|Array.<number>>>}} 24063 */ 24064 this.collation = rule; 24065 this.map = {}; 24066 this.lastMap = -1; 24067 this.keysize = this.collation.keysize[this.level-1]; 24068 24069 if (typeof(this.collation.inherit) !== 'undefined') { 24070 for (var i = 0; i < this.collation.inherit.length; i++) { 24071 var col = this.collation.inherit[i]; 24072 rule = typeof(col) === 'object' ? col.name : col; 24073 if (rules[rule]) { 24074 this._addRules(rules[rule], col.start || this.lastMap+1); 24075 } 24076 } 24077 } 24078 this._addRules(this.collation, this.lastMap+1); 24079 }, 24080 24081 /** 24082 * @private 24083 */ 24084 _basicCompare: function(left, right) { 24085 var l = (left instanceof NormString) ? left : new NormString(left), 24086 r = (right instanceof NormString) ? right : new NormString(right), 24087 lchar, 24088 rchar, 24089 lelements, 24090 relements; 24091 24092 if (this.numeric) { 24093 var lvalue = new INumber(left, {locale: this.locale}); 24094 var rvalue = new INumber(right, {locale: this.locale}); 24095 if (!isNaN(lvalue.valueOf()) && !isNaN(rvalue.valueOf())) { 24096 var diff = lvalue.valueOf() - rvalue.valueOf(); 24097 if (diff) { 24098 return diff; 24099 } else { 24100 // skip the numeric part and compare the rest lexically 24101 l = new NormString(left.substring(lvalue.parsed.length)); 24102 r = new NormString(right.substring(rvalue.parsed.length)); 24103 } 24104 } 24105 // else if they aren't both numbers, then let the code below take care of the lexical comparison instead 24106 } 24107 24108 lelements = new ElementIterator(new CodePointSource(l, this.ignorePunctuation), this.map, this.keysize); 24109 relements = new ElementIterator(new CodePointSource(r, this.ignorePunctuation), this.map, this.keysize); 24110 24111 while (lelements.hasNext() && relements.hasNext()) { 24112 var diff = lelements.next() - relements.next(); 24113 if (diff) { 24114 return diff; 24115 } 24116 } 24117 if (!lelements.hasNext() && !relements.hasNext()) { 24118 return 0; 24119 } else if (lelements.hasNext()) { 24120 return 1; 24121 } else { 24122 return -1; 24123 } 24124 }, 24125 24126 /** 24127 * Compare two strings together according to the rules of this 24128 * collator instance. Do not use this function directly with 24129 * Array.sort, as it will not have its collation data available 24130 * and therefore will not function properly. Use the function 24131 * returned by getComparator() instead. 24132 * 24133 * @param {string} left the left string to compare 24134 * @param {string} right the right string to compare 24135 * @return {number} a negative number if left comes before right, a 24136 * positive number if right comes before left, and zero if left and 24137 * right are equivalent according to this collator 24138 */ 24139 compare: function (left, right) { 24140 // last resort: use the "C" locale 24141 if (this.collator) { 24142 // implemented by the core engine 24143 return this.collator.compare(left, right); 24144 } 24145 24146 var ret = this._basicCompare(left, right); 24147 return this.reverse ? -ret : ret; 24148 }, 24149 24150 /** 24151 * Return a comparator function that can compare two strings together 24152 * according to the rules of this collator instance. The function 24153 * returns a negative number if the left 24154 * string comes before right, a positive number if the right string comes 24155 * before the left, and zero if left and right are equivalent. If the 24156 * reverse property was given as true to the collator constructor, this 24157 * function will 24158 * switch the sign of those values to cause sorting to happen in the 24159 * reverse order. 24160 * 24161 * @return {function(...)|undefined} a comparator function that 24162 * can compare two strings together according to the rules of this 24163 * collator instance 24164 */ 24165 getComparator: function() { 24166 // bind the function to this instance so that we have the collation 24167 // rules available to do the work 24168 if (this.collator) { 24169 // implemented by the core engine 24170 return this.collator.compare; 24171 } 24172 24173 return ilib.bind(this, this.compare); 24174 }, 24175 24176 /** 24177 * Return a sort key string for the given string. The sort key 24178 * string is a list of values that represent each character 24179 * in the original string. The sort key 24180 * values for any particular character consists of 3 numbers that 24181 * encode the primary, secondary, and tertiary characteristics 24182 * of that character. The values of each characteristic are 24183 * modified according to the strength of this collator instance 24184 * to give the correct collation order. The idea is that this 24185 * sort key string is directly comparable byte-for-byte to 24186 * other sort key strings generated by this collator without 24187 * any further knowledge of the collation rules for the locale. 24188 * More formally, if a < b according to the rules of this collation, 24189 * then it is guaranteed that sortkey(a) < sortkey(b) when compared 24190 * byte-for-byte. The sort key string can therefore be used 24191 * without the collator to sort an array of strings efficiently 24192 * because the work of determining the applicability of various 24193 * collation rules is done once up-front when generating 24194 * the sort key.<p> 24195 * 24196 * The sort key string can be treated as a regular, albeit somewhat 24197 * odd-looking, string. That is, it can be pass to regular 24198 * Javascript functions without problems. 24199 * 24200 * @param {string} str the original string to generate the sort key for 24201 * @return {string} a sort key string for the given string 24202 */ 24203 sortKey: function (str) { 24204 if (!str) { 24205 return ""; 24206 } 24207 24208 if (this.collator) { 24209 // native, no sort keys available 24210 return str; 24211 } 24212 24213 if (this.numeric) { 24214 var v = new INumber(str, {locale: this.locale}); 24215 var s = isNaN(v.valueOf()) ? "" : v.valueOf().toString(16); 24216 return JSUtils.pad(s, 16); 24217 } else { 24218 var n = (typeof(str) === "string") ? new NormString(str) : str, 24219 ret = "", 24220 lelements = new ElementIterator(new CodePointSource(n, this.ignorePunctuation), this.map, this.keysize), 24221 element; 24222 24223 while (lelements.hasNext()) { 24224 element = lelements.next(); 24225 if (this.reverse) { 24226 // for reverse, take the bitwise inverse 24227 element = (1 << this.keysize) - element; 24228 } 24229 ret += JSUtils.pad(element.toString(16), this.keysize/4); 24230 } 24231 } 24232 return ret; 24233 } 24234 }; 24235 24236 /** 24237 * Retrieve the list of collation style names that are available for the 24238 * given locale. This list varies depending on the locale, and depending 24239 * on whether or not the data for that locale was assembled into this copy 24240 * of ilib. 24241 * 24242 * @param {Locale|string=} locale The locale for which the available 24243 * styles are being sought 24244 * @return Array.<string> an array of style names that are available for 24245 * the given locale 24246 */ 24247 Collator.getAvailableStyles = function (locale) { 24248 return [ "standard" ]; 24249 }; 24250 24251 /** 24252 * Retrieve the list of ISO 15924 script codes that are available in this 24253 * copy of ilib. This list varies depending on whether or not the data for 24254 * various scripts was assembled into this copy of ilib. If the "ducet" 24255 * data is assembled into this copy of ilib, this method will report the 24256 * entire list of scripts as being available. If a collator instance is 24257 * instantiated with a script code that is not on the list returned by this 24258 * function, it will be ignored and text in that script will be sorted by 24259 * numeric Unicode values of the characters. 24260 * 24261 * @return Array.<string> an array of ISO 15924 script codes that are 24262 * available 24263 */ 24264 Collator.getAvailableScripts = function () { 24265 return [ "Latn" ]; 24266 }; 24267 24268 24269 24270 /*< nfd/all.js */ 24271 /* 24272 * all.js - include file for normalization data for a particular script 24273 * 24274 * Copyright © 2013-2015, JEDLSoft 24275 * 24276 * Licensed under the Apache License, Version 2.0 (the "License"); 24277 * you may not use this file except in compliance with the License. 24278 * You may obtain a copy of the License at 24279 * 24280 * http://www.apache.org/licenses/LICENSE-2.0 24281 * 24282 * Unless required by applicable law or agreed to in writing, software 24283 * distributed under the License is distributed on an "AS IS" BASIS, 24284 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24285 * 24286 * See the License for the specific language governing permissions and 24287 * limitations under the License. 24288 */ 24289 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 24290 // !data normdata nfd/all 24291 ilib.extend(ilib.data.norm, ilib.data.normdata); 24292 ilib.extend(ilib.data.norm.nfd, ilib.data.nfd_all); 24293 ilib.data.normdata = undefined; 24294 ilib.data.nfd_all = undefined; 24295 /*< nfkd/all.js */ 24296 /* 24297 * all.js - include file for normalization data for a particular script 24298 * 24299 * Copyright © 2013-2015, JEDLSoft 24300 * 24301 * Licensed under the Apache License, Version 2.0 (the "License"); 24302 * you may not use this file except in compliance with the License. 24303 * You may obtain a copy of the License at 24304 * 24305 * http://www.apache.org/licenses/LICENSE-2.0 24306 * 24307 * Unless required by applicable law or agreed to in writing, software 24308 * distributed under the License is distributed on an "AS IS" BASIS, 24309 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24310 * 24311 * See the License for the specific language governing permissions and 24312 * limitations under the License. 24313 */ 24314 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 24315 // !depends nfd/all.js 24316 // !data normdata nfkd/all 24317 ilib.extend(ilib.data.norm, ilib.data.normdata); 24318 ilib.extend(ilib.data.norm.nfkd, ilib.data.nfkd_all); 24319 ilib.data.normdata = undefined; 24320 ilib.data.nfkd_all = undefined; 24321 /*< nfkc/all.js */ 24322 /* 24323 * all.js - include file for normalization data for a particular script 24324 * 24325 * Copyright © 2013-2015, JEDLSoft 24326 * 24327 * Licensed under the Apache License, Version 2.0 (the "License"); 24328 * you may not use this file except in compliance with the License. 24329 * You may obtain a copy of the License at 24330 * 24331 * http://www.apache.org/licenses/LICENSE-2.0 24332 * 24333 * Unless required by applicable law or agreed to in writing, software 24334 * distributed under the License is distributed on an "AS IS" BASIS, 24335 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24336 * 24337 * See the License for the specific language governing permissions and 24338 * limitations under the License. 24339 */ 24340 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 24341 // !depends nfd/all.js nfkd/all.js 24342 // !data norm 24343 ilib.extend(ilib.data.norm, ilib.data.normdata); 24344 ilib.data.normdata = undefined; 24345 24346 /*< LocaleMatcher.js */ 24347 /* 24348 * LocaleMatcher.js - Locale matcher definition 24349 * 24350 * Copyright © 2013-2015, JEDLSoft 24351 * 24352 * Licensed under the Apache License, Version 2.0 (the "License"); 24353 * you may not use this file except in compliance with the License. 24354 * You may obtain a copy of the License at 24355 * 24356 * http://www.apache.org/licenses/LICENSE-2.0 24357 * 24358 * Unless required by applicable law or agreed to in writing, software 24359 * distributed under the License is distributed on an "AS IS" BASIS, 24360 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24361 * 24362 * See the License for the specific language governing permissions and 24363 * limitations under the License. 24364 */ 24365 24366 // !depends ilib.js Locale.js Utils.js 24367 // !data likelylocales 24368 24369 24370 /** 24371 * @class 24372 * Create a new locale matcher instance. This is used 24373 * to see which locales can be matched with each other in 24374 * various ways.<p> 24375 * 24376 * The options object may contain any of the following properties: 24377 * 24378 * <ul> 24379 * <li><i>locale</i> - the locale to match 24380 * 24381 * <li><i>onLoad</i> - a callback function to call when the locale matcher object is fully 24382 * loaded. When the onLoad option is given, the locale matcher object will attempt to 24383 * load any missing locale data using the ilib loader callback. 24384 * When the constructor is done (even if the data is already preassembled), the 24385 * onLoad function is called with the current instance as a parameter, so this 24386 * callback can be used with preassembled or dynamic loading or a mix of the two. 24387 * 24388 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 24389 * asynchronously. If this option is given as "false", then the "onLoad" 24390 * callback must be given, as the instance returned from this constructor will 24391 * not be usable for a while. 24392 * 24393 * <li><i>loadParams</i> - an object containing parameters to pass to the 24394 * loader callback function when locale data is missing. The parameters are not 24395 * interpretted or modified in any way. They are simply passed along. The object 24396 * may contain any property/value pairs as long as the calling code is in 24397 * agreement with the loader callback function as to what those parameters mean. 24398 * </ul> 24399 * 24400 * 24401 * @constructor 24402 * @param {Object} options parameters to initialize this matcher 24403 */ 24404 var LocaleMatcher = function(options) { 24405 var sync = true, 24406 loadParams = undefined; 24407 24408 this.locale = new Locale(); 24409 24410 if (options) { 24411 if (typeof(options.locale) !== 'undefined') { 24412 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 24413 } 24414 24415 if (typeof(options.sync) !== 'undefined') { 24416 sync = (options.sync == true); 24417 } 24418 24419 if (typeof(options.loadParams) !== 'undefined') { 24420 loadParams = options.loadParams; 24421 } 24422 } 24423 24424 if (!LocaleMatcher.cache) { 24425 LocaleMatcher.cache = {}; 24426 } 24427 24428 if (typeof(ilib.data.likelylocales) === 'undefined') { 24429 Utils.loadData({ 24430 object: LocaleMatcher, 24431 locale: "-", 24432 name: "likelylocales.json", 24433 sync: sync, 24434 loadParams: loadParams, 24435 callback: ilib.bind(this, function (info) { 24436 if (!info) { 24437 info = {}; 24438 var spec = this.locale.getSpec().replace(/-/g, "_"); 24439 LocaleMatcher.cache[spec] = info; 24440 } 24441 /** @type {Object.<string,string>} */ 24442 this.info = info; 24443 if (options && typeof(options.onLoad) === 'function') { 24444 options.onLoad(this); 24445 } 24446 }) 24447 }); 24448 } else { 24449 this.info = ilib.data.likelylocales; 24450 } 24451 }; 24452 24453 24454 LocaleMatcher.prototype = { 24455 /** 24456 * Return the locale used to construct this instance. 24457 * @return {Locale|undefined} the locale for this matcher 24458 */ 24459 getLocale: function() { 24460 return this.locale; 24461 }, 24462 24463 /** 24464 * Return an Locale instance that is fully specified based on partial information 24465 * given to the constructor of this locale matcher instance. For example, if the locale 24466 * spec given to this locale matcher instance is simply "ru" (for the Russian language), 24467 * then it will fill in the missing region and script tags and return a locale with 24468 * the specifier "ru-Cyrl-RU". (ie. Russian language, Cyrillic, Russian Federation). 24469 * Any one or two of the language, script, or region parts may be left unspecified, 24470 * and the other one or two parts will be filled in automatically. If this 24471 * class has no information about the given locale, then the locale of this 24472 * locale matcher instance is returned unchanged. 24473 * 24474 * @returns {Locale} the most likely completion of the partial locale given 24475 * to the constructor of this locale matcher instance 24476 */ 24477 getLikelyLocale: function () { 24478 if (typeof(this.info[this.locale.getSpec()]) === 'undefined') { 24479 return this.locale; 24480 } 24481 24482 return new Locale(this.info[this.locale.getSpec()]); 24483 } 24484 }; 24485 24486 24487 /*< CaseMapper.js */ 24488 /* 24489 * caseMapper.js - define upper- and lower-case mapper 24490 * 24491 * Copyright © 2014-2015, JEDLSoft 24492 * 24493 * Licensed under the Apache License, Version 2.0 (the "License"); 24494 * you may not use this file except in compliance with the License. 24495 * You may obtain a copy of the License at 24496 * 24497 * http://www.apache.org/licenses/LICENSE-2.0 24498 * 24499 * Unless required by applicable law or agreed to in writing, software 24500 * distributed under the License is distributed on an "AS IS" BASIS, 24501 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24502 * 24503 * See the License for the specific language governing permissions and 24504 * limitations under the License. 24505 */ 24506 24507 // !depends Locale.js IString.js 24508 24509 24510 24511 /** 24512 * @class 24513 * Create a new string mapper instance that maps strings to upper or 24514 * lower case. This mapping will work for any string as characters 24515 * that have no case will be returned unchanged.<p> 24516 * 24517 * The options may contain any of the following properties: 24518 * 24519 * <ul> 24520 * <li><i>locale</i> - locale to use when loading the mapper. Some maps are 24521 * locale-dependent, and this locale selects the right one. Default if this is 24522 * not specified is the current locale. 24523 * 24524 * <li><i>direction</i> - "toupper" for upper-casing, or "tolower" for lower-casing. 24525 * Default if not specified is "toupper". 24526 * </ul> 24527 * 24528 * 24529 * @constructor 24530 * @param {Object=} options options to initialize this mapper 24531 */ 24532 var CaseMapper = function (options) { 24533 this.up = true; 24534 this.locale = new Locale(); 24535 24536 if (options) { 24537 if (typeof(options.locale) !== 'undefined') { 24538 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 24539 } 24540 24541 this.up = (!options.direction || options.direction === "toupper"); 24542 } 24543 24544 this.mapData = this.up ? { 24545 "ß": "SS", // German 24546 'ΐ': 'Ι', // Greek 24547 'ά': 'Α', 24548 'έ': 'Ε', 24549 'ή': 'Η', 24550 'ί': 'Ι', 24551 'ΰ': 'Υ', 24552 'ϊ': 'Ι', 24553 'ϋ': 'Υ', 24554 'ό': 'Ο', 24555 'ύ': 'Υ', 24556 'ώ': 'Ω', 24557 'Ӏ': 'Ӏ', // Russian and slavic languages 24558 'ӏ': 'Ӏ' 24559 } : { 24560 'Ӏ': 'Ӏ' // Russian and slavic languages 24561 }; 24562 24563 switch (this.locale.getLanguage()) { 24564 case "az": 24565 case "tr": 24566 case "crh": 24567 case "kk": 24568 case "krc": 24569 case "tt": 24570 var lower = "iı"; 24571 var upper = "İI"; 24572 this._setUpMap(lower, upper); 24573 break; 24574 case "fr": 24575 if (this.up && this.locale.getRegion() !== "CA") { 24576 this._setUpMap("àáâãäçèéêëìíîïñòóôöùúûü", "AAAAACEEEEIIIINOOOOUUUU"); 24577 } 24578 break; 24579 } 24580 24581 if (ilib._getBrowser() === "ie" || ilib._getBrowser() === "Edge") { 24582 // IE is missing these mappings for some reason 24583 if (this.up) { 24584 this.mapData['ς'] = 'Σ'; 24585 } 24586 this._setUpMap("ⲁⲃⲅⲇⲉⲋⲍⲏⲑⲓⲕⲗⲙⲛⲝⲟⲡⲣⲥⲧⲩⲫⲭⲯⲱⳁⳉⳋ", "ⲀⲂⲄⲆⲈⲊⲌⲎⲐⲒⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⳀⳈⳊ"); // Coptic 24587 // Georgian Nuskhuri <-> Asomtavruli 24588 this._setUpMap("ⴀⴁⴂⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌⴍⴎⴏⴐⴑⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜⴝⴞⴟⴠⴡⴢⴣⴤⴥ", "ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀჁჂჃჄჅ"); 24589 } 24590 }; 24591 24592 CaseMapper.prototype = { 24593 /** 24594 * @private 24595 */ 24596 _charMapper: function(string) { 24597 if (!string) { 24598 return string; 24599 } 24600 var input = (typeof(string) === 'string') ? new IString(string) : string.toString(); 24601 var ret = ""; 24602 var it = input.charIterator(); 24603 var c; 24604 24605 while (it.hasNext()) { 24606 c = it.next(); 24607 if (!this.up && c === 'Σ') { 24608 if (it.hasNext()) { 24609 c = it.next(); 24610 var code = c.charCodeAt(0); 24611 // if the next char is not a greek letter, this is the end of the word so use the 24612 // final form of sigma. Otherwise, use the mid-word form. 24613 ret += ((code < 0x0388 && code !== 0x0386) || code > 0x03CE) ? 'ς' : 'σ'; 24614 ret += c.toLowerCase(); 24615 } else { 24616 // no next char means this is the end of the word, so use the final form of sigma 24617 ret += 'ς'; 24618 } 24619 } else { 24620 if (this.mapData[c]) { 24621 ret += this.mapData[c]; 24622 } else { 24623 ret += this.up ? c.toUpperCase() : c.toLowerCase(); 24624 } 24625 } 24626 } 24627 24628 return ret; 24629 }, 24630 24631 /** @private */ 24632 _setUpMap: function(lower, upper) { 24633 var from, to; 24634 if (this.up) { 24635 from = lower; 24636 to = upper; 24637 } else { 24638 from = upper; 24639 to = lower; 24640 } 24641 for (var i = 0; i < upper.length; i++) { 24642 this.mapData[from[i]] = to[i]; 24643 } 24644 }, 24645 24646 /** 24647 * Return the locale that this mapper was constructed with. 24648 * @returns {Locale} the locale that this mapper was constructed with 24649 */ 24650 getLocale: function () { 24651 return this.locale; 24652 }, 24653 24654 /** 24655 * Map a string to lower case in a locale-sensitive manner. 24656 * 24657 * @param {string|undefined} string 24658 * @return {string|undefined} 24659 */ 24660 map: function (string) { 24661 return this._charMapper(string); 24662 } 24663 }; 24664 24665 24666 24667 /*< NumberingPlan.js */ 24668 /* 24669 * NumPlan.js - Represent a phone numbering plan. 24670 * 24671 * Copyright © 2014-2015, JEDLSoft 24672 * 24673 * Licensed under the Apache License, Version 2.0 (the "License"); 24674 * you may not use this file except in compliance with the License. 24675 * You may obtain a copy of the License at 24676 * 24677 * http://www.apache.org/licenses/LICENSE-2.0 24678 * 24679 * Unless required by applicable law or agreed to in writing, software 24680 * distributed under the License is distributed on an "AS IS" BASIS, 24681 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24682 * 24683 * See the License for the specific language governing permissions and 24684 * limitations under the License. 24685 */ 24686 24687 /* 24688 !depends 24689 ilib.js 24690 Locale.js 24691 Utils.js 24692 JSUtils.js 24693 */ 24694 24695 // !data numplan 24696 24697 24698 /** 24699 * @class 24700 * Create a numbering plan information instance for a particular country's plan.<p> 24701 * 24702 * The options may contain any of the following properties: 24703 * 24704 * <ul> 24705 * <li><i>locale</i> - locale for which the numbering plan is sought. This locale 24706 * will be mapped to the actual numbering plan, which may be shared amongst a 24707 * number of countries. 24708 * 24709 * <li>onLoad - a callback function to call when the date format object is fully 24710 * loaded. When the onLoad option is given, the DateFmt object will attempt to 24711 * load any missing locale data using the ilib loader callback. 24712 * When the constructor is done (even if the data is already preassembled), the 24713 * onLoad function is called with the current instance as a parameter, so this 24714 * callback can be used with preassembled or dynamic loading or a mix of the two. 24715 * 24716 * <li>sync - tell whether to load any missing locale data synchronously or 24717 * asynchronously. If this option is given as "false", then the "onLoad" 24718 * callback must be given, as the instance returned from this constructor will 24719 * not be usable for a while. 24720 * 24721 * <li><i>loadParams</i> - an object containing parameters to pass to the 24722 * loader callback function when locale data is missing. The parameters are not 24723 * interpretted or modified in any way. They are simply passed along. The object 24724 * may contain any property/value pairs as long as the calling code is in 24725 * agreement with the loader callback function as to what those parameters mean. 24726 * </ul> 24727 * 24728 * @private 24729 * @constructor 24730 * @param {Object} options options governing the way this plan is loaded 24731 */ 24732 var NumberingPlan = function (options) { 24733 var sync = true, 24734 loadParams = {}; 24735 24736 this.locale = new Locale(); 24737 24738 if (options) { 24739 if (options.locale) { 24740 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 24741 } 24742 24743 if (typeof(options.sync) !== 'undefined') { 24744 sync = (options.sync == true); 24745 } 24746 24747 if (options.loadParams) { 24748 loadParams = options.loadParams; 24749 } 24750 } 24751 24752 Utils.loadData({ 24753 name: "numplan.json", 24754 object: NumberingPlan, 24755 locale: this.locale, 24756 sync: sync, 24757 loadParams: loadParams, 24758 callback: ilib.bind(this, function (npdata) { 24759 if (!npdata) { 24760 npdata = { 24761 "region": "XX", 24762 "skipTrunk": false, 24763 "trunkCode": "0", 24764 "iddCode": "00", 24765 "dialingPlan": "closed", 24766 "commonFormatChars": " ()-./", 24767 "fieldLengths": { 24768 "areaCode": 0, 24769 "cic": 0, 24770 "mobilePrefix": 0, 24771 "serviceCode": 0 24772 } 24773 }; 24774 } 24775 24776 /** 24777 * @type {{ 24778 * region:string, 24779 * skipTrunk:boolean, 24780 * trunkCode:string, 24781 * iddCode:string, 24782 * dialingPlan:string, 24783 * commonFormatChars:string, 24784 * fieldLengths:Object.<string,number>, 24785 * contextFree:boolean, 24786 * findExtensions:boolean, 24787 * trunkRequired:boolean, 24788 * extendedAreaCodes:boolean 24789 * }} 24790 */ 24791 this.npdata = npdata; 24792 if (options && typeof(options.onLoad) === 'function') { 24793 options.onLoad(this); 24794 } 24795 }) 24796 }); 24797 }; 24798 24799 NumberingPlan.prototype = { 24800 /** 24801 * Return the name of this plan. This may be different than the 24802 * name of the region because sometimes multiple countries share 24803 * the same plan. 24804 * @return {string} the name of the plan 24805 */ 24806 getName: function() { 24807 return this.npdata.region; 24808 }, 24809 24810 /** 24811 * Return the trunk code of the current plan as a string. 24812 * @return {string|undefined} the trunk code of the plan or 24813 * undefined if there is no trunk code in this plan 24814 */ 24815 getTrunkCode: function() { 24816 return this.npdata.trunkCode; 24817 }, 24818 24819 /** 24820 * Return the international direct dialing code of this plan. 24821 * @return {string} the IDD code of this plan 24822 */ 24823 getIDDCode: function() { 24824 return this.npdata.iddCode; 24825 }, 24826 24827 /** 24828 * Return the plan style for this plan. The plan style may be 24829 * one of: 24830 * 24831 * <ul> 24832 * <li>"open" - area codes may be left off if the caller is 24833 * dialing to another number within the same area code 24834 * <li>"closed" - the area code must always be specified, even 24835 * if calling another number within the same area code 24836 * </ul> 24837 * 24838 * @return {string} the plan style, "open" or "closed" 24839 */ 24840 getPlanStyle: function() { 24841 return this.npdata.dialingPlan; 24842 }, 24843 /** [Need Comment] 24844 * Return a contextFree 24845 * 24846 * @return {boolean} 24847 */ 24848 getContextFree: function() { 24849 return this.npdata.contextFree; 24850 }, 24851 /** [Need Comment] 24852 * Return a findExtensions 24853 * 24854 * @return {boolean} 24855 */ 24856 getFindExtensions: function() { 24857 return this.npdata.findExtensions; 24858 }, 24859 /** [Need Comment] 24860 * Return a skipTrunk 24861 * 24862 * @return {boolean} 24863 */ 24864 getSkipTrunk: function() { 24865 return this.npdata.skipTrunk; 24866 }, 24867 /** [Need Comment] 24868 * Return a skipTrunk 24869 * 24870 * @return {boolean} 24871 */ 24872 getTrunkRequired: function() { 24873 return this.npdata.trunkRequired; 24874 }, 24875 /** 24876 * Return true if this plan uses extended area codes. 24877 * @return {boolean} true if the plan uses extended area codes 24878 */ 24879 getExtendedAreaCode: function() { 24880 return this.npdata.extendedAreaCodes; 24881 }, 24882 /** 24883 * Return a string containing all of the common format characters 24884 * used to format numbers. 24885 * @return {string} the common format characters fused in this locale 24886 */ 24887 getCommonFormatChars: function() { 24888 return this.npdata.commonFormatChars; 24889 }, 24890 24891 /** 24892 * Return the length of the field with the given name. If the length 24893 * is returned as 0, this means it is variable length. 24894 * 24895 * @param {string} field name of the field for which the length is 24896 * being sought 24897 * @return {number} if positive, this gives the length of the given 24898 * field. If zero, the field is variable length. If negative, the 24899 * field is not known. 24900 */ 24901 getFieldLength: function (field) { 24902 var dataField = this.npdata.fieldLengths; 24903 24904 return dataField[field]; 24905 } 24906 }; 24907 24908 24909 /*< PhoneLocale.js */ 24910 /* 24911 * phoneloc.js - Represent a phone locale object. 24912 * 24913 * Copyright © 2014-2015, JEDLSoft 24914 * 24915 * Licensed under the Apache License, Version 2.0 (the "License"); 24916 * you may not use this file except in compliance with the License. 24917 * You may obtain a copy of the License at 24918 * 24919 * http://www.apache.org/licenses/LICENSE-2.0 24920 * 24921 * Unless required by applicable law or agreed to in writing, software 24922 * distributed under the License is distributed on an "AS IS" BASIS, 24923 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24924 * 24925 * See the License for the specific language governing permissions and 24926 * limitations under the License. 24927 */ 24928 24929 /* 24930 !depends 24931 ilib.js 24932 Locale.js 24933 Utils.js 24934 */ 24935 24936 // !data phoneloc 24937 24938 24939 /** 24940 * @class 24941 * Extension of the locale class that has extra methods to map various numbers 24942 * related to phone number parsing. 24943 * 24944 * @param {Object} options Options that govern how this phone locale works 24945 * 24946 * @private 24947 * @constructor 24948 * @extends Locale 24949 */ 24950 var PhoneLocale = function(options) { 24951 var region, 24952 mcc, 24953 cc, 24954 sync = true, 24955 loadParams = {}, 24956 locale; 24957 24958 locale = (options && options.locale) || ilib.getLocale(); 24959 24960 this.parent.call(this, locale); 24961 24962 region = this.region; 24963 24964 if (options) { 24965 if (typeof(options.mcc) !== 'undefined') { 24966 mcc = options.mcc; 24967 } 24968 24969 if (typeof(options.countryCode) !== 'undefined') { 24970 cc = options.countryCode; 24971 } 24972 24973 if (typeof(options.sync) !== 'undefined') { 24974 sync = (options.sync == true); 24975 } 24976 24977 if (options.loadParams) { 24978 loadParams = options.loadParams; 24979 } 24980 } 24981 24982 Utils.loadData({ 24983 name: "phoneloc.json", 24984 object: PhoneLocale, 24985 nonlocale: true, 24986 sync: sync, 24987 loadParams: loadParams, 24988 callback: ilib.bind(this, function (data) { 24989 /** @type {{mcc2reg:Object.<string,string>,cc2reg:Object.<string,string>,reg2cc:Object.<string,string>,area2reg:Object.<string,string>}} */ 24990 this.mappings = data; 24991 24992 if (typeof(mcc) !== 'undefined') { 24993 region = this.mappings.mcc2reg[mcc]; 24994 } 24995 24996 if (typeof(cc) !== 'undefined') { 24997 region = this.mappings.cc2reg[cc]; 24998 } 24999 25000 if (!region) { 25001 region = "XX"; 25002 } 25003 25004 this.region = this._normPhoneReg(region); 25005 this._genSpec(); 25006 25007 if (options && typeof(options.onLoad) === 'function') { 25008 options.onLoad(this); 25009 } 25010 }) 25011 }); 25012 }; 25013 25014 PhoneLocale.prototype = new Locale(); 25015 PhoneLocale.prototype.parent = Locale; 25016 PhoneLocale.prototype.constructor = PhoneLocale; 25017 25018 /** 25019 * Map a mobile carrier code to a region code. 25020 * 25021 * @static 25022 * @package 25023 * @param {string|undefined} mcc the MCC to map 25024 * @return {string|undefined} the region code 25025 */ 25026 25027 PhoneLocale.prototype._mapMCCtoRegion = function(mcc) { 25028 if (!mcc) { 25029 return undefined; 25030 } 25031 return this.mappings.mcc2reg && this.mappings.mcc2reg[mcc] || "XX"; 25032 }; 25033 25034 /** 25035 * Map a country code to a region code. 25036 * 25037 * @static 25038 * @package 25039 * @param {string|undefined} cc the country code to map 25040 * @return {string|undefined} the region code 25041 */ 25042 PhoneLocale.prototype._mapCCtoRegion = function(cc) { 25043 if (!cc) { 25044 return undefined; 25045 } 25046 return this.mappings.cc2reg && this.mappings.cc2reg[cc] || "XX"; 25047 }; 25048 25049 /** 25050 * Map a region code to a country code. 25051 * 25052 * @static 25053 * @package 25054 * @param {string|undefined} region the region code to map 25055 * @return {string|undefined} the country code 25056 */ 25057 PhoneLocale.prototype._mapRegiontoCC = function(region) { 25058 if (!region) { 25059 return undefined; 25060 } 25061 return this.mappings.reg2cc && this.mappings.reg2cc[region] || "0"; 25062 }; 25063 25064 /** 25065 * Map a country code to a region code. 25066 * 25067 * @static 25068 * @package 25069 * @param {string|undefined} cc the country code to map 25070 * @param {string|undefined} area the area code within the country code's numbering plan 25071 * @return {string|undefined} the region code 25072 */ 25073 PhoneLocale.prototype._mapAreatoRegion = function(cc, area) { 25074 if (!cc) { 25075 return undefined; 25076 } 25077 if (cc in this.mappings.area2reg) { 25078 return this.mappings.area2reg[cc][area] || this.mappings.area2reg[cc]["default"]; 25079 } else { 25080 return this.mappings.cc2reg[cc]; 25081 } 25082 }; 25083 25084 /** 25085 * Return the region that controls the dialing plan in the given 25086 * region. (ie. the "normalized phone region".) 25087 * 25088 * @static 25089 * @package 25090 * @param {string} region the region code to normalize 25091 * @return {string} the normalized region code 25092 */ 25093 PhoneLocale.prototype._normPhoneReg = function(region) { 25094 var norm; 25095 25096 // Map all NANP regions to the right region, so that they get parsed using the 25097 // correct state table 25098 switch (region) { 25099 case "US": // usa 25100 case "CA": // canada 25101 case "AG": // antigua and barbuda 25102 case "BS": // bahamas 25103 case "BB": // barbados 25104 case "DM": // dominica 25105 case "DO": // dominican republic 25106 case "GD": // grenada 25107 case "JM": // jamaica 25108 case "KN": // st. kitts and nevis 25109 case "LC": // st. lucia 25110 case "VC": // st. vincent and the grenadines 25111 case "TT": // trinidad and tobago 25112 case "AI": // anguilla 25113 case "BM": // bermuda 25114 case "VG": // british virgin islands 25115 case "KY": // cayman islands 25116 case "MS": // montserrat 25117 case "TC": // turks and caicos 25118 case "AS": // American Samoa 25119 case "VI": // Virgin Islands, U.S. 25120 case "PR": // Puerto Rico 25121 case "MP": // Northern Mariana Islands 25122 case "T:": // East Timor 25123 case "GU": // Guam 25124 norm = "US"; 25125 break; 25126 25127 // these all use the Italian dialling plan 25128 case "IT": // italy 25129 case "SM": // san marino 25130 case "VA": // vatican city 25131 norm = "IT"; 25132 break; 25133 25134 // all the French dependencies are on the French dialling plan 25135 case "FR": // france 25136 case "GF": // french guiana 25137 case "MQ": // martinique 25138 case "GP": // guadeloupe, 25139 case "BL": // saint barthélemy 25140 case "MF": // saint martin 25141 case "RE": // réunion, mayotte 25142 norm = "FR"; 25143 break; 25144 default: 25145 norm = region; 25146 break; 25147 } 25148 return norm; 25149 }; 25150 25151 25152 /*< PhoneHandlerFactory.js */ 25153 /* 25154 * handler.js - Handle phone number parse states 25155 * 25156 * Copyright © 2014-2015, JEDLSoft 25157 * 25158 * Licensed under the Apache License, Version 2.0 (the "License"); 25159 * you may not use this file except in compliance with the License. 25160 * You may obtain a copy of the License at 25161 * 25162 * http://www.apache.org/licenses/LICENSE-2.0 25163 * 25164 * Unless required by applicable law or agreed to in writing, software 25165 * distributed under the License is distributed on an "AS IS" BASIS, 25166 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25167 * 25168 * See the License for the specific language governing permissions and 25169 * limitations under the License. 25170 */ 25171 25172 25173 /** 25174 * @class 25175 * @private 25176 * @constructor 25177 */ 25178 var PhoneHandler = function () { 25179 return this; 25180 }; 25181 25182 PhoneHandler.prototype = { 25183 /** 25184 * @private 25185 * @param {string} number phone number 25186 * @param {Object} fields the fields that have been extracted so far 25187 * @param {Object} regionSettings settings used to parse the rest of the number 25188 */ 25189 processSubscriberNumber: function(number, fields, regionSettings) { 25190 var last; 25191 25192 last = number.search(/[xwtp,;]/i); // last digit of the local number 25193 25194 if (last > -1) { 25195 if (last > 0) { 25196 fields.subscriberNumber = number.substring(0, last); 25197 } 25198 // strip x's which are there to indicate a break between the local subscriber number and the extension, but 25199 // are not themselves a dialable character 25200 fields.extension = number.substring(last).replace('x', ''); 25201 } else { 25202 if (number.length) { 25203 fields.subscriberNumber = number; 25204 } 25205 } 25206 25207 if (regionSettings.plan.getFieldLength('maxLocalLength') && 25208 fields.subscriberNumber && 25209 fields.subscriberNumber.length > regionSettings.plan.getFieldLength('maxLocalLength')) { 25210 fields.invalid = true; 25211 } 25212 }, 25213 /** 25214 * @private 25215 * @param {string} fieldName 25216 * @param {number} length length of phone number 25217 * @param {string} number phone number 25218 * @param {number} currentChar currentChar to be parsed 25219 * @param {Object} fields the fields that have been extracted so far 25220 * @param {Object} regionSettings settings used to parse the rest of the number 25221 * @param {boolean} noExtractTrunk 25222 */ 25223 processFieldWithSubscriberNumber: function(fieldName, length, number, currentChar, fields, regionSettings, noExtractTrunk) { 25224 var ret, end; 25225 25226 if (length !== undefined && length > 0) { 25227 // fixed length 25228 end = length; 25229 if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { 25230 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 25231 } 25232 } else { 25233 // variable length 25234 // the setting is the negative of the length to add, so subtract to make it positive 25235 end = currentChar + 1 - length; 25236 } 25237 25238 if (fields[fieldName] !== undefined) { 25239 // we have a spurious recognition, because this number already contains that field! So, just put 25240 // everything into the subscriberNumber as the default 25241 this.processSubscriberNumber(number, fields, regionSettings); 25242 } else { 25243 fields[fieldName] = number.substring(0, end); 25244 if (number.length > end) { 25245 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 25246 } 25247 } 25248 25249 ret = { 25250 number: "" 25251 }; 25252 25253 return ret; 25254 }, 25255 /** 25256 * @private 25257 * @param {string} fieldName 25258 * @param {number} length length of phone number 25259 * @param {string} number phone number 25260 * @param {number} currentChar currentChar to be parsed 25261 * @param {Object} fields the fields that have been extracted so far 25262 * @param {Object} regionSettings settings used to parse the rest of the number 25263 */ 25264 processField: function(fieldName, length, number, currentChar, fields, regionSettings) { 25265 var ret = {}, end; 25266 25267 if (length !== undefined && length > 0) { 25268 // fixed length 25269 end = length; 25270 if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { 25271 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 25272 } 25273 } else { 25274 // variable length 25275 // the setting is the negative of the length to add, so subtract to make it positive 25276 end = currentChar + 1 - length; 25277 } 25278 25279 if (fields[fieldName] !== undefined) { 25280 // we have a spurious recognition, because this number already contains that field! So, just put 25281 // everything into the subscriberNumber as the default 25282 this.processSubscriberNumber(number, fields, regionSettings); 25283 ret.number = ""; 25284 } else { 25285 fields[fieldName] = number.substring(0, end); 25286 ret.number = (number.length > end) ? number.substring(end) : ""; 25287 } 25288 25289 return ret; 25290 }, 25291 /** 25292 * @private 25293 * @param {string} number phone number 25294 * @param {number} currentChar currentChar to be parsed 25295 * @param {Object} fields the fields that have been extracted so far 25296 * @param {Object} regionSettings settings used to parse the rest of the number 25297 */ 25298 trunk: function(number, currentChar, fields, regionSettings) { 25299 var ret, trunkLength; 25300 25301 if (fields.trunkAccess !== undefined) { 25302 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 25303 this.processSubscriberNumber(number, fields, regionSettings); 25304 number = ""; 25305 } else { 25306 trunkLength = regionSettings.plan.getTrunkCode().length; 25307 fields.trunkAccess = number.substring(0, trunkLength); 25308 number = (number.length > trunkLength) ? number.substring(trunkLength) : ""; 25309 } 25310 25311 ret = { 25312 number: number 25313 }; 25314 25315 return ret; 25316 }, 25317 /** 25318 * @private 25319 * @param {string} number phone number 25320 * @param {number} currentChar currentChar to be parsed 25321 * @param {Object} fields the fields that have been extracted so far 25322 * @param {Object} regionSettings settings used to parse the rest of the number 25323 */ 25324 plus: function(number, currentChar, fields, regionSettings) { 25325 var ret = {}; 25326 25327 if (fields.iddPrefix !== undefined) { 25328 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 25329 this.processSubscriberNumber(number, fields, regionSettings); 25330 ret.number = ""; 25331 } else { 25332 // found the idd prefix, so save it and cause the function to parse the next part 25333 // of the number with the idd table 25334 fields.iddPrefix = number.substring(0, 1); 25335 25336 ret = { 25337 number: number.substring(1), 25338 table: 'idd' // shared subtable that parses the country code 25339 }; 25340 } 25341 return ret; 25342 }, 25343 /** 25344 * @private 25345 * @param {string} number phone number 25346 * @param {number} currentChar currentChar to be parsed 25347 * @param {Object} fields the fields that have been extracted so far 25348 * @param {Object} regionSettings settings used to parse the rest of the number 25349 */ 25350 idd: function(number, currentChar, fields, regionSettings) { 25351 var ret = {}; 25352 25353 if (fields.iddPrefix !== undefined) { 25354 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 25355 this.processSubscriberNumber(number, fields, regionSettings); 25356 ret.number = ""; 25357 } else { 25358 // found the idd prefix, so save it and cause the function to parse the next part 25359 // of the number with the idd table 25360 fields.iddPrefix = number.substring(0, currentChar+1); 25361 25362 ret = { 25363 number: number.substring(currentChar+1), 25364 table: 'idd' // shared subtable that parses the country code 25365 }; 25366 } 25367 25368 return ret; 25369 }, 25370 /** 25371 * @private 25372 * @param {string} number phone number 25373 * @param {number} currentChar currentChar to be parsed 25374 * @param {Object} fields the fields that have been extracted so far 25375 * @param {Object} regionSettings settings used to parse the rest of the number 25376 */ 25377 country: function(number, currentChar, fields, regionSettings) { 25378 var ret, cc; 25379 25380 // found the country code of an IDD number, so save it and cause the function to 25381 // parse the rest of the number with the regular table for this locale 25382 fields.countryCode = number.substring(0, currentChar+1); 25383 cc = fields.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 25384 // console.log("Found country code " + fields.countryCode + ". Switching to country " + locale.region + " to parse the rest of the number"); 25385 25386 ret = { 25387 number: number.substring(currentChar+1), 25388 countryCode: cc 25389 }; 25390 25391 return ret; 25392 }, 25393 /** 25394 * @private 25395 * @param {string} number phone number 25396 * @param {number} currentChar currentChar to be parsed 25397 * @param {Object} fields the fields that have been extracted so far 25398 * @param {Object} regionSettings settings used to parse the rest of the number 25399 */ 25400 cic: function(number, currentChar, fields, regionSettings) { 25401 return this.processField('cic', regionSettings.plan.getFieldLength('cic'), number, currentChar, fields, regionSettings); 25402 }, 25403 /** 25404 * @private 25405 * @param {string} number phone number 25406 * @param {number} currentChar currentChar to be parsed 25407 * @param {Object} fields the fields that have been extracted so far 25408 * @param {Object} regionSettings settings used to parse the rest of the number 25409 */ 25410 service: function(number, currentChar, fields, regionSettings) { 25411 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('serviceCode'), number, currentChar, fields, regionSettings, false); 25412 }, 25413 /** 25414 * @private 25415 * @param {string} number phone number 25416 * @param {number} currentChar currentChar to be parsed 25417 * @param {Object} fields the fields that have been extracted so far 25418 * @param {Object} regionSettings settings used to parse the rest of the number 25419 */ 25420 area: function(number, currentChar, fields, regionSettings) { 25421 var ret, last, end, localLength; 25422 25423 last = number.search(/[xwtp]/i); // last digit of the local number 25424 localLength = (last > -1) ? last : number.length; 25425 25426 if (regionSettings.plan.getFieldLength('areaCode') > 0) { 25427 // fixed length 25428 end = regionSettings.plan.getFieldLength('areaCode'); 25429 if (regionSettings.plan.getTrunkCode() === number.charAt(0)) { 25430 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 25431 localLength -= regionSettings.plan.getTrunkCode().length; 25432 } 25433 } else { 25434 // variable length 25435 // the setting is the negative of the length to add, so subtract to make it positive 25436 end = currentChar + 1 - regionSettings.plan.getFieldLength('areaCode'); 25437 } 25438 25439 // substring() extracts the part of the string up to but not including the end character, 25440 // so add one to compensate 25441 if (regionSettings.plan.getFieldLength('maxLocalLength') !== undefined) { 25442 if (fields.trunkAccess !== undefined || fields.mobilePrefix !== undefined || 25443 fields.countryCode !== undefined || 25444 localLength > regionSettings.plan.getFieldLength('maxLocalLength')) { 25445 // too long for a local number by itself, or a different final state already parsed out the trunk 25446 // or mobile prefix, then consider the rest of this number to be an area code + part of the subscriber number 25447 fields.areaCode = number.substring(0, end); 25448 if (number.length > end) { 25449 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 25450 } 25451 } else { 25452 // shorter than the length needed for a local number, so just consider it a local number 25453 this.processSubscriberNumber(number, fields, regionSettings); 25454 } 25455 } else { 25456 fields.areaCode = number.substring(0, end); 25457 if (number.length > end) { 25458 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 25459 } 25460 } 25461 25462 // extensions are separated from the number by a dash in Germany 25463 if (regionSettings.plan.getFindExtensions() !== undefined && fields.subscriberNumber !== undefined) { 25464 var dash = fields.subscriberNumber.indexOf("-"); 25465 if (dash > -1) { 25466 fields.subscriberNumber = fields.subscriberNumber.substring(0, dash); 25467 fields.extension = fields.subscriberNumber.substring(dash+1); 25468 } 25469 } 25470 25471 ret = { 25472 number: "" 25473 }; 25474 25475 return ret; 25476 }, 25477 /** 25478 * @private 25479 * @param {string} number phone number 25480 * @param {number} currentChar currentChar to be parsed 25481 * @param {Object} fields the fields that have been extracted so far 25482 * @param {Object} regionSettings settings used to parse the rest of the number 25483 */ 25484 none: function(number, currentChar, fields, regionSettings) { 25485 var ret; 25486 25487 // this is a last resort function that is called when nothing is recognized. 25488 // When this happens, just put the whole stripped number into the subscriber number 25489 25490 if (number.length > 0) { 25491 this.processSubscriberNumber(number, fields, regionSettings); 25492 if (currentChar > 0 && currentChar < number.length) { 25493 // if we were part-way through parsing, and we hit an invalid digit, 25494 // indicate that the number could not be parsed properly 25495 fields.invalid = true; 25496 } 25497 } 25498 25499 ret = { 25500 number:"" 25501 }; 25502 25503 return ret; 25504 }, 25505 /** 25506 * @private 25507 * @param {string} number phone number 25508 * @param {number} currentChar currentChar to be parsed 25509 * @param {Object} fields the fields that have been extracted so far 25510 * @param {Object} regionSettings settings used to parse the rest of the number 25511 */ 25512 vsc: function(number, currentChar, fields, regionSettings) { 25513 var ret, length, end; 25514 25515 if (fields.vsc === undefined) { 25516 length = regionSettings.plan.getFieldLength('vsc') || 0; 25517 if (length !== undefined && length > 0) { 25518 // fixed length 25519 end = length; 25520 } else { 25521 // variable length 25522 // the setting is the negative of the length to add, so subtract to make it positive 25523 end = currentChar + 1 - length; 25524 } 25525 25526 // found a VSC code (ie. a "star code"), so save it and cause the function to 25527 // parse the rest of the number with the same table for this locale 25528 fields.vsc = number.substring(0, end); 25529 number = (number.length > end) ? "^" + number.substring(end) : ""; 25530 } else { 25531 // got it twice??? Okay, this is a bogus number then. Just put everything else into the subscriber number as the default 25532 this.processSubscriberNumber(number, fields, regionSettings); 25533 number = ""; 25534 } 25535 25536 // treat the rest of the number as if it were a completely new number 25537 ret = { 25538 number: number 25539 }; 25540 25541 return ret; 25542 }, 25543 /** 25544 * @private 25545 * @param {string} number phone number 25546 * @param {number} currentChar currentChar to be parsed 25547 * @param {Object} fields the fields that have been extracted so far 25548 * @param {Object} regionSettings settings used to parse the rest of the number 25549 */ 25550 cell: function(number, currentChar, fields, regionSettings) { 25551 return this.processFieldWithSubscriberNumber('mobilePrefix', regionSettings.plan.getFieldLength('mobilePrefix'), number, currentChar, fields, regionSettings, false); 25552 }, 25553 /** 25554 * @private 25555 * @param {string} number phone number 25556 * @param {number} currentChar currentChar to be parsed 25557 * @param {Object} fields the fields that have been extracted so far 25558 * @param {Object} regionSettings settings used to parse the rest of the number 25559 */ 25560 personal: function(number, currentChar, fields, regionSettings) { 25561 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('personal'), number, currentChar, fields, regionSettings, false); 25562 }, 25563 /** 25564 * @private 25565 * @param {string} number phone number 25566 * @param {number} currentChar currentChar to be parsed 25567 * @param {Object} fields the fields that have been extracted so far 25568 * @param {Object} regionSettings settings used to parse the rest of the number 25569 */ 25570 emergency: function(number, currentChar, fields, regionSettings) { 25571 return this.processFieldWithSubscriberNumber('emergency', regionSettings.plan.getFieldLength('emergency'), number, currentChar, fields, regionSettings, true); 25572 }, 25573 /** 25574 * @private 25575 * @param {string} number phone number 25576 * @param {number} currentChar currentChar to be parsed 25577 * @param {Object} fields the fields that have been extracted so far 25578 * @param {Object} regionSettings settings used to parse the rest of the number 25579 */ 25580 premium: function(number, currentChar, fields, regionSettings) { 25581 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('premium'), number, currentChar, fields, regionSettings, false); 25582 }, 25583 /** 25584 * @private 25585 * @param {string} number phone number 25586 * @param {number} currentChar currentChar to be parsed 25587 * @param {Object} fields the fields that have been extracted so far 25588 * @param {Object} regionSettings settings used to parse the rest of the number 25589 */ 25590 special: function(number, currentChar, fields, regionSettings) { 25591 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('special'), number, currentChar, fields, regionSettings, false); 25592 }, 25593 /** 25594 * @private 25595 * @param {string} number phone number 25596 * @param {number} currentChar currentChar to be parsed 25597 * @param {Object} fields the fields that have been extracted so far 25598 * @param {Object} regionSettings settings used to parse the rest of the number 25599 */ 25600 service2: function(number, currentChar, fields, regionSettings) { 25601 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service2'), number, currentChar, fields, regionSettings, false); 25602 }, 25603 /** 25604 * @private 25605 * @param {string} number phone number 25606 * @param {number} currentChar currentChar to be parsed 25607 * @param {Object} fields the fields that have been extracted so far 25608 * @param {Object} regionSettings settings used to parse the rest of the number 25609 */ 25610 service3: function(number, currentChar, fields, regionSettings) { 25611 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service3'), number, currentChar, fields, regionSettings, false); 25612 }, 25613 /** 25614 * @private 25615 * @param {string} number phone number 25616 * @param {number} currentChar currentChar to be parsed 25617 * @param {Object} fields the fields that have been extracted so far 25618 * @param {Object} regionSettings settings used to parse the rest of the number 25619 */ 25620 service4: function(number, currentChar, fields, regionSettings) { 25621 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service4'), number, currentChar, fields, regionSettings, false); 25622 }, 25623 /** 25624 * @private 25625 * @param {string} number phone number 25626 * @param {number} currentChar currentChar to be parsed 25627 * @param {Object} fields the fields that have been extracted so far 25628 * @param {Object} regionSettings settings used to parse the rest of the number 25629 */ 25630 cic2: function(number, currentChar, fields, regionSettings) { 25631 return this.processField('cic', regionSettings.plan.getFieldLength('cic2'), number, currentChar, fields, regionSettings); 25632 }, 25633 /** 25634 * @private 25635 * @param {string} number phone number 25636 * @param {number} currentChar currentChar to be parsed 25637 * @param {Object} fields the fields that have been extracted so far 25638 * @param {Object} regionSettings settings used to parse the rest of the number 25639 */ 25640 cic3: function(number, currentChar, fields, regionSettings) { 25641 return this.processField('cic', regionSettings.plan.getFieldLength('cic3'), number, currentChar, fields, regionSettings); 25642 }, 25643 /** 25644 * @private 25645 * @param {string} number phone number 25646 * @param {number} currentChar currentChar to be parsed 25647 * @param {Object} fields the fields that have been extracted so far 25648 * @param {Object} regionSettings settings used to parse the rest of the number 25649 */ 25650 start: function(number, currentChar, fields, regionSettings) { 25651 // don't do anything except transition to the next state 25652 return { 25653 number: number 25654 }; 25655 }, 25656 /** 25657 * @private 25658 * @param {string} number phone number 25659 * @param {number} currentChar currentChar to be parsed 25660 * @param {Object} fields the fields that have been extracted so far 25661 * @param {Object} regionSettings settings used to parse the rest of the number 25662 */ 25663 local: function(number, currentChar, fields, regionSettings) { 25664 // in open dialling plans, we can tell that this number is a local subscriber number because it 25665 // starts with a digit that indicates as such 25666 this.processSubscriberNumber(number, fields, regionSettings); 25667 return { 25668 number: "" 25669 }; 25670 } 25671 }; 25672 25673 // context-sensitive handler 25674 /** 25675 * @class 25676 * @private 25677 * @extends PhoneHandler 25678 * @constructor 25679 */ 25680 var CSStateHandler = function () { 25681 return this; 25682 }; 25683 25684 CSStateHandler.prototype = new PhoneHandler(); 25685 CSStateHandler.prototype.special = function (number, currentChar, fields, regionSettings) { 25686 var ret; 25687 25688 // found a special area code that is both a node and a leaf. In 25689 // this state, we have found the leaf, so chop off the end 25690 // character to make it a leaf. 25691 if (number.charAt(0) === "0") { 25692 fields.trunkAccess = number.charAt(0); 25693 fields.areaCode = number.substring(1, currentChar); 25694 } else { 25695 fields.areaCode = number.substring(0, currentChar); 25696 } 25697 this.processSubscriberNumber(number.substring(currentChar), fields, regionSettings); 25698 25699 ret = { 25700 number: "" 25701 }; 25702 25703 return ret; 25704 }; 25705 25706 /** 25707 * @class 25708 * @private 25709 * @extends PhoneHandler 25710 * @constructor 25711 */ 25712 var USStateHandler = function () { 25713 return this; 25714 }; 25715 25716 USStateHandler.prototype = new PhoneHandler(); 25717 USStateHandler.prototype.vsc = function (number, currentChar, fields, regionSettings) { 25718 var ret; 25719 25720 // found a VSC code (ie. a "star code") 25721 fields.vsc = number; 25722 25723 // treat the rest of the number as if it were a completely new number 25724 ret = { 25725 number: "" 25726 }; 25727 25728 return ret; 25729 }; 25730 25731 /** 25732 * Creates a phone handler instance that is correct for the locale. Phone handlers are 25733 * used to handle parsing of the various fields in a phone number. 25734 * 25735 * @protected 25736 * @static 25737 * @return {PhoneHandler} the correct phone handler for the locale 25738 */ 25739 var PhoneHandlerFactory = function (locale, plan) { 25740 if (plan.getContextFree() !== undefined && typeof(plan.getContextFree()) === 'boolean' && plan.getContextFree() === false) { 25741 return new CSStateHandler(); 25742 } 25743 var region = (locale && locale.getRegion()) || "ZZ"; 25744 switch (region) { 25745 case 'US': 25746 return new USStateHandler(); 25747 break; 25748 default: 25749 return new PhoneHandler(); 25750 } 25751 }; 25752 25753 25754 /*< PhoneNumber.js */ 25755 /* 25756 * phonenum.js - Represent a phone number. 25757 * 25758 * Copyright © 2014-2015, JEDLSoft 25759 * 25760 * Licensed under the Apache License, Version 2.0 (the "License"); 25761 * you may not use this file except in compliance with the License. 25762 * You may obtain a copy of the License at 25763 * 25764 * http://www.apache.org/licenses/LICENSE-2.0 25765 * 25766 * Unless required by applicable law or agreed to in writing, software 25767 * distributed under the License is distributed on an "AS IS" BASIS, 25768 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25769 * 25770 * See the License for the specific language governing permissions and 25771 * limitations under the License. 25772 */ 25773 25774 /* 25775 !depends 25776 ilib.js 25777 NumberingPlan.js 25778 PhoneLocale.js 25779 PhoneHandlerFactory.js 25780 Utils.js 25781 JSUtils.js 25782 */ 25783 25784 // !data states idd mnc 25785 25786 25787 /** 25788 * @class 25789 * Create a new phone number instance that parses the phone number parameter for its 25790 * constituent parts, and store them as separate fields in the returned object. 25791 * 25792 * The options object may include any of these properties: 25793 * 25794 * <ul> 25795 * <li><i>locale</i> The locale with which to parse the number. This gives a clue as to which 25796 * numbering plan to use. 25797 * <li><i>mcc</i> The mobile carrier code (MCC) associated with the carrier that the phone is 25798 * currently connected to, if known. This also can give a clue as to which numbering plan to 25799 * use 25800 * <li>onLoad - a callback function to call when this instance is fully 25801 * loaded. When the onLoad option is given, this class will attempt to 25802 * load any missing locale data using the ilib loader callback. 25803 * When the constructor is done (even if the data is already preassembled), the 25804 * onLoad function is called with the current instance as a parameter, so this 25805 * callback can be used with preassembled or dynamic loading or a mix of the two. 25806 * <li>sync - tell whether to load any missing locale data synchronously or 25807 * asynchronously. If this option is given as "false", then the "onLoad" 25808 * callback must be given, as the instance returned from this constructor will 25809 * not be usable for a while. 25810 * <li><i>loadParams</i> - an object containing parameters to pass to the 25811 * loader callback function when locale data is missing. The parameters are not 25812 * interpretted or modified in any way. They are simply passed along. The object 25813 * may contain any property/value pairs as long as the calling code is in 25814 * agreement with the loader callback function as to what those parameters mean. 25815 * </ul> 25816 * 25817 * This function is locale-sensitive, and will assume any number passed to it is 25818 * appropriate for the given locale. If the MCC is given, this method will assume 25819 * that numbers without an explicit country code have been dialled within the country 25820 * given by the MCC. This affects how things like area codes are parsed. If the MCC 25821 * is not given, this method will use the given locale to determine the country 25822 * code. If the locale is not explicitly given either, then this function uses the 25823 * region of current locale as the default.<p> 25824 * 25825 * The input number may contain any formatting characters for the given locale. Each 25826 * field that is returned in the json object is a simple string of digits with 25827 * all formatting and whitespace characters removed.<p> 25828 * 25829 * The number is decomposed into its parts, regardless if the number 25830 * contains formatting characters. If a particular part cannot be extracted from given 25831 * number, the field will not be returned as a field in the object. If no fields can be 25832 * extracted from the number at all, then all digits found in the string will be 25833 * returned in the subscriberNumber field. If the number parameter contains no 25834 * digits, an empty object is returned.<p> 25835 * 25836 * This instance can contain any of the following fields after parsing is done: 25837 * 25838 * <ul> 25839 * <li>vsc - if this number starts with a VSC (Vertical Service Code, or "star code"), this field will contain the star and the code together 25840 * <li>iddPrefix - the prefix for international direct dialing. This can either be in the form of a plus character or the IDD access code for the given locale 25841 * <li>countryCode - if this number is an international direct dial number, this is the country code 25842 * <li>cic - for "dial-around" services (access to other carriers), this is the prefix used as the carrier identification code 25843 * <li>emergency - an emergency services number 25844 * <li>mobilePrefix - prefix that introduces a mobile phone number 25845 * <li>trunkAccess - trunk access code (long-distance access) 25846 * <li>serviceCode - like a geographic area code, but it is a required prefix for various services 25847 * <li>areaCode - geographic area codes 25848 * <li>subscriberNumber - the unique number of the person or company that pays for this phone line 25849 * <li>extension - in some countries, extensions are dialed directly without going through an operator or a voice prompt system. If the number includes an extension, it is given in this field. 25850 * <li>invalid - this property is added and set to true if the parser found that the number is invalid in the numbering plan for the country. This method will make its best effort at parsing, but any digits after the error will go into the subscriberNumber field 25851 * </ul> 25852 * 25853 * The following rules determine how the number is parsed: 25854 * 25855 * <ol> 25856 * <li>If the number starts with a character that is alphabetic instead of numeric, do 25857 * not parse the number at all. There is a good chance that it is not really a phone number. 25858 * In this case, an empty instance will be returned. 25859 * <li>If the phone number uses the plus notation or explicitly uses the international direct 25860 * dialing prefix for the given locale, then the country code is identified in 25861 * the number. The rules of given locale are used to parse the IDD prefix, and then the rules 25862 * of the country in the prefix are used to parse the rest of the number. 25863 * <li>If a country code is provided as an argument to the function call, use that country's 25864 * parsing rules for the number. This is intended for programs like a Contacts application that 25865 * know what the country is of the person that owns the phone number and can pass that on as 25866 * a hint. 25867 * <li>If the appropriate locale cannot be easily determined, default to using the rules 25868 * for the current user's region. 25869 * </ol> 25870 * 25871 * Example: parsing the number "+49 02101345345-78" will give the following properties in the 25872 * resulting phone number instance: 25873 * 25874 * <pre> 25875 * { 25876 * iddPrefix: "+", 25877 * countryCode: "49", 25878 * areaCode: "02101", 25879 * subscriberNumber: "345345", 25880 * extension: "78" 25881 * } 25882 * </pre> 25883 * 25884 * Note that in this example, because international direct dialing is explicitly used 25885 * in the number, the part of this number after the IDD prefix and country code will be 25886 * parsed exactly the same way in all locales with German rules (country code 49). 25887 * 25888 * Regions currently supported are: 25889 * 25890 * <ul> 25891 * <li>NANP (North American Numbering Plan) countries - USA, Canada, Bermuda, various Caribbean nations 25892 * <li>UK 25893 * <li>Republic of Ireland 25894 * <li>Germany 25895 * <li>France 25896 * <li>Spain 25897 * <li>Italy 25898 * <li>Mexico 25899 * <li>India 25900 * <li>People's Republic of China 25901 * <li>Netherlands 25902 * <li>Belgium 25903 * <li>Luxembourg 25904 * <li>Australia 25905 * <li>New Zealand 25906 * <li>Singapore 25907 * <li>Korea 25908 * <li>Japan 25909 * <li>Russia 25910 * <li>Brazil 25911 * </ul> 25912 * 25913 * @constructor 25914 * @param {!string|PhoneNumber} number A free-form phone number to be parsed, or another phone 25915 * number instance to copy 25916 * @param {Object=} options options that guide the parser in parsing the number 25917 */ 25918 var PhoneNumber = function(number, options) { 25919 var stateData, 25920 regionSettings; 25921 25922 this.sync = true; 25923 this.loadParams = {}; 25924 25925 if (!number || (typeof number === "string" && number.length === 0)) { 25926 return this; 25927 } 25928 25929 if (options) { 25930 if (typeof(options.sync) === 'boolean') { 25931 this.sync = options.sync; 25932 } 25933 25934 if (options.loadParams) { 25935 this.loadParams = options.loadParams; 25936 } 25937 25938 if (typeof(options.onLoad) === 'function') { 25939 /** 25940 * @private 25941 * @type {function(PhoneNumber)} 25942 */ 25943 this.onLoad = options.onLoad; 25944 } 25945 } 25946 25947 if (typeof number === "object") { 25948 /** 25949 * The vertical service code. These are codes that typically 25950 * start with a star or hash, like "*69" for "dial back the 25951 * last number that called me". 25952 * @type {string|undefined} 25953 */ 25954 this.vsc = number.vsc; 25955 25956 /** 25957 * The international direct dialing prefix. This is always 25958 * followed by the country code. 25959 * @type {string} 25960 */ 25961 this.iddPrefix = number.iddPrefix; 25962 25963 /** 25964 * The unique IDD country code for the country where the 25965 * phone number is serviced. 25966 * @type {string|undefined} 25967 */ 25968 this.countryCode = number.countryCode; 25969 25970 /** 25971 * The digits required to access the trunk. 25972 * @type {string|undefined} 25973 */ 25974 this.trunkAccess = number.trunkAccess; 25975 25976 /** 25977 * The carrier identification code used to identify 25978 * alternate long distance or international carriers. 25979 * @type {string|undefined} 25980 */ 25981 this.cic = number.cic; 25982 25983 /** 25984 * Identifies an emergency number that is typically 25985 * short, such as "911" in North America or "112" in 25986 * many other places in the world. 25987 * @type {string|undefined} 25988 */ 25989 this.emergency = number.emergency; 25990 25991 /** 25992 * The prefix of the subscriber number that indicates 25993 * that this is the number of a mobile phone. 25994 * @type {string|undefined} 25995 */ 25996 this.mobilePrefix = number.mobilePrefix; 25997 25998 /** 25999 * The prefix that identifies this number as commercial 26000 * service number. 26001 * @type {string|undefined} 26002 */ 26003 this.serviceCode = number.serviceCode; 26004 26005 /** 26006 * The area code prefix of a land line number. 26007 * @type {string|undefined} 26008 */ 26009 this.areaCode = number.areaCode; 26010 26011 /** 26012 * The unique number associated with the subscriber 26013 * of this phone. 26014 * @type {string|undefined} 26015 */ 26016 this.subscriberNumber = number.subscriberNumber; 26017 26018 /** 26019 * The direct dial extension number. 26020 * @type {string|undefined} 26021 */ 26022 this.extension = number.extension; 26023 26024 /** 26025 * @private 26026 * @type {boolean} 26027 */ 26028 this.invalid = number.invalid; 26029 26030 if (number.plan && number.locale) { 26031 /** 26032 * @private 26033 * @type {NumberingPlan} 26034 */ 26035 this.plan = number.plan; 26036 26037 /** 26038 * @private 26039 * @type {PhoneLocale} 26040 */ 26041 this.locale = number.locale; 26042 26043 /** 26044 * @private 26045 * @type {NumberingPlan} 26046 */ 26047 this.destinationPlan = number.destinationPlan; 26048 26049 /** 26050 * @private 26051 * @type {PhoneLocale} 26052 */ 26053 this.destinationLocale = number.destinationLocale; 26054 26055 if (options && typeof(options.onLoad) === 'function') { 26056 options.onLoad(this); 26057 } 26058 return; 26059 } 26060 } 26061 26062 new PhoneLocale({ 26063 locale: options && options.locale, 26064 mcc: options && options.mcc, 26065 sync: this.sync, 26066 loadParams: this.loadParams, 26067 onLoad: ilib.bind(this, function(loc) { 26068 this.locale = this.destinationLocale = loc; 26069 new NumberingPlan({ 26070 locale: this.locale, 26071 sync: this.sync, 26072 loadParms: this.loadParams, 26073 onLoad: ilib.bind(this, function (plan) { 26074 this.plan = this.destinationPlan = plan; 26075 26076 if (typeof number === "object") { 26077 // the copy constructor code above did not find the locale 26078 // or plan before, but now they are loaded, so we can return 26079 // already without going further 26080 return; 26081 } 26082 Utils.loadData({ 26083 name: "states.json", 26084 object: PhoneNumber, 26085 locale: this.locale, 26086 sync: this.sync, 26087 loadParams: JSUtils.merge(this.loadParams, { 26088 returnOne: true 26089 }), 26090 callback: ilib.bind(this, function (stdata) { 26091 if (!stdata) { 26092 stdata = PhoneNumber._defaultStates; 26093 } 26094 26095 stateData = stdata; 26096 26097 regionSettings = { 26098 stateData: stateData, 26099 plan: plan, 26100 handler: PhoneHandlerFactory(this.locale, plan) 26101 }; 26102 26103 // use ^ to indicate the beginning of the number, because certain things only match at the beginning 26104 number = "^" + number.replace(/\^/g, ''); 26105 number = PhoneNumber._stripFormatting(number); 26106 26107 this._parseNumber(number, regionSettings, options); 26108 }) 26109 }); 26110 }) 26111 }); 26112 }) 26113 }); 26114 }; 26115 26116 /** 26117 * Parse an International Mobile Subscriber Identity (IMSI) number into its 3 constituent parts: 26118 * 26119 * <ol> 26120 * <li>mcc - Mobile Country Code, which identifies the country where the phone is currently receiving 26121 * service. 26122 * <li>mnc - Mobile Network Code, which identifies the carrier which is currently providing service to the phone 26123 * <li>msin - Mobile Subscription Identifier Number. This is a unique number identifying the mobile phone on 26124 * the network, which usually maps to an account/subscriber in the carrier's database. 26125 * </ol> 26126 * 26127 * Because this function may need to load data to identify the above parts, you can pass an options 26128 * object that controls how the data is loaded. The options may contain any of the following properties: 26129 * 26130 * <ul> 26131 * <li>onLoad - a callback function to call when the parsing is done. When the onLoad option is given, 26132 * this method will attempt to load the locale data using the ilib loader callback. When it is done 26133 * (even if the data is already preassembled), the onLoad function is called with the parsing results 26134 * as a parameter, so this callback can be used with preassembled or dynamic, synchronous or 26135 * asynchronous loading or a mix of the above. 26136 * <li>sync - tell whether to load any missing locale data synchronously or asynchronously. If this 26137 * option is given as "false", then the "onLoad" callback must be given, as the results returned from 26138 * this constructor will not be usable for a while. 26139 * <li><i>loadParams</i> - an object containing parameters to pass to the loader callback function 26140 * when locale data is missing. The parameters are not interpretted or modified in any way. They are 26141 * simply passed along. The object may contain any property/value pairs as long as the calling code is in 26142 * agreement with the loader callback function as to what those parameters mean. 26143 * </ul> 26144 * 26145 * @static 26146 * @param {string} imsi IMSI number to parse 26147 * @param {Object} options options controlling the loading of the locale data 26148 * @return {{mcc:string,mnc:string,msin:string}|undefined} components of the IMSI number, when the locale data 26149 * is loaded synchronously, or undefined if asynchronous 26150 */ 26151 PhoneNumber.parseImsi = function(imsi, options) { 26152 var sync = true, 26153 loadParams = {}, 26154 fields = {}; 26155 26156 if (!imsi) { 26157 return undefined; 26158 } 26159 26160 if (options) { 26161 if (typeof(options.sync) !== 'undefined') { 26162 sync = (options.sync == true); 26163 } 26164 26165 if (options.loadParams) { 26166 loadParams = options.loadParams; 26167 } 26168 } 26169 26170 if (ilib.data.mnc) { 26171 fields = PhoneNumber._parseImsi(ilib.data.mnc, imsi); 26172 26173 if (options && typeof(options.onLoad) === 'function') { 26174 options.onLoad(fields); 26175 } 26176 } else { 26177 Utils.loadData({ 26178 name: "mnc.json", 26179 object: PhoneNumber, 26180 nonlocale: true, 26181 sync: sync, 26182 loadParams: loadParams, 26183 callback: ilib.bind(this, function(data) { 26184 ilib.data.mnc = data; 26185 fields = PhoneNumber._parseImsi(data, imsi); 26186 26187 if (options && typeof(options.onLoad) === 'function') { 26188 options.onLoad(fields); 26189 } 26190 }) 26191 }); 26192 } 26193 return fields; 26194 }; 26195 26196 26197 /** 26198 * @static 26199 * @protected 26200 */ 26201 PhoneNumber._parseImsi = function(data, imsi) { 26202 var ch, 26203 i, 26204 currentState, 26205 end, 26206 handlerMethod, 26207 state = 0, 26208 newState, 26209 fields = {}, 26210 lastLeaf, 26211 consumed = 0; 26212 26213 currentState = data; 26214 if (!currentState) { 26215 // can't parse anything 26216 return undefined; 26217 } 26218 26219 i = 0; 26220 while (i < imsi.length) { 26221 ch = PhoneNumber._getCharacterCode(imsi.charAt(i)); 26222 // console.info("parsing char " + imsi.charAt(i) + " code: " + ch); 26223 if (ch >= 0) { 26224 newState = currentState.s && currentState.s[ch]; 26225 26226 if (typeof(newState) === 'object') { 26227 if (typeof(newState.l) !== 'undefined') { 26228 // save for latter if needed 26229 lastLeaf = newState; 26230 consumed = i; 26231 } 26232 // console.info("recognized digit " + ch + " continuing..."); 26233 // recognized digit, so continue parsing 26234 currentState = newState; 26235 i++; 26236 } else { 26237 if ((typeof(newState) === 'undefined' || newState === 0 || 26238 (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && 26239 lastLeaf) { 26240 // this is possibly a look-ahead and it didn't work... 26241 // so fall back to the last leaf and use that as the 26242 // final state 26243 newState = lastLeaf; 26244 i = consumed; 26245 } 26246 26247 if ((typeof(newState) === 'number' && newState) || 26248 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 26249 // final state 26250 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 26251 handlerMethod = PhoneNumber._states[stateNumber]; 26252 26253 // console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); 26254 26255 // deal with syntactic ambiguity by using the "special" end state instead of "area" 26256 if ( handlerMethod === "area" ) { 26257 end = i+1; 26258 } else { 26259 // unrecognized imsi, so just assume the mnc is 3 digits 26260 end = 6; 26261 } 26262 26263 fields.mcc = imsi.substring(0,3); 26264 fields.mnc = imsi.substring(3,end); 26265 fields.msin = imsi.substring(end); 26266 26267 return fields; 26268 } else { 26269 // parse error 26270 if (imsi.length >= 6) { 26271 fields.mcc = imsi.substring(0,3); 26272 fields.mnc = imsi.substring(3,6); 26273 fields.msin = imsi.substring(6); 26274 } 26275 return fields; 26276 } 26277 } 26278 } else if (ch === -1) { 26279 // non-transition character, continue parsing in the same state 26280 i++; 26281 } else { 26282 // should not happen 26283 // console.info("skipping character " + ch); 26284 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 26285 i++; 26286 } 26287 } 26288 26289 if (i >= imsi.length && imsi.length >= 6) { 26290 // we reached the end of the imsi, but did not finish recognizing anything. 26291 // Default to last resort and assume 3 digit mnc 26292 fields.mcc = imsi.substring(0,3); 26293 fields.mnc = imsi.substring(3,6); 26294 fields.msin = imsi.substring(6); 26295 } else { 26296 // unknown or not enough characters for a real imsi 26297 fields = undefined; 26298 } 26299 26300 // console.info("Globalization.Phone.parseImsi: final result is: " + JSON.stringify(fields)); 26301 return fields; 26302 }; 26303 26304 /** 26305 * @static 26306 * @private 26307 */ 26308 PhoneNumber._stripFormatting = function(str) { 26309 var ret = ""; 26310 var i; 26311 26312 for (i = 0; i < str.length; i++) { 26313 if (PhoneNumber._getCharacterCode(str.charAt(i)) >= -1) { 26314 ret += str.charAt(i); 26315 } 26316 } 26317 return ret; 26318 }; 26319 26320 /** 26321 * @static 26322 * @protected 26323 */ 26324 PhoneNumber._getCharacterCode = function(ch) { 26325 if (ch >= '0' && ch <= '9') { 26326 return ch - '0'; 26327 } 26328 switch (ch) { 26329 case '+': 26330 return 10; 26331 case '*': 26332 return 11; 26333 case '#': 26334 return 12; 26335 case '^': 26336 return 13; 26337 case 'p': // pause chars 26338 case 'P': 26339 case 't': 26340 case 'T': 26341 case 'w': 26342 case 'W': 26343 return -1; 26344 case 'x': 26345 case 'X': 26346 case ',': 26347 case ';': // extension char 26348 return -1; 26349 } 26350 return -2; 26351 }; 26352 26353 /** 26354 * @private 26355 */ 26356 PhoneNumber._states = [ 26357 "none", 26358 "unknown", 26359 "plus", 26360 "idd", 26361 "cic", 26362 "service", 26363 "cell", 26364 "area", 26365 "vsc", 26366 "country", 26367 "personal", 26368 "special", 26369 "trunk", 26370 "premium", 26371 "emergency", 26372 "service2", 26373 "service3", 26374 "service4", 26375 "cic2", 26376 "cic3", 26377 "start", 26378 "local" 26379 ]; 26380 26381 /** 26382 * @private 26383 */ 26384 PhoneNumber._fieldOrder = [ 26385 "vsc", 26386 "iddPrefix", 26387 "countryCode", 26388 "trunkAccess", 26389 "cic", 26390 "emergency", 26391 "mobilePrefix", 26392 "serviceCode", 26393 "areaCode", 26394 "subscriberNumber", 26395 "extension" 26396 ]; 26397 26398 PhoneNumber._defaultStates = { 26399 "s": [ 26400 0, 26401 21, // 1 -> local 26402 21, // 2 -> local 26403 21, // 3 -> local 26404 21, // 4 -> local 26405 21, // 5 -> local 26406 21, // 6 -> local 26407 21, // 7 -> local 26408 21, // 8 -> local 26409 21, // 9 -> local 26410 0,0,0, 26411 { // ^ 26412 "s": [ 26413 { // ^0 26414 "s": [3], // ^00 -> idd 26415 "l": 12 // ^0 -> trunk 26416 }, 26417 21, // ^1 -> local 26418 21, // ^2 -> local 26419 21, // ^3 -> local 26420 21, // ^4 -> local 26421 21, // ^5 -> local 26422 21, // ^6 -> local 26423 21, // ^7 -> local 26424 21, // ^8 -> local 26425 21, // ^9 -> local 26426 2 // ^+ -> plus 26427 ] 26428 } 26429 ] 26430 }; 26431 26432 PhoneNumber.prototype = { 26433 /** 26434 * @protected 26435 * @param {string} number 26436 * @param {Object} regionData 26437 * @param {Object} options 26438 * @param {string} countryCode 26439 */ 26440 _parseOtherCountry: function(number, regionData, options, countryCode) { 26441 new PhoneLocale({ 26442 locale: this.locale, 26443 countryCode: countryCode, 26444 sync: this.sync, 26445 loadParms: this.loadParams, 26446 onLoad: ilib.bind(this, function (loc) { 26447 /* 26448 * this.locale is the locale where this number is being parsed, 26449 * and is used to parse the IDD prefix, if any, and this.destinationLocale is 26450 * the locale of the rest of this number after the IDD prefix. 26451 */ 26452 /** @type {PhoneLocale} */ 26453 this.destinationLocale = loc; 26454 26455 Utils.loadData({ 26456 name: "states.json", 26457 object: PhoneNumber, 26458 locale: this.destinationLocale, 26459 sync: this.sync, 26460 loadParams: JSUtils.merge(this.loadParams, { 26461 returnOne: true 26462 }), 26463 callback: ilib.bind(this, function (stateData) { 26464 if (!stateData) { 26465 stateData = PhoneNumber._defaultStates; 26466 } 26467 26468 new NumberingPlan({ 26469 locale: this.destinationLocale, 26470 sync: this.sync, 26471 loadParms: this.loadParams, 26472 onLoad: ilib.bind(this, function (plan) { 26473 /* 26474 * this.plan is the plan where this number is being parsed, 26475 * and is used to parse the IDD prefix, if any, and this.destinationPlan is 26476 * the plan of the rest of this number after the IDD prefix in the 26477 * destination locale. 26478 */ 26479 /** @type {NumberingPlan} */ 26480 this.destinationPlan = plan; 26481 26482 var regionSettings = { 26483 stateData: stateData, 26484 plan: plan, 26485 handler: PhoneHandlerFactory(this.destinationLocale, plan) 26486 }; 26487 26488 // for plans that do not skip the trunk code when dialing from 26489 // abroad, we need to treat the number from here on in as if it 26490 // were parsing a local number from scratch. That way, the parser 26491 // does not get confused between parts of the number at the 26492 // beginning of the number, and parts in the middle. 26493 if (!plan.getSkipTrunk()) { 26494 number = '^' + number; 26495 } 26496 26497 // recursively call the parser with the new states data 26498 // to finish the parsing 26499 this._parseNumber(number, regionSettings, options); 26500 }) 26501 }); 26502 }) 26503 }); 26504 }) 26505 }); 26506 }, 26507 26508 /** 26509 * @protected 26510 * @param {string} number 26511 * @param {Object} regionData 26512 * @param {Object} options 26513 */ 26514 _parseNumber: function(number, regionData, options) { 26515 var i, ch, 26516 regionSettings, 26517 newState, 26518 dot, 26519 handlerMethod, 26520 result, 26521 currentState = regionData.stateData, 26522 lastLeaf = undefined, 26523 consumed = 0; 26524 26525 regionSettings = regionData; 26526 dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java 26527 26528 i = 0; 26529 while (i < number.length) { 26530 ch = PhoneNumber._getCharacterCode(number.charAt(i)); 26531 if (ch >= 0) { 26532 // newState = stateData.states[state][ch]; 26533 newState = currentState.s && currentState.s[ch]; 26534 26535 if (!newState && currentState.s && currentState.s[dot]) { 26536 newState = currentState.s[dot]; 26537 } 26538 26539 if (typeof(newState) === 'object' && i+1 < number.length) { 26540 if (typeof(newState.l) !== 'undefined') { 26541 // this is a leaf node, so save that for later if needed 26542 lastLeaf = newState; 26543 consumed = i; 26544 } 26545 // console.info("recognized digit " + ch + " continuing..."); 26546 // recognized digit, so continue parsing 26547 currentState = newState; 26548 i++; 26549 } else { 26550 if ((typeof(newState) === 'undefined' || newState === 0 || 26551 (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && 26552 lastLeaf) { 26553 // this is possibly a look-ahead and it didn't work... 26554 // so fall back to the last leaf and use that as the 26555 // final state 26556 newState = lastLeaf; 26557 i = consumed; 26558 consumed = 0; 26559 lastLeaf = undefined; 26560 } 26561 26562 if ((typeof(newState) === 'number' && newState) || 26563 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 26564 // final state 26565 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 26566 handlerMethod = PhoneNumber._states[stateNumber]; 26567 26568 if (number.charAt(0) === '^') { 26569 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 26570 } else { 26571 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 26572 } 26573 26574 // reparse whatever is left 26575 number = result.number; 26576 i = consumed = 0; 26577 lastLeaf = undefined; 26578 26579 //console.log("reparsing with new number: " + number); 26580 currentState = regionSettings.stateData; 26581 // if the handler requested a special sub-table, use it for this round of parsing, 26582 // otherwise, set it back to the regular table to continue parsing 26583 26584 if (result.countryCode !== undefined) { 26585 this._parseOtherCountry(number, regionData, options, result.countryCode); 26586 // don't process any further -- let the work be done in the onLoad callbacks 26587 return; 26588 } else if (result.table !== undefined) { 26589 Utils.loadData({ 26590 name: result.table + ".json", 26591 object: PhoneNumber, 26592 nonlocale: true, 26593 sync: this.sync, 26594 loadParams: this.loadParams, 26595 callback: ilib.bind(this, function (data) { 26596 if (!data) { 26597 data = PhoneNumber._defaultStates; 26598 } 26599 26600 regionSettings = { 26601 stateData: data, 26602 plan: regionSettings.plan, 26603 handler: regionSettings.handler 26604 }; 26605 26606 // recursively call the parser with the new states data 26607 // to finish the parsing 26608 this._parseNumber(number, regionSettings, options); 26609 }) 26610 }); 26611 // don't process any further -- let the work be done in the onLoad callbacks 26612 return; 26613 } else if (result.skipTrunk !== undefined) { 26614 ch = PhoneNumber._getCharacterCode(regionSettings.plan.getTrunkCode()); 26615 currentState = currentState.s && currentState.s[ch]; 26616 } 26617 } else { 26618 handlerMethod = (typeof(newState) === 'number') ? "none" : "local"; 26619 // failed parse. Either no last leaf to fall back to, or there was an explicit 26620 // zero in the table. Put everything else in the subscriberNumber field as the 26621 // default place 26622 if (number.charAt(0) === '^') { 26623 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 26624 } else { 26625 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 26626 } 26627 break; 26628 } 26629 } 26630 } else if (ch === -1) { 26631 // non-transition character, continue parsing in the same state 26632 i++; 26633 } else { 26634 // should not happen 26635 // console.info("skipping character " + ch); 26636 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 26637 i++; 26638 } 26639 } 26640 if (i >= number.length && currentState !== regionData.stateData) { 26641 handlerMethod = (typeof(currentState.l) === 'undefined' || currentState === 0) ? "none" : "local"; 26642 // we reached the end of the phone number, but did not finish recognizing anything. 26643 // Default to last resort and put everything that is left into the subscriber number 26644 //console.log("Reached end of number before parsing was complete. Using handler for method none.") 26645 if (number.charAt(0) === '^') { 26646 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 26647 } else { 26648 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 26649 } 26650 } 26651 26652 // let the caller know we are done parsing 26653 if (this.onLoad) { 26654 this.onLoad(this); 26655 } 26656 }, 26657 /** 26658 * @protected 26659 */ 26660 _getPrefix: function() { 26661 return this.areaCode || this.serviceCode || this.mobilePrefix || ""; 26662 }, 26663 26664 /** 26665 * @protected 26666 */ 26667 _hasPrefix: function() { 26668 return (this._getPrefix() !== ""); 26669 }, 26670 26671 /** 26672 * Exclusive or -- return true, if one is defined and the other isn't 26673 * @protected 26674 */ 26675 _xor : function(left, right) { 26676 if ((left === undefined && right === undefined ) || (left !== undefined && right !== undefined)) { 26677 return false; 26678 } else { 26679 return true; 26680 } 26681 }, 26682 26683 /** 26684 * return a version of the phone number that contains only the dialable digits in the correct order 26685 * @protected 26686 */ 26687 _join: function () { 26688 var fieldName, formatted = ""; 26689 26690 try { 26691 for (var field in PhoneNumber._fieldOrder) { 26692 if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string') { 26693 fieldName = PhoneNumber._fieldOrder[field]; 26694 // console.info("normalize: formatting field " + fieldName); 26695 if (this[fieldName] !== undefined) { 26696 formatted += this[fieldName]; 26697 } 26698 } 26699 } 26700 } catch ( e ) { 26701 //console.warn("caught exception in _join: " + e); 26702 throw e; 26703 } 26704 return formatted; 26705 }, 26706 26707 /** 26708 * This routine will compare the two phone numbers in an locale-sensitive 26709 * manner to see if they possibly reference the same phone number.<p> 26710 * 26711 * In many places, 26712 * there are multiple ways to reach the same phone number. In North America for 26713 * example, you might have a number with the trunk access code of "1" and another 26714 * without, and they reference the exact same phone number. This is considered a 26715 * strong match. For a different pair of numbers, one may be a local number and 26716 * the other a full phone number with area code, which may reference the same 26717 * phone number if the local number happens to be located in that area code. 26718 * However, you cannot say for sure if it is in that area code, so it will 26719 * be considered a somewhat weaker match.<p> 26720 * 26721 * Similarly, in other countries, there are sometimes different ways of 26722 * reaching the same destination, and the way that numbers 26723 * match depends on the locale.<p> 26724 * 26725 * The various phone number fields are handled differently for matches. There 26726 * are various fields that do not need to match at all. For example, you may 26727 * type equally enter "00" or "+" into your phone to start international direct 26728 * dialling, so the iddPrefix field does not need to match at all.<p> 26729 * 26730 * Typically, fields that require matches need to match exactly if both sides have a value 26731 * for that field. If both sides specify a value and those values differ, that is 26732 * a strong non-match. If one side does not have a value and the other does, that 26733 * causes a partial match, because the number with the missing field may possibly 26734 * have an implied value that matches the other number. For example, the numbers 26735 * "650-555-1234" and "555-1234" have a partial match as the local number "555-1234" 26736 * might possibly have the same 650 area code as the first number, and might possibly 26737 * not. If both side do not specify a value for a particular field, that field is 26738 * considered matching.<p> 26739 * 26740 * The values of following fields are ignored when performing matches: 26741 * 26742 * <ul> 26743 * <li>vsc 26744 * <li>iddPrefix 26745 * <li>cic 26746 * <li>trunkAccess 26747 * </ul> 26748 * 26749 * The values of the following fields matter if they do not match: 26750 * 26751 * <ul> 26752 * <li>countryCode - A difference causes a moderately strong problem except for 26753 * certain countries where there is a way to access the same subscriber via IDD 26754 * and via intranetwork dialling 26755 * <li>mobilePrefix - A difference causes a possible non-match 26756 * <li>serviceCode - A difference causes a possible non-match 26757 * <li>areaCode - A difference causes a possible non-match 26758 * <li>subscriberNumber - A difference causes a very strong non-match 26759 * <li>extension - A difference causes a minor non-match 26760 * </ul> 26761 * 26762 * @param {string|PhoneNumber} other other phone number to compare this one to 26763 * @return {number} non-negative integer describing the percentage quality of the 26764 * match. 100 means a very strong match (100%), and lower numbers are less and 26765 * less strong, down to 0 meaning not at all a match. 26766 */ 26767 compare: function (other) { 26768 var match = 100, 26769 FRdepartments = {"590":1, "594":1, "596":1, "262":1}, 26770 ITcountries = {"378":1, "379":1}, 26771 thisPrefix, 26772 otherPrefix, 26773 currentCountryCode = 0; 26774 26775 if (typeof this.locale.region === "string") { 26776 currentCountryCode = this.locale._mapRegiontoCC(this.locale.region); 26777 } 26778 26779 // subscriber number must be present and must match 26780 if (!this.subscriberNumber || !other.subscriberNumber || this.subscriberNumber !== other.subscriberNumber) { 26781 return 0; 26782 } 26783 26784 // extension must match if it is present 26785 if (this._xor(this.extension, other.extension) || this.extension !== other.extension) { 26786 return 0; 26787 } 26788 26789 if (this._xor(this.countryCode, other.countryCode)) { 26790 // if one doesn't have a country code, give it some demerit points, but if the 26791 // one that has the country code has something other than the current country 26792 // add even more. Ignore the special cases where you can dial the same number internationally or via 26793 // the local numbering system 26794 switch (this.locale.getRegion()) { 26795 case 'FR': 26796 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { 26797 if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { 26798 match -= 100; 26799 } 26800 } else { 26801 match -= 16; 26802 } 26803 break; 26804 case 'IT': 26805 if (this.countryCode in ITcountries || other.countryCode in ITcountries) { 26806 if (this.areaCode !== other.areaCode) { 26807 match -= 100; 26808 } 26809 } else { 26810 match -= 16; 26811 } 26812 break; 26813 default: 26814 match -= 16; 26815 if ((this.countryCode !== undefined && this.countryCode !== currentCountryCode) || 26816 (other.countryCode !== undefined && other.countryCode !== currentCountryCode)) { 26817 match -= 16; 26818 } 26819 } 26820 } else if (this.countryCode !== other.countryCode) { 26821 // ignore the special cases where you can dial the same number internationally or via 26822 // the local numbering system 26823 if (other.countryCode === '33' || this.countryCode === '33') { 26824 // france 26825 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { 26826 if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { 26827 match -= 100; 26828 } 26829 } else { 26830 match -= 100; 26831 } 26832 } else if (this.countryCode === '39' || other.countryCode === '39') { 26833 // italy 26834 if (this.countryCode in ITcountries || other.countryCode in ITcountries) { 26835 if (this.areaCode !== other.areaCode) { 26836 match -= 100; 26837 } 26838 } else { 26839 match -= 100; 26840 } 26841 } else { 26842 match -= 100; 26843 } 26844 } 26845 26846 if (this._xor(this.serviceCode, other.serviceCode)) { 26847 match -= 20; 26848 } else if (this.serviceCode !== other.serviceCode) { 26849 match -= 100; 26850 } 26851 26852 if (this._xor(this.mobilePrefix, other.mobilePrefix)) { 26853 match -= 20; 26854 } else if (this.mobilePrefix !== other.mobilePrefix) { 26855 match -= 100; 26856 } 26857 26858 if (this._xor(this.areaCode, other.areaCode)) { 26859 // one has an area code, the other doesn't, so dock some points. It could be a match if the local 26860 // number in the one number has the same implied area code as the explicit area code in the other number. 26861 match -= 12; 26862 } else if (this.areaCode !== other.areaCode) { 26863 match -= 100; 26864 } 26865 26866 thisPrefix = this._getPrefix(); 26867 otherPrefix = other._getPrefix(); 26868 26869 if (thisPrefix && otherPrefix && thisPrefix !== otherPrefix) { 26870 match -= 100; 26871 } 26872 26873 // make sure we are between 0 and 100 26874 if (match < 0) { 26875 match = 0; 26876 } else if (match > 100) { 26877 match = 100; 26878 } 26879 26880 return match; 26881 }, 26882 26883 /** 26884 * Determine whether or not the other phone number is exactly equal to the current one.<p> 26885 * 26886 * The difference between the compare method and the equals method is that the compare 26887 * method compares normalized numbers with each other and returns the degree of match, 26888 * whereas the equals operator returns true iff the two numbers contain the same fields 26889 * and the fields are exactly the same. Functions and other non-phone number properties 26890 * are not compared. 26891 * @param {string|PhoneNumber} other another phone number to compare to this one 26892 * @return {boolean} true if the numbers are the same, false otherwise 26893 */ 26894 equals: function equals(other) { 26895 if (other.locale && this.locale && !this.locale.equals(other.locale) && (!this.countryCode || !other.countryCode)) { 26896 return false; 26897 } 26898 26899 for (var p in other) { 26900 if (p !== undefined && this[p] !== undefined && typeof(this[p]) !== 'object') { 26901 if (other[p] === undefined) { 26902 /*console.error("PhoneNumber.equals: other is missing property " + p + " which has the value " + this[p] + " in this"); 26903 console.error("this is : " + JSON.stringify(this)); 26904 console.error("other is: " + JSON.stringify(other));*/ 26905 return false; 26906 } 26907 if (this[p] !== other[p]) { 26908 /*console.error("PhoneNumber.equals: difference in property " + p); 26909 console.error("this is : " + JSON.stringify(this)); 26910 console.error("other is: " + JSON.stringify(other));*/ 26911 return false; 26912 } 26913 } 26914 } 26915 for (var p in other) { 26916 if (p !== undefined && other[p] !== undefined && typeof(other[p]) !== 'object') { 26917 if (this[p] === undefined) { 26918 /*console.error("PhoneNumber.equals: this is missing property " + p + " which has the value " + other[p] + " in the other"); 26919 console.error("this is : " + JSON.stringify(this)); 26920 console.error("other is: " + JSON.stringify(other));*/ 26921 return false; 26922 } 26923 if (this[p] !== other[p]) { 26924 /*console.error("PhoneNumber.equals: difference in property " + p); 26925 console.error("this is : " + JSON.stringify(this)); 26926 console.error("other is: " + JSON.stringify(other));*/ 26927 return false; 26928 } 26929 } 26930 } 26931 return true; 26932 }, 26933 26934 26935 /** 26936 * @private 26937 * @param {{ 26938 * mcc:string, 26939 * defaultAreaCode:string, 26940 * country:string, 26941 * networkType:string, 26942 * assistedDialing:boolean, 26943 * sms:boolean, 26944 * manualDialing:boolean 26945 * }} options an object containing options to help in normalizing. 26946 * @param {PhoneNumber} norm 26947 * @param {PhoneLocale} homeLocale 26948 * @param {PhoneLocale} currentLocale 26949 * @param {NumberingPlan} currentPlan 26950 * @param {PhoneLocale} destinationLocale 26951 * @param {NumberingPlan} destinationPlan 26952 * @param {boolean} sync 26953 * @param {Object|undefined} loadParams 26954 */ 26955 _doNormalize: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams) { 26956 var formatted = ""; 26957 26958 if (!norm.invalid && options && options.assistedDialing) { 26959 // don't normalize things that don't have subscriber numbers. Also, don't normalize 26960 // manually dialed local numbers. Do normalize local numbers in contact entries. 26961 if (norm.subscriberNumber && 26962 (!options.manualDialing || 26963 norm.iddPrefix || 26964 norm.countryCode || 26965 norm.trunkAccess)) { 26966 // console.log("normalize: assisted dialling normalization of " + JSON.stringify(norm)); 26967 if (currentLocale.getRegion() !== destinationLocale.getRegion()) { 26968 // we are currently calling internationally 26969 if (!norm._hasPrefix() && 26970 options.defaultAreaCode && 26971 destinationLocale.getRegion() === homeLocale.getRegion() && 26972 (!destinationPlan.getFieldLength("minLocalLength") || 26973 norm.subscriberNumber.length >= destinationPlan.getFieldLength("minLocalLength"))) { 26974 // area code is required when dialling from international, but only add it if we are dialing 26975 // to our home area. Otherwise, the default area code is not valid! 26976 norm.areaCode = options.defaultAreaCode; 26977 if (!destinationPlan.getSkipTrunk() && destinationPlan.getTrunkCode()) { 26978 // some phone systems require the trunk access code, even when dialling from international 26979 norm.trunkAccess = destinationPlan.getTrunkCode(); 26980 } 26981 } 26982 26983 if (norm.trunkAccess && destinationPlan.getSkipTrunk()) { 26984 // on some phone systems, the trunk access code is dropped when dialling from international 26985 delete norm.trunkAccess; 26986 } 26987 26988 // make sure to get the country code for the destination region, not the current region! 26989 if (options.sms) { 26990 if (homeLocale.getRegion() === "US" && currentLocale.getRegion() !== "US") { 26991 if (destinationLocale.getRegion() !== "US") { 26992 norm.iddPrefix = "011"; // non-standard code to make it go through the US first 26993 norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.getRegion()); 26994 } else if (options.networkType === "cdma") { 26995 delete norm.iddPrefix; 26996 delete norm.countryCode; 26997 if (norm.areaCode) { 26998 norm.trunkAccess = "1"; 26999 } 27000 } else if (norm.areaCode) { 27001 norm.iddPrefix = "+"; 27002 norm.countryCode = "1"; 27003 delete norm.trunkAccess; 27004 } 27005 } else { 27006 norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; 27007 norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.region); 27008 } 27009 } else if (norm._hasPrefix() && !norm.countryCode) { 27010 norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.region); 27011 } 27012 27013 if (norm.countryCode && !options.sms) { 27014 // for CDMA, make sure to get the international dialling access code for the current region, not the destination region 27015 // all umts carriers support plus dialing 27016 norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; 27017 } 27018 } else { 27019 // console.log("normalize: dialing within the country"); 27020 if (options.defaultAreaCode) { 27021 if (destinationPlan.getPlanStyle() === "open") { 27022 if (!norm.trunkAccess && norm._hasPrefix() && destinationPlan.getTrunkCode()) { 27023 // call is not local to this area code, so you have to dial the trunk code and the area code 27024 norm.trunkAccess = destinationPlan.getTrunkCode(); 27025 } 27026 } else { 27027 // In closed plans, you always have to dial the area code, even if the call is local. 27028 if (!norm._hasPrefix()) { 27029 if (destinationLocale.getRegion() === homeLocale.getRegion()) { 27030 norm.areaCode = options.defaultAreaCode; 27031 if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { 27032 norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); 27033 } 27034 } 27035 } else { 27036 if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { 27037 norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); 27038 } 27039 } 27040 } 27041 } 27042 27043 if (options.sms && 27044 homeLocale.getRegion() === "US" && 27045 currentLocale.getRegion() !== "US") { 27046 norm.iddPrefix = "011"; // make it go through the US first 27047 if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { 27048 delete norm.trunkAccess; 27049 } 27050 } else if (norm.iddPrefix || norm.countryCode) { 27051 // we are in our destination country, so strip the international dialling prefixes 27052 delete norm.iddPrefix; 27053 delete norm.countryCode; 27054 27055 if ((destinationPlan.getPlanStyle() === "open" || destinationPlan.getTrunkRequired()) && destinationPlan.getTrunkCode()) { 27056 norm.trunkAccess = destinationPlan.getTrunkCode(); 27057 } 27058 } 27059 } 27060 } 27061 } else if (!norm.invalid) { 27062 // console.log("normalize: non-assisted normalization"); 27063 if (!norm._hasPrefix() && options && options.defaultAreaCode && destinationLocale.getRegion() === homeLocale.region) { 27064 norm.areaCode = options.defaultAreaCode; 27065 } 27066 27067 if (!norm.countryCode && norm._hasPrefix()) { 27068 norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.getRegion()); 27069 } 27070 27071 if (norm.countryCode) { 27072 if (options && options.networkType && options.networkType === "cdma") { 27073 norm.iddPrefix = currentPlan.getIDDCode(); 27074 } else { 27075 // all umts carriers support plus dialing 27076 norm.iddPrefix = "+"; 27077 } 27078 27079 if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { 27080 delete norm.trunkAccess; 27081 } else if (!destinationPlan.getSkipTrunk() && !norm.trunkAccess && destinationPlan.getTrunkCode()) { 27082 norm.trunkAccess = destinationPlan.getTrunkCode(); 27083 } 27084 } 27085 } 27086 27087 // console.info("normalize: after normalization, the normalized phone number is: " + JSON.stringify(norm)); 27088 formatted = norm._join(); 27089 27090 return formatted; 27091 }, 27092 27093 /** 27094 * @private 27095 * @param {{ 27096 * mcc:string, 27097 * defaultAreaCode:string, 27098 * country:string, 27099 * networkType:string, 27100 * assistedDialing:boolean, 27101 * sms:boolean, 27102 * manualDialing:boolean 27103 * }} options an object containing options to help in normalizing. 27104 * @param {PhoneNumber} norm 27105 * @param {PhoneLocale} homeLocale 27106 * @param {PhoneLocale} currentLocale 27107 * @param {NumberingPlan} currentPlan 27108 * @param {PhoneLocale} destinationLocale 27109 * @param {NumberingPlan} destinationPlan 27110 * @param {boolean} sync 27111 * @param {Object|undefined} loadParams 27112 * @param {function(string)} callback 27113 */ 27114 _doReparse: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams, callback) { 27115 var formatted, 27116 tempRegion; 27117 27118 if (options && 27119 options.assistedDialing && 27120 !norm.trunkAccess && 27121 !norm.iddPrefix && 27122 norm.subscriberNumber && 27123 norm.subscriberNumber.length > destinationPlan.getFieldLength("maxLocalLength")) { 27124 27125 // numbers that are too long are sometimes international direct dialed numbers that 27126 // are missing the IDD prefix. So, try reparsing it using a plus in front to see if that works. 27127 new PhoneNumber("+" + this._join(), { 27128 locale: this.locale, 27129 sync: sync, 27130 loadParms: loadParams, 27131 onLoad: ilib.bind(this, function (data) { 27132 tempRegion = (data.countryCode && data.locale._mapCCtoRegion(data.countryCode)); 27133 27134 if (tempRegion && tempRegion !== "unknown" && tempRegion !== "SG") { 27135 // only use it if it is a recognized country code. Singapore (SG) is a special case. 27136 norm = data; 27137 destinationLocale = data.destinationLocale; 27138 destinationPlan = data.destinationPlan; 27139 } 27140 27141 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 27142 if (typeof(callback) === 'function') { 27143 callback(formatted); 27144 } 27145 }) 27146 }); 27147 } else if (options && options.assistedDialing && norm.invalid && currentLocale.region !== norm.locale.region) { 27148 // if this number is not valid for the locale it was parsed with, try again with the current locale 27149 // console.log("norm is invalid. Attempting to reparse with the current locale"); 27150 27151 new PhoneNumber(this._join(), { 27152 locale: currentLocale, 27153 sync: sync, 27154 loadParms: loadParams, 27155 onLoad: ilib.bind(this, function (data) { 27156 if (data && !data.invalid) { 27157 norm = data; 27158 } 27159 27160 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 27161 if (typeof(callback) === 'function') { 27162 callback(formatted); 27163 } 27164 }) 27165 }); 27166 } else { 27167 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 27168 if (typeof(callback) === 'function') { 27169 callback(formatted); 27170 } 27171 } 27172 }, 27173 27174 /** 27175 * This function normalizes the current phone number to a canonical format and returns a 27176 * string with that phone number. If parts are missing, this function attempts to fill in 27177 * those parts.<p> 27178 * 27179 * The options object contains a set of properties that can possibly help normalize 27180 * this number by providing "extra" information to the algorithm. The options 27181 * parameter may be null or an empty object if no hints can be determined before 27182 * this call is made. If any particular hint is not 27183 * available, it does not need to be present in the options object.<p> 27184 * 27185 * The following is a list of hints that the algorithm will look for in the options 27186 * object: 27187 * 27188 * <ul> 27189 * <li><i>mcc</i> the mobile carrier code of the current network upon which this 27190 * phone is operating. This is translated into an IDD country code. This is 27191 * useful if the number being normalized comes from CNAP (callerid) and the 27192 * MCC is known. 27193 * <li><i>defaultAreaCode</i> the area code of the phone number of the current 27194 * device, if available. Local numbers in a person's contact list are most 27195 * probably in this same area code. 27196 * <li><i>country</i> the 2 letter ISO 3166 code of the country if it is 27197 * known from some other means such as parsing the physical address of the 27198 * person associated with the phone number, or the from the domain name 27199 * of the person's email address 27200 * <li><i>networkType</i> specifies whether the phone is currently connected to a 27201 * CDMA network or a UMTS network. Valid values are the strings "cdma" and "umts". 27202 * If one of those two strings are not specified, or if this property is left off 27203 * completely, this method will assume UMTS. 27204 * </ul> 27205 * 27206 * The following are a list of options that control the behaviour of the normalization: 27207 * 27208 * <ul> 27209 * <li><i>assistedDialing</i> if this is set to true, the number will be normalized 27210 * so that it can dialled directly on the type of network this phone is 27211 * currently connected to. This allows customers to dial numbers or use numbers 27212 * in their contact list that are specific to their "home" region when they are 27213 * roaming and those numbers would not otherwise work with the current roaming 27214 * carrier as they are. The home region is 27215 * specified as the phoneRegion system preference that is settable in the 27216 * regional settings app. With assisted dialling, this method will add or 27217 * remove international direct dialling prefixes and country codes, as well as 27218 * national trunk access codes, as required by the current roaming carrier and the 27219 * home region in order to dial the number properly. If it is not possible to 27220 * construct a full international dialling sequence from the options and hints given, 27221 * this function will not modify the phone number, and will return "undefined". 27222 * If assisted dialling is false or not specified, then this method will attempt 27223 * to add all the information it can to the number so that it is as fully 27224 * specified as possible. This allows two numbers to be compared more easily when 27225 * those two numbers were otherwise only partially specified. 27226 * <li><i>sms</i> set this option to true for the following conditions: 27227 * <ul> 27228 * <li>assisted dialing is turned on 27229 * <li>the phone number represents the destination of an SMS message 27230 * <li>the phone is UMTS 27231 * <li>the phone is SIM-locked to its carrier 27232 * </ul> 27233 * This enables special international direct dialling codes to route the SMS message to 27234 * the correct carrier. If assisted dialling is not turned on, this option has no 27235 * affect. 27236 * <li><i>manualDialing</i> set this option to true if the user is entering this number on 27237 * the keypad directly, and false when the number comes from a stored location like a 27238 * contact entry or a call log entry. When true, this option causes the normalizer to 27239 * not perform any normalization on numbers that look like local numbers in the home 27240 * country. If false, all numbers go through normalization. This option only has an effect 27241 * when the assistedDialing option is true as well, otherwise it is ignored. 27242 * </ul> 27243 * 27244 * If both a set of options and a locale are given, and they offer conflicting 27245 * information, the options will take precedence. The idea is that the locale 27246 * tells you the region setting that the user has chosen (probably in 27247 * firstuse), whereas the the hints are more current information such as 27248 * where the phone is currently operating (the MCC).<p> 27249 * 27250 * This function performs the following types of normalizations with assisted 27251 * dialling turned on: 27252 * 27253 * <ol> 27254 * <li>If the current location of the phone matches the home country, this is a 27255 * domestic call. 27256 * <ul> 27257 * <li>Remove any iddPrefix and countryCode fields, as they are not needed 27258 * <li>Add in a trunkAccess field that may be necessary to call a domestic numbers 27259 * in the home country 27260 * </ul> 27261 * <li> If the current location of the phone does not match the home country, 27262 * attempt to form a whole international number. 27263 * <ul> 27264 * <li>Add in the area code if it is missing from the phone number and the area code 27265 * of the current phone is available in the hints 27266 * <li>Add the country dialling code for the home country if it is missing from the 27267 * phone number 27268 * <li>Add or replace the iddPrefix with the correct one for the current country. The 27269 * phone number will have been parsed with the settings for the home country, so 27270 * the iddPrefix may be incorrect for the 27271 * current country. The iddPrefix for the current country can be "+" if the phone 27272 * is connected to a UMTS network, and either a "+" or a country-dependent 27273 * sequences of digits for CDMA networks. 27274 * </ul> 27275 * </ol> 27276 * 27277 * This function performs the following types of normalization with assisted 27278 * dialling turned off: 27279 * 27280 * <ul> 27281 * <li>Normalize the international direct dialing prefix to be a plus or the 27282 * international direct dialling access code for the current country, depending 27283 * on the network type. 27284 * <li>If a number is a local number (ie. it is missing its area code), 27285 * use a default area code from the hints if available. CDMA phones always know their area 27286 * code, and GSM/UMTS phones know their area code in many instances, but not always 27287 * (ie. not on Vodaphone or Telcel phones). If the default area code is not available, 27288 * do not add it. 27289 * <li>In assisted dialling mode, if a number is missing its country code, 27290 * use the current MCC number if 27291 * it is available to figure out the current country code, and prepend that 27292 * to the number. If it is not available, leave it off. Also, use that 27293 * country's settings to parse the number instead of the current format 27294 * locale. 27295 * <li>For North American numbers with an area code but no trunk access 27296 * code, add in the trunk access code. 27297 * <li>For other countries, if the country code is added in step 3, remove the 27298 * trunk access code when required by that country's conventions for 27299 * international calls. If the country requires a trunk access code for 27300 * international calls and it doesn't exist, add one. 27301 * </ul> 27302 * 27303 * This method modifies the current object, and also returns a string 27304 * containing the normalized phone number that can be compared directly against 27305 * other normalized numbers. The canonical format for phone numbers that is 27306 * returned from thhomeLocaleis method is simply an uninterrupted and unformatted string 27307 * of dialable digits. 27308 * 27309 * @param {{ 27310 * mcc:string, 27311 * defaultAreaCode:string, 27312 * country:string, 27313 * networkType:string, 27314 * assistedDialing:boolean, 27315 * sms:boolean, 27316 * manualDialing:boolean 27317 * }} options an object containing options to help in normalizing. 27318 * @return {string|undefined} the normalized string, or undefined if the number 27319 * could not be normalized 27320 */ 27321 normalize: function(options) { 27322 var norm, 27323 sync = true, 27324 loadParams = {}; 27325 27326 27327 if (options) { 27328 if (typeof(options.sync) !== 'undefined') { 27329 sync = (options.sync == true); 27330 } 27331 27332 if (options.loadParams) { 27333 loadParams = options.loadParams; 27334 } 27335 } 27336 27337 // Clone this number, so we don't mess with the original. 27338 // No need to do this asynchronously because it's a copy constructor which doesn't 27339 // load any extra files. 27340 norm = new PhoneNumber(this); 27341 27342 var normalized; 27343 27344 if (options && (typeof(options.mcc) !== 'undefined' || typeof(options.country) !== 'undefined')) { 27345 new PhoneLocale({ 27346 mcc: options.mcc, 27347 countryCode: options.countryCode, 27348 locale: this.locale, 27349 sync: sync, 27350 loadParams: loadParams, 27351 onLoad: ilib.bind(this, function(loc) { 27352 new NumberingPlan({ 27353 locale: loc, 27354 sync: sync, 27355 loadParms: loadParams, 27356 onLoad: ilib.bind(this, function (plan) { 27357 this._doReparse(options, norm, this.locale, loc, plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { 27358 normalized = fmt; 27359 27360 if (options && typeof(options.onLoad) === 'function') { 27361 options.onLoad(fmt); 27362 } 27363 }); 27364 }) 27365 }); 27366 }) 27367 }); 27368 } else { 27369 this._doReparse(options, norm, this.locale, this.locale, this.plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { 27370 normalized = fmt; 27371 27372 if (options && typeof(options.onLoad) === 'function') { 27373 options.onLoad(fmt); 27374 } 27375 }); 27376 } 27377 27378 // return the value for the synchronous case 27379 return normalized; 27380 } 27381 }; 27382 27383 27384 /*< PhoneFmt.js */ 27385 /* 27386 * phonefmt.js - Represent a phone number formatter. 27387 * 27388 * Copyright © 2014-2015, JEDLSoft 27389 * 27390 * Licensed under the Apache License, Version 2.0 (the "License"); 27391 * you may not use this file except in compliance with the License. 27392 * You may obtain a copy of the License at 27393 * 27394 * http://www.apache.org/licenses/LICENSE-2.0 27395 * 27396 * Unless required by applicable law or agreed to in writing, software 27397 * distributed under the License is distributed on an "AS IS" BASIS, 27398 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27399 * 27400 * See the License for the specific language governing permissions and 27401 * limitations under the License. 27402 */ 27403 27404 /* 27405 !depends 27406 ilib.js 27407 Locale.js 27408 NumberingPlan.js 27409 PhoneNumber.js 27410 PhoneLocale.js 27411 Utils.js 27412 JSUtils.js 27413 */ 27414 27415 // !data phonefmt 27416 27417 27418 /** 27419 * @class 27420 * Create a new phone number formatter object that formats numbers according to the parameters.<p> 27421 * 27422 * The options object can contain zero or more of the following parameters: 27423 * 27424 * <ul> 27425 * <li><i>locale</i> locale to use to format this number, or undefined to use the default locale 27426 * <li><i>style</i> the name of style to use to format numbers, or undefined to use the default style 27427 * <li><i>mcc</i> the MCC of the country to use if the number is a local number and the country code is not known 27428 * 27429 * <li><i>onLoad</i> - a callback function to call when the locale data is fully loaded and the address has been 27430 * parsed. When the onLoad option is given, the address formatter object 27431 * will attempt to load any missing locale data using the ilib loader callback. 27432 * When the constructor is done (even if the data is already preassembled), the 27433 * onLoad function is called with the current instance as a parameter, so this 27434 * callback can be used with preassembled or dynamic loading or a mix of the two. 27435 * 27436 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 27437 * asynchronously. If this option is given as "false", then the "onLoad" 27438 * callback must be given, as the instance returned from this constructor will 27439 * not be usable for a while. 27440 * 27441 * <li><i>loadParams</i> - an object containing parameters to pass to the 27442 * loader callback function when locale data is missing. The parameters are not 27443 * interpretted or modified in any way. They are simply passed along. The object 27444 * may contain any property/value pairs as long as the calling code is in 27445 * agreement with the loader callback function as to what those parameters mean. 27446 * </ul> 27447 * 27448 * Some regions have more than one style of formatting, and the style parameter 27449 * selects which style the user prefers. An array of style names that this locale 27450 * supports can be found by calling {@link PhoneFmt.getAvailableStyles}. 27451 * Example phone numbers can be retrieved for each style by calling 27452 * {@link PhoneFmt.getStyleExample}. 27453 * <p> 27454 * 27455 * If the MCC is given, numbers will be formatted in the manner of the country 27456 * specified by the MCC. If it is not given, but the locale is, the manner of 27457 * the country in the locale will be used. If neither the locale or MCC are not given, 27458 * then the country of the current ilib locale is used. 27459 * 27460 * @constructor 27461 * @param {Object} options properties that control how this formatter behaves 27462 */ 27463 var PhoneFmt = function(options) { 27464 this.sync = true; 27465 this.styleName = 'default', 27466 this.loadParams = {}; 27467 27468 var locale = new Locale(); 27469 27470 if (options) { 27471 if (options.locale) { 27472 locale = options.locale; 27473 } 27474 27475 if (typeof(options.sync) !== 'undefined') { 27476 this.sync = (options.sync == true); 27477 } 27478 27479 if (options.loadParams) { 27480 this.loadParams = options.loadParams; 27481 } 27482 27483 if (options.style) { 27484 this.style = options.style; 27485 } 27486 } 27487 27488 new PhoneLocale({ 27489 locale: locale, 27490 mcc: options && options.mcc, 27491 countryCode: options && options.countryCode, 27492 onLoad: ilib.bind(this, function (data) { 27493 /** @type {PhoneLocale} */ 27494 this.locale = data; 27495 27496 new NumberingPlan({ 27497 locale: this.locale, 27498 sync: this.sync, 27499 loadParms: this.loadParams, 27500 onLoad: ilib.bind(this, function (plan) { 27501 /** @type {NumberingPlan} */ 27502 this.plan = plan; 27503 27504 Utils.loadData({ 27505 name: "phonefmt.json", 27506 object: PhoneFmt, 27507 locale: this.locale, 27508 sync: this.sync, 27509 loadParams: JSUtils.merge(this.loadParams, { 27510 returnOne: true 27511 }), 27512 callback: ilib.bind(this, function (fmtdata) { 27513 this.fmtdata = fmtdata; 27514 27515 if (options && typeof(options.onLoad) === 'function') { 27516 options.onLoad(this); 27517 } 27518 }) 27519 }); 27520 }) 27521 }); 27522 }) 27523 }); 27524 }; 27525 27526 PhoneFmt.prototype = { 27527 /** 27528 * 27529 * @protected 27530 * @param {string} part 27531 * @param {Object} formats 27532 * @param {boolean} mustUseAll 27533 */ 27534 _substituteDigits: function(part, formats, mustUseAll) { 27535 var formatString, 27536 formatted = "", 27537 partIndex = 0, 27538 templates, 27539 i; 27540 27541 // console.info("Globalization.Phone._substituteDigits: typeof(formats) is " + typeof(formats)); 27542 if (!part) { 27543 return formatted; 27544 } 27545 27546 if (typeof(formats) === "object") { 27547 templates = (typeof(formats.template) !== 'undefined') ? formats.template : formats; 27548 if (part.length > templates.length) { 27549 // too big, so just use last resort rule. 27550 throw "part " + part + " is too big. We do not have a format template to format it."; 27551 } 27552 // use the format in this array that corresponds to the digit length of this 27553 // part of the phone number 27554 formatString = templates[part.length-1]; 27555 // console.info("Globalization.Phone._substituteDigits: formats is an Array: " + JSON.stringify(formats)); 27556 } else { 27557 formatString = formats; 27558 } 27559 27560 for (i = 0; i < formatString.length; i++) { 27561 if (formatString.charAt(i) === "X") { 27562 formatted += part.charAt(partIndex); 27563 partIndex++; 27564 } else { 27565 formatted += formatString.charAt(i); 27566 } 27567 } 27568 27569 if (mustUseAll && partIndex < part.length-1) { 27570 // didn't use the whole thing in this format? Hmm... go to last resort rule 27571 throw "too many digits in " + part + " for format " + formatString; 27572 } 27573 27574 return formatted; 27575 }, 27576 27577 /** 27578 * Returns the style with the given name, or the default style if there 27579 * is no style with that name. 27580 * @protected 27581 * @return {{example:string,whole:Object.<string,string>,partial:Object.<string,string>}|Object.<string,string>} 27582 */ 27583 _getStyle: function (name, fmtdata) { 27584 return fmtdata[name] || fmtdata["default"]; 27585 }, 27586 27587 /** 27588 * Do the actual work of formatting the phone number starting at the given 27589 * field in the regular field order. 27590 * 27591 * @param {!PhoneNumber} number 27592 * @param {{ 27593 * partial:boolean, 27594 * style:string, 27595 * mcc:string, 27596 * locale:(string|Locale), 27597 * sync:boolean, 27598 * loadParams:Object, 27599 * onLoad:function(string) 27600 * }} options Parameters which control how to format the number 27601 * @param {number} startField 27602 */ 27603 _doFormat: function(number, options, startField, locale, fmtdata, callback) { 27604 var sync = true, 27605 loadParams = {}, 27606 temp, 27607 templates, 27608 fieldName, 27609 countryCode, 27610 isWhole, 27611 style, 27612 formatted = "", 27613 styleTemplates, 27614 lastFieldName; 27615 27616 if (options) { 27617 if (typeof(options.sync) !== 'undefined') { 27618 sync = (options.sync == true); 27619 } 27620 27621 if (options.loadParams) { 27622 loadParams = options.loadParams; 27623 } 27624 } 27625 27626 style = this.style; // default style for this formatter 27627 27628 // figure out what style to use for this type of number 27629 if (number.countryCode) { 27630 // dialing from outside the country 27631 // check to see if it to a mobile number because they are often formatted differently 27632 style = (number.mobilePrefix) ? "internationalmobile" : "international"; 27633 } else if (number.mobilePrefix !== undefined) { 27634 style = "mobile"; 27635 } else if (number.serviceCode !== undefined && typeof(fmtdata["service"]) !== 'undefined') { 27636 // if there is a special format for service numbers, then use it 27637 style = "service"; 27638 } 27639 27640 isWhole = (!options || !options.partial); 27641 styleTemplates = this._getStyle(style, fmtdata); 27642 27643 // console.log("Style ends up being " + style + " and using subtype " + (isWhole ? "whole" : "partial")); 27644 styleTemplates = (isWhole ? styleTemplates.whole : styleTemplates.partial) || styleTemplates; 27645 27646 for (var i = startField; i < PhoneNumber._fieldOrder.length; i++) { 27647 fieldName = PhoneNumber._fieldOrder[i]; 27648 // console.info("format: formatting field " + fieldName + " value: " + number[fieldName]); 27649 if (number[fieldName] !== undefined) { 27650 if (styleTemplates[fieldName] !== undefined) { 27651 templates = styleTemplates[fieldName]; 27652 if (fieldName === "trunkAccess") { 27653 if (number.areaCode === undefined && number.serviceCode === undefined && number.mobilePrefix === undefined) { 27654 templates = "X"; 27655 } 27656 } 27657 if (lastFieldName && typeof(styleTemplates[lastFieldName].suffix) !== 'undefined') { 27658 if (fieldName !== "extension" && number[fieldName].search(/[xwtp,;]/i) <= -1) { 27659 formatted += styleTemplates[lastFieldName].suffix; 27660 } 27661 } 27662 lastFieldName = fieldName; 27663 27664 // console.info("format: formatting field " + fieldName + " with templates " + JSON.stringify(templates)); 27665 temp = this._substituteDigits(number[fieldName], templates, (fieldName === "subscriberNumber")); 27666 // console.info("format: formatted is: " + temp); 27667 formatted += temp; 27668 27669 if (fieldName === "countryCode") { 27670 // switch to the new country to format the rest of the number 27671 countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 27672 27673 new PhoneLocale({ 27674 locale: this.locale, 27675 sync: sync, 27676 loadParms: loadParams, 27677 countryCode: countryCode, 27678 onLoad: ilib.bind(this, function (locale) { 27679 Utils.loadData({ 27680 name: "phonefmt.json", 27681 object: PhoneFmt, 27682 locale: locale, 27683 sync: sync, 27684 loadParams: JSUtils.merge(loadParams, { 27685 returnOne: true 27686 }), 27687 callback: ilib.bind(this, function (fmtdata) { 27688 // console.info("format: switching to region " + locale.region + " and style " + style + " to format the rest of the number "); 27689 27690 var subfmt = ""; 27691 27692 this._doFormat(number, options, i+1, locale, fmtdata, function (subformat) { 27693 subfmt = subformat; 27694 if (typeof(callback) === 'function') { 27695 callback(formatted + subformat); 27696 } 27697 }); 27698 27699 formatted += subfmt; 27700 }) 27701 }); 27702 }) 27703 }); 27704 return formatted; 27705 } 27706 } else { 27707 //console.warn("PhoneFmt.format: cannot find format template for field " + fieldName + ", region " + locale.region + ", style " + style); 27708 // use default of "minimal formatting" so we don't miss parts because of bugs in the format templates 27709 formatted += number[fieldName]; 27710 } 27711 } 27712 } 27713 27714 if (typeof(callback) === 'function') { 27715 callback(formatted); 27716 } 27717 27718 return formatted; 27719 }, 27720 27721 /** 27722 * Format the parts of a phone number appropriately according to the settings in 27723 * this formatter instance. 27724 * 27725 * The options can contain zero or more of these properties: 27726 * 27727 * <ul> 27728 * <li><i>partial</i> boolean which tells whether or not this phone number 27729 * represents a partial number or not. The default is false, which means the number 27730 * represents a whole number. 27731 * <li><i>style</i> style to use to format the number, if different from the 27732 * default style or the style specified in the constructor 27733 * <li><i>locale</i> The locale with which to parse the number. This gives a clue as to which 27734 * numbering plan to use. 27735 * <li><i>mcc</i> The mobile carrier code (MCC) associated with the carrier that the phone is 27736 * currently connected to, if known. This also can give a clue as to which numbering plan to 27737 * use 27738 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 27739 * loaded. When the onLoad option is given, the DateFmt object will attempt to 27740 * load any missing locale data using the ilib loader callback. 27741 * When the constructor is done (even if the data is already preassembled), the 27742 * onLoad function is called with the current instance as a parameter, so this 27743 * callback can be used with preassembled or dynamic loading or a mix of the two. 27744 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 27745 * asynchronously. If this option is given as "false", then the "onLoad" 27746 * callback must be given, as the instance returned from this constructor will 27747 * not be usable for a while. 27748 * <li><i>loadParams</i> - an object containing parameters to pass to the 27749 * loader callback function when locale data is missing. The parameters are not 27750 * interpretted or modified in any way. They are simply passed along. The object 27751 * may contain any property/value pairs as long as the calling code is in 27752 * agreement with the loader callback function as to what those parameters mean. 27753 * </ul> 27754 * 27755 * The partial parameter specifies whether or not the phone number contains 27756 * a partial phone number or if it is a whole phone number. A partial 27757 * number is usually a number as the user is entering it with a dial pad. The 27758 * reason is that certain types of phone numbers should be formatted differently 27759 * depending on whether or not it represents a whole number. Specifically, SMS 27760 * short codes are formatted differently.<p> 27761 * 27762 * Example: a subscriber number of "48773" in the US would get formatted as: 27763 * 27764 * <ul> 27765 * <li>partial: 487-73 (perhaps the user is in the process of typing a whole phone 27766 * number such as 487-7379) 27767 * <li>whole: 48773 (this is the entire SMS short code) 27768 * </ul> 27769 * 27770 * Any place in the UI where the user types in phone numbers, such as the keypad in 27771 * the phone app, should pass in partial: true to this formatting routine. All other 27772 * places, such as the call log in the phone app, should pass in partial: false, or 27773 * leave the partial flag out of the parameters entirely. 27774 * 27775 * @param {!PhoneNumber} number object containing the phone number to format 27776 * @param {{ 27777 * partial:boolean, 27778 * style:string, 27779 * mcc:string, 27780 * locale:(string|Locale), 27781 * sync:boolean, 27782 * loadParams:Object, 27783 * onLoad:function(string) 27784 * }} options Parameters which control how to format the number 27785 * @return {string} Returns the formatted phone number as a string. 27786 */ 27787 format: function (number, options) { 27788 var formatted = "", 27789 callback; 27790 27791 callback = options && options.onLoad; 27792 27793 try { 27794 this._doFormat(number, options, 0, this.locale, this.fmtdata, function (fmt) { 27795 formatted = fmt; 27796 27797 if (typeof(callback) === 'function') { 27798 callback(fmt); 27799 } 27800 }); 27801 } catch (e) { 27802 if (typeof(e) === 'string') { 27803 // console.warn("caught exception: " + e + ". Using last resort rule."); 27804 // if there was some exception, use this last resort rule 27805 formatted = ""; 27806 for (var field in PhoneNumber._fieldOrder) { 27807 if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string' && number[PhoneNumber._fieldOrder[field]] !== undefined) { 27808 // just concatenate without any formatting 27809 formatted += number[PhoneNumber._fieldOrder[field]]; 27810 if (PhoneNumber._fieldOrder[field] === 'countryCode') { 27811 formatted += ' '; // fix for NOV-107894 27812 } 27813 } 27814 } 27815 } else { 27816 throw e; 27817 } 27818 27819 if (typeof(callback) === 'function') { 27820 callback(formatted); 27821 } 27822 } 27823 return formatted; 27824 }, 27825 27826 /** 27827 * Return an array of names of all available styles that can be used with the current 27828 * formatter. 27829 * @return {Array.<string>} an array of names of styles that are supported by this formatter 27830 */ 27831 getAvailableStyles: function () { 27832 var ret = [], 27833 style; 27834 27835 if (this.fmtdata) { 27836 for (style in this.fmtdata) { 27837 if (this.fmtdata[style].example) { 27838 ret.push(style); 27839 } 27840 } 27841 } 27842 return ret; 27843 }, 27844 27845 /** 27846 * Return an example phone number formatted with the given style. 27847 * 27848 * @param {string|undefined} style style to get an example of, or undefined to use 27849 * the current default style for this formatter 27850 * @return {string|undefined} an example phone number formatted according to the 27851 * given style, or undefined if the style is not recognized or does not have an 27852 * example 27853 */ 27854 getStyleExample: function (style) { 27855 return this.fmtdata[style].example || undefined; 27856 } 27857 }; 27858 27859 27860 /*< PhoneGeoLocator.js */ 27861 /* 27862 * phonegeo.js - Represent a phone number geolocator object. 27863 * 27864 * Copyright © 2014-2015, JEDLSoft 27865 * 27866 * Licensed under the Apache License, Version 2.0 (the "License"); 27867 * you may not use this file except in compliance with the License. 27868 * You may obtain a copy of the License at 27869 * 27870 * http://www.apache.org/licenses/LICENSE-2.0 27871 * 27872 * Unless required by applicable law or agreed to in writing, software 27873 * distributed under the License is distributed on an "AS IS" BASIS, 27874 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27875 * 27876 * See the License for the specific language governing permissions and 27877 * limitations under the License. 27878 */ 27879 27880 /* 27881 !depends 27882 ilib.js 27883 NumberingPlan.js 27884 PhoneLocale.js 27885 PhoneNumber.js 27886 Utils.js 27887 JSUtils.js 27888 ResBundle.js 27889 */ 27890 27891 // !data iddarea area extarea extstates phoneres 27892 27893 27894 27895 /** 27896 * @class 27897 * Create an instance that can geographically locate a phone number.<p> 27898 * 27899 * The location of the number is calculated according to the following rules: 27900 * 27901 * <ol> 27902 * <li>If the areaCode property is undefined or empty, or if the number specifies a 27903 * country code for which we do not have information, then the area property may be 27904 * missing from the returned object. In this case, only the country object will be returned. 27905 * 27906 * <li>If there is no area code, but there is a mobile prefix, service code, or emergency 27907 * code, then a fixed string indicating the type of number will be returned. 27908 * 27909 * <li>The country object is filled out according to the countryCode property of the phone 27910 * number. 27911 * 27912 * <li>If the phone number does not have an explicit country code, the MCC will be used if 27913 * it is available. The country code can be gleaned directly from the MCC. If the MCC 27914 * of the carrier to which the phone is currently connected is available, it should be 27915 * passed in so that local phone numbers will look correct. 27916 * 27917 * <li>If the country's dialling plan mandates a fixed length for phone numbers, and a 27918 * particular number exceeds that length, then the area code will not be given on the 27919 * assumption that the number has problems in the first place and we cannot guess 27920 * correctly. 27921 * </ol> 27922 * 27923 * The returned area property varies in specificity according 27924 * to the locale. In North America, the area is no finer than large parts of states 27925 * or provinces. In Germany and the UK, the area can be as fine as small towns.<p> 27926 * 27927 * If the number passed in is invalid, no geolocation will be performed. If the location 27928 * information about the country where the phone number is located is not available, 27929 * then the area information will be missing and only the country will be available.<p> 27930 * 27931 * The options parameter can contain any one of the following properties: 27932 * 27933 * <ul> 27934 * <li><i>locale</i> The locale parameter is used to load translations of the names of regions and 27935 * areas if available. For example, if the locale property is given as "en-US" (English for USA), 27936 * but the phone number being geolocated is in Germany, then this class would return the the names 27937 * of the country (Germany) and region inside of Germany in English instead of German. That is, a 27938 * phone number in Munich and return the country "Germany" and the area code "Munich" 27939 * instead of "Deutschland" and "München". The default display locale is the current ilib locale. 27940 * If translations are not available, the region and area names are given in English, which should 27941 * always be available. 27942 * <li><i>mcc</i> The mcc of the current mobile carrier, if known. 27943 * 27944 * <li><i>onLoad</i> - a callback function to call when the data for the 27945 * locale is fully loaded. When the onLoad option is given, this object 27946 * will attempt to load any missing locale data using the ilib loader callback. 27947 * When the constructor is done (even if the data is already preassembled), the 27948 * onLoad function is called with the current instance as a parameter, so this 27949 * callback can be used with preassembled or dynamic loading or a mix of the two. 27950 * 27951 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 27952 * asynchronously. If this option is given as "false", then the "onLoad" 27953 * callback must be given, as the instance returned from this constructor will 27954 * not be usable for a while. 27955 * 27956 * <li><i>loadParams</i> - an object containing parameters to pass to the 27957 * loader callback function when locale data is missing. The parameters are not 27958 * interpretted or modified in any way. They are simply passed along. The object 27959 * may contain any property/value pairs as long as the calling code is in 27960 * agreement with the loader callback function as to what those parameters mean. 27961 * </ul> 27962 * 27963 * @constructor 27964 * @param {Object} options parameters controlling the geolocation of the phone number. 27965 */ 27966 var PhoneGeoLocator = function(options) { 27967 var sync = true, 27968 loadParams = {}, 27969 locale = ilib.getLocale(); 27970 27971 if (options) { 27972 if (options.locale) { 27973 locale = options.locale; 27974 } 27975 27976 if (typeof(options.sync) === 'boolean') { 27977 sync = options.sync; 27978 } 27979 27980 if (options.loadParams) { 27981 loadParams = options.loadParams; 27982 } 27983 } 27984 27985 new PhoneLocale({ 27986 locale: locale, 27987 mcc: options && options.mcc, 27988 countryCode: options && options.countryCode, 27989 sync: sync, 27990 loadParams: loadParams, 27991 onLoad: ilib.bind(this, function (loc) { 27992 this.locale = loc; 27993 new NumberingPlan({ 27994 locale: this.locale, 27995 sync: sync, 27996 loadParams: loadParams, 27997 onLoad: ilib.bind(this, function (plan) { 27998 this.plan = plan; 27999 28000 new ResBundle({ 28001 locale: this.locale, 28002 name: "phoneres", 28003 sync: sync, 28004 loadParams: loadParams, 28005 onLoad: ilib.bind(this, function (rb) { 28006 this.rb = rb; 28007 28008 Utils.loadData({ 28009 name: "iddarea.json", 28010 object: PhoneGeoLocator, 28011 nonlocale: true, 28012 sync: sync, 28013 loadParams: loadParams, 28014 callback: ilib.bind(this, function (data) { 28015 this.regiondata = data; 28016 Utils.loadData({ 28017 name: "area.json", 28018 object: PhoneGeoLocator, 28019 locale: this.locale, 28020 sync: sync, 28021 loadParams: JSUtils.merge(loadParams, { 28022 returnOne: true 28023 }), 28024 callback: ilib.bind(this, function (areadata) { 28025 this.areadata = areadata; 28026 28027 if (options && typeof(options.onLoad) === 'function') { 28028 options.onLoad(this); 28029 } 28030 }) 28031 }); 28032 }) 28033 }); 28034 }) 28035 }); 28036 }) 28037 }); 28038 }) 28039 }); 28040 }; 28041 28042 PhoneGeoLocator.prototype = { 28043 /** 28044 * @private 28045 * 28046 * Used for locales where the area code is very general, and you need to add in 28047 * the initial digits of the subscriber number in order to get the area 28048 * 28049 * @param {string} number 28050 * @param {Object} stateTable 28051 */ 28052 _parseAreaAndSubscriber: function (number, stateTable) { 28053 var ch, 28054 i, 28055 handlerMethod, 28056 newState, 28057 prefix = "", 28058 consumed, 28059 lastLeaf, 28060 currentState, 28061 dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java 28062 28063 if (!number || !stateTable) { 28064 // can't parse anything 28065 return undefined; 28066 } 28067 28068 //console.log("GeoLocator._parseAreaAndSubscriber: parsing number " + number); 28069 28070 currentState = stateTable; 28071 i = 0; 28072 while (i < number.length) { 28073 ch = PhoneNumber._getCharacterCode(number.charAt(i)); 28074 if (ch >= 0) { 28075 // newState = stateData.states[state][ch]; 28076 newState = currentState.s && currentState.s[ch]; 28077 28078 if (!newState && currentState.s && currentState.s[dot]) { 28079 newState = currentState.s[dot]; 28080 } 28081 28082 if (typeof(newState) === 'object') { 28083 if (typeof(newState.l) !== 'undefined') { 28084 // save for latter if needed 28085 lastLeaf = newState; 28086 consumed = i; 28087 } 28088 // console.info("recognized digit " + ch + " continuing..."); 28089 // recognized digit, so continue parsing 28090 currentState = newState; 28091 i++; 28092 } else { 28093 if (typeof(newState) === 'undefined' || newState === 0) { 28094 // this is possibly a look-ahead and it didn't work... 28095 // so fall back to the last leaf and use that as the 28096 // final state 28097 newState = lastLeaf; 28098 i = consumed; 28099 } 28100 28101 if ((typeof(newState) === 'number' && newState) || 28102 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 28103 // final state 28104 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 28105 handlerMethod = PhoneNumber._states[stateNumber]; 28106 28107 //console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); 28108 28109 return (handlerMethod === "area") ? number.substring(0, i+1) : undefined; 28110 } else { 28111 // failed parse. Either no last leaf to fall back to, or there was an explicit 28112 // zero in the table 28113 break; 28114 } 28115 } 28116 } else if (ch === -1) { 28117 // non-transition character, continue parsing in the same state 28118 i++; 28119 } else { 28120 // should not happen 28121 // console.info("skipping character " + ch); 28122 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 28123 i++; 28124 } 28125 } 28126 return undefined; 28127 }, 28128 /** 28129 * @private 28130 * @param prefix 28131 * @param table 28132 * @returns 28133 */ 28134 _matchPrefix: function(prefix, table) { 28135 var i, matchedDot, matchesWithDots = []; 28136 28137 if (table[prefix]) { 28138 return table[prefix]; 28139 } 28140 for (var entry in table) { 28141 if (entry && typeof(entry) === 'string') { 28142 i = 0; 28143 matchedDot = false; 28144 while (i < entry.length && (entry.charAt(i) === prefix.charAt(i) || entry.charAt(i) === '.')) { 28145 if (entry.charAt(i) === '.') { 28146 matchedDot = true; 28147 } 28148 i++; 28149 } 28150 if (i >= entry.length) { 28151 if (matchedDot) { 28152 matchesWithDots.push(entry); 28153 } else { 28154 return table[entry]; 28155 } 28156 } 28157 } 28158 } 28159 28160 // match entries with dots last, so sort the matches so that the entry with the 28161 // most dots sorts last. The entry that ends up at the beginning of the list is 28162 // the best match because it has the fewest dots 28163 if (matchesWithDots.length > 0) { 28164 matchesWithDots.sort(function (left, right) { 28165 return (right < left) ? -1 : ((left < right) ? 1 : 0); 28166 }); 28167 return table[matchesWithDots[0]]; 28168 } 28169 28170 return undefined; 28171 }, 28172 /** 28173 * @private 28174 * @param number 28175 * @param data 28176 * @param locale 28177 * @param plan 28178 * @param options 28179 * @returns {Object} 28180 */ 28181 _getAreaInfo: function(number, data, locale, plan, options) { 28182 var sync = true, 28183 ret = {}, 28184 countryCode, 28185 areaInfo, 28186 temp, 28187 areaCode, 28188 geoTable, 28189 tempNumber, 28190 prefix; 28191 28192 if (options && typeof(options.sync) === 'boolean') { 28193 sync = options.sync; 28194 } 28195 28196 prefix = number.areaCode || number.serviceCode; 28197 geoTable = data; 28198 28199 if (prefix !== undefined) { 28200 if (plan.getExtendedAreaCode()) { 28201 // for countries where the area code is very general and large, and you need a few initial 28202 // digits of the subscriber number in order find the actual area 28203 tempNumber = prefix + number.subscriberNumber; 28204 tempNumber = tempNumber.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 28205 28206 Utils.loadData({ 28207 name: "extarea.json", 28208 object: PhoneGeoLocator, 28209 locale: locale, 28210 sync: sync, 28211 loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), 28212 callback: ilib.bind(this, function (data) { 28213 this.extarea = data; 28214 Utils.loadData({ 28215 name: "extstates.json", 28216 object: PhoneGeoLocator, 28217 locale: locale, 28218 sync: sync, 28219 loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), 28220 callback: ilib.bind(this, function (data) { 28221 this.extstates = data; 28222 geoTable = this.extarea; 28223 if (this.extarea && this.extstates) { 28224 prefix = this._parseAreaAndSubscriber(tempNumber, this.extstates); 28225 } 28226 28227 if (!prefix) { 28228 // not a recognized prefix, so now try the general table 28229 geoTable = this.areadata; 28230 prefix = number.areaCode || number.serviceCode; 28231 } 28232 28233 if ((!plan.fieldLengths || 28234 plan.getFieldLength('maxLocalLength') === undefined || 28235 !number.subscriberNumber || 28236 number.subscriberNumber.length <= plan.fieldLengths('maxLocalLength'))) { 28237 areaInfo = this._matchPrefix(prefix, geoTable); 28238 if (areaInfo && areaInfo.sn && areaInfo.ln) { 28239 //console.log("Found areaInfo " + JSON.stringify(areaInfo)); 28240 ret.area = { 28241 sn: this.rb.getString(areaInfo.sn).toString(), 28242 ln: this.rb.getString(areaInfo.ln).toString() 28243 }; 28244 } 28245 } 28246 }) 28247 }); 28248 }) 28249 }); 28250 28251 } else if (!plan || 28252 plan.getFieldLength('maxLocalLength') === undefined || 28253 !number.subscriberNumber || 28254 number.subscriberNumber.length <= plan.getFieldLength('maxLocalLength')) { 28255 if (geoTable) { 28256 areaCode = prefix.replace(/[wWpPtT\+#\*]/g, ''); 28257 areaInfo = this._matchPrefix(areaCode, geoTable); 28258 28259 if (areaInfo && areaInfo.sn && areaInfo.ln) { 28260 ret.area = { 28261 sn: this.rb.getString(areaInfo.sn).toString(), 28262 ln: this.rb.getString(areaInfo.ln).toString() 28263 }; 28264 } else if (number.serviceCode) { 28265 ret.area = { 28266 sn: this.rb.getString("Service Number").toString(), 28267 ln: this.rb.getString("Service Number").toString() 28268 }; 28269 } 28270 } else { 28271 countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); 28272 if (countryCode !== "0" && this.regiondata) { 28273 temp = this.regiondata[countryCode]; 28274 if (temp && temp.sn) { 28275 ret.country = { 28276 sn: this.rb.getString(temp.sn).toString(), 28277 ln: this.rb.getString(temp.ln).toString(), 28278 code: this.locale.getRegion() 28279 }; 28280 } 28281 } 28282 } 28283 } else { 28284 countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); 28285 if (countryCode !== "0" && this.regiondata) { 28286 temp = this.regiondata[countryCode]; 28287 if (temp && temp.sn) { 28288 ret.country = { 28289 sn: this.rb.getString(temp.sn).toString(), 28290 ln: this.rb.getString(temp.ln).toString(), 28291 code: this.locale.getRegion() 28292 }; 28293 } 28294 } 28295 } 28296 28297 } else if (number.mobilePrefix) { 28298 ret.area = { 28299 sn: this.rb.getString("Mobile Number").toString(), 28300 ln: this.rb.getString("Mobile Number").toString() 28301 }; 28302 } else if (number.emergency) { 28303 ret.area = { 28304 sn: this.rb.getString("Emergency Services Number").toString(), 28305 ln: this.rb.getString("Emergency Services Number").toString() 28306 }; 28307 } 28308 28309 return ret; 28310 }, 28311 /** 28312 * Returns a the location of the given phone number, if known. 28313 * The returned object has 2 properties, each of which has an sn (short name) 28314 * and an ln (long name) string. Additionally, the country code, if given, 28315 * includes the 2 letter ISO code for the recognized country. 28316 * { 28317 * "country": { 28318 * "sn": "North America", 28319 * "ln": "North America and the Caribbean Islands", 28320 * "code": "us" 28321 * }, 28322 * "area": { 28323 * "sn": "California", 28324 * "ln": "Central California: San Jose, Los Gatos, Milpitas, Sunnyvale, Cupertino, Gilroy" 28325 * } 28326 * } 28327 * 28328 * The location name is subject to the following rules: 28329 * 28330 * If the areaCode property is undefined or empty, or if the number specifies a 28331 * country code for which we do not have information, then the area property may be 28332 * missing from the returned object. In this case, only the country object will be returned. 28333 * 28334 * If there is no area code, but there is a mobile prefix, service code, or emergency 28335 * code, then a fixed string indicating the type of number will be returned. 28336 * 28337 * The country object is filled out according to the countryCode property of the phone 28338 * number. 28339 * 28340 * If the phone number does not have an explicit country code, the MCC will be used if 28341 * it is available. The country code can be gleaned directly from the MCC. If the MCC 28342 * of the carrier to which the phone is currently connected is available, it should be 28343 * passed in so that local phone numbers will look correct. 28344 * 28345 * If the country's dialling plan mandates a fixed length for phone numbers, and a 28346 * particular number exceeds that length, then the area code will not be given on the 28347 * assumption that the number has problems in the first place and we cannot guess 28348 * correctly. 28349 * 28350 * The returned area property varies in specificity according 28351 * to the locale. In North America, the area is no finer than large parts of states 28352 * or provinces. In Germany and the UK, the area can be as fine as small towns. 28353 * 28354 * The strings returned from this function are already localized to the 28355 * given locale, and thus are ready for display to the user. 28356 * 28357 * If the number passed in is invalid, an empty object is returned. If the location 28358 * information about the country where the phone number is located is not available, 28359 * then the area information will be missing and only the country will be returned. 28360 * 28361 * The options parameter can contain any one of the following properties: 28362 * 28363 * <ul> 28364 * <li><i>locale</i> The locale parameter is used to load translations of the names of regions and 28365 * areas if available. For example, if the locale property is given as "en-US" (English for USA), 28366 * but the phone number being geolocated is in Germany, then this class would return the the names 28367 * of the country (Germany) and region inside of Germany in English instead of German. That is, a 28368 * phone number in Munich and return the country "Germany" and the area code "Munich" 28369 * instead of "Deutschland" and "München". The default display locale is the current ilib locale. 28370 * If translations are not available, the region and area names are given in English, which should 28371 * always be available. 28372 * <li><i>mcc</i> The mcc of the current mobile carrier, if known. 28373 * 28374 * <li><i>onLoad</i> - a callback function to call when the data for the 28375 * locale is fully loaded. When the onLoad option is given, this object 28376 * will attempt to load any missing locale data using the ilib loader callback. 28377 * When the constructor is done (even if the data is already preassembled), the 28378 * onLoad function is called with the current instance as a parameter, so this 28379 * callback can be used with preassembled or dynamic loading or a mix of the two. 28380 * 28381 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 28382 * asynchronously. If this option is given as "false", then the "onLoad" 28383 * callback must be given, as the instance returned from this constructor will 28384 * not be usable for a while. 28385 * 28386 * <li><i>loadParams</i> - an object containing parameters to pass to the 28387 * loader callback function when locale data is missing. The parameters are not 28388 * interpretted or modified in any way. They are simply passed along. The object 28389 * may contain any property/value pairs as long as the calling code is in 28390 * agreement with the loader callback function as to what those parameters mean. 28391 * </ul> 28392 * 28393 * @param {PhoneNumber} number phone number to locate 28394 * @param {Object} options options governing the way this ares is loaded 28395 * @return {Object} an object 28396 * that describes the country and the area in that country corresponding to this 28397 * phone number. Each of the country and area contain a short name (sn) and long 28398 * name (ln) that describes the location. 28399 */ 28400 locate: function(number, options) { 28401 var loadParams = {}, 28402 ret = {}, 28403 region, 28404 countryCode, 28405 temp, 28406 plan, 28407 areaResult, 28408 phoneLoc = this.locale, 28409 sync = true; 28410 28411 if (number === undefined || typeof(number) !== 'object' || !(number instanceof PhoneNumber)) { 28412 return ret; 28413 } 28414 28415 if (options) { 28416 if (typeof(options.sync) !== 'undefined') { 28417 sync = (options.sync == true); 28418 } 28419 28420 if (options.loadParams) { 28421 loadParams = options.loadParams; 28422 } 28423 } 28424 28425 // console.log("GeoLocator.locate: looking for geo for number " + JSON.stringify(number)); 28426 region = this.locale.getRegion(); 28427 if (number.countryCode !== undefined && this.regiondata) { 28428 countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); 28429 temp = this.regiondata[countryCode]; 28430 phoneLoc = number.destinationLocale; 28431 plan = number.destinationPlan; 28432 ret.country = { 28433 sn: this.rb.getString(temp.sn).toString(), 28434 ln: this.rb.getString(temp.ln).toString(), 28435 code: phoneLoc.getRegion() 28436 }; 28437 } 28438 28439 if (!plan) { 28440 plan = this.plan; 28441 } 28442 28443 Utils.loadData({ 28444 name: "area.json", 28445 object: PhoneGeoLocator, 28446 locale: phoneLoc, 28447 sync: sync, 28448 loadParams: JSUtils.merge(loadParams, { 28449 returnOne: true 28450 }), 28451 callback: ilib.bind(this, function (areadata) { 28452 if (areadata) { 28453 this.areadata = areadata; 28454 } 28455 areaResult = this._getAreaInfo(number, this.areadata, phoneLoc, plan, options); 28456 ret = JSUtils.merge(ret, areaResult); 28457 28458 if (ret.country === undefined) { 28459 countryCode = number.locale._mapRegiontoCC(region); 28460 28461 if (countryCode !== "0" && this.regiondata) { 28462 temp = this.regiondata[countryCode]; 28463 if (temp && temp.sn) { 28464 ret.country = { 28465 sn: this.rb.getString(temp.sn).toString(), 28466 ln: this.rb.getString(temp.ln).toString(), 28467 code: this.locale.getRegion() 28468 }; 28469 } 28470 } 28471 } 28472 }) 28473 }); 28474 28475 return ret; 28476 }, 28477 28478 /** 28479 * Returns a string that describes the ISO-3166-2 country code of the given phone 28480 * number.<p> 28481 * 28482 * If the phone number is a local phone number and does not contain 28483 * any country information, this routine will return the region for the current 28484 * formatter instance. 28485 * 28486 * @param {PhoneNumber} number An PhoneNumber instance 28487 * @return {string} 28488 */ 28489 country: function(number) { 28490 var countryCode, 28491 region, 28492 phoneLoc; 28493 28494 if (!number || !(number instanceof PhoneNumber)) { 28495 return ""; 28496 } 28497 28498 phoneLoc = number.locale; 28499 28500 region = (number.countryCode && phoneLoc._mapCCtoRegion(number.countryCode)) || 28501 (number.locale && number.locale.region) || 28502 phoneLoc.locale.getRegion() || 28503 this.locale.getRegion(); 28504 28505 countryCode = number.countryCode || phoneLoc._mapRegiontoCC(region); 28506 28507 if (number.areaCode) { 28508 region = phoneLoc._mapAreatoRegion(countryCode, number.areaCode); 28509 } else if (countryCode === "33" && number.serviceCode) { 28510 // french departments are in the service code, not the area code 28511 region = phoneLoc._mapAreatoRegion(countryCode, number.serviceCode); 28512 } 28513 return region; 28514 } 28515 }; 28516 28517 28518 /*< Measurement.js */ 28519 /* 28520 * Measurement.js - Measurement unit superclass 28521 * 28522 * Copyright © 2014-2015, JEDLSoft 28523 * 28524 * Licensed under the Apache License, Version 2.0 (the "License"); 28525 * you may not use this file except in compliance with the License. 28526 * You may obtain a copy of the License at 28527 * 28528 * http://www.apache.org/licenses/LICENSE-2.0 28529 * 28530 * Unless required by applicable law or agreed to in writing, software 28531 * distributed under the License is distributed on an "AS IS" BASIS, 28532 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28533 * 28534 * See the License for the specific language governing permissions and 28535 * limitations under the License. 28536 */ 28537 28538 /** 28539 * @class 28540 * Superclass for measurement instances that contains shared functionality 28541 * and defines the interface. <p> 28542 * 28543 * This class is never instantiated on its own. Instead, measurements should 28544 * be created using the {@link MeasurementFactory} function, which creates the 28545 * correct subclass based on the given parameters.<p> 28546 * 28547 * @private 28548 * @constructor 28549 */ 28550 var Measurement = function() { 28551 }; 28552 28553 /** 28554 * @private 28555 */ 28556 Measurement._constructors = {}; 28557 28558 Measurement.prototype = { 28559 /** 28560 * Return the normalized name of the given units. If the units are 28561 * not recognized, this method returns its parameter unmodified.<p> 28562 * 28563 * Examples: 28564 * 28565 * <ui> 28566 * <li>"metres" gets normalized to "meter"<br> 28567 * <li>"ml" gets normalized to "milliliter"<br> 28568 * <li>"foobar" gets normalized to "foobar" (no change because it is not recognized) 28569 * </ul> 28570 * 28571 * @param {string} name name of the units to normalize. 28572 * @returns {string} normalized name of the units 28573 */ 28574 normalizeUnits: function(name) { 28575 return this.aliases[name] || name; 28576 }, 28577 28578 /** 28579 * Return the normalized units used in this measurement. 28580 * @return {string} name of the unit of measurement 28581 */ 28582 getUnit: function() { 28583 return this.unit; 28584 }, 28585 28586 /** 28587 * Return the units originally used to construct this measurement 28588 * before it was normalized. 28589 * @return {string} name of the unit of measurement 28590 */ 28591 getOriginalUnit: function() { 28592 return this.originalUnit; 28593 }, 28594 28595 /** 28596 * Return the numeric amount of this measurement. 28597 * @return {number} the numeric amount of this measurement 28598 */ 28599 getAmount: function() { 28600 return this.amount; 28601 }, 28602 28603 /** 28604 * Return the type of this measurement. Examples are "mass", 28605 * "length", "speed", etc. Measurements can only be converted 28606 * to measurements of the same type.<p> 28607 * 28608 * The type of the units is determined automatically from the 28609 * units. For example, the unit "grams" is type "mass". Use the 28610 * static call {@link Measurement.getAvailableUnits} 28611 * to find out what units this version of ilib supports. 28612 * 28613 * @return {string} the name of the type of this measurement 28614 */ 28615 getMeasure: function() {}, 28616 28617 /** 28618 * Return a new measurement instance that is converted to a new 28619 * measurement unit. Measurements can only be converted 28620 * to measurements of the same type.<p> 28621 * 28622 * @param {string} to The name of the units to convert to 28623 * @return {Measurement|undefined} the converted measurement 28624 * or undefined if the requested units are for a different 28625 * measurement type 28626 */ 28627 convert: function(to) {}, 28628 28629 /** 28630 * Scale the measurement unit to an acceptable level. The scaling 28631 * happens so that the integer part of the amount is as small as 28632 * possible without being below zero. This will result in the 28633 * largest units that can represent this measurement without 28634 * fractions. Measurements can only be scaled to other measurements 28635 * of the same type. 28636 * 28637 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 28638 * or undefined if the system can be inferred from the current measure 28639 * @return {Measurement} a new instance that is scaled to the 28640 * right level 28641 */ 28642 scale: function(measurementsystem) {}, 28643 28644 /** 28645 * Localize the measurement to the commonly used measurement in that locale, for example 28646 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 28647 * the formatted number should be automatically converted to the most appropriate 28648 * measure in the other system, in this case, mph. The formatted result should 28649 * appear as "37.3 mph". 28650 * 28651 * @param {string} locale current locale string 28652 * @returns {Measurement} a new instance that is converted to locale 28653 */ 28654 localize: function(locale) {} 28655 }; 28656 28657 28658 28659 /*< UnknownUnit.js */ 28660 /* 28661 * Unknown.js - Dummy unit conversions for unknown types 28662 * 28663 * Copyright © 2014-2015, JEDLSoft 28664 * 28665 * Licensed under the Apache License, Version 2.0 (the "License"); 28666 * you may not use this file except in compliance with the License. 28667 * You may obtain a copy of the License at 28668 * 28669 * http://www.apache.org/licenses/LICENSE-2.0 28670 * 28671 * Unless required by applicable law or agreed to in writing, software 28672 * distributed under the License is distributed on an "AS IS" BASIS, 28673 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28674 * 28675 * See the License for the specific language governing permissions and 28676 * limitations under the License. 28677 */ 28678 28679 // !depends Measurement.js 28680 28681 28682 /** 28683 * @class 28684 * Create a new unknown measurement instance. 28685 * 28686 * @constructor 28687 * @extends Measurement 28688 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 28689 * the construction of this instance 28690 */ 28691 var UnknownUnit = function (options) { 28692 if (options) { 28693 this.unit = options.unit; 28694 this.amount = options.amount; 28695 } 28696 }; 28697 28698 UnknownUnit.prototype = new Measurement(); 28699 UnknownUnit.prototype.parent = Measurement; 28700 UnknownUnit.prototype.constructor = UnknownUnit; 28701 28702 UnknownUnit.aliases = { 28703 "unknown":"unknown" 28704 }; 28705 28706 /** 28707 * Return the type of this measurement. Examples are "mass", 28708 * "length", "speed", etc. Measurements can only be converted 28709 * to measurements of the same type.<p> 28710 * 28711 * The type of the units is determined automatically from the 28712 * units. For example, the unit "grams" is type "mass". Use the 28713 * static call {@link Measurement.getAvailableUnits} 28714 * to find out what units this version of ilib supports. 28715 * 28716 * @return {string} the name of the type of this measurement 28717 */ 28718 UnknownUnit.prototype.getMeasure = function() { 28719 return "unknown"; 28720 }; 28721 28722 /** 28723 * Return a new measurement instance that is converted to a new 28724 * measurement unit. Measurements can only be converted 28725 * to measurements of the same type.<p> 28726 * 28727 * @param {string} to The name of the units to convert to 28728 * @return {Measurement|undefined} the converted measurement 28729 * or undefined if the requested units are for a different 28730 * measurement type 28731 */ 28732 UnknownUnit.prototype.convert = function(to) { 28733 return undefined; 28734 }; 28735 28736 /** 28737 * Convert a unknown to another measure. 28738 * @static 28739 * @param {string} to unit to convert to 28740 * @param {string} from unit to convert from 28741 * @param {number} unknown amount to be convert 28742 * @returns {number|undefined} the converted amount 28743 */ 28744 UnknownUnit.convert = function(to, from, unknown) { 28745 return undefined; 28746 }; 28747 28748 /** 28749 * Localize the measurement to the commonly used measurement in that locale. For example 28750 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 28751 * the formatted number should be automatically converted to the most appropriate 28752 * measure in the other system, in this case, mph. The formatted result should 28753 * appear as "37.3 mph". 28754 * 28755 * @param {string} locale current locale string 28756 * @returns {Measurement} a new instance that is converted to locale 28757 */ 28758 UnknownUnit.prototype.localize = function(locale) { 28759 return new UnknownUnit({ 28760 unit: this.unit, 28761 amount: this.amount 28762 }); 28763 }; 28764 28765 /** 28766 * Scale the measurement unit to an acceptable level. The scaling 28767 * happens so that the integer part of the amount is as small as 28768 * possible without being below zero. This will result in the 28769 * largest units that can represent this measurement without 28770 * fractions. Measurements can only be scaled to other measurements 28771 * of the same type. 28772 * 28773 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 28774 * or undefined if the system can be inferred from the current measure 28775 * @return {Measurement} a new instance that is scaled to the 28776 * right level 28777 */ 28778 UnknownUnit.prototype.scale = function(measurementsystem) { 28779 return new UnknownUnit({ 28780 unit: this.unit, 28781 amount: this.amount 28782 }); 28783 }; 28784 28785 /** 28786 * @private 28787 * @static 28788 */ 28789 UnknownUnit.getMeasures = function () { 28790 return []; 28791 }; 28792 28793 28794 /*< AreaUnit.js */ 28795 /* 28796 * area.js - Unit conversions for Area 28797 * 28798 * Copyright © 2014-2015, JEDLSoft 28799 * 28800 * Licensed under the Apache License, Version 2.0 (the "License"); 28801 * you may not use this file except in compliance with the License. 28802 * You may obtain a copy of the License at 28803 * 28804 * http://www.apache.org/licenses/LICENSE-2.0 28805 * 28806 * Unless required by applicable law or agreed to in writing, software 28807 * distributed under the License is distributed on an "AS IS" BASIS, 28808 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28809 * 28810 * See the License for the specific language governing permissions and 28811 * limitations under the License. 28812 */ 28813 28814 /* 28815 !depends 28816 Measurement.js 28817 */ 28818 28819 28820 /** 28821 * @class 28822 * Create a new area measurement instance. 28823 * @constructor 28824 * @extends Measurement 28825 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 28826 * the construction of this instance 28827 */ 28828 var AreaUnit = function (options) { 28829 this.unit = "square meter"; 28830 this.amount = 0; 28831 this.aliases = AreaUnit.aliases; // share this table in all instances 28832 28833 if (options) { 28834 if (typeof(options.unit) !== 'undefined') { 28835 this.originalUnit = options.unit; 28836 this.unit = this.aliases[options.unit] || options.unit; 28837 } 28838 28839 if (typeof(options.amount) === 'object') { 28840 if (options.amount.getMeasure() === "area") { 28841 this.amount = AreaUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 28842 } else { 28843 throw "Cannot convert unit " + options.amount.unit + " to area"; 28844 } 28845 } else if (typeof(options.amount) !== 'undefined') { 28846 this.amount = parseFloat(options.amount); 28847 } 28848 } 28849 28850 if (typeof(AreaUnit.ratios[this.unit]) === 'undefined') { 28851 throw "Unknown unit: " + options.unit; 28852 } 28853 }; 28854 28855 AreaUnit.prototype = new Measurement(); 28856 AreaUnit.prototype.parent = Measurement; 28857 AreaUnit.prototype.constructor = AreaUnit; 28858 28859 AreaUnit.ratios = { 28860 /* index square cm, square meter, hectare, square km, , square inch square foot, square yard, acre, square mile */ 28861 "square centimeter":[1, 1, 0.0001, 1e-8, 1e-10, 0.15500031, 0.00107639104, 0.000119599005, 2.47105381e-8, 3.86102159e-11 ], 28862 "square meter": [2, 10000, 1, 1e-4, 1e-6, 1550, 10.7639, 1.19599, 0.000247105, 3.861e-7 ], 28863 "hectare": [3, 100000000, 10000, 1, 0.01, 1.55e+7, 107639, 11959.9, 2.47105 , 0.00386102 ], 28864 "square km": [4, 10000000000, 1e+6, 100, 1, 1.55e+9, 1.076e+7, 1.196e+6, 247.105 , 0.386102 ], 28865 "square inch": [5, 6.4516, 0.00064516, 6.4516e-8, 6.4516e-10, 1, 0.000771605, 0.0007716051, 1.5942e-7, 2.491e-10 ], 28866 "square foot": [6, 929.0304, 0.092903, 9.2903e-6, 9.2903e-8, 144, 1, 0.111111, 2.2957e-5, 3.587e-8 ], 28867 "square yard": [7, 8361.2736, 0.836127, 8.3613e-5, 8.3613e-7, 1296, 9, 1, 0.000206612, 3.2283e-7 ], 28868 "acre": [8, 40468564.2, 4046.86, 0.404686, 0.00404686, 6.273e+6, 43560, 4840, 1, 0.0015625 ], 28869 "square mile": [9, 2.58998811e+10, 2.59e+6, 258.999, 2.58999, 4.014e+9, 2.788e+7, 3.098e+6, 640, 1 ] 28870 } 28871 28872 /** 28873 * Return the type of this measurement. Examples are "mass", 28874 * "length", "speed", etc. Measurements can only be converted 28875 * to measurements of the same type.<p> 28876 * 28877 * The type of the units is determined automatically from the 28878 * units. For example, the unit "grams" is type "mass". Use the 28879 * static call {@link Measurement.getAvailableUnits} 28880 * to find out what units this version of ilib supports. 28881 * 28882 * @return {string} the name of the type of this measurement 28883 */ 28884 AreaUnit.prototype.getMeasure = function() { 28885 return "area"; 28886 }; 28887 28888 /** 28889 * Return a new measurement instance that is converted to a new 28890 * measurement unit. Measurements can only be converted 28891 * to measurements of the same type.<p> 28892 * 28893 * @param {string} to The name of the units to convert to 28894 * @return {Measurement|undefined} the converted measurement 28895 * or undefined if the requested units are for a different 28896 * measurement type 28897 * 28898 */ 28899 AreaUnit.prototype.convert = function(to) { 28900 if (!to || typeof(AreaUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 28901 return undefined; 28902 } 28903 return new AreaUnit({ 28904 unit: to, 28905 amount: this 28906 }); 28907 }; 28908 28909 AreaUnit.aliases = { 28910 "square centimeter":"square centimeter", 28911 "square cm":"square centimeter", 28912 "sq cm":"square centimeter", 28913 "Square Cm":"square centimeter", 28914 "square Centimeters":"square centimeter", 28915 "square Centimeter":"square centimeter", 28916 "square Centimetre":"square centimeter", 28917 "square Centimetres":"square centimeter", 28918 "square centimeters":"square centimeter", 28919 "Square km": "square km", 28920 "Square kilometre":"square km", 28921 "square kilometer":"square km", 28922 "square kilometre":"square km", 28923 "square kilometers":"square km", 28924 "square kilometres":"square km", 28925 "square km":"square km", 28926 "sq km":"square km", 28927 "km2":"square km", 28928 "Hectare":"hectare", 28929 "hectare":"hectare", 28930 "ha":"hectare", 28931 "Square meter": "square meter", 28932 "Square meters":"square meter", 28933 "square meter": "square meter", 28934 "square meters":"square meter", 28935 "Square metre": "square meter", 28936 "Square metres":"square meter", 28937 "square metres": "square meter", 28938 "Square Metres":"square meter", 28939 "sqm":"square meter", 28940 "m2": "square meter", 28941 "Square mile":"square mile", 28942 "Square miles":"square mile", 28943 "square mile":"square mile", 28944 "square miles":"square mile", 28945 "square mi":"square mile", 28946 "Square mi":"square mile", 28947 "sq mi":"square mile", 28948 "mi2":"square mile", 28949 "Acre": "acre", 28950 "acre": "acre", 28951 "Acres":"acre", 28952 "acres":"acre", 28953 "Square yard": "square yard", 28954 "Square yards":"square yard", 28955 "square yard": "square yard", 28956 "square yards":"square yard", 28957 "yd2":"square yard", 28958 "Square foot": "square foot", 28959 "square foot": "square foot", 28960 "Square feet": "square foot", 28961 "Square Feet": "square foot", 28962 "sq ft":"square foot", 28963 "ft2":"square foot", 28964 "Square inch":"square inch", 28965 "square inch":"square inch", 28966 "Square inches":"square inch", 28967 "square inches":"square inch", 28968 "in2":"square inch" 28969 }; 28970 28971 /** 28972 * Convert a Area to another measure. 28973 * @static 28974 * @param to {string} unit to convert to 28975 * @param from {string} unit to convert from 28976 * @param area {number} amount to be convert 28977 * @returns {number|undefined} the converted amount 28978 */ 28979 AreaUnit.convert = function(to, from, area) { 28980 from = AreaUnit.aliases[from] || from; 28981 to = AreaUnit.aliases[to] || to; 28982 var fromRow = AreaUnit.ratios[from]; 28983 var toRow = AreaUnit.ratios[to]; 28984 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 28985 return undefined; 28986 } 28987 return area* fromRow[toRow[0]]; 28988 }; 28989 28990 /** 28991 * @private 28992 * @static 28993 */ 28994 AreaUnit.getMeasures = function () { 28995 var ret = []; 28996 for (var m in AreaUnit.ratios) { 28997 ret.push(m); 28998 } 28999 return ret; 29000 }; 29001 29002 AreaUnit.metricSystem = { 29003 "square centimeter" : 1, 29004 "square meter" : 2, 29005 "hectare" : 3, 29006 "square km" : 4 29007 }; 29008 AreaUnit.imperialSystem = { 29009 "square inch" : 5, 29010 "square foot" : 6, 29011 "square yard" : 7, 29012 "acre" : 8, 29013 "square mile" : 9 29014 }; 29015 AreaUnit.uscustomarySystem = { 29016 "square inch" : 5, 29017 "square foot" : 6, 29018 "square yard" : 7, 29019 "acre" : 8, 29020 "square mile" : 9 29021 }; 29022 29023 AreaUnit.metricToUScustomary = { 29024 "square centimeter" : "square inch", 29025 "square meter" : "square yard", 29026 "hectare" : "acre", 29027 "square km" : "square mile" 29028 }; 29029 AreaUnit.usCustomaryToMetric = { 29030 "square inch" : "square centimeter", 29031 "square foot" : "square meter", 29032 "square yard" : "square meter", 29033 "acre" : "hectare", 29034 "square mile" : "square km" 29035 }; 29036 29037 29038 /** 29039 * Scale the measurement unit to an acceptable level. The scaling 29040 * happens so that the integer part of the amount is as small as 29041 * possible without being below zero. This will result in the 29042 * largest units that can represent this measurement without 29043 * fractions. Measurements can only be scaled to other measurements 29044 * of the same type. 29045 * 29046 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 29047 * or undefined if the system can be inferred from the current measure 29048 * @return {Measurement} a new instance that is scaled to the 29049 * right level 29050 */ 29051 AreaUnit.prototype.scale = function(measurementsystem) { 29052 var fromRow = AreaUnit.ratios[this.unit]; 29053 var mSystem; 29054 29055 if (measurementsystem === "metric" || (typeof(measurementsystem) === 'undefined' 29056 && typeof(AreaUnit.metricSystem[this.unit]) !== 'undefined')) { 29057 mSystem = AreaUnit.metricSystem; 29058 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 29059 && typeof(AreaUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 29060 mSystem = AreaUnit.uscustomarySystem; 29061 } else if (measurementsystem === "imperial" || (typeof(measurementsystem) === 'undefined' 29062 && typeof(AreaUnit.imperialSystem[this.unit]) !== 'undefined')) { 29063 mSystem = AreaUnit.imperialSystem; 29064 } 29065 29066 var area = this.amount; 29067 var munit = this.unit; 29068 29069 area = 18446744073709551999; 29070 29071 for (var m in mSystem) { 29072 var tmp = this.amount * fromRow[mSystem[m]]; 29073 if (tmp >= 1 && tmp < area) { 29074 area = tmp; 29075 munit = m; 29076 } 29077 } 29078 29079 return new AreaUnit({ 29080 unit: munit, 29081 amount: area 29082 }); 29083 }; 29084 29085 /** 29086 * Localize the measurement to the commonly used measurement in that locale. For example 29087 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29088 * the formatted number should be automatically converted to the most appropriate 29089 * measure in the other system, in this case, mph. The formatted result should 29090 * appear as "37.3 mph". 29091 * 29092 * @param {string} locale current locale string 29093 * @returns {Measurement} a new instance that is converted to locale 29094 */ 29095 AreaUnit.prototype.localize = function(locale) { 29096 var to; 29097 if (locale === "en-US" || locale === "en-GB") { 29098 to = AreaUnit.metricToUScustomary[this.unit] || this.unit; 29099 } else { 29100 to = AreaUnit.usCustomaryToMetric[this.unit] || this.unit; 29101 } 29102 return new AreaUnit({ 29103 unit: to, 29104 amount: this 29105 }); 29106 }; 29107 29108 29109 //register with the factory method 29110 Measurement._constructors["area"] = AreaUnit; 29111 29112 29113 /*< DigitalStorageUnit.js */ 29114 /* 29115 * digitalStorage.js - Unit conversions for Digital Storage 29116 * 29117 * Copyright © 2014-2015, JEDLSoft 29118 * 29119 * Licensed under the Apache License, Version 2.0 (the "License"); 29120 * you may not use this file except in compliance with the License. 29121 * You may obtain a copy of the License at 29122 * 29123 * http://www.apache.org/licenses/LICENSE-2.0 29124 * 29125 * Unless required by applicable law or agreed to in writing, software 29126 * distributed under the License is distributed on an "AS IS" BASIS, 29127 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29128 * 29129 * See the License for the specific language governing permissions and 29130 * limitations under the License. 29131 */ 29132 29133 /* 29134 !depends 29135 Measurement.js 29136 */ 29137 29138 29139 /** 29140 * @class 29141 * Create a new DigitalStorage measurement instance. 29142 * 29143 * @constructor 29144 * @extends Measurement 29145 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 29146 * the construction of this instance 29147 */ 29148 var DigitalStorageUnit = function (options) { 29149 this.unit = "byte"; 29150 this.amount = 0; 29151 this.aliases = DigitalStorageUnit.aliases; // share this table in all instances 29152 29153 if (options) { 29154 if (typeof(options.unit) !== 'undefined') { 29155 this.originalUnit = options.unit; 29156 this.unit = this.aliases[options.unit] || options.unit; 29157 } 29158 29159 if (typeof(options.amount) === 'object') { 29160 if (options.amount.getMeasure() === "digitalStorage") { 29161 this.amount = DigitalStorageUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 29162 } else { 29163 throw "Cannot convert unit " + options.amount.unit + " to a digitalStorage"; 29164 } 29165 } else if (typeof(options.amount) !== 'undefined') { 29166 this.amount = parseFloat(options.amount); 29167 } 29168 } 29169 29170 if (typeof(DigitalStorageUnit.ratios[this.unit]) === 'undefined') { 29171 throw "Unknown unit: " + options.unit; 29172 } 29173 }; 29174 29175 DigitalStorageUnit.prototype = new Measurement(); 29176 DigitalStorageUnit.prototype.parent = Measurement; 29177 DigitalStorageUnit.prototype.constructor = DigitalStorageUnit; 29178 29179 DigitalStorageUnit.ratios = { 29180 /* # bit byte kb kB mb mB gb gB tb tB pb pB */ 29181 "bit": [ 1, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13, 8.881784197e-16, 1.110223025e-16 ], 29182 "byte": [ 2, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13, 7.105427358e-15, 8.881784197e-16 ], 29183 "kilobit": [ 3, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13 ], 29184 "kilobyte": [ 4, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13 ], 29185 "megabit": [ 5, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10 ], 29186 "megabyte": [ 6, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10 ], 29187 "gigabit": [ 7, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7 ], 29188 "gigabyte": [ 8, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7 ], 29189 "terabit": [ 9, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4 ], 29190 "terabyte": [ 10, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625 ], 29191 "petabit": [ 11, 1.125899907e15, 1.407374884e14, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125 ], 29192 "petabyte": [ 12, 9.007199255e15, 1.125899907e15, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1 ] 29193 }; 29194 29195 DigitalStorageUnit.bitSystem = { 29196 "bit": 1, 29197 "kilobit": 3, 29198 "megabit": 5, 29199 "gigabit": 7, 29200 "terabit": 9, 29201 "petabit": 11 29202 }; 29203 DigitalStorageUnit.byteSystem = { 29204 "byte": 2, 29205 "kilobyte": 4, 29206 "megabyte": 6, 29207 "gigabyte": 8, 29208 "terabyte": 10, 29209 "petabyte": 12 29210 }; 29211 29212 /** 29213 * Return the type of this measurement. Examples are "mass", 29214 * "length", "speed", etc. Measurements can only be converted 29215 * to measurements of the same type.<p> 29216 * 29217 * The type of the units is determined automatically from the 29218 * units. For example, the unit "grams" is type "mass". Use the 29219 * static call {@link Measurement.getAvailableUnits} 29220 * to find out what units this version of ilib supports. 29221 * 29222 * @return {string} the name of the type of this measurement 29223 */ 29224 DigitalStorageUnit.prototype.getMeasure = function() { 29225 return "digitalStorage"; 29226 }; 29227 29228 /** 29229 * Return a new measurement instance that is converted to a new 29230 * measurement unit. Measurements can only be converted 29231 * to measurements of the same type.<p> 29232 * 29233 * @param {string} to The name of the units to convert to 29234 * @return {Measurement|undefined} the converted measurement 29235 * or undefined if the requested units are for a different 29236 * measurement type 29237 * 29238 */ 29239 DigitalStorageUnit.prototype.convert = function(to) { 29240 if (!to || typeof(DigitalStorageUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 29241 return undefined; 29242 } 29243 return new DigitalStorageUnit({ 29244 unit: to, 29245 amount: this 29246 }); 29247 }; 29248 29249 /** 29250 * Localize the measurement to the commonly used measurement in that locale. For example 29251 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29252 * the formatted number should be automatically converted to the most appropriate 29253 * measure in the other system, in this case, mph. The formatted result should 29254 * appear as "37.3 mph". 29255 * 29256 * @param {string} locale current locale string 29257 * @returns {Measurement} a new instance that is converted to locale 29258 */ 29259 DigitalStorageUnit.prototype.localize = function(locale) { 29260 return new DigitalStorageUnit({ 29261 unit: this.unit, 29262 amount: this.amount 29263 }); 29264 }; 29265 29266 /** 29267 * Scale the measurement unit to an acceptable level. The scaling 29268 * happens so that the integer part of the amount is as small as 29269 * possible without being below zero. This will result in the 29270 * largest units that can represent this measurement without 29271 * fractions. Measurements can only be scaled to other measurements 29272 * of the same type. 29273 * 29274 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 29275 * or undefined if the system can be inferred from the current measure 29276 * @return {Measurement} a new instance that is scaled to the 29277 * right level 29278 */ 29279 DigitalStorageUnit.prototype.scale = function(measurementsystem) { 29280 var mSystem; 29281 if (this.unit in DigitalStorageUnit.bitSystem) { 29282 mSystem = DigitalStorageUnit.bitSystem; 29283 } else { 29284 mSystem = DigitalStorageUnit.byteSystem; 29285 } 29286 29287 var dStorage = this.amount; 29288 var munit = this.unit; 29289 var fromRow = DigitalStorageUnit.ratios[this.unit]; 29290 29291 dStorage = 18446744073709551999; 29292 for (var m in mSystem) { 29293 var tmp = this.amount * fromRow[mSystem[m]]; 29294 if (tmp >= 1 && tmp < dStorage) { 29295 dStorage = tmp; 29296 munit = m; 29297 } 29298 } 29299 29300 return new DigitalStorageUnit({ 29301 unit: munit, 29302 amount: dStorage 29303 }); 29304 }; 29305 29306 DigitalStorageUnit.aliases = { 29307 "bits": "bit", 29308 "bit": "bit", 29309 "Bits": "bit", 29310 "Bit": "bit", 29311 "byte": "byte", 29312 "bytes": "byte", 29313 "Byte": "byte", 29314 "Bytes": "byte", 29315 "kilobits": "kilobit", 29316 "Kilobits": "kilobit", 29317 "KiloBits": "kilobit", 29318 "kiloBits": "kilobit", 29319 "kilobit": "kilobit", 29320 "Kilobit": "kilobit", 29321 "kiloBit": "kilobit", 29322 "KiloBit": "kilobit", 29323 "kb": "kilobit", 29324 "Kb": "kilobit", 29325 "kilobyte": "kilobyte", 29326 "Kilobyte": "kilobyte", 29327 "kiloByte": "kilobyte", 29328 "KiloByte": "kilobyte", 29329 "kilobytes": "kilobyte", 29330 "Kilobytes": "kilobyte", 29331 "kiloBytes": "kilobyte", 29332 "KiloBytes": "kilobyte", 29333 "kB": "kilobyte", 29334 "KB": "kilobyte", 29335 "megabit": "megabit", 29336 "Megabit": "megabit", 29337 "megaBit": "megabit", 29338 "MegaBit": "megabit", 29339 "megabits": "megabit", 29340 "Megabits": "megabit", 29341 "megaBits": "megabit", 29342 "MegaBits": "megabit", 29343 "Mb": "megabit", 29344 "mb": "megabit", 29345 "megabyte": "megabyte", 29346 "Megabyte": "megabyte", 29347 "megaByte": "megabyte", 29348 "MegaByte": "megabyte", 29349 "megabytes": "megabyte", 29350 "Megabytes": "megabyte", 29351 "megaBytes": "megabyte", 29352 "MegaBytes": "megabyte", 29353 "MB": "megabyte", 29354 "mB": "megabyte", 29355 "gigabit": "gigabit", 29356 "Gigabit": "gigabit", 29357 "gigaBit": "gigabit", 29358 "GigaBit": "gigabit", 29359 "gigabits": "gigabit", 29360 "Gigabits": "gigabit", 29361 "gigaBits": "gigabyte", 29362 "GigaBits": "gigabit", 29363 "Gb": "gigabit", 29364 "gb": "gigabit", 29365 "gigabyte": "gigabyte", 29366 "Gigabyte": "gigabyte", 29367 "gigaByte": "gigabyte", 29368 "GigaByte": "gigabyte", 29369 "gigabytes": "gigabyte", 29370 "Gigabytes": "gigabyte", 29371 "gigaBytes": "gigabyte", 29372 "GigaBytes": "gigabyte", 29373 "GB": "gigabyte", 29374 "gB": "gigabyte", 29375 "terabit": "terabit", 29376 "Terabit": "terabit", 29377 "teraBit": "terabit", 29378 "TeraBit": "terabit", 29379 "terabits": "terabit", 29380 "Terabits": "terabit", 29381 "teraBits": "terabit", 29382 "TeraBits": "terabit", 29383 "tb": "terabit", 29384 "Tb": "terabit", 29385 "terabyte": "terabyte", 29386 "Terabyte": "terabyte", 29387 "teraByte": "terabyte", 29388 "TeraByte": "terabyte", 29389 "terabytes": "terabyte", 29390 "Terabytes": "terabyte", 29391 "teraBytes": "terabyte", 29392 "TeraBytes": "terabyte", 29393 "TB": "terabyte", 29394 "tB": "terabyte", 29395 "petabit": "petabit", 29396 "Petabit": "petabit", 29397 "petaBit": "petabit", 29398 "PetaBit": "petabit", 29399 "petabits": "petabit", 29400 "Petabits": "petabit", 29401 "petaBits": "petabit", 29402 "PetaBits": "petabit", 29403 "pb": "petabit", 29404 "Pb": "petabit", 29405 "petabyte": "petabyte", 29406 "Petabyte": "petabyte", 29407 "petaByte": "petabyte", 29408 "PetaByte": "petabyte", 29409 "petabytes": "petabyte", 29410 "Petabytes": "petabyte", 29411 "petaBytes": "petabyte", 29412 "PetaBytes": "petabyte", 29413 "PB": "petabyte", 29414 "pB": "petabyte" 29415 }; 29416 29417 /** 29418 * Convert a digitalStorage to another measure. 29419 * @static 29420 * @param to {string} unit to convert to 29421 * @param from {string} unit to convert from 29422 * @param digitalStorage {number} amount to be convert 29423 * @returns {number|undefined} the converted amount 29424 */ 29425 DigitalStorageUnit.convert = function(to, from, digitalStorage) { 29426 from = DigitalStorageUnit.aliases[from] || from; 29427 to = DigitalStorageUnit.aliases[to] || to; 29428 var fromRow = DigitalStorageUnit.ratios[from]; 29429 var toRow = DigitalStorageUnit.ratios[to]; 29430 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 29431 return undefined; 29432 } 29433 var result = digitalStorage * fromRow[toRow[0]]; 29434 return result; 29435 }; 29436 29437 /** 29438 * @private 29439 * @static 29440 */ 29441 DigitalStorageUnit.getMeasures = function () { 29442 var ret = []; 29443 for (var m in DigitalStorageUnit.ratios) { 29444 ret.push(m); 29445 } 29446 return ret; 29447 }; 29448 29449 //register with the factory method 29450 Measurement._constructors["digitalStorage"] = DigitalStorageUnit; 29451 29452 29453 /*< EnergyUnit.js */ 29454 /* 29455 * Energy.js - Unit conversions for Energys/energys 29456 * 29457 * Copyright © 2014-2015, JEDLSoft 29458 * 29459 * Licensed under the Apache License, Version 2.0 (the "License"); 29460 * you may not use this file except in compliance with the License. 29461 * You may obtain a copy of the License at 29462 * 29463 * http://www.apache.org/licenses/LICENSE-2.0 29464 * 29465 * Unless required by applicable law or agreed to in writing, software 29466 * distributed under the License is distributed on an "AS IS" BASIS, 29467 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29468 * 29469 * See the License for the specific language governing permissions and 29470 * limitations under the License. 29471 */ 29472 29473 /* 29474 !depends 29475 Measurement.js 29476 */ 29477 29478 29479 /** 29480 * @class 29481 * Create a new energy measurement instance. 29482 * 29483 * @constructor 29484 * @extends Measurement 29485 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 29486 * the construction of this instance 29487 */ 29488 var EnergyUnit = function (options) { 29489 this.unit = "joule"; 29490 this.amount = 0; 29491 this.aliases = EnergyUnit.aliases; // share this table in all instances 29492 29493 if (options) { 29494 if (typeof(options.unit) !== 'undefined') { 29495 this.originalUnit = options.unit; 29496 this.unit = this.aliases[options.unit] || options.unit; 29497 } 29498 29499 if (typeof(options.amount) === 'object') { 29500 if (options.amount.getMeasure() === "energy") { 29501 this.amount = EnergyUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 29502 } else { 29503 throw "Cannot convert units " + options.amount.unit + " to a energy"; 29504 } 29505 } else if (typeof(options.amount) !== 'undefined') { 29506 this.amount = parseFloat(options.amount); 29507 } 29508 } 29509 29510 if (typeof(EnergyUnit.ratios[this.unit]) === 'undefined') { 29511 throw "Unknown unit: " + options.unit; 29512 } 29513 }; 29514 29515 EnergyUnit.prototype = new Measurement(); 29516 EnergyUnit.prototype.parent = Measurement; 29517 EnergyUnit.prototype.constructor = EnergyUnit; 29518 29519 EnergyUnit.ratios = { 29520 /* index mJ J BTU kJ Wh Cal MJ kWh gJ MWh GWh */ 29521 "millijoule": [ 1, 1, 0.001, 9.4781707775e-7, 1e-6, 2.7777777778e-7, 2.3884589663e-7, 1.0e-9, 2.7777777778e-10, 1.0e-12, 2.7777777778e-13, 2.7777777778e-16 ], 29522 "joule": [ 2, 1000, 1, 9.4781707775e-4, 0.001, 2.7777777778e-4, 2.3884589663e-4, 1.0e-6, 2.7777777778e-7, 1.0e-9, 2.7777777778e-10, 2.7777777778e-13 ], 29523 "BTU": [ 3, 1055055.9, 1055.0559, 1, 1.0550559, 0.29307108333, 0.25199577243, 1.0550559e-3, 2.9307108333e-4, 1.0550559e-6, 2.9307108333e-7, 2.9307108333e-10 ], 29524 "kilojoule": [ 4, 1000000, 1000, 0.94781707775, 1, 0.27777777778, 0.23884589663, 0.001, 2.7777777778e-4, 1.0e-6, 2.7777777778e-7, 2.7777777778e-10 ], 29525 "watt hour": [ 5, 3.6e+6, 3600, 3.4121414799, 3.6, 1, 0.85984522786, 0.0036, 0.001, 3.6e-6, 1.0e-6, 1.0e-9 ], 29526 "calorie": [ 6, 4.868e+5, 4186.8, 3.9683205411, 4.1868, 1.163, 1, 4.1868e-3, 1.163e-3, 4.1868e-6, 1.163e-6, 1.163e-9 ], 29527 "megajoule": [ 7, 1e+9, 1e+6, 947.81707775, 1000, 277.77777778, 238.84589663, 1, 0.27777777778, 0.001, 2.7777777778e-4, 2.7777777778e-7 ], 29528 "kilowatt hour":[ 8, 3.6e+9, 3.6e+6, 3412.1414799, 3600, 1000, 859.84522786, 3.6, 1, 3.6e-3, 0.001, 1e-6 ], 29529 "gigajoule": [ 9, 1e+12, 1e+9, 947817.07775, 1e+6, 277777.77778, 238845.89663, 1000, 277.77777778, 1, 0.27777777778, 2.7777777778e-4 ], 29530 "megawatt hour":[ 10, 3.6e+12, 3.6e+9, 3412141.4799, 3.6e+6, 1e+6, 859845.22786, 3600, 1000, 3.6, 1, 0.001 ], 29531 "gigawatt hour":[ 11, 3.6e+15, 3.6e+12, 3412141479.9, 3.6e+9, 1e+9, 859845227.86, 3.6e+6, 1e+6, 3600, 1000, 1 ] 29532 }; 29533 29534 /** 29535 * Return the type of this measurement. Examples are "mass", 29536 * "length", "speed", etc. Measurements can only be converted 29537 * to measurements of the same type.<p> 29538 * 29539 * The type of the units is determined automatically from the 29540 * units. For example, the unit "grams" is type "mass". Use the 29541 * static call {@link Measurement.getAvailableUnits} 29542 * to find out what units this version of ilib supports. 29543 * 29544 * @return {string} the name of the type of this measurement 29545 */ 29546 EnergyUnit.prototype.getMeasure = function() { 29547 return "energy"; 29548 }; 29549 29550 /** 29551 * Return a new measurement instance that is converted to a new 29552 * measurement unit. Measurements can only be converted 29553 * to measurements of the same type.<p> 29554 * 29555 * @param {string} to The name of the units to convert to 29556 * @return {Measurement|undefined} the converted measurement 29557 * or undefined if the requested units are for a different 29558 * measurement type 29559 */ 29560 EnergyUnit.prototype.convert = function(to) { 29561 if (!to || typeof(EnergyUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 29562 return undefined; 29563 } 29564 return new EnergyUnit({ 29565 unit: to, 29566 amount: this 29567 }); 29568 }; 29569 29570 EnergyUnit.aliases = { 29571 "milli joule": "millijoule", 29572 "millijoule": "millijoule", 29573 "MilliJoule": "millijoule", 29574 "milliJ": "millijoule", 29575 "joule": "joule", 29576 "J": "joule", 29577 "j": "joule", 29578 "Joule": "joule", 29579 "Joules": "joule", 29580 "joules": "joule", 29581 "BTU": "BTU", 29582 "btu": "BTU", 29583 "British thermal unit": "BTU", 29584 "british thermal unit": "BTU", 29585 "kilo joule": "kilojoule", 29586 "kJ": "kilojoule", 29587 "kj": "kilojoule", 29588 "Kj": "kilojoule", 29589 "kiloJoule": "kilojoule", 29590 "kilojoule": "kilojoule", 29591 "kjoule": "kilojoule", 29592 "watt hour": "watt hour", 29593 "Wh": "watt hour", 29594 "wh": "watt hour", 29595 "watt-hour": "watt hour", 29596 "calorie": "calorie", 29597 "Cal": "calorie", 29598 "cal": "calorie", 29599 "Calorie": "calorie", 29600 "calories": "calorie", 29601 "mega joule": "megajoule", 29602 "MJ": "megajoule", 29603 "megajoule": "megajoule", 29604 "megajoules": "megajoule", 29605 "Megajoules": "megajoule", 29606 "megaJoules": "megajoule", 29607 "MegaJoules": "megajoule", 29608 "megaJoule": "megajoule", 29609 "MegaJoule": "megajoule", 29610 "kilo Watt hour": "kilowatt hour", 29611 "kWh": "kilowatt hour", 29612 "kiloWh": "kilowatt hour", 29613 "KiloWh": "kilowatt hour", 29614 "KiloWatt-hour": "kilowatt hour", 29615 "kilowatt hour": "kilowatt hour", 29616 "kilowatt-hour": "kilowatt hour", 29617 "KiloWatt-hours": "kilowatt hour", 29618 "kilowatt-hours": "kilowatt hour", 29619 "Kilo Watt-hour": "kilowatt hour", 29620 "Kilo Watt-hours": "kilowatt hour", 29621 "giga joule": "gigajoule", 29622 "gJ": "gigajoule", 29623 "GJ": "gigajoule", 29624 "GigaJoule": "gigajoule", 29625 "gigaJoule": "gigajoule", 29626 "gigajoule": "gigajoule", 29627 "GigaJoules": "gigajoule", 29628 "gigaJoules": "gigajoule", 29629 "Gigajoules": "gigajoule", 29630 "gigajoules": "gigajoule", 29631 "mega watt hour": "megawatt hour", 29632 "MWh": "megawatt hour", 29633 "MegaWh": "megawatt hour", 29634 "megaWh": "megawatt hour", 29635 "megaWatthour": "megawatt hour", 29636 "megaWatt-hour": "megawatt hour", 29637 "mega Watt-hour": "megawatt hour", 29638 "megaWatt hour": "megawatt hour", 29639 "megawatt hour": "megawatt hour", 29640 "mega Watt hour": "megawatt hour", 29641 "giga watt hour": "gigawatt hour", 29642 "gWh": "gigawatt hour", 29643 "GWh": "gigawatt hour", 29644 "gigaWh": "gigawatt hour", 29645 "gigaWatt-hour": "gigawatt hour", 29646 "gigawatt-hour": "gigawatt hour", 29647 "gigaWatt hour": "gigawatt hour", 29648 "gigawatt hour": "gigawatt hour", 29649 "gigawatthour": "gigawatt hour" 29650 }; 29651 29652 /** 29653 * Convert a energy to another measure. 29654 * @static 29655 * @param to {string} unit to convert to 29656 * @param from {string} unit to convert from 29657 * @param energy {number} amount to be convert 29658 * @returns {number|undefined} the converted amount 29659 */ 29660 EnergyUnit.convert = function(to, from, energy) { 29661 from = EnergyUnit.aliases[from] || from; 29662 to = EnergyUnit.aliases[to] || to; 29663 var fromRow = EnergyUnit.ratios[from]; 29664 var toRow = EnergyUnit.ratios[to]; 29665 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 29666 return undefined; 29667 } 29668 return energy * fromRow[toRow[0]]; 29669 }; 29670 29671 /** 29672 * @private 29673 * @static 29674 */ 29675 EnergyUnit.getMeasures = function () { 29676 var ret = []; 29677 for (var m in EnergyUnit.ratios) { 29678 ret.push(m); 29679 } 29680 return ret; 29681 }; 29682 29683 EnergyUnit.metricJouleSystem = { 29684 "millijoule": 1, 29685 "joule": 2, 29686 "kilojoule": 4, 29687 "megajoule": 7, 29688 "gigajoule": 9 29689 }; 29690 EnergyUnit.metricWattHourSystem = { 29691 "watt hour": 5, 29692 "kilowatt hour": 8, 29693 "megawatt hour": 10, 29694 "gigawatt hour": 11 29695 }; 29696 29697 EnergyUnit.imperialSystem = { 29698 "BTU": 3 29699 }; 29700 EnergyUnit.uscustomarySystem = { 29701 "calorie": 6 29702 }; 29703 29704 EnergyUnit.metricToImperial = { 29705 "millijoule": "BTU", 29706 "joule": "BTU", 29707 "kilojoule": "BTU", 29708 "megajoule": "BTU", 29709 "gigajoule": "BTU" 29710 }; 29711 EnergyUnit.imperialToMetric = { 29712 "BTU": "joule" 29713 }; 29714 29715 /** 29716 * Localize the measurement to the commonly used measurement in that locale. For example 29717 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29718 * the formatted number should be automatically converted to the most appropriate 29719 * measure in the other system, in this case, mph. The formatted result should 29720 * appear as "37.3 mph". 29721 * 29722 * @param {string} locale current locale string 29723 * @returns {Measurement} a new instance that is converted to locale 29724 */ 29725 EnergyUnit.prototype.localize = function(locale) { 29726 var to; 29727 if (locale === "en-GB") { 29728 to = EnergyUnit.metricToImperial[this.unit] || this.unit; 29729 } else { 29730 to = EnergyUnit.imperialToMetric[this.unit] || this.unit; 29731 } 29732 29733 return new EnergyUnit({ 29734 unit: to, 29735 amount: this 29736 }); 29737 }; 29738 29739 /** 29740 * Scale the measurement unit to an acceptable level. The scaling 29741 * happens so that the integer part of the amount is as small as 29742 * possible without being below zero. This will result in the 29743 * largest units that can represent this measurement without 29744 * fractions. Measurements can only be scaled to other measurements 29745 * of the same type. 29746 * 29747 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 29748 * or undefined if the system can be inferred from the current measure 29749 * @return {Measurement} a new instance that is scaled to the 29750 * right level 29751 */ 29752 EnergyUnit.prototype.scale = function(measurementsystem) { 29753 var fromRow = EnergyUnit.ratios[this.unit]; 29754 var mSystem; 29755 29756 if ((measurementsystem === "metric" && typeof(EnergyUnit.metricJouleSystem[this.unit]) !== 'undefined')|| (typeof(measurementsystem) === 'undefined' 29757 && typeof(EnergyUnit.metricJouleSystem[this.unit]) !== 'undefined')) { 29758 mSystem = EnergyUnit.metricJouleSystem; 29759 } 29760 else if ((measurementsystem === "metric" && typeof(EnergyUnit.metricWattHourSystem[this.unit]) !== 'undefined')|| (typeof(measurementsystem) === 'undefined' 29761 && typeof(EnergyUnit.metricWattHourSystem[this.unit]) !== 'undefined')) { 29762 mSystem = EnergyUnit.metricWattHourSystem; 29763 } 29764 29765 else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 29766 && typeof(EnergyUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 29767 mSystem = EnergyUnit.uscustomarySystem; 29768 } 29769 else if (measurementsystem === "imperial"|| (typeof(measurementsystem) === 'undefined' 29770 && typeof(EnergyUnit.imperialSystem[this.unit]) !== 'undefined')) { 29771 mSystem = EnergyUnit.imperialSystem; 29772 } 29773 29774 var energy = this.amount; 29775 var munit = this.unit; 29776 29777 energy = 18446744073709551999; 29778 29779 for (var m in mSystem) { 29780 var tmp = this.amount * fromRow[mSystem[m]]; 29781 if (tmp >= 1 && tmp < energy) { 29782 energy = tmp; 29783 munit = m; 29784 } 29785 } 29786 29787 return new EnergyUnit({ 29788 unit: munit, 29789 amount: energy 29790 }); 29791 }; 29792 //register with the factory method 29793 Measurement._constructors["energy"] = EnergyUnit; 29794 29795 29796 /*< FuelConsumptionUnit.js */ 29797 /* 29798 * fuelconsumption.js - Unit conversions for FuelConsumption 29799 * 29800 * Copyright © 2014-2015, JEDLSoft 29801 * 29802 * Licensed under the Apache License, Version 2.0 (the "License"); 29803 * you may not use this file except in compliance with the License. 29804 * You may obtain a copy of the License at 29805 * 29806 * http://www.apache.org/licenses/LICENSE-2.0 29807 * 29808 * Unless required by applicable law or agreed to in writing, software 29809 * distributed under the License is distributed on an "AS IS" BASIS, 29810 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29811 * 29812 * See the License for the specific language governing permissions and 29813 * limitations under the License. 29814 */ 29815 29816 /* 29817 !depends 29818 Measurement.js 29819 */ 29820 29821 29822 /** 29823 * @class 29824 * Create a new fuelconsumption measurement instance. 29825 * 29826 * @constructor 29827 * @extends Measurement 29828 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 29829 * the construction of this instance 29830 */ 29831 var FuelConsumptionUnit = function(options) { 29832 this.unit = "km/liter"; 29833 this.amount = 0; 29834 this.aliases = FuelConsumptionUnit.aliases; // share this table in all instances 29835 29836 if (options) { 29837 if (typeof(options.unit) !== 'undefined') { 29838 this.originalUnit = options.unit; 29839 this.unit = this.aliases[options.unit] || options.unit; 29840 } 29841 29842 if (typeof(options.amount) === 'object') { 29843 if (options.amount.getMeasure() === "fuelconsumption") { 29844 this.amount = FuelConsumptionUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 29845 } else { 29846 throw "Cannot convert unit " + options.amount.unit + " to fuelconsumption"; 29847 } 29848 } else if (typeof(options.amount) !== 'undefined') { 29849 this.amount = parseFloat(options.amount); 29850 } 29851 } 29852 }; 29853 29854 FuelConsumptionUnit.prototype = new Measurement(); 29855 FuelConsumptionUnit.prototype.parent = Measurement; 29856 FuelConsumptionUnit.prototype.constructor = FuelConsumptionUnit; 29857 29858 FuelConsumptionUnit.ratios = [ 29859 "km/liter", 29860 "liter/100km", 29861 "mpg", 29862 "mpg(imp)" 29863 ]; 29864 29865 /** 29866 * Return the type of this measurement. Examples are "mass", 29867 * "length", "speed", etc. Measurements can only be converted 29868 * to measurements of the same type.<p> 29869 * 29870 * The type of the units is determined automatically from the 29871 * units. For example, the unit "grams" is type "mass". Use the 29872 * static call {@link Measurement.getAvailableUnits} 29873 * to find out what units this version of ilib supports. 29874 * 29875 * @return {string} the name of the type of this measurement 29876 */ 29877 FuelConsumptionUnit.prototype.getMeasure = function() { 29878 return "fuelconsumption"; 29879 }; 29880 29881 /** 29882 * Return a new measurement instance that is converted to a new 29883 * measurement unit. Measurements can only be converted 29884 * to measurements of the same type.<p> 29885 * 29886 * @param {string} to The name of the units to convert to 29887 * @return {Measurement|undefined} the converted measurement 29888 * or undefined if the requested units are for a different 29889 * measurement type 29890 */ 29891 FuelConsumptionUnit.prototype.convert = function(to) { 29892 if (!to || typeof(FuelConsumptionUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 29893 return undefined; 29894 } 29895 return new FuelConsumptionUnit({ 29896 unit: to, 29897 amount: this 29898 }); 29899 }; 29900 /*["km/liter", "liter/100km", "mpg", "mpg(imp)"*/ 29901 FuelConsumptionUnit.aliases = { 29902 "Km/liter": "km/liter", 29903 "KM/Liter": "km/liter", 29904 "KM/L": "km/liter", 29905 "Kilometers Per Liter": "km/liter", 29906 "kilometers per liter": "km/liter", 29907 "km/l": "km/liter", 29908 "Kilometers/Liter": "km/liter", 29909 "Kilometer/Liter": "km/liter", 29910 "kilometers/liter": "km/liter", 29911 "kilometer/liter": "km/liter", 29912 "km/liter": "km/liter", 29913 "Liter/100km": "liter/100km", 29914 "Liters/100km": "liter/100km", 29915 "Liter/100kms": "liter/100km", 29916 "Liters/100kms": "liter/100km", 29917 "liter/100km": "liter/100km", 29918 "liters/100kms": "liter/100km", 29919 "liters/100km": "liter/100km", 29920 "liter/100kms": "liter/100km", 29921 "Liter/100KM": "liter/100km", 29922 "Liters/100KM": "liter/100km", 29923 "L/100km": "liter/100km", 29924 "L/100KM": "liter/100km", 29925 "l/100KM": "liter/100km", 29926 "l/100km": "liter/100km", 29927 "l/100kms": "liter/100km", 29928 "MPG(US)": "mpg", 29929 "USMPG ": "mpg", 29930 "mpg": "mpg", 29931 "mpgUS": "mpg", 29932 "mpg(US)": "mpg", 29933 "mpg(us)": "mpg", 29934 "mpg-us": "mpg", 29935 "mpg Imp": "mpg(imp)", 29936 "MPG(imp)": "mpg(imp)", 29937 "mpg(imp)": "mpg(imp)", 29938 "mpg-imp": "mpg(imp)" 29939 }; 29940 29941 FuelConsumptionUnit.metricToUScustomary = { 29942 "km/liter": "mpg", 29943 "liter/100km": "mpg" 29944 }; 29945 FuelConsumptionUnit.metricToImperial = { 29946 "km/liter": "mpg(imp)", 29947 "liter/100km": "mpg(imp)" 29948 }; 29949 29950 FuelConsumptionUnit.imperialToMetric = { 29951 "mpg(imp)": "km/liter" 29952 }; 29953 FuelConsumptionUnit.imperialToUScustomary = { 29954 "mpg(imp)": "mpg" 29955 }; 29956 29957 FuelConsumptionUnit.uScustomaryToImperial = { 29958 "mpg": "mpg(imp)" 29959 }; 29960 FuelConsumptionUnit.uScustomarylToMetric = { 29961 "mpg": "km/liter" 29962 }; 29963 29964 /** 29965 * Localize the measurement to the commonly used measurement in that locale. For example 29966 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29967 * the formatted number should be automatically converted to the most appropriate 29968 * measure in the other system, in this case, mph. The formatted result should 29969 * appear as "37.3 mph". 29970 * 29971 * @param {string} locale current locale string 29972 * @returns {Measurement} a new instance that is converted to locale 29973 */ 29974 FuelConsumptionUnit.prototype.localize = function(locale) { 29975 var to; 29976 if (locale === "en-US") { 29977 to = FuelConsumptionUnit.metricToUScustomary[this.unit] || 29978 FuelConsumptionUnit.imperialToUScustomary[this.unit] || 29979 this.unit; 29980 } else if (locale === "en-GB") { 29981 to = FuelConsumptionUnit.metricToImperial[this.unit] || 29982 FuelConsumptionUnit.uScustomaryToImperial[this.unit] || 29983 this.unit; 29984 } else { 29985 to = FuelConsumptionUnit.uScustomarylToMetric[this.unit] || 29986 FuelConsumptionUnit.imperialToUScustomary[this.unit] || 29987 this.unit; 29988 } 29989 return new FuelConsumptionUnit({ 29990 unit: to, 29991 amount: this 29992 }); 29993 }; 29994 29995 /** 29996 * Convert a FuelConsumption to another measure. 29997 * 29998 * @static 29999 * @param to {string} unit to convert to 30000 * @param from {string} unit to convert from 30001 * @param fuelConsumption {number} amount to be convert 30002 * @returns {number|undefined} the converted amount 30003 */ 30004 FuelConsumptionUnit.convert = function(to, from, fuelConsumption) { 30005 from = FuelConsumptionUnit.aliases[from] || from; 30006 to = FuelConsumptionUnit.aliases[to] || to; 30007 var returnValue = 0; 30008 30009 switch (from) { 30010 case "km/liter": 30011 switch (to) { 30012 case "km/liter": 30013 returnValue = fuelConsumption * 1; 30014 break; 30015 case "liter/100km": 30016 returnValue = 100 / fuelConsumption; 30017 break; 30018 case "mpg": 30019 returnValue = fuelConsumption * 2.35215; 30020 break; 30021 case "mpg(imp)": 30022 returnValue = fuelConsumption * 2.82481; 30023 break; 30024 } 30025 break; 30026 case "liter/100km": 30027 switch (to) { 30028 case "km/liter": 30029 returnValue = 100 / fuelConsumption; 30030 break; 30031 case "liter/100km": 30032 returnValue = fuelConsumption * 1; 30033 break; 30034 case "mpg": 30035 returnValue = 235.215 / fuelConsumption; 30036 break; 30037 case "mpg(imp)": 30038 returnValue = 282.481 / fuelConsumption; 30039 break; 30040 } 30041 break; 30042 case "mpg": 30043 switch (to) { 30044 case "km/liter": 30045 returnValue = fuelConsumption * 0.425144; 30046 break; 30047 case "liter/100km": 30048 returnValue = 235.215 / fuelConsumption; 30049 break; 30050 case "mpg": 30051 returnValue = 1 * fuelConsumption; 30052 break; 30053 case "mpg(imp)": 30054 returnValue = 1.20095 * fuelConsumption; 30055 break; 30056 } 30057 break; 30058 case "mpg(imp)": 30059 switch (to) { 30060 case "km/liter": 30061 returnValue = fuelConsumption * 0.354006; 30062 break; 30063 case "liter/100km": 30064 returnValue = 282.481 / fuelConsumption; 30065 break; 30066 case "mpg": 30067 returnValue = 0.832674 * fuelConsumption; 30068 break; 30069 case "mpg(imp)": 30070 returnValue = 1 * fuelConsumption; 30071 break; 30072 } 30073 break; 30074 } 30075 return returnValue; 30076 }; 30077 30078 /** 30079 * Scale the measurement unit to an acceptable level. The scaling 30080 * happens so that the integer part of the amount is as small as 30081 * possible without being below zero. This will result in the 30082 * largest units that can represent this measurement without 30083 * fractions. Measurements can only be scaled to other measurements 30084 * of the same type. 30085 * 30086 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30087 * or undefined if the system can be inferred from the current measure 30088 * @return {Measurement} a new instance that is scaled to the 30089 * right level 30090 */ 30091 FuelConsumptionUnit.prototype.scale = function(measurementsystem) { 30092 return new FuelConsumptionUnit({ 30093 unit: this.unit, 30094 amount: this.amount 30095 }); 30096 }; 30097 30098 /** 30099 * @private 30100 * @static 30101 */ 30102 FuelConsumptionUnit.getMeasures = function() { 30103 var ret = []; 30104 ret.push("km/liter"); 30105 ret.push("liter/100km"); 30106 ret.push("mpg"); 30107 ret.push("mpg(imp)"); 30108 30109 return ret; 30110 }; 30111 30112 //register with the factory method 30113 Measurement._constructors["fuelconsumption"] = FuelConsumptionUnit; 30114 30115 30116 /*< LengthUnit.js */ 30117 /* 30118 * LengthUnit.js - Unit conversions for Lengths/lengths 30119 * 30120 * Copyright © 2014-2015, JEDLSoft 30121 * 30122 * Licensed under the Apache License, Version 2.0 (the "License"); 30123 * you may not use this file except in compliance with the License. 30124 * You may obtain a copy of the License at 30125 * 30126 * http://www.apache.org/licenses/LICENSE-2.0 30127 * 30128 * Unless required by applicable law or agreed to in writing, software 30129 * distributed under the License is distributed on an "AS IS" BASIS, 30130 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30131 * 30132 * See the License for the specific language governing permissions and 30133 * limitations under the License. 30134 */ 30135 30136 /* 30137 !depends 30138 Measurement.js 30139 */ 30140 30141 30142 /** 30143 * @class 30144 * Create a new length measurement instance. 30145 * 30146 * @constructor 30147 * @extends Measurement 30148 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30149 * the construction of this instance 30150 */ 30151 var LengthUnit = function (options) { 30152 this.unit = "meter"; 30153 this.amount = 0; 30154 this.aliases = LengthUnit.aliases; // share this table in all instances 30155 30156 if (options) { 30157 if (typeof(options.unit) !== 'undefined') { 30158 this.originalUnit = options.unit; 30159 this.unit = this.aliases[options.unit] || options.unit; 30160 } 30161 30162 if (typeof(options.amount) === 'object') { 30163 if (options.amount.getMeasure() === "length") { 30164 this.amount = LengthUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30165 } else { 30166 throw "Cannot convert unit " + options.amount.unit + " to a length"; 30167 } 30168 } else if (typeof(options.amount) !== 'undefined') { 30169 this.amount = parseFloat(options.amount); 30170 } 30171 } 30172 30173 if (typeof(LengthUnit.ratios[this.unit]) === 'undefined') { 30174 throw "Unknown unit: " + options.unit; 30175 } 30176 }; 30177 30178 LengthUnit.prototype = new Measurement(); 30179 LengthUnit.prototype.parent = Measurement; 30180 LengthUnit.prototype.constructor = LengthUnit; 30181 30182 LengthUnit.ratios = { 30183 /* index, µm mm cm inch dm foot yard m dam hm km mile nm Mm Gm */ 30184 "micrometer": [ 1, 1, 1e-3, 1e-4, 3.93701e-5, 1e-5, 3.28084e-6, 1.09361e-6, 1e-6, 1e-7, 1e-8, 1e-9, 6.21373e-10, 5.39957e-10, 1e-12, 1e-15 ], 30185 "millimeter": [ 2, 1000, 1, 0.1, 0.0393701, 0.01, 0.00328084, 1.09361e-3, 0.001, 1e-4, 1e-5, 1e-6, 6.21373e-7, 5.39957e-7, 1e-9, 1e-12 ], 30186 "centimeter": [ 3, 1e4, 10, 1, 0.393701, 0.1, 0.0328084, 0.0109361, 0.01, 0.001, 1e-4, 1e-5, 6.21373e-6, 5.39957e-6, 1e-8, 1e-9 ], 30187 "inch": [ 4, 25399.986, 25.399986, 2.5399986, 1, 0.25399986, 0.083333333, 0.027777778, 0.025399986, 2.5399986e-3, 2.5399986e-4, 2.5399986e-5, 1.5783e-5, 1.3715e-5, 2.5399986e-8, 2.5399986e-11 ], 30188 "decimeter": [ 5, 1e5, 100, 10, 3.93701, 1, 0.328084, 0.109361, 0.1, 0.01, 0.001, 1e-4, 6.21373e-5, 5.39957e-5, 1e-7, 1e-8 ], 30189 "foot": [ 6, 304799.99, 304.79999, 30.479999, 12, 3.0479999, 1, 0.33333333, 0.30479999, 0.030479999, 3.0479999e-3, 3.0479999e-4, 1.89394e-4, 1.64579e-4, 3.0479999e-7, 3.0479999e-10 ], 30190 "yard": [ 7, 914402.758, 914.402758, 91.4402758, 36, 9.14402758, 3, 1, 0.914402758, 0.0914402758, 9.14402758e-3, 9.14402758e-4, 5.68182e-4, 4.93737e-4, 9.14402758e-7, 9.14402758e-10 ], 30191 "meter": [ 8, 1e6, 1000, 100, 39.3701, 10, 3.28084, 1.09361, 1, 0.1, 0.01, 0.001, 6.213712e-4, 5.39957e-4, 1e-6, 1e-7 ], 30192 "decameter": [ 9, 1e7, 1e4, 1000, 393.701, 100, 32.8084, 10.9361, 10, 1, 0.1, 0.01, 6.21373e-3, 5.39957e-3, 1e-5, 1e-6 ], 30193 "hectometer": [ 10, 1e8, 1e5, 1e4, 3937.01, 1000, 328.084, 109.361, 100, 10, 1, 0.1, 0.0621373, 0.0539957, 1e-4, 1e-5 ], 30194 "kilometer": [ 11, 1e9, 1e6, 1e5, 39370.1, 1e4, 3280.84, 1093.61, 1000, 100, 10, 1, 0.621373, 0.539957, 0.001, 1e-4 ], 30195 "mile": [ 12, 1.60934e9, 1.60934e6, 1.60934e5, 63360, 1.60934e4, 5280, 1760, 1609.34, 160.934, 16.0934, 1.60934, 1, 0.868976, 1.60934e-3, 1.60934e-6 ], 30196 "nauticalmile": [ 13, 1.852e9, 1.852e6, 1.852e5, 72913.4, 1.852e4, 6076.12, 2025.37, 1852, 185.2, 18.52, 1.852, 1.15078, 1, 1.852e-3, 1.852e-6 ], 30197 "megameter": [ 14, 1e12, 1e9, 1e6, 3.93701e7, 1e5, 3.28084e6, 1.09361e6, 1e4, 1000, 100, 10, 621.373, 539.957, 1, 0.001 ], 30198 "gigameter": [ 15, 1e15, 1e12, 1e9, 3.93701e10, 1e8, 3.28084e9, 1.09361e9, 1e7, 1e6, 1e5, 1e4, 621373.0, 539957.0, 1000, 1 ] 30199 }; 30200 30201 LengthUnit.metricSystem = { 30202 "micrometer": 1, 30203 "millimeter": 2, 30204 "centimeter": 3, 30205 "decimeter": 5, 30206 "meter": 8, 30207 "decameter": 9, 30208 "hectometer": 10, 30209 "kilometer": 11, 30210 "megameter": 14, 30211 "gigameter": 15 30212 }; 30213 LengthUnit.imperialSystem = { 30214 "inch": 4, 30215 "foot": 6, 30216 "yard": 7, 30217 "mile": 12, 30218 "nauticalmile": 13 30219 }; 30220 LengthUnit.uscustomarySystem = { 30221 "inch": 4, 30222 "foot": 6, 30223 "yard": 7, 30224 "mile": 12, 30225 "nauticalmile": 13 30226 }; 30227 30228 LengthUnit.metricToUScustomary = { 30229 "micrometer": "inch", 30230 "millimeter": "inch", 30231 "centimeter": "inch", 30232 "decimeter": "inch", 30233 "meter": "yard", 30234 "decameter": "yard", 30235 "hectometer": "mile", 30236 "kilometer": "mile", 30237 "megameter": "nauticalmile", 30238 "gigameter": "nauticalmile" 30239 }; 30240 LengthUnit.usCustomaryToMetric = { 30241 "inch": "centimeter", 30242 "foot": "centimeter", 30243 "yard": "meter", 30244 "mile": "kilometer", 30245 "nauticalmile": "kilometer" 30246 }; 30247 30248 /** 30249 * Return the type of this measurement. Examples are "mass", 30250 * "length", "speed", etc. Measurements can only be converted 30251 * to measurements of the same type.<p> 30252 * 30253 * The type of the units is determined automatically from the 30254 * units. For example, the unit "grams" is type "mass". Use the 30255 * static call {@link Measurement.getAvailableUnits} 30256 * to find out what units this version of ilib supports. 30257 * 30258 * @return {string} the name of the type of this measurement 30259 */ 30260 LengthUnit.prototype.getMeasure = function() { 30261 return "length"; 30262 }; 30263 30264 /** 30265 * Localize the measurement to the commonly used measurement in that locale. For example 30266 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30267 * the formatted number should be automatically converted to the most appropriate 30268 * measure in the other system, in this case, mph. The formatted result should 30269 * appear as "37.3 mph". 30270 * 30271 * @param {string} locale current locale string 30272 * @returns {Measurement} a new instance that is converted to locale 30273 */ 30274 LengthUnit.prototype.localize = function(locale) { 30275 var to; 30276 if (locale === "en-US" || locale === "en-GB") { 30277 to = LengthUnit.metricToUScustomary[this.unit] || this.unit; 30278 } else { 30279 to = LengthUnit.usCustomaryToMetric[this.unit] || this.unit; 30280 } 30281 return new LengthUnit({ 30282 unit: to, 30283 amount: this 30284 }); 30285 }; 30286 30287 /** 30288 * Return a new measurement instance that is converted to a new 30289 * measurement unit. Measurements can only be converted 30290 * to measurements of the same type.<p> 30291 * 30292 * @param {string} to The name of the units to convert to 30293 * @return {Measurement|undefined} the converted measurement 30294 * or undefined if the requested units are for a different 30295 * measurement type 30296 */ 30297 LengthUnit.prototype.convert = function(to) { 30298 if (!to || typeof(LengthUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30299 return undefined; 30300 } 30301 return new LengthUnit({ 30302 unit: to, 30303 amount: this 30304 }); 30305 }; 30306 30307 /** 30308 * Scale the measurement unit to an acceptable level. The scaling 30309 * happens so that the integer part of the amount is as small as 30310 * possible without being below zero. This will result in the 30311 * largest units that can represent this measurement without 30312 * fractions. Measurements can only be scaled to other measurements 30313 * of the same type. 30314 * 30315 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30316 * or undefined if the system can be inferred from the current measure 30317 * @return {Measurement} a new instance that is scaled to the 30318 * right level 30319 */ 30320 LengthUnit.prototype.scale = function(measurementsystem) { 30321 var mSystem; 30322 if (measurementsystem === "metric" || (typeof(measurementsystem) === 'undefined' 30323 && typeof(LengthUnit.metricSystem[this.unit]) !== 'undefined')) { 30324 mSystem = LengthUnit.metricSystem; 30325 } else if (measurementsystem === "imperial" || (typeof(measurementsystem) === 'undefined' 30326 && typeof(LengthUnit.imperialSystem[this.unit]) !== 'undefined')) { 30327 mSystem = LengthUnit.imperialSystem; 30328 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 30329 && typeof(LengthUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 30330 mSystem = LengthUnit.uscustomarySystem; 30331 } else { 30332 return new LengthUnit({ 30333 unit: this.unit, 30334 amount: this.amount 30335 }); 30336 } 30337 30338 var length = this.amount; 30339 var munit = this.unit; 30340 var fromRow = LengthUnit.ratios[this.unit]; 30341 30342 length = 18446744073709551999; 30343 for (var m in mSystem) { 30344 var tmp = this.amount * fromRow[mSystem[m]]; 30345 if (tmp >= 1 && tmp < length) { 30346 length = tmp; 30347 munit = m; 30348 } 30349 } 30350 30351 return new LengthUnit({ 30352 unit: munit, 30353 amount: length 30354 }); 30355 }; 30356 30357 LengthUnit.aliases = { 30358 "miles": "mile", 30359 "mile":"mile", 30360 "nauticalmiles": "nauticalmile", 30361 "nautical mile": "nauticalmile", 30362 "nautical miles": "nauticalmile", 30363 "nauticalmile":"nauticalmile", 30364 "yards": "yard", 30365 "yard": "yard", 30366 "feet": "foot", 30367 "foot": "foot", 30368 "inches": "inch", 30369 "inch": "inch", 30370 "meters": "meter", 30371 "metre": "meter", 30372 "metres": "meter", 30373 "m": "meter", 30374 "meter": "meter", 30375 "micrometers": "micrometer", 30376 "micrometres": "micrometer", 30377 "micrometre": "micrometer", 30378 "µm": "micrometer", 30379 "micrometer": "micrometer", 30380 "millimeters": "millimeter", 30381 "millimetres": "millimeter", 30382 "millimetre": "millimeter", 30383 "mm": "millimeter", 30384 "millimeter": "millimeter", 30385 "centimeters": "centimeter", 30386 "centimetres": "centimeter", 30387 "centimetre": "centimeter", 30388 "cm": "centimeter", 30389 "centimeter": "centimeter", 30390 "decimeters": "decimeter", 30391 "decimetres": "decimeter", 30392 "decimetre": "decimeter", 30393 "dm": "decimeter", 30394 "decimeter": "decimeter", 30395 "decameters": "decameter", 30396 "decametres": "decameter", 30397 "decametre": "decameter", 30398 "dam": "decameter", 30399 "decameter": "decameter", 30400 "hectometers": "hectometer", 30401 "hectometres": "hectometer", 30402 "hectometre": "hectometer", 30403 "hm": "hectometer", 30404 "hectometer": "hectometer", 30405 "kilometers": "kilometer", 30406 "kilometres": "kilometer", 30407 "kilometre": "kilometer", 30408 "km": "kilometer", 30409 "kilometer": "kilometer", 30410 "megameters": "megameter", 30411 "megametres": "megameter", 30412 "megametre": "megameter", 30413 "Mm": "megameter", 30414 "megameter": "megameter", 30415 "gigameters": "gigameter", 30416 "gigametres": "gigameter", 30417 "gigametre": "gigameter", 30418 "Gm": "gigameter", 30419 "gigameter": "gigameter" 30420 }; 30421 30422 /** 30423 * Convert a length to another measure. 30424 * @static 30425 * @param to {string} unit to convert to 30426 * @param from {string} unit to convert from 30427 * @param length {number} amount to be convert 30428 * @returns {number|undefined} the converted amount 30429 */ 30430 LengthUnit.convert = function(to, from, length) { 30431 from = LengthUnit.aliases[from] || from; 30432 to = LengthUnit.aliases[to] || to; 30433 var fromRow = LengthUnit.ratios[from]; 30434 var toRow = LengthUnit.ratios[to]; 30435 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 30436 return undefined; 30437 } 30438 return length * fromRow[toRow[0]]; 30439 }; 30440 30441 /** 30442 * @private 30443 * @static 30444 */ 30445 LengthUnit.getMeasures = function () { 30446 var ret = []; 30447 for (var m in LengthUnit.ratios) { 30448 ret.push(m); 30449 } 30450 return ret; 30451 }; 30452 30453 //register with the factory method 30454 Measurement._constructors["length"] = LengthUnit; 30455 30456 30457 /*< MassUnit.js */ 30458 /* 30459 * MassUnit.js - Unit conversions for Mass/mass 30460 * 30461 * Copyright © 2014-2015, JEDLSoft 30462 * 30463 * Licensed under the Apache License, Version 2.0 (the "License"); 30464 * you may not use this file except in compliance with the License. 30465 * You may obtain a copy of the License at 30466 * 30467 * http://www.apache.org/licenses/LICENSE-2.0 30468 * 30469 * Unless required by applicable law or agreed to in writing, software 30470 * distributed under the License is distributed on an "AS IS" BASIS, 30471 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30472 * 30473 * See the License for the specific language governing permissions and 30474 * limitations under the License. 30475 */ 30476 30477 /* 30478 !depends 30479 Measurement.js 30480 */ 30481 30482 30483 /** 30484 * @class 30485 * Create a new mass measurement instance. 30486 * 30487 * @constructor 30488 * @extends Measurement 30489 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30490 * the construction of this instance 30491 */ 30492 var MassUnit = function (options) { 30493 this.unit = "gram"; 30494 this.amount = 0; 30495 this.aliases = MassUnit.aliases; // share this table in all instances 30496 30497 if (options) { 30498 if (typeof(options.unit) !== 'undefined') { 30499 this.originalUnit = options.unit; 30500 this.unit = this.aliases[options.unit] || options.unit; 30501 } 30502 30503 if (typeof(options.amount) === 'object') { 30504 if (options.amount.getMeasure() === "mass") { 30505 this.amount = MassUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30506 } else { 30507 throw "Cannot convert units " + options.amount.unit + " to a mass"; 30508 } 30509 } else if (typeof(options.amount) !== 'undefined') { 30510 this.amount = parseFloat(options.amount); 30511 } 30512 } 30513 30514 if (typeof(MassUnit.ratios[this.unit]) === 'undefined') { 30515 throw "Unknown unit: " + options.unit; 30516 } 30517 }; 30518 30519 MassUnit.prototype = new Measurement(); 30520 MassUnit.prototype.parent = Measurement; 30521 MassUnit.prototype.constructor = MassUnit; 30522 30523 MassUnit.ratios = { 30524 /* index µg mg g oz lp kg st sh ton mt ton ln ton */ 30525 "microgram": [ 1, 1, 0.001, 1e-6, 3.5274e-8, 2.2046e-9, 1e-9, 1.5747e-10, 1.1023e-12, 1e-12, 9.8421e-13 ], 30526 "milligram": [ 2, 1000, 1, 0.001, 3.5274e-5, 2.2046e-6, 1e-6, 1.5747e-7, 1.1023e-9, 1e-9, 9.8421e-10 ], 30527 "gram": [ 3, 1e+6, 1000, 1, 0.035274, 0.00220462, 0.001, 0.000157473, 1.1023e-6, 1e-6, 9.8421e-7 ], 30528 "ounce": [ 4, 2.835e+7, 28349.5, 28.3495, 1, 0.0625, 0.0283495, 0.00446429, 3.125e-5, 2.835e-5, 2.7902e-5 ], 30529 "pound": [ 5, 4.536e+8, 453592, 453.592, 16, 1, 0.453592, 0.0714286, 0.0005, 0.000453592, 0.000446429 ], 30530 "kilogram": [ 6, 1e+9, 1e+6, 1000, 35.274, 2.20462, 1, 0.157473, 0.00110231, 0.001, 0.000984207 ], 30531 "stone": [ 7, 6.35e+9, 6.35e+6, 6350.29, 224, 14, 6.35029, 1, 0.007, 0.00635029, 0.00625 ], 30532 "short ton": [ 8, 9.072e+11, 9.072e+8, 907185, 32000, 2000, 907.185, 142.857, 1, 0.907185, 0.892857 ], 30533 "metric ton": [ 9, 1e+12, 1e+9, 1e+6, 35274, 2204.62, 1000, 157.473, 1.10231, 1, 0.984207 ], 30534 "long ton": [ 10, 1.016e+12, 1.016e+9, 1.016e+6, 35840, 2240, 1016.05, 160, 1.12, 1.01605, 1 ] 30535 }; 30536 30537 MassUnit.metricSystem = { 30538 "microgram": 1, 30539 "milligram": 2, 30540 "gram": 3, 30541 "kilogram": 6, 30542 "metric ton": 9 30543 }; 30544 MassUnit.imperialSystem = { 30545 "ounce": 4, 30546 "pound": 5, 30547 "stone": 7, 30548 "long ton": 10 30549 }; 30550 MassUnit.uscustomarySystem = { 30551 "ounce": 4, 30552 "pound": 5, 30553 "short ton": 8 30554 }; 30555 30556 MassUnit.metricToUScustomary = { 30557 "microgram": "ounce", 30558 "milligram": "ounce", 30559 "gram": "ounce", 30560 "kilogram": "pound", 30561 "metric ton": "long ton" 30562 }; 30563 MassUnit.metricToImperial = { 30564 "microgram": "ounce", 30565 "milligram": "ounce", 30566 "gram": "ounce", 30567 "kilogram": "pound", 30568 "metric ton": "short ton" 30569 }; 30570 30571 MassUnit.imperialToMetric = { 30572 "ounce": "gram", 30573 "pound": "kilogram", 30574 "stone": "kilogram", 30575 "short ton": "metric ton" 30576 }; 30577 MassUnit.imperialToUScustomary = { 30578 "ounce": "ounce", 30579 "pound": "pound", 30580 "stone": "stone", 30581 "short ton": "long ton" 30582 }; 30583 30584 MassUnit.uScustomaryToImperial = { 30585 "ounce": "ounce", 30586 "pound": "pound", 30587 "stone": "stone", 30588 "long ton": "short ton" 30589 }; 30590 MassUnit.uScustomarylToMetric = { 30591 "ounce": "gram", 30592 "pound": "kilogram", 30593 "stone": "kilogram", 30594 "long ton": "metric ton" 30595 }; 30596 30597 /** 30598 * Localize the measurement to the commonly used measurement in that locale. For example 30599 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30600 * the formatted number should be automatically converted to the most appropriate 30601 * measure in the other system, in this case, mph. The formatted result should 30602 * appear as "37.3 mph". 30603 * 30604 * @param {string} locale current locale string 30605 * @returns {Measurement} a new instance that is converted to locale 30606 */ 30607 MassUnit.prototype.localize = function(locale) { 30608 var to; 30609 if (locale === "en-US") { 30610 to = MassUnit.metricToUScustomary[this.unit] || 30611 MassUnit.imperialToUScustomary[this.unit] || this.unit; 30612 } else if (locale === "en-GB") { 30613 to = MassUnit.metricToImperial[this.unit] || 30614 MassUnit.uScustomaryToImperial[this.unit] || this.unit; 30615 } else { 30616 to = MassUnit.uScustomarylToMetric[this.unit] || 30617 MassUnit.imperialToUScustomary[this.unit] || this.unit; 30618 } 30619 return new MassUnit({ 30620 unit: to, 30621 amount: this 30622 }); 30623 }; 30624 30625 /** 30626 * Return the type of this measurement. Examples are "mass", 30627 * "length", "speed", etc. Measurements can only be converted 30628 * to measurements of the same type.<p> 30629 * 30630 * The type of the units is determined automatically from the 30631 * units. For example, the unit "grams" is type "mass". Use the 30632 * static call {@link Measurement.getAvailableUnits} 30633 * to find out what units this version of ilib supports. 30634 * 30635 * @return {string} the name of the type of this measurement 30636 */ 30637 MassUnit.prototype.getMeasure = function() { 30638 return "mass"; 30639 }; 30640 30641 /** 30642 * Return a new measurement instance that is converted to a new 30643 * measurement unit. Measurements can only be converted 30644 * to measurements of the same type.<p> 30645 * 30646 * @param {string} to The name of the units to convert to 30647 * @return {Measurement|undefined} the converted measurement 30648 * or undefined if the requested units are for a different 30649 * measurement type 30650 */ 30651 MassUnit.prototype.convert = function(to) { 30652 if (!to || typeof(MassUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30653 return undefined; 30654 } 30655 return new MassUnit({ 30656 unit: to, 30657 amount: this 30658 }); 30659 }; 30660 30661 MassUnit.aliases = { 30662 "µg":"microgram", 30663 "microgram":"microgram", 30664 "mcg":"microgram", 30665 "milligram":"milligram", 30666 "mg":"milligram", 30667 "milligrams":"milligram", 30668 "Milligram":"milligram", 30669 "Milligrams":"milligram", 30670 "MilliGram":"milligram", 30671 "MilliGrams":"milligram", 30672 "g":"gram", 30673 "gram":"gram", 30674 "grams":"gram", 30675 "Gram":"gram", 30676 "Grams":"gram", 30677 "ounce":"ounce", 30678 "oz":"ounce", 30679 "Ounce":"ounce", 30680 "℥":"ounce", 30681 "pound":"pound", 30682 "poundm":"pound", 30683 "℔":"pound", 30684 "lb":"pound", 30685 "pounds":"pound", 30686 "Pound":"pound", 30687 "Pounds":"pound", 30688 "kilogram":"kilogram", 30689 "kg":"kilogram", 30690 "kilograms":"kilogram", 30691 "kilo grams":"kilogram", 30692 "kilo gram":"kilogram", 30693 "Kilogram":"kilogram", 30694 "Kilograms":"kilogram", 30695 "KiloGram":"kilogram", 30696 "KiloGrams":"kilogram", 30697 "Kilo gram":"kilogram", 30698 "Kilo grams":"kilogram", 30699 "Kilo Gram":"kilogram", 30700 "Kilo Grams":"kilogram", 30701 "stone":"stone", 30702 "st":"stone", 30703 "stones":"stone", 30704 "Stone":"stone", 30705 "short ton":"short ton", 30706 "Short ton":"short ton", 30707 "Short Ton":"short ton", 30708 "metric ton":"metric ton", 30709 "metricton":"metric ton", 30710 "t":"metric ton", 30711 "tonne":"metric ton", 30712 "Tonne":"metric ton", 30713 "Metric Ton":"metric ton", 30714 "MetricTon":"metric ton", 30715 "long ton":"long ton", 30716 "longton":"long ton", 30717 "Longton":"long ton", 30718 "Long ton":"long ton", 30719 "Long Ton":"long ton", 30720 "ton":"long ton", 30721 "Ton":"long ton" 30722 }; 30723 30724 /** 30725 * Convert a mass to another measure. 30726 * @static 30727 * @param to {string} unit to convert to 30728 * @param from {string} unit to convert from 30729 * @param mass {number} amount to be convert 30730 * @returns {number|undefined} the converted amount 30731 */ 30732 MassUnit.convert = function(to, from, mass) { 30733 from = MassUnit.aliases[from] || from; 30734 to = MassUnit.aliases[to] || to; 30735 var fromRow = MassUnit.ratios[from]; 30736 var toRow = MassUnit.ratios[to]; 30737 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 30738 return undefined; 30739 } 30740 return mass * fromRow[toRow[0]]; 30741 }; 30742 30743 /** 30744 * Scale the measurement unit to an acceptable level. The scaling 30745 * happens so that the integer part of the amount is as small as 30746 * possible without being below zero. This will result in the 30747 * largest units that can represent this measurement without 30748 * fractions. Measurements can only be scaled to other measurements 30749 * of the same type. 30750 * 30751 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30752 * or undefined if the system can be inferred from the current measure 30753 * @return {Measurement} a new instance that is scaled to the 30754 * right level 30755 */ 30756 MassUnit.prototype.scale = function(measurementsystem) { 30757 var mSystem; 30758 if (measurementsystem === "metric" || (typeof(measurementsystem) === 'undefined' 30759 && typeof(MassUnit.metricSystem[this.unit]) !== 'undefined')) { 30760 mSystem = MassUnit.metricSystem; 30761 } else if (measurementsystem === "imperial" || (typeof(measurementsystem) === 'undefined' 30762 && typeof(MassUnit.imperialSystem[this.unit]) !== 'undefined')) { 30763 mSystem = MassUnit.imperialSystem; 30764 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 30765 && typeof(MassUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 30766 mSystem = MassUnit.uscustomarySystem; 30767 } else { 30768 return new MassUnit({ 30769 unit: this.unit, 30770 amount: this.amount 30771 }); 30772 } 30773 30774 var mass = this.amount; 30775 var munit = this.amount; 30776 var fromRow = MassUnit.ratios[this.unit]; 30777 30778 mass = 18446744073709551999; 30779 30780 for (var m in mSystem) { 30781 var tmp = this.amount * fromRow[mSystem[m]]; 30782 if (tmp >= 1 && tmp < mass) { 30783 mass = tmp; 30784 munit = m; 30785 } 30786 } 30787 30788 return new MassUnit({ 30789 unit: munit, 30790 amount: mass 30791 }); 30792 }; 30793 30794 /** 30795 * @private 30796 * @static 30797 */ 30798 MassUnit.getMeasures = function () { 30799 var ret = []; 30800 for (var m in MassUnit.ratios) { 30801 ret.push(m); 30802 } 30803 return ret; 30804 }; 30805 30806 //register with the factory method 30807 Measurement._constructors["mass"] = MassUnit; 30808 30809 30810 /*< TemperatureUnit.js */ 30811 /* 30812 * temperature.js - Unit conversions for Temperature/temperature 30813 * 30814 * Copyright © 2014-2015, JEDLSoft 30815 * 30816 * Licensed under the Apache License, Version 2.0 (the "License"); 30817 * you may not use this file except in compliance with the License. 30818 * You may obtain a copy of the License at 30819 * 30820 * http://www.apache.org/licenses/LICENSE-2.0 30821 * 30822 * Unless required by applicable law or agreed to in writing, software 30823 * distributed under the License is distributed on an "AS IS" BASIS, 30824 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30825 * 30826 * See the License for the specific language governing permissions and 30827 * limitations under the License. 30828 */ 30829 30830 /* 30831 !depends 30832 Measurement.js 30833 */ 30834 30835 30836 /** 30837 * @class 30838 * Create a new Temperature measurement instance. 30839 * 30840 * @constructor 30841 * @extends Measurement 30842 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30843 * the construction of this instance 30844 */ 30845 var TemperatureUnit = function (options) { 30846 this.unit = "celsius"; 30847 this.amount = 0; 30848 this.aliases = TemperatureUnit.aliases; // share this table in all instances 30849 30850 if (options) { 30851 if (typeof(options.unit) !== 'undefined') { 30852 this.originalUnit = options.unit; 30853 this.unit = this.aliases[options.unit] || options.unit; 30854 } 30855 30856 if (typeof(options.amount) === 'object') { 30857 if (options.amount.getMeasure() === "temperature") { 30858 this.amount = TemperatureUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30859 } else { 30860 throw "Cannot convert unit " + options.amount.unit + " to a temperature"; 30861 } 30862 } else if (typeof(options.amount) !== 'undefined') { 30863 this.amount = parseFloat(options.amount); 30864 } 30865 } 30866 }; 30867 30868 TemperatureUnit.prototype = new Measurement(); 30869 TemperatureUnit.prototype.parent = Measurement; 30870 TemperatureUnit.prototype.constructor = TemperatureUnit; 30871 30872 /** 30873 * Return the type of this measurement. Examples are "mass", 30874 * "length", "speed", etc. Measurements can only be converted 30875 * to measurements of the same type.<p> 30876 * 30877 * The type of the units is determined automatically from the 30878 * units. For example, the unit "grams" is type "mass". Use the 30879 * static call {@link Measurement.getAvailableUnits} 30880 * to find out what units this version of ilib supports. 30881 * 30882 * @return {string} the name of the type of this measurement 30883 */ 30884 TemperatureUnit.prototype.getMeasure = function() { 30885 return "temperature"; 30886 }; 30887 30888 TemperatureUnit.aliases = { 30889 "Celsius": "celsius", 30890 "celsius": "celsius", 30891 "C": "celsius", 30892 "centegrade": "celsius", 30893 "Centegrade": "celsius", 30894 "centigrade": "celsius", 30895 "Centigrade": "celsius", 30896 "fahrenheit": "fahrenheit", 30897 "Fahrenheit": "fahrenheit", 30898 "F": "fahrenheit", 30899 "kelvin": "kelvin", 30900 "K": "kelvin", 30901 "Kelvin": "kelvin", 30902 "°F": "fahrenheit", 30903 "℉": "fahrenheit", 30904 "℃": "celsius", 30905 "°C": "celsius" 30906 }; 30907 30908 /** 30909 * Return a new measurement instance that is converted to a new 30910 * measurement unit. Measurements can only be converted 30911 * to measurements of the same type.<p> 30912 * 30913 * @param {string} to The name of the units to convert to 30914 * @return {Measurement|undefined} the converted measurement 30915 * or undefined if the requested units are for a different 30916 * measurement type 30917 */ 30918 TemperatureUnit.prototype.convert = function(to) { 30919 if (!to || typeof(TemperatureUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30920 return undefined; 30921 } 30922 return new TemperatureUnit({ 30923 unit: to, 30924 amount: this 30925 }); 30926 }; 30927 30928 /** 30929 * Convert a temperature to another measure. 30930 * @static 30931 * @param to {string} unit to convert to 30932 * @param from {string} unit to convert from 30933 * @param temperature {number} amount to be convert 30934 * @returns {number|undefined} the converted amount 30935 */ 30936 TemperatureUnit.convert = function(to, from, temperature) { 30937 var result = 0; 30938 from = TemperatureUnit.aliases[from] || from; 30939 to = TemperatureUnit.aliases[to] || to; 30940 if (from === to) 30941 return temperature; 30942 30943 else if (from === "celsius") { 30944 if (to === "fahrenheit") { 30945 result = ((temperature * 9 / 5) + 32); 30946 } else if (to === "kelvin") { 30947 result = (temperature + 273.15); 30948 } 30949 30950 } else if (from === "fahrenheit") { 30951 if (to === "celsius") { 30952 result = ((5 / 9 * (temperature - 32))); 30953 } else if (to === "kelvin") { 30954 result = ((temperature + 459.67) * 5 / 9); 30955 } 30956 } else if (from === "kelvin") { 30957 if (to === "celsius") { 30958 result = (temperature - 273.15); 30959 } else if (to === "fahrenheit") { 30960 result = ((temperature * 9 / 5) - 459.67); 30961 } 30962 } 30963 30964 return result; 30965 }; 30966 30967 /** 30968 * Scale the measurement unit to an acceptable level. The scaling 30969 * happens so that the integer part of the amount is as small as 30970 * possible without being below zero. This will result in the 30971 * largest units that can represent this measurement without 30972 * fractions. Measurements can only be scaled to other measurements 30973 * of the same type. 30974 * 30975 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30976 * or undefined if the system can be inferred from the current measure 30977 * @return {Measurement} a new instance that is scaled to the 30978 * right level 30979 */ 30980 TemperatureUnit.prototype.scale = function(measurementsystem) { 30981 return new TemperatureUnit({ 30982 unit: this.unit, 30983 amount: this.amount 30984 }); 30985 }; 30986 30987 /** 30988 * @private 30989 * @static 30990 */ 30991 TemperatureUnit.getMeasures = function () { 30992 return ["celsius", "kelvin", "fahrenheit"]; 30993 }; 30994 TemperatureUnit.metricToUScustomary = { 30995 "celsius": "fahrenheit" 30996 }; 30997 TemperatureUnit.usCustomaryToMetric = { 30998 "fahrenheit": "celsius" 30999 }; 31000 31001 /** 31002 * Localize the measurement to the commonly used measurement in that locale. For example 31003 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31004 * the formatted number should be automatically converted to the most appropriate 31005 * measure in the other system, in this case, mph. The formatted result should 31006 * appear as "37.3 mph". 31007 * 31008 * @param {string} locale current locale string 31009 * @returns {Measurement} a new instance that is converted to locale 31010 */ 31011 TemperatureUnit.prototype.localize = function(locale) { 31012 var to; 31013 if (locale === "en-US" ) { 31014 to = TemperatureUnit.metricToUScustomary[this.unit] || this.unit; 31015 } else { 31016 to = TemperatureUnit.usCustomaryToMetric[this.unit] || this.unit; 31017 } 31018 return new TemperatureUnit({ 31019 unit: to, 31020 amount: this 31021 }); 31022 }; 31023 //register with the factory method 31024 Measurement._constructors["temperature"] = TemperatureUnit; 31025 31026 31027 /*< TimeUnit.js */ 31028 /* 31029 * Time.js - Unit conversions for Times/times 31030 * 31031 * Copyright © 2014-2015, JEDLSoft 31032 * 31033 * Licensed under the Apache License, Version 2.0 (the "License"); 31034 * you may not use this file except in compliance with the License. 31035 * You may obtain a copy of the License at 31036 * 31037 * http://www.apache.org/licenses/LICENSE-2.0 31038 * 31039 * Unless required by applicable law or agreed to in writing, software 31040 * distributed under the License is distributed on an "AS IS" BASIS, 31041 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31042 * 31043 * See the License for the specific language governing permissions and 31044 * limitations under the License. 31045 */ 31046 31047 /* 31048 !depends 31049 Measurement.js 31050 */ 31051 31052 31053 /** 31054 * @class 31055 * Create a new time measurement instance. 31056 * 31057 * @constructor 31058 * @extends Measurement 31059 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 31060 * the construction of this instance 31061 */ 31062 var TimeUnit = function (options) { 31063 this.unit = "second"; 31064 this.amount = 0; 31065 this.aliases = TimeUnit.aliases; // share this table in all instances 31066 31067 if (options) { 31068 if (typeof(options.unit) !== 'undefined') { 31069 this.originalUnit = options.unit; 31070 this.unit = this.aliases[options.unit] || options.unit; 31071 } 31072 31073 if (typeof(options.amount) === 'object') { 31074 if (options.amount.getMeasure() === "time") { 31075 this.amount = TimeUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 31076 } else { 31077 throw "Cannot convert units " + options.amount.unit + " to a time"; 31078 } 31079 } else if (typeof(options.amount) !== 'undefined') { 31080 this.amount = parseFloat(options.amount); 31081 } 31082 } 31083 31084 if (typeof(TimeUnit.ratios[this.unit]) === 'undefined') { 31085 throw "Unknown unit: " + options.unit; 31086 } 31087 }; 31088 31089 TimeUnit.prototype = new Measurement(); 31090 TimeUnit.prototype.parent = Measurement; 31091 TimeUnit.prototype.constructor = TimeUnit; 31092 31093 TimeUnit.ratios = { 31094 /* index nsec msec mlsec sec min hour day week month year decade century */ 31095 "nanosecond": [ 1, 1, 0.001, 1e-6, 1e-9, 1.6667e-11, 2.7778e-13, 1.1574e-14, 1.6534e-15, 3.8027e-16, 3.1689e-17, 3.1689e-18, 3.1689e-19 ], 31096 "microsecond": [ 2, 1000, 1, 0.001, 1e-6, 1.6667e-8, 2.7778e-10, 1.1574e-11, 1.6534e-12, 3.8027e-13, 3.1689e-14, 3.1689e-15, 3.1689e-16 ], 31097 "millisecond": [ 3, 1e+6, 1000, 1, 0.001, 1.6667e-5, 2.7778e-7, 1.1574e-8, 1.6534e-9, 3.8027e-10, 3.1689e-11, 3.1689e-12, 3.1689e-13 ], 31098 "second": [ 4, 1e+9, 1e+6, 1000, 1, 0.0166667, 0.000277778, 1.1574e-5, 1.6534e-6, 3.8027e-7, 3.1689e-8, 3.1689e-9, 3.1689e-10 ], 31099 "minute": [ 5, 6e+10, 6e+7, 60000, 60, 1, 0.0166667, 0.000694444, 9.9206e-5, 2.2816e-5, 1.9013e-6, 1.9013e-7, 1.9013e-8 ], 31100 "hour": [ 6, 3.6e+12, 3.6e+9, 3.6e+6, 3600, 60, 1, 0.0416667, 0.00595238, 0.00136895, 0.00011408, 1.1408e-5, 1.1408e-6 ], 31101 "day": [ 7, 8.64e+13, 8.64e+10, 8.64e+7, 86400, 1440, 24, 1, 0.142857, 0.0328549, 0.00273791, 0.000273791, 2.7379e-5 ], 31102 "week": [ 8, 6.048e+14, 6.048e+11, 6.048e+8, 604800, 10080, 168, 7, 1, 0.229984, 0.0191654, 0.00191654, 0.000191654 ], 31103 "month": [ 9, 2.63e+15, 2.63e+12, 2.63e+9, 2.63e+6, 43829.1, 730.484, 30.4368, 4.34812, 1, 0.0833333, 0.00833333, 0.000833333 ], 31104 "year": [ 10, 3.156e+16, 3.156e+13, 3.156e+10, 3.156e+7, 525949, 8765.81, 365.242, 52.1775, 12, 1, 0.1, 0.01 ], 31105 "decade": [ 11, 3.156e+17, 3.156e+14, 3.156e+11, 3.156e+8, 5.259e+6, 87658.1, 3652.42, 521.775, 120, 10, 1, 0.1 ], 31106 "century": [ 12, 3.156e+18, 3.156e+18, 3.156e+12, 3.156e+9, 5.259e+7, 876581, 36524.2, 5217.75, 1200, 100, 10, 1 ] 31107 }; 31108 31109 /** 31110 * Return the type of this measurement. Examples are "mass", 31111 * "length", "speed", etc. Measurements can only be converted 31112 * to measurements of the same type.<p> 31113 * 31114 * The type of the units is determined automatically from the 31115 * units. For example, the unit "grams" is type "mass". Use the 31116 * static call {@link Measurement.getAvailableUnits} 31117 * to find out what units this version of ilib supports. 31118 * 31119 * @return {string} the name of the type of this measurement 31120 */ 31121 TimeUnit.prototype.getMeasure = function() { 31122 return "time"; 31123 }; 31124 31125 /** 31126 * Return a new measurement instance that is converted to a new 31127 * measurement unit. Measurements can only be converted 31128 * to measurements of the same type.<p> 31129 * 31130 * @param {string} to The name of the units to convert to 31131 * @return {Measurement|undefined} the converted measurement 31132 * or undefined if the requested units are for a different 31133 * measurement type 31134 */ 31135 TimeUnit.prototype.convert = function(to) { 31136 if (!to || typeof(TimeUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 31137 return undefined; 31138 } 31139 return new TimeUnit({ 31140 unit: to, 31141 amount: this 31142 }); 31143 }; 31144 31145 TimeUnit.aliases = { 31146 "ns": "nanosecond", 31147 "NS": "nanosecond", 31148 "nS": "nanosecond", 31149 "Ns": "nanosecond", 31150 "Nanosecond": "nanosecond", 31151 "Nanoseconds": "nanosecond", 31152 "nanosecond": "nanosecond", 31153 "nanoseconds": "nanosecond", 31154 "NanoSecond": "nanosecond", 31155 "NanoSeconds": "nanosecond", 31156 "μs": "microsecond", 31157 "μS": "microsecond", 31158 "microsecond": "microsecond", 31159 "microseconds": "microsecond", 31160 "Microsecond": "microsecond", 31161 "Microseconds": "microsecond", 31162 "MicroSecond": "microsecond", 31163 "MicroSeconds": "microsecond", 31164 "ms": "millisecond", 31165 "MS": "millisecond", 31166 "mS": "millisecond", 31167 "Ms": "millisecond", 31168 "millisecond": "millisecond", 31169 "milliseconds": "millisecond", 31170 "Millisecond": "millisecond", 31171 "Milliseconds": "millisecond", 31172 "MilliSecond": "millisecond", 31173 "MilliSeconds": "millisecond", 31174 "s": "second", 31175 "S": "second", 31176 "sec": "second", 31177 "second": "second", 31178 "seconds": "second", 31179 "Second": "second", 31180 "Seconds": "second", 31181 "min": "minute", 31182 "Min": "minute", 31183 "minute": "minute", 31184 "minutes": "minute", 31185 "Minute": "minute", 31186 "Minutes": "minute", 31187 "h": "hour", 31188 "H": "hour", 31189 "hr": "hour", 31190 "Hr": "hour", 31191 "hR": "hour", 31192 "HR": "hour", 31193 "hour": "hour", 31194 "hours": "hour", 31195 "Hour": "hour", 31196 "Hours": "hour", 31197 "Hrs": "hour", 31198 "hrs": "hour", 31199 "day": "day", 31200 "days": "day", 31201 "Day": "day", 31202 "Days": "day", 31203 "week": "week", 31204 "weeks": "week", 31205 "Week": "week", 31206 "Weeks": "week", 31207 "month": "month", 31208 "Month": "month", 31209 "months": "month", 31210 "Months": "month", 31211 "year": "year", 31212 "years": "year", 31213 "Year": "year", 31214 "Years": "year", 31215 "yr": "year", 31216 "Yr": "year", 31217 "yrs": "year", 31218 "Yrs": "year", 31219 "decade": "decade", 31220 "decades": "decade", 31221 "Decade": "decade", 31222 "Decades": "decade", 31223 "century": "century", 31224 "centuries": "century", 31225 "Century": "century", 31226 "Centuries": "century" 31227 }; 31228 31229 /** 31230 * Convert a time to another measure. 31231 * @static 31232 * @param to {string} unit to convert to 31233 * @param from {string} unit to convert from 31234 * @param time {number} amount to be convert 31235 * @returns {number|undefined} the converted amount 31236 */ 31237 TimeUnit.convert = function(to, from, time) { 31238 from = TimeUnit.aliases[from] || from; 31239 to = TimeUnit.aliases[to] || to; 31240 var fromRow = TimeUnit.ratios[from]; 31241 var toRow = TimeUnit.ratios[to]; 31242 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31243 return undefined; 31244 } 31245 return time * fromRow[toRow[0]]; 31246 }; 31247 31248 /** 31249 * Localize the measurement to the commonly used measurement in that locale. For example 31250 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31251 * the formatted number should be automatically converted to the most appropriate 31252 * measure in the other system, in this case, mph. The formatted result should 31253 * appear as "37.3 mph". 31254 * 31255 * @param {string} locale current locale string 31256 * @returns {Measurement} a new instance that is converted to locale 31257 */ 31258 TimeUnit.prototype.localize = function(locale) { 31259 return new TimeUnit({ 31260 unit: this.unit, 31261 amount: this.amount 31262 }); 31263 }; 31264 31265 /** 31266 * Scale the measurement unit to an acceptable level. The scaling 31267 * happens so that the integer part of the amount is as small as 31268 * possible without being below zero. This will result in the 31269 * largest units that can represent this measurement without 31270 * fractions. Measurements can only be scaled to other measurements 31271 * of the same type. 31272 * 31273 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31274 * or undefined if the system can be inferred from the current measure 31275 * @return {Measurement} a new instance that is scaled to the 31276 * right level 31277 */ 31278 TimeUnit.prototype.scale = function(measurementsystem) { 31279 31280 var fromRow = TimeUnit.ratios[this.unit]; 31281 var time = this.amount; 31282 var munit = this.unit; 31283 var i; 31284 31285 time = 18446744073709551999; 31286 for (var m in TimeUnit.ratios) { 31287 i = TimeUnit.ratios[m][0]; 31288 var tmp = this.amount * fromRow[i]; 31289 if (tmp >= 1 && tmp < time) { 31290 time = tmp; 31291 munit = m; 31292 } 31293 } 31294 31295 return new TimeUnit({ 31296 unit: munit, 31297 amount: time 31298 }); 31299 }; 31300 /** 31301 * @private 31302 * @static 31303 */ 31304 TimeUnit.getMeasures = function () { 31305 var ret = []; 31306 for (var m in TimeUnit.ratios) { 31307 ret.push(m); 31308 } 31309 return ret; 31310 }; 31311 31312 //register with the factory method 31313 Measurement._constructors["time"] = TimeUnit; 31314 31315 31316 /*< VelocityUnit.js */ 31317 /* 31318 * VelocityUnit.js - Unit conversions for VelocityUnits/speeds 31319 * 31320 * Copyright © 2014-2015, JEDLSoft 31321 * 31322 * Licensed under the Apache License, Version 2.0 (the "License"); 31323 * you may not use this file except in compliance with the License. 31324 * You may obtain a copy of the License at 31325 * 31326 * http://www.apache.org/licenses/LICENSE-2.0 31327 * 31328 * Unless required by applicable law or agreed to in writing, software 31329 * distributed under the License is distributed on an "AS IS" BASIS, 31330 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31331 * 31332 * See the License for the specific language governing permissions and 31333 * limitations under the License. 31334 */ 31335 31336 /* 31337 !depends 31338 Measurement.js 31339 */ 31340 31341 31342 /** 31343 * @class 31344 * Create a new speed measurement instance. 31345 * 31346 * @constructor 31347 * @extends Measurement 31348 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 31349 * the construction of this instance 31350 */ 31351 var VelocityUnit = function (options) { 31352 this.unit = "meters/second"; 31353 this.amount = 0; 31354 this.aliases = VelocityUnit.aliases; // share this table in all instances 31355 31356 if (options) { 31357 if (typeof(options.unit) !== 'undefined') { 31358 this.originalUnit = options.unit; 31359 this.unit = this.aliases[options.unit] || options.unit; 31360 } 31361 31362 if (typeof(options.amount) === 'object') { 31363 if (options.amount.getMeasure() === "speed") { 31364 this.amount = VelocityUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 31365 } else { 31366 throw "Cannot convert units " + options.amount.unit + " to a speed"; 31367 } 31368 } else if (typeof(options.amount) !== 'undefined') { 31369 this.amount = parseFloat(options.amount); 31370 } 31371 } 31372 31373 if (typeof(VelocityUnit.ratios[this.unit]) === 'undefined') { 31374 throw "Unknown unit: " + options.unit; 31375 } 31376 }; 31377 31378 VelocityUnit.prototype = new Measurement(); 31379 VelocityUnit.prototype.parent = Measurement; 31380 VelocityUnit.prototype.constructor = VelocityUnit; 31381 31382 VelocityUnit.ratios = { 31383 /* index, k/h f/s miles/h knot m/s km/s miles/s */ 31384 "kilometer/hour": [ 1, 1, 0.911344, 0.621371, 0.539957, 0.277778, 2.77778e-4, 1.72603109e-4 ], 31385 "feet/second": [ 2, 1.09728, 1, 0.681818, 0.592484, 0.3048, 3.048e-4, 1.89393939e-4 ], 31386 "miles/hour": [ 3, 1.60934, 1.46667, 1, 0.868976, 0.44704, 4.4704e-4, 2.77777778e-4 ], 31387 "knot": [ 4, 1.852, 1.68781, 1.15078, 1, 0.514444, 5.14444e-4, 3.19660958e-4 ], 31388 "meters/second": [ 5, 3.6, 3.28084, 2.236936, 1.94384, 1, 0.001, 6.21371192e-4 ], 31389 "kilometer/second": [ 6, 3600, 3280.8399, 2236.93629, 1943.84449, 1000, 1, 0.621371192 ], 31390 "miles/second": [ 7, 5793.6384, 5280, 3600, 3128.31447, 1609.344, 1.609344, 1 ] 31391 }; 31392 31393 VelocityUnit.metricSystem = { 31394 "kilometer/hour": 1, 31395 "meters/second": 5, 31396 "kilometer/second": 6 31397 }; 31398 VelocityUnit.imperialSystem = { 31399 "feet/second": 2, 31400 "miles/hour": 3, 31401 "knot": 4, 31402 "miles/second": 7 31403 }; 31404 VelocityUnit.uscustomarySystem = { 31405 "feet/second": 2, 31406 "miles/hour": 3, 31407 "knot": 4, 31408 "miles/second": 7 31409 }; 31410 31411 VelocityUnit.metricToUScustomary = { 31412 "kilometer/hour": "miles/hour", 31413 "meters/second": "feet/second", 31414 "kilometer/second": "miles/second" 31415 }; 31416 VelocityUnit.UScustomaryTometric = { 31417 "miles/hour": "kilometer/hour", 31418 "feet/second": "meters/second", 31419 "miles/second": "kilometer/second", 31420 "knot": "kilometer/hour" 31421 }; 31422 31423 /** 31424 * Return the type of this measurement. Examples are "mass", 31425 * "length", "speed", etc. Measurements can only be converted 31426 * to measurements of the same type.<p> 31427 * 31428 * The type of the units is determined automatically from the 31429 * units. For example, the unit "grams" is type "mass". Use the 31430 * static call {@link Measurement.getAvailableUnits} 31431 * to find out what units this version of ilib supports. 31432 * 31433 * @return {string} the name of the type of this measurement 31434 */ 31435 VelocityUnit.prototype.getMeasure = function() { 31436 return "speed"; 31437 }; 31438 31439 /** 31440 * Return a new measurement instance that is converted to a new 31441 * measurement unit. Measurements can only be converted 31442 * to measurements of the same type.<p> 31443 * 31444 * @param {string} to The name of the units to convert to 31445 * @return {Measurement|undefined} the converted measurement 31446 * or undefined if the requested units are for a different 31447 * measurement type 31448 */ 31449 VelocityUnit.prototype.convert = function(to) { 31450 if (!to || typeof(VelocityUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 31451 return undefined; 31452 } 31453 return new VelocityUnit({ 31454 unit: to, 31455 amount: this 31456 }); 31457 }; 31458 31459 /** 31460 * Scale the measurement unit to an acceptable level. The scaling 31461 * happens so that the integer part of the amount is as small as 31462 * possible without being below zero. This will result in the 31463 * largest units that can represent this measurement without 31464 * fractions. Measurements can only be scaled to other measurements 31465 * of the same type. 31466 * 31467 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31468 * or undefined if the system can be inferred from the current measure 31469 * @return {Measurement} a new instance that is scaled to the 31470 * right level 31471 */ 31472 VelocityUnit.prototype.scale = function(measurementsystem) { 31473 var mSystem; 31474 if (measurementsystem === "metric" || 31475 (typeof (measurementsystem) === 'undefined' && typeof (VelocityUnit.metricSystem[this.unit]) !== 'undefined')) { 31476 mSystem = VelocityUnit.metricSystem; 31477 } else if (measurementsystem === "imperial" || 31478 (typeof (measurementsystem) === 'undefined' && typeof (VelocityUnit.imperialSystem[this.unit]) !== 'undefined')) { 31479 mSystem = VelocityUnit.imperialSystem; 31480 } else if (measurementsystem === "uscustomary" || 31481 (typeof (measurementsystem) === 'undefined' && typeof (VelocityUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 31482 mSystem = VelocityUnit.uscustomarySystem; 31483 } else { 31484 return new VelocityUnit({ 31485 unit: this.unit, 31486 amount: this.amount 31487 }); 31488 } 31489 31490 var speed = this.amount; 31491 var munit = this.unit; 31492 var fromRow = VelocityUnit.ratios[this.unit]; 31493 31494 speed = 18446744073709551999; 31495 31496 for ( var m in mSystem) { 31497 var tmp = this.amount * fromRow[mSystem[m]]; 31498 if (tmp >= 1 && tmp < speed) { 31499 speed = tmp; 31500 munit = m; 31501 } 31502 } 31503 31504 return new VelocityUnit({ 31505 unit: munit, 31506 amount: speed 31507 }); 31508 }; 31509 31510 /** 31511 * Localize the measurement to the commonly used measurement in that locale. For example 31512 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31513 * the formatted number should be automatically converted to the most appropriate 31514 * measure in the other system, in this case, mph. The formatted result should 31515 * appear as "37.3 mph". 31516 * 31517 * @param {string} locale current locale string 31518 * @returns {Measurement} a new instance that is converted to locale 31519 */ 31520 VelocityUnit.prototype.localize = function(locale) { 31521 var to; 31522 if (locale === "en-US" || locale === "en-GB") { 31523 to = VelocityUnit.metricToUScustomary[this.unit] || this.unit; 31524 } else { 31525 to = VelocityUnit.UScustomaryTometric[this.unit] || this.unit; 31526 } 31527 return new VelocityUnit({ 31528 unit: to, 31529 amount: this 31530 }); 31531 }; 31532 31533 VelocityUnit.aliases = { 31534 "foot/sec": "feet/second", 31535 "foot/s": "feet/second", 31536 "feet/s": "feet/second", 31537 "f/s": "feet/second", 31538 "feet/second": "feet/second", 31539 "feet/sec": "feet/second", 31540 "meter/sec": "meters/second", 31541 "meter/s": "meters/second", 31542 "meters/s": "meters/second", 31543 "metre/sec": "meters/second", 31544 "metre/s": "meters/second", 31545 "metres/s": "meters/second", 31546 "mt/sec": "meters/second", 31547 "m/sec": "meters/second", 31548 "mt/s": "meters/second", 31549 "m/s": "meters/second", 31550 "mps": "meters/second", 31551 "meters/second": "meters/second", 31552 "meters/sec": "meters/second", 31553 "kilometer/hour": "kilometer/hour", 31554 "km/hour": "kilometer/hour", 31555 "kilometers/hour": "kilometer/hour", 31556 "kmh": "kilometer/hour", 31557 "km/h": "kilometer/hour", 31558 "kilometer/h": "kilometer/hour", 31559 "kilometers/h": "kilometer/hour", 31560 "km/hr": "kilometer/hour", 31561 "kilometer/hr": "kilometer/hour", 31562 "kilometers/hr": "kilometer/hour", 31563 "kilometre/hour": "kilometer/hour", 31564 "mph": "miles/hour", 31565 "mile/hour": "miles/hour", 31566 "mile/hr": "miles/hour", 31567 "mile/h": "miles/hour", 31568 "miles/h": "miles/hour", 31569 "miles/hr": "miles/hour", 31570 "miles/hour": "miles/hour", 31571 "kn": "knot", 31572 "kt": "knot", 31573 "kts": "knot", 31574 "knots": "knot", 31575 "nm/h": "knot", 31576 "nm/hr": "knot", 31577 "nauticalmile/h": "knot", 31578 "nauticalmile/hr": "knot", 31579 "nauticalmile/hour": "knot", 31580 "nauticalmiles/hr": "knot", 31581 "nauticalmiles/hour": "knot", 31582 "knot": "knot", 31583 "kilometer/second": "kilometer/second", 31584 "kilometer/sec": "kilometer/second", 31585 "kilometre/sec": "kilometer/second", 31586 "Kilometre/sec": "kilometer/second", 31587 "kilometers/second": "kilometer/second", 31588 "kilometers/sec": "kilometer/second", 31589 "kilometres/sec": "kilometer/second", 31590 "Kilometres/sec": "kilometer/second", 31591 "km/sec": "kilometer/second", 31592 "Km/s": "kilometer/second", 31593 "km/s": "kilometer/second", 31594 "miles/second": "miles/second", 31595 "miles/sec": "miles/second", 31596 "miles/s": "miles/second", 31597 "mile/s": "miles/second", 31598 "mile/sec": "miles/second", 31599 "Mile/s": "miles/second" 31600 }; 31601 31602 /** 31603 * Convert a speed to another measure. 31604 * @static 31605 * @param to {string} unit to convert to 31606 * @param from {string} unit to convert from 31607 * @param speed {number} amount to be convert 31608 * @returns {number|undefined} the converted amount 31609 */ 31610 VelocityUnit.convert = function(to, from, speed) { 31611 from = VelocityUnit.aliases[from] || from; 31612 to = VelocityUnit.aliases[to] || to; 31613 var fromRow = VelocityUnit.ratios[from]; 31614 var toRow = VelocityUnit.ratios[to]; 31615 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31616 return undefined; 31617 } 31618 var result = speed * fromRow[toRow[0]]; 31619 return result; 31620 }; 31621 31622 /** 31623 * @private 31624 * @static 31625 */ 31626 VelocityUnit.getMeasures = function () { 31627 var ret = []; 31628 for (var m in VelocityUnit.ratios) { 31629 ret.push(m); 31630 } 31631 return ret; 31632 }; 31633 31634 //register with the factory method 31635 Measurement._constructors["speed"] = VelocityUnit; 31636 Measurement._constructors["velocity"] = VelocityUnit; 31637 31638 31639 /*< VolumeUnit.js */ 31640 /* 31641 * volume.js - Unit conversions for volume 31642 * 31643 * Copyright © 2014-2015, JEDLSoft 31644 * 31645 * Licensed under the Apache License, Version 2.0 (the "License"); 31646 * you may not use this file except in compliance with the License. 31647 * You may obtain a copy of the License at 31648 * 31649 * http://www.apache.org/licenses/LICENSE-2.0 31650 * 31651 * 31652 * Unless required by applicable law or agreed to in writing, software 31653 * distributed under the License is distributed on an "AS IS" BASIS, 31654 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31655 * 31656 * See the License for the specific language governing permissions and 31657 * limitations under the License. 31658 */ 31659 31660 /* 31661 !depends 31662 Measurement.js 31663 */ 31664 31665 31666 /** 31667 * @class 31668 * Create a new Volume measurement instance. 31669 * 31670 * @constructor 31671 * @extends Measurement 31672 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 31673 * the construction of this instance 31674 */ 31675 var VolumeUnit = function (options) { 31676 this.unit = "cubic meter"; 31677 this.amount = 0; 31678 this.aliases = VolumeUnit.aliases; // share this table in all instances 31679 31680 if (options) { 31681 if (typeof(options.unit) !== 'undefined') { 31682 this.originalUnit = options.unit; 31683 this.unit = this.aliases[options.unit] || options.unit; 31684 } 31685 31686 if (typeof(options.amount) === 'object') { 31687 if (options.amount.getMeasure() === "volume") { 31688 this.amount = VolumeUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 31689 } else { 31690 throw "Cannot convert unit " + options.amount.unit + " to a volume"; 31691 } 31692 } else if (typeof(options.amount) !== 'undefined') { 31693 this.amount = parseFloat(options.amount); 31694 } 31695 } 31696 31697 if (typeof(VolumeUnit.ratios[this.unit]) === 'undefined') { 31698 throw "Unknown unit: " + options.unit; 31699 } 31700 }; 31701 31702 VolumeUnit.prototype = new Measurement(); 31703 VolumeUnit.prototype.parent = Measurement; 31704 VolumeUnit.prototype.constructor = VolumeUnit; 31705 31706 VolumeUnit.ratios = { 31707 /* index, tsp, tbsp, cubic inch us ounce, cup, pint, quart, gallon, cubic foot, milliliter liter, cubic meter, imperial tsp, imperial tbsp, imperial ounce, imperial pint, imperial quart, imperial gal, */ 31708 "tsp" : [1, 1, 0.333333, 0.300781, 0.166667, 0.0208333, 0.0104167, 0.00520833, 0.00130208, 0.000174063, 4.92892, 0.00492892, 4.9289e-6, 0.832674, 0.277558, 0.173474, 0.00867369, 0.00433684, 0.00108421 ], 31709 "tbsp": [2, 3, 1, 0.902344, 0.5, 0.0625, 0.0312, 0.015625, 0.00390625, 0.00052219, 14.7868, 0.0147868, 1.4787e-5, 2.49802, 0.832674, 0.520421, 0.0260211, 0.0130105, 0.00325263 ], 31710 "cubic inch": [3, 3.32468, 1.10823, 1, 0.554113, 0.0692641, 0.034632, 0.017316, 0.004329, 0.000578704, 16.3871, 0.0163871, 1.6387e-5, 2.76837, 0.92279, 0.576744, 0.0288372, 0.0144186, 0.00360465 ], 31711 "us ounce": [4, 6, 2, 1.80469, 1, 0.125, 0.0625, 0.0078125, 0.0078125, 0.00104438, 29.5735, 0.0295735, 2.9574e-5, 4.99604, 1.04084, 1.04084, 0.0520421, 0.0260211, 0.00650526 ], 31712 "cup": [5, 48, 16, 14.4375, 8, 1, 0.5, 0.25, 0.0625, 0.00835503, 236.588, 0.236588, 0.000236588, 39.9683, 13.3228, 8.32674, 0.416337, 0.208168, 0.0520421 ], 31713 "pint": [6, 96, 32, 28.875, 16, 2, 1, 0.5, 0.125, 0.0167101, 473.176, 0.473176, 0.000473176, 79.9367, 26.6456, 16.6535, 0.832674, 0.416337, 0.104084 ], 31714 "quart": [7, 192, 64, 57.75, 32, 4, 2, 1, 0.25, 0.0334201, 946.353, 0.946353, 0.000946353, 159.873, 53.2911, 33.307, 1.66535, 0.832674, 0.208168 ], 31715 "gallon": [8, 768, 256, 231, 128, 16, 8, 4, 1, 0.133681, 3785.41, 3.78541, 0.00378541, 639.494, 213.165, 133.228, 6.66139, 3.3307, 0.832674 ], 31716 "cubic foot": [9, 5745.04, 1915.01, 1728, 957.506, 119.688, 59.8442, 29.9221, 7.48052, 1, 28316.8, 28.3168, 0.0283168, 4783.74, 1594.58, 996.613, 49.8307, 24.9153, 6.22883 ], 31717 "milliliter": [10, 0.202884, 0.067628, 0.0610237, 0.033814, 0.00422675, 0.00211338, 0.00105669, 0.000264172, 3.5315e-5, 1, 0.001, 1e-6, 0.168936, 0.0563121, 0.0351951, 0.00175975, 0.000879877, 0.000219969 ], 31718 "liter": [11, 202.884, 67.628, 61.0237, 33.814, 4.22675, 2.11338, 1.05669, 0.264172, 0.0353147, 1000, 1, 0.001, 56.3121, 56.3121, 35.191, 1.75975, 0.879877, 0.219969 ], 31719 "cubic meter": [12, 202884, 67628, 61023.7, 33814, 4226.75, 2113.38, 1056.69, 264.172, 35.3147, 1e+6, 1000, 1, 168936, 56312.1, 35195.1, 1759.75, 879.877, 219.969 ], 31720 "imperial tsp": [13, 1.20095, 0.200158, 0.361223, 0.600475, 0.0250198, 0.0125099, 0.00625495, 0.00156374, 0.000209041, 5.91939, 0.00591939, 5.9194e-6, 1, 0.333333, 0.208333, 0.0104167, 0.00520833, 0.00130208 ], 31721 "imperial tbsp": [14, 3.60285, 1.20095, 1.08367, 0.600475, 0.0750594, 0.0375297, 0.0187649, 0.00469121, 0.000627124, 17.7582, 0.0177582, 1.7758e-5, 3, 1, 0.625, 0.03125, 0.015625, 0.00390625 ], 31722 "imperial ounce": [15, 5.76456, 1.92152, 1.73387, 0.96076, 0.120095, 0.0600475, 0.0300238, 0.00750594, 0.0010034, 28.4131, 0.0284131, 2.8413e-5, 4.8, 1.6, 1, 0.05, 0.025, 0.00625 ], 31723 "imperial pint": [16, 115.291, 38.4304, 34.6774, 19.2152, 2.4019, 1.20095, 0.600475, 0.150119, 0.020068, 568.261, 0.568261, 0.000568261, 96, 32, 20, 1, 0.5, 0.125 ], 31724 "imperial quart": [17, 230.582, 76.8608, 69.3549, 38.4304, 4.8038, 2.4019, 1.20095, 0.300238, 0.0401359, 1136.52, 1.13652, 0.00113652, 192, 64, 40, 2, 1, 0.25 ], 31725 "imperial gallon": [18, 922.33, 307.443, 277.42, 153.722, 19.2152, 9.6076, 4.8038, 1.20095, 0.160544, 4546.09, 4.54609, 0.00454609, 768, 256, 160, 8, 4, 1 ] 31726 }; 31727 31728 /** 31729 * Return the type of this measurement. Examples are "mass", 31730 * "length", "speed", etc. Measurements can only be converted 31731 * to measurements of the same type.<p> 31732 * 31733 * The type of the units is determined automatically from the 31734 * units. For example, the unit "grams" is type "mass". Use the 31735 * static call {@link Measurement.getAvailableUnits} 31736 * to find out what units this version of ilib supports. 31737 * 31738 * @return {string} the name of the type of this measurement 31739 */ 31740 VolumeUnit.prototype.getMeasure = function() { 31741 return "volume"; 31742 }; 31743 31744 /** 31745 * Return a new measurement instance that is converted to a new 31746 * measurement unit. Measurements can only be converted 31747 * to measurements of the same type.<p> 31748 * 31749 * @param {string} to The name of the units to convert to 31750 * @return {Measurement|undefined} the converted measurement 31751 * or undefined if the requested units are for a different 31752 * measurement type 31753 */ 31754 VolumeUnit.prototype.convert = function(to) { 31755 if (!to || typeof(VolumeUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 31756 return undefined; 31757 } 31758 return new VolumeUnit({ 31759 unit: to, 31760 amount: this 31761 }); 31762 }; 31763 31764 VolumeUnit.aliases = { 31765 "US gal": "gallon", 31766 "US gallon": "gallon", 31767 "US Gal": "gallon", 31768 "US Gallons": "gallon", 31769 "Gal(US)": "gallon", 31770 "gal(US)": "gallon", 31771 "gallon": "gallon", 31772 "quart": "quart", 31773 "US quart": "quart", 31774 "US quarts": "quart", 31775 "US Quart": "quart", 31776 "US Quarts": "quart", 31777 "US qt": "quart", 31778 "Qt(US)": "quart", 31779 "qt(US)": "quart", 31780 "US pint": "pint", 31781 "US Pint": "pint", 31782 "pint": "pint", 31783 "pint(US)": "pint", 31784 "Pint(US)": "pint", 31785 "US cup": "cup", 31786 "US Cup": "cup", 31787 "cup(US)": "cup", 31788 "Cup(US)": "cup", 31789 "cup": "cup", 31790 "us ounce": "us ounce", 31791 "US ounce": "us ounce", 31792 "℥": "us ounce", 31793 "US Oz": "us ounce", 31794 "oz(US)": "us ounce", 31795 "Oz(US)": "us ounce", 31796 "US tbsp": "tbsp", 31797 "tbsp": "tbsp", 31798 "tbsp(US)": "tbsp", 31799 "US tablespoon": "tbsp", 31800 "US tsp": "tsp", 31801 "tsp(US)": "tsp", 31802 "tsp": "tsp", 31803 "Cubic meter": "cubic meter", 31804 "cubic meter": "cubic meter", 31805 "Cubic metre": "cubic meter", 31806 "cubic metre": "cubic meter", 31807 "m3": "cubic meter", 31808 "Liter": "liter", 31809 "Liters": "liter", 31810 "liter": "liter", 31811 "L": "liter", 31812 "l": "liter", 31813 "Milliliter": "milliliter", 31814 "ML": "milliliter", 31815 "ml": "milliliter", 31816 "milliliter": "milliliter", 31817 "mL": "milliliter", 31818 "Imperial gal": "imperial gallon", 31819 "imperial gallon": "imperial gallon", 31820 "Imperial gallon": "imperial gallon", 31821 "gallon(imperial)": "imperial gallon", 31822 "gal(imperial)": "imperial gallon", 31823 "Imperial quart": "imperial quart", 31824 "imperial quart": "imperial quart", 31825 "Imperial Quart": "imperial quart", 31826 "IMperial qt": "imperial quart", 31827 "qt(Imperial)": "imperial quart", 31828 "quart(imperial)": "imperial quart", 31829 "Imperial pint": "imperial pint", 31830 "imperial pint": "imperial pint", 31831 "pint(Imperial)": "imperial pint", 31832 "imperial oz": "imperial ounce", 31833 "imperial ounce": "imperial ounce", 31834 "Imperial Ounce": "imperial ounce", 31835 "Imperial tbsp": "imperial tbsp", 31836 "imperial tbsp": "imperial tbsp", 31837 "tbsp(Imperial)": "imperial tbsp", 31838 "Imperial tsp": "imperial tsp", 31839 "imperial tsp": "imperial tsp", 31840 "tsp(Imperial)": "imperial tsp", 31841 "Cubic foot": "cubic foot", 31842 "cubic foot": "cubic foot", 31843 "Cubic Foot": "cubic foot", 31844 "Cubic feet": "cubic foot", 31845 "cubic Feet": "cubic foot", 31846 "cubic ft": "cubic foot", 31847 "ft3": "cubic foot", 31848 "Cubic inch": "cubic inch", 31849 "Cubic inches": "cubic inch", 31850 "cubic inches": "cubic inch", 31851 "cubic inch": "cubic inch", 31852 "cubic in": "cubic inch", 31853 "cu in": "cubic inch", 31854 "cu inch": "cubic inch", 31855 "inch³": "cubic inch", 31856 "in³": "cubic inch", 31857 "inch^3": "cubic inch", 31858 "in^3": "cubic inch", 31859 "c.i": "cubic inch", 31860 "CI": "cubic inch", 31861 "cui": "cubic inch" 31862 }; 31863 31864 /** 31865 * Convert a volume to another measure. 31866 * @static 31867 * @param to {string} unit to convert to 31868 * @param from {string} unit to convert from 31869 * @param volume {number} amount to be convert 31870 * @returns {number|undefined} the converted amount 31871 */ 31872 VolumeUnit.convert = function(to, from, volume) { 31873 from = VolumeUnit.aliases[from] || from; 31874 to = VolumeUnit.aliases[to] || to; 31875 var fromRow = VolumeUnit.ratios[from]; 31876 var toRow = VolumeUnit.ratios[to]; 31877 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31878 return undefined; 31879 } 31880 var result = volume * fromRow[toRow[0]]; 31881 return result; 31882 }; 31883 31884 /** 31885 * @private 31886 * @static 31887 */ 31888 VolumeUnit.getMeasures = function () { 31889 var ret = []; 31890 for (var m in VolumeUnit.ratios) { 31891 ret.push(m); 31892 } 31893 return ret; 31894 }; 31895 VolumeUnit.metricSystem = { 31896 "milliliter": 10, 31897 "liter": 11, 31898 "cubic meter": 12 31899 }; 31900 VolumeUnit.imperialSystem = { 31901 "imperial tsp": 13, 31902 "imperial tbsp": 14, 31903 "imperial ounce": 15, 31904 "imperial pint": 16, 31905 "imperial quart": 17, 31906 "imperial gallon": 18 31907 }; 31908 VolumeUnit.uscustomarySystem = { 31909 "tsp": 1, 31910 "tbsp": 2, 31911 "cubic inch": 3, 31912 "us ounce": 4, 31913 "cup": 5, 31914 "pint": 6, 31915 "quart": 7, 31916 "gallon": 8, 31917 "cubic foot": 9 31918 }; 31919 31920 VolumeUnit.metricToUScustomary = { 31921 "milliliter": "tsp", 31922 "liter": "quart", 31923 "cubic meter": "cubic foot" 31924 }; 31925 VolumeUnit.metricToImperial = { 31926 "milliliter": "imperial tsp", 31927 "liter": "imperial quart", 31928 "cubic meter": "imperial gallon" 31929 }; 31930 31931 VolumeUnit.imperialToMetric = { 31932 "imperial tsp": "milliliter", 31933 "imperial tbsp": "milliliter", 31934 "imperial ounce": "milliliter", 31935 "imperial pint": "liter", 31936 "imperial quart": "liter", 31937 "imperial gallon": "cubic meter" 31938 }; 31939 VolumeUnit.imperialToUScustomary = { 31940 "imperial tsp": "tsp", 31941 "imperial tbsp": "tbsp", 31942 "imperial ounce": "us ounce", 31943 "imperial pint": "pint", 31944 "imperial quart": "quart", 31945 "imperial gallon": "gallon" 31946 }; 31947 31948 VolumeUnit.uScustomaryToImperial = { 31949 "tsp": "imperial tsp", 31950 "tbsp": "imperial tbsp", 31951 "cubic inch": "imperial tbsp", 31952 "us ounce": "imperial ounce", 31953 "cup": "imperial ounce", 31954 "pint": "imperial pint", 31955 "quart": "imperial quart", 31956 "gallon": "imperial gallon", 31957 "cubic foot": "imperial gallon" 31958 }; 31959 VolumeUnit.uScustomarylToMetric = { 31960 "tsp": "milliliter", 31961 "tbsp": "milliliter", 31962 "cubic inch": "milliliter", 31963 "us ounce": "milliliter", 31964 "cup": "milliliter", 31965 "pint": "liter", 31966 "quart": "liter", 31967 "gallon": "cubic meter", 31968 "cubic foot": "cubic meter" 31969 }; 31970 31971 /** 31972 * Localize the measurement to the commonly used measurement in that locale. For example 31973 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31974 * the formatted number should be automatically converted to the most appropriate 31975 * measure in the other system, in this case, mph. The formatted result should 31976 * appear as "37.3 mph". 31977 * 31978 * @param {string} locale current locale string 31979 * @returns {Measurement} a new instance that is converted to locale 31980 */ 31981 VolumeUnit.prototype.localize = function(locale) { 31982 var to; 31983 if (locale === "en-US") { 31984 to = VolumeUnit.metricToUScustomary[this.unit] || 31985 VolumeUnit.imperialToUScustomary[this.unit] || 31986 this.unit; 31987 } else if (locale === "en-GB") { 31988 to = VolumeUnit.metricToImperial[this.unit] || 31989 VolumeUnit.uScustomaryToImperial[this.unit] || 31990 this.unit; 31991 } else { 31992 to = VolumeUnit.uScustomarylToMetric[this.unit] || 31993 VolumeUnit.imperialToUScustomary[this.unit] || 31994 this.unit; 31995 } 31996 return new VolumeUnit({ 31997 unit: to, 31998 amount: this 31999 }); 32000 }; 32001 32002 /** 32003 * Scale the measurement unit to an acceptable level. The scaling 32004 * happens so that the integer part of the amount is as small as 32005 * possible without being below zero. This will result in the 32006 * largest units that can represent this measurement without 32007 * fractions. Measurements can only be scaled to other measurements 32008 * of the same type. 32009 * 32010 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 32011 * or undefined if the system can be inferred from the current measure 32012 * @return {Measurement} a new instance that is scaled to the 32013 * right level 32014 */ 32015 VolumeUnit.prototype.scale = function(measurementsystem) { 32016 var fromRow = VolumeUnit.ratios[this.unit]; 32017 var mSystem; 32018 32019 if (measurementsystem === "metric"|| (typeof(measurementsystem) === 'undefined' 32020 && typeof(VolumeUnit.metricSystem[this.unit]) !== 'undefined')) { 32021 mSystem = VolumeUnit.metricSystem; 32022 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 32023 && typeof(VolumeUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 32024 mSystem = VolumeUnit.uscustomarySystem; 32025 } else if (measurementsystem === "imperial"|| (typeof(measurementsystem) === 'undefined' 32026 && typeof(VolumeUnit.imperialSystem[this.unit]) !== 'undefined')) { 32027 mSystem = VolumeUnit.imperialSystem; 32028 } 32029 32030 var volume = this.amount; 32031 var munit = this.unit; 32032 32033 volume = 18446744073709551999; 32034 32035 for (var m in mSystem) { 32036 var tmp = this.amount * fromRow[mSystem[m]]; 32037 if (tmp >= 1 && tmp < volume) { 32038 volume = tmp; 32039 munit = m; 32040 } 32041 } 32042 32043 return new VolumeUnit({ 32044 unit: munit, 32045 amount: volume 32046 }); 32047 }; 32048 32049 //register with the factory method 32050 Measurement._constructors["volume"] = VolumeUnit; 32051 32052 32053 32054 /*< MeasurementFactory.js */ 32055 /* 32056 * MeasurementFactory.js - Function to instantiate the appropriate subclasses of 32057 * the Measurement class. 32058 * 32059 * Copyright © 2015, JEDLSoft 32060 * 32061 * Licensed under the Apache License, Version 2.0 (the "License"); 32062 * you may not use this file except in compliance with the License. 32063 * You may obtain a copy of the License at 32064 * 32065 * http://www.apache.org/licenses/LICENSE-2.0 32066 * 32067 * Unless required by applicable law or agreed to in writing, software 32068 * distributed under the License is distributed on an "AS IS" BASIS, 32069 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32070 * 32071 * See the License for the specific language governing permissions and 32072 * limitations under the License. 32073 */ 32074 32075 /* 32076 !depends 32077 UnknownUnit.js 32078 AreaUnit.js 32079 DigitalStorageUnit.js 32080 EnergyUnit.js 32081 FuelConsumptionUnit.js 32082 LengthUnit.js 32083 MassUnit.js 32084 TemperatureUnit.js 32085 TimeUnit.js 32086 VelocityUnit.js 32087 VolumeUnit.js 32088 Measurement.js 32089 */ 32090 32091 // TODO: make these dependencies dynamic or at least generate them in the build 32092 // These will each add themselves to Measurement._constructors[] 32093 32094 32095 /** 32096 * Create a measurement subclass instance based on a particular measure 32097 * required. The measurement is immutable once 32098 * it is created, but it can be converted to other measurements later.<p> 32099 * 32100 * The options may contain any of the following properties: 32101 * 32102 * <ul> 32103 * <li><i>amount</i> - either a numeric amount for this measurement given 32104 * as a number of the specified units, or another Measurement instance 32105 * to convert to the requested units. If converting to new units, the type 32106 * of measure between the other instance's units and the current units 32107 * must be the same. That is, you can only convert one unit of mass to 32108 * another. You cannot convert a unit of mass into a unit of length. 32109 * 32110 * <li><i>unit</i> - units of this measurement. Use the 32111 * static call {@link MeasurementFactory.getAvailableUnits} 32112 * to find out what units this version of ilib supports. If the given unit 32113 * is not a base unit, the amount will be normalized to the number of base units 32114 * and stored as that number of base units. 32115 * For example, if an instance is constructed with 1 kg, this will be converted 32116 * automatically into 1000 g, as grams are the base unit and kg is merely a 32117 * commonly used scale of grams. 32118 * </ul> 32119 * 32120 * Here are some examples of converting a length into new units. 32121 * The first method is via this factory function by passing the old measurement 32122 * in as the "amount" property.<p> 32123 * 32124 * <pre> 32125 * var measurement1 = MeasurementFactory({ 32126 * amount: 5, 32127 * units: "kilometers" 32128 * }); 32129 * var measurement2 = MeasurementFactory({ 32130 * amount: measurement1, 32131 * units: "miles" 32132 * }); 32133 * </pre> 32134 * 32135 * The value in measurement2 will end up being about 3.125 miles.<p> 32136 * 32137 * The second method uses the convert method.<p> 32138 * 32139 * <pre> 32140 * var measurement1 = MeasurementFactory({ 32141 * amount: 5, 32142 * units: "kilometers" 32143 * }); 32144 * var measurement2 = measurement1.convert("miles"); 32145 * }); 32146 * </pre> 32147 * 32148 * The value in measurement2 will again end up being about 3.125 miles. 32149 * 32150 * @static 32151 * @param {Object=} options options that control the construction of this instance 32152 */ 32153 var MeasurementFactory = function(options) { 32154 if (!options || typeof(options.unit) === 'undefined') { 32155 return undefined; 32156 } 32157 32158 var measure = undefined; 32159 32160 for (var c in Measurement._constructors) { 32161 var measurement = Measurement._constructors[c]; 32162 if (typeof(measurement.aliases[options.unit]) !== 'undefined') { 32163 measure = c; 32164 break; 32165 } 32166 } 32167 32168 if (!measure || typeof(measure) === 'undefined') { 32169 return new UnknownUnit({ 32170 unit: options.unit, 32171 amount: options.amount 32172 }); 32173 } else { 32174 return new Measurement._constructors[measure](options); 32175 } 32176 }; 32177 32178 /** 32179 * Return a list of all possible units that this version of ilib supports. 32180 * Typically, the units are given as their full names in English. Unit names 32181 * are case-insensitive. 32182 * 32183 * @static 32184 * @return {Array.<string>} an array of strings containing names of measurement 32185 * units available 32186 */ 32187 MeasurementFactory.getAvailableUnits = function () { 32188 var units = []; 32189 for (var c in Measurement._constructors) { 32190 var measure = Measurement._constructors[c]; 32191 units = units.concat(measure.getMeasures()); 32192 } 32193 return units; 32194 }; 32195 32196 32197 32198 /*< UnitFmt.js */ 32199 /* 32200 * UnitFmt.js - Unit formatter class 32201 * 32202 * Copyright © 2014-2015, JEDLSoft 32203 * 32204 * Licensed under the Apache License, Version 2.0 (the "License"); 32205 * you may not use this file except in compliance with the License. 32206 * You may obtain a copy of the License at 32207 * 32208 * http://www.apache.org/licenses/LICENSE-2.0 32209 * 32210 * Unless required by applicable law or agreed to in writing, software 32211 * distributed under the License is distributed on an "AS IS" BASIS, 32212 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32213 * 32214 * See the License for the specific language governing permissions and 32215 * limitations under the License. 32216 */ 32217 32218 /* 32219 !depends 32220 ilib.js 32221 Locale.js 32222 ResBundle.js 32223 LocaleInfo.js 32224 IString.js 32225 NumFmt.js 32226 Utils.js 32227 */ 32228 32229 // !data unitfmt 32230 32231 32232 32233 /** 32234 * @class 32235 * Create a new unit formatter instance. The unit formatter is immutable once 32236 * it is created, but can format as many different strings with different values 32237 * as needed with the same options. Create different unit formatter instances 32238 * for different purposes and then keep them cached for use later if you have 32239 * more than one unit string to format.<p> 32240 * 32241 * The options may contain any of the following properties: 32242 * 32243 * <ul> 32244 * <li><i>locale</i> - locale to use when formatting the units. The locale also 32245 * controls the translation of the names of the units. If the locale is 32246 * not specified, then the default locale of the app or web page will be used. 32247 * 32248 * <li><i>autoScale</i> - when true, automatically scale the amount to get the smallest 32249 * number greater than 1, where possible, possibly by converting units within the locale's 32250 * measurement system. For example, if the current locale is "en-US", and we have 32251 * a measurement containing 278 fluid ounces, then the number "278" can be scaled down 32252 * by converting the units to a larger one such as gallons. The scaled size would be 32253 * 2.17188 gallons. Since iLib does not have a US customary measure larger than gallons, 32254 * it cannot scale it down any further. If the amount is less than the smallest measure 32255 * already, it cannot be scaled down any further and no autoscaling will be applied. 32256 * Default for the autoScale property is "true", so it only needs to be specified when 32257 * you want to turn off autoscaling. 32258 * 32259 * <li><i>autoConvert</i> - automatically convert the units to the nearest appropriate 32260 * measure of the same type in the measurement system used by the locale. For example, 32261 * if a measurement of length is given in meters, but the current locale is "en-US" 32262 * which uses the US Customary system, then the nearest appropriate measure would be 32263 * "yards", and the amount would be converted from meters to yards automatically before 32264 * being formatted. Default for the autoConvert property is "true", so it only needs to 32265 * be specified when you want to turn off autoconversion. 32266 * 32267 * <li><i>maxFractionDigits</i> - the maximum number of digits that should appear in the 32268 * formatted output after the decimal. A value of -1 means unlimited, and 0 means only print 32269 * the integral part of the number. 32270 * 32271 * <li><i>minFractionDigits</i> - the minimum number of fractional digits that should 32272 * appear in the formatted output. If the number does not have enough fractional digits 32273 * to reach this minimum, the number will be zero-padded at the end to get to the limit. 32274 * 32275 * <li><i>roundingMode</i> - When the maxFractionDigits or maxIntegerDigits is specified, 32276 * this property governs how the least significant digits are rounded to conform to that 32277 * maximum. The value of this property is a string with one of the following values: 32278 * <ul> 32279 * <li><i>up</i> - round away from zero 32280 * <li><i>down</i> - round towards zero. This has the effect of truncating the number 32281 * <li><i>ceiling</i> - round towards positive infinity 32282 * <li><i>floor</i> - round towards negative infinity 32283 * <li><i>halfup</i> - round towards nearest neighbour. If equidistant, round up. 32284 * <li><i>halfdown</i> - round towards nearest neighbour. If equidistant, round down. 32285 * <li><i>halfeven</i> - round towards nearest neighbour. If equidistant, round towards the even neighbour 32286 * <li><i>halfodd</i> - round towards nearest neighbour. If equidistant, round towards the odd neighbour 32287 * </ul> 32288 * 32289 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 32290 * loaded. When the onLoad option is given, the UnitFmt object will attempt to 32291 * load any missing locale data using the ilib loader callback. 32292 * When the constructor is done (even if the data is already preassembled), the 32293 * onLoad function is called with the current instance as a parameter, so this 32294 * callback can be used with preassembled or dynamic loading or a mix of the two. 32295 * 32296 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 32297 * asynchronously. If this option is given as "false", then the "onLoad" 32298 * callback must be given, as the instance returned from this constructor will 32299 * not be usable for a while. 32300 * 32301 * <li><i>loadParams</i> - an object containing parameters to pass to the 32302 * loader callback function when locale data is missing. The parameters are not 32303 * interpretted or modified in any way. They are simply passed along. The object 32304 * may contain any property/value pairs as long as the calling code is in 32305 * agreement with the loader callback function as to what those parameters mean. 32306 * </ul> 32307 * 32308 * Here is an example of how you might use the unit formatter to format a string with 32309 * the correct units.<p> 32310 * 32311 * 32312 * @constructor 32313 * @param {Object} options options governing the way this date formatter instance works 32314 */ 32315 var UnitFmt = function(options) { 32316 var sync = true, 32317 loadParams = undefined; 32318 32319 this.length = "long"; 32320 this.scale = true; 32321 this.measurementType = 'undefined'; 32322 this.convert = true; 32323 this.locale = new Locale(); 32324 32325 if (options) { 32326 if (options.locale) { 32327 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 32328 } 32329 32330 if (typeof(options.sync) === 'boolean') { 32331 sync = options.sync; 32332 } 32333 32334 if (typeof(options.loadParams) !== 'undefined') { 32335 loadParams = options.loadParams; 32336 } 32337 32338 if (options.length) { 32339 this.length = options.length; 32340 } 32341 32342 if (typeof(options.autoScale) === 'boolean') { 32343 this.scale = options.autoScale; 32344 } 32345 32346 if (typeof(options.autoConvert) === 'boolean') { 32347 this.convert = options.autoConvert; 32348 } 32349 32350 if (typeof(options.useNative) === 'boolean') { 32351 this.useNative = options.useNative; 32352 } 32353 32354 if (options.measurementSystem) { 32355 this.measurementSystem = options.measurementSystem; 32356 } 32357 32358 if (typeof (options.maxFractionDigits) === 'number') { 32359 /** 32360 * @private 32361 * @type {number|undefined} 32362 */ 32363 this.maxFractionDigits = options.maxFractionDigits; 32364 } 32365 if (typeof (options.minFractionDigits) === 'number') { 32366 /** 32367 * @private 32368 * @type {number|undefined} 32369 */ 32370 this.minFractionDigits = options.minFractionDigits; 32371 } 32372 /** 32373 * @private 32374 * @type {string} 32375 */ 32376 this.roundingMode = options.roundingMode; 32377 } 32378 32379 if (!UnitFmt.cache) { 32380 UnitFmt.cache = {}; 32381 } 32382 32383 Utils.loadData({ 32384 object: UnitFmt, 32385 locale: this.locale, 32386 name: "unitfmt.json", 32387 sync: sync, 32388 loadParams: loadParams, 32389 callback: ilib.bind(this, function (format) { 32390 var formatted = format; 32391 this.template = formatted["unitfmt"][this.length]; 32392 32393 new NumFmt({ 32394 locale: this.locale, 32395 useNative: this.useNative, 32396 maxFractionDigits: this.maxFractionDigits, 32397 minFractionDigits: this.minFractionDigits, 32398 roundingMode: this.roundingMode, 32399 sync: sync, 32400 loadParams: loadParams, 32401 onLoad: ilib.bind(this, function (numfmt) { 32402 this.numFmt = numfmt; 32403 32404 if (options && typeof(options.onLoad) === 'function') { 32405 options.onLoad(this); 32406 } 32407 }) 32408 }); 32409 }) 32410 }); 32411 }; 32412 32413 UnitFmt.prototype = { 32414 32415 /** 32416 * Return the locale used with this formatter instance. 32417 * @return {Locale} the Locale instance for this formatter 32418 */ 32419 getLocale: function() { 32420 return this.locale; 32421 }, 32422 32423 /** 32424 * Return the template string that is used to format date/times for this 32425 * formatter instance. This will work, even when the template property is not explicitly 32426 * given in the options to the constructor. Without the template option, the constructor 32427 * will build the appropriate template according to the options and use that template 32428 * in the format method. 32429 * 32430 * @return {string} the format template for this formatter 32431 */ 32432 getTemplate: function() { 32433 return this.template; 32434 }, 32435 32436 /** 32437 * Convert this formatter to a string representation by returning the 32438 * format template. This method delegates to getTemplate. 32439 * 32440 * @return {string} the format template 32441 */ 32442 toString: function() { 32443 return this.getTemplate(); 32444 }, 32445 32446 /** 32447 * Return whether or not this formatter will auto-scale the units while formatting. 32448 * @returns {boolean} true if auto-scaling is turned on 32449 */ 32450 getScale: function() { 32451 return this.scale; 32452 }, 32453 32454 /** 32455 * Return the measurement system that is used for this formatter. 32456 * @returns {string} the measurement system used in this formatter 32457 */ 32458 getMeasurementSystem: function() { 32459 return this.measurementSystem; 32460 }, 32461 32462 /** 32463 * Format a particular unit instance according to the settings of this 32464 * formatter object. 32465 * 32466 * @param {Measurement} measurement measurement to format 32467 * @return {string} the formatted version of the given date instance 32468 */ 32469 format: function (measurement) { 32470 var u = this.convert ? measurement.localize(this.locale.getSpec()) : measurement; 32471 u = this.scale ? u.scale(this.measurementSystem) : u; 32472 var formatted = new IString(this.template[u.getUnit()]); 32473 // make sure to use the right plural rules 32474 formatted.setLocale(this.locale, true, undefined, undefined); 32475 formatted = formatted.formatChoice(u.amount,{n:this.numFmt.format(u.amount)}); 32476 return formatted.length > 0 ? formatted : u.amount +" " + u.unit; 32477 } 32478 }; 32479 32480 32481 /*< Charset.js */ 32482 /* 32483 * Charset.js - Return information about a particular character set 32484 * 32485 * Copyright © 2014-2015, JEDLSoft 32486 * 32487 * Licensed under the Apache License, Version 2.0 (the "License"); 32488 * you may not use this file except in compliance with the License. 32489 * You may obtain a copy of the License at 32490 * 32491 * http://www.apache.org/licenses/LICENSE-2.0 32492 * 32493 * Unless required by applicable law or agreed to in writing, software 32494 * distributed under the License is distributed on an "AS IS" BASIS, 32495 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32496 * 32497 * See the License for the specific language governing permissions and 32498 * limitations under the License. 32499 */ 32500 32501 // !depends ilib.js Utils.js 32502 // !data charsetaliases charset/ISO-8859-1 charset/ISO-8859-15 charset/UTF-8 32503 32504 32505 /** 32506 * @class 32507 * Create a new character set info instance. Charset instances give information about 32508 * a particular character set, such as whether or not it is single byte or multibyte, 32509 * and which languages commonly use that charset.<p> 32510 * 32511 * The optional options object holds extra parameters if they are necessary. The 32512 * current list of supported options are: 32513 * 32514 * <ul> 32515 * <li><i>name</i> - the name of the charset. This can be given as any commonly 32516 * used name for the character set, which is normalized to a standard IANA name 32517 * before its info is loaded. If a name is not given, 32518 * this class will return information about the base character set of Javascript, 32519 * which is currently Unicode as encoded in UTF-16. 32520 * 32521 * <li><i>onLoad</i> - a callback function to call when this object is fully 32522 * loaded. When the onLoad option is given, this class will attempt to 32523 * load any missing data using the ilib loader callback. 32524 * When the constructor is done (even if the data is already preassembled), the 32525 * onLoad function is called with the current instance as a parameter, so this 32526 * callback can be used with preassembled or dynamic loading or a mix of the two. 32527 * 32528 * <li><i>sync</i> - tell whether to load any missing data synchronously or 32529 * asynchronously. If this option is given as "false", then the "onLoad" 32530 * callback must be given, because the instance returned from this constructor will 32531 * not be usable for a while. 32532 * 32533 * <li><i>loadParams</i> - an object containing parameters to pass to the 32534 * loader callback function when data is missing. The parameters are not 32535 * interpretted or modified in any way. They are simply passed along. The object 32536 * may contain any property/value pairs as long as the calling code is in 32537 * agreement with the loader callback function as to what those parameters mean. 32538 * </ul> 32539 * 32540 * If this copy of ilib is pre-assembled and all the data is already available, 32541 * or if the data was already previously loaded, then this constructor will call 32542 * the onLoad callback immediately when the initialization is done. 32543 * If the onLoad option is not given, this class will only attempt to load any 32544 * missing data synchronously. 32545 * 32546 * Depends directive: !depends charset.js 32547 * 32548 * @constructor 32549 * @see {ilib.setLoaderCallback} for information about registering a loader callback instance 32550 * @param {Object=} options options which govern the construction of this instance 32551 */ 32552 var Charset = function(options) { 32553 var sync = true, 32554 loadParams = undefined; 32555 this.originalName = "UTF-8"; 32556 32557 if (options) { 32558 if (typeof(options.name) !== 'undefined') { 32559 this.originalName = options.name; 32560 } 32561 32562 if (typeof(options.sync) !== 'undefined') { 32563 sync = (options.sync == true); 32564 } 32565 32566 if (typeof(options.loadParams) !== 'undefined') { 32567 loadParams = options.loadParams; 32568 } 32569 } 32570 32571 if (!Charset.cache) { 32572 Charset.cache = {}; 32573 } 32574 32575 // default data. A majority of charsets use this info 32576 this.info = { 32577 description: "default", 32578 min: 1, 32579 max: 1, 32580 bigendian: true, 32581 scripts: ["Latn"], 32582 locales: ["*"] 32583 }; 32584 32585 Utils.loadData({ 32586 object: Charset, 32587 locale: "-", 32588 nonlocale: true, 32589 name: "charsetaliases.json", 32590 sync: sync, 32591 loadParams: loadParams, 32592 callback: ilib.bind(this, function (info) { 32593 // first map the given original name to one of the standardized IANA names 32594 if (info) { 32595 // recognize better by getting rid of extraneous crap and upper-casing 32596 // it so that the match is case-insensitive 32597 var n = this.originalName.replace(/[-_,:\+\.\(\)]/g, '').toUpperCase(); 32598 this.name = info[n]; 32599 } 32600 if (!this.name) { 32601 this.name = this.originalName; 32602 } 32603 Utils.loadData({ 32604 object: Charset, 32605 locale: "-", 32606 nonlocale: true, 32607 name: "charset/" + this.name + ".json", 32608 sync: sync, 32609 loadParams: loadParams, 32610 callback: ilib.bind(this, function (info) { 32611 if (info) { 32612 ilib.extend(this.info, info); 32613 } 32614 if (options && typeof(options.onLoad) === 'function') { 32615 options.onLoad(this); 32616 } 32617 }) 32618 }); 32619 }) 32620 }); 32621 }; 32622 32623 Charset.prototype = { 32624 /** 32625 * Return the standard normalized name of this charset. The list of standard names 32626 * comes from the IANA registry of character set names at 32627 * <a href="http://www.iana.org/assignments/character-sets/character-sets.xhtml">http://www.iana.org/assignments/character-sets/character-sets.xhtml</a>. 32628 * 32629 * @returns {string} the name of the charset 32630 */ 32631 getName: function () { 32632 return this.name; 32633 }, 32634 32635 /** 32636 * Return the original name that this instance was constructed with before it was 32637 * normalized to the standard name returned by {@link #getName}. 32638 * 32639 * @returns {String} the original name that this instance was constructed with 32640 */ 32641 getOriginalName: function() { 32642 return this.originalName; 32643 }, 32644 32645 /** 32646 * Return a short description of the character set. 32647 * 32648 * @returns {string} a description of the character set 32649 */ 32650 getDescription: function() { 32651 return this.info.description || this.getName(); 32652 }, 32653 32654 /** 32655 * Return the smallest number of bytes that a single character in this charset 32656 * could use. For most charsets, this is 1, but for some charsets such as Unicode 32657 * encoded in UTF-16, this may be 2 or more. 32658 * @returns {number} the smallest number of bytes that a single character in 32659 * this charset uses 32660 */ 32661 getMinCharWidth: function () { 32662 return this.info.min; 32663 }, 32664 32665 /** 32666 * Return the largest number of bytes that a single character in this charset 32667 * could use. 32668 * @returns {number} the largest number of bytes that a single character in 32669 * this charset uses 32670 */ 32671 getMaxCharWidth: function () { 32672 return this.info.max; 32673 }, 32674 32675 /** 32676 * Return true if this is a multibyte character set, or false for a fixed 32677 * width character set. A multibyte character set is one in which the characters 32678 * have a variable width. That is, one character may use 1 byte and a different 32679 * character might use 2 or 3 bytes. 32680 * 32681 * @returns {boolean} true if this is a multibyte charset, or false otherwise 32682 */ 32683 isMultibyte: function() { 32684 return this.getMaxCharWidth() > this.getMinCharWidth(); 32685 }, 32686 32687 /** 32688 * Return whether or not characters larger than 1 byte use the big endian order 32689 * or little endian. 32690 * 32691 * @returns {boolean} true if this character set uses big endian order, or false 32692 * otherwise 32693 */ 32694 isBigEndian: function() { 32695 return this.info.bigendian; 32696 }, 32697 32698 /** 32699 * Return an array of ISO script codes whose characters can be encoded with this 32700 * character set. 32701 * 32702 * @returns {Array.<string>} an array of ISO script codes supported by this charset 32703 */ 32704 getScripts: function() { 32705 return this.info.scripts; 32706 } 32707 }; 32708 32709 32710 /*< Charmap.js */ 32711 /* 32712 * Charmap.js - A character set mapping class 32713 * 32714 * Copyright © 2014-2015, JEDLSoft 32715 * 32716 * Licensed under the Apache License, Version 2.0 (the "License"); 32717 * you may not use this file except in compliance with the License. 32718 * You may obtain a copy of the License at 32719 * 32720 * http://www.apache.org/licenses/LICENSE-2.0 32721 * 32722 * Unless required by applicable law or agreed to in writing, software 32723 * distributed under the License is distributed on an "AS IS" BASIS, 32724 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32725 * 32726 * See the License for the specific language governing permissions and 32727 * limitations under the License. 32728 */ 32729 32730 // !depends ilib.js Utils.js Charset.js JSUtils.js IString.js 32731 32732 // !data charset/US-ASCII charset/ISO-10646-UCS-2 charset/ISO-10646-UCS-4 charset/ISO-10646-Unicode-Latin1 32733 32734 32735 /** 32736 * @class 32737 * Create a new default character set mapping instance. This class is the parent 32738 * class of all of the charmapping subclasses, and only implements basic US-ASCII 32739 * mapping. The subclasses implement all other charsets, some algorithmically, and 32740 * some in a table-based way. Use {@link CharmapFactory} to create the correct 32741 * subclass instance for the desired charmap.<p> 32742 * 32743 * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base 32744 * character set and encoding used by Javascript itself. In order to convert 32745 * between two non-Unicode character sets, you must chain two charmap instances together 32746 * to first map to Unicode and then back to the second charset. <p> 32747 * 32748 * The options parameter controls which mapping is constructed and its behaviours. The 32749 * current list of supported options are: 32750 * 32751 * <ul> 32752 * <li><i>missing</i> - specify what to do if a mapping is missing for a particular 32753 * character. For example, if you are mapping Unicode characters to a particular native 32754 * character set that does not support particular Unicode characters, the mapper will 32755 * follow the behaviour specified in this property. Valid values are: 32756 * <ul> 32757 * <li><i>skip</i> - skip any characters that do not exist in the target charset 32758 * <li><i>placeholder</i> - put a static placeholder character in the output string 32759 * wherever there is an unknown character in the input string. Use the <i>placeholder</i> 32760 * parameter to specify which character to use in this case 32761 * <li><i>escape</i> - use an escape sequence to represent the unknown character 32762 * </ul> 32763 * The default value for the missing property if not otherwise specified is "escape" 32764 * so that information is not lost. 32765 * 32766 * <li><i>placeholder</i> - specify the placeholder character to use when the 32767 * mapper cannot map a particular input character to the output string. If this 32768 * option is not specified, then the '?' (question mark) character is used where 32769 * possible. 32770 * 32771 * <li><i>escapeStyle</i> - what style of escape sequences should be used to 32772 * escape unknown characters in the input when mapping to native, and what 32773 * style of espcae sequences should be parsed when mapping to Unicode. Valid 32774 * values are: 32775 * <ul> 32776 * <li><i>html</i> - Escape the characters as HTML entities. This would use 32777 * the standard HTML 5.0 (or later) entity names where possible, and numeric 32778 * entities in all other cases. Eg. an "e" with an acute accent would be 32779 * "é" 32780 * <li><i>js</i> - Use the Javascript escape style. Eg. an "e" with an acute 32781 * accent would be "\u00E9". This can also be specified as "c#" as 32782 * it uses a similar escape syntax. 32783 * <li><i>c</i> - Use the C/C++ escape style, which is similar to the the 32784 * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an 32785 * acute accent would be "\x00E9". This can also be specified as "c++". 32786 * <li><i>java</i> - Use the Java escape style. This is very similar to the 32787 * the Javascript style, but the backslash has to be escaped twice. Eg. an 32788 * "e" with an acute accent would be "\\u00E9". This can also be specified 32789 * as "ruby", as Ruby uses a similar escape syntax with double backslashes. 32790 * <li><i>perl</i> - Use the Perl escape style. Eg. an "e" with an acute 32791 * accent would be "\N{U+00E9}" 32792 * </ul> 32793 * The default if this style is not specified is "js" for Javascript. 32794 * </ul> 32795 * 32796 * If this copy of ilib is pre-assembled and all the data is already available, 32797 * or if the data was already previously loaded, then this constructor will call 32798 * the onLoad callback immediately when the initialization is done. 32799 * If the onLoad option is not given, this class will only attempt to load any 32800 * missing data synchronously. 32801 * 32802 * @constructor 32803 * @param {Object=} options options which govern the construction of this instance 32804 */ 32805 var Charmap = function(options) { 32806 var sync = true, 32807 loadParams = undefined; 32808 32809 this.charset = new Charset({name: "US-ASCII"}); 32810 this.missing = "placeholder"; 32811 this.placeholder = "?"; 32812 this.escapeStyle = "js"; 32813 this.expansionFactor = 1; 32814 32815 if (options) { 32816 if (typeof(options.placeholder) !== 'undefined') { 32817 this.placeholder = options.placeholder; 32818 } 32819 32820 var escapes = { 32821 "html": "html", 32822 "js": "js", 32823 "c#": "js", 32824 "c": "c", 32825 "c++": "c", 32826 "java": "java", 32827 "ruby": "java", 32828 "perl": "perl" 32829 }; 32830 32831 if (typeof(options.escapeStyle) !== 'undefined') { 32832 if (typeof(escapes[options.escapeStyle]) !== 'undefined') { 32833 this.escapeStyle = escapes[options.escapeStyle]; 32834 } 32835 } 32836 32837 if (typeof(options.missing) !== 'undefined') { 32838 if (options.missing === "skip" || options.missing === "placeholder" || options.missing === "escape") { 32839 this.missing = options.missing; 32840 } 32841 } 32842 } 32843 32844 this._calcExpansionFactor(); 32845 }; 32846 32847 /** 32848 * A place for the algorithmic conversions to register themselves as 32849 * they are defined. 32850 * 32851 * @static 32852 * @private 32853 */ 32854 Charmap._algorithms = {}; 32855 32856 Charmap.prototype = { 32857 /** 32858 * Return the standard name of this charmap. All charmaps map from 32859 * Unicode to the native charset, so the name returned from this 32860 * function corresponds to the native charset. 32861 * 32862 * @returns {string} the name of the locale's language in English 32863 */ 32864 getName: function () { 32865 return this.charset.getName(); 32866 }, 32867 32868 /** 32869 * @private 32870 */ 32871 writeNative: function (array, start, value) { 32872 // console.log("Charmap.writeNative: start " + start + " adding " + JSON.stringify(value)); 32873 if (ilib.isArray(value)) { 32874 for (var i = 0; i < value.length; i++) { 32875 array[start+i] = value[i]; 32876 } 32877 32878 return value.length; 32879 } else { 32880 array[start] = value; 32881 return 1; 32882 } 32883 }, 32884 32885 /** 32886 * @private 32887 */ 32888 writeNativeString: function (array, start, string) { 32889 // console.log("Charmap.writeNativeString: start " + start + " adding " + JSON.stringify(string)); 32890 for (var i = 0; i < string.length; i++) { 32891 array[start+i] = string.charCodeAt(i); 32892 } 32893 return string.length; 32894 }, 32895 32896 /** 32897 * @private 32898 */ 32899 _calcExpansionFactor: function() { 32900 var factor = 1; 32901 factor = Math.max(factor, this.charset.getMaxCharWidth()); 32902 switch (this.missing) { 32903 case "placeholder": 32904 if (this.placeholder) { 32905 factor = Math.max(factor, this.placeholder.length); 32906 } 32907 break; 32908 case "escape": 32909 switch (this.escapeStyle) { 32910 case "html": 32911 factor = Math.max(factor, 8); // HHHH; 32912 break; 32913 case "c": 32914 factor = Math.max(factor, 6); // \xHHHH 32915 break; 32916 case "perl": 32917 factor = Math.max(factor, 10); // \N{U+HHHH} 32918 break; 32919 32920 default: 32921 factor = Math.max(factor, 6); // \uHHHH 32922 break; 32923 } 32924 break; 32925 default: 32926 break; 32927 } 32928 32929 this.expansionFactor = factor; 32930 }, 32931 32932 /** 32933 * @private 32934 */ 32935 dealWithMissingChar: function(c) { 32936 var seq = ""; 32937 32938 switch (this.missing) { 32939 case "skip": 32940 // do nothing 32941 break; 32942 32943 case "escape": 32944 var num = (typeof(c) === 'string') ? c.charCodeAt(0) : c; 32945 var bigc = JSUtils.pad(num.toString(16), 4).toUpperCase(); 32946 switch (this.escapeStyle) { 32947 case "html": 32948 seq = "" + bigc + ";"; 32949 break; 32950 case "c": 32951 seq = "\\x" + bigc; 32952 break; 32953 case "java": 32954 seq = "\\\\u" + bigc; 32955 break; 32956 case "perl": 32957 seq = "\\N{U+" + bigc + "}"; 32958 break; 32959 32960 default: 32961 case "js": 32962 seq = "\\u" + bigc; 32963 break; 32964 } 32965 break; 32966 32967 default: 32968 case "placeholder": 32969 seq = this.placeholder; 32970 break; 32971 } 32972 32973 return seq; 32974 }, 32975 32976 /** 32977 * Map a string to the native character set. This string may be 32978 * given as an intrinsic Javascript string object or an IString 32979 * object. 32980 * 32981 * @param {string|IString} string string to map to a different 32982 * character set. 32983 * @return {Uint8Array} An array of bytes representing the string 32984 * in the native character set 32985 */ 32986 mapToNative: function(string) { 32987 if (!string) { 32988 return new Uint8Array(0); 32989 } 32990 32991 if (this.algorithm) { 32992 return this.algorithm.mapToNative(string); 32993 } 32994 32995 // the default algorithm is plain old ASCII 32996 var str = (string instanceof IString) ? string : new IString(string); 32997 32998 // use IString's iterator so that we take care of walking through 32999 // the code points correctly, including the surrogate pairs 33000 var c, i = 0, j = 0, it = str.iterator(); 33001 var ret = new Uint8Array(str.length * this.expansionFactor); 33002 33003 while (it.hasNext() && i < ret.length) { 33004 c = it.next(); 33005 if (c < 127) { 33006 ret[i++] = c; 33007 } else { 33008 i += this.writeNativeString(ret, i, this.dealWithMissingChar(c)); 33009 } 33010 } 33011 33012 return ret; 33013 }, 33014 33015 /** 33016 * Map a native string to the standard Javascript charset of UTF-16. 33017 * This string may be given as an array of numbers where each number 33018 * represents a code point in the "from" charset, or as a Uint8Array 33019 * array of bytes representing the bytes of the string in order. 33020 * 33021 * @param {Array.<number>|Uint8Array} bytes bytes to map to 33022 * a Unicode string 33023 * @return {string} A string in the standard Javascript charset UTF-16 33024 */ 33025 mapToUnicode: function(bytes) { 33026 var ret = ""; 33027 var c, i = 0; 33028 33029 while (i < bytes.length) { 33030 c = bytes[i]; 33031 33032 // the default algorithm is plain old ASCII 33033 if (c < 128) { 33034 ret += String.fromCharCode(c); 33035 } else { 33036 // The byte at "i" wasn't ASCII 33037 ret += this.dealWithMissingChar(bytes[i++]); 33038 } 33039 } 33040 33041 return ret; 33042 } 33043 }; 33044 33045 33046 /*< CharmapTable.js */ 33047 /* 33048 * CharmapTable.js - A character set mapping class that maps using trie table 33049 * 33050 * Copyright © 2014-2015, JEDLSoft 33051 * 33052 * Licensed under the Apache License, Version 2.0 (the "License"); 33053 * you may not use this file except in compliance with the License. 33054 * You may obtain a copy of the License at 33055 * 33056 * http://www.apache.org/licenses/LICENSE-2.0 33057 * 33058 * Unless required by applicable law or agreed to in writing, software 33059 * distributed under the License is distributed on an "AS IS" BASIS, 33060 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33061 * 33062 * See the License for the specific language governing permissions and 33063 * limitations under the License. 33064 */ 33065 33066 // !depends ilib.js Utils.js Charset.js Charmap.js IString.js 33067 33068 // !data charmaps/ISO-8859-1 charset/ISO-8859-1 33069 33070 33071 /** 33072 * @class 33073 * Create a new character set mapping instance using based on a trie table. Charmap 33074 * instances map strings to 33075 * other character sets. The charsets can be of any type, single-byte, multi-byte, 33076 * shifting, etc. <p> 33077 * 33078 * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base 33079 * character set and encoding used by Javascript itself. In order to convert 33080 * between two non-Unicode character sets, you must chain two charmap instances together 33081 * to first map to Unicode and then back to the second charset. <p> 33082 * 33083 * The options parameter controls which mapping is constructed and its behaviours. The 33084 * current list of supported options are: 33085 * 33086 * <ul> 33087 * <li><i>charset</i> - the name of the native charset to map to or from. This can be 33088 * given as an {@link Charset} instance or as a string that contains any commonly used name 33089 * for the character set, which is normalized to a standard IANA name. 33090 * If a name is not given, this class will default to the Western European character 33091 * set called ISO-8859-15. 33092 * 33093 * <li><i>missing</i> - specify what to do if a mapping is missing for a particular 33094 * character. For example, if you are mapping Unicode characters to a particular native 33095 * character set that does not support particular Unicode characters, the mapper will 33096 * follow the behaviour specified in this property. Valid values are: 33097 * <ul> 33098 * <li><i>skip</i> - skip any characters that do not exist in the target charset 33099 * <li><i>placeholder</i> - put a static placeholder character in the output string 33100 * wherever there is an unknown character in the input string. Use the <i>placeholder</i> 33101 * parameter to specify which character to use in this case 33102 * <li><i>escape</i> - use an escape sequence to represent the unknown character 33103 * </ul> 33104 * The default value for the missing property if not otherwise specified is "escape" 33105 * so that information is not lost. 33106 * 33107 * <li><i>placeholder</i> - specify the placeholder character to use when the 33108 * mapper cannot map a particular input character to the output string. If this 33109 * option is not specified, then the '?' (question mark) character is used where 33110 * possible. 33111 * 33112 * <li><i>escapeStyle</i> - what style of escape sequences should be used to 33113 * escape unknown characters in the input when mapping to native, and what 33114 * style of espcae sequences should be parsed when mapping to Unicode. Valid 33115 * values are: 33116 * <ul> 33117 * <li><i>html</i> - Escape the characters as HTML entities. This would use 33118 * the standard HTML 5.0 (or later) entity names where possible, and numeric 33119 * entities in all other cases. Eg. an "e" with an acute accent would be 33120 * "é" 33121 * <li><i>js</i> - Use the Javascript escape style. Eg. an "e" with an acute 33122 * accent would be "\u00E9". This can also be specified as "c#" as 33123 * it uses a similar escape syntax. 33124 * <li><i>c</i> - Use the C/C++ escape style, which is similar to the the 33125 * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an 33126 * acute accent would be "\x00E9". This can also be specified as "c++". 33127 * <li><i>java</i> - Use the Java escape style. This is very similar to the 33128 * the Javascript style, but the backslash has to be escaped twice. Eg. an 33129 * "e" with an acute accent would be "\\u00E9". This can also be specified 33130 * as "ruby", as Ruby uses a similar escape syntax with double backslashes. 33131 * <li><i>perl</i> - Use the Perl escape style. Eg. an "e" with an acute 33132 * accent would be "\N{U+00E9}" 33133 * </ul> 33134 * The default if this style is not specified is "js" for Javascript. 33135 * 33136 * <li><i>onLoad</i> - a callback function to call when this object is fully 33137 * loaded. When the onLoad option is given, this class will attempt to 33138 * load any missing data using the ilib loader callback. 33139 * When the constructor is done (even if the data is already preassembled), the 33140 * onLoad function is called with the current instance as a parameter, so this 33141 * callback can be used with preassembled or dynamic loading or a mix of the two. 33142 * 33143 * <li><i>sync</i> - tell whether to load any missing data synchronously or 33144 * asynchronously. If this option is given as "false", then the "onLoad" 33145 * callback must be given, because the instance returned from this constructor will 33146 * not be usable for a while. 33147 * 33148 * <li><i>loadParams</i> - an object containing parameters to pass to the 33149 * loader callback function when data is missing. The parameters are not 33150 * interpretted or modified in any way. They are simply passed along. The object 33151 * may contain any property/value pairs as long as the calling code is in 33152 * agreement with the loader callback function as to what those parameters mean. 33153 * </ul> 33154 * 33155 * If this copy of ilib is pre-assembled and all the data is already available, 33156 * or if the data was already previously loaded, then this constructor will call 33157 * the onLoad callback immediately when the initialization is done. 33158 * If the onLoad option is not given, this class will only attempt to load any 33159 * missing data synchronously. 33160 * 33161 * @constructor 33162 * @see {ilib.setLoaderCallback} for information about registering a loader callback instance 33163 * @extends Charmap 33164 * @param {Object=} options options which govern the construction of this instance 33165 */ 33166 var CharmapTable = function(options) { 33167 var sync = true, 33168 loadParams = undefined; 33169 33170 // console.log("CharmapTable: constructor with options: " + JSON.stringify(options)); 33171 33172 this.parent.call(this, options); 33173 33174 if (options) { 33175 if (typeof(options.charset) === "object") { 33176 this.charset = options.charset; 33177 } else if (typeof(options.name) !== 'undefined') { 33178 this.charset = new Charset({name: options.name}); 33179 } 33180 33181 if (typeof(options.sync) !== 'undefined') { 33182 sync = (options.sync == true); 33183 } 33184 33185 if (typeof(options.loadParams) !== 'undefined') { 33186 loadParams = options.loadParams; 33187 } 33188 } 33189 33190 if (!this.charset) { 33191 this.charset = new Charset({name: "ISO-8859-15"}); 33192 } 33193 33194 this._calcExpansionFactor(); 33195 33196 if (!Charmap.cache) { 33197 Charmap.cache = {}; 33198 } 33199 33200 Utils.loadData({ 33201 object: Charmap, 33202 locale: "-", 33203 nonlocale: true, 33204 name: "charmaps/" + this.charset.getName() + ".json", 33205 sync: sync, 33206 loadParams: loadParams, 33207 callback: ilib.bind(this, function (mapping) { 33208 if (!mapping) { 33209 throw "No mapping found for " + this.charset.getName(); 33210 } 33211 33212 /** @type {{from:Object,to:Object}} */ 33213 this.map = mapping; 33214 if (options && typeof(options.onLoad) === 'function') { 33215 options.onLoad(this); 33216 } 33217 }) 33218 }); 33219 }; 33220 33221 CharmapTable.prototype = new Charmap(); 33222 CharmapTable.prototype.parent = Charmap; 33223 CharmapTable.prototype.constructor = CharmapTable; 33224 33225 /** 33226 * Walk a trie to find the value for the current position in the given array. 33227 * @private 33228 */ 33229 CharmapTable.prototype._trieWalk = function(trie, array, start) { 33230 function isValue(node) { 33231 return (typeof(node) === 'string' || typeof(node) === 'number' || 33232 (typeof(node) === 'object' && ilib.isArray(node))); 33233 } 33234 33235 var lastLeaf = undefined, 33236 i = start, 33237 trienode = trie; 33238 33239 while (i < array.length) { 33240 if (typeof(trienode.__leaf) !== 'undefined') { 33241 lastLeaf = { 33242 consumed: i - start + 1, 33243 value: trienode.__leaf 33244 }; 33245 } 33246 if (array[i] === 0) { 33247 // null-terminator, so end the mapping. 33248 return { 33249 consumed: 1, 33250 value: 0 33251 }; 33252 } else if (typeof(trienode[array[i]]) !== 'undefined') { 33253 // we have a mapping 33254 if (isValue(trienode[array[i]])) { 33255 // it is a leaf node 33256 return { 33257 consumed: i - start + 1, 33258 value: trienode[array[i]] 33259 }; 33260 } else { 33261 // it is an intermediate node 33262 trienode = trienode[array[i++]]; 33263 } 33264 } else { 33265 // no mapping for this array element, so return the last known 33266 // leaf. If none, this will return undefined. 33267 return lastLeaf; 33268 } 33269 } 33270 33271 return undefined; 33272 }; 33273 33274 /** 33275 * Map a string to the native character set. This string may be 33276 * given as an intrinsic Javascript string object or an IString 33277 * object. 33278 * 33279 * @param {string|IString} string string to map to a different 33280 * character set. 33281 * @return {Uint8Array} An array of bytes representing the string 33282 * in the native character set 33283 */ 33284 CharmapTable.prototype.mapToNative = function(string) { 33285 if (!string) { 33286 return new Uint8Array(0); 33287 } 33288 33289 var str = (string instanceof IString) ? string : new IString(string); 33290 33291 // use IString's iterator so that we take care of walking through 33292 // the code points correctly, including the surrogate pairs 33293 // var c, i = 0, it = str.charIterator(); 33294 var ret = new Uint8Array(str.length * this.expansionFactor); 33295 33296 var i = 0, j = 0; 33297 33298 while (i < string.length) { 33299 var result = this._trieWalk(this.map.from, string, i); 33300 if (result) { 33301 if (result.value) { 33302 i += result.consumed; 33303 j += this.writeNative(ret, j, result.value); 33304 } else { 33305 // null-termination 33306 i = string.length; 33307 this.writeNative(ret, j, [result.value]); 33308 } 33309 } else { 33310 // The unicode char at "i" didn't have any mapping, so 33311 // deal with the missing char 33312 j += this.writeNativeString(ret, j, this.dealWithMissingChar(string[i++])); 33313 } 33314 } 33315 33316 return ret.subarray(0, j); 33317 }; 33318 33319 /** 33320 * Map a native string to the standard Javascript charset of UTF-16. 33321 * This string may be given as an array of numbers where each number 33322 * represents a code point in the "from" charset, or as a Uint8Array 33323 * array of bytes representing the bytes of the string in order. 33324 * 33325 * @param {Array.<number>|Uint8Array} bytes bytes to map to 33326 * a Unicode string 33327 * @return {string} A string in the standard Javascript charset UTF-16 33328 */ 33329 CharmapTable.prototype.mapToUnicode = function(bytes) { 33330 var ret = ""; 33331 var i = 0; 33332 33333 while (i < bytes.length) { 33334 var result = this._trieWalk(this.map.to, bytes, i); 33335 if (result) { 33336 if (result.value) { 33337 i += result.consumed; 33338 if (typeof(result.value) === 'string') { 33339 ret += result.value; 33340 } else if (ilib.isArray(result.value)) { 33341 for (var j = 0; j < result.value.length; j++) { 33342 ret += result.value[j]; 33343 } 33344 } // else error in charmap file?? 33345 } else { 33346 // null-termination 33347 i = bytes.length; 33348 } 33349 } else { 33350 // The byte at "i" wasn't a lead byte, so start again at the 33351 // next byte instead. This may synchronize the rest 33352 // of the string. 33353 ret += this.dealWithMissingChar(bytes[i++]); 33354 } 33355 } 33356 33357 return ret; 33358 }; 33359 33360 Charmap._algorithms["CharmapTable"] = CharmapTable; 33361 33362 33363 /*< CharmapFactory.js */ 33364 /* 33365 * CharmapFactory.js - Factory class to create the right subclasses of a charmap for any 33366 * given chararacter set. 33367 * 33368 * Copyright © 2015, JEDLSoft 33369 * 33370 * Licensed under the Apache License, Version 2.0 (the "License"); 33371 * you may not use this file except in compliance with the License. 33372 * You may obtain a copy of the License at 33373 * 33374 * http://www.apache.org/licenses/LICENSE-2.0 33375 * 33376 * Unless required by applicable law or agreed to in writing, software 33377 * distributed under the License is distributed on an "AS IS" BASIS, 33378 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33379 * 33380 * See the License for the specific language governing permissions and 33381 * limitations under the License. 33382 */ 33383 33384 /* !depends ilib.js JSUtils.js Charmap.js CharmapTable.js */ 33385 // !data charset/ISO-8859-15 charmaps/ISO-8859-15 33386 33387 33388 33389 /** 33390 * Factory method to create a new instance of a character set mapping (charmap) 33391 * subclass that is appropriate for the requested charset. Charmap instances map strings to 33392 * other character sets. The charsets can be of any type, single-byte, multi-byte, 33393 * shifting, etc. <p> 33394 * 33395 * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base 33396 * character set and encoding used by Javascript itself. In order to convert 33397 * between two non-Unicode character sets, you must chain two charmap instances together 33398 * to first map to Unicode and then back to the second charset. <p> 33399 * 33400 * The options parameter controls which mapping is constructed and its behaviours. The 33401 * current list of supported options are: 33402 * 33403 * <ul> 33404 * <li><i>name</i> - the name of the native charset to map to or from. This can be 33405 * given as an {@link Charset} instance or as a string that contains any commonly used name 33406 * for the character set, which is normalized to a standard IANA name. 33407 * If a name is not given, this class will default to the Western European character 33408 * set called ISO-8859-15. 33409 * 33410 * <li><i>missing</i> - specify what to do if a mapping is missing for a particular 33411 * character. For example, if you are mapping Unicode characters to a particular native 33412 * character set that does not support particular Unicode characters, the mapper will 33413 * follow the behaviour specified in this property. Valid values are: 33414 * <ul> 33415 * <li><i>skip</i> - skip any characters that do not exist in the target charset 33416 * <li><i>placeholder</i> - put a static placeholder character in the output string 33417 * wherever there is an unknown character in the input string. Use the <i>placeholder</i> 33418 * parameter to specify which character to use in this case 33419 * <li><i>escape</i> - use an escape sequence to represent the unknown character 33420 * </ul> 33421 * The default value for the missing property if not otherwise specified is "escape" 33422 * so that information is not lost. 33423 * 33424 * <li><i>placeholder</i> - specify the placeholder character to use when the 33425 * mapper cannot map a particular input character to the output string. If this 33426 * option is not specified, then the '?' (question mark) character is used where 33427 * possible. 33428 * 33429 * <li><i>escapeStyle</i> - what style of escape sequences should be used to 33430 * escape unknown characters in the input when mapping to native, and what 33431 * style of espcae sequences should be parsed when mapping to Unicode. Valid 33432 * values are: 33433 * <ul> 33434 * <li><i>html</i> - Escape the characters as HTML entities. This would use 33435 * the standard HTML 5.0 (or later) entity names where possible, and numeric 33436 * entities in all other cases. Eg. an "e" with an acute accent would be 33437 * "é" 33438 * <li><i>js</i> - Use the Javascript escape style. Eg. an "e" with an acute 33439 * accent would be "\u00E9". This can also be specified as "c#" as 33440 * it uses a similar escape syntax. 33441 * <li><i>c</i> - Use the C/C++ escape style, which is similar to the the 33442 * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an 33443 * acute accent would be "\x00E9". This can also be specified as "c++". 33444 * <li><i>java</i> - Use the Java escape style. This is very similar to the 33445 * the Javascript style, but the backslash has to be escaped twice. Eg. an 33446 * "e" with an acute accent would be "\\u00E9". This can also be specified 33447 * as "ruby", as Ruby uses a similar escape syntax with double backslashes. 33448 * <li><i>perl</i> - Use the Perl escape style. Eg. an "e" with an acute 33449 * accent would be "\N{U+00E9}" 33450 * </ul> 33451 * The default if this style is not specified is "js" for Javascript. 33452 * 33453 * <li><i>onLoad</i> - a callback function to call when this object is fully 33454 * loaded. When the onLoad option is given, this class will attempt to 33455 * load any missing data using the ilib loader callback. 33456 * When the constructor is done (even if the data is already preassembled), the 33457 * onLoad function is called with the current instance as a parameter, so this 33458 * callback can be used with preassembled or dynamic loading or a mix of the two. 33459 * 33460 * <li><i>sync</i> - tell whether to load any missing data synchronously or 33461 * asynchronously. If this option is given as "false", then the "onLoad" 33462 * callback must be given, because the instance returned from this constructor will 33463 * not be usable for a while. 33464 * 33465 * <li><i>loadParams</i> - an object containing parameters to pass to the 33466 * loader callback function when data is missing. The parameters are not 33467 * interpretted or modified in any way. They are simply passed along. The object 33468 * may contain any property/value pairs as long as the calling code is in 33469 * agreement with the loader callback function as to what those parameters mean. 33470 * </ul> 33471 * 33472 * If this copy of ilib is pre-assembled and all the data is already available, 33473 * or if the data was already previously loaded, then this constructor will call 33474 * the onLoad callback immediately when the initialization is done. 33475 * If the onLoad option is not given, this class will only attempt to load any 33476 * missing data synchronously. 33477 * 33478 * @static 33479 * @param {Object=} options options controlling the construction of this instance, or 33480 * undefined to use the default options 33481 * @return {Charmap|undefined} an instance of a character set mapping class appropriate for 33482 * the requested charset, or undefined if no mapper could be found that supports the 33483 * requested charset 33484 */ 33485 var CharmapFactory = function(options) { 33486 var charsetName = (options && options.name) || "ISO-8859-15"; 33487 var sync = true; 33488 33489 // console.log("CharmapFactory: called with options: " + JSON.stringify(options)); 33490 33491 if (options) { 33492 if (typeof(options.sync) === 'boolean') { 33493 sync = options.sync; 33494 } 33495 } 33496 33497 var instance; 33498 33499 new Charset({ 33500 name: charsetName, 33501 sync: sync, 33502 loadParams: options && options.loadParams, 33503 onLoad: function (charset) { 33504 // name will be normalized already 33505 var cons, name = charset.getName(); 33506 33507 // console.log("CharmapFactory: normalized charset name: " + name); 33508 33509 if (!Charmap._algorithms[name] && ilib.isDynCode()) { 33510 // console.log("CharmapFactory: isDynCode. Doing require"); 33511 var entry = CharmapFactory._dynMap[name] || "CharmapTable"; 33512 cons = Charmap._algorithms[name] = require("./" + entry + ".js"); 33513 } 33514 33515 if (!cons) { 33516 cons = Charmap._algorithms[name] || Charmap._algorithms["CharmapTable"]; 33517 } 33518 33519 // console.log("CharmapFactory: cons is "); console.dir(cons); 33520 33521 // pass the same options through to the constructor so the subclass 33522 // has the ability to do something with if it needs to 33523 instance = cons && new cons(JSUtils.merge(options || {}, {charset: charset})); 33524 } 33525 }); 33526 33527 return instance; 33528 }; 33529 33530 33531 /** 33532 * Map standardized charset names to classes to initialize in the dynamic code model. 33533 * These classes implement algorithmic mappings instead of table-based ones. 33534 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 33535 * @private 33536 */ 33537 CharmapFactory._dynMap = { 33538 "UTF-8": "UTF8", 33539 "UTF-16": "UTF16LE", 33540 "UTF-16LE": "UTF16LE", 33541 "UTF-16BE": "UTF16BE", 33542 "US-ASCII": "Charmap" 33543 /* 33544 not implemented yet 33545 "ISO-2022-JP": "ISO2022", 33546 "ISO-2022-JP-1": "ISO2022", 33547 "ISO-2022-JP-2": "ISO2022", 33548 "ISO-2022-JP-3": "ISO2022", 33549 "ISO-2022-JP-2004": "ISO2022", 33550 "ISO-2022-CN": "ISO2022", 33551 "ISO-2022-CN-EXT": "ISO2022", 33552 "ISO-2022-KR": "ISO2022" 33553 */ 33554 }; 33555 33556 33557 /*< UTF8.js */ 33558 /* 33559 * UTF8.js - Implement Unicode Transformation Format 8-bit mappings 33560 * 33561 * Copyright © 2014-2015, JEDLSoft 33562 * 33563 * Licensed under the Apache License, Version 2.0 (the "License"); 33564 * you may not use this file except in compliance with the License. 33565 * You may obtain a copy of the License at 33566 * 33567 * http://www.apache.org/licenses/LICENSE-2.0 33568 * 33569 * Unless required by applicable law or agreed to in writing, software 33570 * distributed under the License is distributed on an "AS IS" BASIS, 33571 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33572 * 33573 * See the License for the specific language governing permissions and 33574 * limitations under the License. 33575 */ 33576 33577 // !depends Charmap.js IString.js 33578 33579 // !data charset/UTF-8 33580 33581 33582 /** 33583 * @class 33584 * Create a new UTF-8 mapping instance 33585 * @constructor 33586 * @extends Charmap 33587 */ 33588 var UTF8 = function (options) { 33589 this.charset = new Charset({name: "UTF-8"}); 33590 }; 33591 33592 UTF8.prototype = new Charmap(); 33593 UTF8.prototype.parent = Charmap; 33594 UTF8.prototype.constructor = UTF8; 33595 33596 UTF8.prototype.validate = function(bytes) { 33597 var i = 0; 33598 while (i < bytes.length) { 33599 if ((bytes[i] & 0x80) === 0) { 33600 i++; 33601 } else { 33602 var len; 33603 if ((bytes[i] & 0xC0) === 0xC0) { 33604 len = 2; 33605 } else if ((bytes[i] & 0xE0) === 0xE0) { 33606 len = 3; 33607 } else if ((bytes[i] & 0xF0) === 0xF0) { 33608 len = 4; 33609 } else { 33610 // invalid lead byte 33611 return false; 33612 } 33613 if (i + len > bytes.length) { 33614 // not enough trailing bytes 33615 return false; 33616 } 33617 for (var j = 1; j < len; j++) { 33618 // check each trailing byte to see if it has the correct form 33619 if ((bytes[i+j] & 0x80) !== 0x80) { 33620 return false; 33621 } 33622 } 33623 i += len; 33624 } 33625 } 33626 33627 return true; 33628 }; 33629 33630 UTF8.prototype.mapToUnicode = function (bytes) { 33631 if (typeof(Buffer) !== "undefined") { 33632 // nodejs can convert it quickly in native code 33633 var b = new Buffer(bytes); 33634 return b.toString("utf8"); 33635 } 33636 // otherwise we have to implement it in pure JS 33637 var ret = ""; 33638 var i = 0; 33639 while (i < bytes.length) { 33640 if (bytes[i] === 0) { 33641 // null-terminator 33642 i = bytes.length; 33643 } else if ((bytes[i] & 0x80) === 0) { 33644 // 1 byte char 33645 ret += String.fromCharCode(bytes[i++]); 33646 } else if ((bytes[i] & 0xE0) === 0xC0) { 33647 // 2 byte char 33648 if (i + 1 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80) { 33649 throw "invalid utf-8 bytes"; 33650 } 33651 // xxx xxyyyyyy 33652 ret += String.fromCharCode((bytes[i] & 0x1F) << 6 | (bytes[i+1] & 0x3F)); 33653 i += 2; 33654 } else if ((bytes[i] & 0xF0) === 0xE0) { 33655 // 3 byte char 33656 if (i + 2 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80) { 33657 throw "invalid utf-8 bytes"; 33658 } 33659 // xxxxyyyy yyzzzzzz 33660 ret += String.fromCharCode((bytes[i] & 0xF) << 12 | (bytes[i+1] & 0x3F) << 6 | (bytes[i+2] & 0x3F)); 33661 i += 3; 33662 } else if ((bytes[i] & 0xF8) === 0xF0) { 33663 // 4 byte char 33664 if (i + 3 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80 || (bytes[i+3] & 0x80) !== 0x80) { 33665 throw "invalid utf-8 bytes"; 33666 } 33667 // wwwxx xxxxyyyy yyzzzzzz 33668 ret += IString.fromCodePoint((bytes[i] & 0x7) << 18 | (bytes[i+1] & 0x3F) << 12 | (bytes[i+2] & 0x3F) << 6 | (bytes[i+3] & 0x3F)); 33669 i += 4; 33670 } else { 33671 throw "invalid utf-8 bytes"; 33672 } 33673 } 33674 33675 return ret; 33676 }; 33677 33678 UTF8.prototype.mapToNative = function(str) { 33679 if (typeof(Buffer) !== "undefined") { 33680 // nodejs can convert it quickly in native code 33681 var b = new Buffer(str, "utf8"); 33682 return new Uint8Array(b); 33683 } 33684 // otherwise we have to implement it in pure JS 33685 var istr = (str instanceof IString) ? str : new IString(str); 33686 33687 // step through the surrogate pairs as single code points by using 33688 // IString's iterator 33689 var it = istr.iterator(); 33690 33691 // multiply by 4 because the max size of a UTF-8 char is 4 bytes, so 33692 // this will at least get us enough room to encode everything. Add 1 33693 // for the null terminator 33694 var ret = new Uint8Array(istr.length * 4 + 1); 33695 var i = 0; 33696 33697 while (it.hasNext()) { 33698 var c = it.next(); 33699 if (c > 0x7F) { 33700 if (c > 0x7FF) { 33701 if (c > 0xFFFF) { 33702 // astral planes char 33703 ret[i] = 0xF0 | ((c >> 18) & 0x3); 33704 ret[i+1] = 0x80 | ((c >> 12) & 0x3F); 33705 ret[i+2] = 0x80 | ((c >> 6) & 0x3F); 33706 ret[i+3] = 0x80 | (c & 0x3F); 33707 33708 i += 4; 33709 } else { 33710 ret[i] = 0xE0 | ((c >> 12) & 0xF); 33711 ret[i+1] = 0x80 | ((c >> 6) & 0x3F); 33712 ret[i+2] = 0x80 | (c & 0x3F); 33713 33714 i += 3; 33715 } 33716 } else { 33717 ret[i] = 0xC0 | ((c >> 6) & 0x1F); 33718 ret[i+1] = 0x80 | (c & 0x3F); 33719 33720 i += 2; 33721 } 33722 } else { 33723 ret[i++] = (c & 0x7F); 33724 } 33725 } 33726 ret[i] = 0; // null-terminate it 33727 33728 return ret; 33729 }; 33730 33731 Charmap._algorithms["UTF-8"] = UTF8; 33732 33733 33734 /*< UTF16BE.js */ 33735 /* 33736 * UTF16BE.js - Implement Unicode Transformation Format 16-bit, 33737 * Big Endian mappings 33738 * 33739 * Copyright © 2014-2015, JEDLSoft 33740 * 33741 * Licensed under the Apache License, Version 2.0 (the "License"); 33742 * you may not use this file except in compliance with the License. 33743 * You may obtain a copy of the License at 33744 * 33745 * http://www.apache.org/licenses/LICENSE-2.0 33746 * 33747 * Unless required by applicable law or agreed to in writing, software 33748 * distributed under the License is distributed on an "AS IS" BASIS, 33749 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33750 * 33751 * See the License for the specific language governing permissions and 33752 * limitations under the License. 33753 */ 33754 33755 // !depends Charmap.js 33756 33757 // !data charset/UTF-16 charset/UTF-16BE 33758 33759 33760 /** 33761 * @class 33762 * Create a new UTF-16BE mapping instance 33763 * @constructor 33764 * @extends Charmap 33765 */ 33766 var UTF16BE = function (options) { 33767 this.charset = new Charset({name: "UTF-16BE"}); 33768 }; 33769 33770 UTF16BE.prototype = new Charmap(); 33771 UTF16BE.prototype.parent = Charmap; 33772 UTF16BE.prototype.constructor = UTF16BE; 33773 33774 UTF16BE.prototype.mapToUnicode = function (bytes) { 33775 // nodejs can't convert big-endian in native code, 33776 // so we would have to flip each Uint16 ourselves. 33777 // At that point, it's just quicker to convert 33778 // in JS code anyways 33779 var ret = ""; 33780 for (var i = 0; i < bytes.length; i += 2) { 33781 ret += String.fromCharCode(bytes[i] << 8 | bytes[i+1]); 33782 } 33783 33784 return ret; 33785 }; 33786 33787 UTF16BE.prototype.mapToNative = function(str) { 33788 // nodejs can't convert big-endian in native code, 33789 // so we would have to flip each Uint16 ourselves. 33790 // At that point, it's just quicker to convert 33791 // in JS code anyways 33792 var ret = new Uint8Array(str.length * 2 + 2); 33793 var c; 33794 for (var i = 0; i < str.length; i++) { 33795 c = str.charCodeAt(i); 33796 ret[i*2] = (c >> 8) & 0xFF; 33797 ret[i*2+1] = c & 0xFF; 33798 } 33799 // double null terminate it, just in case 33800 ret[i*2+1] = 0; 33801 ret[i*2+2] = 0; 33802 33803 return ret; 33804 }; 33805 33806 Charmap._algorithms["UTF-16BE"] = UTF16BE; 33807 33808 33809 /*< UTF16LE.js */ 33810 /* 33811 * UTF16LE.js - Implement Unicode Transformation Format 16 bit, 33812 * Little Endian mappings 33813 * 33814 * Copyright © 2014-2015, JEDLSoft 33815 * 33816 * Licensed under the Apache License, Version 2.0 (the "License"); 33817 * you may not use this file except in compliance with the License. 33818 * You may obtain a copy of the License at 33819 * 33820 * http://www.apache.org/licenses/LICENSE-2.0 33821 * 33822 * Unless required by applicable law or agreed to in writing, software 33823 * distributed under the License is distributed on an "AS IS" BASIS, 33824 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33825 * 33826 * See the License for the specific language governing permissions and 33827 * limitations under the License. 33828 */ 33829 33830 // !depends Charmap.js 33831 33832 // !data charset/UTF-16 charset/UTF-16LE 33833 33834 33835 /** 33836 * @class 33837 * Create a new UTF-16LE mapping instance 33838 * @constructor 33839 * @extends Charmap 33840 */ 33841 var UTF16LE = function (options) { 33842 this.charset = new Charset({name: "UTF-16LE"}); 33843 }; 33844 33845 UTF16LE.prototype = new Charmap(); 33846 UTF16LE.prototype.parent = Charmap; 33847 UTF16LE.prototype.constructor = UTF16LE; 33848 33849 UTF16LE.prototype.mapToUnicode = function (bytes) { 33850 if (typeof(Buffer) !== "undefined") { 33851 // nodejs can convert it quickly in native code 33852 var b = new Buffer(bytes); 33853 return b.toString("utf16le"); 33854 } 33855 // otherwise we have to implement it in pure JS 33856 var ret = ""; 33857 for (var i = 0; i < bytes.length; i += 2) { 33858 ret += String.fromCharCode(bytes[i+1] << 8 | bytes[i]); 33859 } 33860 33861 return ret; 33862 }; 33863 33864 UTF16LE.prototype.mapToNative = function(str) { 33865 if (typeof(Buffer) !== "undefined") { 33866 // nodejs can convert it quickly in native code 33867 var b = new Buffer(str, "utf16le"); 33868 return new Uint8Array(b); 33869 } 33870 // otherwise we have to implement it in pure JS 33871 var ret = new Uint8Array(str.length * 2 + 2); 33872 var c; 33873 for (var i = 0; i < str.length; i++) { 33874 c = str.charCodeAt(i); 33875 ret[i*2] = c & 0xFF; 33876 ret[i*2+1] = (c >> 8) & 0xFF; 33877 } 33878 // double null terminate it, just in case 33879 ret[i*2+1] = 0; 33880 ret[i*2+2] = 0; 33881 33882 return ret; 33883 }; 33884 33885 Charmap._algorithms["UTF-16"] = UTF16LE; 33886 Charmap._algorithms["UTF-16LE"] = UTF16LE; 33887 33888 33889 /*< /mnt/Terasaur/root/home/edwin/ht/ilib/js/lib/ilib-full-inc.js */ 33890 /** 33891 * @license 33892 * Copyright © 2012-2015, JEDLSoft 33893 * 33894 * Licensed under the Apache License, Version 2.0 (the "License"); 33895 * you may not use this file except in compliance with the License. 33896 * You may obtain a copy of the License at 33897 * 33898 * http://www.apache.org/licenses/LICENSE-2.0 33899 * 33900 * Unless required by applicable law or agreed to in writing, software 33901 * distributed under the License is distributed on an "AS IS" BASIS, 33902 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33903 * 33904 * See the License for the specific language governing permissions and 33905 * limitations under the License. 33906 */ 33907 33908 /* 33909 * ilib-full-inc.js - metafile that includes all other js files 33910 */ 33911 33912 /* !depends 33913 ilib.js 33914 DateRngFmt.js 33915 IDate.js 33916 DateFactory.js 33917 HebrewDate.js 33918 HebrewCal.js 33919 IslamicCal.js 33920 IslamicDate.js 33921 JulianCal.js 33922 JulianDate.js 33923 GregorianCal.js 33924 GregorianDate.js 33925 ThaiSolarCal.js 33926 ThaiSolarDate.js 33927 PersianCal.js 33928 PersianDate.js 33929 PersianAlgoCal.js 33930 PersianAlgoDate.js 33931 HanCal.js 33932 HanDate.js 33933 EthiopicCal.js 33934 EthiopicDate.js 33935 CopticCal.js 33936 CopticDate.js 33937 INumber.js 33938 NumFmt.js 33939 JulianDay.js 33940 DateFmt.js 33941 Calendar.js 33942 CalendarFactory.js 33943 Utils.js 33944 Locale.js 33945 IString.js 33946 DurationFmt.js 33947 ResBundle.js 33948 CType.js 33949 LocaleInfo.js 33950 DateRngFmt.js 33951 isAlnum.js 33952 isAlpha.js 33953 isAscii.js 33954 isBlank.js 33955 isCntrl.js 33956 isDigit.js 33957 isGraph.js 33958 isIdeo.js 33959 isLower.js 33960 isPrint.js 33961 isPunct.js 33962 isSpace.js 33963 isUpper.js 33964 isXdigit.js 33965 isScript.js 33966 ScriptInfo.js 33967 Name.js 33968 NameFmt.js 33969 Address.js 33970 AddressFmt.js 33971 Collator.js 33972 nfkc/all.js 33973 LocaleMatcher.js 33974 NormString.js 33975 CaseMapper.js 33976 GlyphString.js 33977 PhoneFmt.js 33978 PhoneGeoLocator.js 33979 PhoneNumber.js 33980 Measurement.js 33981 MeasurementFactory.js 33982 UnitFmt.js 33983 LengthUnit.js 33984 VelocityUnit.js 33985 DigitalStorageUnit.js 33986 TemperatureUnit.js 33987 UnknownUnit.js 33988 TimeUnit.js 33989 MassUnit.js 33990 AreaUnit.js 33991 FuelConsumptionUnit.js 33992 VolumeUnit.js 33993 EnergyUnit.js 33994 Charset.js 33995 Charmap.js 33996 CharmapFactory.js 33997 CharmapTable.js 33998 UTF8.js 33999 UTF16BE.js 34000 UTF16LE.js 34001 */ 34002 34003