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 "11.0.006" 26 */ 27 var ilib = ilib || {}; 28 29 /** @private */ 30 ilib._ver = function() { 31 return "11.0.006" 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() || "11.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 {null|Object.<string,Array.<Array.<number>>>} */ ctype: null, 63 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_c: null, 64 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_l: null, 65 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_m: null, 66 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_p: null, 67 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_z: null, 68 /** @type {null|Object.<string,Array.<Array.<number>>>} */ scriptToRange: null, 69 /** @type {null|Object.<string,string|Object.<string|Object.<string,string>>>} */ dateformats: null, 70 /** @type {null|Array.<string>} */ timezones: [] 71 }; 72 73 /* 74 if (typeof(window) !== 'undefined') { 75 window["ilib"] = ilib; 76 } 77 */ 78 79 // export ilib for use as a module in nodejs 80 if (typeof(module) !== 'undefined') { 81 82 module.exports.ilib = ilib; // for backwards compatibility with older versions of ilib 83 } 84 85 /** 86 * Sets the pseudo locale. Pseudolocalization (or pseudo-localization) is used for testing 87 * internationalization aspects of software. Instead of translating the text of the software 88 * into a foreign language, as in the process of localization, the textual elements of an application 89 * are replaced with an altered version of the original language.These specific alterations make 90 * the original words appear readable, but include the most problematic characteristics of 91 * the world's languages: varying length of text or characters, language direction, and so on. 92 * Regular Latin pseudo locale: eu-ES and RTL pseudo locale: ps-AF 93 * 94 * @param {string|undefined|null} localename the locale specifier for the pseudo locale 95 */ 96 ilib.setAsPseudoLocale = function (localename) { 97 if (localename) { 98 ilib.pseudoLocales.push(localename) 99 } 100 }; 101 102 /** 103 * Reset the list of pseudo locales back to the default single locale of zxx-XX. 104 * @static 105 */ 106 ilib.clearPseudoLocales = function() { 107 ilib.pseudoLocales = [ 108 "zxx-XX", 109 "zxx-Cyrl-XX", 110 "zxx-Hans-XX", 111 "zxx-Hebr-XX" 112 ]; 113 }; 114 115 ilib.clearPseudoLocales(); 116 117 /** 118 * Return the name of the platform 119 * @private 120 * @static 121 * @return {string} string naming the platform 122 */ 123 ilib._getPlatform = function () { 124 if (!ilib._platform) { 125 try { 126 if (typeof(java.lang.Object) !== 'undefined') { 127 ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino"; 128 return ilib._platform; 129 } 130 } catch (e) {} 131 132 if (typeof(process) !== 'undefined' && typeof(module) !== 'undefined') { 133 ilib._platform = "nodejs"; 134 } else if (typeof(Qt) !== 'undefined') { 135 ilib._platform = "qt"; 136 } else if (typeof(window) !== 'undefined') { 137 ilib._platform = (typeof(PalmSystem) !== 'undefined') ? "webos" : "browser"; 138 } else { 139 ilib._platform = "unknown"; 140 } 141 } 142 return ilib._platform; 143 }; 144 145 /** 146 * If this ilib is running in a browser, return the name of that browser. 147 * @private 148 * @static 149 * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie", 150 * "safari", or "opera"), or undefined if this is not running in a browser or if 151 * the browser name could not be determined 152 */ 153 ilib._getBrowser = function () { 154 var browser = undefined; 155 if (ilib._getPlatform() === "browser") { 156 if (navigator && navigator.userAgent) { 157 if (navigator.userAgent.indexOf("Firefox") > -1) { 158 browser = "firefox"; 159 } 160 if (navigator.userAgent.indexOf("Opera") > -1) { 161 browser = "opera"; 162 } 163 if (navigator.userAgent.indexOf("Chrome") > -1) { 164 browser = "chrome"; 165 } 166 if (navigator.userAgent.indexOf(" .NET") > -1) { 167 browser = "ie"; 168 } 169 if (navigator.userAgent.indexOf("Safari") > -1) { 170 // chrome also has the string Safari in its userAgent, but the chrome case is 171 // already taken care of above 172 browser = "safari"; 173 } 174 } 175 } 176 return browser; 177 }; 178 179 /** 180 * Return true if the global variable is defined on this platform. 181 * @private 182 * @static 183 * @param {string} name the name of the variable to check 184 * @return {boolean} true if the global variable is defined on this platform, false otherwise 185 */ 186 ilib._isGlobal = function(name) { 187 switch (ilib._getPlatform()) { 188 case "rhino": 189 var top = (function() { 190 return (typeof global === 'object') ? global : this; 191 })(); 192 return typeof(top[name]) !== 'undefined'; 193 case "nodejs": 194 case "trireme": 195 var root = typeof(global) !== 'undefined' ? global : this; 196 return root && typeof(root[name]) !== 'undefined'; 197 case "qt": 198 return false; 199 default: 200 try { 201 return window && typeof(window[name]) !== 'undefined'; 202 } catch (e) { 203 return false; 204 } 205 } 206 }; 207 208 /** 209 * Sets the default locale for all of ilib. This locale will be used 210 * when no explicit locale is passed to any ilib class. If the default 211 * locale is not set, ilib will attempt to use the locale of the 212 * environment it is running in, if it can find that. If not, it will 213 * default to the locale "en-US". If a type of parameter is string, 214 * ilib will take only well-formed BCP-47 tag <p> 215 * 216 * 217 * @static 218 * @param {string|undefined|null} spec the locale specifier for the default locale 219 */ 220 ilib.setLocale = function (spec) { 221 if (typeof(spec) === 'string' || !spec) { 222 ilib.locale = spec; 223 } 224 // else ignore other data types, as we don't have the dependencies 225 // to look into them to find a locale 226 }; 227 228 /** 229 * Return the default locale for all of ilib if one has been set. This 230 * locale will be used when no explicit locale is passed to any ilib 231 * class. If the default 232 * locale is not set, ilib will attempt to use the locale of the 233 * environment it is running in, if it can find that. If not, it will 234 * default to the locale "en-US".<p> 235 * 236 * 237 * @static 238 * @return {string} the locale specifier for the default locale 239 */ 240 ilib.getLocale = function () { 241 if (typeof(ilib.locale) !== 'string') { 242 var plat = ilib._getPlatform(); 243 switch (plat) { 244 case 'browser': 245 // running in a browser 246 ilib.locale = navigator.language.substring(0,3) + navigator.language.substring(3,5).toUpperCase(); // FF/Opera/Chrome/Webkit 247 if (!ilib.locale) { 248 // IE on Windows 249 var lang = typeof(navigator.browserLanguage) !== 'undefined' ? 250 navigator.browserLanguage : 251 (typeof(navigator.userLanguage) !== 'undefined' ? 252 navigator.userLanguage : 253 (typeof(navigator.systemLanguage) !== 'undefined' ? 254 navigator.systemLanguage : 255 undefined)); 256 if (typeof(lang) !== 'undefined' && lang) { 257 // for some reason, MS uses lower case region tags 258 ilib.locale = lang.substring(0,3) + lang.substring(3,5).toUpperCase(); 259 } 260 } 261 break; 262 case 'webos': 263 // webOS 264 if (typeof(PalmSystem.locales) !== 'undefined' && 265 typeof(PalmSystem.locales.UI) != 'undefined' && 266 PalmSystem.locales.UI.length > 0) { 267 ilib.locale = PalmSystem.locales.UI; 268 } else if (typeof(PalmSystem.locale) !== 'undefined') { 269 ilib.locale = PalmSystem.locale; 270 } 271 break; 272 case 'rhino': 273 if (typeof(environment) !== 'undefined' && environment.user && typeof(environment.user.language) === 'string' && environment.user.language.length > 0) { 274 // running under plain rhino 275 ilib.locale = environment.user.language; 276 if (typeof(environment.user.country) === 'string' && environment.user.country.length > 0) { 277 ilib.locale += '-' + environment.user.country; 278 } 279 } 280 break; 281 case "trireme": 282 // under trireme on rhino emulating nodejs 283 var lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL; 284 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 285 // where language and region are the correct ISO codes separated by 286 // an underscore. This translate it back to the BCP-47 form. 287 if (lang && typeof(lang) !== 'undefined') { 288 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 289 } 290 break; 291 case 'nodejs': 292 // running under nodejs 293 var lang = process.env.LANG || process.env.LC_ALL; 294 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 295 // where language and region are the correct ISO codes separated by 296 // an underscore. This translate it back to the BCP-47 form. 297 if (lang && typeof(lang) !== 'undefined') { 298 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 299 } 300 break; 301 case 'qt': 302 // running in the Javascript engine under Qt/QML 303 var locobj = Qt.locale(); 304 var lang = locobj.name && locobj.name.replace("_", "-") || "en-US"; 305 break; 306 } 307 ilib.locale = typeof(ilib.locale) === 'string' ? ilib.locale : 'en-US'; 308 } 309 return ilib.locale; 310 }; 311 312 /** 313 * Sets the default time zone for all of ilib. This time zone will be used when 314 * no explicit time zone is passed to any ilib class. If the default time zone 315 * is not set, ilib will attempt to use the time zone of the 316 * environment it is running in, if it can find that. If not, it will 317 * default to the the UTC zone "Etc/UTC".<p> 318 * 319 * 320 * @static 321 * @param {string} tz the name of the time zone to set as the default time zone 322 */ 323 ilib.setTimeZone = function (tz) { 324 ilib.tz = tz || ilib.tz; 325 }; 326 327 /** 328 * Return the default time zone for all of ilib if one has been set. This 329 * time zone will be used when no explicit time zone is passed to any ilib 330 * class. If the default time zone 331 * is not set, ilib will attempt to use the locale of the 332 * environment it is running in, if it can find that. If not, it will 333 * default to the the zone "local".<p> 334 * 335 * 336 * @static 337 * @return {string} the default time zone for ilib 338 */ 339 ilib.getTimeZone = function() { 340 if (typeof(ilib.tz) === 'undefined') { 341 if (typeof(navigator) !== 'undefined' && typeof(navigator.timezone) !== 'undefined') { 342 // running in a browser 343 if (navigator.timezone.length > 0) { 344 ilib.tz = navigator.timezone; 345 } 346 } else if (typeof(PalmSystem) !== 'undefined' && typeof(PalmSystem.timezone) !== 'undefined') { 347 // running in webkit on webOS 348 if (PalmSystem.timezone.length > 0) { 349 ilib.tz = PalmSystem.timezone; 350 } 351 } else if (typeof(environment) !== 'undefined' && typeof(environment.user) !== 'undefined') { 352 // running under rhino 353 if (typeof(environment.user.timezone) !== 'undefined' && environment.user.timezone.length > 0) { 354 ilib.tz = environment.user.timezone; 355 } 356 } else if (typeof(process) !== 'undefined' && typeof(process.env) !== 'undefined') { 357 // running in nodejs 358 if (process.env.TZ && typeof(process.env.TZ) !== "undefined") { 359 ilib.tz = process.env.TZ; 360 } 361 } 362 363 ilib.tz = ilib.tz || "local"; 364 } 365 366 return ilib.tz; 367 }; 368 369 /** 370 * @class 371 * Defines the interface for the loader class for ilib. The main method of the 372 * loader object is loadFiles(), which loads a set of requested locale data files 373 * from where-ever it is stored. 374 * @interface 375 */ 376 ilib.Loader = function() {}; 377 378 /** 379 * Load a set of files from where-ever it is stored.<p> 380 * 381 * This is the main function define a callback function for loading missing locale 382 * data or resources. 383 * If this copy of ilib is assembled without including the required locale data 384 * or resources, then that data can be lazy loaded dynamically when it is 385 * needed by calling this method. Each ilib class will first 386 * check for the existence of data under ilib.data, and if it is not there, 387 * it will attempt to load it by calling this method of the laoder, and then place 388 * it there.<p> 389 * 390 * Suggested implementations of this method might load files 391 * directly from disk under nodejs or rhino, or within web pages, to load 392 * files from the server with XHR calls.<p> 393 * 394 * The first parameter to this method, paths, is an array of relative paths within 395 * the ilib dir structure for the 396 * requested data. These paths will already have the locale spec integrated 397 * into them, so no further tweaking needs to happen to load the data. Simply 398 * load the named files. The second 399 * parameter tells the loader whether to load the files synchronously or asynchronously. 400 * If the sync parameters is false, then the onLoad function must also be specified. 401 * The third parameter gives extra parameters to the loader passed from the calling 402 * code. This may contain any property/value pairs. The last parameter, callback, 403 * is a callback function to call when all of the data is finishing loading. Make 404 * sure to call the callback with the context of "this" so that the caller has their 405 * context back again.<p> 406 * 407 * The loader function must be able to operate either synchronously or asychronously. 408 * If the loader function is called with an undefined callback function, it is 409 * expected to load the data synchronously, convert it to javascript 410 * objects, and return the array of json objects as the return value of the 411 * function. If the loader 412 * function is called with a callback function, it may load the data 413 * synchronously or asynchronously (doesn't matter which) as long as it calls 414 * the callback function with the data converted to a javascript objects 415 * when it becomes available. If a particular file could not be loaded, the 416 * loader function should put undefined into the corresponding entry in the 417 * results array. 418 * Note that it is important that all the data is loaded before the callback 419 * is called.<p> 420 * 421 * An example implementation for nodejs might be: 422 * 423 * <pre> 424 * * 425 * var myLoader = function() {}; 426 * myLoader.prototype = new Loader(); 427 * myLoader.prototype.constructor = myLoader; 428 * myLoader.prototype.loadFiles = function(paths, sync, params, callback) { 429 * if (sync) { 430 * var ret = []; 431 * // synchronous load -- just return the result 432 * paths.forEach(function (path) { 433 * var json = fs.readFileSync(path, "utf-8"); 434 * ret.push(json ? JSON.parse(json) : undefined); 435 * }); 436 * 437 * return ret; 438 * } 439 * this.callback = callback; 440 * 441 * // asynchronous 442 * this.results = []; 443 * this._loadFilesAsync(paths); 444 * } 445 * myLoader.prototype._loadFilesAsync = function (paths) { 446 * if (paths.length > 0) { 447 * var file = paths.shift(); 448 * fs.readFile(file, "utf-8", function(err, json) { 449 * this.results.push(err ? undefined : JSON.parse(json)); 450 * // call self recursively so that the callback is only called at the end 451 * // when all the files are loaded sequentially 452 * if (paths.length > 0) { 453 * this._loadFilesAsync(paths); 454 * } else { 455 * this.callback(this.results); 456 * } 457 * }); 458 * } 459 * } 460 * 461 * // bind to "this" so that "this" is relative to your own instance 462 * ilib.setLoaderCallback(new myLoader()); 463 * </pre> 464 465 * @param {Array.<string>} paths An array of paths to load from wherever the files are stored 466 * @param {Boolean} sync if true, load the files synchronously, and false means asynchronously 467 * @param {Object} params an object with any extra parameters for the loader. These can be 468 * anything. The caller of the ilib class passes these parameters in. Presumably, the code that 469 * calls ilib and the code that provides the loader are together and can have a private 470 * agreement between them about what the parameters should contain. 471 * @param {function(Object)} callback function to call when the files are all loaded. The 472 * parameter of the callback function is the contents of the files. 473 */ 474 ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {}; 475 476 /** 477 * Return all files available for loading using this loader instance. 478 * This method returns an object where the properties are the paths to 479 * directories where files are loaded from and the values are an array 480 * of strings containing the relative paths under the directory of each 481 * file that can be loaded.<p> 482 * 483 * Example: 484 * <pre> 485 * { 486 * "/usr/share/javascript/ilib/locale": [ 487 * "dateformats.json", 488 * "aa/dateformats.json", 489 * "af/dateformats.json", 490 * "agq/dateformats.json", 491 * "ak/dateformats.json", 492 * ... 493 * "zxx/dateformats.json" 494 * ] 495 * } 496 * </pre> 497 * @returns {Object} a hash containing directory names and 498 * paths to file that can be loaded by this loader 499 */ 500 ilib.Loader.prototype.listAvailableFiles = function() {}; 501 502 /** 503 * Return true if the file in the named path is available for loading using 504 * this loader. The path may be given as an absolute path, in which case 505 * only that file is checked, or as a relative path, in which case, the 506 * relative path may appear underneath any of the directories that the loader 507 * knows about. 508 * @returns {boolean} true if the file in the named path is available for loading, and 509 * false otherwise 510 */ 511 ilib.Loader.prototype.isAvailable = function(path) {}; 512 513 /** 514 * Set the custom loader used to load ilib's locale data in your environment. 515 * The instance passed in must implement the Loader interface. See the 516 * Loader class documentation for more information about loaders. 517 * 518 * @static 519 * @param {ilib.Loader} loader class to call to access the requested data. 520 * @return {boolean} true if the loader was installed correctly, or false 521 * if not 522 */ 523 ilib.setLoaderCallback = function(loader) { 524 // only a basic check 525 if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') || 526 typeof(loader) === 'function' || typeof(loader) === 'undefined') { 527 //console.log("setting callback loader to " + (loader ? loader.name : "undefined")); 528 ilib._load = loader; 529 return true; 530 } 531 return false; 532 }; 533 534 /** 535 * Return the custom Loader instance currently in use with this instance 536 * of ilib. If there is no loader, this method returns undefined. 537 * 538 * @protected 539 * @static 540 * @return {ilib.Loader|undefined} the loader instance currently in use, or 541 * undefined if there is no such loader 542 */ 543 ilib.getLoader = function() { 544 return ilib._load; 545 }; 546 547 /** 548 * Test whether an object in an javascript array. 549 * 550 * @static 551 * @param {*} object The object to test 552 * @return {boolean} return true if the object is an array 553 * and false otherwise 554 */ 555 ilib.isArray = function(object) { 556 var o; 557 if (typeof(object) === 'object') { 558 o = /** @type {Object|null|undefined} */ object; 559 return Object.prototype.toString.call(o) === '[object Array]'; 560 } 561 return false; 562 }; 563 564 /** 565 * Extend object1 by mixing in everything from object2 into it. The objects 566 * are deeply extended, meaning that this method recursively descends the 567 * tree in the objects and mixes them in at each level. Arrays are extended 568 * by concatenating the elements of object2 onto those of object1. 569 * 570 * @static 571 * @param {Object} object1 the target object to extend 572 * @param {Object=} object2 the object to mix in to object1 573 * @return {Object} returns object1 574 */ 575 ilib.extend = function (object1, object2) { 576 var prop = undefined; 577 if (object2) { 578 for (prop in object2) { 579 // don't extend object with undefined or functions 580 if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") { 581 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 582 //console.log("Merging array prop " + prop); 583 object1[prop] = object1[prop].concat(object2[prop]); 584 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 585 //console.log("Merging object prop " + prop); 586 if (prop !== "ilib") { 587 object1[prop] = ilib.extend(object1[prop], object2[prop]); 588 } 589 } else { 590 //console.log("Copying prop " + prop); 591 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 592 object1[prop] = object2[prop]; 593 } 594 } 595 } 596 } 597 return object1; 598 }; 599 600 ilib.extend2 = function (object1, object2) { 601 var prop = undefined; 602 if (object2) { 603 for (prop in object2) { 604 // don't extend object with undefined or functions 605 if (prop && typeof(object2[prop]) !== 'undefined') { 606 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 607 //console.log("Merging array prop " + prop); 608 object1[prop] = object1[prop].concat(object2[prop]); 609 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 610 //console.log("Merging object prop " + prop); 611 if (prop !== "ilib") { 612 object1[prop] = ilib.extend2(object1[prop], object2[prop]); 613 } 614 } else { 615 //console.log("Copying prop " + prop); 616 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 617 object1[prop] = object2[prop]; 618 } 619 } 620 } 621 } 622 return object1; 623 }; 624 625 /** 626 * If Function.prototype.bind does not exist in this JS engine, this 627 * function reimplements it in terms of older JS functions. 628 * bind() doesn't exist in many older browsers. 629 * 630 * @static 631 * @param {Object} scope object that the method should operate on 632 * @param {function(...)} method method to call 633 * @return {function(...)|undefined} function that calls the given method 634 * in the given scope with all of its arguments properly attached, or 635 * undefined if there was a problem with the arguments 636 */ 637 ilib.bind = function(scope, method/*, bound arguments*/){ 638 if (!scope || !method) { 639 return undefined; 640 } 641 642 /** @protected 643 * @param {Arguments} inArrayLike 644 * @param {number=} inOffset 645 */ 646 function cloneArray(inArrayLike, inOffset) { 647 var arr = []; 648 for(var i = inOffset || 0, l = inArrayLike.length; i<l; i++){ 649 arr.push(inArrayLike[i]); 650 } 651 return arr; 652 } 653 654 if (typeof(method) === 'function') { 655 var func, args = cloneArray(arguments, 2); 656 if (typeof(method.bind) === 'function') { 657 func = method.bind.apply(method, [scope].concat(args)); 658 } else { 659 func = function() { 660 var nargs = cloneArray(arguments); 661 // invoke with collected args 662 return method.apply(scope, args.concat(nargs)); 663 }; 664 } 665 return func; 666 } 667 return undefined; 668 }; 669 670 /** 671 * @private 672 */ 673 ilib._dyncode = false; 674 675 /** 676 * Return true if this copy of ilib is using dynamically loaded code. It returns 677 * false for pre-assembled code. 678 * 679 * @static 680 * @return {boolean} true if this ilib uses dynamically loaded code, and false otherwise 681 */ 682 ilib.isDynCode = function() { 683 return ilib._dyncode; 684 }; 685 686 /** 687 * @private 688 */ 689 ilib._dyndata = false; 690 691 /** 692 * Return true if this copy of ilib is using dynamically loaded locale data. It returns 693 * false for pre-assembled data. 694 * 695 * @static 696 * @return {boolean} true if this ilib uses dynamically loaded locale data, and false otherwise 697 */ 698 ilib.isDynData = function() { 699 return ilib._dyndata; 700 }; 701 702 ilib._loadtime = new Date().getTime(); 703 /*< JSUtils.js */ 704 /* 705 * JSUtils.js - Misc utilities to work around Javascript engine differences 706 * 707 * Copyright © 2013-2015, JEDLSoft 708 * 709 * Licensed under the Apache License, Version 2.0 (the "License"); 710 * you may not use this file except in compliance with the License. 711 * You may obtain a copy of the License at 712 * 713 * http://www.apache.org/licenses/LICENSE-2.0 714 * 715 * Unless required by applicable law or agreed to in writing, software 716 * distributed under the License is distributed on an "AS IS" BASIS, 717 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 718 * 719 * See the License for the specific language governing permissions and 720 * limitations under the License. 721 */ 722 723 // !depends ilib.js 724 725 726 var JSUtils = {}; 727 728 /** 729 * Perform a shallow copy of the source object to the target object. This only 730 * copies the assignments of the source properties to the target properties, 731 * but not recursively from there.<p> 732 * 733 * 734 * @static 735 * @param {Object} source the source object to copy properties from 736 * @param {Object} target the target object to copy properties into 737 */ 738 JSUtils.shallowCopy = function (source, target) { 739 var prop = undefined; 740 if (source && target) { 741 for (prop in source) { 742 if (prop !== undefined && typeof(source[prop]) !== 'undefined') { 743 target[prop] = source[prop]; 744 } 745 } 746 } 747 }; 748 749 /** 750 * Perform a recursive deep copy from the "from" object to the "deep" object. 751 * 752 * @static 753 * @param {Object} from the object to copy from 754 * @param {Object} to the object to copy to 755 * @return {Object} a reference to the the "to" object 756 */ 757 JSUtils.deepCopy = function(from, to) { 758 var prop; 759 760 for (prop in from) { 761 if (prop) { 762 if (typeof(from[prop]) === 'object') { 763 to[prop] = {}; 764 JSUtils.deepCopy(from[prop], to[prop]); 765 } else { 766 to[prop] = from[prop]; 767 } 768 } 769 } 770 return to; 771 }; 772 773 /** 774 * Map a string to the given set of alternate characters. If the target set 775 * does not contain a particular character in the input string, then that 776 * character will be copied to the output unmapped. 777 * 778 * @static 779 * @param {string} str a string to map to an alternate set of characters 780 * @param {Array.<string>|Object} map a mapping to alternate characters 781 * @return {string} the source string where each character is mapped to alternate characters 782 */ 783 JSUtils.mapString = function (str, map) { 784 var mapped = ""; 785 if (map && str) { 786 for (var i = 0; i < str.length; i++) { 787 var c = str.charAt(i); // TODO use a char iterator? 788 mapped += map[c] || c; 789 } 790 } else { 791 mapped = str; 792 } 793 return mapped; 794 }; 795 796 /** 797 * Check if an object is a member of the given array. If this javascript engine 798 * support indexOf, it is used directly. Otherwise, this function implements it 799 * itself. The idea is to make sure that you can use the quick indexOf if it is 800 * available, but use a slower implementation in older engines as well. 801 * 802 * @static 803 * @param {Array.<Object>} array array to search 804 * @param {Object} obj object being sought. This should be of the same type as the 805 * members of the array being searched. If not, this function will not return 806 * any results. 807 * @return {number} index of the object in the array, or -1 if it is not in the array. 808 */ 809 JSUtils.indexOf = function(array, obj) { 810 if (!array || !obj) { 811 return -1; 812 } 813 if (typeof(array.indexOf) === 'function') { 814 return array.indexOf(obj); 815 } else { 816 for (var i = 0; i < array.length; i++) { 817 if (array[i] === obj) { 818 return i; 819 } 820 } 821 return -1; 822 } 823 }; 824 825 /** 826 * Convert a string into the hexadecimal representation 827 * of the Unicode characters in that string. 828 * 829 * @static 830 * @param {string} string The string to convert 831 * @param {number=} limit the number of digits to use to represent the character (1 to 8) 832 * @return {string} a hexadecimal representation of the 833 * Unicode characters in the input string 834 */ 835 JSUtils.toHexString = function(string, limit) { 836 var i, 837 result = "", 838 lim = (limit && limit < 9) ? limit : 4; 839 840 if (!string) { 841 return ""; 842 } 843 for (i = 0; i < string.length; i++) { 844 var ch = string.charCodeAt(i).toString(16); 845 result += "00000000".substring(0, lim-ch.length) + ch; 846 } 847 return result.toUpperCase(); 848 }; 849 850 /** 851 * Test whether an object in a Javascript Date. 852 * 853 * @static 854 * @param {*} object The object to test 855 * @return {boolean} return true if the object is a Date 856 * and false otherwise 857 */ 858 JSUtils.isDate = function(object) { 859 var o; 860 if (typeof(object) === 'object') { 861 o = /** @type {Object|null|undefined} */ object; 862 return Object.prototype.toString.call(o) === '[object Date]'; 863 } 864 return false; 865 }; 866 867 /** 868 * Merge the properties of object2 into object1 in a deep manner and return a merged 869 * object. If the property exists in both objects, the value in object2 will overwrite 870 * the value in object1. If a property exists in object1, but not in object2, its value 871 * will not be touched. If a property exists in object2, but not in object1, it will be 872 * added to the merged result.<p> 873 * 874 * Name1 and name2 are for creating debug output only. They are not necessary.<p> 875 * 876 * 877 * @static 878 * @param {*} object1 the object to merge into 879 * @param {*} object2 the object to merge 880 * @param {boolean=} replace if true, replace the array elements in object1 with those in object2. 881 * If false, concatenate array elements in object1 with items in object2. 882 * @param {string=} name1 name of the object being merged into 883 * @param {string=} name2 name of the object being merged in 884 * @return {Object} the merged object 885 */ 886 JSUtils.merge = function (object1, object2, replace, name1, name2) { 887 var prop = undefined, 888 newObj = {}; 889 for (prop in object1) { 890 if (prop && typeof(object1[prop]) !== 'undefined') { 891 newObj[prop] = object1[prop]; 892 } 893 } 894 for (prop in object2) { 895 if (prop && typeof(object2[prop]) !== 'undefined') { 896 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 897 if (typeof(replace) !== 'boolean' || !replace) { 898 newObj[prop] = [].concat(object1[prop]); 899 newObj[prop] = newObj[prop].concat(object2[prop]); 900 } else { 901 newObj[prop] = object2[prop]; 902 } 903 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 904 newObj[prop] = JSUtils.merge(object1[prop], object2[prop], replace); 905 } else { 906 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 907 if (name1 && name2 && newObj[prop] == object2[prop]) { 908 console.log("Property " + prop + " in " + name1 + " is being overridden by the same value in " + name2); 909 } 910 newObj[prop] = object2[prop]; 911 } 912 } 913 } 914 return newObj; 915 }; 916 917 /** 918 * Return true if the given object has no properties.<p> 919 * 920 * 921 * @static 922 * @param {Object} obj the object to check 923 * @return {boolean} true if the given object has no properties, false otherwise 924 */ 925 JSUtils.isEmpty = function (obj) { 926 var prop = undefined; 927 928 if (!obj) { 929 return true; 930 } 931 932 for (prop in obj) { 933 if (prop && typeof(obj[prop]) !== 'undefined') { 934 return false; 935 } 936 } 937 return true; 938 }; 939 940 /** 941 * @static 942 */ 943 JSUtils.hashCode = function(obj) { 944 var hash = 0; 945 946 function addHash(hash, newValue) { 947 // co-prime numbers creates a nicely distributed hash 948 hash *= 65543; 949 hash += newValue; 950 hash %= 2147483647; 951 return hash; 952 } 953 954 function stringHash(str) { 955 var hash = 0; 956 for (var i = 0; i < str.length; i++) { 957 hash = addHash(hash, str.charCodeAt(i)); 958 } 959 return hash; 960 } 961 962 switch (typeof(obj)) { 963 case 'undefined': 964 hash = 0; 965 break; 966 case 'string': 967 hash = stringHash(obj); 968 break; 969 case 'function': 970 case 'number': 971 case 'xml': 972 hash = stringHash(String(obj)); 973 break; 974 case 'boolean': 975 hash = obj ? 1 : 0; 976 break; 977 case 'object': 978 var props = []; 979 for (var p in obj) { 980 if (obj.hasOwnProperty(p)) { 981 props.push(p); 982 } 983 } 984 // make sure the order of the properties doesn't matter 985 props.sort(); 986 for (var i = 0; i < props.length; i++) { 987 hash = addHash(hash, stringHash(props[i])); 988 hash = addHash(hash, JSUtils.hashCode(obj[props[i]])); 989 } 990 break; 991 } 992 993 return hash; 994 }; 995 996 997 998 999 /*< Locale.js */ 1000 /* 1001 * Locale.js - Locale specifier definition 1002 * 1003 * Copyright © 2012-2015, JEDLSoft 1004 * 1005 * Licensed under the Apache License, Version 2.0 (the "License"); 1006 * you may not use this file except in compliance with the License. 1007 * You may obtain a copy of the License at 1008 * 1009 * http://www.apache.org/licenses/LICENSE-2.0 1010 * 1011 * Unless required by applicable law or agreed to in writing, software 1012 * distributed under the License is distributed on an "AS IS" BASIS, 1013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1014 * 1015 * See the License for the specific language governing permissions and 1016 * limitations under the License. 1017 */ 1018 1019 // !depends ilib.js JSUtils.js 1020 1021 1022 /** 1023 * @class 1024 * Create a new locale instance. Locales are specified either with a specifier string 1025 * that follows the BCP-47 convention (roughly: "language-region-script-variant") or 1026 * with 4 parameters that specify the language, region, variant, and script individually.<p> 1027 * 1028 * The language is given as an ISO 639-1 two-letter, lower-case language code. You 1029 * can find a full list of these codes at 1030 * <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> 1031 * 1032 * The region is given as an ISO 3166-1 two-letter, upper-case region code. You can 1033 * find a full list of these codes at 1034 * <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2</a>.<p> 1035 * 1036 * The variant is any string that does not contain a dash which further differentiates 1037 * locales from each other.<p> 1038 * 1039 * The script is given as the ISO 15924 four-letter script code. In some locales, 1040 * text may be validly written in more than one script. For example, Serbian is often 1041 * written in both Latin and Cyrillic, though not usually mixed together. You can find a 1042 * full list of these codes at 1043 * <a href="http://en.wikipedia.org/wiki/ISO_15924#List_of_codes">http://en.wikipedia.org/wiki/ISO_15924#List_of_codes</a>.<p> 1044 * 1045 * As an example in ilib, the script can be used in the date formatter. Dates formatted 1046 * in Serbian could have day-of-week names or month names written in the Latin 1047 * or Cyrillic script. Often one script is default such that sr-SR-Latn is the same 1048 * as sr-SR so the script code "Latn" can be left off of the locale spec.<p> 1049 * 1050 * Each part is optional, and an empty string in the specifier before or after a 1051 * dash or as a parameter to the constructor denotes an unspecified value. In this 1052 * case, many of the ilib functions will treat the locale as generic. For example 1053 * the locale "en-" is equivalent to "en" and to "en--" and denotes a locale 1054 * of "English" with an unspecified region and variant, which typically matches 1055 * any region or variant.<p> 1056 * 1057 * Without any arguments to the constructor, this function returns the locale of 1058 * the host Javascript engine.<p> 1059 * 1060 * 1061 * @constructor 1062 * @param {?string|Locale=} language the ISO 639 2-letter code for the language, or a full 1063 * locale spec in BCP-47 format, or another Locale instance to copy from 1064 * @param {string=} region the ISO 3166 2-letter code for the region 1065 * @param {string=} variant the name of the variant of this locale, if any 1066 * @param {string=} script the ISO 15924 code of the script for this locale, if any 1067 */ 1068 var Locale = function(language, region, variant, script) { 1069 if (typeof(region) === 'undefined') { 1070 var spec = language || ilib.getLocale(); 1071 if (typeof(spec) === 'string') { 1072 var parts = spec.split('-'); 1073 for ( var i = 0; i < parts.length; i++ ) { 1074 if (Locale._isLanguageCode(parts[i])) { 1075 /** 1076 * @private 1077 * @type {string|undefined} 1078 */ 1079 this.language = parts[i]; 1080 } else if (Locale._isRegionCode(parts[i])) { 1081 /** 1082 * @private 1083 * @type {string|undefined} 1084 */ 1085 this.region = parts[i]; 1086 } else if (Locale._isScriptCode(parts[i])) { 1087 /** 1088 * @private 1089 * @type {string|undefined} 1090 */ 1091 this.script = parts[i]; 1092 } else { 1093 /** 1094 * @private 1095 * @type {string|undefined} 1096 */ 1097 this.variant = parts[i]; 1098 } 1099 } 1100 this.language = this.language || undefined; 1101 this.region = this.region || undefined; 1102 this.script = this.script || undefined; 1103 this.variant = this.variant || undefined; 1104 } else if (typeof(spec) === 'object') { 1105 this.language = spec.language || undefined; 1106 this.region = spec.region || undefined; 1107 this.script = spec.script || undefined; 1108 this.variant = spec.variant || undefined; 1109 } 1110 } else { 1111 if (language) { 1112 language = language.trim(); 1113 this.language = language.length > 0 ? language.toLowerCase() : undefined; 1114 } else { 1115 this.language = undefined; 1116 } 1117 if (region) { 1118 region = region.trim(); 1119 this.region = region.length > 0 ? region.toUpperCase() : undefined; 1120 } else { 1121 this.region = undefined; 1122 } 1123 if (variant) { 1124 variant = variant.trim(); 1125 this.variant = variant.length > 0 ? variant : undefined; 1126 } else { 1127 this.variant = undefined; 1128 } 1129 if (script) { 1130 script = script.trim(); 1131 this.script = script.length > 0 ? script : undefined; 1132 } else { 1133 this.script = undefined; 1134 } 1135 } 1136 this._genSpec(); 1137 }; 1138 1139 // from http://en.wikipedia.org/wiki/ISO_3166-1 1140 Locale.a2toa3regmap = { 1141 "AF": "AFG", 1142 "AX": "ALA", 1143 "AL": "ALB", 1144 "DZ": "DZA", 1145 "AS": "ASM", 1146 "AD": "AND", 1147 "AO": "AGO", 1148 "AI": "AIA", 1149 "AQ": "ATA", 1150 "AG": "ATG", 1151 "AR": "ARG", 1152 "AM": "ARM", 1153 "AW": "ABW", 1154 "AU": "AUS", 1155 "AT": "AUT", 1156 "AZ": "AZE", 1157 "BS": "BHS", 1158 "BH": "BHR", 1159 "BD": "BGD", 1160 "BB": "BRB", 1161 "BY": "BLR", 1162 "BE": "BEL", 1163 "BZ": "BLZ", 1164 "BJ": "BEN", 1165 "BM": "BMU", 1166 "BT": "BTN", 1167 "BO": "BOL", 1168 "BQ": "BES", 1169 "BA": "BIH", 1170 "BW": "BWA", 1171 "BV": "BVT", 1172 "BR": "BRA", 1173 "IO": "IOT", 1174 "BN": "BRN", 1175 "BG": "BGR", 1176 "BF": "BFA", 1177 "BI": "BDI", 1178 "KH": "KHM", 1179 "CM": "CMR", 1180 "CA": "CAN", 1181 "CV": "CPV", 1182 "KY": "CYM", 1183 "CF": "CAF", 1184 "TD": "TCD", 1185 "CL": "CHL", 1186 "CN": "CHN", 1187 "CX": "CXR", 1188 "CC": "CCK", 1189 "CO": "COL", 1190 "KM": "COM", 1191 "CG": "COG", 1192 "CD": "COD", 1193 "CK": "COK", 1194 "CR": "CRI", 1195 "CI": "CIV", 1196 "HR": "HRV", 1197 "CU": "CUB", 1198 "CW": "CUW", 1199 "CY": "CYP", 1200 "CZ": "CZE", 1201 "DK": "DNK", 1202 "DJ": "DJI", 1203 "DM": "DMA", 1204 "DO": "DOM", 1205 "EC": "ECU", 1206 "EG": "EGY", 1207 "SV": "SLV", 1208 "GQ": "GNQ", 1209 "ER": "ERI", 1210 "EE": "EST", 1211 "ET": "ETH", 1212 "FK": "FLK", 1213 "FO": "FRO", 1214 "FJ": "FJI", 1215 "FI": "FIN", 1216 "FR": "FRA", 1217 "GF": "GUF", 1218 "PF": "PYF", 1219 "TF": "ATF", 1220 "GA": "GAB", 1221 "GM": "GMB", 1222 "GE": "GEO", 1223 "DE": "DEU", 1224 "GH": "GHA", 1225 "GI": "GIB", 1226 "GR": "GRC", 1227 "GL": "GRL", 1228 "GD": "GRD", 1229 "GP": "GLP", 1230 "GU": "GUM", 1231 "GT": "GTM", 1232 "GG": "GGY", 1233 "GN": "GIN", 1234 "GW": "GNB", 1235 "GY": "GUY", 1236 "HT": "HTI", 1237 "HM": "HMD", 1238 "VA": "VAT", 1239 "HN": "HND", 1240 "HK": "HKG", 1241 "HU": "HUN", 1242 "IS": "ISL", 1243 "IN": "IND", 1244 "ID": "IDN", 1245 "IR": "IRN", 1246 "IQ": "IRQ", 1247 "IE": "IRL", 1248 "IM": "IMN", 1249 "IL": "ISR", 1250 "IT": "ITA", 1251 "JM": "JAM", 1252 "JP": "JPN", 1253 "JE": "JEY", 1254 "JO": "JOR", 1255 "KZ": "KAZ", 1256 "KE": "KEN", 1257 "KI": "KIR", 1258 "KP": "PRK", 1259 "KR": "KOR", 1260 "KW": "KWT", 1261 "KG": "KGZ", 1262 "LA": "LAO", 1263 "LV": "LVA", 1264 "LB": "LBN", 1265 "LS": "LSO", 1266 "LR": "LBR", 1267 "LY": "LBY", 1268 "LI": "LIE", 1269 "LT": "LTU", 1270 "LU": "LUX", 1271 "MO": "MAC", 1272 "MK": "MKD", 1273 "MG": "MDG", 1274 "MW": "MWI", 1275 "MY": "MYS", 1276 "MV": "MDV", 1277 "ML": "MLI", 1278 "MT": "MLT", 1279 "MH": "MHL", 1280 "MQ": "MTQ", 1281 "MR": "MRT", 1282 "MU": "MUS", 1283 "YT": "MYT", 1284 "MX": "MEX", 1285 "FM": "FSM", 1286 "MD": "MDA", 1287 "MC": "MCO", 1288 "MN": "MNG", 1289 "ME": "MNE", 1290 "MS": "MSR", 1291 "MA": "MAR", 1292 "MZ": "MOZ", 1293 "MM": "MMR", 1294 "NA": "NAM", 1295 "NR": "NRU", 1296 "NP": "NPL", 1297 "NL": "NLD", 1298 "NC": "NCL", 1299 "NZ": "NZL", 1300 "NI": "NIC", 1301 "NE": "NER", 1302 "NG": "NGA", 1303 "NU": "NIU", 1304 "NF": "NFK", 1305 "MP": "MNP", 1306 "NO": "NOR", 1307 "OM": "OMN", 1308 "PK": "PAK", 1309 "PW": "PLW", 1310 "PS": "PSE", 1311 "PA": "PAN", 1312 "PG": "PNG", 1313 "PY": "PRY", 1314 "PE": "PER", 1315 "PH": "PHL", 1316 "PN": "PCN", 1317 "PL": "POL", 1318 "PT": "PRT", 1319 "PR": "PRI", 1320 "QA": "QAT", 1321 "RE": "REU", 1322 "RO": "ROU", 1323 "RU": "RUS", 1324 "RW": "RWA", 1325 "BL": "BLM", 1326 "SH": "SHN", 1327 "KN": "KNA", 1328 "LC": "LCA", 1329 "MF": "MAF", 1330 "PM": "SPM", 1331 "VC": "VCT", 1332 "WS": "WSM", 1333 "SM": "SMR", 1334 "ST": "STP", 1335 "SA": "SAU", 1336 "SN": "SEN", 1337 "RS": "SRB", 1338 "SC": "SYC", 1339 "SL": "SLE", 1340 "SG": "SGP", 1341 "SX": "SXM", 1342 "SK": "SVK", 1343 "SI": "SVN", 1344 "SB": "SLB", 1345 "SO": "SOM", 1346 "ZA": "ZAF", 1347 "GS": "SGS", 1348 "SS": "SSD", 1349 "ES": "ESP", 1350 "LK": "LKA", 1351 "SD": "SDN", 1352 "SR": "SUR", 1353 "SJ": "SJM", 1354 "SZ": "SWZ", 1355 "SE": "SWE", 1356 "CH": "CHE", 1357 "SY": "SYR", 1358 "TW": "TWN", 1359 "TJ": "TJK", 1360 "TZ": "TZA", 1361 "TH": "THA", 1362 "TL": "TLS", 1363 "TG": "TGO", 1364 "TK": "TKL", 1365 "TO": "TON", 1366 "TT": "TTO", 1367 "TN": "TUN", 1368 "TR": "TUR", 1369 "TM": "TKM", 1370 "TC": "TCA", 1371 "TV": "TUV", 1372 "UG": "UGA", 1373 "UA": "UKR", 1374 "AE": "ARE", 1375 "GB": "GBR", 1376 "US": "USA", 1377 "UM": "UMI", 1378 "UY": "URY", 1379 "UZ": "UZB", 1380 "VU": "VUT", 1381 "VE": "VEN", 1382 "VN": "VNM", 1383 "VG": "VGB", 1384 "VI": "VIR", 1385 "WF": "WLF", 1386 "EH": "ESH", 1387 "YE": "YEM", 1388 "ZM": "ZMB", 1389 "ZW": "ZWE" 1390 }; 1391 1392 1393 Locale.a1toa3langmap = { 1394 "ab": "abk", 1395 "aa": "aar", 1396 "af": "afr", 1397 "ak": "aka", 1398 "sq": "sqi", 1399 "am": "amh", 1400 "ar": "ara", 1401 "an": "arg", 1402 "hy": "hye", 1403 "as": "asm", 1404 "av": "ava", 1405 "ae": "ave", 1406 "ay": "aym", 1407 "az": "aze", 1408 "bm": "bam", 1409 "ba": "bak", 1410 "eu": "eus", 1411 "be": "bel", 1412 "bn": "ben", 1413 "bh": "bih", 1414 "bi": "bis", 1415 "bs": "bos", 1416 "br": "bre", 1417 "bg": "bul", 1418 "my": "mya", 1419 "ca": "cat", 1420 "ch": "cha", 1421 "ce": "che", 1422 "ny": "nya", 1423 "zh": "zho", 1424 "cv": "chv", 1425 "kw": "cor", 1426 "co": "cos", 1427 "cr": "cre", 1428 "hr": "hrv", 1429 "cs": "ces", 1430 "da": "dan", 1431 "dv": "div", 1432 "nl": "nld", 1433 "dz": "dzo", 1434 "en": "eng", 1435 "eo": "epo", 1436 "et": "est", 1437 "ee": "ewe", 1438 "fo": "fao", 1439 "fj": "fij", 1440 "fi": "fin", 1441 "fr": "fra", 1442 "ff": "ful", 1443 "gl": "glg", 1444 "ka": "kat", 1445 "de": "deu", 1446 "el": "ell", 1447 "gn": "grn", 1448 "gu": "guj", 1449 "ht": "hat", 1450 "ha": "hau", 1451 "he": "heb", 1452 "hz": "her", 1453 "hi": "hin", 1454 "ho": "hmo", 1455 "hu": "hun", 1456 "ia": "ina", 1457 "id": "ind", 1458 "ie": "ile", 1459 "ga": "gle", 1460 "ig": "ibo", 1461 "ik": "ipk", 1462 "io": "ido", 1463 "is": "isl", 1464 "it": "ita", 1465 "iu": "iku", 1466 "ja": "jpn", 1467 "jv": "jav", 1468 "kl": "kal", 1469 "kn": "kan", 1470 "kr": "kau", 1471 "ks": "kas", 1472 "kk": "kaz", 1473 "km": "khm", 1474 "ki": "kik", 1475 "rw": "kin", 1476 "ky": "kir", 1477 "kv": "kom", 1478 "kg": "kon", 1479 "ko": "kor", 1480 "ku": "kur", 1481 "kj": "kua", 1482 "la": "lat", 1483 "lb": "ltz", 1484 "lg": "lug", 1485 "li": "lim", 1486 "ln": "lin", 1487 "lo": "lao", 1488 "lt": "lit", 1489 "lu": "lub", 1490 "lv": "lav", 1491 "gv": "glv", 1492 "mk": "mkd", 1493 "mg": "mlg", 1494 "ms": "msa", 1495 "ml": "mal", 1496 "mt": "mlt", 1497 "mi": "mri", 1498 "mr": "mar", 1499 "mh": "mah", 1500 "mn": "mon", 1501 "na": "nau", 1502 "nv": "nav", 1503 "nb": "nob", 1504 "nd": "nde", 1505 "ne": "nep", 1506 "ng": "ndo", 1507 "nn": "nno", 1508 "no": "nor", 1509 "ii": "iii", 1510 "nr": "nbl", 1511 "oc": "oci", 1512 "oj": "oji", 1513 "cu": "chu", 1514 "om": "orm", 1515 "or": "ori", 1516 "os": "oss", 1517 "pa": "pan", 1518 "pi": "pli", 1519 "fa": "fas", 1520 "pl": "pol", 1521 "ps": "pus", 1522 "pt": "por", 1523 "qu": "que", 1524 "rm": "roh", 1525 "rn": "run", 1526 "ro": "ron", 1527 "ru": "rus", 1528 "sa": "san", 1529 "sc": "srd", 1530 "sd": "snd", 1531 "se": "sme", 1532 "sm": "smo", 1533 "sg": "sag", 1534 "sr": "srp", 1535 "gd": "gla", 1536 "sn": "sna", 1537 "si": "sin", 1538 "sk": "slk", 1539 "sl": "slv", 1540 "so": "som", 1541 "st": "sot", 1542 "az": "azb", 1543 "es": "spa", 1544 "su": "sun", 1545 "sw": "swa", 1546 "ss": "ssw", 1547 "sv": "swe", 1548 "ta": "tam", 1549 "te": "tel", 1550 "tg": "tgk", 1551 "th": "tha", 1552 "ti": "tir", 1553 "bo": "bod", 1554 "tk": "tuk", 1555 "tl": "tgl", 1556 "tn": "tsn", 1557 "to": "ton", 1558 "tr": "tur", 1559 "ts": "tso", 1560 "tt": "tat", 1561 "tw": "twi", 1562 "ty": "tah", 1563 "ug": "uig", 1564 "uk": "ukr", 1565 "ur": "urd", 1566 "uz": "uzb", 1567 "ve": "ven", 1568 "vi": "vie", 1569 "vo": "vol", 1570 "wa": "wln", 1571 "cy": "cym", 1572 "wo": "wol", 1573 "fy": "fry", 1574 "xh": "xho", 1575 "yi": "yid", 1576 "yo": "yor", 1577 "za": "zha", 1578 "zu": "zul" 1579 }; 1580 1581 /** 1582 * Tell whether or not the str does not start with a lower case ASCII char. 1583 * @private 1584 * @param {string} str the char to check 1585 * @return {boolean} true if the char is not a lower case ASCII char 1586 */ 1587 Locale._notLower = function(str) { 1588 // do this with ASCII only so we don't have to depend on the CType functions 1589 var ch = str.charCodeAt(0); 1590 return ch < 97 || ch > 122; 1591 }; 1592 1593 /** 1594 * Tell whether or not the str does not start with an upper case ASCII char. 1595 * @private 1596 * @param {string} str the char to check 1597 * @return {boolean} true if the char is a not an upper case ASCII char 1598 */ 1599 Locale._notUpper = function(str) { 1600 // do this with ASCII only so we don't have to depend on the CType functions 1601 var ch = str.charCodeAt(0); 1602 return ch < 65 || ch > 90; 1603 }; 1604 1605 /** 1606 * Tell whether or not the str does not start with a digit char. 1607 * @private 1608 * @param {string} str the char to check 1609 * @return {boolean} true if the char is a not an upper case ASCII char 1610 */ 1611 Locale._notDigit = function(str) { 1612 // do this with ASCII only so we don't have to depend on the CType functions 1613 var ch = str.charCodeAt(0); 1614 return ch < 48 || ch > 57; 1615 }; 1616 1617 /** 1618 * Tell whether or not the given string has the correct syntax to be 1619 * an ISO 639 language code. 1620 * 1621 * @private 1622 * @param {string} str the string to parse 1623 * @return {boolean} true if the string could syntactically be a language code. 1624 */ 1625 Locale._isLanguageCode = function(str) { 1626 if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { 1627 return false; 1628 } 1629 1630 for (var i = 0; i < str.length; i++) { 1631 if (Locale._notLower(str.charAt(i))) { 1632 return false; 1633 } 1634 } 1635 1636 return true; 1637 }; 1638 1639 /** 1640 * Tell whether or not the given string has the correct syntax to be 1641 * an ISO 3166 2-letter region code or M.49 3-digit region code. 1642 * 1643 * @private 1644 * @param {string} str the string to parse 1645 * @return {boolean} true if the string could syntactically be a language code. 1646 */ 1647 Locale._isRegionCode = function (str) { 1648 if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { 1649 return false; 1650 } 1651 1652 if (str.length === 2) { 1653 for (var i = 0; i < str.length; i++) { 1654 if (Locale._notUpper(str.charAt(i))) { 1655 return false; 1656 } 1657 } 1658 } else { 1659 for (var i = 0; i < str.length; i++) { 1660 if (Locale._notDigit(str.charAt(i))) { 1661 return false; 1662 } 1663 } 1664 } 1665 1666 return true; 1667 }; 1668 1669 /** 1670 * Tell whether or not the given string has the correct syntax to be 1671 * an ISO 639 language code. 1672 * 1673 * @private 1674 * @param {string} str the string to parse 1675 * @return {boolean} true if the string could syntactically be a language code. 1676 */ 1677 Locale._isScriptCode = function(str) { 1678 if (typeof(str) === 'undefined' || str.length !== 4 || Locale._notUpper(str.charAt(0))) { 1679 return false; 1680 } 1681 1682 for (var i = 1; i < 4; i++) { 1683 if (Locale._notLower(str.charAt(i))) { 1684 return false; 1685 } 1686 } 1687 1688 return true; 1689 }; 1690 1691 /** 1692 * Return the ISO-3166 alpha3 equivalent region code for the given ISO 3166 alpha2 1693 * region code. If the given alpha2 code is not found, this function returns its 1694 * argument unchanged. 1695 * @static 1696 * @param {string|undefined} alpha2 the alpha2 code to map 1697 * @return {string|undefined} the alpha3 equivalent of the given alpha2 code, or the alpha2 1698 * parameter if the alpha2 value is not found 1699 */ 1700 Locale.regionAlpha2ToAlpha3 = function(alpha2) { 1701 return Locale.a2toa3regmap[alpha2] || alpha2; 1702 }; 1703 1704 /** 1705 * Return the ISO-639 alpha3 equivalent language code for the given ISO 639 alpha1 1706 * language code. If the given alpha1 code is not found, this function returns its 1707 * argument unchanged. 1708 * @static 1709 * @param {string|undefined} alpha1 the alpha1 code to map 1710 * @return {string|undefined} the alpha3 equivalent of the given alpha1 code, or the alpha1 1711 * parameter if the alpha1 value is not found 1712 */ 1713 Locale.languageAlpha1ToAlpha3 = function(alpha1) { 1714 return Locale.a1toa3langmap[alpha1] || alpha1; 1715 }; 1716 1717 Locale.prototype = { 1718 /** 1719 * @private 1720 */ 1721 _genSpec: function () { 1722 this.spec = this.language || ""; 1723 1724 if (this.script) { 1725 if (this.spec.length > 0) { 1726 this.spec += "-"; 1727 } 1728 this.spec += this.script; 1729 } 1730 1731 if (this.region) { 1732 if (this.spec.length > 0) { 1733 this.spec += "-"; 1734 } 1735 this.spec += this.region; 1736 } 1737 1738 if (this.variant) { 1739 if (this.spec.length > 0) { 1740 this.spec += "-"; 1741 } 1742 this.spec += this.variant; 1743 } 1744 }, 1745 1746 /** 1747 * Return the ISO 639 language code for this locale. 1748 * @return {string|undefined} the language code for this locale 1749 */ 1750 getLanguage: function() { 1751 return this.language; 1752 }, 1753 1754 /** 1755 * Return the language of this locale as an ISO-639-alpha3 language code 1756 * @return {string|undefined} the alpha3 language code of this locale 1757 */ 1758 getLanguageAlpha3: function() { 1759 return Locale.languageAlpha1ToAlpha3(this.language); 1760 }, 1761 1762 /** 1763 * Return the ISO 3166 region code for this locale. 1764 * @return {string|undefined} the region code of this locale 1765 */ 1766 getRegion: function() { 1767 return this.region; 1768 }, 1769 1770 /** 1771 * Return the region of this locale as an ISO-3166-alpha3 region code 1772 * @return {string|undefined} the alpha3 region code of this locale 1773 */ 1774 getRegionAlpha3: function() { 1775 return Locale.regionAlpha2ToAlpha3(this.region); 1776 }, 1777 1778 /** 1779 * Return the ISO 15924 script code for this locale 1780 * @return {string|undefined} the script code of this locale 1781 */ 1782 getScript: function () { 1783 return this.script; 1784 }, 1785 1786 /** 1787 * Return the variant code for this locale 1788 * @return {string|undefined} the variant code of this locale, if any 1789 */ 1790 getVariant: function() { 1791 return this.variant; 1792 }, 1793 1794 /** 1795 * Return the whole locale specifier as a string. 1796 * @return {string} the locale specifier 1797 */ 1798 getSpec: function() { 1799 return this.spec; 1800 }, 1801 1802 /** 1803 * Express this locale object as a string. Currently, this simply calls the getSpec 1804 * function to represent the locale as its specifier. 1805 * 1806 * @return {string} the locale specifier 1807 */ 1808 toString: function() { 1809 return this.getSpec(); 1810 }, 1811 1812 /** 1813 * Return true if the the other locale is exactly equal to the current one. 1814 * @return {boolean} whether or not the other locale is equal to the current one 1815 */ 1816 equals: function(other) { 1817 return this.language === other.language && 1818 this.region === other.region && 1819 this.script === other.script && 1820 this.variant === other.variant; 1821 }, 1822 1823 /** 1824 * Return true if the current locale is the special pseudo locale. 1825 * @return {boolean} true if the current locale is the special pseudo locale 1826 */ 1827 isPseudo: function () { 1828 return JSUtils.indexOf(ilib.pseudoLocales, this.spec) > -1; 1829 } 1830 }; 1831 1832 // static functions 1833 /** 1834 * @private 1835 */ 1836 Locale.locales = [ 1837 1838 ]; 1839 1840 /** 1841 * Return the list of available locales that this iLib file supports. 1842 * If this copy of ilib is pre-assembled with locale data, then the 1843 * list locales may be much smaller 1844 * than the list of all available locales in the iLib repository. The 1845 * assembly tool will automatically fill in the list for an assembled 1846 * copy of iLib. If this copy is being used with dynamically loaded 1847 * data, then you 1848 * can load any locale that iLib supports. You can form a locale with any 1849 * combination of a language and region tags that exist in the locale 1850 * data directory. Language tags are in the root of the locale data dir, 1851 * and region tags can be found underneath the "und" directory. (The 1852 * region tags are separated into a different dir because the region names 1853 * conflict with language names on file systems that are case-insensitive.) 1854 * If you have culled the locale data directory to limit the size of 1855 * your app, then this function should return only those files that actually exist 1856 * according to the ilibmanifest.json file in the root of that locale 1857 * data dir. Make sure your ilibmanifest.json file is up-to-date with 1858 * respect to the list of files that exist in the locale data dir. 1859 * 1860 * @param {boolean} sync if false, load the list of available files from disk 1861 * asynchronously, otherwise load them synchronously. (Default: true/synchronously) 1862 * @param {Function} onLoad a callback function to call if asynchronous 1863 * load was requested and the list of files have been loaded. 1864 * @return {Array.<string>} this is an array of locale specs for which 1865 * this iLib file has locale data for 1866 */ 1867 Locale.getAvailableLocales = function (sync, onLoad) { 1868 var locales = []; 1869 if (Locale.locales.length || typeof(ilib._load.listAvailableFiles) !== 'function') { 1870 locales = Locale.locales; 1871 if (onLoad && typeof(onLoad) === 'function') { 1872 onLoad(locales); 1873 } 1874 } else { 1875 if (typeof(sync) === 'undefined') { 1876 sync = true; 1877 } 1878 ilib._load.listAvailableFiles(sync, function(manifest) { 1879 if (manifest) { 1880 for (var dir in manifest) { 1881 var filelist = manifest[dir]; 1882 for (var i = 0; i < filelist.length; i++) { 1883 if (filelist[i].length > 15 && filelist[i].substr(-15) === "localeinfo.json") { 1884 locales.push(filelist[i].substring(0,filelist[i].length-16).replace(/\//g, "-")); 1885 } 1886 } 1887 } 1888 } 1889 if (onLoad && typeof(onLoad) === 'function') { 1890 onLoad(locales); 1891 } 1892 }); 1893 } 1894 return locales; 1895 }; 1896 1897 1898 1899 /*< Utils.js */ 1900 /* 1901 * Utils.js - Core utility routines 1902 * 1903 * Copyright © 2012-2015, JEDLSoft 1904 * 1905 * Licensed under the Apache License, Version 2.0 (the "License"); 1906 * you may not use this file except in compliance with the License. 1907 * You may obtain a copy of the License at 1908 * 1909 * http://www.apache.org/licenses/LICENSE-2.0 1910 * 1911 * Unless required by applicable law or agreed to in writing, software 1912 * distributed under the License is distributed on an "AS IS" BASIS, 1913 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1914 * 1915 * See the License for the specific language governing permissions and 1916 * limitations under the License. 1917 */ 1918 1919 // !depends ilib.js Locale.js JSUtils.js 1920 1921 1922 var Utils = {}; 1923 1924 /** 1925 * Find and merge all the locale data for a particular prefix in the given locale 1926 * and return it as a single javascript object. This merges the data in the 1927 * correct order: 1928 * 1929 * <ol> 1930 * <li>shared data (usually English) 1931 * <li>data for language 1932 * <li>data for language + region 1933 * <li>data for language + region + script 1934 * <li>data for language + region + script + variant 1935 * </ol> 1936 * 1937 * It is okay for any of the above to be missing. This function will just skip the 1938 * missing data. However, if everything except the shared data is missing, this 1939 * function returns undefined, allowing the caller to go and dynamically load the 1940 * data instead. 1941 * 1942 * @static 1943 * @param {string} prefix prefix under ilib.data of the data to merge 1944 * @param {Locale} locale locale of the data being sought 1945 * @param {boolean=} replaceArrays if true, replace the array elements in object1 with those in object2. 1946 * If false, concatenate array elements in object1 with items in object2. 1947 * @param {boolean=} returnOne if true, only return the most locale-specific data. If false, 1948 * merge all the relevant locale data together. 1949 * @return {Object?} the merged locale data 1950 */ 1951 Utils.mergeLocData = function (prefix, locale, replaceArrays, returnOne) { 1952 var data = undefined; 1953 var loc = locale || new Locale(); 1954 var foundLocaleData = false; 1955 var property = prefix; 1956 var mostSpecific; 1957 1958 data = ilib.data[prefix] || {}; 1959 1960 mostSpecific = data; 1961 1962 if (loc.getLanguage()) { 1963 property = prefix + '_' + loc.getLanguage(); 1964 if (ilib.data[property]) { 1965 foundLocaleData = true; 1966 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 1967 mostSpecific = ilib.data[property]; 1968 } 1969 } 1970 1971 if (loc.getRegion()) { 1972 property = prefix + '_' + loc.getRegion(); 1973 if (ilib.data[property]) { 1974 foundLocaleData = true; 1975 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 1976 mostSpecific = ilib.data[property]; 1977 } 1978 } 1979 1980 if (loc.getLanguage()) { 1981 property = prefix + '_' + loc.getLanguage(); 1982 1983 if (loc.getScript()) { 1984 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript(); 1985 if (ilib.data[property]) { 1986 foundLocaleData = true; 1987 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 1988 mostSpecific = ilib.data[property]; 1989 } 1990 } 1991 1992 if (loc.getRegion()) { 1993 property = prefix + '_' + loc.getLanguage() + '_' + loc.getRegion(); 1994 if (ilib.data[property]) { 1995 foundLocaleData = true; 1996 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 1997 mostSpecific = ilib.data[property]; 1998 } 1999 } 2000 } 2001 2002 if (loc.getRegion() && loc.getVariant()) { 2003 property = prefix + '_' + loc.getLanguage() + '_' + loc.getVariant(); 2004 if (ilib.data[property]) { 2005 foundLocaleData = true; 2006 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2007 mostSpecific = ilib.data[property]; 2008 } 2009 } 2010 2011 if (loc.getLanguage() && loc.getScript() && loc.getRegion()) { 2012 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript() + '_' + loc.getRegion(); 2013 if (ilib.data[property]) { 2014 foundLocaleData = true; 2015 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2016 mostSpecific = ilib.data[property]; 2017 } 2018 } 2019 2020 if (loc.getLanguage() && loc.getRegion() && loc.getVariant()) { 2021 property = prefix + '_' + loc.getLanguage() + '_' + loc.getRegion() + '_' + loc.getVariant(); 2022 if (ilib.data[property]) { 2023 foundLocaleData = true; 2024 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2025 mostSpecific = ilib.data[property]; 2026 } 2027 } 2028 2029 if (loc.getLanguage() && loc.getScript() && loc.getRegion() && loc.getVariant()) { 2030 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript() + '_' + loc.getRegion() + '_' + loc.getVariant(); 2031 if (ilib.data[property]) { 2032 foundLocaleData = true; 2033 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2034 mostSpecific = ilib.data[property]; 2035 } 2036 } 2037 2038 return foundLocaleData ? (returnOne ? mostSpecific : data) : undefined; 2039 }; 2040 2041 /** 2042 * Return an array of relative path names for the 2043 * files that represent the data for the given locale.<p> 2044 * 2045 * Note that to prevent the situation where a directory for 2046 * a language exists next to the directory for a region where 2047 * the language code and region code differ only by case, the 2048 * plain region directories are located under the special 2049 * "undefined" language directory which has the ISO code "und". 2050 * The reason is that some platforms have case-insensitive 2051 * file systems, and you cannot have 2 directories with the 2052 * same name which only differ by case. For example, "es" is 2053 * the ISO 639 code for the language "Spanish" and "ES" is 2054 * the ISO 3166 code for the region "Spain", so both the 2055 * directories cannot exist underneath "locale". The region 2056 * therefore will be loaded from "und/ES" instead.<p> 2057 * 2058 * <h4>Variations</h4> 2059 * 2060 * With only language and region specified, the following 2061 * sequence of paths will be generated:<p> 2062 * 2063 * <pre> 2064 * language 2065 * und/region 2066 * language/region 2067 * </pre> 2068 * 2069 * With only language and script specified:<p> 2070 * 2071 * <pre> 2072 * language 2073 * language/script 2074 * </pre> 2075 * 2076 * With only script and region specified:<p> 2077 * 2078 * <pre> 2079 * und/region 2080 * </pre> 2081 * 2082 * With only region and variant specified:<p> 2083 * 2084 * <pre> 2085 * und/region 2086 * region/variant 2087 * </pre> 2088 * 2089 * With only language, script, and region specified:<p> 2090 * 2091 * <pre> 2092 * language 2093 * und/region 2094 * language/script 2095 * language/region 2096 * language/script/region 2097 * </pre> 2098 * 2099 * With only language, region, and variant specified:<p> 2100 * 2101 * <pre> 2102 * language 2103 * und/region 2104 * language/region 2105 * region/variant 2106 * language/region/variant 2107 * </pre> 2108 * 2109 * With all parts specified:<p> 2110 * 2111 * <pre> 2112 * language 2113 * und/region 2114 * language/script 2115 * language/region 2116 * region/variant 2117 * language/script/region 2118 * language/region/variant 2119 * language/script/region/variant 2120 * </pre> 2121 * 2122 * @static 2123 * @param {Locale} locale load the files for this locale 2124 * @param {string?} name the file name of each file to load without 2125 * any path 2126 * @return {Array.<string>} An array of relative path names 2127 * for the files that contain the locale data 2128 */ 2129 Utils.getLocFiles = function(locale, name) { 2130 var dir = ""; 2131 var files = []; 2132 var filename = name || "resources.json"; 2133 var loc = locale || new Locale(); 2134 2135 var language = loc.getLanguage(); 2136 var region = loc.getRegion(); 2137 var script = loc.getScript(); 2138 var variant = loc.getVariant(); 2139 2140 files.push(filename); // generic shared file 2141 2142 if (language) { 2143 dir = language + "/"; 2144 files.push(dir + filename); 2145 } 2146 2147 if (region) { 2148 dir = "und/" + region + "/"; 2149 files.push(dir + filename); 2150 } 2151 2152 if (language) { 2153 if (script) { 2154 dir = language + "/" + script + "/"; 2155 files.push(dir + filename); 2156 } 2157 if (region) { 2158 dir = language + "/" + region + "/"; 2159 files.push(dir + filename); 2160 } 2161 } 2162 2163 if (region && variant) { 2164 dir = "und/" + region + "/" + variant + "/"; 2165 files.push(dir + filename); 2166 } 2167 2168 if (language && script && region) { 2169 dir = language + "/" + script + "/" + region + "/"; 2170 files.push(dir + filename); 2171 } 2172 2173 if (language && region && variant) { 2174 dir = language + "/" + region + "/" + variant + "/"; 2175 files.push(dir + filename); 2176 } 2177 2178 if (language && script && region && variant) { 2179 dir = language + "/" + script + "/" + region + "/" + variant + "/"; 2180 files.push(dir + filename); 2181 } 2182 2183 return files; 2184 }; 2185 2186 /** 2187 * Load data using the new loader object or via the old function callback. 2188 * @static 2189 * @private 2190 */ 2191 Utils._callLoadData = function (files, sync, params, callback) { 2192 // console.log("Utils._callLoadData called"); 2193 if (typeof(ilib._load) === 'function') { 2194 // console.log("Utils._callLoadData: calling as a regular function"); 2195 return ilib._load(files, sync, params, callback); 2196 } else if (typeof(ilib._load) === 'object' && typeof(ilib._load.loadFiles) === 'function') { 2197 // console.log("Utils._callLoadData: calling as an object"); 2198 return ilib._load.loadFiles(files, sync, params, callback); 2199 } 2200 2201 // console.log("Utils._callLoadData: not calling. Type is " + typeof(ilib._load) + " and instanceof says " + (ilib._load instanceof Loader)); 2202 return undefined; 2203 }; 2204 2205 /** 2206 * Find locale data or load it in. If the data with the given name is preassembled, it will 2207 * find the data in ilib.data. If the data is not preassembled but there is a loader function, 2208 * this function will call it to load the data. Otherwise, the callback will be called with 2209 * undefined as the data. This function will create a cache under the given class object. 2210 * If data was successfully loaded, it will be set into the cache so that future access to 2211 * the same data for the same locale is much quicker.<p> 2212 * 2213 * The parameters can specify any of the following properties:<p> 2214 * 2215 * <ul> 2216 * <li><i>name</i> - String. The name of the file being loaded. Default: ResBundle.json 2217 * <li><i>object</i> - Object. The class attempting to load data. The cache is stored inside of here. 2218 * <li><i>locale</i> - Locale. The locale for which data is loaded. Default is the current locale. 2219 * <li><i>nonlocale</i> - boolean. If true, the data being loaded is not locale-specific. 2220 * <li><i>type</i> - String. Type of file to load. This can be "json" or "other" type. Default: "json" 2221 * <li><i>replace</i> - boolean. When merging json objects, this parameter controls whether to merge arrays 2222 * or have arrays replace each other. If true, arrays in child objects replace the arrays in parent 2223 * objects. When false, the arrays in child objects are concatenated with the arrays in parent objects. 2224 * <li><i>loadParams</i> - Object. An object with parameters to pass to the loader function 2225 * <li><i>sync</i> - boolean. Whether or not to load the data synchronously 2226 * <li><i>callback</i> - function(?)=. callback Call back function to call when the data is available. 2227 * Data is not returned from this method, so a callback function is mandatory. 2228 * </ul> 2229 * 2230 * @static 2231 * @param {Object} params Parameters configuring how to load the files (see above) 2232 */ 2233 Utils.loadData = function(params) { 2234 var name = "resources.json", 2235 object = undefined, 2236 locale = new Locale(ilib.getLocale()), 2237 sync = false, 2238 type = undefined, 2239 loadParams = {}, 2240 callback = undefined, 2241 nonlocale = false, 2242 replace = false, 2243 basename; 2244 2245 if (!params || typeof(params.callback) !== 'function') { 2246 return; 2247 } 2248 2249 if (params.name) { 2250 name = params.name; 2251 } 2252 if (params.object) { 2253 object = params.object; 2254 } 2255 if (params.locale) { 2256 locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 2257 } 2258 if (params.type) { 2259 type = params.type; 2260 } 2261 if (params.loadParams) { 2262 loadParams = params.loadParams; 2263 } 2264 if (params.sync) { 2265 sync = params.sync; 2266 } 2267 if (params.nonlocale) { 2268 nonlocale = !!params.nonlocale; 2269 } 2270 if (typeof(params.replace) === 'boolean') { 2271 replace = params.replace; 2272 } 2273 2274 callback = params.callback; 2275 2276 if (object && !object.cache) { 2277 object.cache = {}; 2278 } 2279 2280 if (!type) { 2281 var dot = name.lastIndexOf("."); 2282 type = (dot !== -1) ? name.substring(dot+1) : "text"; 2283 } 2284 2285 var spec = ((!nonlocale && locale.getSpec().replace(/-/g, '_')) || "root") + "," + name + "," + String(JSUtils.hashCode(loadParams)); 2286 if (!object || typeof(object.cache[spec]) === 'undefined') { 2287 var data, returnOne = (loadParams && loadParams.returnOne); 2288 2289 if (type === "json") { 2290 // console.log("type is json"); 2291 basename = name.substring(0, name.lastIndexOf(".")); 2292 if (nonlocale) { 2293 basename = basename.replace(/\//g, '.').replace(/[\\\+\-]/g, "_"); 2294 data = ilib.data[basename]; 2295 } else { 2296 data = Utils.mergeLocData(basename, locale, replace, returnOne); 2297 } 2298 if (data) { 2299 // console.log("found assembled data"); 2300 if (object) { 2301 object.cache[spec] = data; 2302 } 2303 callback(data); 2304 return; 2305 } 2306 } 2307 2308 // console.log("ilib._load is " + typeof(ilib._load)); 2309 if (typeof(ilib._load) !== 'undefined') { 2310 // the data is not preassembled, so attempt to load it dynamically 2311 var files = nonlocale ? [ name || "resources.json" ] : Utils.getLocFiles(locale, name); 2312 if (type !== "json") { 2313 loadParams.returnOne = true; 2314 } 2315 2316 Utils._callLoadData(files, sync, loadParams, ilib.bind(this, function(arr) { 2317 if (type === "json") { 2318 data = ilib.data[basename] || {}; 2319 for (var i = 0; i < arr.length; i++) { 2320 if (typeof(arr[i]) !== 'undefined') { 2321 data = loadParams.returnOne ? arr[i] : JSUtils.merge(data, arr[i], replace); 2322 } 2323 } 2324 2325 if (object) { 2326 object.cache[spec] = data; 2327 } 2328 callback(data); 2329 } else { 2330 var i = arr.length-1; 2331 while (i > -1 && !arr[i]) { 2332 i--; 2333 } 2334 if (i > -1) { 2335 if (object) { 2336 object.cache[spec] = arr[i]; 2337 } 2338 callback(arr[i]); 2339 } else { 2340 callback(undefined); 2341 } 2342 } 2343 })); 2344 } else { 2345 // no data other than the generic shared data 2346 if (type === "json") { 2347 data = ilib.data[basename]; 2348 } 2349 if (object && data) { 2350 object.cache[spec] = data; 2351 } 2352 callback(data); 2353 } 2354 } else { 2355 callback(object.cache[spec]); 2356 } 2357 }; 2358 2359 2360 /*< LocaleInfo.js */ 2361 /* 2362 * LocaleInfo.js - Encode locale-specific defaults 2363 * 2364 * Copyright © 2012-2015, JEDLSoft 2365 * 2366 * Licensed under the Apache License, Version 2.0 (the "License"); 2367 * you may not use this file except in compliance with the License. 2368 * You may obtain a copy of the License at 2369 * 2370 * http://www.apache.org/licenses/LICENSE-2.0 2371 * 2372 * Unless required by applicable law or agreed to in writing, software 2373 * distributed under the License is distributed on an "AS IS" BASIS, 2374 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2375 * 2376 * See the License for the specific language governing permissions and 2377 * limitations under the License. 2378 */ 2379 2380 // !depends ilib.js Locale.js Utils.js 2381 2382 // !data localeinfo 2383 2384 2385 /** 2386 * @class 2387 * Create a new locale info instance. Locale info instances give information about 2388 * the default settings for a particular locale. These settings may be overridden 2389 * by various parts of the code, and should be used as a fall-back setting of last 2390 * resort. <p> 2391 * 2392 * The optional options object holds extra parameters if they are necessary. The 2393 * current list of supported options are: 2394 * 2395 * <ul> 2396 * <li><i>onLoad</i> - a callback function to call when the locale info object is fully 2397 * loaded. When the onLoad option is given, the localeinfo object will attempt to 2398 * load any missing locale data using the ilib loader callback. 2399 * When the constructor is done (even if the data is already preassembled), the 2400 * onLoad function is called with the current instance as a parameter, so this 2401 * callback can be used with preassembled or dynamic loading or a mix of the two. 2402 * 2403 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 2404 * asynchronously. If this option is given as "false", then the "onLoad" 2405 * callback must be given, as the instance returned from this constructor will 2406 * not be usable for a while. 2407 * 2408 * <li><i>loadParams</i> - an object containing parameters to pass to the 2409 * loader callback function when locale data is missing. The parameters are not 2410 * interpretted or modified in any way. They are simply passed along. The object 2411 * may contain any property/value pairs as long as the calling code is in 2412 * agreement with the loader callback function as to what those parameters mean. 2413 * </ul> 2414 * 2415 * If this copy of ilib is pre-assembled and all the data is already available, 2416 * or if the data was already previously loaded, then this constructor will call 2417 * the onLoad callback immediately when the initialization is done. 2418 * If the onLoad option is not given, this class will only attempt to load any 2419 * missing locale data synchronously. 2420 * 2421 * 2422 * @constructor 2423 * @see {ilib.setLoaderCallback} for information about registering a loader callback 2424 * function 2425 * @param {Locale|string=} locale the locale for which the info is sought, or undefined for 2426 * @param {Object=} options the locale for which the info is sought, or undefined for 2427 * the current locale 2428 */ 2429 var LocaleInfo = function(locale, options) { 2430 var sync = true, 2431 loadParams = undefined; 2432 2433 /* these are all the defaults. Essentially, en-US */ 2434 /** 2435 @private 2436 @type {{ 2437 scripts:Array.<string>, 2438 timezone:string, 2439 units:string, 2440 calendar:string, 2441 clock:string, 2442 currency:string, 2443 firstDayOfWeek:number, 2444 weekendStart:number, 2445 weekendEnd:number, 2446 meridiems:string, 2447 unitfmt: {long:string,short:string}, 2448 numfmt:Object.<{ 2449 currencyFormats:Object.<{common:string,commonNegative:string,iso:string,isoNegative:string}>, 2450 script:string, 2451 decimalChar:string, 2452 groupChar:string, 2453 prigroupSize:number, 2454 secgroupSize:number, 2455 negativenumFmt:string, 2456 pctFmt:string, 2457 negativepctFmt:string, 2458 pctChar:string, 2459 roundingMode:string, 2460 exponential:string, 2461 digits:string 2462 }> 2463 }} 2464 */ 2465 this.info = LocaleInfo.defaultInfo; 2466 2467 switch (typeof(locale)) { 2468 case "string": 2469 this.locale = new Locale(locale); 2470 break; 2471 default: 2472 case "undefined": 2473 this.locale = new Locale(); 2474 break; 2475 case "object": 2476 this.locale = locale; 2477 break; 2478 } 2479 2480 if (options) { 2481 if (typeof(options.sync) !== 'undefined') { 2482 sync = (options.sync == true); 2483 } 2484 2485 if (typeof(options.loadParams) !== 'undefined') { 2486 loadParams = options.loadParams; 2487 } 2488 } 2489 2490 if (!LocaleInfo.cache) { 2491 LocaleInfo.cache = {}; 2492 } 2493 2494 Utils.loadData({ 2495 object: LocaleInfo, 2496 locale: this.locale, 2497 name: "localeinfo.json", 2498 sync: sync, 2499 loadParams: loadParams, 2500 callback: ilib.bind(this, function (info) { 2501 if (!info) { 2502 info = LocaleInfo.defaultInfo; 2503 var spec = this.locale.getSpec().replace(/-/g, "_"); 2504 LocaleInfo.cache[spec] = info; 2505 } 2506 this.info = info; 2507 if (options && typeof(options.onLoad) === 'function') { 2508 options.onLoad(this); 2509 } 2510 }) 2511 }); 2512 }; 2513 2514 LocaleInfo.defaultInfo = /** @type {{ 2515 scripts:Array.<string>, 2516 timezone:string, 2517 units:string, 2518 calendar:string, 2519 clock:string, 2520 currency:string, 2521 firstDayOfWeek:number, 2522 weekendStart:number, 2523 weekendEnd:number, 2524 meridiems:string, 2525 unitfmt: {long:string,short:string}, 2526 numfmt:Object.<{ 2527 currencyFormats:Object.<{ 2528 common:string, 2529 commonNegative:string, 2530 iso:string, 2531 isoNegative:string 2532 }>, 2533 script:string, 2534 decimalChar:string, 2535 groupChar:string, 2536 prigroupSize:number, 2537 secgroupSize:number, 2538 negativenumFmt:string, 2539 pctFmt:string, 2540 negativepctFmt:string, 2541 pctChar:string, 2542 roundingMode:string, 2543 exponential:string, 2544 digits:string 2545 }> 2546 }}*/ ilib.data.localeinfo; 2547 LocaleInfo.defaultInfo = LocaleInfo.defaultInfo || { 2548 "scripts": ["Latn"], 2549 "timezone": "Etc/UTC", 2550 "units": "metric", 2551 "calendar": "gregorian", 2552 "clock": "24", 2553 "currency": "USD", 2554 "firstDayOfWeek": 1, 2555 "meridiems": "gregorian", 2556 "numfmt": { 2557 "currencyFormats": { 2558 "common": "{s}{n}", 2559 "commonNegative": "{s}-{n}", 2560 "iso": "{s}{n}", 2561 "isoNegative": "{s}-{n}" 2562 }, 2563 "script": "Latn", 2564 "decimalChar": ",", 2565 "groupChar": ".", 2566 "prigroupSize": 3, 2567 "secgroupSize": 0, 2568 "pctFmt": "{n}%", 2569 "negativepctFmt": "-{n}%", 2570 "pctChar": "%", 2571 "roundingMode": "halfdown", 2572 "exponential": "e", 2573 "digits": "" 2574 } 2575 }; 2576 2577 LocaleInfo.prototype = { 2578 /** 2579 * Return the name of the locale's language in English. 2580 * @returns {string} the name of the locale's language in English 2581 */ 2582 getLanguageName: function () { 2583 return this.info["language.name"]; 2584 }, 2585 2586 /** 2587 * Return the name of the locale's region in English. If the locale 2588 * has no region, this returns undefined. 2589 * 2590 * @returns {string|undefined} the name of the locale's region in English 2591 */ 2592 getRegionName: function () { 2593 return this.info["region.name"]; 2594 }, 2595 2596 /** 2597 * Return whether this locale commonly uses the 12- or the 24-hour clock. 2598 * 2599 * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24" 2600 * if the locale commonly uses a 24-hour clock. 2601 */ 2602 getClock: function() { 2603 return this.info.clock; 2604 }, 2605 2606 /** 2607 * Return the locale that this info object was created with. 2608 * @returns {Locale} The locale spec of the locale used to construct this info instance 2609 */ 2610 getLocale: function () { 2611 return this.locale; 2612 }, 2613 2614 /** 2615 * Return the name of the measuring system that is commonly used in the given locale. 2616 * Valid values are "uscustomary", "imperial", and "metric". 2617 * 2618 * @returns {string} The name of the measuring system commonly used in the locale 2619 */ 2620 getUnits: function () { 2621 return this.info.units; 2622 }, 2623 2624 getUnitFormat: function () { 2625 return this.info.unitfmt; 2626 }, 2627 2628 /** 2629 * Return the name of the calendar that is commonly used in the given locale. 2630 * 2631 * @returns {string} The name of the calendar commonly used in the locale 2632 */ 2633 getCalendar: function () { 2634 return this.info.calendar; 2635 }, 2636 2637 /** 2638 * Return the day of week that starts weeks in the current locale. Days are still 2639 * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars 2640 * should be displayed and weeks calculated with the day of week returned from this 2641 * function as the first day of the week. 2642 * 2643 * @returns {number} the day of the week that starts weeks in the current locale. 2644 */ 2645 getFirstDayOfWeek: function () { 2646 return this.info.firstDayOfWeek; 2647 }, 2648 2649 /** 2650 * Return the day of week that starts weekend in the current locale. Days are still 2651 * numbered the standard way with 0 for Sunday through 6 for Saturday. 2652 * 2653 * @returns {number} the day of the week that starts weeks in the current locale. 2654 */ 2655 getWeekEndStart: function () { 2656 return this.info.weekendStart; 2657 }, 2658 2659 /** 2660 * Return the day of week that starts weekend in the current locale. Days are still 2661 * numbered the standard way with 0 for Sunday through 6 for Saturday. 2662 * 2663 * @returns {number} the day of the week that starts weeks in the current locale. 2664 */ 2665 getWeekEndEnd: function () { 2666 return this.info.weekendEnd; 2667 }, 2668 2669 /** 2670 * Return the default time zone for this locale. Many locales span across multiple 2671 * time zones. In this case, the time zone with the largest population is chosen 2672 * to represent the locale. This is obviously not that accurate, but then again, 2673 * this method's return value should only be used as a default anyways. 2674 * @returns {string} the default time zone for this locale. 2675 */ 2676 getTimeZone: function () { 2677 return this.info.timezone; 2678 }, 2679 2680 /** 2681 * Return the decimal separator for formatted numbers in this locale. 2682 * @returns {string} the decimal separator char 2683 */ 2684 getDecimalSeparator: function () { 2685 return this.info.numfmt.decimalChar; 2686 }, 2687 2688 /** 2689 * Return the decimal separator for formatted numbers in this locale for native script. 2690 * @returns {string} the decimal separator char 2691 */ 2692 getNativeDecimalSeparator: function () { 2693 return (this.info.native_numfmt && this.info.native_numfmt.decimalChar) || this.info.numfmt.decimalChar; 2694 }, 2695 2696 /** 2697 * Return the separator character used to separate groups of digits on the 2698 * integer side of the decimal character. 2699 * @returns {string} the grouping separator char 2700 */ 2701 getGroupingSeparator: function () { 2702 return this.info.numfmt.groupChar; 2703 }, 2704 2705 /** 2706 * Return the separator character used to separate groups of digits on the 2707 * integer side of the decimal character for the native script if present other than the default script. 2708 * @returns {string} the grouping separator char 2709 */ 2710 getNativeGroupingSeparator: function () { 2711 return (this.info.native_numfmt && this.info.native_numfmt.groupChar) || this.info.numfmt.groupChar; 2712 }, 2713 2714 /** 2715 * Return the minimum number of digits grouped together on the integer side 2716 * for the first (primary) group. 2717 * In western European cultures, groupings are in 1000s, so the number of digits 2718 * is 3. 2719 * @returns {number} the number of digits in a primary grouping, or 0 for no grouping 2720 */ 2721 getPrimaryGroupingDigits: function () { 2722 return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' && this.info.numfmt.prigroupSize) || 0; 2723 }, 2724 2725 /** 2726 * Return the minimum number of digits grouped together on the integer side 2727 * for the second or more (secondary) group.<p> 2728 * 2729 * In western European cultures, all groupings are by 1000s, so the secondary 2730 * size should be 0 because there is no secondary size. In general, if this 2731 * method returns 0, then all groupings are of the primary size.<p> 2732 * 2733 * For some other cultures, the first grouping (primary) 2734 * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be 2735 * written as: "1,00,000". 2736 * 2737 * @returns {number} the number of digits in a secondary grouping, or 0 for no 2738 * secondary grouping. 2739 */ 2740 getSecondaryGroupingDigits: function () { 2741 return this.info.numfmt.secgroupSize || 0; 2742 }, 2743 2744 /** 2745 * Return the format template used to format percentages in this locale. 2746 * @returns {string} the format template for formatting percentages 2747 */ 2748 getPercentageFormat: function () { 2749 return this.info.numfmt.pctFmt; 2750 }, 2751 2752 /** 2753 * Return the format template used to format percentages in this locale 2754 * with negative amounts. 2755 * @returns {string} the format template for formatting percentages 2756 */ 2757 getNegativePercentageFormat: function () { 2758 return this.info.numfmt.negativepctFmt; 2759 }, 2760 2761 /** 2762 * Return the symbol used for percentages in this locale. 2763 * @returns {string} the symbol used for percentages in this locale 2764 */ 2765 getPercentageSymbol: function () { 2766 return this.info.numfmt.pctChar || "%"; 2767 }, 2768 2769 /** 2770 * Return the symbol used for exponential in this locale. 2771 * @returns {string} the symbol used for exponential in this locale 2772 */ 2773 getExponential: function () { 2774 return this.info.numfmt.exponential; 2775 }, 2776 2777 /** 2778 * Return the symbol used for exponential in this locale for native script. 2779 * @returns {string} the symbol used for exponential in this locale for native script 2780 */ 2781 getNativeExponential: function () { 2782 return (this.info.native_numfmt && this.info.native_numfmt.exponential) || this.info.numfmt.exponential; 2783 }, 2784 2785 /** 2786 * Return the symbol used for percentages in this locale for native script. 2787 * @returns {string} the symbol used for percentages in this locale for native script 2788 */ 2789 getNativePercentageSymbol: function () { 2790 return (this.info.native_numfmt && this.info.native_numfmt.pctChar) || this.info.numfmt.pctChar || "%"; 2791 2792 }, 2793 /** 2794 * Return the format template used to format negative numbers in this locale. 2795 * @returns {string} the format template for formatting negative numbers 2796 */ 2797 getNegativeNumberFormat: function () { 2798 return this.info.numfmt.negativenumFmt; 2799 }, 2800 2801 /** 2802 * Return an object containing the format templates for formatting currencies 2803 * in this locale. The object has a number of properties in it that each are 2804 * a particular style of format. Normally, this contains a "common" and an "iso" 2805 * style, but may contain others in the future. 2806 * @returns {Object} an object containing the format templates for currencies 2807 */ 2808 getCurrencyFormats: function () { 2809 return this.info.numfmt.currencyFormats; 2810 }, 2811 2812 /** 2813 * Return the currency that is legal in the locale, or which is most commonly 2814 * used in regular commerce. 2815 * @returns {string} the ISO 4217 code for the currency of this locale 2816 */ 2817 getCurrency: function () { 2818 return this.info.currency; 2819 }, 2820 2821 /** 2822 * Return a string that describes the style of digits used by this locale. 2823 * Possible return values are: 2824 * <ul> 2825 * <li><i>western</i> - uses the regular western 10-based digits 0 through 9 2826 * <li><i>optional</i> - native 10-based digits exist, but in modern usage, 2827 * this locale most often uses western digits 2828 * <li><i>native</i> - native 10-based native digits exist and are used 2829 * regularly by this locale 2830 * <li><i>custom</i> - uses native digits by default that are not 10-based 2831 * </ul> 2832 * @returns {string} string that describes the style of digits used in this locale 2833 */ 2834 getDigitsStyle: function () { 2835 if (this.info.numfmt.useNative) { 2836 return "native"; 2837 } 2838 if (typeof(this.info.native_numfmt) !== 'undefined') { 2839 return "optional"; 2840 } 2841 return "western"; 2842 }, 2843 2844 /** 2845 * Return the digits of the default script if they are defined. 2846 * If not defined, the default should be the regular "Arabic numerals" 2847 * used in the Latin script. (0-9) 2848 * @returns {string|undefined} the digits used in the default script 2849 */ 2850 getDigits: function () { 2851 return this.info.numfmt.digits; 2852 }, 2853 2854 /** 2855 * Return the digits of the native script if they are defined. 2856 * @returns {string|undefined} the digits used in the default script 2857 */ 2858 getNativeDigits: function () { 2859 return (this.info.numfmt.useNative && this.info.numfmt.digits) || (this.info.native_numfmt && this.info.native_numfmt.digits); 2860 }, 2861 2862 /** 2863 * If this locale typically uses a different type of rounding for numeric 2864 * formatting other than halfdown, especially for currency, then it can be 2865 * specified in the localeinfo. If the locale uses the default, then this 2866 * method returns undefined. The locale's rounding method overrides the 2867 * rounding method for the currency itself, which can sometimes shared 2868 * between various locales so it is less specific. 2869 * @returns {string} the name of the rounding mode typically used in this 2870 * locale, or "halfdown" if the locale does not override the default 2871 */ 2872 getRoundingMode: function () { 2873 return this.info.numfmt.roundingMode; 2874 }, 2875 2876 /** 2877 * Return the default script used to write text in the language of this 2878 * locale. Text for most languages is written in only one script, but there 2879 * are some languages where the text can be written in a number of scripts, 2880 * depending on a variety of things such as the region, ethnicity, religion, 2881 * etc. of the author. This method returns the default script for the 2882 * locale, in which the language is most commonly written.<p> 2883 * 2884 * The script is returned as an ISO 15924 4-letter code. 2885 * 2886 * @returns {string} the ISO 15924 code for the default script used to write 2887 * text in this locale 2888 */ 2889 getDefaultScript: function() { 2890 return (this.info.scripts) ? this.info.scripts[0] : "Latn"; 2891 }, 2892 2893 /** 2894 * Return the script used for the current locale. If the current locale 2895 * explicitly defines a script, then this script is returned. If not, then 2896 * the default script for the locale is returned. 2897 * 2898 * @see LocaleInfo.getDefaultScript 2899 * @returns {string} the ISO 15924 code for the script used to write 2900 * text in this locale 2901 */ 2902 getScript: function() { 2903 return this.locale.getScript() || this.getDefaultScript(); 2904 }, 2905 2906 /** 2907 * Return an array of script codes which are used to write text in the current 2908 * language. Text for most languages is written in only one script, but there 2909 * are some languages where the text can be written in a number of scripts, 2910 * depending on a variety of things such as the region, ethnicity, religion, 2911 * etc. of the author. This method returns an array of script codes in which 2912 * the language is commonly written. 2913 * 2914 * @returns {Array.<string>} an array of ISO 15924 codes for the scripts used 2915 * to write text in this language 2916 */ 2917 getAllScripts: function() { 2918 return this.info.scripts || ["Latn"]; 2919 }, 2920 2921 /** 2922 * Return the default style of meridiems used in this locale. Meridiems are 2923 * times of day like AM/PM. In a few locales with some calendars, for example 2924 * Amharic/Ethiopia using the Ethiopic calendar, the times of day may be 2925 * split into different segments than simple AM/PM as in the Gregorian 2926 * calendar. Only a few locales are like that. For most locales, formatting 2927 * a Gregorian date will use the regular Gregorian AM/PM meridiems. 2928 * 2929 * @returns {string} the default meridiems style used in this locale. Possible 2930 * values are "gregorian", "chinese", and "ethiopic" 2931 */ 2932 getMeridiemsStyle: function () { 2933 return this.info.meridiems || "gregorian"; 2934 } 2935 }; 2936 2937 2938 2939 /*< IDate.js */ 2940 /* 2941 * IDate.js - Represent a date in any calendar. This class is subclassed for each 2942 * calendar and includes some shared functionality. 2943 * 2944 * Copyright © 2012-2015, JEDLSoft 2945 * 2946 * Licensed under the Apache License, Version 2.0 (the "License"); 2947 * you may not use this file except in compliance with the License. 2948 * You may obtain a copy of the License at 2949 * 2950 * http://www.apache.org/licenses/LICENSE-2.0 2951 * 2952 * Unless required by applicable law or agreed to in writing, software 2953 * distributed under the License is distributed on an "AS IS" BASIS, 2954 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2955 * 2956 * See the License for the specific language governing permissions and 2957 * limitations under the License. 2958 */ 2959 2960 /* !depends LocaleInfo.js */ 2961 2962 2963 /** 2964 * @class 2965 * Superclass for all the calendar date classes that contains shared 2966 * functionality. This class is never instantiated on its own. Instead, 2967 * you should use the {@link DateFactory} function to manufacture a new 2968 * instance of a subclass of IDate. This class is called IDate for "ilib 2969 * date" so that it does not conflict with the built-in Javascript Date 2970 * class. 2971 * 2972 * @private 2973 * @constructor 2974 * @param {Object=} options The date components to initialize this date with 2975 */ 2976 var IDate = function(options) { 2977 }; 2978 2979 /* place for the subclasses to put their constructors so that the factory method 2980 * can find them. Do this to add your date after it's defined: 2981 * IDate._constructors["mytype"] = IDate.MyTypeConstructor; 2982 */ 2983 IDate._constructors = {}; 2984 2985 IDate.prototype = { 2986 getType: function() { 2987 return "date"; 2988 }, 2989 2990 /** 2991 * Return the unix time equivalent to this date instance. Unix time is 2992 * the number of milliseconds since midnight on Jan 1, 1970 UTC (Gregorian). This 2993 * method only returns a valid number for dates between midnight, 2994 * Jan 1, 1970 UTC (Gregorian) and Jan 19, 2038 at 3:14:07am UTC (Gregorian) when 2995 * the unix time runs out. If this instance encodes a date outside of that range, 2996 * this method will return -1. For date types that are not Gregorian, the point 2997 * in time represented by this date object will only give a return value if it 2998 * is in the correct range in the Gregorian calendar as given previously. 2999 * 3000 * @return {number} a number giving the unix time, or -1 if the date is outside the 3001 * valid unix time range 3002 */ 3003 getTime: function() { 3004 return this.rd.getTime(); 3005 }, 3006 3007 /** 3008 * Return the extended unix time equivalent to this Gregorian date instance. Unix time is 3009 * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time 3010 * (or the type "time_t" in C/C++) is only encoded with an unsigned 32 bit integer, and thus 3011 * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above 3012 * 32 bits and the Date object allows you to encode up to 100 million days worth of time 3013 * after Jan 1, 1970, and even more interestingly, 100 million days worth of time before 3014 * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended 3015 * range. If this instance encodes a date outside of that range, this method will return 3016 * NaN. 3017 * 3018 * @return {number} a number giving the extended unix time, or Nan if the date is outside 3019 * the valid extended unix time range 3020 */ 3021 getTimeExtended: function() { 3022 return this.rd.getTimeExtended(); 3023 }, 3024 3025 /** 3026 * Set the time of this instance according to the given unix time. Unix time is 3027 * the number of milliseconds since midnight on Jan 1, 1970. 3028 * 3029 * @param {number} millis the unix time to set this date to in milliseconds 3030 */ 3031 setTime: function(millis) { 3032 this.rd = this.newRd({ 3033 unixtime: millis, 3034 cal: this.cal 3035 }); 3036 this._calcDateComponents(); 3037 }, 3038 3039 getDays: function() { 3040 return this.day; 3041 }, 3042 getMonths: function() { 3043 return this.month; 3044 }, 3045 getYears: function() { 3046 return this.year; 3047 }, 3048 getHours: function() { 3049 return this.hour; 3050 }, 3051 getMinutes: function() { 3052 return this.minute; 3053 }, 3054 getSeconds: function() { 3055 return this.second; 3056 }, 3057 getMilliseconds: function() { 3058 return this.millisecond; 3059 }, 3060 getEra: function() { 3061 return (this.year < 1) ? -1 : 1; 3062 }, 3063 3064 setDays: function(day) { 3065 this.day = parseInt(day, 10) || 1; 3066 this.rd._setDateComponents(this); 3067 }, 3068 setMonths: function(month) { 3069 this.month = parseInt(month, 10) || 1; 3070 this.rd._setDateComponents(this); 3071 }, 3072 setYears: function(year) { 3073 this.year = parseInt(year, 10) || 0; 3074 this.rd._setDateComponents(this); 3075 }, 3076 3077 setHours: function(hour) { 3078 this.hour = parseInt(hour, 10) || 0; 3079 this.rd._setDateComponents(this); 3080 }, 3081 setMinutes: function(minute) { 3082 this.minute = parseInt(minute, 10) || 0; 3083 this.rd._setDateComponents(this); 3084 }, 3085 setSeconds: function(second) { 3086 this.second = parseInt(second, 10) || 0; 3087 this.rd._setDateComponents(this); 3088 }, 3089 setMilliseconds: function(milli) { 3090 this.millisecond = parseInt(milli, 10) || 0; 3091 this.rd._setDateComponents(this); 3092 }, 3093 3094 /** 3095 * Return a new date instance in the current calendar that represents the first instance 3096 * of the given day of the week before the current date. The day of the week is encoded 3097 * as a number where 0 = Sunday, 1 = Monday, etc. 3098 * 3099 * @param {number} dow the day of the week before the current date that is being sought 3100 * @return {IDate} the date being sought 3101 */ 3102 before: function (dow) { 3103 return new this.constructor({ 3104 rd: this.rd.before(dow, this.offset), 3105 timezone: this.timezone 3106 }); 3107 }, 3108 3109 /** 3110 * Return a new date instance in the current calendar that represents the first instance 3111 * of the given day of the week after the current date. The day of the week is encoded 3112 * as a number where 0 = Sunday, 1 = Monday, etc. 3113 * 3114 * @param {number} dow the day of the week after the current date that is being sought 3115 * @return {IDate} the date being sought 3116 */ 3117 after: function (dow) { 3118 return new this.constructor({ 3119 rd: this.rd.after(dow, this.offset), 3120 timezone: this.timezone 3121 }); 3122 }, 3123 3124 /** 3125 * Return a new Gregorian date instance that represents the first instance of the 3126 * given day of the week on or before the current date. The day of the week is encoded 3127 * as a number where 0 = Sunday, 1 = Monday, etc. 3128 * 3129 * @param {number} dow the day of the week on or before the current date that is being sought 3130 * @return {IDate} the date being sought 3131 */ 3132 onOrBefore: function (dow) { 3133 return new this.constructor({ 3134 rd: this.rd.onOrBefore(dow, this.offset), 3135 timezone: this.timezone 3136 }); 3137 }, 3138 3139 /** 3140 * Return a new Gregorian date instance that represents the first instance of the 3141 * given day of the week on or after the current date. The day of the week is encoded 3142 * as a number where 0 = Sunday, 1 = Monday, etc. 3143 * 3144 * @param {number} dow the day of the week on or after the current date that is being sought 3145 * @return {IDate} the date being sought 3146 */ 3147 onOrAfter: function (dow) { 3148 return new this.constructor({ 3149 rd: this.rd.onOrAfter(dow, this.offset), 3150 timezone: this.timezone 3151 }); 3152 }, 3153 3154 /** 3155 * Return a Javascript Date object that is equivalent to this date 3156 * object. 3157 * 3158 * @return {Date|undefined} a javascript Date object 3159 */ 3160 getJSDate: function() { 3161 var unix = this.rd.getTimeExtended(); 3162 return isNaN(unix) ? undefined : new Date(unix); 3163 }, 3164 3165 /** 3166 * Return the Rata Die (fixed day) number of this date. 3167 * 3168 * @protected 3169 * @return {number} the rd date as a number 3170 */ 3171 getRataDie: function() { 3172 return this.rd.getRataDie(); 3173 }, 3174 3175 /** 3176 * Set the date components of this instance based on the given rd. 3177 * @protected 3178 * @param {number} rd the rata die date to set 3179 */ 3180 setRd: function (rd) { 3181 this.rd = this.newRd({ 3182 rd: rd, 3183 cal: this.cal 3184 }); 3185 this._calcDateComponents(); 3186 }, 3187 3188 /** 3189 * Return the Julian Day equivalent to this calendar date as a number. 3190 * 3191 * @return {number} the julian date equivalent of this date 3192 */ 3193 getJulianDay: function() { 3194 return this.rd.getJulianDay(); 3195 }, 3196 3197 /** 3198 * Set the date of this instance using a Julian Day. 3199 * @param {number|JulianDay} date the Julian Day to use to set this date 3200 */ 3201 setJulianDay: function (date) { 3202 this.rd = this.newRd({ 3203 julianday: (typeof(date) === 'object') ? date.getDate() : date, 3204 cal: this.cal 3205 }); 3206 this._calcDateComponents(); 3207 }, 3208 3209 /** 3210 * Return the time zone associated with this date, or 3211 * undefined if none was specified in the constructor. 3212 * 3213 * @return {string|undefined} the name of the time zone for this date instance 3214 */ 3215 getTimeZone: function() { 3216 return this.timezone || "local"; 3217 }, 3218 3219 /** 3220 * Set the time zone associated with this date. 3221 * @param {string=} tzName the name of the time zone to set into this date instance, 3222 * or "undefined" to unset the time zone 3223 */ 3224 setTimeZone: function (tzName) { 3225 if (!tzName || tzName === "") { 3226 // same as undefining it 3227 this.timezone = undefined; 3228 this.tz = undefined; 3229 } else if (typeof(tzName) === 'string') { 3230 this.timezone = tzName; 3231 this.tz = undefined; 3232 // assuming the same UTC time, but a new time zone, now we have to 3233 // recalculate what the date components are 3234 this._calcDateComponents(); 3235 } 3236 }, 3237 3238 /** 3239 * Return the rd number of the first Sunday of the given ISO year. 3240 * @protected 3241 * @param {number} year the year for which the first Sunday is being sought 3242 * @return {number} the rd of the first Sunday of the ISO year 3243 */ 3244 firstSunday: function (year) { 3245 var firstDay = this.newRd({ 3246 year: year, 3247 month: 1, 3248 day: 1, 3249 hour: 0, 3250 minute: 0, 3251 second: 0, 3252 millisecond: 0, 3253 cal: this.cal 3254 }); 3255 var firstThu = this.newRd({ 3256 rd: firstDay.onOrAfter(4), 3257 cal: this.cal 3258 }); 3259 return firstThu.before(0); 3260 }, 3261 3262 /** 3263 * Return the ISO 8601 week number in the current year for the current date. The week 3264 * number ranges from 0 to 55, as some years have 55 weeks assigned to them in some 3265 * calendars. 3266 * 3267 * @return {number} the week number for the current date 3268 */ 3269 getWeekOfYear: function() { 3270 var rd = Math.floor(this.rd.getRataDie()); 3271 var year = this._calcYear(rd + this.offset); 3272 var yearStart = this.firstSunday(year); 3273 var nextYear; 3274 3275 // if we have a January date, it may be in this ISO year or the previous year 3276 if (rd < yearStart) { 3277 yearStart = this.firstSunday(year-1); 3278 } else { 3279 // if we have a late December date, it may be in this ISO year, or the next year 3280 nextYear = this.firstSunday(year+1); 3281 if (rd >= nextYear) { 3282 yearStart = nextYear; 3283 } 3284 } 3285 3286 return Math.floor((rd-yearStart)/7) + 1; 3287 }, 3288 3289 /** 3290 * Return the ordinal number of the week within the month. The first week of a month is 3291 * the first one that contains 4 or more days in that month. If any days precede this 3292 * first week, they are marked as being in week 0. This function returns values from 0 3293 * through 6.<p> 3294 * 3295 * The locale is a required parameter because different locales that use the same 3296 * Gregorian calendar consider different days of the week to be the beginning of 3297 * the week. This can affect the week of the month in which some days are located. 3298 * 3299 * @param {Locale|string} locale the locale or locale spec to use when figuring out 3300 * the first day of the week 3301 * @return {number} the ordinal number of the week within the current month 3302 */ 3303 getWeekOfMonth: function(locale) { 3304 var li = new LocaleInfo(locale); 3305 3306 var first = this.newRd({ 3307 year: this._calcYear(this.rd.getRataDie()+this.offset), 3308 month: this.getMonths(), 3309 day: 1, 3310 hour: 0, 3311 minute: 0, 3312 second: 0, 3313 millisecond: 0, 3314 cal: this.cal 3315 }); 3316 var weekStart = first.onOrAfter(li.getFirstDayOfWeek()); 3317 3318 if (weekStart - first.getRataDie() > 3) { 3319 // if the first week has 4 or more days in it of the current month, then consider 3320 // that week 1. Otherwise, it is week 0. To make it week 1, move the week start 3321 // one week earlier. 3322 weekStart -= 7; 3323 } 3324 return Math.floor((this.rd.getRataDie() - weekStart) / 7) + 1; 3325 } 3326 }; 3327 3328 3329 /*< MathUtils.js */ 3330 /* 3331 * MathUtils.js - Misc math utility routines 3332 * 3333 * Copyright © 2013-2015, JEDLSoft 3334 * 3335 * Licensed under the Apache License, Version 2.0 (the "License"); 3336 * you may not use this file except in compliance with the License. 3337 * You may obtain a copy of the License at 3338 * 3339 * http://www.apache.org/licenses/LICENSE-2.0 3340 * 3341 * Unless required by applicable law or agreed to in writing, software 3342 * distributed under the License is distributed on an "AS IS" BASIS, 3343 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3344 * 3345 * See the License for the specific language governing permissions and 3346 * limitations under the License. 3347 */ 3348 3349 var MathUtils = {}; 3350 3351 /** 3352 * Return the sign of the given number. If the sign is negative, this function 3353 * returns -1. If the sign is positive or zero, this function returns 1. 3354 * @static 3355 * @param {number} num the number to test 3356 * @return {number} -1 if the number is negative, and 1 otherwise 3357 */ 3358 MathUtils.signum = function (num) { 3359 var n = num; 3360 if (typeof(num) === 'string') { 3361 n = parseInt(num, 10); 3362 } else if (typeof(num) !== 'number') { 3363 return 1; 3364 } 3365 return (n < 0) ? -1 : 1; 3366 }; 3367 3368 /** 3369 * @static 3370 * @protected 3371 * @param {number} num number to round 3372 * @return {number} rounded number 3373 */ 3374 MathUtils.floor = function (num) { 3375 return Math.floor(num); 3376 }; 3377 3378 /** 3379 * @static 3380 * @protected 3381 * @param {number} num number to round 3382 * @return {number} rounded number 3383 */ 3384 MathUtils.ceiling = function (num) { 3385 return Math.ceil(num); 3386 }; 3387 3388 /** 3389 * @static 3390 * @protected 3391 * @param {number} num number to round 3392 * @return {number} rounded number 3393 */ 3394 MathUtils.down = function (num) { 3395 return (num < 0) ? Math.ceil(num) : Math.floor(num); 3396 }; 3397 3398 /** 3399 * @static 3400 * @protected 3401 * @param {number} num number to round 3402 * @return {number} rounded number 3403 */ 3404 MathUtils.up = function (num) { 3405 return (num < 0) ? Math.floor(num) : Math.ceil(num); 3406 }; 3407 3408 /** 3409 * @static 3410 * @protected 3411 * @param {number} num number to round 3412 * @return {number} rounded number 3413 */ 3414 MathUtils.halfup = function (num) { 3415 return (num < 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 3416 }; 3417 3418 /** 3419 * @static 3420 * @protected 3421 * @param {number} num number to round 3422 * @return {number} rounded number 3423 */ 3424 MathUtils.halfdown = function (num) { 3425 return (num < 0) ? Math.floor(num + 0.5) : Math.ceil(num - 0.5); 3426 }; 3427 3428 /** 3429 * @static 3430 * @protected 3431 * @param {number} num number to round 3432 * @return {number} rounded number 3433 */ 3434 MathUtils.halfeven = function (num) { 3435 return (Math.floor(num) % 2 === 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 3436 }; 3437 3438 /** 3439 * @static 3440 * @protected 3441 * @param {number} num number to round 3442 * @return {number} rounded number 3443 */ 3444 MathUtils.halfodd = function (num) { 3445 return (Math.floor(num) % 2 !== 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 3446 }; 3447 3448 /** 3449 * Do a proper modulo function. The Javascript % operator will give the truncated 3450 * division algorithm, but for calendrical calculations, we need the Euclidean 3451 * division algorithm where the remainder of any division, whether the dividend 3452 * is negative or not, is always a positive number in the range [0, modulus).<p> 3453 * 3454 * 3455 * @static 3456 * @param {number} dividend the number being divided 3457 * @param {number} modulus the number dividing the dividend. This should always be a positive number. 3458 * @return the remainder of dividing the dividend by the modulus. 3459 */ 3460 MathUtils.mod = function (dividend, modulus) { 3461 if (modulus == 0) { 3462 return 0; 3463 } 3464 var x = dividend % modulus; 3465 return (x < 0) ? x + modulus : x; 3466 }; 3467 3468 /** 3469 * Do a proper adjusted modulo function. The Javascript % operator will give the truncated 3470 * division algorithm, but for calendrical calculations, we need the Euclidean 3471 * division algorithm where the remainder of any division, whether the dividend 3472 * is negative or not, is always a positive number in the range (0, modulus]. The adjusted 3473 * modulo function differs from the regular modulo function in that when the remainder is 3474 * zero, the modulus should be returned instead.<p> 3475 * 3476 * 3477 * @static 3478 * @param {number} dividend the number being divided 3479 * @param {number} modulus the number dividing the dividend. This should always be a positive number. 3480 * @return the remainder of dividing the dividend by the modulus. 3481 */ 3482 MathUtils.amod = function (dividend, modulus) { 3483 if (modulus == 0) { 3484 return 0; 3485 } 3486 var x = dividend % modulus; 3487 return (x <= 0) ? x + modulus : x; 3488 }; 3489 3490 3491 3492 /*< IString.js */ 3493 /* 3494 * IString.js - ilib string subclass definition 3495 * 3496 * Copyright © 2012-2015, JEDLSoft 3497 * 3498 * Licensed under the Apache License, Version 2.0 (the "License"); 3499 * you may not use this file except in compliance with the License. 3500 * You may obtain a copy of the License at 3501 * 3502 * http://www.apache.org/licenses/LICENSE-2.0 3503 * 3504 * Unless required by applicable law or agreed to in writing, software 3505 * distributed under the License is distributed on an "AS IS" BASIS, 3506 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3507 * 3508 * See the License for the specific language governing permissions and 3509 * limitations under the License. 3510 */ 3511 3512 // !depends ilib.js Utils.js Locale.js MathUtils.js 3513 3514 // !data plurals 3515 3516 3517 /** 3518 * @class 3519 * Create a new ilib string instance. This string inherits from and 3520 * extends the Javascript String class. It can be 3521 * used almost anywhere that a normal Javascript string is used, though in 3522 * some instances you will need to call the {@link #toString} method when 3523 * a built-in Javascript string is needed. The formatting methods are 3524 * methods that are not in the intrinsic String class and are most useful 3525 * when localizing strings in an app or web site in combination with 3526 * the ResBundle class.<p> 3527 * 3528 * This class is named IString ("ilib string") so as not to conflict with the 3529 * built-in Javascript String class. 3530 * 3531 * @constructor 3532 * @param {string|IString=} string initialize this instance with this string 3533 */ 3534 var IString = function (string) { 3535 if (typeof(string) === 'object') { 3536 if (string instanceof IString) { 3537 this.str = string.str; 3538 } else { 3539 this.str = string.toString(); 3540 } 3541 } else if (typeof(string) === 'string') { 3542 this.str = new String(string); 3543 } else { 3544 this.str = ""; 3545 } 3546 this.length = this.str.length; 3547 this.cpLength = -1; 3548 this.localeSpec = ilib.getLocale(); 3549 }; 3550 3551 /** 3552 * Return true if the given character is a Unicode surrogate character, 3553 * either high or low. 3554 * 3555 * @private 3556 * @static 3557 * @param {string} ch character to check 3558 * @return {boolean} true if the character is a surrogate 3559 */ 3560 IString._isSurrogate = function (ch) { 3561 var n = ch.charCodeAt(0); 3562 return ((n >= 0xDC00 && n <= 0xDFFF) || (n >= 0xD800 && n <= 0xDBFF)); 3563 }; 3564 3565 /** 3566 * Convert a UCS-4 code point to a Javascript string. The codepoint can be any valid 3567 * UCS-4 Unicode character, including supplementary characters. Standard Javascript 3568 * only supports supplementary characters using the UTF-16 encoding, which has 3569 * values in the range 0x0000-0xFFFF. String.fromCharCode() will only 3570 * give you a string containing 16-bit characters, and will not properly convert 3571 * the code point for a supplementary character (which has a value > 0xFFFF) into 3572 * two UTF-16 surrogate characters. Instead, it will just just give you whatever 3573 * single character happens to be the same as your code point modulo 0x10000, which 3574 * is almost never what you want.<p> 3575 * 3576 * Similarly, that means if you use String.charCodeAt() 3577 * you will only retrieve a 16-bit value, which may possibly be a single 3578 * surrogate character that is part of a surrogate pair representing a character 3579 * in the supplementary plane. It will not give you a code point. Use 3580 * IString.codePointAt() to access code points in a string, or use 3581 * an iterator to walk through the code points in a string. 3582 * 3583 * @static 3584 * @param {number} codepoint UCS-4 code point to convert to a character 3585 * @return {string} a string containing the character represented by the codepoint 3586 */ 3587 IString.fromCodePoint = function (codepoint) { 3588 if (codepoint < 0x10000) { 3589 return String.fromCharCode(codepoint); 3590 } else { 3591 var high = Math.floor(codepoint / 0x10000) - 1; 3592 var low = codepoint & 0xFFFF; 3593 3594 return String.fromCharCode(0xD800 | ((high & 0x000F) << 6) | ((low & 0xFC00) >> 10)) + 3595 String.fromCharCode(0xDC00 | (low & 0x3FF)); 3596 } 3597 }; 3598 3599 /** 3600 * Convert the character or the surrogate pair at the given 3601 * index into the intrinsic Javascript string to a Unicode 3602 * UCS-4 code point. 3603 * 3604 * @param {string} str string to get the code point from 3605 * @param {number} index index into the string 3606 * @return {number} code point of the character at the 3607 * given index into the string 3608 */ 3609 IString.toCodePoint = function(str, index) { 3610 if (!str || str.length === 0) { 3611 return -1; 3612 } 3613 var code = -1, high = str.charCodeAt(index); 3614 if (high >= 0xD800 && high <= 0xDBFF) { 3615 if (str.length > index+1) { 3616 var low = str.charCodeAt(index+1); 3617 if (low >= 0xDC00 && low <= 0xDFFF) { 3618 code = (((high & 0x3C0) >> 6) + 1) << 16 | 3619 (((high & 0x3F) << 10) | (low & 0x3FF)); 3620 } 3621 } 3622 } else { 3623 code = high; 3624 } 3625 3626 return code; 3627 }; 3628 3629 /** 3630 * Load the plural the definitions of plurals for the locale. 3631 * @param {boolean=} sync 3632 * @param {Locale|string=} locale 3633 * @param {Object=} loadParams 3634 * @param {function(*)=} onLoad 3635 */ 3636 IString.loadPlurals = function (sync, locale, loadParams, onLoad) { 3637 var loc; 3638 if (locale) { 3639 loc = (typeof(locale) === 'string') ? new Locale(locale) : locale; 3640 } else { 3641 loc = new Locale(ilib.getLocale()); 3642 } 3643 var spec = loc.getLanguage(); 3644 if (!ilib.data["plurals_" + spec]) { 3645 Utils.loadData({ 3646 name: "plurals.json", 3647 object: IString, 3648 locale: loc, 3649 sync: sync, 3650 loadParams: loadParams, 3651 callback: /** @type function(Object=):undefined */ ilib.bind(this, /** @type function() */ function(plurals) { 3652 if (!plurals) { 3653 IString.cache[spec] = {}; 3654 } 3655 ilib.data["plurals_" + spec] = plurals || {}; 3656 if (onLoad && typeof(onLoad) === 'function') { 3657 onLoad(ilib.data["plurals_" + spec]); 3658 } 3659 }) 3660 }); 3661 } else { 3662 if (onLoad && typeof(onLoad) === 'function') { 3663 onLoad(ilib.data["plurals_" + spec]); 3664 } 3665 } 3666 }; 3667 3668 /** 3669 * @private 3670 * @static 3671 */ 3672 IString._fncs = { 3673 /** 3674 * @private 3675 * @param {Object} obj 3676 * @return {string|undefined} 3677 */ 3678 firstProp: function (obj) { 3679 for (var p in obj) { 3680 if (p && obj[p]) { 3681 return p; 3682 } 3683 } 3684 return undefined; // should never get here 3685 }, 3686 3687 /** 3688 * @private 3689 * @param {Object} obj 3690 * @param {number} n 3691 * @return {?} 3692 */ 3693 getValue: function (obj, n) { 3694 if (typeof(obj) === 'object') { 3695 var subrule = IString._fncs.firstProp(obj); 3696 return IString._fncs[subrule](obj[subrule], n); 3697 } else if (typeof(obj) === 'string') { 3698 return n; 3699 } else { 3700 return obj; 3701 } 3702 }, 3703 3704 /** 3705 * @private 3706 * @param {number} n 3707 * @param {Array.<number|Array.<number>>} range 3708 * @return {boolean} 3709 */ 3710 matchRangeContinuous: function(n, range) { 3711 for (var num in range) { 3712 if (typeof(num) !== 'undefined' && typeof(range[num]) !== 'undefined') { 3713 var obj = /** @type {Object|null|undefined} */ range[num]; 3714 if (typeof(obj) === 'number') { 3715 if (n === range[num]) { 3716 return true; 3717 } 3718 } else if (Object.prototype.toString.call(obj) === '[object Array]') { 3719 if (n >= obj[0] && n <= obj[1]) { 3720 return true; 3721 } 3722 } 3723 } 3724 } 3725 return false; 3726 }, 3727 3728 /** 3729 * @private 3730 * @param {number} n 3731 * @param {Array.<number|Array.<number>>} range 3732 * @return {boolean} 3733 */ 3734 matchRange: function(n, range) { 3735 if (Math.floor(n) !== n) { 3736 return false; 3737 } 3738 return IString._fncs.matchRangeContinuous(n, range); 3739 }, 3740 3741 /** 3742 * @private 3743 * @param {Object} rule 3744 * @param {number} n 3745 * @return {boolean} 3746 */ 3747 is: function(rule, n) { 3748 var left = IString._fncs.getValue(rule[0], n); 3749 var right = IString._fncs.getValue(rule[1], n); 3750 return left == right; 3751 // return IString._fncs.getValue(rule[0]) == IString._fncs.getValue(rule[1]); 3752 }, 3753 3754 /** 3755 * @private 3756 * @param {Object} rule 3757 * @param {number} n 3758 * @return {boolean} 3759 */ 3760 isnot: function(rule, n) { 3761 return IString._fncs.getValue(rule[0], n) != IString._fncs.getValue(rule[1], n); 3762 }, 3763 3764 /** 3765 * @private 3766 * @param {Object} rule 3767 * @param {number} n 3768 * @return {boolean} 3769 */ 3770 inrange: function(rule, n) { 3771 return IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); 3772 }, 3773 3774 /** 3775 * @private 3776 * @param {Object} rule 3777 * @param {number} n 3778 * @return {boolean} 3779 */ 3780 notin: function(rule, n) { 3781 return !IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); 3782 }, 3783 3784 /** 3785 * @private 3786 * @param {Object} rule 3787 * @param {number} n 3788 * @return {boolean} 3789 */ 3790 within: function(rule, n) { 3791 return IString._fncs.matchRangeContinuous(IString._fncs.getValue(rule[0], n), rule[1]); 3792 }, 3793 3794 /** 3795 * @private 3796 * @param {Object} rule 3797 * @param {number} n 3798 * @return {number} 3799 */ 3800 mod: function(rule, n) { 3801 return MathUtils.mod(IString._fncs.getValue(rule[0], n), IString._fncs.getValue(rule[1], n)); 3802 }, 3803 3804 /** 3805 * @private 3806 * @param {Object} rule 3807 * @param {number} n 3808 * @return {number} 3809 */ 3810 n: function(rule, n) { 3811 return n; 3812 }, 3813 3814 /** 3815 * @private 3816 * @param {Object} rule 3817 * @param {number} n 3818 * @return {boolean} 3819 */ 3820 or: function(rule, n) { 3821 return IString._fncs.getValue(rule[0], n) || IString._fncs.getValue(rule[1], n); 3822 }, 3823 3824 /** 3825 * @private 3826 * @param {Object} rule 3827 * @param {number} n 3828 * @return {boolean} 3829 */ 3830 and: function(rule, n) { 3831 return IString._fncs.getValue(rule[0], n) && IString._fncs.getValue(rule[1], n); 3832 } 3833 }; 3834 3835 IString.prototype = { 3836 /** 3837 * Return the length of this string in characters. This function defers to the regular 3838 * Javascript string class in order to perform the length function. Please note that this 3839 * method is a real method, whereas the length property of Javascript strings is 3840 * implemented by native code and appears as a property.<p> 3841 * 3842 * Example: 3843 * 3844 * <pre> 3845 * var str = new IString("this is a string"); 3846 * console.log("String is " + str._length() + " characters long."); 3847 * </pre> 3848 * @private 3849 */ 3850 _length: function () { 3851 return this.str.length; 3852 }, 3853 3854 /** 3855 * Format this string instance as a message, replacing the parameters with 3856 * the given values.<p> 3857 * 3858 * The string can contain any text that a regular Javascript string can 3859 * contain. Replacement parameters have the syntax: 3860 * 3861 * <pre> 3862 * {name} 3863 * </pre> 3864 * 3865 * Where "name" can be any string surrounded by curly brackets. The value of 3866 * "name" is taken from the parameters argument.<p> 3867 * 3868 * Example: 3869 * 3870 * <pre> 3871 * var str = new IString("There are {num} objects."); 3872 * console.log(str.format({ 3873 * num: 12 3874 * }); 3875 * </pre> 3876 * 3877 * Would give the output: 3878 * 3879 * <pre> 3880 * There are 12 objects. 3881 * </pre> 3882 * 3883 * If a property is missing from the parameter block, the replacement 3884 * parameter substring is left untouched in the string, and a different 3885 * set of parameters may be applied a second time. This way, different 3886 * parts of the code may format different parts of the message that they 3887 * happen to know about.<p> 3888 * 3889 * Example: 3890 * 3891 * <pre> 3892 * var str = new IString("There are {num} objects in the {container}."); 3893 * console.log(str.format({ 3894 * num: 12 3895 * }); 3896 * </pre> 3897 * 3898 * Would give the output:<p> 3899 * 3900 * <pre> 3901 * There are 12 objects in the {container}. 3902 * </pre> 3903 * 3904 * The result can then be formatted again with a different parameter block that 3905 * specifies a value for the container property. 3906 * 3907 * @param params a Javascript object containing values for the replacement 3908 * parameters in the current string 3909 * @return a new IString instance with as many replacement parameters filled 3910 * out as possible with real values. 3911 */ 3912 format: function (params) { 3913 var formatted = this.str; 3914 if (params) { 3915 var regex; 3916 for (var p in params) { 3917 if (typeof(params[p]) !== 'undefined') { 3918 regex = new RegExp("\{"+p+"\}", "g"); 3919 formatted = formatted.replace(regex, params[p]); 3920 } 3921 } 3922 } 3923 return formatted.toString(); 3924 }, 3925 3926 /** 3927 * Format a string as one of a choice of strings dependent on the value of 3928 * a particular argument index.<p> 3929 * 3930 * The syntax of the choice string is as follows. The string contains a 3931 * series of choices separated by a vertical bar character "|". Each choice 3932 * has a value or range of values to match followed by a hash character "#" 3933 * followed by the string to use if the variable matches the criteria.<p> 3934 * 3935 * Example string: 3936 * 3937 * <pre> 3938 * var num = 2; 3939 * var str = new IString("0#There are no objects.|1#There is one object.|2#There are {number} objects."); 3940 * console.log(str.formatChoice(num, { 3941 * number: num 3942 * })); 3943 * </pre> 3944 * 3945 * Gives the output: 3946 * 3947 * <pre> 3948 * "There are 2 objects." 3949 * </pre> 3950 * 3951 * The strings to format may contain replacement variables that will be formatted 3952 * using the format() method above and the params argument as a source of values 3953 * to use while formatting those variables.<p> 3954 * 3955 * If the criterion for a particular choice is empty, that choice will be used 3956 * as the default one for use when none of the other choice's criteria match.<p> 3957 * 3958 * Example string: 3959 * 3960 * <pre> 3961 * var num = 22; 3962 * var str = new IString("0#There are no objects.|1#There is one object.|#There are {number} objects."); 3963 * console.log(str.formatChoice(num, { 3964 * number: num 3965 * })); 3966 * </pre> 3967 * 3968 * Gives the output: 3969 * 3970 * <pre> 3971 * "There are 22 objects." 3972 * </pre> 3973 * 3974 * If multiple choice patterns can match a given argument index, the first one 3975 * encountered in the string will be used. If no choice patterns match the 3976 * argument index, then the default choice will be used. If there is no default 3977 * choice defined, then this method will return an empty string.<p> 3978 * 3979 * <b>Special Syntax</b><p> 3980 * 3981 * For any choice format string, all of the patterns in the string should be 3982 * of a single type: numeric, boolean, or string/regexp. The type of the 3983 * patterns is determined by the type of the argument index parameter.<p> 3984 * 3985 * If the argument index is numeric, then some special syntax can be used 3986 * in the patterns to match numeric ranges.<p> 3987 * 3988 * <ul> 3989 * <li><i>>x</i> - match any number that is greater than x 3990 * <li><i>>=x</i> - match any number that is greater than or equal to x 3991 * <li><i><x</i> - match any number that is less than x 3992 * <li><i><=x</i> - match any number that is less than or equal to x 3993 * <li><i>start-end</i> - match any number in the range [start,end) 3994 * <li><i>zero</i> - match any number in the class "zero". (See below for 3995 * a description of number classes.) 3996 * <li><i>one</i> - match any number in the class "one" 3997 * <li><i>two</i> - match any number in the class "two" 3998 * <li><i>few</i> - match any number in the class "few" 3999 * <li><i>many</i> - match any number in the class "many" 4000 * </ul> 4001 * 4002 * A number class defines a set of numbers that receive a particular syntax 4003 * in the strings. For example, in Slovenian, integers ending in the digit 4004 * "1" are in the "one" class, including 1, 21, 31, ... 101, 111, etc. 4005 * Similarly, integers ending in the digit "2" are in the "two" class. 4006 * Integers ending in the digits "3" or "4" are in the "few" class, and 4007 * every other integer is handled by the default string.<p> 4008 * 4009 * The definition of what numbers are included in a class is locale-dependent. 4010 * They are defined in the data file plurals.json. If your string is in a 4011 * different locale than the default for ilib, you should call the setLocale() 4012 * method of the string instance before calling this method.<p> 4013 * 4014 * <b>Other Pattern Types</b><p> 4015 * 4016 * If the argument index is a boolean, the string values "true" and "false" 4017 * may appear as the choice patterns.<p> 4018 * 4019 * If the argument index is of type string, then the choice patterns may contain 4020 * regular expressions, or static strings as degenerate regexps. 4021 * 4022 * @param {*} argIndex The index into the choice array of the current parameter 4023 * @param {Object} params The hash of parameter values that replace the replacement 4024 * variables in the string 4025 * @throws "syntax error in choice format pattern: " if there is a syntax error 4026 * @return {string} the formatted string 4027 */ 4028 formatChoice: function(argIndex, params) { 4029 var choices = this.str.split("|"); 4030 var type = typeof(argIndex); 4031 var limits = []; 4032 var strings = []; 4033 var i; 4034 var parts; 4035 var limit; 4036 var arg; 4037 var result = undefined; 4038 var defaultCase = ""; 4039 4040 if (this.str.length === 0) { 4041 // nothing to do 4042 return ""; 4043 } 4044 4045 // first parse all the choices 4046 for (i = 0; i < choices.length; i++) { 4047 parts = choices[i].split("#"); 4048 if (parts.length > 2) { 4049 limits[i] = parts[0]; 4050 parts = parts.shift(); 4051 strings[i] = parts.join("#"); 4052 } else if (parts.length === 2) { 4053 limits[i] = parts[0]; 4054 strings[i] = parts[1]; 4055 } else { 4056 // syntax error 4057 throw "syntax error in choice format pattern: " + choices[i]; 4058 } 4059 } 4060 4061 // then apply the argument index 4062 for (i = 0; i < limits.length; i++) { 4063 if (limits[i].length === 0) { 4064 // this is default case 4065 defaultCase = new IString(strings[i]); 4066 } else { 4067 switch (type) { 4068 case 'number': 4069 arg = parseInt(argIndex, 10); 4070 4071 if (limits[i].substring(0,2) === "<=") { 4072 limit = parseFloat(limits[i].substring(2)); 4073 if (arg <= limit) { 4074 result = new IString(strings[i]); 4075 i = limits.length; 4076 } 4077 } else if (limits[i].substring(0,2) === ">=") { 4078 limit = parseFloat(limits[i].substring(2)); 4079 if (arg >= limit) { 4080 result = new IString(strings[i]); 4081 i = limits.length; 4082 } 4083 } else if (limits[i].charAt(0) === "<") { 4084 limit = parseFloat(limits[i].substring(1)); 4085 if (arg < limit) { 4086 result = new IString(strings[i]); 4087 i = limits.length; 4088 } 4089 } else if (limits[i].charAt(0) === ">") { 4090 limit = parseFloat(limits[i].substring(1)); 4091 if (arg > limit) { 4092 result = new IString(strings[i]); 4093 i = limits.length; 4094 } 4095 } else { 4096 this.locale = this.locale || new Locale(this.localeSpec); 4097 switch (limits[i]) { 4098 case "zero": 4099 case "one": 4100 case "two": 4101 case "few": 4102 case "many": 4103 // CLDR locale-dependent number classes 4104 var ruleset = ilib.data["plurals_" + this.locale.getLanguage()]; 4105 if (ruleset) { 4106 var rule = ruleset[limits[i]]; 4107 if (IString._fncs.getValue(rule, arg)) { 4108 result = new IString(strings[i]); 4109 i = limits.length; 4110 } 4111 } 4112 break; 4113 default: 4114 var dash = limits[i].indexOf("-"); 4115 if (dash !== -1) { 4116 // range 4117 var start = limits[i].substring(0, dash); 4118 var end = limits[i].substring(dash+1); 4119 if (arg >= parseInt(start, 10) && arg <= parseInt(end, 10)) { 4120 result = new IString(strings[i]); 4121 i = limits.length; 4122 } 4123 } else if (arg === parseInt(limits[i], 10)) { 4124 // exact amount 4125 result = new IString(strings[i]); 4126 i = limits.length; 4127 } 4128 break; 4129 } 4130 } 4131 break; 4132 case 'boolean': 4133 if (limits[i] === "true" && argIndex === true) { 4134 result = new IString(strings[i]); 4135 i = limits.length; 4136 } else if (limits[i] === "false" && argIndex === false) { 4137 result = new IString(strings[i]); 4138 i = limits.length; 4139 } 4140 break; 4141 case 'string': 4142 var regexp = new RegExp(limits[i], "i"); 4143 if (regexp.test(argIndex)) { 4144 result = new IString(strings[i]); 4145 i = limits.length; 4146 } 4147 break; 4148 case 'object': 4149 throw "syntax error: fmtChoice parameter for the argument index cannot be an object"; 4150 } 4151 } 4152 } 4153 4154 if (!result) { 4155 result = defaultCase || new IString(""); 4156 } 4157 4158 result = result.format(params); 4159 4160 return result.toString(); 4161 }, 4162 4163 // delegates 4164 /** 4165 * Same as String.toString() 4166 * @return {string} this instance as regular Javascript string 4167 */ 4168 toString: function () { 4169 return this.str.toString(); 4170 }, 4171 4172 /** 4173 * Same as String.valueOf() 4174 * @return {string} this instance as a regular Javascript string 4175 */ 4176 valueOf: function () { 4177 return this.str.valueOf(); 4178 }, 4179 4180 /** 4181 * Same as String.charAt() 4182 * @param {number} index the index of the character being sought 4183 * @return {IString} the character at the given index 4184 */ 4185 charAt: function(index) { 4186 return new IString(this.str.charAt(index)); 4187 }, 4188 4189 /** 4190 * Same as String.charCodeAt(). This only reports on 4191 * 2-byte UCS-2 Unicode values, and does not take into 4192 * account supplementary characters encoded in UTF-16. 4193 * If you would like to take account of those characters, 4194 * use codePointAt() instead. 4195 * @param {number} index the index of the character being sought 4196 * @return {number} the character code of the character at the 4197 * given index in the string 4198 */ 4199 charCodeAt: function(index) { 4200 return this.str.charCodeAt(index); 4201 }, 4202 4203 /** 4204 * Same as String.concat() 4205 * @param {string} strings strings to concatenate to the current one 4206 * @return {IString} a concatenation of the given strings 4207 */ 4208 concat: function(strings) { 4209 return new IString(this.str.concat(strings)); 4210 }, 4211 4212 /** 4213 * Same as String.indexOf() 4214 * @param {string} searchValue string to search for 4215 * @param {number} start index into the string to start searching, or 4216 * undefined to search the entire string 4217 * @return {number} index into the string of the string being sought, 4218 * or -1 if the string is not found 4219 */ 4220 indexOf: function(searchValue, start) { 4221 return this.str.indexOf(searchValue, start); 4222 }, 4223 4224 /** 4225 * Same as String.lastIndexOf() 4226 * @param {string} searchValue string to search for 4227 * @param {number} start index into the string to start searching, or 4228 * undefined to search the entire string 4229 * @return {number} index into the string of the string being sought, 4230 * or -1 if the string is not found 4231 */ 4232 lastIndexOf: function(searchValue, start) { 4233 return this.str.lastIndexOf(searchValue, start); 4234 }, 4235 4236 /** 4237 * Same as String.match() 4238 * @param {string} regexp the regular expression to match 4239 * @return {Array.<string>} an array of matches 4240 */ 4241 match: function(regexp) { 4242 return this.str.match(regexp); 4243 }, 4244 4245 /** 4246 * Same as String.replace() 4247 * @param {string} searchValue a regular expression to search for 4248 * @param {string} newValue the string to replace the matches with 4249 * @return {IString} a new string with all the matches replaced 4250 * with the new value 4251 */ 4252 replace: function(searchValue, newValue) { 4253 return new IString(this.str.replace(searchValue, newValue)); 4254 }, 4255 4256 /** 4257 * Same as String.search() 4258 * @param {string} regexp the regular expression to search for 4259 * @return {number} position of the match, or -1 for no match 4260 */ 4261 search: function(regexp) { 4262 return this.str.search(regexp); 4263 }, 4264 4265 /** 4266 * Same as String.slice() 4267 * @param {number} start first character to include in the string 4268 * @param {number} end include all characters up to, but not including 4269 * the end character 4270 * @return {IString} a slice of the current string 4271 */ 4272 slice: function(start, end) { 4273 return new IString(this.str.slice(start, end)); 4274 }, 4275 4276 /** 4277 * Same as String.split() 4278 * @param {string} separator regular expression to match to find 4279 * separations between the parts of the text 4280 * @param {number} limit maximum number of items in the final 4281 * output array. Any items beyond that limit will be ignored. 4282 * @return {Array.<string>} the parts of the current string split 4283 * by the separator 4284 */ 4285 split: function(separator, limit) { 4286 return this.str.split(separator, limit); 4287 }, 4288 4289 /** 4290 * Same as String.substr() 4291 * @param {number} start the index of the character that should 4292 * begin the returned substring 4293 * @param {number} length the number of characters to return after 4294 * the start character. 4295 * @return {IString} the requested substring 4296 */ 4297 substr: function(start, length) { 4298 var plat = ilib._getPlatform(); 4299 if (plat === "qt" || plat === "rhino" || plat === "trireme") { 4300 // qt and rhino have a broken implementation of substr(), so 4301 // work around it 4302 if (typeof(length) === "undefined") { 4303 length = this.str.length - start; 4304 } 4305 } 4306 return new IString(this.str.substr(start, length)); 4307 }, 4308 4309 /** 4310 * Same as String.substring() 4311 * @param {number} from the index of the character that should 4312 * begin the returned substring 4313 * @param {number} to the index where to stop the extraction. If 4314 * omitted, extracts the rest of the string 4315 * @return {IString} the requested substring 4316 */ 4317 substring: function(from, to) { 4318 return this.str.substring(from, to); 4319 }, 4320 4321 /** 4322 * Same as String.toLowerCase(). Note that this method is 4323 * not locale-sensitive. 4324 * @return {IString} a string with the first character 4325 * lower-cased 4326 */ 4327 toLowerCase: function() { 4328 return this.str.toLowerCase(); 4329 }, 4330 4331 /** 4332 * Same as String.toUpperCase(). Note that this method is 4333 * not locale-sensitive. Use toLocaleUpperCase() instead 4334 * to get locale-sensitive behaviour. 4335 * @return {IString} a string with the first character 4336 * upper-cased 4337 */ 4338 toUpperCase: function() { 4339 return this.str.toUpperCase(); 4340 }, 4341 4342 /** 4343 * Convert the character or the surrogate pair at the given 4344 * index into the string to a Unicode UCS-4 code point. 4345 * @protected 4346 * @param {number} index index into the string 4347 * @return {number} code point of the character at the 4348 * given index into the string 4349 */ 4350 _toCodePoint: function (index) { 4351 return IString.toCodePoint(this.str, index); 4352 }, 4353 4354 /** 4355 * Call the callback with each character in the string one at 4356 * a time, taking care to step through the surrogate pairs in 4357 * the UTF-16 encoding properly.<p> 4358 * 4359 * The standard Javascript String's charAt() method only 4360 * returns a particular 16-bit character in the 4361 * UTF-16 encoding scheme. 4362 * If the index to charAt() is pointing to a low- or 4363 * high-surrogate character, 4364 * it will return the surrogate character rather 4365 * than the the character 4366 * in the supplementary planes that the two surrogates together 4367 * encode. This function will call the callback with the full 4368 * character, making sure to join two 4369 * surrogates into one character in the supplementary planes 4370 * where necessary.<p> 4371 * 4372 * @param {function(string)} callback a callback function to call with each 4373 * full character in the current string 4374 */ 4375 forEach: function(callback) { 4376 if (typeof(callback) === 'function') { 4377 var it = this.charIterator(); 4378 while (it.hasNext()) { 4379 callback(it.next()); 4380 } 4381 } 4382 }, 4383 4384 /** 4385 * Call the callback with each numeric code point in the string one at 4386 * a time, taking care to step through the surrogate pairs in 4387 * the UTF-16 encoding properly.<p> 4388 * 4389 * The standard Javascript String's charCodeAt() method only 4390 * returns information about a particular 16-bit character in the 4391 * UTF-16 encoding scheme. 4392 * If the index to charCodeAt() is pointing to a low- or 4393 * high-surrogate character, 4394 * it will return the code point of the surrogate character rather 4395 * than the code point of the character 4396 * in the supplementary planes that the two surrogates together 4397 * encode. This function will call the callback with the full 4398 * code point of each character, making sure to join two 4399 * surrogates into one code point in the supplementary planes.<p> 4400 * 4401 * @param {function(string)} callback a callback function to call with each 4402 * code point in the current string 4403 */ 4404 forEachCodePoint: function(callback) { 4405 if (typeof(callback) === 'function') { 4406 var it = this.iterator(); 4407 while (it.hasNext()) { 4408 callback(it.next()); 4409 } 4410 } 4411 }, 4412 4413 /** 4414 * Return an iterator that will step through all of the characters 4415 * in the string one at a time and return their code points, taking 4416 * care to step through the surrogate pairs in UTF-16 encoding 4417 * properly.<p> 4418 * 4419 * The standard Javascript String's charCodeAt() method only 4420 * returns information about a particular 16-bit character in the 4421 * UTF-16 encoding scheme. 4422 * If the index is pointing to a low- or high-surrogate character, 4423 * it will return a code point of the surrogate character rather 4424 * than the code point of the character 4425 * in the supplementary planes that the two surrogates together 4426 * encode.<p> 4427 * 4428 * The iterator instance returned has two methods, hasNext() which 4429 * returns true if the iterator has more code points to iterate through, 4430 * and next() which returns the next code point as a number.<p> 4431 * 4432 * @return {Object} an iterator 4433 * that iterates through all the code points in the string 4434 */ 4435 iterator: function() { 4436 /** 4437 * @constructor 4438 */ 4439 function _iterator (istring) { 4440 this.index = 0; 4441 this.hasNext = function () { 4442 return (this.index < istring.str.length); 4443 }; 4444 this.next = function () { 4445 if (this.index < istring.str.length) { 4446 var num = istring._toCodePoint(this.index); 4447 this.index += ((num > 0xFFFF) ? 2 : 1); 4448 } else { 4449 num = -1; 4450 } 4451 return num; 4452 }; 4453 }; 4454 return new _iterator(this); 4455 }, 4456 4457 /** 4458 * Return an iterator that will step through all of the characters 4459 * in the string one at a time, taking 4460 * care to step through the surrogate pairs in UTF-16 encoding 4461 * properly.<p> 4462 * 4463 * The standard Javascript String's charAt() method only 4464 * returns information about a particular 16-bit character in the 4465 * UTF-16 encoding scheme. 4466 * If the index is pointing to a low- or high-surrogate character, 4467 * it will return that surrogate character rather 4468 * than the surrogate pair which represents a character 4469 * in the supplementary planes.<p> 4470 * 4471 * The iterator instance returned has two methods, hasNext() which 4472 * returns true if the iterator has more characters to iterate through, 4473 * and next() which returns the next character.<p> 4474 * 4475 * @return {Object} an iterator 4476 * that iterates through all the characters in the string 4477 */ 4478 charIterator: function() { 4479 /** 4480 * @constructor 4481 */ 4482 function _chiterator (istring) { 4483 this.index = 0; 4484 this.hasNext = function () { 4485 return (this.index < istring.str.length); 4486 }; 4487 this.next = function () { 4488 var ch; 4489 if (this.index < istring.str.length) { 4490 ch = istring.str.charAt(this.index); 4491 if (IString._isSurrogate(ch) && 4492 this.index+1 < istring.str.length && 4493 IString._isSurrogate(istring.str.charAt(this.index+1))) { 4494 this.index++; 4495 ch += istring.str.charAt(this.index); 4496 } 4497 this.index++; 4498 } 4499 return ch; 4500 }; 4501 }; 4502 return new _chiterator(this); 4503 }, 4504 4505 /** 4506 * Return the code point at the given index when the string is viewed 4507 * as an array of code points. If the index is beyond the end of the 4508 * array of code points or if the index is negative, -1 is returned. 4509 * @param {number} index index of the code point 4510 * @return {number} code point of the character at the given index into 4511 * the string 4512 */ 4513 codePointAt: function (index) { 4514 if (index < 0) { 4515 return -1; 4516 } 4517 var count, 4518 it = this.iterator(), 4519 ch; 4520 for (count = index; count >= 0 && it.hasNext(); count--) { 4521 ch = it.next(); 4522 } 4523 return (count < 0) ? ch : -1; 4524 }, 4525 4526 /** 4527 * Set the locale to use when processing choice formats. The locale 4528 * affects how number classes are interpretted. In some cultures, 4529 * the limit "few" maps to "any integer that ends in the digits 2 to 9" and 4530 * in yet others, "few" maps to "any integer that ends in the digits 4531 * 3 or 4". 4532 * @param {Locale|string} locale locale to use when processing choice 4533 * formats with this string 4534 * @param {boolean=} sync [optional] whether to load the locale data synchronously 4535 * or not 4536 * @param {Object=} loadParams [optional] parameters to pass to the loader function 4537 * @param {function(*)=} onLoad [optional] function to call when the loading is done 4538 */ 4539 setLocale: function (locale, sync, loadParams, onLoad) { 4540 if (typeof(locale) === 'object') { 4541 this.locale = locale; 4542 } else { 4543 this.localeSpec = locale; 4544 this.locale = new Locale(locale); 4545 } 4546 4547 IString.loadPlurals(typeof(sync) !== 'undefined' ? sync : true, this.locale, loadParams, onLoad); 4548 }, 4549 4550 /** 4551 * Return the locale to use when processing choice formats. The locale 4552 * affects how number classes are interpretted. In some cultures, 4553 * the limit "few" maps to "any integer that ends in the digits 2 to 9" and 4554 * in yet others, "few" maps to "any integer that ends in the digits 4555 * 3 or 4". 4556 * @return {string} localespec to use when processing choice 4557 * formats with this string 4558 */ 4559 getLocale: function () { 4560 return (this.locale ? this.locale.getSpec() : this.localeSpec) || ilib.getLocale(); 4561 }, 4562 4563 /** 4564 * Return the number of code points in this string. This may be different 4565 * than the number of characters, as the UTF-16 encoding that Javascript 4566 * uses for its basis returns surrogate pairs separately. Two 2-byte 4567 * surrogate characters together make up one character/code point in 4568 * the supplementary character planes. If your string contains no 4569 * characters in the supplementary planes, this method will return the 4570 * same thing as the length() method. 4571 * @return {number} the number of code points in this string 4572 */ 4573 codePointLength: function () { 4574 if (this.cpLength === -1) { 4575 var it = this.iterator(); 4576 this.cpLength = 0; 4577 while (it.hasNext()) { 4578 this.cpLength++; 4579 it.next(); 4580 }; 4581 } 4582 return this.cpLength; 4583 } 4584 }; 4585 4586 4587 /*< Calendar.js */ 4588 /* 4589 * Calendar.js - Represent a calendar object. 4590 * 4591 * Copyright © 2012-2015, JEDLSoft 4592 * 4593 * Licensed under the Apache License, Version 2.0 (the "License"); 4594 * you may not use this file except in compliance with the License. 4595 * You may obtain a copy of the License at 4596 * 4597 * http://www.apache.org/licenses/LICENSE-2.0 4598 * 4599 * Unless required by applicable law or agreed to in writing, software 4600 * distributed under the License is distributed on an "AS IS" BASIS, 4601 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4602 * 4603 * See the License for the specific language governing permissions and 4604 * limitations under the License. 4605 */ 4606 4607 /** 4608 * @class 4609 * Superclass for all calendar subclasses that contains shared 4610 * functionality. This class is never instantiated on its own. Instead, 4611 * you should use the {@link CalendarFactory} function to manufacture a new 4612 * instance of a subclass of Calendar. 4613 * 4614 * @private 4615 * @constructor 4616 */ 4617 var Calendar = function() { 4618 }; 4619 4620 /* place for the subclasses to put their constructors so that the factory method 4621 * can find them. Do this to add your calendar after it's defined: 4622 * Calendar._constructors["mytype"] = Calendar.MyTypeConstructor; 4623 */ 4624 Calendar._constructors = {}; 4625 4626 Calendar.prototype = { 4627 /** 4628 * Return the type of this calendar. 4629 * 4630 * @return {string} the name of the type of this calendar 4631 */ 4632 getType: function() { 4633 throw "Cannot call methods of abstract class Calendar"; 4634 }, 4635 4636 /** 4637 * Return the number of months in the given year. The number of months in a year varies 4638 * for some luni-solar calendars because in some years, an extra month is needed to extend the 4639 * days in a year to an entire solar year. The month is represented as a 1-based number 4640 * where 1=first month, 2=second month, etc. 4641 * 4642 * @param {number} year a year for which the number of months is sought 4643 * @return {number} The number of months in the given year 4644 */ 4645 getNumMonths: function(year) { 4646 throw "Cannot call methods of abstract class Calendar"; 4647 }, 4648 4649 /** 4650 * Return the number of days in a particular month in a particular year. This function 4651 * can return a different number for a month depending on the year because of things 4652 * like leap years. 4653 * 4654 * @param {number} month the month for which the length is sought 4655 * @param {number} year the year within which that month can be found 4656 * @return {number} the number of days within the given month in the given year 4657 */ 4658 getMonLength: function(month, year) { 4659 throw "Cannot call methods of abstract class Calendar"; 4660 }, 4661 4662 /** 4663 * Return true if the given year is a leap year in this calendar. 4664 * The year parameter may be given as a number. 4665 * 4666 * @param {number} year the year for which the leap year information is being sought 4667 * @return {boolean} true if the given year is a leap year 4668 */ 4669 isLeapYear: function(year) { 4670 throw "Cannot call methods of abstract class Calendar"; 4671 } 4672 }; 4673 4674 4675 /*< CalendarFactory.js */ 4676 /* 4677 * CalendarFactory.js - Constructs new instances of the right subclass of Calendar 4678 * 4679 * Copyright © 2015, JEDLSoft 4680 * 4681 * Licensed under the Apache License, Version 2.0 (the "License"); 4682 * you may not use this file except in compliance with the License. 4683 * You may obtain a copy of the License at 4684 * 4685 * http://www.apache.org/licenses/LICENSE-2.0 4686 * 4687 * Unless required by applicable law or agreed to in writing, software 4688 * distributed under the License is distributed on an "AS IS" BASIS, 4689 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4690 * 4691 * See the License for the specific language governing permissions and 4692 * limitations under the License. 4693 */ 4694 4695 /* !depends 4696 ilib.js 4697 Locale.js 4698 LocaleInfo.js 4699 Calendar.js 4700 */ 4701 4702 4703 /** 4704 * Factory method to create a new instance of a calendar subclass.<p> 4705 * 4706 * The options parameter can be an object that contains the following 4707 * properties: 4708 * 4709 * <ul> 4710 * <li><i>type</i> - specify the type of the calendar desired. The 4711 * list of valid values changes depending on which calendars are 4712 * defined. When assembling your iliball.js, include those calendars 4713 * you wish to use in your program or web page, and they will register 4714 * themselves with this factory method. The "official", "gregorian", 4715 * and "julian" calendars are all included by default, as they are the 4716 * standard calendars for much of the world. 4717 * <li><i>locale</i> - some calendars vary depending on the locale. 4718 * For example, the "official" calendar transitions from a Julian-style 4719 * calendar to a Gregorian-style calendar on a different date for 4720 * each country, as the governments of those countries decided to 4721 * adopt the Gregorian calendar at different times. 4722 * 4723 * <li><i>onLoad</i> - a callback function to call when the calendar object is fully 4724 * loaded. When the onLoad option is given, the calendar factory will attempt to 4725 * load any missing locale data using the ilib loader callback. 4726 * When the constructor is done (even if the data is already preassembled), the 4727 * onLoad function is called with the current instance as a parameter, so this 4728 * callback can be used with preassembled or dynamic loading or a mix of the two. 4729 * 4730 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 4731 * asynchronously. If this option is given as "false", then the "onLoad" 4732 * callback must be given, as the instance returned from this constructor will 4733 * not be usable for a while. 4734 * 4735 * <li><i>loadParams</i> - an object containing parameters to pass to the 4736 * loader callback function when locale data is missing. The parameters are not 4737 * interpretted or modified in any way. They are simply passed along. The object 4738 * may contain any property/value pairs as long as the calling code is in 4739 * agreement with the loader callback function as to what those parameters mean. 4740 * </ul> 4741 * 4742 * If a locale is specified, but no type, then the calendar that is default for 4743 * the locale will be instantiated and returned. If neither the type nor 4744 * the locale are specified, then the calendar for the default locale will 4745 * be used. 4746 * 4747 * @static 4748 * @param {Object=} options options controlling the construction of this instance, or 4749 * undefined to use the default options 4750 * @return {Calendar} an instance of a calendar object of the appropriate type 4751 */ 4752 var CalendarFactory = function (options) { 4753 var locale, 4754 type, 4755 sync = true, 4756 instance; 4757 4758 if (options) { 4759 if (options.locale) { 4760 locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 4761 } 4762 4763 type = options.type || options.calendar; 4764 4765 if (typeof(options.sync) === 'boolean') { 4766 sync = options.sync; 4767 } 4768 } 4769 4770 if (!locale) { 4771 locale = new Locale(); // default locale 4772 } 4773 4774 if (!type) { 4775 new LocaleInfo(locale, { 4776 sync: sync, 4777 loadParams: options && options.loadParams, 4778 onLoad: ilib.bind(this, function(info) { 4779 type = info.getCalendar(); 4780 4781 instance = CalendarFactory._init(type, options); 4782 4783 if (options && typeof(options.onLoad) === 'function') { 4784 options.onLoad(instance); 4785 } 4786 }) 4787 }); 4788 } else { 4789 instance = CalendarFactory._init(type, options); 4790 } 4791 4792 return instance; 4793 }; 4794 4795 /** 4796 * Map calendar names to classes to initialize in the dynamic code model. 4797 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 4798 * @private 4799 */ 4800 CalendarFactory._dynMap = { 4801 "coptic": "Coptic", 4802 "ethiopic": "Ethiopic", 4803 "gregorian": "Gregorian", 4804 "han": "Han", 4805 "hebrew": "Hebrew", 4806 "islamic": "Islamic", 4807 "julian": "Julian", 4808 "persian": "Persian", 4809 "persian-algo": "PersianAlgo", 4810 "thaisolar": "ThaiSolar" 4811 }; 4812 4813 /** 4814 * Dynamically load the code for a calendar and calendar class if necessary. 4815 * @protected 4816 */ 4817 CalendarFactory._dynLoadCalendar = function (name) { 4818 if (!Calendar._constructors[name]) { 4819 var entry = CalendarFactory._dynMap[name]; 4820 if (entry) { 4821 Calendar._constructors[name] = require("./" + entry + "Cal.js"); 4822 } 4823 } 4824 return Calendar._constructors[name]; 4825 }; 4826 4827 /** @private */ 4828 CalendarFactory._init = function(type, options) { 4829 var cons; 4830 4831 if (ilib.isDynCode()) { 4832 CalendarFactory._dynLoadCalendar(type); 4833 } 4834 4835 cons = Calendar._constructors[type]; 4836 4837 // pass the same options through to the constructor so the subclass 4838 // has the ability to do something with if it needs to 4839 return cons && new cons(options); 4840 }; 4841 4842 /** 4843 * Return an array of known calendar types that the factory method can instantiate. 4844 * 4845 * @return {Array.<string>} an array of calendar types 4846 */ 4847 CalendarFactory.getCalendars = function () { 4848 var arr = [], 4849 c; 4850 4851 if (ilib.isDynCode()) { 4852 for (c in CalendarFactory._dynMap) { 4853 CalendarFactory._dynLoadCalendar(c); 4854 } 4855 } 4856 4857 for (c in Calendar._constructors) { 4858 if (c && Calendar._constructors[c]) { 4859 arr.push(c); // code like a pirate 4860 } 4861 } 4862 4863 return arr; 4864 }; 4865 4866 4867 /*< GregorianCal.js */ 4868 /* 4869 * gregorian.js - Represent a Gregorian calendar object. 4870 * 4871 * Copyright © 2012-2015, JEDLSoft 4872 * 4873 * Licensed under the Apache License, Version 2.0 (the "License"); 4874 * you may not use this file except in compliance with the License. 4875 * You may obtain a copy of the License at 4876 * 4877 * http://www.apache.org/licenses/LICENSE-2.0 4878 * 4879 * Unless required by applicable law or agreed to in writing, software 4880 * distributed under the License is distributed on an "AS IS" BASIS, 4881 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4882 * 4883 * See the License for the specific language governing permissions and 4884 * limitations under the License. 4885 */ 4886 4887 4888 /* !depends ilib.js Calendar.js Utils.js MathUtils.js */ 4889 4890 4891 /** 4892 * @class 4893 * Construct a new Gregorian calendar object. This class encodes information about 4894 * a Gregorian calendar.<p> 4895 * 4896 * 4897 * @constructor 4898 * @param {{noinstance:boolean}=} options 4899 * @extends Calendar 4900 */ 4901 var GregorianCal = function(options) { 4902 if (!options || !options.noinstance) { 4903 this.type = "gregorian"; 4904 } 4905 }; 4906 4907 /** 4908 * the lengths of each month 4909 * @private 4910 * @const 4911 * @type Array.<number> 4912 */ 4913 GregorianCal.monthLengths = [ 4914 31, /* Jan */ 4915 28, /* Feb */ 4916 31, /* Mar */ 4917 30, /* Apr */ 4918 31, /* May */ 4919 30, /* Jun */ 4920 31, /* Jul */ 4921 31, /* Aug */ 4922 30, /* Sep */ 4923 31, /* Oct */ 4924 30, /* Nov */ 4925 31 /* Dec */ 4926 ]; 4927 4928 /** 4929 * Return the number of months in the given year. The number of months in a year varies 4930 * for some luni-solar calendars because in some years, an extra month is needed to extend the 4931 * days in a year to an entire solar year. The month is represented as a 1-based number 4932 * where 1=first month, 2=second month, etc. 4933 * 4934 * @param {number} year a year for which the number of months is sought 4935 * @return {number} The number of months in the given year 4936 */ 4937 GregorianCal.prototype.getNumMonths = function(year) { 4938 return 12; 4939 }; 4940 4941 /** 4942 * Return the number of days in a particular month in a particular year. This function 4943 * can return a different number for a month depending on the year because of things 4944 * like leap years. 4945 * 4946 * @param {number} month the month for which the length is sought 4947 * @param {number} year the year within which that month can be found 4948 * @return {number} the number of days within the given month in the given year 4949 */ 4950 GregorianCal.prototype.getMonLength = function(month, year) { 4951 if (month !== 2 || !this.isLeapYear(year)) { 4952 return GregorianCal.monthLengths[month-1]; 4953 } else { 4954 return 29; 4955 } 4956 }; 4957 4958 /** 4959 * Return true if the given year is a leap year in the Gregorian calendar. 4960 * The year parameter may be given as a number, or as a GregDate object. 4961 * @param {number|GregorianDate} year the year for which the leap year information is being sought 4962 * @return {boolean} true if the given year is a leap year 4963 */ 4964 GregorianCal.prototype.isLeapYear = function(year) { 4965 var y = (typeof(year) === 'number' ? year : year.getYears()); 4966 var centuries = MathUtils.mod(y, 400); 4967 return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); 4968 }; 4969 4970 /** 4971 * Return the type of this calendar. 4972 * 4973 * @return {string} the name of the type of this calendar 4974 */ 4975 GregorianCal.prototype.getType = function() { 4976 return this.type; 4977 }; 4978 4979 /** 4980 * Return a date instance for this calendar type using the given 4981 * options. 4982 * @param {Object} options options controlling the construction of 4983 * the date instance 4984 * @return {IDate} a date appropriate for this calendar type 4985 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 4986 */ 4987 GregorianCal.prototype.newDateInstance = function (options) { 4988 return new GregorianDate(options); 4989 }; 4990 4991 /* register this calendar for the factory method */ 4992 Calendar._constructors["gregorian"] = GregorianCal; 4993 4994 4995 /*< JulianDay.js */ 4996 /* 4997 * JulianDay.js - A Julian Day object. 4998 * 4999 * Copyright © 2012-2015, JEDLSoft 5000 * 5001 * Licensed under the Apache License, Version 2.0 (the "License"); 5002 * you may not use this file except in compliance with the License. 5003 * You may obtain a copy of the License at 5004 * 5005 * http://www.apache.org/licenses/LICENSE-2.0 5006 * 5007 * Unless required by applicable law or agreed to in writing, software 5008 * distributed under the License is distributed on an "AS IS" BASIS, 5009 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5010 * 5011 * See the License for the specific language governing permissions and 5012 * limitations under the License. 5013 */ 5014 5015 /** 5016 * @class 5017 * A Julian Day class. A Julian Day is a date based on the Julian Day count 5018 * of time invented by Joseph Scaliger in 1583 for use with astronomical calculations. 5019 * Do not confuse it with a date in the Julian calendar, which it has very 5020 * little in common with. The naming is unfortunately close, and comes from history.<p> 5021 * 5022 * 5023 * @constructor 5024 * @param {number} num the Julian Day expressed as a floating point number 5025 */ 5026 var JulianDay = function(num) { 5027 this.jd = num; 5028 this.days = Math.floor(this.jd); 5029 this.frac = num - this.days; 5030 }; 5031 5032 JulianDay.prototype = { 5033 /** 5034 * Return the integral portion of this Julian Day instance. This corresponds to 5035 * the number of days since the beginning of the epoch. 5036 * 5037 * @return {number} the integral portion of this Julian Day 5038 */ 5039 getDays: function() { 5040 return this.days; 5041 }, 5042 5043 /** 5044 * Set the date of this Julian Day instance. 5045 * 5046 * @param {number} days the julian date expressed as a floating point number 5047 */ 5048 setDays: function(days) { 5049 this.days = Math.floor(days); 5050 this.jd = this.days + this.frac; 5051 }, 5052 5053 /** 5054 * Return the fractional portion of this Julian Day instance. This portion 5055 * corresponds to the time of day for the instance. 5056 */ 5057 getDayFraction: function() { 5058 return this.frac; 5059 }, 5060 5061 /** 5062 * Set the fractional part of the Julian Day. The fractional part represents 5063 * the portion of a fully day. Julian dates start at noon, and proceed until 5064 * noon of the next day. That would mean midnight is represented as a fractional 5065 * part of 0.5. 5066 * 5067 * @param {number} fraction The fractional part of the Julian date 5068 */ 5069 setDayFraction: function(fraction) { 5070 var t = Math.floor(fraction); 5071 this.frac = fraction - t; 5072 this.jd = this.days + this.frac; 5073 }, 5074 5075 /** 5076 * Return the Julian Day expressed as a floating point number. 5077 * @return {number} the Julian Day as a number 5078 */ 5079 getDate: function () { 5080 return this.jd; 5081 }, 5082 5083 /** 5084 * Set the date of this Julian Day instance. 5085 * 5086 * @param {number} num the numeric Julian Day to set into this instance 5087 */ 5088 setDate: function (num) { 5089 this.jd = num; 5090 }, 5091 5092 /** 5093 * Add an offset to the current date instance. The offset should be expressed in 5094 * terms of Julian days. That is, each integral unit represents one day of time, and 5095 * fractional part represents a fraction of a regular 24-hour day. 5096 * 5097 * @param {number} offset an amount to add (or subtract) to the current result instance. 5098 */ 5099 addDate: function(offset) { 5100 if (typeof(offset) === 'number') { 5101 this.jd += offset; 5102 this.days = Math.floor(this.jd); 5103 this.frac = this.jd - this.days; 5104 } 5105 } 5106 }; 5107 5108 5109 5110 /*< RataDie.js */ 5111 /* 5112 * ratadie.js - Represent the RD date number in the calendar 5113 * 5114 * Copyright © 2014-2015, JEDLSoft 5115 * 5116 * Licensed under the Apache License, Version 2.0 (the "License"); 5117 * you may not use this file except in compliance with the License. 5118 * You may obtain a copy of the License at 5119 * 5120 * http://www.apache.org/licenses/LICENSE-2.0 5121 * 5122 * Unless required by applicable law or agreed to in writing, software 5123 * distributed under the License is distributed on an "AS IS" BASIS, 5124 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5125 * 5126 * See the License for the specific language governing permissions and 5127 * limitations under the License. 5128 */ 5129 5130 /* !depends 5131 ilib.js 5132 JulianDay.js 5133 MathUtils.js 5134 JSUtils.js 5135 */ 5136 5137 5138 /** 5139 * @class 5140 * Construct a new RD date number object. The constructor parameters can 5141 * contain any of the following properties: 5142 * 5143 * <ul> 5144 * <li><i>unixtime<i> - sets the time of this instance according to the given 5145 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 5146 * 5147 * <li><i>julianday</i> - sets the time of this instance according to the given 5148 * Julian Day instance or the Julian Day given as a float 5149 * 5150 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 5151 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 5152 * linear count of years since the beginning of the epoch, much like other calendars. This linear 5153 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 5154 * to 60 and treated as if it were a year in the regular 60-year cycle. 5155 * 5156 * <li><i>year</i> - any integer, including 0 5157 * 5158 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 5159 * 5160 * <li><i>day</i> - 1 to 31 5161 * 5162 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 5163 * is always done with an unambiguous 24 hour representation 5164 * 5165 * <li><i>minute</i> - 0 to 59 5166 * 5167 * <li><i>second</i> - 0 to 59 5168 * 5169 * <li><i>millisecond</i> - 0 to 999 5170 * 5171 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 5172 * the parts or specify the minutes, seconds, and milliseconds, but not both. This is only used 5173 * in the Hebrew calendar. 5174 * 5175 * <li><i>minute</i> - 0 to 59 5176 * 5177 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 5178 * </ul> 5179 * 5180 * If the constructor is called with another date instance instead of 5181 * a parameter block, the other instance acts as a parameter block and its 5182 * settings are copied into the current instance.<p> 5183 * 5184 * If the constructor is called with no arguments at all or if none of the 5185 * properties listed above are present, then the RD is calculate based on 5186 * the current date at the time of instantiation. <p> 5187 * 5188 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 5189 * specified in the params, it is assumed that they have the smallest possible 5190 * value in the range for the property (zero or one).<p> 5191 * 5192 * 5193 * @private 5194 * @constructor 5195 * @param {Object=} params parameters that govern the settings and behaviour of this RD date 5196 */ 5197 var RataDie = function(params) { 5198 if (params) { 5199 if (typeof(params.date) !== 'undefined') { 5200 // accept JS Date classes or strings 5201 var date = params.date; 5202 if (!(JSUtils.isDate(date))) { 5203 date = new Date(date); // maybe a string initializer? 5204 } 5205 this._setTime(date.getTime()); 5206 } else if (typeof(params.unixtime) !== 'undefined') { 5207 this._setTime(parseInt(params.unixtime, 10)); 5208 } else if (typeof(params.julianday) !== 'undefined') { 5209 // JD time is defined to be UTC 5210 this._setJulianDay(parseFloat(params.julianday)); 5211 } else if (params.year || params.month || params.day || params.hour || 5212 params.minute || params.second || params.millisecond || params.parts || params.cycle) { 5213 this._setDateComponents(params); 5214 } else if (typeof(params.rd) !== 'undefined') { 5215 this.rd = (typeof(params.rd) === 'object' && params.rd instanceof RataDie) ? params.rd.rd : params.rd; 5216 } 5217 } 5218 5219 /** 5220 * @type {number} the Rata Die number of this date for this calendar type 5221 */ 5222 if (typeof(this.rd) === 'undefined') { 5223 var now = new Date(); 5224 this._setTime(now.getTime()); 5225 } 5226 }; 5227 5228 /** 5229 * @private 5230 * @const 5231 * @type {number} 5232 */ 5233 RataDie.gregorianEpoch = 1721424.5; 5234 5235 RataDie.prototype = { 5236 /** 5237 * @protected 5238 * @const 5239 * @type {number} 5240 * the difference between a zero Julian day and the zero Gregorian date. 5241 */ 5242 epoch: RataDie.gregorianEpoch, 5243 5244 /** 5245 * Set the RD of this instance according to the given unix time. Unix time is 5246 * the number of milliseconds since midnight on Jan 1, 1970. 5247 * 5248 * @protected 5249 * @param {number} millis the unix time to set this date to in milliseconds 5250 */ 5251 _setTime: function(millis) { 5252 // 2440587.5 is the julian day of midnight Jan 1, 1970, UTC (Gregorian) 5253 this._setJulianDay(2440587.5 + millis / 86400000); 5254 }, 5255 5256 /** 5257 * Set the date of this instance using a Julian Day. 5258 * @protected 5259 * @param {number} date the Julian Day to use to set this date 5260 */ 5261 _setJulianDay: function (date) { 5262 var jd = (typeof(date) === 'number') ? new JulianDay(date) : date; 5263 // round to the nearest millisecond 5264 this.rd = MathUtils.halfup((jd.getDate() - this.epoch) * 86400000) / 86400000; 5265 }, 5266 5267 /** 5268 * Return the rd number of the particular day of the week on or before the 5269 * given rd. eg. The Sunday on or before the given rd. 5270 * @protected 5271 * @param {number} rd the rata die date of the reference date 5272 * @param {number} dayOfWeek the day of the week that is being sought relative 5273 * to the current date 5274 * @return {number} the rd of the day of the week 5275 */ 5276 _onOrBefore: function(rd, dayOfWeek) { 5277 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 2, 7); 5278 }, 5279 5280 /** 5281 * Return the rd number of the particular day of the week on or before the current rd. 5282 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 5283 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5284 * wall time, so it it would give the wrong day of the week if this calculation was 5285 * done in UTC time when the caller really wanted wall time. Even though the calculation 5286 * may be done in wall time, the return value is nonetheless always given in UTC. 5287 * @param {number} dayOfWeek the day of the week that is being sought relative 5288 * to the current date 5289 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5290 * not given 5291 * @return {number} the rd of the day of the week 5292 */ 5293 onOrBefore: function(dayOfWeek, offset) { 5294 offset = offset || 0; 5295 return this._onOrBefore(this.rd + offset, dayOfWeek) - offset; 5296 }, 5297 5298 /** 5299 * Return the rd number of the particular day of the week on or before the current rd. 5300 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 5301 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5302 * wall time, so it it would give the wrong day of the week if this calculation was 5303 * done in UTC time when the caller really wanted wall time. Even though the calculation 5304 * may be done in wall time, the return value is nonetheless always given in UTC. 5305 * @param {number} dayOfWeek the day of the week that is being sought relative 5306 * to the reference date 5307 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5308 * not given 5309 * @return {number} the day of the week 5310 */ 5311 onOrAfter: function(dayOfWeek, offset) { 5312 offset = offset || 0; 5313 return this._onOrBefore(this.rd+6+offset, dayOfWeek) - offset; 5314 }, 5315 5316 /** 5317 * Return the rd number of the particular day of the week before the current rd. 5318 * eg. The Sunday before the current rd. If the offset is given, the calculation 5319 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5320 * wall time, so it it would give the wrong day of the week if this calculation was 5321 * done in UTC time when the caller really wanted wall time. Even though the calculation 5322 * may be done in wall time, the return value is nonetheless always given in UTC. 5323 * @param {number} dayOfWeek the day of the week that is being sought relative 5324 * to the reference date 5325 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5326 * not given 5327 * @return {number} the day of the week 5328 */ 5329 before: function(dayOfWeek, offset) { 5330 offset = offset || 0; 5331 return this._onOrBefore(this.rd-1+offset, dayOfWeek) - offset; 5332 }, 5333 5334 /** 5335 * Return the rd number of the particular day of the week after the current rd. 5336 * eg. The Sunday after the current rd. If the offset is given, the calculation 5337 * happens in wall time instead of UTC. UTC time may be a day before or day behind 5338 * wall time, so it it would give the wrong day of the week if this calculation was 5339 * done in UTC time when the caller really wanted wall time. Even though the calculation 5340 * may be done in wall time, the return value is nonetheless always given in UTC. 5341 * @param {number} dayOfWeek the day of the week that is being sought relative 5342 * to the reference date 5343 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 5344 * not given 5345 * @return {number} the day of the week 5346 */ 5347 after: function(dayOfWeek, offset) { 5348 offset = offset || 0; 5349 return this._onOrBefore(this.rd+7+offset, dayOfWeek) - offset; 5350 }, 5351 5352 /** 5353 * Return the unix time equivalent to this Gregorian date instance. Unix time is 5354 * the number of milliseconds since midnight on Jan 1, 1970 UTC. This method only 5355 * returns a valid number for dates between midnight, Jan 1, 1970 and 5356 * Jan 19, 2038 at 3:14:07am when the unix time runs out. If this instance 5357 * encodes a date outside of that range, this method will return -1. 5358 * 5359 * @return {number} a number giving the unix time, or -1 if the date is outside the 5360 * valid unix time range 5361 */ 5362 getTime: function() { 5363 // earlier than Jan 1, 1970 5364 // or later than Jan 19, 2038 at 3:14:07am 5365 var jd = this.getJulianDay(); 5366 if (jd < 2440587.5 || jd > 2465442.634803241) { 5367 return -1; 5368 } 5369 5370 // avoid the rounding errors in the floating point math by only using 5371 // the whole days from the rd, and then calculating the milliseconds directly 5372 return Math.round((jd - 2440587.5) * 86400000); 5373 }, 5374 5375 /** 5376 * Return the extended unix time equivalent to this Gregorian date instance. Unix time is 5377 * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time 5378 * (or the type "time_t" in C/C++) is only encoded with a unsigned 32 bit integer, and thus 5379 * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above 5380 * 32 bits and the Date object allows you to encode up to 100 million days worth of time 5381 * after Jan 1, 1970, and even more interestingly 100 million days worth of time before 5382 * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended 5383 * range. If this instance encodes a date outside of that range, this method will return 5384 * NaN. 5385 * 5386 * @return {number} a number giving the extended unix time, or NaN if the date is outside 5387 * the valid extended unix time range 5388 */ 5389 getTimeExtended: function() { 5390 var jd = this.getJulianDay(); 5391 5392 // test if earlier than Jan 1, 1970 - 100 million days 5393 // or later than Jan 1, 1970 + 100 million days 5394 if (jd < -97559412.5 || jd > 102440587.5) { 5395 return NaN; 5396 } 5397 5398 // avoid the rounding errors in the floating point math by only using 5399 // the whole days from the rd, and then calculating the milliseconds directly 5400 return Math.round((jd - 2440587.5) * 86400000); 5401 }, 5402 5403 /** 5404 * Return the Julian Day equivalent to this calendar date as a number. 5405 * This returns the julian day in UTC. 5406 * 5407 * @return {number} the julian date equivalent of this date 5408 */ 5409 getJulianDay: function() { 5410 return this.rd + this.epoch; 5411 }, 5412 5413 /** 5414 * Return the Rata Die (fixed day) number of this RD date. 5415 * 5416 * @return {number} the rd date as a number 5417 */ 5418 getRataDie: function() { 5419 return this.rd; 5420 } 5421 }; 5422 5423 5424 /*< GregRataDie.js */ 5425 /* 5426 * gregratadie.js - Represent the RD date number in the Gregorian calendar 5427 * 5428 * Copyright © 2014-2015, JEDLSoft 5429 * 5430 * Licensed under the Apache License, Version 2.0 (the "License"); 5431 * you may not use this file except in compliance with the License. 5432 * You may obtain a copy of the License at 5433 * 5434 * http://www.apache.org/licenses/LICENSE-2.0 5435 * 5436 * Unless required by applicable law or agreed to in writing, software 5437 * distributed under the License is distributed on an "AS IS" BASIS, 5438 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5439 * 5440 * See the License for the specific language governing permissions and 5441 * limitations under the License. 5442 */ 5443 5444 /* !depends 5445 ilib.js 5446 GregorianCal.js 5447 RataDie.js 5448 MathUtils.js 5449 */ 5450 5451 5452 /** 5453 * @class 5454 * Construct a new Gregorian RD date number object. The constructor parameters can 5455 * contain any of the following properties: 5456 * 5457 * <ul> 5458 * <li><i>unixtime<i> - sets the time of this instance according to the given 5459 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 5460 * 5461 * <li><i>julianday</i> - sets the time of this instance according to the given 5462 * Julian Day instance or the Julian Day given as a float 5463 * 5464 * <li><i>year</i> - any integer, including 0 5465 * 5466 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 5467 * 5468 * <li><i>day</i> - 1 to 31 5469 * 5470 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 5471 * is always done with an unambiguous 24 hour representation 5472 * 5473 * <li><i>minute</i> - 0 to 59 5474 * 5475 * <li><i>second</i> - 0 to 59 5476 * 5477 * <li><i>millisecond</i> - 0 to 999 5478 * 5479 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 5480 * </ul> 5481 * 5482 * If the constructor is called with another Gregorian date instance instead of 5483 * a parameter block, the other instance acts as a parameter block and its 5484 * settings are copied into the current instance.<p> 5485 * 5486 * If the constructor is called with no arguments at all or if none of the 5487 * properties listed above are present, then the RD is calculate based on 5488 * the current date at the time of instantiation. <p> 5489 * 5490 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 5491 * specified in the params, it is assumed that they have the smallest possible 5492 * value in the range for the property (zero or one).<p> 5493 * 5494 * 5495 * @private 5496 * @constructor 5497 * @extends RataDie 5498 * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian RD date 5499 */ 5500 var GregRataDie = function(params) { 5501 this.cal = params && params.cal || new GregorianCal(); 5502 /** @type {number|undefined} */ 5503 this.rd = undefined; 5504 RataDie.call(this, params); 5505 }; 5506 5507 GregRataDie.prototype = new RataDie(); 5508 GregRataDie.prototype.parent = RataDie; 5509 GregRataDie.prototype.constructor = GregRataDie; 5510 5511 /** 5512 * the cumulative lengths of each month, for a non-leap year 5513 * @private 5514 * @const 5515 * @type Array.<number> 5516 */ 5517 GregRataDie.cumMonthLengths = [ 5518 0, /* Jan */ 5519 31, /* Feb */ 5520 59, /* Mar */ 5521 90, /* Apr */ 5522 120, /* May */ 5523 151, /* Jun */ 5524 181, /* Jul */ 5525 212, /* Aug */ 5526 243, /* Sep */ 5527 273, /* Oct */ 5528 304, /* Nov */ 5529 334, /* Dec */ 5530 365 5531 ]; 5532 5533 /** 5534 * the cumulative lengths of each month, for a leap year 5535 * @private 5536 * @const 5537 * @type Array.<number> 5538 */ 5539 GregRataDie.cumMonthLengthsLeap = [ 5540 0, /* Jan */ 5541 31, /* Feb */ 5542 60, /* Mar */ 5543 91, /* Apr */ 5544 121, /* May */ 5545 152, /* Jun */ 5546 182, /* Jul */ 5547 213, /* Aug */ 5548 244, /* Sep */ 5549 274, /* Oct */ 5550 305, /* Nov */ 5551 335, /* Dec */ 5552 366 5553 ]; 5554 5555 /** 5556 * Calculate the Rata Die (fixed day) number of the given date. 5557 * 5558 * @private 5559 * @param {Object} date the date components to calculate the RD from 5560 */ 5561 GregRataDie.prototype._setDateComponents = function(date) { 5562 var year = parseInt(date.year, 10) || 0; 5563 var month = parseInt(date.month, 10) || 1; 5564 var day = parseInt(date.day, 10) || 1; 5565 var hour = parseInt(date.hour, 10) || 0; 5566 var minute = parseInt(date.minute, 10) || 0; 5567 var second = parseInt(date.second, 10) || 0; 5568 var millisecond = parseInt(date.millisecond, 10) || 0; 5569 5570 var years = 365 * (year - 1) + 5571 Math.floor((year-1)/4) - 5572 Math.floor((year-1)/100) + 5573 Math.floor((year-1)/400); 5574 5575 var dayInYear = (month > 1 ? GregRataDie.cumMonthLengths[month-1] : 0) + 5576 day + 5577 (GregorianCal.prototype.isLeapYear.call(this.cal, year) && month > 2 ? 1 : 0); 5578 var rdtime = (hour * 3600000 + 5579 minute * 60000 + 5580 second * 1000 + 5581 millisecond) / 5582 86400000; 5583 /* 5584 debug("getRataDie: converting " + JSON.stringify(this)); 5585 debug("getRataDie: year is " + years); 5586 debug("getRataDie: day in year is " + dayInYear); 5587 debug("getRataDie: rdtime is " + rdtime); 5588 debug("getRataDie: rd is " + (years + dayInYear + rdtime)); 5589 */ 5590 5591 /** 5592 * @type {number|undefined} the RD number of this Gregorian date 5593 */ 5594 this.rd = years + dayInYear + rdtime; 5595 }; 5596 5597 /** 5598 * Return the rd number of the particular day of the week on or before the 5599 * given rd. eg. The Sunday on or before the given rd. 5600 * @private 5601 * @param {number} rd the rata die date of the reference date 5602 * @param {number} dayOfWeek the day of the week that is being sought relative 5603 * to the current date 5604 * @return {number} the rd of the day of the week 5605 */ 5606 GregRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 5607 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); 5608 }; 5609 5610 5611 /*< TimeZone.js */ 5612 /* 5613 * TimeZone.js - Definition of a time zone class 5614 * 5615 * Copyright © 2012-2015, JEDLSoft 5616 * 5617 * Licensed under the Apache License, Version 2.0 (the "License"); 5618 * you may not use this file except in compliance with the License. 5619 * You may obtain a copy of the License at 5620 * 5621 * http://www.apache.org/licenses/LICENSE-2.0 5622 * 5623 * Unless required by applicable law or agreed to in writing, software 5624 * distributed under the License is distributed on an "AS IS" BASIS, 5625 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5626 * 5627 * See the License for the specific language governing permissions and 5628 * limitations under the License. 5629 */ 5630 5631 /* 5632 !depends 5633 ilib.js 5634 Locale.js 5635 LocaleInfo.js 5636 Utils.js 5637 MathUtils.js 5638 JSUtils.js 5639 GregRataDie.js 5640 IString.js 5641 CalendarFactory.js 5642 */ 5643 5644 // !data localeinfo zoneinfo 5645 5646 5647 5648 5649 /** 5650 * @class 5651 * Create a time zone instance. 5652 * 5653 * This class reports and transforms 5654 * information about particular time zones.<p> 5655 * 5656 * The options parameter may contain any of the following properties: 5657 * 5658 * <ul> 5659 * <li><i>id</i> - The id of the requested time zone such as "Europe/London" or 5660 * "America/Los_Angeles". These are taken from the IANA time zone database. (See 5661 * http://www.iana.org/time-zones for more information.) <p> 5662 * 5663 * There is one special 5664 * time zone that is not taken from the IANA database called simply "local". In 5665 * this case, this class will attempt to discover the current time zone and 5666 * daylight savings time settings by calling standard Javascript classes to 5667 * determine the offsets from UTC. 5668 * 5669 * <li><i>locale</i> - The locale for this time zone. 5670 * 5671 * <li><i>offset</i> - Choose the time zone based on the offset from UTC given in 5672 * number of minutes (negative is west, positive is east). 5673 * 5674 * <li><i>onLoad</i> - a callback function to call when the data is fully 5675 * loaded. When the onLoad option is given, this class will attempt to 5676 * load any missing locale data using the ilib loader callback. 5677 * When the data is loaded, the onLoad function is called with the current 5678 * instance as a parameter. 5679 * 5680 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 5681 * asynchronously. If this option is given as "false", then the "onLoad" 5682 * callback must be given, as the instance returned from this constructor will 5683 * not be usable for a while. 5684 * 5685 * <li><i>loadParams</i> - an object containing parameters to pass to the 5686 * loader callback function when locale data is missing. The parameters are not 5687 * interpretted or modified in any way. They are simply passed along. The object 5688 * may contain any property/value pairs as long as the calling code is in 5689 * agreement with the loader callback function as to what those parameters mean. 5690 * </ul> 5691 * 5692 * There is currently no way in the ECMAscript 5693 * standard to tell which exact time zone is currently in use. Choosing the 5694 * id "locale" or specifying an explicit offset will not give a specific time zone, 5695 * as it is impossible to tell with certainty which zone the offsets 5696 * match.<p> 5697 * 5698 * When the id "local" is given or the offset option is specified, this class will 5699 * have the following behaviours: 5700 * <ul> 5701 * <li>The display name will always be given as the RFC822 style, no matter what 5702 * style is requested 5703 * <li>The id will also be returned as the RFC822 style display name 5704 * <li>When the offset is explicitly given, this class will assume the time zone 5705 * does not support daylight savings time, and the offsets will be calculated 5706 * the same way year round. 5707 * <li>When the offset is explicitly given, the inDaylightSavings() method will 5708 * always return false. 5709 * <li>When the id "local" is given, this class will attempt to determine the 5710 * daylight savings time settings by examining the offset from UTC on Jan 1 5711 * and June 1 of the current year. If they are different, this class assumes 5712 * that the local time zone uses DST. When the offset for a particular date is 5713 * requested, it will use the built-in Javascript support to determine the 5714 * offset for that date. 5715 * </ul> 5716 * 5717 * If a more specific time zone is 5718 * needed with display names and known start/stop times for DST, use the "id" 5719 * property instead to specify the time zone exactly. You can perhaps ask the 5720 * user which time zone they prefer so that your app does not need to guess.<p> 5721 * 5722 * If the id and the offset are both not given, the default time zone for the 5723 * locale is retrieved from 5724 * the locale info. If the locale is not specified, the default locale for the 5725 * library is used.<p> 5726 * 5727 * Because this class was designed for use in web sites, and the vast majority 5728 * of dates and times being formatted are recent date/times, this class is simplified 5729 * by not implementing historical time zones. That is, when governments change the 5730 * time zone rules for a particular zone, only the latest such rule is implemented 5731 * in this class. That means that determining the offset for a date that is prior 5732 * to the last change may give the wrong result. Historical time zone calculations 5733 * may be implemented in a later version of iLib if there is enough demand for it, 5734 * but it would entail a much larger set of time zone data that would have to be 5735 * loaded. 5736 * 5737 * 5738 * @constructor 5739 * @param {Object} options Options guiding the construction of this time zone instance 5740 */ 5741 var TimeZone = function(options) { 5742 this.sync = true; 5743 this.locale = new Locale(); 5744 this.isLocal = false; 5745 5746 if (options) { 5747 if (options.locale) { 5748 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 5749 } 5750 5751 if (options.id) { 5752 var id = options.id.toString(); 5753 if (id === 'local') { 5754 this.isLocal = true; 5755 5756 // use standard Javascript Date to figure out the time zone offsets 5757 var now = new Date(), 5758 jan1 = new Date(now.getFullYear(), 0, 1), // months in std JS Date object are 0-based 5759 jun1 = new Date(now.getFullYear(), 5, 1); 5760 5761 // Javascript's method returns the offset backwards, so we have to 5762 // take the negative to get the correct offset 5763 this.offsetJan1 = -jan1.getTimezoneOffset(); 5764 this.offsetJun1 = -jun1.getTimezoneOffset(); 5765 // the offset of the standard time for the time zone is always the one that is closest 5766 // to negative infinity of the two, no matter whether you are in the northern or southern 5767 // hemisphere, east or west 5768 this.offset = Math.min(this.offsetJan1, this.offsetJun1); 5769 } 5770 this.id = id; 5771 } else if (options.offset) { 5772 this.offset = (typeof(options.offset) === 'string') ? parseInt(options.offset, 10) : options.offset; 5773 this.id = this.getDisplayName(undefined, undefined); 5774 } 5775 5776 if (typeof(options.sync) !== 'undefined') { 5777 this.sync = !!options.sync; 5778 } 5779 5780 this.loadParams = options.loadParams; 5781 this.onLoad = options.onLoad; 5782 } 5783 5784 //console.log("timezone: locale is " + this.locale); 5785 5786 if (!this.id) { 5787 new LocaleInfo(this.locale, { 5788 sync: this.sync, 5789 onLoad: ilib.bind(this, function (li) { 5790 this.id = li.getTimeZone() || "Etc/UTC"; 5791 this._loadtzdata(); 5792 }) 5793 }); 5794 } else { 5795 this._loadtzdata(); 5796 } 5797 5798 //console.log("localeinfo is: " + JSON.stringify(this.locinfo)); 5799 //console.log("id is: " + JSON.stringify(this.id)); 5800 }; 5801 5802 /* 5803 * Explanation of the compressed time zone info properties. 5804 * { 5805 * "o": "8:0", // offset from UTC 5806 * "f": "W{c}T", // standard abbreviation. For time zones that observe DST, the {c} replacement is replaced with the 5807 * // letter in the e.c or s.c properties below 5808 * "e": { // info about the end of DST 5809 * "j": 78322.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and 5810 * // "t" properties, but not both sets. 5811 * "m": 3, // month that it ends 5812 * "r": "l0", // rule for the day it ends "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". 5813 * // This means the 0-day (Sun) after the 7th of the month. Other possible operators are <, >, <=, >= 5814 * "t": "2:0", // time of day that the DST turns off, hours:minutes 5815 * "c": "S" // character to replace into the abbreviation for standard time 5816 * }, 5817 * "s": { // info about the start of DST 5818 * "j": 78189.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and 5819 * // "t" properties, but not both sets. 5820 * "m": 10, // month that it starts 5821 * "r": "l0", // rule for the day it starts "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". 5822 * // This means the 0-day (Sun) after the 7th of the month. Other possible operators are <, >, <=, >= 5823 * "t": "2:0", // time of day that the DST turns on, hours:minutes 5824 * "v": "1:0", // amount of time saved in hours:minutes 5825 * "c": "D" // character to replace into the abbreviation for daylight time 5826 * }, 5827 * "c": "AU", // ISO code for the country that contains this time zone 5828 * "n": "W. Australia {c} Time" 5829 * // long English name of the zone. The {c} replacement is for the word "Standard" or "Daylight" as appropriate 5830 * } 5831 */ 5832 TimeZone.prototype._loadtzdata = function () { 5833 // console.log("id is: " + JSON.stringify(this.id)); 5834 // console.log("zoneinfo is: " + JSON.stringify(ilib.data.zoneinfo[this.id])); 5835 if (!ilib.data.zoneinfo[this.id] && typeof(this.offset) === 'undefined') { 5836 Utils.loadData({ 5837 object: TimeZone, 5838 nonlocale: true, // locale independent 5839 name: "zoneinfo/" + this.id + ".json", 5840 sync: this.sync, 5841 loadParams: this.loadParams, 5842 callback: ilib.bind(this, function (tzdata) { 5843 if (tzdata && !JSUtils.isEmpty(tzdata)) { 5844 ilib.data.zoneinfo[this.id] = tzdata; 5845 } 5846 this._initZone(); 5847 }) 5848 }); 5849 } else { 5850 this._initZone(); 5851 } 5852 }; 5853 5854 TimeZone.prototype._initZone = function() { 5855 /** 5856 * @private 5857 * @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}} 5858 */ 5859 this.zone = ilib.data.zoneinfo[this.id]; 5860 if (!this.zone && typeof(this.offset) === 'undefined') { 5861 this.id = "Etc/UTC"; 5862 this.zone = ilib.data.zoneinfo[this.id]; 5863 } 5864 5865 this._calcDSTSavings(); 5866 5867 if (typeof(this.offset) === 'undefined' && this.zone.o) { 5868 var offsetParts = this._offsetStringToObj(this.zone.o); 5869 /** 5870 * @private 5871 * @type {number} raw offset from UTC without DST, in minutes 5872 */ 5873 this.offset = (Math.abs(offsetParts.h || 0) * 60 + (offsetParts.m || 0)) * MathUtils.signum(offsetParts.h || 0); 5874 } 5875 5876 if (this.onLoad && typeof(this.onLoad) === 'function') { 5877 this.onLoad(this); 5878 } 5879 }; 5880 5881 /** @private */ 5882 TimeZone._marshallIds = function (country, sync, callback) { 5883 var tz, ids = []; 5884 5885 if (!country) { 5886 // local is a special zone meaning "the local time zone according to the JS engine we are running upon" 5887 ids.push("local"); 5888 for (tz in ilib.data.timezones) { 5889 if (ilib.data.timezones[tz]) { 5890 ids.push(ilib.data.timezones[tz]); 5891 } 5892 } 5893 if (typeof(callback) === 'function') { 5894 callback(ids); 5895 } 5896 } else { 5897 if (!ilib.data.zoneinfo.zonetab) { 5898 Utils.loadData({ 5899 object: TimeZone, 5900 nonlocale: true, // locale independent 5901 name: "zoneinfo/zonetab.json", 5902 sync: sync, 5903 callback: ilib.bind(this, function (tzdata) { 5904 if (tzdata) { 5905 ilib.data.zoneinfo.zonetab = tzdata; 5906 } 5907 5908 ids = ilib.data.zoneinfo.zonetab[country]; 5909 5910 if (typeof(callback) === 'function') { 5911 callback(ids); 5912 } 5913 }) 5914 }); 5915 } else { 5916 ids = ilib.data.zoneinfo.zonetab[country]; 5917 if (typeof(callback) === 'function') { 5918 callback(ids); 5919 } 5920 } 5921 } 5922 5923 return ids; 5924 }; 5925 5926 /** 5927 * Return an array of available zone ids that the constructor knows about. 5928 * The country parameter is optional. If it is not given, all time zones will 5929 * be returned. If it specifies a country code, then only time zones for that 5930 * country will be returned. 5931 * 5932 * @param {string|undefined} country country code for which time zones are being sought 5933 * @param {boolean} sync whether to find the available ids synchronously (true) or asynchronously (false) 5934 * @param {function(Array.<string>)} onLoad callback function to call when the data is finished loading 5935 * @return {Array.<string>} an array of zone id strings 5936 */ 5937 TimeZone.getAvailableIds = function (country, sync, onLoad) { 5938 var tz, ids = []; 5939 5940 if (typeof(sync) !== 'boolean') { 5941 sync = true; 5942 } 5943 5944 if (ilib.data.timezones.length === 0) { 5945 if (typeof(ilib._load) !== 'undefined' && typeof(ilib._load.listAvailableFiles) === 'function') { 5946 ilib._load.listAvailableFiles(sync, function(hash) { 5947 for (var dir in hash) { 5948 var files = hash[dir]; 5949 if (ilib.isArray(files)) { 5950 files.forEach(function (filename) { 5951 if (filename && filename.match(/^zoneinfo/)) { 5952 ilib.data.timezones.push(filename.replace(/^zoneinfo\//, "").replace(/\.json$/, "")); 5953 } 5954 }); 5955 } 5956 } 5957 ids = TimeZone._marshallIds(country, sync, onLoad); 5958 }); 5959 } else { 5960 for (tz in ilib.data.zoneinfo) { 5961 if (ilib.data.zoneinfo[tz]) { 5962 ilib.data.timezones.push(tz); 5963 } 5964 } 5965 ids = TimeZone._marshallIds(country, sync, onLoad); 5966 } 5967 } else { 5968 ids = TimeZone._marshallIds(country, sync, onLoad); 5969 } 5970 5971 return ids; 5972 }; 5973 5974 /** 5975 * Return the id used to uniquely identify this time zone. 5976 * @return {string} a unique id for this time zone 5977 */ 5978 TimeZone.prototype.getId = function () { 5979 return this.id.toString(); 5980 }; 5981 5982 /** 5983 * Return the abbreviation that is used for the current time zone on the given date. 5984 * The date may be in DST or during standard time, and many zone names have different 5985 * abbreviations depending on whether or not the date is falls within DST.<p> 5986 * 5987 * There are two styles that are supported: 5988 * 5989 * <ol> 5990 * <li>standard - returns the 3 to 5 letter abbreviation of the time zone name such 5991 * as "CET" for "Central European Time" or "PDT" for "Pacific Daylight Time" 5992 * <li>rfc822 - returns an RFC 822 style time zone specifier, which specifies more 5993 * explicitly what the offset is from UTC 5994 * <li>long - returns the long name of the zone in English 5995 * </ol> 5996 * 5997 * @param {IDate=} date a date to determine if it is in daylight time or standard time 5998 * @param {string=} style one of "standard" or "rfc822". Default if not specified is "standard" 5999 * @return {string} the name of the time zone, abbreviated according to the style 6000 */ 6001 TimeZone.prototype.getDisplayName = function (date, style) { 6002 style = (this.isLocal || typeof(this.zone) === 'undefined') ? "rfc822" : (style || "standard"); 6003 switch (style) { 6004 default: 6005 case 'standard': 6006 if (this.zone.f && this.zone.f !== "zzz") { 6007 if (this.zone.f.indexOf("{c}") !== -1) { 6008 var letter = ""; 6009 letter = this.inDaylightTime(date) ? this.zone.s && this.zone.s.c : this.zone.e && this.zone.e.c; 6010 var temp = new IString(this.zone.f); 6011 return temp.format({c: letter || ""}); 6012 } 6013 return this.zone.f; 6014 } 6015 var temp = "GMT" + this.zone.o; 6016 if (this.inDaylightTime(date)) { 6017 temp += "+" + this.zone.s.v; 6018 } 6019 return temp; 6020 break; 6021 case 'rfc822': 6022 var offset = this.getOffset(date), // includes the DST if applicable 6023 ret = "UTC", 6024 hour = offset.h || 0, 6025 minute = offset.m || 0; 6026 6027 if (hour !== 0) { 6028 ret += (hour > 0) ? "+" : "-"; 6029 if (Math.abs(hour) < 10) { 6030 ret += "0"; 6031 } 6032 ret += (hour < 0) ? -hour : hour; 6033 if (minute < 10) { 6034 ret += "0"; 6035 } 6036 ret += minute; 6037 } 6038 return ret; 6039 case 'long': 6040 if (this.zone.n) { 6041 if (this.zone.n.indexOf("{c}") !== -1) { 6042 var str = this.inDaylightTime(date) ? "Daylight" : "Standard"; 6043 var temp = new IString(this.zone.n); 6044 return temp.format({c: str || ""}); 6045 } 6046 return this.zone.n; 6047 } 6048 var temp = "GMT" + this.zone.o; 6049 if (this.inDaylightTime(date)) { 6050 temp += "+" + this.zone.s.v; 6051 } 6052 return temp; 6053 break; 6054 } 6055 }; 6056 6057 /** 6058 * Convert the offset string to an object with an h, m, and possibly s property 6059 * to indicate the hours, minutes, and seconds. 6060 * 6061 * @private 6062 * @param {string} str the offset string to convert to an object 6063 * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset for the zone at 6064 * the given date/time, in hours, minutes, and seconds 6065 */ 6066 TimeZone.prototype._offsetStringToObj = function (str) { 6067 var offsetParts = (typeof(str) === 'string') ? str.split(":") : [], 6068 ret = {h:0}, 6069 temp; 6070 6071 if (offsetParts.length > 0) { 6072 ret.h = parseInt(offsetParts[0], 10); 6073 if (offsetParts.length > 1) { 6074 temp = parseInt(offsetParts[1], 10); 6075 if (temp) { 6076 ret.m = temp; 6077 } 6078 if (offsetParts.length > 2) { 6079 temp = parseInt(offsetParts[2], 10); 6080 if (temp) { 6081 ret.s = temp; 6082 } 6083 } 6084 } 6085 } 6086 6087 return ret; 6088 }; 6089 6090 /** 6091 * Returns the offset of this time zone from UTC at the given date/time. If daylight saving 6092 * time is in effect at the given date/time, this method will return the offset value 6093 * adjusted by the amount of daylight saving. 6094 * @param {IDate=} date the date for which the offset is needed 6095 * @return {Object.<{h:number,m:number}>} an object giving the offset for the zone at 6096 * the given date/time, in hours, minutes, and seconds 6097 */ 6098 TimeZone.prototype.getOffset = function (date) { 6099 if (!date) { 6100 return this.getRawOffset(); 6101 } 6102 var offset = this.getOffsetMillis(date)/60000; 6103 6104 var hours = MathUtils.down(offset/60), 6105 minutes = Math.abs(offset) - Math.abs(hours)*60; 6106 6107 var ret = { 6108 h: hours 6109 }; 6110 if (minutes != 0) { 6111 ret.m = minutes; 6112 } 6113 return ret; 6114 }; 6115 6116 /** 6117 * Returns the offset of this time zone from UTC at the given date/time expressed in 6118 * milliseconds. If daylight saving 6119 * time is in effect at the given date/time, this method will return the offset value 6120 * adjusted by the amount of daylight saving. Negative numbers indicate offsets west 6121 * of UTC and conversely, positive numbers indicate offset east of UTC. 6122 * 6123 * @param {IDate=} date the date for which the offset is needed, or null for the 6124 * present date 6125 * @return {number} the number of milliseconds of offset from UTC that the given date is 6126 */ 6127 TimeZone.prototype.getOffsetMillis = function (date) { 6128 var ret; 6129 6130 // check if the dst property is defined -- the intrinsic JS Date object doesn't work so 6131 // well if we are in the overlap time at the end of DST 6132 if (this.isLocal && typeof(date.dst) === 'undefined') { 6133 var d = (!date) ? new Date() : new Date(date.getTimeExtended()); 6134 return -d.getTimezoneOffset() * 60000; 6135 } 6136 6137 ret = this.offset; 6138 6139 if (date && this.inDaylightTime(date)) { 6140 ret += this.dstSavings; 6141 } 6142 6143 return ret * 60000; 6144 }; 6145 6146 /** 6147 * Return the offset in milliseconds when the date has an RD number in wall 6148 * time rather than in UTC time. 6149 * @protected 6150 * @param date the date to check in wall time 6151 * @returns {number} the number of milliseconds of offset from UTC that the given date is 6152 */ 6153 TimeZone.prototype._getOffsetMillisWallTime = function (date) { 6154 var ret; 6155 6156 ret = this.offset; 6157 6158 if (date && this.inDaylightTime(date, true)) { 6159 ret += this.dstSavings; 6160 } 6161 6162 return ret * 60000; 6163 }; 6164 6165 /** 6166 * Returns the offset of this time zone from UTC at the given date/time. If daylight saving 6167 * time is in effect at the given date/time, this method will return the offset value 6168 * adjusted by the amount of daylight saving. 6169 * @param {IDate=} date the date for which the offset is needed 6170 * @return {string} the offset for the zone at the given date/time as a string in the 6171 * format "h:m:s" 6172 */ 6173 TimeZone.prototype.getOffsetStr = function (date) { 6174 var offset = this.getOffset(date), 6175 ret; 6176 6177 ret = offset.h; 6178 if (typeof(offset.m) !== 'undefined') { 6179 ret += ":" + offset.m; 6180 if (typeof(offset.s) !== 'undefined') { 6181 ret += ":" + offset.s; 6182 } 6183 } else { 6184 ret += ":0"; 6185 } 6186 6187 return ret; 6188 }; 6189 6190 /** 6191 * Gets the offset from UTC for this time zone. 6192 * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset from 6193 * UTC for this time zone, in hours, minutes, and seconds 6194 */ 6195 TimeZone.prototype.getRawOffset = function () { 6196 var hours = MathUtils.down(this.offset/60), 6197 minutes = Math.abs(this.offset) - Math.abs(hours)*60; 6198 6199 var ret = { 6200 h: hours 6201 }; 6202 if (minutes != 0) { 6203 ret.m = minutes; 6204 } 6205 return ret; 6206 }; 6207 6208 /** 6209 * Gets the offset from UTC for this time zone expressed in milliseconds. Negative numbers 6210 * indicate zones west of UTC, and positive numbers indicate zones east of UTC. 6211 * 6212 * @return {number} an number giving the offset from 6213 * UTC for this time zone in milliseconds 6214 */ 6215 TimeZone.prototype.getRawOffsetMillis = function () { 6216 return this.offset * 60000; 6217 }; 6218 6219 /** 6220 * Gets the offset from UTC for this time zone without DST savings. 6221 * @return {string} the offset from UTC for this time zone, in the format "h:m:s" 6222 */ 6223 TimeZone.prototype.getRawOffsetStr = function () { 6224 var off = this.getRawOffset(); 6225 return off.h + ":" + (off.m || "0"); 6226 }; 6227 6228 /** 6229 * Return the amount of time in hours:minutes that the clock is advanced during 6230 * daylight savings time. 6231 * @return {Object.<{h:number,m:number,s:number}>} the amount of time that the 6232 * clock advances for DST in hours, minutes, and seconds 6233 */ 6234 TimeZone.prototype.getDSTSavings = function () { 6235 if (this.isLocal) { 6236 // take the absolute because the difference in the offsets may be positive or 6237 // negative, depending on the hemisphere 6238 var savings = Math.abs(this.offsetJan1 - this.offsetJun1); 6239 var hours = MathUtils.down(savings/60), 6240 minutes = savings - hours*60; 6241 return { 6242 h: hours, 6243 m: minutes 6244 }; 6245 } else if (this.zone && this.zone.s) { 6246 return this._offsetStringToObj(this.zone.s.v); // this.zone.start.savings 6247 } 6248 return {h:0}; 6249 }; 6250 6251 /** 6252 * Return the amount of time in hours:minutes that the clock is advanced during 6253 * daylight savings time. 6254 * @return {string} the amount of time that the clock advances for DST in the 6255 * format "h:m:s" 6256 */ 6257 TimeZone.prototype.getDSTSavingsStr = function () { 6258 if (this.isLocal) { 6259 var savings = this.getDSTSavings(); 6260 return savings.h + ":" + savings.m; 6261 } else if (typeof(this.offset) !== 'undefined' && this.zone && this.zone.s) { 6262 return this.zone.s.v; // this.zone.start.savings 6263 } 6264 return "0:0"; 6265 }; 6266 6267 /** 6268 * return the rd of the start of DST transition for the given year 6269 * @protected 6270 * @param {Object} rule set of rules 6271 * @param {number} year year to check 6272 * @return {number} the rd of the start of DST for the year 6273 */ 6274 TimeZone.prototype._calcRuleStart = function (rule, year) { 6275 var type = "=", 6276 weekday = 0, 6277 day, 6278 refDay, 6279 cal, 6280 hour = 0, 6281 minute = 0, 6282 second = 0, 6283 time, 6284 i; 6285 6286 if (typeof(rule.j) !== 'undefined') { 6287 refDay = new GregRataDie({ 6288 julianday: rule.j 6289 }); 6290 } else { 6291 if (rule.r.charAt(0) == 'l' || rule.r.charAt(0) == 'f') { 6292 cal = CalendarFactory({type: "gregorian"}); 6293 type = rule.r.charAt(0); 6294 weekday = parseInt(rule.r.substring(1), 10); 6295 day = (type === 'l') ? cal.getMonLength(rule.m, year) : 1; 6296 //console.log("_calcRuleStart: Calculating the " + 6297 // (rule.r.charAt(0) == 'f' ? "first " : "last ") + weekday + 6298 // " of month " + rule.m); 6299 } else { 6300 i = rule.r.indexOf('<'); 6301 if (i == -1) { 6302 i = rule.r.indexOf('>'); 6303 } 6304 6305 if (i != -1) { 6306 type = rule.r.charAt(i); 6307 weekday = parseInt(rule.r.substring(0, i), 10); 6308 day = parseInt(rule.r.substring(i+1), 10); 6309 //console.log("_calcRuleStart: Calculating the " + weekday + 6310 // type + day + " of month " + rule.m); 6311 } else { 6312 day = parseInt(rule.r, 10); 6313 //console.log("_calcRuleStart: Calculating the " + day + " of month " + rule.m); 6314 } 6315 } 6316 6317 if (rule.t) { 6318 time = rule.t.split(":"); 6319 hour = parseInt(time[0], 10); 6320 if (time.length > 1) { 6321 minute = parseInt(time[1], 10); 6322 if (time.length > 2) { 6323 second = parseInt(time[2], 10); 6324 } 6325 } 6326 } 6327 //console.log("calculating rd of " + year + "/" + rule.m + "/" + day); 6328 refDay = new GregRataDie({ 6329 year: year, 6330 month: rule.m, 6331 day: day, 6332 hour: hour, 6333 minute: minute, 6334 second: second 6335 }); 6336 } 6337 //console.log("refDay is " + JSON.stringify(refDay)); 6338 var d = refDay.getRataDie(); 6339 6340 switch (type) { 6341 case 'l': 6342 case '<': 6343 //console.log("returning " + refDay.onOrBefore(rd, weekday)); 6344 d = refDay.onOrBefore(weekday); 6345 break; 6346 case 'f': 6347 case '>': 6348 //console.log("returning " + refDay.onOrAfterRd(rd, weekday)); 6349 d = refDay.onOrAfter(weekday); 6350 break; 6351 } 6352 return d; 6353 }; 6354 6355 /** 6356 * @private 6357 */ 6358 TimeZone.prototype._calcDSTSavings = function () { 6359 var saveParts = this.getDSTSavings(); 6360 6361 /** 6362 * @private 6363 * @type {number} savings in minutes when DST is in effect 6364 */ 6365 this.dstSavings = (Math.abs(saveParts.h || 0) * 60 + (saveParts.m || 0)) * MathUtils.signum(saveParts.h || 0); 6366 }; 6367 6368 /** 6369 * @private 6370 */ 6371 TimeZone.prototype._getDSTStartRule = function (year) { 6372 // TODO: update this when historic/future zones are supported 6373 return this.zone.s; 6374 }; 6375 6376 /** 6377 * @private 6378 */ 6379 TimeZone.prototype._getDSTEndRule = function (year) { 6380 // TODO: update this when historic/future zones are supported 6381 return this.zone.e; 6382 }; 6383 6384 /** 6385 * Returns whether or not the given date is in daylight saving time for the current 6386 * zone. Note that daylight savings time is observed for the summer. Because 6387 * the seasons are reversed, daylight savings time in the southern hemisphere usually 6388 * runs from the end of the year through New Years into the first few months of the 6389 * next year. This method will correctly calculate the start and end of DST for any 6390 * location. 6391 * 6392 * @param {IDate=} date a date for which the info about daylight time is being sought, 6393 * or undefined to tell whether we are currently in daylight savings time 6394 * @param {boolean=} wallTime if true, then the given date is in wall time. If false or 6395 * undefined, it is in the usual UTC time. 6396 * @return {boolean} true if the given date is in DST for the current zone, and false 6397 * otherwise. 6398 */ 6399 TimeZone.prototype.inDaylightTime = function (date, wallTime) { 6400 var rd, startRd, endRd, year; 6401 6402 if (this.isLocal) { 6403 // check if the dst property is defined -- the intrinsic JS Date object doesn't work so 6404 // well if we are in the overlap time at the end of DST, so we have to work around that 6405 // problem by adding in the savings ourselves 6406 var offset = 0; 6407 if (typeof(date.dst) !== 'undefined' && !date.dst) { 6408 offset = this.dstSavings * 60000; 6409 } 6410 6411 var d = new Date(date ? date.getTimeExtended() + offset: undefined); 6412 // the DST offset is always the one that is closest to positive infinity, no matter 6413 // if you are in the northern or southern hemisphere, east or west 6414 var dst = Math.max(this.offsetJan1, this.offsetJun1); 6415 return (-d.getTimezoneOffset() === dst); 6416 } 6417 6418 if (!date || !date.cal || date.cal.type !== "gregorian") { 6419 // convert to Gregorian so that we can tell if it is in DST or not 6420 var time = date && typeof(date.getTimeExtended) === 'function' ? date.getTimeExtended() : undefined; 6421 rd = new GregRataDie({unixtime: time}).getRataDie(); 6422 year = new Date(time).getUTCFullYear(); 6423 } else { 6424 rd = date.rd.getRataDie(); 6425 year = date.year; 6426 } 6427 // rd should be a Gregorian RD number now, in UTC 6428 6429 // if we aren't using daylight time in this zone for the given year, then we are 6430 // not in daylight time 6431 if (!this.useDaylightTime(year)) { 6432 return false; 6433 } 6434 6435 // these calculate the start/end in local wall time 6436 var startrule = this._getDSTStartRule(year); 6437 var endrule = this._getDSTEndRule(year); 6438 startRd = this._calcRuleStart(startrule, year); 6439 endRd = this._calcRuleStart(endrule, year); 6440 6441 if (wallTime) { 6442 // rd is in wall time, so we have to make sure to skip the missing time 6443 // at the start of DST when standard time ends and daylight time begins 6444 startRd += this.dstSavings/1440; 6445 } else { 6446 // rd is in UTC, so we have to convert the start/end to UTC time so 6447 // that they can be compared directly to the UTC rd number of the date 6448 6449 // when DST starts, time is standard time already, so we only have 6450 // to subtract the offset to get to UTC and not worry about the DST savings 6451 startRd -= this.offset/1440; 6452 6453 // when DST ends, time is in daylight time already, so we have to 6454 // subtract the DST savings to get back to standard time, then the 6455 // offset to get to UTC 6456 endRd -= (this.offset + this.dstSavings)/1440; 6457 } 6458 6459 // In the northern hemisphere, the start comes first some time in spring (Feb-Apr), 6460 // then the end some time in the fall (Sept-Nov). In the southern 6461 // hemisphere, it is the other way around because the seasons are reversed. Standard 6462 // time is still in the winter, but the winter months are May-Aug, and daylight 6463 // savings time usually starts Aug-Oct of one year and runs through Mar-May of the 6464 // next year. 6465 if (rd < endRd && endRd - rd <= this.dstSavings/1440 && typeof(date.dst) === 'boolean') { 6466 // take care of the magic overlap time at the end of DST 6467 return date.dst; 6468 } 6469 if (startRd < endRd) { 6470 // northern hemisphere 6471 return (rd >= startRd && rd < endRd) ? true : false; 6472 } 6473 // southern hemisphere 6474 return (rd >= startRd || rd < endRd) ? true : false; 6475 }; 6476 6477 /** 6478 * Returns true if this time zone switches to daylight savings time at some point 6479 * in the year, and false otherwise. 6480 * @param {number} year Whether or not the time zone uses daylight time in the given year. If 6481 * this parameter is not given, the current year is assumed. 6482 * @return {boolean} true if the time zone uses daylight savings time 6483 */ 6484 TimeZone.prototype.useDaylightTime = function (year) { 6485 6486 // this zone uses daylight savings time iff there is a rule defining when to start 6487 // and when to stop the DST 6488 return (this.isLocal && this.offsetJan1 !== this.offsetJun1) || 6489 (typeof(this.zone) !== 'undefined' && 6490 typeof(this.zone.s) !== 'undefined' && 6491 typeof(this.zone.e) !== 'undefined'); 6492 }; 6493 6494 /** 6495 * Returns the ISO 3166 code of the country for which this time zone is defined. 6496 * @return {string} the ISO 3166 code of the country for this zone 6497 */ 6498 TimeZone.prototype.getCountry = function () { 6499 return this.zone.c; 6500 }; 6501 6502 6503 6504 /*< SearchUtils.js */ 6505 /* 6506 * SearchUtils.js - Misc search utility routines 6507 * 6508 * Copyright © 2013-2015, JEDLSoft 6509 * 6510 * Licensed under the Apache License, Version 2.0 (the "License"); 6511 * you may not use this file except in compliance with the License. 6512 * You may obtain a copy of the License at 6513 * 6514 * http://www.apache.org/licenses/LICENSE-2.0 6515 * 6516 * Unless required by applicable law or agreed to in writing, software 6517 * distributed under the License is distributed on an "AS IS" BASIS, 6518 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6519 * 6520 * See the License for the specific language governing permissions and 6521 * limitations under the License. 6522 */ 6523 6524 var SearchUtils = {}; 6525 6526 /** 6527 * Binary search a sorted array for a particular target value. 6528 * If the exact value is not found, it returns the index of the smallest 6529 * entry that is greater than the given target value.<p> 6530 * 6531 * The comparator 6532 * parameter is a function that knows how to compare elements of the 6533 * array and the target. The function should return a value greater than 0 6534 * if the array element is greater than the target, a value less than 0 if 6535 * the array element is less than the target, and 0 if the array element 6536 * and the target are equivalent.<p> 6537 * 6538 * If the comparator function is not specified, this function assumes 6539 * the array and the target are numeric values and should be compared 6540 * as such.<p> 6541 * 6542 * 6543 * @static 6544 * @param {*} target element being sought 6545 * @param {Array} arr the array being searched 6546 * @param {?function(*,*)=} comparator a comparator that is appropriate for comparing two entries 6547 * in the array 6548 * @return the index of the array into which the value would fit if 6549 * inserted, or -1 if given array is not an array or the target is not 6550 * a number 6551 */ 6552 SearchUtils.bsearch = function(target, arr, comparator) { 6553 if (typeof(arr) === 'undefined' || !arr || typeof(target) === 'undefined') { 6554 return -1; 6555 } 6556 6557 var high = arr.length - 1, 6558 low = 0, 6559 mid = 0, 6560 value, 6561 cmp = comparator || SearchUtils.bsearch.numbers; 6562 6563 while (low <= high) { 6564 mid = Math.floor((high+low)/2); 6565 value = cmp(arr[mid], target); 6566 if (value > 0) { 6567 high = mid - 1; 6568 } else if (value < 0) { 6569 low = mid + 1; 6570 } else { 6571 return mid; 6572 } 6573 } 6574 6575 return low; 6576 }; 6577 6578 /** 6579 * Returns whether or not the given element is greater than, less than, 6580 * or equal to the given target.<p> 6581 * 6582 * @private 6583 * @static 6584 * @param {number} element the element being tested 6585 * @param {number} target the target being sought 6586 */ 6587 SearchUtils.bsearch.numbers = function(element, target) { 6588 return element - target; 6589 }; 6590 6591 /** 6592 * Do a bisection search of a function for a particular target value.<p> 6593 * 6594 * The function to search is a function that takes a numeric parameter, 6595 * does calculations, and returns gives a numeric result. The 6596 * function should should be smooth and not have any discontinuities 6597 * between the low and high values of the parameter. 6598 * 6599 * 6600 * @static 6601 * @param {number} target value being sought 6602 * @param {number} low the lower bounds to start searching 6603 * @param {number} high the upper bounds to start searching 6604 * @param {number} precision minimum precision to support. Use 0 if you want to use the default. 6605 * @param {?function(number)=} func function to search 6606 * @return an approximation of the input value to the function that gives the desired 6607 * target output value, correct to within the error range of Javascript floating point 6608 * arithmetic, or NaN if there was some error 6609 */ 6610 SearchUtils.bisectionSearch = function(target, low, high, precision, func) { 6611 if (typeof(target) !== 'number' || 6612 typeof(low) !== 'number' || 6613 typeof(high) !== 'number' || 6614 typeof(func) !== 'function') { 6615 return NaN; 6616 } 6617 6618 var mid = 0, 6619 value, 6620 pre = precision > 0 ? precision : 1e-13; 6621 6622 do { 6623 mid = (high+low)/2; 6624 value = func(mid); 6625 if (value > target) { 6626 high = mid; 6627 } else if (value < target) { 6628 low = mid; 6629 } 6630 } while (high - low > pre); 6631 6632 return mid; 6633 }; 6634 6635 6636 6637 /*< GregorianDate.js */ 6638 /* 6639 * GregorianDate.js - Represent a date in the Gregorian calendar 6640 * 6641 * Copyright © 2012-2015, JEDLSoft 6642 * 6643 * Licensed under the Apache License, Version 2.0 (the "License"); 6644 * you may not use this file except in compliance with the License. 6645 * You may obtain a copy of the License at 6646 * 6647 * http://www.apache.org/licenses/LICENSE-2.0 6648 * 6649 * Unless required by applicable law or agreed to in writing, software 6650 * distributed under the License is distributed on an "AS IS" BASIS, 6651 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6652 * 6653 * See the License for the specific language governing permissions and 6654 * limitations under the License. 6655 */ 6656 6657 /* !depends 6658 ilib.js 6659 IDate.js 6660 GregorianCal.js 6661 SearchUtils.js 6662 MathUtils.js 6663 Locale.js 6664 LocaleInfo.js 6665 JulianDay.js 6666 GregRataDie.js 6667 TimeZone.js 6668 */ 6669 6670 6671 6672 6673 /** 6674 * @class 6675 * Construct a new Gregorian date object. The constructor parameters can 6676 * contain any of the following properties: 6677 * 6678 * <ul> 6679 * <li><i>unixtime<i> - sets the time of this instance according to the given 6680 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 6681 * 6682 * <li><i>julianday</i> - sets the time of this instance according to the given 6683 * Julian Day instance or the Julian Day given as a float 6684 * 6685 * <li><i>year</i> - any integer, including 0 6686 * 6687 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 6688 * 6689 * <li><i>day</i> - 1 to 31 6690 * 6691 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 6692 * is always done with an unambiguous 24 hour representation 6693 * 6694 * <li><i>minute</i> - 0 to 59 6695 * 6696 * <li><i>second</i> - 0 to 59 6697 * 6698 * <li><i>millisecond</i> - 0 to 999 6699 * 6700 * <li><i>dst</i> - boolean used to specify whether the given time components are 6701 * intended to be in daylight time or not. This is only used in the overlap 6702 * time when transitioning from DST to standard time, and the time components are 6703 * ambiguous. Otherwise at all other times of the year, this flag is ignored. 6704 * If you specify the date using unix time (UTC) or a julian day, then the time is 6705 * already unambiguous and this flag does not need to be specified. 6706 * <p> 6707 * For example, in the US, the transition out of daylight savings time 6708 * in 2014 happens at Nov 2, 2014 2:00am Daylight Time, when the time falls 6709 * back to Nov 2, 2014 1:00am Standard Time. If you give a date/time components as 6710 * "Nov 2, 2014 1:30am", then there are two 1:30am times in that day, and you would 6711 * have to give the standard flag to indicate which of those two you mean. 6712 * (dst=true means daylight time, dst=false means standard time). 6713 * 6714 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 6715 * of this gregorian date. The date/time is kept in the local time. The time zone 6716 * is used later if this date is formatted according to a different time zone and 6717 * the difference has to be calculated, or when the date format has a time zone 6718 * component in it. 6719 * 6720 * <li><i>locale</i> - locale for this gregorian date. If the time zone is not 6721 * given, it can be inferred from this locale. For locales that span multiple 6722 * time zones, the one with the largest population is chosen as the one that 6723 * represents the locale. 6724 * 6725 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 6726 * </ul> 6727 * 6728 * If the constructor is called with another Gregorian date instance instead of 6729 * a parameter block, the other instance acts as a parameter block and its 6730 * settings are copied into the current instance.<p> 6731 * 6732 * If the constructor is called with no arguments at all or if none of the 6733 * properties listed above 6734 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 6735 * components are 6736 * filled in with the current date at the time of instantiation. Note that if 6737 * you do not give the time zone when defaulting to the current time and the 6738 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 6739 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 6740 * Mean Time").<p> 6741 * 6742 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 6743 * specified in the params, it is assumed that they have the smallest possible 6744 * value in the range for the property (zero or one).<p> 6745 * 6746 * 6747 * @constructor 6748 * @extends IDate 6749 * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian date 6750 */ 6751 var GregorianDate = function(params) { 6752 this.cal = new GregorianCal(); 6753 this.timezone = "local"; 6754 6755 if (params) { 6756 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 6757 // for doing inheritance, so don't need to fill in the data. The inheriting class only wants the methods. 6758 return; 6759 } 6760 if (params.locale) { 6761 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 6762 var li = new LocaleInfo(this.locale); 6763 this.timezone = li.getTimeZone(); 6764 } 6765 if (params.timezone) { 6766 this.timezone = params.timezone.toString(); 6767 } 6768 6769 if (params.year || params.month || params.day || params.hour || 6770 params.minute || params.second || params.millisecond ) { 6771 this.year = parseInt(params.year, 10) || 0; 6772 this.month = parseInt(params.month, 10) || 1; 6773 this.day = parseInt(params.day, 10) || 1; 6774 this.hour = parseInt(params.hour, 10) || 0; 6775 this.minute = parseInt(params.minute, 10) || 0; 6776 this.second = parseInt(params.second, 10) || 0; 6777 this.millisecond = parseInt(params.millisecond, 10) || 0; 6778 if (typeof(params.dst) === 'boolean') { 6779 this.dst = params.dst; 6780 } 6781 this.rd = this.newRd(params); 6782 6783 // add the time zone offset to the rd to convert to UTC 6784 this.offset = 0; 6785 if (this.timezone === "local" && typeof(params.dst) === 'undefined') { 6786 // if dst is defined, the intrinsic Date object has no way of specifying which version of a time you mean 6787 // in the overlap time at the end of DST. Do you mean the daylight 1:30am or the standard 1:30am? In this 6788 // case, use the ilib calculations below, which can distinguish between the two properly 6789 var d = new Date(this.year, this.month-1, this.day, this.hour, this.minute, this.second, this.millisecond); 6790 this.offset = -d.getTimezoneOffset() / 1440; 6791 } else { 6792 if (!this.tz) { 6793 this.tz = new TimeZone({id: this.timezone}); 6794 } 6795 // getOffsetMillis requires that this.year, this.rd, and this.dst 6796 // are set in order to figure out which time zone rules apply and 6797 // what the offset is at that point in the year 6798 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 6799 } 6800 if (this.offset !== 0) { 6801 this.rd = this.newRd({ 6802 rd: this.rd.getRataDie() - this.offset 6803 }); 6804 } 6805 } 6806 } 6807 6808 if (!this.rd) { 6809 this.rd = this.newRd(params); 6810 this._calcDateComponents(); 6811 } 6812 }; 6813 6814 GregorianDate.prototype = new IDate({noinstance: true}); 6815 GregorianDate.prototype.parent = IDate; 6816 GregorianDate.prototype.constructor = GregorianDate; 6817 6818 /** 6819 * Return a new RD for this date type using the given params. 6820 * @private 6821 * @param {Object=} params the parameters used to create this rata die instance 6822 * @returns {RataDie} the new RD instance for the given params 6823 */ 6824 GregorianDate.prototype.newRd = function (params) { 6825 return new GregRataDie(params); 6826 }; 6827 6828 /** 6829 * Calculates the Gregorian year for a given rd number. 6830 * @private 6831 * @static 6832 */ 6833 GregorianDate._calcYear = function(rd) { 6834 var days400, 6835 days100, 6836 days4, 6837 years400, 6838 years100, 6839 years4, 6840 years1, 6841 year; 6842 6843 years400 = Math.floor((rd - 1) / 146097); 6844 days400 = MathUtils.mod((rd - 1), 146097); 6845 years100 = Math.floor(days400 / 36524); 6846 days100 = MathUtils.mod(days400, 36524); 6847 years4 = Math.floor(days100 / 1461); 6848 days4 = MathUtils.mod(days100, 1461); 6849 years1 = Math.floor(days4 / 365); 6850 6851 year = 400 * years400 + 100 * years100 + 4 * years4 + years1; 6852 if (years100 !== 4 && years1 !== 4) { 6853 year++; 6854 } 6855 return year; 6856 }; 6857 6858 /** 6859 * @private 6860 */ 6861 GregorianDate.prototype._calcYear = function(rd) { 6862 return GregorianDate._calcYear(rd); 6863 }; 6864 6865 /** 6866 * Calculate the date components for the current time zone 6867 * @private 6868 */ 6869 GregorianDate.prototype._calcDateComponents = function () { 6870 if (this.timezone === "local" && this.rd.getRataDie() >= -99280837 && this.rd.getRataDie() <= 100719163) { 6871 // console.log("using js Date to calculate offset"); 6872 // use the intrinsic JS Date object to do the tz conversion for us, which 6873 // guarantees that it follows the system tz database settings 6874 var d = new Date(this.rd.getTimeExtended()); 6875 6876 /** 6877 * Year in the Gregorian calendar. 6878 * @type number 6879 */ 6880 this.year = d.getFullYear(); 6881 6882 /** 6883 * The month number, ranging from 1 (January) to 12 (December). 6884 * @type number 6885 */ 6886 this.month = d.getMonth()+1; 6887 6888 /** 6889 * The day of the month. This ranges from 1 to 31. 6890 * @type number 6891 */ 6892 this.day = d.getDate(); 6893 6894 /** 6895 * The hour of the day. This can be a number from 0 to 23, as times are 6896 * stored unambiguously in the 24-hour clock. 6897 * @type number 6898 */ 6899 this.hour = d.getHours(); 6900 6901 /** 6902 * The minute of the hours. Ranges from 0 to 59. 6903 * @type number 6904 */ 6905 this.minute = d.getMinutes(); 6906 6907 /** 6908 * The second of the minute. Ranges from 0 to 59. 6909 * @type number 6910 */ 6911 this.second = d.getSeconds(); 6912 6913 /** 6914 * The millisecond of the second. Ranges from 0 to 999. 6915 * @type number 6916 */ 6917 this.millisecond = d.getMilliseconds(); 6918 6919 this.offset = -d.getTimezoneOffset() / 1440; 6920 } else { 6921 // console.log("using ilib to calculate offset. tz is " + this.timezone); 6922 // console.log("GregDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 6923 if (typeof(this.offset) === "undefined") { 6924 // console.log("calculating offset"); 6925 this.year = this._calcYear(this.rd.getRataDie()); 6926 6927 // now offset the RD by the time zone, then recalculate in case we were 6928 // near the year boundary 6929 if (!this.tz) { 6930 this.tz = new TimeZone({id: this.timezone}); 6931 } 6932 this.offset = this.tz.getOffsetMillis(this) / 86400000; 6933 // } else { 6934 // console.log("offset is already defined somehow. type is " + typeof(this.offset)); 6935 // console.trace("Stack is this one"); 6936 } 6937 // console.log("offset is " + this.offset); 6938 var rd = this.rd.getRataDie(); 6939 if (this.offset !== 0) { 6940 rd += this.offset; 6941 } 6942 this.year = this._calcYear(rd); 6943 6944 var yearStartRd = this.newRd({ 6945 year: this.year, 6946 month: 1, 6947 day: 1, 6948 cal: this.cal 6949 }); 6950 6951 // remainder is days into the year 6952 var remainder = rd - yearStartRd.getRataDie() + 1; 6953 6954 var cumulative = GregorianCal.prototype.isLeapYear.call(this.cal, this.year) ? 6955 GregRataDie.cumMonthLengthsLeap : 6956 GregRataDie.cumMonthLengths; 6957 6958 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 6959 remainder = remainder - cumulative[this.month-1]; 6960 6961 this.day = Math.floor(remainder); 6962 remainder -= this.day; 6963 // now convert to milliseconds for the rest of the calculation 6964 remainder = Math.round(remainder * 86400000); 6965 6966 this.hour = Math.floor(remainder/3600000); 6967 remainder -= this.hour * 3600000; 6968 6969 this.minute = Math.floor(remainder/60000); 6970 remainder -= this.minute * 60000; 6971 6972 this.second = Math.floor(remainder/1000); 6973 remainder -= this.second * 1000; 6974 6975 this.millisecond = Math.floor(remainder); 6976 } 6977 }; 6978 6979 /** 6980 * Return the day of the week of this date. The day of the week is encoded 6981 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 6982 * 6983 * @return {number} the day of the week 6984 */ 6985 GregorianDate.prototype.getDayOfWeek = function() { 6986 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 6987 return MathUtils.mod(rd, 7); 6988 }; 6989 6990 /** 6991 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 6992 * 365, regardless of months or weeks, etc. That is, January 1st is day 1, and 6993 * December 31st is 365 in regular years, or 366 in leap years. 6994 * @return {number} the ordinal day of the year 6995 */ 6996 GregorianDate.prototype.getDayOfYear = function() { 6997 var cumulativeMap = this.cal.isLeapYear(this.year) ? 6998 GregRataDie.cumMonthLengthsLeap : 6999 GregRataDie.cumMonthLengths; 7000 7001 return cumulativeMap[this.month-1] + this.day; 7002 }; 7003 7004 /** 7005 * Return the era for this date as a number. The value for the era for Gregorian 7006 * calendars is -1 for "before the common era" (BCE) and 1 for "the common era" (CE). 7007 * BCE dates are any date before Jan 1, 1 CE. In the proleptic Gregorian calendar, 7008 * there is a year 0, so any years that are negative or zero are BCE. In the Julian 7009 * calendar, there is no year 0. Instead, the calendar goes straight from year -1 to 7010 * 1. 7011 * @return {number} 1 if this date is in the common era, -1 if it is before the 7012 * common era 7013 */ 7014 GregorianDate.prototype.getEra = function() { 7015 return (this.year < 1) ? -1 : 1; 7016 }; 7017 7018 /** 7019 * Return the name of the calendar that governs this date. 7020 * 7021 * @return {string} a string giving the name of the calendar 7022 */ 7023 GregorianDate.prototype.getCalendar = function() { 7024 return "gregorian"; 7025 }; 7026 7027 // register with the factory method 7028 IDate._constructors["gregorian"] = GregorianDate; 7029 7030 7031 /*< DateFactory.js */ 7032 /* 7033 * DateFactory.js - Factory class to create the right subclasses of a date for any 7034 * calendar or locale. 7035 * 7036 * Copyright © 2012-2015, JEDLSoft 7037 * 7038 * Licensed under the Apache License, Version 2.0 (the "License"); 7039 * you may not use this file except in compliance with the License. 7040 * You may obtain a copy of the License at 7041 * 7042 * http://www.apache.org/licenses/LICENSE-2.0 7043 * 7044 * Unless required by applicable law or agreed to in writing, software 7045 * distributed under the License is distributed on an "AS IS" BASIS, 7046 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7047 * 7048 * See the License for the specific language governing permissions and 7049 * limitations under the License. 7050 */ 7051 7052 /* !depends ilib.js Locale.js LocaleInfo.js JulianDay.js JSUtils.js CalendarFactory.js IDate.js GregorianDate.js*/ 7053 7054 7055 7056 // Statically depend on these even though we don't use them 7057 // to guarantee they are loaded into the cache already. 7058 7059 /** 7060 * Factory method to create a new instance of a date subclass.<p> 7061 * 7062 * The options parameter can be an object that contains the following 7063 * properties: 7064 * 7065 * <ul> 7066 * <li><i>type</i> - specify the type/calendar of the date desired. The 7067 * list of valid values changes depending on which calendars are 7068 * defined. When assembling your iliball.js, include those date type 7069 * you wish to use in your program or web page, and they will register 7070 * themselves with this factory method. The "gregorian", 7071 * and "julian" calendars are all included by default, as they are the 7072 * standard calendars for much of the world. If not specified, the type 7073 * of the date returned is the one that is appropriate for the locale. 7074 * This property may also be given as "calendar" instead of "type". 7075 * 7076 * <li><i>onLoad</i> - a callback function to call when the date object is fully 7077 * loaded. When the onLoad option is given, the date factory will attempt to 7078 * load any missing locale data using the ilib loader callback. 7079 * When the constructor is done (even if the data is already preassembled), the 7080 * onLoad function is called with the current instance as a parameter, so this 7081 * callback can be used with preassembled or dynamic loading or a mix of the two. 7082 * 7083 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 7084 * asynchronously. If this option is given as "false", then the "onLoad" 7085 * callback must be given, as the instance returned from this constructor will 7086 * not be usable for a while. 7087 * 7088 * <li><i>loadParams</i> - an object containing parameters to pass to the 7089 * loader callback function when locale data is missing. The parameters are not 7090 * interpretted or modified in any way. They are simply passed along. The object 7091 * may contain any property/value pairs as long as the calling code is in 7092 * agreement with the loader callback function as to what those parameters mean. 7093 * </ul> 7094 * 7095 * The options object is also passed down to the date constructor, and 7096 * thus can contain the the properties as the date object being instantiated. 7097 * See the documentation for {@link GregorianDate}, and other 7098 * subclasses for more details on other parameter that may be passed in.<p> 7099 * 7100 * Please note that if you do not give the type parameter, this factory 7101 * method will create a date object that is appropriate for the calendar 7102 * that is most commonly used in the specified or current ilib locale. 7103 * For example, in Thailand, the most common calendar is the Thai solar 7104 * calendar. If the current locale is "th-TH" (Thai for Thailand) and you 7105 * use this factory method to construct a new date without specifying the 7106 * type, it will automatically give you back an instance of 7107 * {@link ThaiSolarDate}. This is convenient because you do not 7108 * need to know which locales use which types of dates. In fact, you 7109 * should always use this factory method to make new date instances unless 7110 * you know that you specifically need a date in a particular calendar.<p> 7111 * 7112 * Also note that when you pass in the date components such as year, month, 7113 * day, etc., these components should be appropriate for the given date 7114 * being instantiated. That is, in our Thai example in the previous 7115 * paragraph, the year and such should be given as a Thai solar year, not 7116 * the Gregorian year that you get from the Javascript Date class. In 7117 * order to initialize a date instance when you don't know what subclass 7118 * will be instantiated for the locale, use a parameter such as "unixtime" 7119 * or "julianday" which are unambiguous and based on UTC time, instead of 7120 * the year/month/date date components. The date components for that UTC 7121 * time will be calculated and the time zone offset will be automatically 7122 * factored in. 7123 * 7124 * @static 7125 * @param {Object=} options options controlling the construction of this instance, or 7126 * undefined to use the default options 7127 * @return {IDate} an instance of a calendar object of the appropriate type 7128 */ 7129 var DateFactory = function(options) { 7130 var locale, 7131 type, 7132 cons, 7133 sync = true, 7134 obj; 7135 7136 if (options) { 7137 if (options.locale) { 7138 locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 7139 } 7140 7141 type = options.type || options.calendar; 7142 7143 if (typeof(options.sync) === 'boolean') { 7144 sync = options.sync; 7145 } 7146 } 7147 7148 if (!locale) { 7149 locale = new Locale(); // default locale 7150 } 7151 7152 if (!type) { 7153 new LocaleInfo(locale, { 7154 sync: sync, 7155 loadParams: options && options.loadParams, 7156 onLoad: ilib.bind(this, function(info) { 7157 type = info.getCalendar(); 7158 7159 obj = DateFactory._init(type, options); 7160 7161 if (options && typeof(options.onLoad) === 'function') { 7162 options.onLoad(obj); 7163 } 7164 }) 7165 }); 7166 } else { 7167 obj = DateFactory._init(type, options); 7168 } 7169 7170 return obj 7171 }; 7172 7173 /** 7174 * Map calendar names to classes to initialize in the dynamic code model. 7175 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 7176 * @private 7177 */ 7178 DateFactory._dynMap = { 7179 "coptic": "Coptic", 7180 "ethiopic": "Ethiopic", 7181 "gregorian": "Gregorian", 7182 "han": "Han", 7183 "hebrew": "Hebrew", 7184 "islamic": "Islamic", 7185 "julian": "Julian", 7186 "persian": "Persian", 7187 "persian-algo": "PersianAlgo", 7188 "thaisolar": "ThaiSolar" 7189 }; 7190 7191 /** 7192 * Dynamically load the code for a calendar and calendar class if necessary. 7193 * @protected 7194 */ 7195 DateFactory._dynLoadDate = function (name) { 7196 if (!IDate._constructors[name]) { 7197 var entry = DateFactory._dynMap[name]; 7198 if (entry) { 7199 IDate._constructors[name] = require("./" + entry + "Date.js"); 7200 } 7201 } 7202 return IDate._constructors[name]; 7203 }; 7204 7205 /** 7206 * @protected 7207 * @static 7208 */ 7209 DateFactory._init = function(type, options) { 7210 var cons; 7211 7212 if (ilib.isDynCode()) { 7213 DateFactory._dynLoadDate(type); 7214 CalendarFactory._dynLoadCalendar(type); 7215 } 7216 7217 cons = IDate._constructors[type]; 7218 7219 // pass the same options through to the constructor so the subclass 7220 // has the ability to do something with if it needs to 7221 return cons && new cons(options); 7222 }; 7223 7224 /** 7225 * Convert JavaScript Date objects and other types into native Dates. This accepts any 7226 * string or number that can be translated by the JavaScript Date class, 7227 * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) 7228 * any JavaScript Date classed object, any IDate subclass, an JulianDay object, an object 7229 * containing the normal options to initialize an IDate instance, or null (will 7230 * return null or undefined if input is null or undefined). Normal output is 7231 * a standard native subclass of the IDate object as appropriate for the locale. 7232 * 7233 * @static 7234 * @protected 7235 * @param {IDate|Object|JulianDay|Date|string|number=} inDate The input date object, string or Number. 7236 * @param {IString|string=} timezone timezone to use if a new date object is created 7237 * @param {Locale|string=} locale locale to use when constructing an IDate 7238 * @return {IDate|null|undefined} an IDate subclass equivalent to the given inDate 7239 */ 7240 DateFactory._dateToIlib = function(inDate, timezone, locale) { 7241 if (typeof(inDate) === 'undefined' || inDate === null) { 7242 return inDate; 7243 } 7244 if (inDate instanceof IDate) { 7245 return inDate; 7246 } 7247 if (JSUtils.isDate(inDate)) { 7248 return DateFactory({ 7249 unixtime: inDate.getTime(), 7250 timezone: timezone, 7251 locale: locale 7252 }); 7253 } 7254 if (inDate instanceof JulianDay) { 7255 return DateFactory({ 7256 jd: inDate, 7257 timezone: timezone, 7258 locale: locale 7259 }); 7260 } 7261 if (typeof(inDate) === 'number') { 7262 return DateFactory({ 7263 unixtime: inDate, 7264 timezone: timezone, 7265 locale: locale 7266 }); 7267 } 7268 if (typeof(inDate) === 'object') { 7269 return DateFactory(inDate); 7270 } 7271 if (typeof(inDate) === 'string') { 7272 inDate = new Date(inDate); 7273 } 7274 return DateFactory({ 7275 unixtime: inDate.getTime(), 7276 timezone: timezone, 7277 locale: locale 7278 }); 7279 }; 7280 7281 7282 /*< ResBundle.js */ 7283 /* 7284 * ResBundle.js - Resource bundle definition 7285 * 7286 * Copyright © 2012-2015, JEDLSoft 7287 * 7288 * Licensed under the Apache License, Version 2.0 (the "License"); 7289 * you may not use this file except in compliance with the License. 7290 * You may obtain a copy of the License at 7291 * 7292 * http://www.apache.org/licenses/LICENSE-2.0 7293 * 7294 * Unless required by applicable law or agreed to in writing, software 7295 * distributed under the License is distributed on an "AS IS" BASIS, 7296 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7297 * 7298 * See the License for the specific language governing permissions and 7299 * limitations under the License. 7300 */ 7301 7302 // !depends ilib.js Locale.js LocaleInfo.js IString.js Utils.js JSUtils.js 7303 7304 // !data pseudomap 7305 7306 7307 7308 7309 /** 7310 * @class 7311 * Create a new resource bundle instance. The resource bundle loads strings 7312 * appropriate for a particular locale and provides them via the getString 7313 * method.<p> 7314 * 7315 * The options object may contain any (or none) of the following properties: 7316 * 7317 * <ul> 7318 * <li><i>locale</i> - The locale of the strings to load. If not specified, the default 7319 * locale is the the default for the web page or app in which the bundle is 7320 * being loaded. 7321 * 7322 * <li><i>name</i> - Base name of the resource bundle to load. If not specified the default 7323 * base name is "resources". 7324 * 7325 * <li><i>type</i> - Name the type of strings this bundle contains. Valid values are 7326 * "xml", "html", "text", or "raw". The default is "text". If the type is "xml" or "html", 7327 * then XML/HTML entities and tags are not pseudo-translated. During a real translation, 7328 * HTML character entities are translated to their corresponding characters in a source 7329 * string before looking that string up in the translations. Also, the characters "<", ">", 7330 * and "&" are converted to entities again in the output, but characters are left as they 7331 * are. If the type is "xml", "html", or "text" types, then the replacement parameter names 7332 * are not pseudo-translated as well so that the output can be used for formatting with 7333 * the IString class. If the type is raw, all characters are pseudo-translated, 7334 * including replacement parameters as well as XML/HTML tags and entities. 7335 * 7336 * <li><i>lengthen</i> - when pseudo-translating the string, tell whether or not to 7337 * automatically lengthen the string to simulate "long" languages such as German 7338 * or French. This is a boolean value. Default is false. 7339 * 7340 * <li><i>missing</i> - what to do when a resource is missing. The choices are: 7341 * <ul> 7342 * <li><i>source</i> - return the source string unchanged 7343 * <li><i>pseudo</i> - return the pseudo-translated source string, translated to the 7344 * script of the locale if the mapping is available, or just the default Latin 7345 * pseudo-translation if not 7346 * <li><i>empty</i> - return the empty string 7347 * </ul> 7348 * The default behaviour is the same as before, which is to return the source string 7349 * unchanged. 7350 * 7351 * <li><i>onLoad</i> - a callback function to call when the resources are fully 7352 * loaded. When the onLoad option is given, this class will attempt to 7353 * load any missing locale data using the ilib loader callback. 7354 * When the constructor is done (even if the data is already preassembled), the 7355 * onLoad function is called with the current instance as a parameter, so this 7356 * callback can be used with preassembled or dynamic loading or a mix of the two. 7357 * 7358 * <li>sync - tell whether to load any missing locale data synchronously or 7359 * asynchronously. If this option is given as "false", then the "onLoad" 7360 * callback must be given, as the instance returned from this constructor will 7361 * not be usable for a while. 7362 * 7363 * <li><i>loadParams</i> - an object containing parameters to pass to the 7364 * loader callback function when locale data is missing. The parameters are not 7365 * interpretted or modified in any way. They are simply passed along. The object 7366 * may contain any property/value pairs as long as the calling code is in 7367 * agreement with the loader callback function as to what those parameters mean. 7368 * </ul> 7369 * 7370 * The locale option may be given as a locale spec string or as an 7371 * Locale object. If the locale option is not specified, then strings for 7372 * the default locale will be loaded.<p> 7373 * 7374 * The name option can be used to put groups of strings together in a 7375 * single bundle. The strings will then appear together in a JS object in 7376 * a JS file that can be included before the ilib.<p> 7377 * 7378 * A resource bundle with a particular name is actually a set of bundles 7379 * that are each specific to a language, a language plus a region, etc. 7380 * All bundles with the same base name should 7381 * contain the same set of source strings, but with different translations for 7382 * the given locale. The user of the bundle does not need to be aware of 7383 * the locale of the bundle, as long as it contains values for the strings 7384 * it needs.<p> 7385 * 7386 * Strings in bundles for a particular locale are inherited from parent bundles 7387 * that are more generic. In general, the hierarchy is as follows (from 7388 * least locale-specific to most locale-specific): 7389 * 7390 * <ol> 7391 * <li> language 7392 * <li> region 7393 * <li> language_script 7394 * <li> language_region 7395 * <li> region_variant 7396 * <li> language_script_region 7397 * <li> language_region_variant 7398 * <li> language_script_region_variant 7399 * </ol> 7400 * 7401 * That is, if the translation for a string does not exist in the current 7402 * locale, the more-generic parent locale is searched for the string. In the 7403 * worst case scenario, the string is not found in the base locale's strings. 7404 * In this case, the missing option guides this class on what to do. If 7405 * the missing option is "source", then the original source is returned as 7406 * the translation. If it is "empty", the empty string is returned. If it 7407 * is "pseudo", then the pseudo-translated string that is appropriate for 7408 * the default script of the locale is returned.<p> 7409 * 7410 * This allows developers to create code with new or changed strings in it and check in that 7411 * code without waiting for the translations to be done first. The translated 7412 * version of the app or web site will still function properly, but will show 7413 * a spurious untranslated string here and there until the translations are 7414 * done and also checked in.<p> 7415 * 7416 * The base is whatever language your developers use to code in. For 7417 * a German web site, strings in the source code may be written in German 7418 * for example. Often this base is English, as many web sites are coded in 7419 * English, but that is not required.<p> 7420 * 7421 * The strings can be extracted with the ilib localization tool (which will be 7422 * shipped at some future time.) Once the strings 7423 * have been translated, the set of translated files can be generated with the 7424 * same tool. The output from the tool can be used as input to the ResBundle 7425 * object. It is up to the web page or app to make sure the JS file that defines 7426 * the bundle is included before creating the ResBundle instance.<p> 7427 * 7428 * A special locale "zxx-XX" is used as the pseudo-translation locale because 7429 * zxx means "no linguistic information" in the ISO 639 standard, and the region 7430 * code XX is defined to be user-defined in the ISO 3166 standard. 7431 * Pseudo-translation is a locale where the translations are generated on 7432 * the fly based on the contents of the source string. Characters in the source 7433 * string are replaced with other characters and returned. 7434 * 7435 * Example. If the source string is: 7436 * 7437 * <pre> 7438 * "This is a string" 7439 * </pre> 7440 * 7441 * then the pseudo-translated version might look something like this: 7442 * 7443 * <pre> 7444 * "Ţħïş ïş á şţřïñĝ" 7445 * </pre> 7446 * <p> 7447 * 7448 * Pseudo-translation can be used to test that your app or web site is translatable 7449 * before an actual translation has happened. These bugs can then be fixed 7450 * before the translation starts, avoiding an explosion of bugs later when 7451 * each language's tester registers the same bug complaining that the same 7452 * string is not translated. When pseudo-localizing with 7453 * the Latin script, this allows the strings to be readable in the UI in the 7454 * source language (if somewhat funky-looking), 7455 * so that a tester can easily verify that the string is properly externalized 7456 * and loaded from a resource bundle without the need to be able to read a 7457 * foreign language.<p> 7458 * 7459 * If one of a list of script tags is given in the pseudo-locale specifier, then the 7460 * pseudo-localization can map characters to very rough transliterations of 7461 * characters in the given script. For example, zxx-Hebr-XX maps strings to 7462 * Hebrew characters, which can be used to test your UI in a right-to-left 7463 * language to catch bidi bugs before a translation is done. Currently, the 7464 * list of target scripts includes Hebrew (Hebr), Chinese Simplified Han (Hans), 7465 * and Cyrillic (Cyrl) with more to be added later. If no script is explicitly 7466 * specified in the locale spec, or if the script is not supported, 7467 * then the default mapping maps Latin base characters to accented versions of 7468 * those Latin characters as in the example above. 7469 * 7470 * When the "lengthen" property is set to true in the options, the 7471 * pseudotranslation code will add digits to the end of the string to simulate 7472 * the lengthening that occurs when translating to other languages. The above 7473 * example will come out like this: 7474 * 7475 * <pre> 7476 * "Ţħïş ïş á şţřïñĝ76543210" 7477 * </pre> 7478 * 7479 * The string is lengthened according to the length of the source string. If 7480 * the source string is less than 20 characters long, the string is lengthened 7481 * by 50%. If the source string is 20-40 7482 * characters long, the string is lengthened by 33%. If te string is greater 7483 * than 40 characters long, the string is lengthened by 20%.<p> 7484 * 7485 * The pseudotranslation always ends a string with the digit "0". If you do 7486 * not see the digit "0" in the UI for your app, you know that truncation 7487 * has occurred, and the number you see at the end of the string tells you 7488 * how many characters were truncated.<p> 7489 * 7490 * 7491 * @constructor 7492 * @param {?Object} options Options controlling how the bundle is created 7493 */ 7494 var ResBundle = function (options) { 7495 var lookupLocale, spec; 7496 7497 this.locale = new Locale(); // use the default locale 7498 this.baseName = "strings"; 7499 this.type = "text"; 7500 this.loadParams = {}; 7501 this.missing = "source"; 7502 this.sync = true; 7503 7504 if (options) { 7505 if (options.locale) { 7506 this.locale = (typeof(options.locale) === 'string') ? 7507 new Locale(options.locale) : 7508 options.locale; 7509 } 7510 if (options.name) { 7511 this.baseName = options.name; 7512 } 7513 if (options.type) { 7514 this.type = options.type; 7515 } 7516 this.lengthen = options.lengthen || false; 7517 7518 if (typeof(options.sync) !== 'undefined') { 7519 this.sync = (options.sync == true); 7520 } 7521 7522 if (typeof(options.loadParams) !== 'undefined') { 7523 this.loadParams = options.loadParams; 7524 } 7525 if (typeof(options.missing) !== 'undefined') { 7526 if (options.missing === "pseudo" || options.missing === "empty") { 7527 this.missing = options.missing; 7528 } 7529 } 7530 } else { 7531 options = {}; 7532 } 7533 7534 this.map = {}; 7535 7536 if (!ResBundle[this.baseName]) { 7537 ResBundle[this.baseName] = {}; 7538 } 7539 7540 lookupLocale = this.locale.isPseudo() ? new Locale("en-US") : this.locale; 7541 7542 Utils.loadData({ 7543 object: ResBundle[this.baseName], 7544 locale: lookupLocale, 7545 name: this.baseName + ".json", 7546 sync: this.sync, 7547 loadParams: this.loadParams, 7548 callback: ilib.bind(this, function (map) { 7549 if (!map) { 7550 map = ilib.data[this.baseName] || {}; 7551 spec = lookupLocale.getSpec().replace(/-/g, '_'); 7552 ResBundle[this.baseName].cache[spec] = map; 7553 } 7554 this.map = map; 7555 if (this.locale.isPseudo()) { 7556 if (!ResBundle.pseudomap) { 7557 ResBundle.pseudomap = {}; 7558 } 7559 7560 this._loadPseudo(this.locale, options.onLoad); 7561 } else if (this.missing === "pseudo") { 7562 if (!ResBundle.pseudomap) { 7563 ResBundle.pseudomap = {}; 7564 } 7565 7566 new LocaleInfo(this.locale, { 7567 sync: this.sync, 7568 loadParams: this.loadParams, 7569 onLoad: ilib.bind(this, function (li) { 7570 var pseudoLocale = new Locale("zxx", "XX", undefined, li.getDefaultScript()); 7571 this._loadPseudo(pseudoLocale, options.onLoad); 7572 }) 7573 }); 7574 } else { 7575 if (typeof(options.onLoad) === 'function') { 7576 options.onLoad(this); 7577 } 7578 } 7579 }) 7580 }); 7581 7582 // console.log("Merged resources " + this.locale.toString() + " are: " + JSON.stringify(this.map)); 7583 //if (!this.locale.isPseudo() && JSUtils.isEmpty(this.map)) { 7584 // console.log("Resources for bundle " + this.baseName + " locale " + this.locale.toString() + " are not available."); 7585 //} 7586 }; 7587 7588 ResBundle.defaultPseudo = ilib.data.pseudomap || { 7589 "a": "à", 7590 "e": "ë", 7591 "i": "í", 7592 "o": "õ", 7593 "u": "ü", 7594 "y": "ÿ", 7595 "A": "Ã", 7596 "E": "Ë", 7597 "I": "Ï", 7598 "O": "Ø", 7599 "U": "Ú", 7600 "Y": "Ŷ" 7601 }; 7602 7603 ResBundle.prototype = { 7604 /** 7605 * @protected 7606 */ 7607 _loadPseudo: function (pseudoLocale, onLoad) { 7608 Utils.loadData({ 7609 object: ResBundle.pseudomap, 7610 locale: pseudoLocale, 7611 name: "pseudomap.json", 7612 sync: this.sync, 7613 loadParams: this.loadParams, 7614 callback: ilib.bind(this, function (map) { 7615 if (!map || JSUtils.isEmpty(map)) { 7616 map = ResBundle.defaultPseudo; 7617 var spec = pseudoLocale.getSpec().replace(/-/g, '_'); 7618 ResBundle.pseudomap.cache[spec] = map; 7619 } 7620 this.pseudomap = map; 7621 if (typeof(onLoad) === 'function') { 7622 onLoad(this); 7623 } 7624 }) 7625 }); 7626 }, 7627 7628 /** 7629 * Return the locale of this resource bundle. 7630 * @return {Locale} the locale of this resource bundle object 7631 */ 7632 getLocale: function () { 7633 return this.locale; 7634 }, 7635 7636 /** 7637 * Return the name of this resource bundle. This corresponds to the name option 7638 * given to the constructor. 7639 * @return {string} name of the the current instance 7640 */ 7641 getName: function () { 7642 return this.baseName; 7643 }, 7644 7645 /** 7646 * Return the type of this resource bundle. This corresponds to the type option 7647 * given to the constructor. 7648 * @return {string} type of the the current instance 7649 */ 7650 getType: function () { 7651 return this.type; 7652 }, 7653 7654 /* 7655 * @private 7656 * Pseudo-translate a string 7657 */ 7658 pseudo: function (str) { 7659 if (!str) { 7660 return undefined; 7661 } 7662 var ret = "", i; 7663 for (i = 0; i < str.length; i++) { 7664 if (this.type !== "raw") { 7665 if (this.type === "html" || this.type === "xml") { 7666 if (str.charAt(i) === '<') { 7667 ret += str.charAt(i++); 7668 while (i < str.length && str.charAt(i) !== '>') { 7669 ret += str.charAt(i++); 7670 } 7671 if (i < str.length) { 7672 ret += str.charAt(i++); 7673 } 7674 } else if (str.charAt(i) === '&') { 7675 ret += str.charAt(i++); 7676 while (i < str.length && str.charAt(i) !== ';' && str.charAt(i) !== ' ') { 7677 ret += str.charAt(i++); 7678 } 7679 if (i < str.length) { 7680 ret += str.charAt(i++); 7681 } 7682 } 7683 } 7684 if (i < str.length) { 7685 if (str.charAt(i) === '{') { 7686 ret += str.charAt(i++); 7687 while (i < str.length && str.charAt(i) !== '}') { 7688 ret += str.charAt(i++); 7689 } 7690 if (i < str.length) { 7691 ret += str.charAt(i); 7692 } 7693 } else { 7694 ret += this.pseudomap[str.charAt(i)] || str.charAt(i); 7695 } 7696 } 7697 } else { 7698 ret += this.pseudomap[str.charAt(i)] || str.charAt(i); 7699 } 7700 } 7701 if (this.lengthen) { 7702 var add; 7703 if (ret.length <= 20) { 7704 add = Math.round(ret.length / 2); 7705 } else if (ret.length > 20 && ret.length <= 40) { 7706 add = Math.round(ret.length / 3); 7707 } else { 7708 add = Math.round(ret.length / 5); 7709 } 7710 for (i = add-1; i >= 0; i--) { 7711 ret += (i % 10); 7712 } 7713 } 7714 if (this.locale.getScript() === "Hans" || this.locale.getScript() === "Hant" || 7715 this.locale.getScript() === "Hani" || 7716 this.locale.getScript() === "Hrkt" || this.locale.getScript() === "Jpan" || 7717 this.locale.getScript() === "Hira" || this.locale.getScript() === "Kana" ) { 7718 // simulate Asian languages by getting rid of all the spaces 7719 ret = ret.replace(/ /g, ""); 7720 } 7721 return ret; 7722 }, 7723 7724 /* 7725 * @private 7726 * Escape html characters in the output. 7727 */ 7728 escapeXml: function (str) { 7729 str = str.replace(/&/g, '&'); 7730 str = str.replace(/</g, '<'); 7731 str = str.replace(/>/g, '>'); 7732 return str; 7733 }, 7734 7735 /* 7736 * @private 7737 * @param {string} str the string to unescape 7738 */ 7739 unescapeXml: function (str) { 7740 str = str.replace(/&/g, '&'); 7741 str = str.replace(/</g, '<'); 7742 str = str.replace(/>/g, '>'); 7743 return str; 7744 }, 7745 7746 /* 7747 * @private 7748 * Create a key name out of a source string. All this does so far is 7749 * compress sequences of white space into a single space on the assumption 7750 * that this doesn't really change the meaning of the string, and therefore 7751 * all such strings that compress to the same thing should share the same 7752 * translation. 7753 * @param {string} source the source string to make a key out of 7754 */ 7755 makeKey: function (source) { 7756 var key = source.replace(/\s+/gm, ' '); 7757 return (this.type === "xml" || this.type === "html") ? this.unescapeXml(key) : key; 7758 }, 7759 7760 /** 7761 * Return a localized string. If the string is not found in the loaded set of 7762 * resources, the original source string is returned. If the key is not given, 7763 * then the source string itself is used as the key. In the case where the 7764 * source string is used as the key, the whitespace is compressed down to 1 space 7765 * each, and the whitespace at the beginning and end of the string is trimmed.<p> 7766 * 7767 * The escape mode specifies what type of output you are escaping the returned 7768 * string for. Modes are similar to the types: 7769 * 7770 * <ul> 7771 * <li>"html" -- prevents HTML injection by escaping the characters < > and & 7772 * <li>"xml" -- currently same as "html" mode 7773 * <li>"js" -- prevents breaking Javascript syntax by backslash escaping all quote and 7774 * double-quote characters 7775 * <li>"attribute" -- meant for HTML attribute values. Currently this is the same as 7776 * "js" escape mode. 7777 * <li>"default" -- use the type parameter from the constructor as the escape mode as well 7778 * <li>"none" or undefined -- no escaping at all. 7779 * </ul> 7780 * 7781 * The type parameter of the constructor specifies what type of strings this bundle 7782 * is operating upon. This allows pseudo-translation and automatic key generation 7783 * to happen properly by telling this class how to parse the string. The escape mode 7784 * for this method is different in that it specifies how this string will be used in 7785 * the calling code and therefore how to escape it properly.<p> 7786 * 7787 * For example, a section of Javascript code may be constructing an HTML snippet in a 7788 * string to add to the web page. In this case, the type parameter in the constructor should 7789 * be "html" so that the source string can be parsed properly, but the escape mode should 7790 * be "js" so that the output string can be used in Javascript without causing syntax 7791 * errors. 7792 * 7793 * @param {?string=} source the source string to translate 7794 * @param {?string=} key optional name of the key, if any 7795 * @param {?string=} escapeMode escape mode, if any 7796 * @return {IString|undefined} the translation of the given source/key or undefined 7797 * if the translation is not found and the source is undefined 7798 */ 7799 getString: function (source, key, escapeMode) { 7800 if (!source && !key) return new IString(""); 7801 7802 var trans; 7803 if (this.locale.isPseudo()) { 7804 var str = source ? source : this.map[key]; 7805 trans = this.pseudo(str || key); 7806 } else { 7807 var keyName = key || this.makeKey(source); 7808 if (typeof(this.map[keyName]) !== 'undefined') { 7809 trans = this.map[keyName]; 7810 } else if (this.missing === "pseudo") { 7811 trans = this.pseudo(source || key); 7812 } else if (this.missing === "empty") { 7813 trans = ""; 7814 } else { 7815 trans = source; 7816 } 7817 } 7818 7819 if (escapeMode && escapeMode !== "none") { 7820 if (escapeMode == "default") { 7821 escapeMode = this.type; 7822 } 7823 if (escapeMode === "xml" || escapeMode === "html") { 7824 trans = this.escapeXml(trans); 7825 } else if (escapeMode == "js" || escapeMode === "attribute") { 7826 trans = trans.replace(/'/g, "\\\'").replace(/"/g, "\\\""); 7827 } 7828 } 7829 if (trans === undefined) { 7830 return undefined; 7831 } else { 7832 var ret = new IString(trans); 7833 ret.setLocale(this.locale.getSpec(), true, this.loadParams); // no callback 7834 return ret; 7835 } 7836 }, 7837 7838 /** 7839 * Return a localized string as a Javascript object. This does the same thing as 7840 * the getString() method, but it returns a regular Javascript string instead of 7841 * and IString instance. This means it cannot be formatted with the format() 7842 * method without being wrapped in an IString instance first. 7843 * 7844 * @param {?string=} source the source string to translate 7845 * @param {?string=} key optional name of the key, if any 7846 * @param {?string=} escapeMode escape mode, if any 7847 * @return {string|undefined} the translation of the given source/key or undefined 7848 * if the translation is not found and the source is undefined 7849 */ 7850 getStringJS: function(source, key, escapeMode) { 7851 return this.getString(source, key, escapeMode).toString(); 7852 }, 7853 7854 /** 7855 * Return true if the current bundle contains a translation for the given key and 7856 * source. The 7857 * getString method will always return a string for any given key and source 7858 * combination, so it cannot be used to tell if a translation exists. Either one 7859 * or both of the source and key must be specified. If both are not specified, 7860 * this method will return false. 7861 * 7862 * @param {?string=} source source string to look up 7863 * @param {?string=} key key to look up 7864 * @return {boolean} true if this bundle contains a translation for the key, and 7865 * false otherwise 7866 */ 7867 containsKey: function(source, key) { 7868 if (typeof(source) === 'undefined' && typeof(key) === 'undefined') { 7869 return false; 7870 } 7871 7872 var keyName = key || this.makeKey(source); 7873 return typeof(this.map[keyName]) !== 'undefined'; 7874 }, 7875 7876 /** 7877 * Return the merged resources as an entire object. When loading resources for a 7878 * locale that are not just a set of translated strings, but instead an entire 7879 * structured javascript object, you can gain access to that object via this call. This method 7880 * will ensure that all the of the parts of the object are correct for the locale.<p> 7881 * 7882 * For pre-assembled data, it starts by loading <i>ilib.data[name]</i>, where 7883 * <i>name</i> is the base name for this set of resources. Then, it successively 7884 * merges objects in the base data using progressively more locale-specific data. 7885 * It loads it in this order from <i>ilib.data</i>: 7886 * 7887 * <ol> 7888 * <li> language 7889 * <li> region 7890 * <li> language_script 7891 * <li> language_region 7892 * <li> region_variant 7893 * <li> language_script_region 7894 * <li> language_region_variant 7895 * <li> language_script_region_variant 7896 * </ol> 7897 * 7898 * For dynamically loaded data, the code attempts to load the same sequence as 7899 * above, but with slash path separators instead of underscores.<p> 7900 * 7901 * Loading the resources this way allows the program to share resources between all 7902 * locales that share a common language, region, or script. As a 7903 * general rule-of-thumb, resources should be as generic as possible in order to 7904 * cover as many locales as possible. 7905 * 7906 * @return {Object} returns the object that is the basis for this resources instance 7907 */ 7908 getResObj: function () { 7909 return this.map; 7910 } 7911 }; 7912 7913 7914 /*< DateFmt.js */ 7915 /* 7916 * DateFmt.js - Date formatter definition 7917 * 7918 * Copyright © 2012-2015, JEDLSoft 7919 * 7920 * Licensed under the Apache License, Version 2.0 (the "License"); 7921 * you may not use this file except in compliance with the License. 7922 * You may obtain a copy of the License at 7923 * 7924 * http://www.apache.org/licenses/LICENSE-2.0 7925 * 7926 * Unless required by applicable law or agreed to in writing, software 7927 * distributed under the License is distributed on an "AS IS" BASIS, 7928 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7929 * 7930 * See the License for the specific language governing permissions and 7931 * limitations under the License. 7932 */ 7933 7934 /* 7935 !depends 7936 ilib.js 7937 Locale.js 7938 IDate.js 7939 DateFactory.js 7940 IString.js 7941 ResBundle.js 7942 Calendar.js 7943 CalendarFactory.js 7944 LocaleInfo.js 7945 TimeZone.js 7946 GregorianCal.js 7947 JSUtils.js 7948 Utils.js 7949 */ 7950 7951 // !data dateformats sysres 7952 7953 7954 7955 7956 7957 /** 7958 * @class 7959 * Create a new date formatter instance. The date formatter is immutable once 7960 * it is created, but can format as many different dates as needed with the same 7961 * options. Create different date formatter instances for different purposes 7962 * and then keep them cached for use later if you have more than one date to 7963 * format.<p> 7964 * 7965 * The options may contain any of the following properties: 7966 * 7967 * <ul> 7968 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 7969 * not specified, then the default locale of the app or web page will be used. 7970 * 7971 * <li><i>calendar</i> - the type of calendar to use for this format. The value should 7972 * be a sting containing the name of the calendar. Currently, the supported 7973 * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the 7974 * calendar is not specified, then the default calendar for the locale is used. When the 7975 * calendar type is specified, then the format method must be called with an instance of 7976 * the appropriate date type. (eg. Gregorian calendar means that the format method must 7977 * be called with a GregDate instance.) 7978 * 7979 * <li><i>timezone</i> - time zone to use when formatting times. This may be a time zone 7980 * instance or a time zone specifier from the IANA list of time zone database names 7981 * (eg. "America/Los_Angeles"), 7982 * the string "local", or a string specifying the offset in RFC 822 format. The IANA 7983 * list of time zone names can be viewed at 7984 * <a href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">this page</a>. 7985 * If the time zone is given as "local", the offset from UTC as given by 7986 * the Javascript system is used. If the offset is given as an RFC 822 style offset 7987 * specifier, it will parse that string and use the resulting offset. If the time zone 7988 * is not specified, the 7989 * default time zone for the locale is used. If both the date object and this formatter 7990 * instance contain time zones and those time zones are different from each other, the 7991 * formatter will calculate the offset between the time zones and subtract it from the 7992 * date before formatting the result for the current time zone. The theory is that a date 7993 * object that contains a time zone specifies a specific instant in time that is valid 7994 * around the world, whereas a date object without one is a local time and can only be 7995 * used for doing things in the local time zone of the user. 7996 * 7997 * <li><i>type</i> - Specify whether this formatter should format times only, dates only, or 7998 * both times and dates together. Valid values are "time", "date", and "datetime". Note that 7999 * in some locales, the standard format uses the order "time followed by date" and in others, 8000 * the order is exactly opposite, so it is better to create a single "datetime" formatter 8001 * than it is to create a time formatter and a date formatter separately and concatenate the 8002 * results. A "datetime" formatter will get the order correct for the locale.<p> 8003 * 8004 * The default type if none is specified in with the type option is "date". 8005 * 8006 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 8007 * formatted string. 8008 * 8009 * <ul> 8010 * <li><i>short</i> - use a short representation of the time. This is the most compact format possible for the locale. 8011 * <li><i>medium</i> - use a medium length representation of the time. This is a slightly longer format. 8012 * <li><i>long</i> - use a long representation of the time. This is a fully specified format, but some of the textual 8013 * components may still be abbreviated 8014 * <li><i>full</i> - use a full representation of the time. This is a fully specified format where all the textual 8015 * components are spelled out completely 8016 * </ul> 8017 * 8018 * 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 8019 * format, the month name is textual instead of numeric and is longer, the year is 4 digits instead of 2, and the format 8020 * contains slightly more spaces and formatting characters.<p> 8021 * 8022 * Note that the length parameter does not specify which components are to be formatted. Use the "date" and the "time" 8023 * properties to specify the components. Also, very few of the components of a time format differ according to the length, 8024 * so this property has little to no affect on time formatting. 8025 * 8026 * <li><i>date</i> - This property tells 8027 * which components of a date format to use. For example, 8028 * sometimes you may wish to format a date that only contains the month and date 8029 * without the year, such as when displaying a person's yearly birthday. The value 8030 * of this property allows you to specify only those components you want to see in the 8031 * final output, ordered correctly for the locale. <p> 8032 * 8033 * Valid values are: 8034 * 8035 * <ul> 8036 * <li><i>dmwy</i> - format all components, weekday, date, month, and year 8037 * <li><i>dmy</i> - format only date, month, and year 8038 * <li><i>dmw</i> - format only weekday, date, and month 8039 * <li><i>dm</i> - format only date and month 8040 * <li><i>my</i> - format only month and year 8041 * <li><i>dw</i> - format only the weekday and date 8042 * <li><i>d</i> - format only the date 8043 * <li><i>m</i> - format only the month, in numbers for shorter lengths, and letters for 8044 * longer lengths 8045 * <li><i>n</i> - format only the month, in letters only for all lengths 8046 * <li><i>y</i> - format only the year 8047 * </ul> 8048 * Default components, if this property is not specified, is "dmy". This property may be specified 8049 * but has no affect if the current formatter is for times only. 8050 * 8051 * <li><i>time</i> - This property gives which components of a time format to use. The time will be formatted 8052 * correctly for the locale with only the time components requested. For example, a clock might only display 8053 * the hour and minute and not need the seconds or the am/pm component. In this case, the time property should be set 8054 * to "hm". <p> 8055 * 8056 * Valid values for this property are: 8057 * 8058 * <ul> 8059 * <li><i>ahmsz</i> - format the hours, minutes, seconds, am/pm (if using a 12 hour clock), and the time zone 8060 * <li><i>ahms</i> - format the hours, minutes, seconds, and am/pm (if using a 12 hour clock) 8061 * <li><i>hmsz</i> - format the hours, minutes, seconds, and the time zone 8062 * <li><i>hms</i> - format the hours, minutes, and seconds 8063 * <li><i>ahmz</i> - format the hours, minutes, am/pm (if using a 12 hour clock), and the time zone 8064 * <li><i>ahm</i> - format the hours, minutes, and am/pm (if using a 12 hour clock) 8065 * <li><i>hmz</i> - format the hours, minutes, and the time zone 8066 * <li><i>ah</i> - format only the hours and am/pm if using a 12 hour clock 8067 * <li><i>hm</i> - format only the hours and minutes 8068 * <li><i>ms</i> - format only the minutes and seconds 8069 * <li><i>h</i> - format only the hours 8070 * <li><i>m</i> - format only the minutes 8071 * <li><i>s</i> - format only the seconds 8072 * </ul> 8073 * 8074 * If you want to format a length of time instead of a particular instant 8075 * in time, use the duration formatter object (DurationFmt) instead because this 8076 * formatter is geared towards instants. A date formatter will make sure that each component of the 8077 * time is within the normal range 8078 * for that component. That is, the minutes will always be between 0 and 59, no matter 8079 * what is specified in the date to format. A duration format will allow the number 8080 * of minutes to exceed 59 if, for example, you were displaying the length of 8081 * a movie of 198 minutes.<p> 8082 * 8083 * Default value if this property is not specified is "hma". 8084 * 8085 * <li><i>clock</i> - specify that the time formatter should use a 12 or 24 hour clock. 8086 * Valid values are "12" and "24".<p> 8087 * 8088 * In some locales, both clocks are used. For example, in en_US, the general populace uses 8089 * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or 8090 * scientific writing, it is more common to use a 24 hour clock. This property allows you to 8091 * construct a formatter that overrides the default for the locale.<p> 8092 * 8093 * If this property is not specified, the default is to use the most widely used convention 8094 * for the locale. 8095 * 8096 * <li><i>template</i> - use the given template string as a fixed format when formatting 8097 * the date/time. Valid codes to use in a template string are as follows: 8098 * 8099 * <ul> 8100 * <li><i>a</i> - am/pm marker 8101 * <li><i>d</i> - 1 or 2 digit date of month, not padded 8102 * <li><i>dd</i> - 1 or 2 digit date of month, 0 padded to 2 digits 8103 * <li><i>O</i> - ordinal representation of the date of month (eg. "1st", "2nd", etc.) 8104 * <li><i>D</i> - 1 to 3 digit day of year 8105 * <li><i>DD</i> - 1 to 3 digit day of year, 0 padded to 2 digits 8106 * <li><i>DDD</i> - 1 to 3 digit day of year, 0 padded to 3 digits 8107 * <li><i>M</i> - 1 or 2 digit month number, not padded 8108 * <li><i>MM</i> - 1 or 2 digit month number, 0 padded to 2 digits 8109 * <li><i>N</i> - 1 character month name abbreviation 8110 * <li><i>NN</i> - 2 character month name abbreviation 8111 * <li><i>MMM</i> - 3 character month month name abbreviation 8112 * <li><i>MMMM</i> - fully spelled out month name 8113 * <li><i>yy</i> - 2 digit year 8114 * <li><i>yyyy</i> - 4 digit year 8115 * <li><i>E</i> - day-of-week name, abbreviated to a single character 8116 * <li><i>EE</i> - day-of-week name, abbreviated to a max of 2 characters 8117 * <li><i>EEE</i> - day-of-week name, abbreviated to a max of 3 characters 8118 * <li><i>EEEE</i> - day-of-week name fully spelled out 8119 * <li><i>G</i> - era designator 8120 * <li><i>w</i> - week number in year 8121 * <li><i>ww</i> - week number in year, 0 padded to 2 digits 8122 * <li><i>W</i> - week in month 8123 * <li><i>h</i> - hour (12 followed by 1 to 11) 8124 * <li><i>hh</i> - hour (12, followed by 1 to 11), 0 padded to 2 digits 8125 * <li><i>k</i> - hour (1 to 24) 8126 * <li><i>kk</i> - hour (1 to 24), 0 padded to 2 digits 8127 * <li><i>H</i> - hour (0 to 23) 8128 * <li><i>HH</i> - hour (0 to 23), 0 padded to 2 digits 8129 * <li><i>K</i> - hour (0 to 11) 8130 * <li><i>KK</i> - hour (0 to 11), 0 padded to 2 digits 8131 * <li><i>m</i> - minute in hour 8132 * <li><i>mm</i> - minute in hour, 0 padded to 2 digits 8133 * <li><i>s</i> - second in minute 8134 * <li><i>ss</i> - second in minute, 0 padded to 2 digits 8135 * <li><i>S</i> - millisecond (1 to 3 digits) 8136 * <li><i>SSS</i> - millisecond, 0 padded to 3 digits 8137 * <li><i>z</i> - general time zone 8138 * <li><i>Z</i> - RFC 822 time zone 8139 * </ul> 8140 * 8141 * <li><i>useNative</i> - the flag used to determine whether to use the native script settings 8142 * for formatting the numbers. 8143 * 8144 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 8145 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 8146 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 8147 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 8148 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 8149 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 8150 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 8151 * when formatting dates in the Gregorian calendar. 8152 * 8153 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 8154 * loaded. When the onLoad option is given, the DateFmt object will attempt to 8155 * load any missing locale data using the ilib loader callback. 8156 * When the constructor is done (even if the data is already preassembled), the 8157 * onLoad function is called with the current instance as a parameter, so this 8158 * callback can be used with preassembled or dynamic loading or a mix of the two. 8159 * 8160 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 8161 * asynchronously. If this option is given as "false", then the "onLoad" 8162 * callback must be given, as the instance returned from this constructor will 8163 * not be usable for a while. 8164 * 8165 * <li><i>loadParams</i> - an object containing parameters to pass to the 8166 * loader callback function when locale data is missing. The parameters are not 8167 * interpretted or modified in any way. They are simply passed along. The object 8168 * may contain any property/value pairs as long as the calling code is in 8169 * agreement with the loader callback function as to what those parameters mean. 8170 * </ul> 8171 * 8172 * Any substring containing letters within single or double quotes will be used 8173 * as-is in the final output and will not be interpretted for codes as above.<p> 8174 * 8175 * Example: a date format in Spanish might be given as: "'El' d. 'de' MMMM", where 8176 * the 'El' and the 'de' are left as-is in the output because they are quoted. Typical 8177 * output for this example template might be, "El 5. de Mayo". 8178 * 8179 * The following options will be used when formatting a date/time with an explicit 8180 * template: 8181 * 8182 * <ul> 8183 * <li>locale - the locale is only used for 8184 * translations of things like month names or day-of-week names. 8185 * <li>calendar - used to translate a date instance into date/time component values 8186 * that can be formatted into the template 8187 * <li>timezone - used to figure out the offset to add or subtract from the time to 8188 * get the final time component values 8189 * <li>clock - used to figure out whether to format times with a 12 or 24 hour clock. 8190 * If this option is specified, it will override the hours portion of a time format. 8191 * That is, "hh" is switched with "HH" and "kk" is switched with "KK" as appropriate. 8192 * If this option is not specified, the 12/24 code in the template will dictate whether 8193 * to use the 12 or 24 clock, and the 12/24 default in the locale will be ignored. 8194 * </ul> 8195 * 8196 * All other options will be ignored and their corresponding getter methods will 8197 * return the empty string.<p> 8198 * 8199 * 8200 * @constructor 8201 * @param {Object} options options governing the way this date formatter instance works 8202 */ 8203 var DateFmt = function(options) { 8204 var arr, i, bad, 8205 sync = true, 8206 loadParams = undefined; 8207 8208 this.locale = new Locale(); 8209 this.type = "date"; 8210 this.length = "s"; 8211 this.dateComponents = "dmy"; 8212 this.timeComponents = "ahm"; 8213 this.meridiems = "default"; 8214 8215 if (options) { 8216 if (options.locale) { 8217 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 8218 } 8219 8220 if (options.type) { 8221 if (options.type === 'date' || options.type === 'time' || options.type === 'datetime') { 8222 this.type = options.type; 8223 } 8224 } 8225 8226 if (options.calendar) { 8227 this.calName = options.calendar; 8228 } 8229 8230 if (options.length) { 8231 if (options.length === 'short' || 8232 options.length === 'medium' || 8233 options.length === 'long' || 8234 options.length === 'full') { 8235 // only use the first char to save space in the json files 8236 this.length = options.length.charAt(0); 8237 } 8238 } 8239 8240 if (options.date) { 8241 arr = options.date.split(""); 8242 arr.sort(function (left, right) { 8243 return (left < right) ? -1 : ((right < left) ? 1 : 0); 8244 }); 8245 bad = false; 8246 for (i = 0; i < arr.length; i++) { 8247 if (arr[i] !== 'd' && arr[i] !== 'm' && arr[i] !== 'y' && arr[i] !== 'w' && arr[i] !== 'n') { 8248 bad = true; 8249 break; 8250 } 8251 } 8252 if (!bad) { 8253 this.dateComponents = arr.join(""); 8254 } 8255 } 8256 8257 if (options.time) { 8258 arr = options.time.split(""); 8259 arr.sort(function (left, right) { 8260 return (left < right) ? -1 : ((right < left) ? 1 : 0); 8261 }); 8262 this.badTime = false; 8263 for (i = 0; i < arr.length; i++) { 8264 if (arr[i] !== 'h' && arr[i] !== 'm' && arr[i] !== 's' && arr[i] !== 'a' && arr[i] !== 'z') { 8265 this.badTime = true; 8266 break; 8267 } 8268 } 8269 if (!this.badTime) { 8270 this.timeComponents = arr.join(""); 8271 } 8272 } 8273 8274 if (options.clock && (options.clock === '12' || options.clock === '24')) { 8275 this.clock = options.clock; 8276 } 8277 8278 if (options.template) { 8279 // many options are not useful when specifying the template directly, so zero 8280 // them out. 8281 this.type = ""; 8282 this.length = ""; 8283 this.dateComponents = ""; 8284 this.timeComponents = ""; 8285 8286 this.template = options.template; 8287 } 8288 8289 if (options.timezone) { 8290 if (options.timezone instanceof TimeZone) { 8291 this.tz = options.timezone; 8292 } else { 8293 this.tz = new TimeZone({ 8294 locale: this.locale, 8295 id: options.timezone 8296 }); 8297 } 8298 } else if (options.locale) { 8299 // if an explicit locale was given, then get the time zone for that locale 8300 this.tz = new TimeZone({ 8301 locale: this.locale 8302 }); 8303 } // else just assume time zone "local" 8304 8305 if (typeof(options.useNative) === 'boolean') { 8306 this.useNative = options.useNative; 8307 } 8308 8309 if (typeof(options.meridiems) !== 'undefined' && 8310 (options.meridiems === "chinese" || 8311 options.meridiems === "gregorian" || 8312 options.meridiems === "ethiopic")) { 8313 this.meridiems = options.meridiems; 8314 } 8315 8316 if (typeof(options.sync) !== 'undefined') { 8317 sync = (options.sync === true); 8318 } 8319 8320 loadParams = options.loadParams; 8321 } 8322 8323 if (!DateFmt.cache) { 8324 DateFmt.cache = {}; 8325 } 8326 8327 new LocaleInfo(this.locale, { 8328 sync: sync, 8329 loadParams: loadParams, 8330 onLoad: ilib.bind(this, function (li) { 8331 this.locinfo = li; 8332 8333 // get the default calendar name from the locale, and if the locale doesn't define 8334 // one, use the hard-coded gregorian as the last resort 8335 this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; 8336 if (ilib.isDynCode()) { 8337 // If we are running in the dynamic code loading assembly of ilib, the following 8338 // will attempt to dynamically load the calendar date class for this calendar. If 8339 // it doesn't work, this just goes on and it will use Gregorian instead. 8340 DateFactory._dynLoadDate(this.calName); 8341 } 8342 8343 this.cal = CalendarFactory({ 8344 type: this.calName 8345 }); 8346 if (!this.cal) { 8347 this.cal = new GregorianCal(); 8348 } 8349 if (this.meridiems === "default") { 8350 this.meridiems = li.getMeridiemsStyle(); 8351 } 8352 8353 /* 8354 if (this.timeComponents && 8355 (this.clock === '24' || 8356 (!this.clock && this.locinfo.getClock() === "24"))) { 8357 // make sure we don't have am/pm in 24 hour mode unless the user specifically 8358 // requested it in the time component option 8359 this.timeComponents = this.timeComponents.replace("a", ""); 8360 } 8361 */ 8362 8363 // load the strings used to translate the components 8364 new ResBundle({ 8365 locale: this.locale, 8366 name: "sysres", 8367 sync: sync, 8368 loadParams: loadParams, 8369 onLoad: ilib.bind(this, function (rb) { 8370 this.sysres = rb; 8371 8372 if (!this.template) { 8373 Utils.loadData({ 8374 object: DateFmt, 8375 locale: this.locale, 8376 name: "dateformats.json", 8377 sync: sync, 8378 loadParams: loadParams, 8379 callback: ilib.bind(this, function (formats) { 8380 if (!formats) { 8381 formats = ilib.data.dateformats || DateFmt.defaultFmt; 8382 var spec = this.locale.getSpec().replace(/-/g, '_'); 8383 DateFmt.cache[spec] = formats; 8384 } 8385 if (typeof(this.clock) === 'undefined') { 8386 // default to the locale instead 8387 this.clock = this.locinfo.getClock(); 8388 } 8389 this._initTemplate(formats); 8390 this._massageTemplate(); 8391 if (options && typeof(options.onLoad) === 'function') { 8392 options.onLoad(this); 8393 } 8394 }) 8395 }); 8396 } else { 8397 this._massageTemplate(); 8398 if (options && typeof(options.onLoad) === 'function') { 8399 options.onLoad(this); 8400 } 8401 } 8402 }) 8403 }); 8404 }) 8405 }); 8406 }; 8407 8408 // used in getLength 8409 DateFmt.lenmap = { 8410 "s": "short", 8411 "m": "medium", 8412 "l": "long", 8413 "f": "full" 8414 }; 8415 8416 DateFmt.zeros = "0000"; 8417 8418 DateFmt.defaultFmt = { 8419 "gregorian": { 8420 "order": "{date} {time}", 8421 "date": { 8422 "dmwy": "EEE d/MM/yyyy", 8423 "dmy": "d/MM/yyyy", 8424 "dmw": "EEE d/MM", 8425 "dm": "d/MM", 8426 "my": "MM/yyyy", 8427 "dw": "EEE d", 8428 "d": "dd", 8429 "m": "MM", 8430 "y": "yyyy", 8431 "n": "NN", 8432 "w": "EEE" 8433 }, 8434 "time": { 8435 "12": "h:mm:ssa", 8436 "24": "H:mm:ss" 8437 }, 8438 "range": { 8439 "c00": "{st} - {et}, {sd}/{sm}/{sy}", 8440 "c01": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 8441 "c02": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 8442 "c03": "{sd}/{sm}/{sy} {st} - {ed}/{em}/{ey} {et}", 8443 "c10": "{sd}-{ed}/{sm}/{sy}", 8444 "c11": "{sd}/{sm} - {ed}/{em} {sy}", 8445 "c12": "{sd}/{sm}/{sy} - {ed}/{em}/{ey}", 8446 "c20": "{sm}/{sy} - {em}/{ey}", 8447 "c30": "{sy} - {ey}" 8448 } 8449 }, 8450 "islamic": "gregorian", 8451 "hebrew": "gregorian", 8452 "julian": "gregorian", 8453 "buddhist": "gregorian", 8454 "persian": "gregorian", 8455 "persian-algo": "gregorian", 8456 "han": "gregorian" 8457 }; 8458 8459 /** 8460 * @static 8461 * @private 8462 */ 8463 DateFmt.monthNameLenMap = { 8464 "short" : "N", 8465 "medium": "NN", 8466 "long": "MMM", 8467 "full": "MMMM" 8468 }; 8469 8470 /** 8471 * @static 8472 * @private 8473 */ 8474 DateFmt.weekDayLenMap = { 8475 "short" : "E", 8476 "medium": "EE", 8477 "long": "EEE", 8478 "full": "EEEE" 8479 }; 8480 8481 /** 8482 * Return the range of possible meridiems (times of day like "AM" or 8483 * "PM") in this date formatter.<p> 8484 * 8485 * The options may contain any of the following properties: 8486 * 8487 * <ul> 8488 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 8489 * not specified, then the default locale of the app or web page will be used. 8490 * 8491 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 8492 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 8493 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 8494 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 8495 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 8496 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 8497 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 8498 * when formatting dates in the Gregorian calendar. 8499 * </ul> 8500 * 8501 * @static 8502 * @public 8503 * @param {Object} options options governing the way this date formatter instance works for getting meridiems range 8504 * @return {Array.<{name:string,start:string,end:string}>} 8505 */ 8506 DateFmt.getMeridiemsRange = function (options) { 8507 options = options || {}; 8508 var args = {}; 8509 if (options.locale) { 8510 args.locale = options.locale; 8511 } 8512 8513 if (options.meridiems) { 8514 args.meridiems = options.meridiems; 8515 } 8516 8517 var fmt = new DateFmt(args); 8518 8519 return fmt.getMeridiemsRange(); 8520 }; 8521 8522 DateFmt.prototype = { 8523 /** 8524 * @protected 8525 */ 8526 _initTemplate: function (formats) { 8527 if (formats[this.calName]) { 8528 /** 8529 * @private 8530 * @type {{order:(string|{s:string,m:string,l:string,f:string}),date:Object.<string, (string|{s:string,m:string,l:string,f:string})>,time:Object.<string,(string|{s:string,m:string,l:string,f:string})>,range:Object.<string, (string|{s:string,m:string,l:string,f:string})>}} 8531 */ 8532 this.formats = formats[this.calName]; 8533 if (typeof(this.formats) === "string") { 8534 // alias to another calendar type 8535 this.formats = formats[this.formats]; 8536 } 8537 8538 this.template = ""; 8539 8540 switch (this.type) { 8541 case "datetime": 8542 this.template = (this.formats && this._getLengthFormat(this.formats.order, this.length)) || "{date} {time}"; 8543 this.template = this.template.replace("{date}", this._getFormat(this.formats.date, this.dateComponents, this.length) || ""); 8544 this.template = this.template.replace("{time}", this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length) || ""); 8545 break; 8546 case "date": 8547 this.template = this._getFormat(this.formats.date, this.dateComponents, this.length); 8548 break; 8549 case "time": 8550 this.template = this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length); 8551 break; 8552 } 8553 } else { 8554 throw "No formats available for calendar " + this.calName + " in locale " + this.locale.toString(); 8555 } 8556 }, 8557 8558 /** 8559 * @protected 8560 */ 8561 _massageTemplate: function () { 8562 var i; 8563 8564 if (this.clock && this.template) { 8565 // explicitly set the hours to the requested type 8566 var temp = ""; 8567 switch (this.clock) { 8568 case "24": 8569 for (i = 0; i < this.template.length; i++) { 8570 if (this.template.charAt(i) == "'") { 8571 temp += this.template.charAt(i++); 8572 while (i < this.template.length && this.template.charAt(i) !== "'") { 8573 temp += this.template.charAt(i++); 8574 } 8575 if (i < this.template.length) { 8576 temp += this.template.charAt(i); 8577 } 8578 } else if (this.template.charAt(i) == 'K') { 8579 temp += 'k'; 8580 } else if (this.template.charAt(i) == 'h') { 8581 temp += 'H'; 8582 } else { 8583 temp += this.template.charAt(i); 8584 } 8585 } 8586 this.template = temp; 8587 break; 8588 case "12": 8589 for (i = 0; i < this.template.length; i++) { 8590 if (this.template.charAt(i) == "'") { 8591 temp += this.template.charAt(i++); 8592 while (i < this.template.length && this.template.charAt(i) !== "'") { 8593 temp += this.template.charAt(i++); 8594 } 8595 if (i < this.template.length) { 8596 temp += this.template.charAt(i); 8597 } 8598 } else if (this.template.charAt(i) == 'k') { 8599 temp += 'K'; 8600 } else if (this.template.charAt(i) == 'H') { 8601 temp += 'h'; 8602 } else { 8603 temp += this.template.charAt(i); 8604 } 8605 } 8606 this.template = temp; 8607 break; 8608 } 8609 } 8610 8611 // tokenize it now for easy formatting 8612 this.templateArr = this._tokenize(this.template); 8613 8614 var digits; 8615 // set up the mapping to native or alternate digits if necessary 8616 if (typeof(this.useNative) === "boolean") { 8617 if (this.useNative) { 8618 digits = this.locinfo.getNativeDigits(); 8619 if (digits) { 8620 this.digits = digits; 8621 } 8622 } 8623 } else if (this.locinfo.getDigitsStyle() === "native") { 8624 digits = this.locinfo.getNativeDigits(); 8625 if (digits) { 8626 this.useNative = true; 8627 this.digits = digits; 8628 } 8629 } 8630 }, 8631 8632 /** 8633 * Convert the template into an array of date components separated by formatting chars. 8634 * @protected 8635 * @param {string} template Format template to tokenize into components 8636 * @return {Array.<string>} a tokenized array of date format components 8637 */ 8638 _tokenize: function (template) { 8639 var i = 0, start, ch, letter, arr = []; 8640 8641 // console.log("_tokenize: tokenizing template " + template); 8642 if (template) { 8643 while (i < template.length) { 8644 ch = template.charAt(i); 8645 start = i; 8646 if (ch === "'") { 8647 // console.log("found quoted string"); 8648 i++; 8649 // escaped string - push as-is, then dequote later 8650 while (i < template.length && template.charAt(i) !== "'") { 8651 i++; 8652 } 8653 if (i < template.length) { 8654 i++; // grab the other quote too 8655 } 8656 } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 8657 letter = template.charAt(i); 8658 // console.log("found letters " + letter); 8659 while (i < template.length && ch === letter) { 8660 ch = template.charAt(++i); 8661 } 8662 } else { 8663 // console.log("found other"); 8664 while (i < template.length && ch !== "'" && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { 8665 ch = template.charAt(++i); 8666 } 8667 } 8668 arr.push(template.substring(start,i)); 8669 // console.log("start is " + start + " i is " + i + " and substr is " + template.substring(start,i)); 8670 } 8671 } 8672 return arr; 8673 }, 8674 8675 /** 8676 * @protected 8677 * @param {Object.<string, (string|{s:string,m:string,l:string,f:string})>} obj Object to search 8678 * @param {string} components Format components to search 8679 * @param {string} length Length of the requested format 8680 * @return {string|undefined} the requested format 8681 */ 8682 _getFormat: function getFormat(obj, components, length) { 8683 if (typeof(components) !== 'undefined' && obj && obj[components]) { 8684 return this._getLengthFormat(obj[components], length); 8685 } 8686 return undefined; 8687 }, 8688 8689 /** 8690 * @protected 8691 * @param {(string|{s:string,m:string,l:string,f:string})} obj Object to search 8692 * @param {string} length Length of the requested format 8693 * @return {(string|undefined)} the requested format 8694 */ 8695 _getLengthFormat: function getLengthFormat(obj, length) { 8696 if (typeof(obj) === 'string') { 8697 return obj; 8698 } else if (obj[length]) { 8699 return obj[length]; 8700 } 8701 return undefined; 8702 }, 8703 8704 /** 8705 * Return the locale used with this formatter instance. 8706 * @return {Locale} the Locale instance for this formatter 8707 */ 8708 getLocale: function() { 8709 return this.locale; 8710 }, 8711 8712 /** 8713 * Return the template string that is used to format date/times for this 8714 * formatter instance. This will work, even when the template property is not explicitly 8715 * given in the options to the constructor. Without the template option, the constructor 8716 * will build the appropriate template according to the options and use that template 8717 * in the format method. 8718 * 8719 * @return {string} the format template for this formatter 8720 */ 8721 getTemplate: function() { 8722 return this.template; 8723 }, 8724 8725 /** 8726 * Return the type of this formatter. The type is a string that has one of the following 8727 * values: "time", "date", "datetime". 8728 * @return {string} the type of the formatter 8729 */ 8730 getType: function() { 8731 return this.type; 8732 }, 8733 8734 /** 8735 * Return the name of the calendar used to format date/times for this 8736 * formatter instance. 8737 * @return {string} the name of the calendar used by this formatter 8738 */ 8739 getCalendar: function () { 8740 return this.cal.getType(); 8741 }, 8742 8743 /** 8744 * Return the length used to format date/times in this formatter. This is either the 8745 * value of the length option to the constructor, or the default value. 8746 * 8747 * @return {string} the length of formats this formatter returns 8748 */ 8749 getLength: function () { 8750 return DateFmt.lenmap[this.length] || ""; 8751 }, 8752 8753 /** 8754 * Return the date components that this formatter formats. This is either the 8755 * value of the date option to the constructor, or the default value. If this 8756 * formatter is a time-only formatter, this method will return the empty 8757 * string. The date component letters may be specified in any order in the 8758 * constructor, but this method will reorder the given components to a standard 8759 * order. 8760 * 8761 * @return {string} the date components that this formatter formats 8762 */ 8763 getDateComponents: function () { 8764 return this.dateComponents || ""; 8765 }, 8766 8767 /** 8768 * Return the time components that this formatter formats. This is either the 8769 * value of the time option to the constructor, or the default value. If this 8770 * formatter is a date-only formatter, this method will return the empty 8771 * string. The time component letters may be specified in any order in the 8772 * constructor, but this method will reorder the given components to a standard 8773 * order. 8774 * 8775 * @return {string} the time components that this formatter formats 8776 */ 8777 getTimeComponents: function () { 8778 return this.timeComponents || ""; 8779 }, 8780 8781 /** 8782 * Return the time zone used to format date/times for this formatter 8783 * instance. 8784 * @return a string naming the time zone 8785 */ 8786 getTimeZone: function () { 8787 // Lazy load the time zone. If it wasn't explicitly set up before, set 8788 // it up now, but use the 8789 // default TZ for the locale. This way, if the caller never uses the 8790 // time zone in their format, we never have to load up a TimeZone 8791 // instance into this formatter. 8792 if (!this.tz) { 8793 this.tz = new TimeZone({id: ilib.getTimeZone()}); 8794 } 8795 return this.tz; 8796 }, 8797 /** 8798 * Return the clock option set in the constructor. If the clock option was 8799 * not given, the default from the locale is returned instead. 8800 * @return {string} "12" or "24" depending on whether this formatter uses 8801 * the 12-hour or 24-hour clock 8802 */ 8803 getClock: function () { 8804 return this.clock || this.locinfo.getClock(); 8805 }, 8806 /** 8807 * Return the meridiems range in current locale. 8808 * @return {Array.<{name:string,start:string,end:string}>} the range of available meridiems 8809 */ 8810 getMeridiemsRange: function () { 8811 var result; 8812 var _getSysString = function (key) { 8813 return (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)).toString(); 8814 }; 8815 8816 switch (this.meridiems) { 8817 case "chinese": 8818 result = [ 8819 { 8820 name: _getSysString.call(this, "azh0"), 8821 start: "00:00", 8822 end: "05:59" 8823 }, 8824 { 8825 name: _getSysString.call(this, "azh1"), 8826 start: "06:00", 8827 end: "08:59" 8828 }, 8829 { 8830 name: _getSysString.call(this, "azh2"), 8831 start: "09:00", 8832 end: "11:59" 8833 }, 8834 { 8835 name: _getSysString.call(this, "azh3"), 8836 start: "12:00", 8837 end: "12:59" 8838 }, 8839 { 8840 name: _getSysString.call(this, "azh4"), 8841 start: "13:00", 8842 end: "17:59" 8843 }, 8844 { 8845 name: _getSysString.call(this, "azh5"), 8846 start: "18:00", 8847 end: "20:59" 8848 }, 8849 { 8850 name: _getSysString.call(this, "azh6"), 8851 start: "21:00", 8852 end: "23:59" 8853 } 8854 ]; 8855 break; 8856 case "ethiopic": 8857 result = [ 8858 { 8859 name: _getSysString.call(this, "a0-ethiopic"), 8860 start: "00:00", 8861 end: "05:59" 8862 }, 8863 { 8864 name: _getSysString.call(this, "a1-ethiopic"), 8865 start: "06:00", 8866 end: "06:00" 8867 }, 8868 { 8869 name: _getSysString.call(this, "a2-ethiopic"), 8870 start: "06:01", 8871 end: "11:59" 8872 }, 8873 { 8874 name: _getSysString.call(this, "a3-ethiopic"), 8875 start: "12:00", 8876 end: "17:59" 8877 }, 8878 { 8879 name: _getSysString.call(this, "a4-ethiopic"), 8880 start: "18:00", 8881 end: "23:59" 8882 } 8883 ]; 8884 break; 8885 default: 8886 result = [ 8887 { 8888 name: _getSysString.call(this, "a0"), 8889 start: "00:00", 8890 end: "11:59" 8891 }, 8892 { 8893 name: _getSysString.call(this, "a1"), 8894 start: "12:00", 8895 end: "23:59" 8896 } 8897 ]; 8898 break; 8899 } 8900 8901 return result; 8902 }, 8903 8904 /** 8905 * @private 8906 */ 8907 _getTemplate: function (prefix, calendar) { 8908 if (calendar !== "gregorian") { 8909 return prefix + "-" + calendar; 8910 } 8911 return prefix; 8912 }, 8913 8914 /** 8915 * Returns an array of the months of the year, formatted to the optional length specified. 8916 * i.e. ...getMonthsOfYear() OR ...getMonthsOfYear({length: "short"}) 8917 * <p> 8918 * The options parameter may contain any of the following properties: 8919 * 8920 * <ul> 8921 * <li><i>length</i> - length of the names of the months being sought. This may be one of 8922 * "short", "medium", "long", or "full" 8923 * <li><i>date</i> - retrieve the names of the months in the date of the given date 8924 * <li><i>year</i> - retrieve the names of the months in the given year. In some calendars, 8925 * the months have different names depending if that year is a leap year or not. 8926 * </ul> 8927 * 8928 * @param {Object=} options an object-literal that contains any of the above properties 8929 * @return {Array} an array of the names of all of the months of the year in the current calendar 8930 */ 8931 getMonthsOfYear: function(options) { 8932 var length = (options && options.length) || this.getLength(), 8933 template = DateFmt.monthNameLenMap[length], 8934 months = [undefined], 8935 date, 8936 monthCount; 8937 8938 if (options) { 8939 if (options.date) { 8940 date = DateFactory._dateToIlib(options.date); 8941 } 8942 8943 if (options.year) { 8944 date = DateFactory({year: options.year, month: 1, day: 1, type: this.cal.getType()}); 8945 } 8946 } 8947 8948 if (!date) { 8949 date = DateFactory({ 8950 calendar: this.cal.getType() 8951 }); 8952 } 8953 8954 monthCount = this.cal.getNumMonths(date.getYears()); 8955 for (var i = 1; i <= monthCount; i++) { 8956 months[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 8957 } 8958 return months; 8959 }, 8960 8961 /** 8962 * Returns an array of the days of the week, formatted to the optional length specified. 8963 * i.e. ...getDaysOfWeek() OR ...getDaysOfWeek({length: "short"}) 8964 * <p> 8965 * The options parameter may contain any of the following properties: 8966 * 8967 * <ul> 8968 * <li><i>length</i> - length of the names of the months being sought. This may be one of 8969 * "short", "medium", "long", or "full" 8970 * </ul> 8971 * @param {Object=} options an object-literal that contains one key 8972 * "length" with the standard length strings 8973 * @return {Array} an array of all of the names of the days of the week 8974 */ 8975 getDaysOfWeek: function(options) { 8976 var length = (options && options.length) || this.getLength(), 8977 template = DateFmt.weekDayLenMap[length], 8978 days = []; 8979 for (var i = 0; i < 7; i++) { 8980 days[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 8981 } 8982 return days; 8983 }, 8984 8985 8986 /** 8987 * Convert this formatter to a string representation by returning the 8988 * format template. This method delegates to getTemplate. 8989 * 8990 * @return {string} the format template 8991 */ 8992 toString: function() { 8993 return this.getTemplate(); 8994 }, 8995 8996 /* 8997 * @private 8998 * Left pad the str to the given length of digits with zeros 8999 * @param {string} str the string to pad 9000 * @param {number} length the desired total length of the output string, padded 9001 */ 9002 _pad: function (str, length) { 9003 if (typeof(str) !== 'string') { 9004 str = "" + str; 9005 } 9006 var start = 0; 9007 if (str.charAt(0) === '-') { 9008 start++; 9009 } 9010 return (str.length >= length+start) ? str : str.substring(0, start) + DateFmt.zeros.substring(0,length-str.length+start) + str.substring(start); 9011 }, 9012 9013 /* 9014 * @private 9015 * Format a date according to a sequence of components. 9016 * @param {IDate} date a date/time object to format 9017 * @param {Array.<string>} templateArr an array of components to format 9018 * @return {string} the formatted date 9019 */ 9020 _formatTemplate: function (date, templateArr) { 9021 var i, key, temp, tz, str = ""; 9022 for (i = 0; i < templateArr.length; i++) { 9023 switch (templateArr[i]) { 9024 case 'd': 9025 str += (date.day || 1); 9026 break; 9027 case 'dd': 9028 str += this._pad(date.day || "1", 2); 9029 break; 9030 case 'yy': 9031 temp = "" + ((date.year || 0) % 100); 9032 str += this._pad(temp, 2); 9033 break; 9034 case 'yyyy': 9035 str += this._pad(date.year || "0", 4); 9036 break; 9037 case 'M': 9038 str += (date.month || 1); 9039 break; 9040 case 'MM': 9041 str += this._pad(date.month || "1", 2); 9042 break; 9043 case 'h': 9044 temp = (date.hour || 0) % 12; 9045 if (temp == 0) { 9046 temp = "12"; 9047 } 9048 str += temp; 9049 break; 9050 case 'hh': 9051 temp = (date.hour || 0) % 12; 9052 if (temp == 0) { 9053 temp = "12"; 9054 } 9055 str += this._pad(temp, 2); 9056 break; 9057 /* 9058 case 'j': 9059 temp = (date.hour || 0) % 12 + 1; 9060 str += temp; 9061 break; 9062 case 'jj': 9063 temp = (date.hour || 0) % 12 + 1; 9064 str += this._pad(temp, 2); 9065 break; 9066 */ 9067 case 'K': 9068 temp = (date.hour || 0) % 12; 9069 str += temp; 9070 break; 9071 case 'KK': 9072 temp = (date.hour || 0) % 12; 9073 str += this._pad(temp, 2); 9074 break; 9075 9076 case 'H': 9077 str += (date.hour || "0"); 9078 break; 9079 case 'HH': 9080 str += this._pad(date.hour || "0", 2); 9081 break; 9082 case 'k': 9083 str += (date.hour == 0 ? "24" : date.hour); 9084 break; 9085 case 'kk': 9086 temp = (date.hour == 0 ? "24" : date.hour); 9087 str += this._pad(temp, 2); 9088 break; 9089 9090 case 'm': 9091 str += (date.minute || "0"); 9092 break; 9093 case 'mm': 9094 str += this._pad(date.minute || "0", 2); 9095 break; 9096 case 's': 9097 str += (date.minute || "0"); 9098 break; 9099 case 'ss': 9100 str += this._pad(date.second || "0", 2); 9101 break; 9102 case 'S': 9103 str += (date.millisecond || "0"); 9104 break; 9105 case 'SSS': 9106 str += this._pad(date.millisecond || "0", 3); 9107 break; 9108 9109 case 'N': 9110 case 'NN': 9111 case 'MMM': 9112 case 'MMMM': 9113 key = templateArr[i] + (date.month || 1); 9114 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9115 break; 9116 9117 case 'E': 9118 case 'EE': 9119 case 'EEE': 9120 case 'EEEE': 9121 key = templateArr[i] + date.getDayOfWeek(); 9122 //console.log("finding " + key + " in the resources"); 9123 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9124 break; 9125 9126 case 'a': 9127 switch (this.meridiems) { 9128 case "chinese": 9129 if (date.hour < 6) { 9130 key = "azh0"; // before dawn 9131 } else if (date.hour < 9) { 9132 key = "azh1"; // morning 9133 } else if (date.hour < 12) { 9134 key = "azh2"; // late morning/day before noon 9135 } else if (date.hour < 13) { 9136 key = "azh3"; // noon hour/midday 9137 } else if (date.hour < 18) { 9138 key = "azh4"; // afternoon 9139 } else if (date.hour < 21) { 9140 key = "azh5"; // evening time/dusk 9141 } else { 9142 key = "azh6"; // night time 9143 } 9144 break; 9145 case "ethiopic": 9146 if (date.hour < 6) { 9147 key = "a0-ethiopic"; // morning 9148 } else if (date.hour === 6 && date.minute === 0) { 9149 key = "a1-ethiopic"; // noon 9150 } else if (date.hour >= 6 && date.hour < 12) { 9151 key = "a2-ethiopic"; // afternoon 9152 } else if (date.hour >= 12 && date.hour < 18) { 9153 key = "a3-ethiopic"; // evening 9154 } else if (date.hour >= 18) { 9155 key = "a4-ethiopic"; // night 9156 } 9157 break; 9158 default: 9159 key = date.hour < 12 ? "a0" : "a1"; 9160 break; 9161 } 9162 //console.log("finding " + key + " in the resources"); 9163 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9164 break; 9165 9166 case 'w': 9167 str += date.getWeekOfYear(); 9168 break; 9169 case 'ww': 9170 str += this._pad(date.getWeekOfYear(), 2); 9171 break; 9172 9173 case 'D': 9174 str += date.getDayOfYear(); 9175 break; 9176 case 'DD': 9177 str += this._pad(date.getDayOfYear(), 2); 9178 break; 9179 case 'DDD': 9180 str += this._pad(date.getDayOfYear(), 3); 9181 break; 9182 case 'W': 9183 str += date.getWeekOfMonth(this.locale); 9184 break; 9185 9186 case 'G': 9187 key = "G" + date.getEra(); 9188 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9189 break; 9190 9191 case 'O': 9192 temp = this.sysres.getString("1#1st|2#2nd|3#3rd|21#21st|22#22nd|23#23rd|31#31st|#{num}th", "ordinalChoice"); 9193 str += temp.formatChoice(date.day, {num: date.day}); 9194 break; 9195 9196 case 'z': // general time zone 9197 tz = this.getTimeZone(); // lazy-load the tz 9198 str += tz.getDisplayName(date, "standard"); 9199 break; 9200 case 'Z': // RFC 822 time zone 9201 tz = this.getTimeZone(); // lazy-load the tz 9202 str += tz.getDisplayName(date, "rfc822"); 9203 break; 9204 9205 default: 9206 str += templateArr[i].replace(/'/g, ""); 9207 break; 9208 } 9209 } 9210 9211 if (this.digits) { 9212 str = JSUtils.mapString(str, this.digits); 9213 } 9214 return str; 9215 }, 9216 9217 /** 9218 * Format a particular date instance according to the settings of this 9219 * formatter object. The type of the date instance being formatted must 9220 * correspond exactly to the calendar type with which this formatter was 9221 * constructed. If the types are not compatible, this formatter will 9222 * produce bogus results. 9223 * 9224 * @param {IDate|Number|String|Date|JulianDay|null|undefined} dateLike a date-like object to format 9225 * @return {string} the formatted version of the given date instance 9226 */ 9227 format: function (dateLike) { 9228 var thisZoneName = this.tz && this.tz.getId() || "local"; 9229 9230 var date = DateFactory._dateToIlib(dateLike, thisZoneName, this.locale); 9231 9232 if (!date.getCalendar || !(date instanceof IDate)) { 9233 throw "Wrong date type passed to DateFmt.format()"; 9234 } 9235 9236 var dateZoneName = date.timezone || "local"; 9237 9238 // convert to the time zone of this formatter before formatting 9239 if (dateZoneName !== thisZoneName || date.getCalendar() !== this.calName) { 9240 // console.log("Differing time zones date: " + dateZoneName + " and fmt: " + thisZoneName + ". Converting..."); 9241 // this will recalculate the date components based on the new time zone 9242 // and/or convert a date in another calendar to the current calendar before formatting it 9243 var newDate = DateFactory({ 9244 type: this.calName, 9245 timezone: thisZoneName, 9246 julianday: date.getJulianDay() 9247 }); 9248 9249 date = newDate; 9250 } 9251 return this._formatTemplate(date, this.templateArr); 9252 }, 9253 9254 /** 9255 * Return a string that describes a date relative to the given 9256 * reference date. The string returned is text that for the locale that 9257 * was specified when the formatter instance was constructed.<p> 9258 * 9259 * The date can be in the future relative to the reference date or in 9260 * the past, and the formatter will generate the appropriate string.<p> 9261 * 9262 * The text used to describe the relative reference depends on the length 9263 * of time between the date and the reference. If the time was in the 9264 * past, it will use the "ago" phrase, and in the future, it will use 9265 * the "in" phrase. Examples:<p> 9266 * 9267 * <ul> 9268 * <li>within a minute: either "X seconds ago" or "in X seconds" 9269 * <li>within an hour: either "X minutes ago" or "in X minutes" 9270 * <li>within a day: either "X hours ago" or "in X hours" 9271 * <li>within 2 weeks: either "X days ago" or "in X days" 9272 * <li>within 12 weeks (~3 months): either "X weeks ago" or "in X weeks" 9273 * <li>within two years: either "X months ago" or "in X months" 9274 * <li>longer than 2 years: "X years ago" or "in X years" 9275 * </ul> 9276 * 9277 * @param {IDate|Number|String|Date|JulianDay|null|undefined} reference a date that the date parameter should be relative to 9278 * @param {IDate|Number|String|Date|JulianDay|null|undefined} date a date being formatted 9279 * @throws "Wrong calendar type" when the start or end dates are not the same 9280 * calendar type as the formatter itself 9281 * @return {string} the formatted relative date 9282 */ 9283 formatRelative: function(reference, date) { 9284 reference = DateFactory._dateToIlib(reference); 9285 date = DateFactory._dateToIlib(date); 9286 9287 var referenceRd, dateRd, fmt, time, diff, num; 9288 9289 if (typeof(reference) !== 'object' || !reference.getCalendar || reference.getCalendar() !== this.calName || 9290 typeof(date) !== 'object' || !date.getCalendar || date.getCalendar() !== this.calName) { 9291 throw "Wrong calendar type"; 9292 } 9293 9294 referenceRd = reference.getRataDie(); 9295 dateRd = date.getRataDie(); 9296 9297 if (dateRd < referenceRd) { 9298 diff = referenceRd - dateRd; 9299 fmt = this.sysres.getString("{duration} ago"); 9300 } else { 9301 diff = dateRd - referenceRd; 9302 fmt = this.sysres.getString("in {duration}"); 9303 } 9304 9305 if (diff < 0.000694444) { 9306 num = Math.round(diff * 86400); 9307 switch (this.length) { 9308 case 's': 9309 time = this.sysres.getString("#{num}s"); 9310 break; 9311 case 'm': 9312 time = this.sysres.getString("1#1 se|#{num} sec"); 9313 break; 9314 case 'l': 9315 time = this.sysres.getString("1#1 sec|#{num} sec"); 9316 break; 9317 default: 9318 case 'f': 9319 time = this.sysres.getString("1#1 second|#{num} seconds"); 9320 break; 9321 } 9322 } else if (diff < 0.041666667) { 9323 num = Math.round(diff * 1440); 9324 switch (this.length) { 9325 case 's': 9326 time = this.sysres.getString("#{num}m", "durationShortMinutes"); 9327 break; 9328 case 'm': 9329 time = this.sysres.getString("1#1 mi|#{num} min"); 9330 break; 9331 case 'l': 9332 time = this.sysres.getString("1#1 min|#{num} min"); 9333 break; 9334 default: 9335 case 'f': 9336 time = this.sysres.getString("1#1 minute|#{num} minutes"); 9337 break; 9338 } 9339 } else if (diff < 1) { 9340 num = Math.round(diff * 24); 9341 switch (this.length) { 9342 case 's': 9343 time = this.sysres.getString("#{num}h"); 9344 break; 9345 case 'm': 9346 time = this.sysres.getString("1#1 hr|#{num} hrs", "durationMediumHours"); 9347 break; 9348 case 'l': 9349 time = this.sysres.getString("1#1 hr|#{num} hrs"); 9350 break; 9351 default: 9352 case 'f': 9353 time = this.sysres.getString("1#1 hour|#{num} hours"); 9354 break; 9355 } 9356 } else if (diff < 14) { 9357 num = Math.round(diff); 9358 switch (this.length) { 9359 case 's': 9360 time = this.sysres.getString("#{num}d"); 9361 break; 9362 case 'm': 9363 time = this.sysres.getString("1#1 dy|#{num} dys"); 9364 break; 9365 case 'l': 9366 time = this.sysres.getString("1#1 day|#{num} days", "durationLongDays"); 9367 break; 9368 default: 9369 case 'f': 9370 time = this.sysres.getString("1#1 day|#{num} days"); 9371 break; 9372 } 9373 } else if (diff < 84) { 9374 num = Math.round(diff/7); 9375 switch (this.length) { 9376 case 's': 9377 time = this.sysres.getString("#{num}w"); 9378 break; 9379 case 'm': 9380 time = this.sysres.getString("1#1 wk|#{num} wks", "durationMediumWeeks"); 9381 break; 9382 case 'l': 9383 time = this.sysres.getString("1#1 wk|#{num} wks"); 9384 break; 9385 default: 9386 case 'f': 9387 time = this.sysres.getString("1#1 week|#{num} weeks"); 9388 break; 9389 } 9390 } else if (diff < 730) { 9391 num = Math.round(diff/30.4); 9392 switch (this.length) { 9393 case 's': 9394 time = this.sysres.getString("#{num}m", "durationShortMonths"); 9395 break; 9396 case 'm': 9397 time = this.sysres.getString("1#1 mo|#{num} mos"); 9398 break; 9399 case 'l': 9400 time = this.sysres.getString("1#1 mon|#{num} mons"); 9401 break; 9402 default: 9403 case 'f': 9404 time = this.sysres.getString("1#1 month|#{num} months"); 9405 break; 9406 } 9407 } else { 9408 num = Math.round(diff/365); 9409 switch (this.length) { 9410 case 's': 9411 time = this.sysres.getString("#{num}y"); 9412 break; 9413 case 'm': 9414 time = this.sysres.getString("1#1 yr|#{num} yrs", "durationMediumYears"); 9415 break; 9416 case 'l': 9417 time = this.sysres.getString("1#1 yr|#{num} yrs"); 9418 break; 9419 default: 9420 case 'f': 9421 time = this.sysres.getString("1#1 year|#{num} years"); 9422 break; 9423 } 9424 } 9425 return fmt.format({duration: time.formatChoice(num, {num: num})}); 9426 } 9427 }; 9428 9429 9430 9431 /*< DateRngFmt.js */ 9432 /* 9433 * DateFmt.js - Date formatter definition 9434 * 9435 * Copyright © 2012-2015, JEDLSoft 9436 * 9437 * Licensed under the Apache License, Version 2.0 (the "License"); 9438 * you may not use this file except in compliance with the License. 9439 * You may obtain a copy of the License at 9440 * 9441 * http://www.apache.org/licenses/LICENSE-2.0 9442 * 9443 * Unless required by applicable law or agreed to in writing, software 9444 * distributed under the License is distributed on an "AS IS" BASIS, 9445 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9446 * 9447 * See the License for the specific language governing permissions and 9448 * limitations under the License. 9449 */ 9450 9451 /* 9452 !depends 9453 ilib.js 9454 Locale.js 9455 IDate.js 9456 IString.js 9457 CalendarFactory.js 9458 LocaleInfo.js 9459 TimeZone.js 9460 DateFmt.js 9461 GregorianCal.js 9462 JSUtils.js 9463 Utils.js 9464 */ 9465 9466 // !data dateformats sysres 9467 9468 9469 9470 9471 9472 /** 9473 * @class 9474 * Create a new date range formatter instance. The date range formatter is immutable once 9475 * it is created, but can format as many different date ranges as needed with the same 9476 * options. Create different date range formatter instances for different purposes 9477 * and then keep them cached for use later if you have more than one range to 9478 * format.<p> 9479 * 9480 * The options may contain any of the following properties: 9481 * 9482 * <ul> 9483 * <li><i>locale</i> - locale to use when formatting the date/times in the range. If the 9484 * locale is not specified, then the default locale of the app or web page will be used. 9485 * 9486 * <li><i>calendar</i> - the type of calendar to use for this format. The value should 9487 * be a sting containing the name of the calendar. Currently, the supported 9488 * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the 9489 * calendar is not specified, then the default calendar for the locale is used. When the 9490 * calendar type is specified, then the format method must be called with an instance of 9491 * the appropriate date type. (eg. Gregorian calendar means that the format method must 9492 * be called with a GregDate instance.) 9493 * 9494 * <li><i>timezone</i> - time zone to use when formatting times. This may be a time zone 9495 * instance or a time zone specifier string in RFC 822 format. If not specified, the 9496 * default time zone for the locale is used. 9497 * 9498 * <li><i>length</i> - Specify the length of the format to use as a string. The length 9499 * is the approximate size of the formatted string. 9500 * 9501 * <ul> 9502 * <li><i>short</i> - use a short representation of the time. This is the most compact format possible for the locale. 9503 * <li><i>medium</i> - use a medium length representation of the time. This is a slightly longer format. 9504 * <li><i>long</i> - use a long representation of the time. This is a fully specified format, but some of the textual 9505 * components may still be abbreviated. (eg. "Tue" instead of "Tuesday") 9506 * <li><i>full</i> - use a full representation of the time. This is a fully specified format where all the textual 9507 * components are spelled out completely. 9508 * </ul> 9509 * 9510 * eg. The "short" format for an en_US range may be "MM/yy - MM/yy", whereas the long format might be 9511 * "MMM, yyyy - MMM, yyyy". In the long format, the month name is textual instead of numeric 9512 * and is longer, the year is 4 digits instead of 2, and the format contains slightly more 9513 * spaces and formatting characters.<p> 9514 * 9515 * Note that the length parameter does not specify which components are to be formatted. The 9516 * components that are formatted depend on the length of time in the range. 9517 * 9518 * <li><i>clock</i> - specify that formatted times should use a 12 or 24 hour clock if the 9519 * format happens to include times. Valid values are "12" and "24".<p> 9520 * 9521 * In some locales, both clocks are used. For example, in en_US, the general populace uses 9522 * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or 9523 * scientific writing, it is more common to use a 24 hour clock. This property allows you to 9524 * construct a formatter that overrides the default for the locale.<p> 9525 * 9526 * If this property is not specified, the default is to use the most widely used convention 9527 * for the locale. 9528 * <li>onLoad - a callback function to call when the date range format object is fully 9529 * loaded. When the onLoad option is given, the DateRngFmt object will attempt to 9530 * load any missing locale data using the ilib loader callback. 9531 * When the constructor is done (even if the data is already preassembled), the 9532 * onLoad function is called with the current instance as a parameter, so this 9533 * callback can be used with preassembled or dynamic loading or a mix of the two. 9534 * 9535 * <li>sync - tell whether to load any missing locale data synchronously or 9536 * asynchronously. If this option is given as "false", then the "onLoad" 9537 * callback must be given, as the instance returned from this constructor will 9538 * not be usable for a while. 9539 * 9540 * <li><i>loadParams</i> - an object containing parameters to pass to the 9541 * loader callback function when locale data is missing. The parameters are not 9542 * interpretted or modified in any way. They are simply passed along. The object 9543 * may contain any property/value pairs as long as the calling code is in 9544 * agreement with the loader callback function as to what those parameters mean. 9545 * </ul> 9546 * <p> 9547 * 9548 * 9549 * @constructor 9550 * @param {Object} options options governing the way this date range formatter instance works 9551 */ 9552 var DateRngFmt = function(options) { 9553 var sync = true; 9554 var loadParams = undefined; 9555 this.locale = new Locale(); 9556 this.length = "s"; 9557 9558 if (options) { 9559 if (options.locale) { 9560 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 9561 } 9562 9563 if (options.calendar) { 9564 this.calName = options.calendar; 9565 } 9566 9567 if (options.length) { 9568 if (options.length === 'short' || 9569 options.length === 'medium' || 9570 options.length === 'long' || 9571 options.length === 'full') { 9572 // only use the first char to save space in the json files 9573 this.length = options.length.charAt(0); 9574 } 9575 } 9576 if (typeof(options.sync) !== 'undefined') { 9577 sync = (options.sync == true); 9578 } 9579 9580 loadParams = options.loadParams; 9581 } 9582 9583 var opts = {}; 9584 JSUtils.shallowCopy(options, opts); 9585 opts.sync = sync; 9586 opts.loadParams = loadParams; 9587 9588 /** 9589 * @private 9590 */ 9591 opts.onLoad = ilib.bind(this, function (fmt) { 9592 this.dateFmt = fmt; 9593 if (fmt) { 9594 this.locinfo = this.dateFmt.locinfo; 9595 9596 // get the default calendar name from the locale, and if the locale doesn't define 9597 // one, use the hard-coded gregorian as the last resort 9598 this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; 9599 this.cal = CalendarFactory({ 9600 type: this.calName 9601 }); 9602 if (!this.cal) { 9603 this.cal = new GregorianCal(); 9604 } 9605 9606 this.timeTemplate = this.dateFmt._getFormat(this.dateFmt.formats.time[this.dateFmt.clock], this.dateFmt.timeComponents, this.length) || "hh:mm"; 9607 this.timeTemplateArr = this.dateFmt._tokenize(this.timeTemplate); 9608 9609 if (options && typeof(options.onLoad) === 'function') { 9610 options.onLoad(this); 9611 } 9612 } 9613 }); 9614 9615 // delegate a bunch of the formatting to this formatter 9616 new DateFmt(opts); 9617 }; 9618 9619 DateRngFmt.prototype = { 9620 /** 9621 * Return the locale used with this formatter instance. 9622 * @return {Locale} the Locale instance for this formatter 9623 */ 9624 getLocale: function() { 9625 return this.locale; 9626 }, 9627 9628 /** 9629 * Return the name of the calendar used to format date/times for this 9630 * formatter instance. 9631 * @return {string} the name of the calendar used by this formatter 9632 */ 9633 getCalendar: function () { 9634 return this.dateFmt.getCalendar(); 9635 }, 9636 9637 /** 9638 * Return the length used to format date/times in this formatter. This is either the 9639 * value of the length option to the constructor, or the default value. 9640 * 9641 * @return {string} the length of formats this formatter returns 9642 */ 9643 getLength: function () { 9644 return DateFmt.lenmap[this.length] || ""; 9645 }, 9646 9647 /** 9648 * Return the time zone used to format date/times for this formatter 9649 * instance. 9650 * @return {TimeZone} a string naming the time zone 9651 */ 9652 getTimeZone: function () { 9653 return this.dateFmt.getTimeZone(); 9654 }, 9655 9656 /** 9657 * Return the clock option set in the constructor. If the clock option was 9658 * not given, the default from the locale is returned instead. 9659 * @return {string} "12" or "24" depending on whether this formatter uses 9660 * the 12-hour or 24-hour clock 9661 */ 9662 getClock: function () { 9663 return this.dateFmt.getClock(); 9664 }, 9665 9666 /** 9667 * Format a date/time range according to the settings of the current 9668 * formatter. The range is specified as being from the "start" date until 9669 * the "end" date. <p> 9670 * 9671 * The template that the date/time range uses depends on the 9672 * length of time between the dates, on the premise that a long date range 9673 * which is too specific is not useful. For example, when giving 9674 * the dates of the 100 Years War, in most situations it would be more 9675 * appropriate to format the range as "1337 - 1453" than to format it as 9676 * "10:37am November 9, 1337 - 4:37pm July 17, 1453", as the latter format 9677 * is much too specific given the length of time that the range represents. 9678 * If a very specific, but long, date range really is needed, the caller 9679 * should format two specific dates separately and put them 9680 * together as you might with other normal strings.<p> 9681 * 9682 * The format used for a date range contains the following date components, 9683 * where the order of those components is rearranged and the component values 9684 * are translated according to each locale: 9685 * 9686 * <ul> 9687 * <li>within 3 days: the times of day, dates, months, and years 9688 * <li>within 730 days (2 years): the dates, months, and years 9689 * <li>within 3650 days (10 years): the months and years 9690 * <li>longer than 10 years: the years only 9691 * </ul> 9692 * 9693 * In general, if any of the date components share a value between the 9694 * start and end date, that component is only given once. For example, 9695 * if the range is from November 15, 2011 to November 26, 2011, the 9696 * start and end dates both share the same month and year. The 9697 * range would then be formatted as "November 15-26, 2011". <p> 9698 * 9699 * If you want to format a length of time instead of a particular range of 9700 * time (for example, the length of an event rather than the specific start time 9701 * and end time of that event), then use a duration formatter instance 9702 * (DurationFmt) instead. The formatRange method will make sure that each component 9703 * of the date/time is within the normal range for that component. For example, 9704 * the minutes will always be between 0 and 59, no matter what is specified in 9705 * the date to format, because that is the normal range for minutes. A duration 9706 * format will allow the number of minutes to exceed 59. For example, if you 9707 * were displaying the length of a movie that is 198 minutes long, the minutes 9708 * component of a duration could be 198.<p> 9709 * 9710 * @param {IDate} start the starting date/time of the range. This must be of 9711 * the same calendar type as the formatter itself. 9712 * @param {IDate} end the ending date/time of the range. This must be of the 9713 * same calendar type as the formatter itself. 9714 * @throws "Wrong calendar type" when the start or end dates are not the same 9715 * calendar type as the formatter itself 9716 * @return {string} a date range formatted for the locale 9717 */ 9718 format: function (start, end) { 9719 var startRd, endRd, fmt = "", yearTemplate, monthTemplate, dayTemplate; 9720 9721 if (typeof(start) !== 'object' || !start.getCalendar || start.getCalendar() !== this.calName || 9722 typeof(end) !== 'object' || !end.getCalendar || end.getCalendar() !== this.calName) { 9723 throw "Wrong calendar type"; 9724 } 9725 9726 startRd = start.getRataDie(); 9727 endRd = end.getRataDie(); 9728 9729 // 9730 // legend: 9731 // c00 - difference is less than 3 days. Year, month, and date are same, but time is different 9732 // c01 - difference is less than 3 days. Year and month are same but date and time are different 9733 // c02 - difference is less than 3 days. Year is same but month, date, and time are different. (ie. it straddles a month boundary) 9734 // c03 - difference is less than 3 days. Year, month, date, and time are all different. (ie. it straddles a year boundary) 9735 // c10 - difference is less than 2 years. Year and month are the same, but date is different. 9736 // c11 - difference is less than 2 years. Year is the same, but month, date, and time are different. 9737 // c12 - difference is less than 2 years. All fields are different. (ie. straddles a year boundary) 9738 // c20 - difference is less than 10 years. All fields are different. 9739 // c30 - difference is more than 10 years. All fields are different. 9740 // 9741 9742 if (endRd - startRd < 3) { 9743 if (start.year === end.year) { 9744 if (start.month === end.month) { 9745 if (start.day === end.day) { 9746 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c00", this.length)); 9747 } else { 9748 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c01", this.length)); 9749 } 9750 } else { 9751 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c02", this.length)); 9752 } 9753 } else { 9754 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c03", this.length)); 9755 } 9756 } else if (endRd - startRd < 730) { 9757 if (start.year === end.year) { 9758 if (start.month === end.month) { 9759 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c10", this.length)); 9760 } else { 9761 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c11", this.length)); 9762 } 9763 } else { 9764 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c12", this.length)); 9765 } 9766 } else if (endRd - startRd < 3650) { 9767 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c20", this.length)); 9768 } else { 9769 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c30", this.length)); 9770 } 9771 9772 yearTemplate = this.dateFmt._tokenize(this.dateFmt._getFormat(this.dateFmt.formats.date, "y", this.length) || "yyyy"); 9773 monthTemplate = this.dateFmt._tokenize(this.dateFmt._getFormat(this.dateFmt.formats.date, "m", this.length) || "MM"); 9774 dayTemplate = this.dateFmt._tokenize(this.dateFmt._getFormat(this.dateFmt.formats.date, "d", this.length) || "dd"); 9775 9776 /* 9777 console.log("fmt is " + fmt.toString()); 9778 console.log("year template is " + yearTemplate); 9779 console.log("month template is " + monthTemplate); 9780 console.log("day template is " + dayTemplate); 9781 */ 9782 9783 return fmt.format({ 9784 sy: this.dateFmt._formatTemplate(start, yearTemplate), 9785 sm: this.dateFmt._formatTemplate(start, monthTemplate), 9786 sd: this.dateFmt._formatTemplate(start, dayTemplate), 9787 st: this.dateFmt._formatTemplate(start, this.timeTemplateArr), 9788 ey: this.dateFmt._formatTemplate(end, yearTemplate), 9789 em: this.dateFmt._formatTemplate(end, monthTemplate), 9790 ed: this.dateFmt._formatTemplate(end, dayTemplate), 9791 et: this.dateFmt._formatTemplate(end, this.timeTemplateArr) 9792 }); 9793 } 9794 }; 9795 9796 9797 /*< HebrewCal.js */ 9798 /* 9799 * hebrew.js - Represent a Hebrew calendar object. 9800 * 9801 * Copyright © 2012-2015, JEDLSoft 9802 * 9803 * Licensed under the Apache License, Version 2.0 (the "License"); 9804 * you may not use this file except in compliance with the License. 9805 * You may obtain a copy of the License at 9806 * 9807 * http://www.apache.org/licenses/LICENSE-2.0 9808 * 9809 * Unless required by applicable law or agreed to in writing, software 9810 * distributed under the License is distributed on an "AS IS" BASIS, 9811 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9812 * 9813 * See the License for the specific language governing permissions and 9814 * limitations under the License. 9815 */ 9816 9817 9818 /* !depends ilib.js Calendar.js MathUtils.js */ 9819 9820 9821 /** 9822 * @class 9823 * Construct a new Hebrew calendar object. This class encodes information about 9824 * the Hebrew (Jewish) calendar. The Hebrew calendar is a tabular hebrew 9825 * calendar where the dates are calculated by arithmetic rules. This differs from 9826 * the religious Hebrew calendar which is used to mark the beginning of particular 9827 * holidays. The religious calendar depends on the first sighting of the new 9828 * crescent moon to determine the first day of the new month. Because humans and 9829 * weather are both involved, the actual time of sighting varies, so it is not 9830 * really possible to precalculate the religious calendar. Certain groups, such 9831 * as the Hebrew Society of North America, decreed in in 2007 that they will use 9832 * a calendar based on calculations rather than observations to determine the 9833 * beginning of lunar months, and therefore the dates of holidays.<p> 9834 * 9835 * 9836 * @constructor 9837 * @extends Calendar 9838 */ 9839 var HebrewCal = function() { 9840 this.type = "hebrew"; 9841 }; 9842 9843 /** 9844 * Return the number of days elapsed in the Hebrew calendar before the 9845 * given year starts. 9846 * @private 9847 * @param {number} year the year for which the number of days is sought 9848 * @return {number} the number of days elapsed in the Hebrew calendar before the 9849 * given year starts 9850 */ 9851 HebrewCal.elapsedDays = function(year) { 9852 var months = Math.floor(((235*year) - 234)/19); 9853 var parts = 204 + 793 * MathUtils.mod(months, 1080); 9854 var hours = 11 + 12 * months + 793 * Math.floor(months/1080) + 9855 Math.floor(parts/1080); 9856 var days = 29 * months + Math.floor(hours/24); 9857 return (MathUtils.mod(3 * (days + 1), 7) < 3) ? days + 1 : days; 9858 }; 9859 9860 /** 9861 * Return the number of days that the New Year's (Rosh HaShanah) in the Hebrew 9862 * calendar will be corrected for the given year. Corrections are caused because New 9863 * Year's is not allowed to start on certain days of the week. To deal with 9864 * it, the start of the new year is corrected for the next year by adding a 9865 * day to the 8th month (Heshvan) and/or the 9th month (Kislev) in the current 9866 * year to make them 30 days long instead of 29. 9867 * 9868 * @private 9869 * @param {number} year the year for which the correction is sought 9870 * @param {number} elapsed number of days elapsed up to this year 9871 * @return {number} the number of days correction in the current year to make sure 9872 * Rosh HaShanah does not fall on undesirable days of the week 9873 */ 9874 HebrewCal.newYearsCorrection = function(year, elapsed) { 9875 var lastYear = HebrewCal.elapsedDays(year-1), 9876 thisYear = elapsed, 9877 nextYear = HebrewCal.elapsedDays(year+1); 9878 9879 return (nextYear - thisYear) == 356 ? 2 : ((thisYear - lastYear) == 382 ? 1 : 0); 9880 }; 9881 9882 /** 9883 * Return the rata die date of the new year for the given hebrew year. 9884 * @private 9885 * @param {number} year the year for which the new year is needed 9886 * @return {number} the rata die date of the new year 9887 */ 9888 HebrewCal.newYear = function(year) { 9889 var elapsed = HebrewCal.elapsedDays(year); 9890 9891 return elapsed + HebrewCal.newYearsCorrection(year, elapsed); 9892 }; 9893 9894 /** 9895 * Return the number of days in the given year. Years contain a variable number of 9896 * days because the date of Rosh HaShanah (New Year's) changes so that it doesn't 9897 * fall on particular days of the week. Days are added to the months of Heshvan 9898 * and/or Kislev in the previous year in order to prevent the current year's New 9899 * Year from being on Sunday, Wednesday, or Friday. 9900 * 9901 * @param {number} year the year for which the length is sought 9902 * @return {number} number of days in the given year 9903 */ 9904 HebrewCal.daysInYear = function(year) { 9905 return HebrewCal.newYear(year+1) - HebrewCal.newYear(year); 9906 }; 9907 9908 /** 9909 * Return true if the given year contains a long month of Heshvan. That is, 9910 * it is 30 days instead of 29. 9911 * 9912 * @private 9913 * @param {number} year the year in which that month is questioned 9914 * @return {boolean} true if the given year contains a long month of Heshvan 9915 */ 9916 HebrewCal.longHeshvan = function(year) { 9917 return MathUtils.mod(HebrewCal.daysInYear(year), 10) === 5; 9918 }; 9919 9920 /** 9921 * Return true if the given year contains a long month of Kislev. That is, 9922 * it is 30 days instead of 29. 9923 * 9924 * @private 9925 * @param {number} year the year in which that month is questioned 9926 * @return {boolean} true if the given year contains a short month of Kislev 9927 */ 9928 HebrewCal.longKislev = function(year) { 9929 return MathUtils.mod(HebrewCal.daysInYear(year), 10) !== 3; 9930 }; 9931 9932 /** 9933 * Return the date of the last day of the month for the given year. The date of 9934 * the last day of the month is variable because a number of months gain an extra 9935 * day in leap years, and it is variable which months gain a day for each leap 9936 * year and which do not. 9937 * 9938 * @param {number} month the month for which the number of days is sought 9939 * @param {number} year the year in which that month is 9940 * @return {number} the number of days in the given month and year 9941 */ 9942 HebrewCal.prototype.lastDayOfMonth = function(month, year) { 9943 switch (month) { 9944 case 2: 9945 case 4: 9946 case 6: 9947 case 10: 9948 return 29; 9949 case 13: 9950 return this.isLeapYear(year) ? 29 : 0; 9951 case 8: 9952 return HebrewCal.longHeshvan(year) ? 30 : 29; 9953 case 9: 9954 return HebrewCal.longKislev(year) ? 30 : 29; 9955 case 12: 9956 case 1: 9957 case 3: 9958 case 5: 9959 case 7: 9960 case 11: 9961 return 30; 9962 default: 9963 return 0; 9964 } 9965 }; 9966 9967 /** 9968 * Return the number of months in the given year. The number of months in a year varies 9969 * for luni-solar calendars because in some years, an extra month is needed to extend the 9970 * days in a year to an entire solar year. The month is represented as a 1-based number 9971 * where 1=first month, 2=second month, etc. 9972 * 9973 * @param {number} year a year for which the number of months is sought 9974 */ 9975 HebrewCal.prototype.getNumMonths = function(year) { 9976 return this.isLeapYear(year) ? 13 : 12; 9977 }; 9978 9979 /** 9980 * Return the number of days in a particular month in a particular year. This function 9981 * can return a different number for a month depending on the year because of leap years. 9982 * 9983 * @param {number} month the month for which the length is sought 9984 * @param {number} year the year within which that month can be found 9985 * @returns {number} the number of days within the given month in the given year, or 9986 * 0 for an invalid month in the year 9987 */ 9988 HebrewCal.prototype.getMonLength = function(month, year) { 9989 if (month < 1 || month > 13 || (month == 13 && !this.isLeapYear(year))) { 9990 return 0; 9991 } 9992 return this.lastDayOfMonth(month, year); 9993 }; 9994 9995 /** 9996 * Return true if the given year is a leap year in the Hebrew calendar. 9997 * The year parameter may be given as a number, or as a HebrewDate object. 9998 * @param {number|Object} year the year for which the leap year information is being sought 9999 * @returns {boolean} true if the given year is a leap year 10000 */ 10001 HebrewCal.prototype.isLeapYear = function(year) { 10002 var y = (typeof(year) == 'number') ? year : year.year; 10003 return (MathUtils.mod(1 + 7 * y, 19) < 7); 10004 }; 10005 10006 /** 10007 * Return the type of this calendar. 10008 * 10009 * @returns {string} the name of the type of this calendar 10010 */ 10011 HebrewCal.prototype.getType = function() { 10012 return this.type; 10013 }; 10014 10015 /** 10016 * Return a date instance for this calendar type using the given 10017 * options. 10018 * @param {Object} options options controlling the construction of 10019 * the date instance 10020 * @returns {HebrewDate} a date appropriate for this calendar type 10021 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 10022 */ 10023 HebrewCal.prototype.newDateInstance = function (options) { 10024 return new HebrewDate(options); 10025 }; 10026 10027 /*register this calendar for the factory method */ 10028 Calendar._constructors["hebrew"] = HebrewCal; 10029 10030 10031 10032 /*< HebrewRataDie.js */ 10033 /* 10034 * HebrewRataDie.js - Represent an RD date in the Hebrew calendar 10035 * 10036 * Copyright © 2012-2015, JEDLSoft 10037 * 10038 * Licensed under the Apache License, Version 2.0 (the "License"); 10039 * you may not use this file except in compliance with the License. 10040 * You may obtain a copy of the License at 10041 * 10042 * http://www.apache.org/licenses/LICENSE-2.0 10043 * 10044 * Unless required by applicable law or agreed to in writing, software 10045 * distributed under the License is distributed on an "AS IS" BASIS, 10046 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10047 * 10048 * See the License for the specific language governing permissions and 10049 * limitations under the License. 10050 */ 10051 10052 /* !depends 10053 MathUtils.js 10054 HebrewCal.js 10055 RataDie.js 10056 */ 10057 10058 10059 /** 10060 * @class 10061 * Construct a new Hebrew RD date number object. The constructor parameters can 10062 * contain any of the following properties: 10063 * 10064 * <ul> 10065 * <li><i>unixtime<i> - sets the time of this instance according to the given 10066 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 10067 * 10068 * <li><i>julianday</i> - sets the time of this instance according to the given 10069 * Julian Day instance or the Julian Day given as a float 10070 * 10071 * <li><i>year</i> - any integer, including 0 10072 * 10073 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 10074 * 10075 * <li><i>day</i> - 1 to 31 10076 * 10077 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 10078 * is always done with an unambiguous 24 hour representation 10079 * 10080 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 10081 * the parts or specify the minutes, seconds, and milliseconds, but not both. 10082 * 10083 * <li><i>minute</i> - 0 to 59 10084 * 10085 * <li><i>second</i> - 0 to 59 10086 * 10087 * <li><i>millisecond</i> - 0 to 999 10088 * 10089 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 10090 * </ul> 10091 * 10092 * If the constructor is called with another Hebrew date instance instead of 10093 * a parameter block, the other instance acts as a parameter block and its 10094 * settings are copied into the current instance.<p> 10095 * 10096 * If the constructor is called with no arguments at all or if none of the 10097 * properties listed above are present, then the RD is calculate based on 10098 * the current date at the time of instantiation. <p> 10099 * 10100 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 10101 * specified in the params, it is assumed that they have the smallest possible 10102 * value in the range for the property (zero or one).<p> 10103 * 10104 * 10105 * @private 10106 * @constructor 10107 * @extends RataDie 10108 * @param {Object=} params parameters that govern the settings and behaviour of this Hebrew RD date 10109 */ 10110 var HebrewRataDie = function(params) { 10111 this.cal = params && params.cal || new HebrewCal(); 10112 this.rd = undefined; 10113 RataDie.call(this, params); 10114 }; 10115 10116 HebrewRataDie.prototype = new RataDie(); 10117 HebrewRataDie.prototype.parent = RataDie; 10118 HebrewRataDie.prototype.constructor = HebrewRataDie; 10119 10120 /** 10121 * The difference between a zero Julian day and the first day of the Hebrew 10122 * calendar: sunset on Monday, Tishri 1, 1 = September 7, 3760 BC Gregorian = JD 347997.25 10123 * @private 10124 * @const 10125 * @type number 10126 */ 10127 HebrewRataDie.prototype.epoch = 347997.25; 10128 10129 /** 10130 * the cumulative lengths of each month for a non-leap year, without new years corrections 10131 * @private 10132 * @const 10133 * @type Array.<number> 10134 */ 10135 HebrewRataDie.cumMonthLengths = [ 10136 176, /* Nisan */ 10137 206, /* Iyyar */ 10138 235, /* Sivan */ 10139 265, /* Tammuz */ 10140 294, /* Av */ 10141 324, /* Elul */ 10142 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10143 30, /* Heshvan */ 10144 59, /* Kislev */ 10145 88, /* Teveth */ 10146 117, /* Shevat */ 10147 147 /* Adar I */ 10148 ]; 10149 10150 /** 10151 * the cumulative lengths of each month for a leap year, without new years corrections 10152 * @private 10153 * @const 10154 * @type Array.<number> 10155 */ 10156 HebrewRataDie.cumMonthLengthsLeap = [ 10157 206, /* Nisan */ 10158 236, /* Iyyar */ 10159 265, /* Sivan */ 10160 295, /* Tammuz */ 10161 324, /* Av */ 10162 354, /* Elul */ 10163 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10164 30, /* Heshvan */ 10165 59, /* Kislev */ 10166 88, /* Teveth */ 10167 117, /* Shevat */ 10168 147, /* Adar I */ 10169 177 /* Adar II */ 10170 ]; 10171 10172 /** 10173 * Calculate the Rata Die (fixed day) number of the given date from the 10174 * date components. 10175 * 10176 * @private 10177 * @param {Object} date the date components to calculate the RD from 10178 */ 10179 HebrewRataDie.prototype._setDateComponents = function(date) { 10180 var elapsed = HebrewCal.elapsedDays(date.year); 10181 var days = elapsed + 10182 HebrewCal.newYearsCorrection(date.year, elapsed) + 10183 date.day - 1; 10184 var sum = 0, table; 10185 10186 //console.log("getRataDie: converting " + JSON.stringify(date)); 10187 //console.log("getRataDie: days is " + days); 10188 //console.log("getRataDie: new years correction is " + HebrewCal.newYearsCorrection(date.year, elapsed)); 10189 10190 table = this.cal.isLeapYear(date.year) ? 10191 HebrewRataDie.cumMonthLengthsLeap : 10192 HebrewRataDie.cumMonthLengths; 10193 sum = table[date.month-1]; 10194 10195 // gets cumulative without correction, so now add in the correction 10196 if ((date.month < 7 || date.month > 8) && HebrewCal.longHeshvan(date.year)) { 10197 sum++; 10198 } 10199 if ((date.month < 7 || date.month > 9) && HebrewCal.longKislev(date.year)) { 10200 sum++; 10201 } 10202 // console.log("getRataDie: cum days is now " + sum); 10203 10204 days += sum; 10205 10206 // the date starts at sunset, which we take as 18:00, so the hours from 10207 // midnight to 18:00 are on the current Gregorian day, and the hours from 10208 // 18:00 to midnight are on the previous Gregorian day. So to calculate the 10209 // number of hours into the current day that this time represents, we have 10210 // to count from 18:00 to midnight first, and add in 6 hours if the time is 10211 // less than 18:00 10212 var minute, second, millisecond; 10213 10214 if (typeof(date.parts) !== 'undefined') { 10215 // The parts (halaqim) of the hour. This can be a number from 0 to 1079. 10216 var parts = parseInt(date.parts, 10); 10217 var seconds = parseInt(parts, 10) * 3.333333333333; 10218 minute = Math.floor(seconds / 60); 10219 seconds -= minute * 60; 10220 second = Math.floor(seconds); 10221 millisecond = (seconds - second); 10222 } else { 10223 minute = parseInt(date.minute, 10) || 0; 10224 second = parseInt(date.second, 10) || 0; 10225 millisecond = parseInt(date.millisecond, 10) || 0; 10226 } 10227 10228 var time; 10229 if (date.hour >= 18) { 10230 time = ((date.hour - 18 || 0) * 3600000 + 10231 (minute || 0) * 60000 + 10232 (second || 0) * 1000 + 10233 (millisecond || 0)) / 10234 86400000; 10235 } else { 10236 time = 0.25 + // 6 hours from 18:00 to midnight on the previous gregorian day 10237 ((date.hour || 0) * 3600000 + 10238 (minute || 0) * 60000 + 10239 (second || 0) * 1000 + 10240 (millisecond || 0)) / 10241 86400000; 10242 } 10243 10244 //console.log("getRataDie: rd is " + (days + time)); 10245 this.rd = days + time; 10246 }; 10247 10248 /** 10249 * Return the rd number of the particular day of the week on or before the 10250 * given rd. eg. The Sunday on or before the given rd. 10251 * @private 10252 * @param {number} rd the rata die date of the reference date 10253 * @param {number} dayOfWeek the day of the week that is being sought relative 10254 * to the current date 10255 * @return {number} the rd of the day of the week 10256 */ 10257 HebrewRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 10258 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek + 1, 7); 10259 }; 10260 10261 10262 10263 /*< HebrewDate.js */ 10264 /* 10265 * HebrewDate.js - Represent a date in the Hebrew calendar 10266 * 10267 * Copyright © 2012-2015, JEDLSoft 10268 * 10269 * Licensed under the Apache License, Version 2.0 (the "License"); 10270 * you may not use this file except in compliance with the License. 10271 * You may obtain a copy of the License at 10272 * 10273 * http://www.apache.org/licenses/LICENSE-2.0 10274 * 10275 * Unless required by applicable law or agreed to in writing, software 10276 * distributed under the License is distributed on an "AS IS" BASIS, 10277 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10278 * 10279 * See the License for the specific language governing permissions and 10280 * limitations under the License. 10281 */ 10282 10283 /* !depends 10284 ilib.js 10285 Locale.js 10286 LocaleInfo.js 10287 TimeZone.js 10288 IDate.js 10289 MathUtils.js 10290 Calendar.js 10291 HebrewCal.js 10292 HebrewRataDie.js 10293 */ 10294 10295 10296 10297 10298 /** 10299 * @class 10300 * Construct a new civil Hebrew date object. The constructor can be called 10301 * with a params object that can contain the following properties:<p> 10302 * 10303 * <ul> 10304 * <li><i>julianday</i> - the Julian Day to set into this date 10305 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year 10306 * <li><i>month</i> - 1 to 12, where 1 means Nisan, 2 means Iyyar, etc. 10307 * <li><i>day</i> - 1 to 30 10308 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 10309 * is always done with an unambiguous 24 hour representation 10310 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 10311 * the parts or specify the minutes, seconds, and milliseconds, but not both. 10312 * <li><i>minute</i> - 0 to 59 10313 * <li><i>second</i> - 0 to 59 10314 * <li><i>millisecond</i> - 0 to 999 10315 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 10316 * of this julian date. The date/time is kept in the local time. The time zone 10317 * is used later if this date is formatted according to a different time zone and 10318 * the difference has to be calculated, or when the date format has a time zone 10319 * component in it. 10320 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 10321 * given, it can be inferred from this locale. For locales that span multiple 10322 * time zones, the one with the largest population is chosen as the one that 10323 * represents the locale. 10324 * 10325 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 10326 * </ul> 10327 * 10328 * If called with another Hebrew date argument, the date components of the given 10329 * date are copied into the current one.<p> 10330 * 10331 * If the constructor is called with no arguments at all or if none of the 10332 * properties listed above 10333 * from <i>julianday</i> through <i>millisecond</i> are present, then the date 10334 * components are 10335 * filled in with the current date at the time of instantiation. Note that if 10336 * you do not give the time zone when defaulting to the current time and the 10337 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 10338 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 10339 * Mean Time").<p> 10340 * 10341 * 10342 * @constructor 10343 * @extends IDate 10344 * @param {Object=} params parameters that govern the settings and behaviour of this Hebrew date 10345 */ 10346 var HebrewDate = function(params) { 10347 this.cal = new HebrewCal(); 10348 10349 if (params) { 10350 if (params.timezone) { 10351 this.timezone = params.timezone; 10352 } 10353 if (params.locale) { 10354 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 10355 if (!this.timezone) { 10356 var li = new LocaleInfo(this.locale); 10357 this.timezone = li.getTimeZone(); 10358 } 10359 } 10360 10361 if (params.year || params.month || params.day || params.hour || 10362 params.minute || params.second || params.millisecond || params.parts ) { 10363 /** 10364 * Year in the Hebrew calendar. 10365 * @type number 10366 */ 10367 this.year = parseInt(params.year, 10) || 0; 10368 10369 /** 10370 * The month number, ranging from 1 to 13. 10371 * @type number 10372 */ 10373 this.month = parseInt(params.month, 10) || 1; 10374 10375 /** 10376 * The day of the month. This ranges from 1 to 30. 10377 * @type number 10378 */ 10379 this.day = parseInt(params.day, 10) || 1; 10380 10381 /** 10382 * The hour of the day. This can be a number from 0 to 23, as times are 10383 * stored unambiguously in the 24-hour clock. 10384 * @type number 10385 */ 10386 this.hour = parseInt(params.hour, 10) || 0; 10387 10388 if (typeof(params.parts) !== 'undefined') { 10389 /** 10390 * The parts (halaqim) of the hour. This can be a number from 0 to 1079. 10391 * @type number 10392 */ 10393 this.parts = parseInt(params.parts, 10); 10394 var seconds = parseInt(params.parts, 10) * 3.333333333333; 10395 this.minute = Math.floor(seconds / 60); 10396 seconds -= this.minute * 60; 10397 this.second = Math.floor(seconds); 10398 this.millisecond = (seconds - this.second); 10399 } else { 10400 /** 10401 * The minute of the hours. Ranges from 0 to 59. 10402 * @type number 10403 */ 10404 this.minute = parseInt(params.minute, 10) || 0; 10405 10406 /** 10407 * The second of the minute. Ranges from 0 to 59. 10408 * @type number 10409 */ 10410 this.second = parseInt(params.second, 10) || 0; 10411 10412 /** 10413 * The millisecond of the second. Ranges from 0 to 999. 10414 * @type number 10415 */ 10416 this.millisecond = parseInt(params.millisecond, 10) || 0; 10417 } 10418 10419 /** 10420 * The day of the year. Ranges from 1 to 383. 10421 * @type number 10422 */ 10423 this.dayOfYear = parseInt(params.dayOfYear, 10); 10424 10425 if (typeof(params.dst) === 'boolean') { 10426 this.dst = params.dst; 10427 } 10428 10429 this.rd = this.newRd(this); 10430 10431 // add the time zone offset to the rd to convert to UTC 10432 if (!this.tz) { 10433 this.tz = new TimeZone({id: this.timezone}); 10434 } 10435 // getOffsetMillis requires that this.year, this.rd, and this.dst 10436 // are set in order to figure out which time zone rules apply and 10437 // what the offset is at that point in the year 10438 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 10439 if (this.offset !== 0) { 10440 this.rd = this.newRd({ 10441 rd: this.rd.getRataDie() - this.offset 10442 }); 10443 } 10444 } 10445 } 10446 10447 if (!this.rd) { 10448 this.rd = this.newRd(params); 10449 this._calcDateComponents(); 10450 } 10451 }; 10452 10453 HebrewDate.prototype = new IDate({noinstance: true}); 10454 HebrewDate.prototype.parent = IDate; 10455 HebrewDate.prototype.constructor = HebrewDate; 10456 10457 /** 10458 * the cumulative lengths of each month for a non-leap year, without new years corrections, 10459 * that can be used in reverse to map days to months 10460 * @private 10461 * @const 10462 * @type Array.<number> 10463 */ 10464 HebrewDate.cumMonthLengthsReverse = [ 10465 // [days, monthnumber], 10466 [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10467 [30, 8], /* Heshvan */ 10468 [59, 9], /* Kislev */ 10469 [88, 10], /* Teveth */ 10470 [117, 11], /* Shevat */ 10471 [147, 12], /* Adar I */ 10472 [176, 1], /* Nisan */ 10473 [206, 2], /* Iyyar */ 10474 [235, 3], /* Sivan */ 10475 [265, 4], /* Tammuz */ 10476 [294, 5], /* Av */ 10477 [324, 6], /* Elul */ 10478 [354, 7] /* end of year sentinel value */ 10479 ]; 10480 10481 /** 10482 * the cumulative lengths of each month for a leap year, without new years corrections 10483 * that can be used in reverse to map days to months 10484 * 10485 * @private 10486 * @const 10487 * @type Array.<number> 10488 */ 10489 HebrewDate.cumMonthLengthsLeapReverse = [ 10490 // [days, monthnumber], 10491 [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10492 [30, 8], /* Heshvan */ 10493 [59, 9], /* Kislev */ 10494 [88, 10], /* Teveth */ 10495 [117, 11], /* Shevat */ 10496 [147, 12], /* Adar I */ 10497 [177, 13], /* Adar II */ 10498 [206, 1], /* Nisan */ 10499 [236, 2], /* Iyyar */ 10500 [265, 3], /* Sivan */ 10501 [295, 4], /* Tammuz */ 10502 [324, 5], /* Av */ 10503 [354, 6], /* Elul */ 10504 [384, 7] /* end of year sentinel value */ 10505 ]; 10506 10507 /** 10508 * Number of days difference between RD 0 of the Hebrew calendar 10509 * (Jan 1, 1 Gregorian = JD 1721057.5) and RD 0 of the Hebrew calendar 10510 * (September 7, -3760 Gregorian = JD 347997.25) 10511 * @private 10512 * @const 10513 * @type number 10514 */ 10515 HebrewDate.GregorianDiff = 1373060.25; 10516 10517 /** 10518 * Return a new RD for this date type using the given params. 10519 * @private 10520 * @param {Object=} params the parameters used to create this rata die instance 10521 * @returns {RataDie} the new RD instance for the given params 10522 */ 10523 HebrewDate.prototype.newRd = function (params) { 10524 return new HebrewRataDie(params); 10525 }; 10526 10527 /** 10528 * Return the year for the given RD 10529 * @protected 10530 * @param {number} rd RD to calculate from 10531 * @returns {number} the year for the RD 10532 */ 10533 HebrewDate.prototype._calcYear = function(rd) { 10534 var year, approximation, nextNewYear; 10535 10536 // divide by the average number of days per year in the Hebrew calendar 10537 // to approximate the year, then tweak it to get the real year 10538 approximation = Math.floor(rd / 365.246822206) + 1; 10539 10540 // console.log("HebrewDate._calcYear: approx is " + approximation); 10541 10542 // search forward from approximation-1 for the year that actually contains this rd 10543 year = approximation; 10544 nextNewYear = HebrewCal.newYear(year); 10545 while (rd >= nextNewYear) { 10546 year++; 10547 nextNewYear = HebrewCal.newYear(year); 10548 } 10549 return year - 1; 10550 }; 10551 10552 /** 10553 * Calculate date components for the given RD date. 10554 * @protected 10555 */ 10556 HebrewDate.prototype._calcDateComponents = function () { 10557 var remainder, 10558 i, 10559 table, 10560 target, 10561 rd = this.rd.getRataDie(); 10562 10563 // console.log("HebrewDate.calcComponents: calculating for rd " + rd); 10564 10565 if (typeof(this.offset) === "undefined") { 10566 this.year = this._calcYear(rd); 10567 10568 // now offset the RD by the time zone, then recalculate in case we were 10569 // near the year boundary 10570 if (!this.tz) { 10571 this.tz = new TimeZone({id: this.timezone}); 10572 } 10573 this.offset = this.tz.getOffsetMillis(this) / 86400000; 10574 } 10575 10576 if (this.offset !== 0) { 10577 rd += this.offset; 10578 this.year = this._calcYear(rd); 10579 } 10580 10581 // console.log("HebrewDate.calcComponents: year is " + this.year + " with starting rd " + thisNewYear); 10582 10583 remainder = rd - HebrewCal.newYear(this.year); 10584 // console.log("HebrewDate.calcComponents: remainder is " + remainder); 10585 10586 // take out new years corrections so we get the right month when we look it up in the table 10587 if (remainder >= 59) { 10588 if (remainder >= 88) { 10589 if (HebrewCal.longKislev(this.year)) { 10590 remainder--; 10591 } 10592 } 10593 if (HebrewCal.longHeshvan(this.year)) { 10594 remainder--; 10595 } 10596 } 10597 10598 // console.log("HebrewDate.calcComponents: after new years corrections, remainder is " + remainder); 10599 10600 table = this.cal.isLeapYear(this.year) ? 10601 HebrewDate.cumMonthLengthsLeapReverse : 10602 HebrewDate.cumMonthLengthsReverse; 10603 10604 i = 0; 10605 target = Math.floor(remainder); 10606 while (i+1 < table.length && target >= table[i+1][0]) { 10607 i++; 10608 } 10609 10610 this.month = table[i][1]; 10611 // console.log("HebrewDate.calcComponents: remainder is " + remainder); 10612 remainder -= table[i][0]; 10613 10614 // console.log("HebrewDate.calcComponents: month is " + this.month + " and remainder is " + remainder); 10615 10616 this.day = Math.floor(remainder); 10617 remainder -= this.day; 10618 this.day++; // days are 1-based 10619 10620 // console.log("HebrewDate.calcComponents: day is " + this.day + " and remainder is " + remainder); 10621 10622 // now convert to milliseconds for the rest of the calculation 10623 remainder = Math.round(remainder * 86400000); 10624 10625 this.hour = Math.floor(remainder/3600000); 10626 remainder -= this.hour * 3600000; 10627 10628 // the hours from 0 to 6 are actually 18:00 to midnight of the previous 10629 // gregorian day, so we have to adjust for that 10630 if (this.hour >= 6) { 10631 this.hour -= 6; 10632 } else { 10633 this.hour += 18; 10634 } 10635 10636 this.minute = Math.floor(remainder/60000); 10637 remainder -= this.minute * 60000; 10638 10639 this.second = Math.floor(remainder/1000); 10640 remainder -= this.second * 1000; 10641 10642 this.millisecond = Math.floor(remainder); 10643 }; 10644 10645 /** 10646 * Return the day of the week of this date. The day of the week is encoded 10647 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 10648 * 10649 * @return {number} the day of the week 10650 */ 10651 HebrewDate.prototype.getDayOfWeek = function() { 10652 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 10653 return MathUtils.mod(rd+1, 7); 10654 }; 10655 10656 /** 10657 * Get the Halaqim (parts) of an hour. There are 1080 parts in an hour, which means 10658 * each part is 3.33333333 seconds long. This means the number returned may not 10659 * be an integer. 10660 * 10661 * @return {number} the halaqim parts of the current hour 10662 */ 10663 HebrewDate.prototype.getHalaqim = function() { 10664 if (this.parts < 0) { 10665 // convert to ms first, then to parts 10666 var h = this.minute * 60000 + this.second * 1000 + this.millisecond; 10667 this.parts = (h * 0.0003); 10668 } 10669 return this.parts; 10670 }; 10671 10672 /** 10673 * Return the rd number of the first Sunday of the given ISO year. 10674 * @protected 10675 * @return the rd of the first Sunday of the ISO year 10676 */ 10677 HebrewDate.prototype.firstSunday = function (year) { 10678 var tishri1 = this.newRd({ 10679 year: year, 10680 month: 7, 10681 day: 1, 10682 hour: 18, 10683 minute: 0, 10684 second: 0, 10685 millisecond: 0, 10686 cal: this.cal 10687 }); 10688 var firstThu = this.newRd({ 10689 rd: tishri1.onOrAfter(4), 10690 cal: this.cal 10691 }); 10692 return firstThu.before(0); 10693 }; 10694 10695 /** 10696 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 10697 * 385, regardless of months or weeks, etc. That is, Tishri 1st is day 1, and 10698 * Elul 29 is 385 for a leap year with a long Heshvan and long Kislev. 10699 * @return {number} the ordinal day of the year 10700 */ 10701 HebrewDate.prototype.getDayOfYear = function() { 10702 var table = this.cal.isLeapYear(this.year) ? 10703 HebrewRataDie.cumMonthLengthsLeap : 10704 HebrewRataDie.cumMonthLengths; 10705 var days = table[this.month-1]; 10706 if ((this.month < 7 || this.month > 8) && HebrewCal.longHeshvan(this.year)) { 10707 days++; 10708 } 10709 if ((this.month < 7 || this.month > 9) && HebrewCal.longKislev(this.year)) { 10710 days++; 10711 } 10712 10713 return days + this.day; 10714 }; 10715 10716 /** 10717 * Return the ordinal number of the week within the month. The first week of a month is 10718 * the first one that contains 4 or more days in that month. If any days precede this 10719 * first week, they are marked as being in week 0. This function returns values from 0 10720 * through 6.<p> 10721 * 10722 * The locale is a required parameter because different locales that use the same 10723 * Hebrew calendar consider different days of the week to be the beginning of 10724 * the week. This can affect the week of the month in which some days are located. 10725 * 10726 * @param {Locale|string} locale the locale or locale spec to use when figuring out 10727 * the first day of the week 10728 * @return {number} the ordinal number of the week within the current month 10729 */ 10730 HebrewDate.prototype.getWeekOfMonth = function(locale) { 10731 var li = new LocaleInfo(locale), 10732 first = this.newRd({ 10733 year: this.year, 10734 month: this.month, 10735 day: 1, 10736 hour: 18, 10737 minute: 0, 10738 second: 0, 10739 millisecond: 0 10740 }), 10741 rd = this.rd.getRataDie(), 10742 weekStart = first.onOrAfter(li.getFirstDayOfWeek()); 10743 10744 if (weekStart - first.getRataDie() > 3) { 10745 // if the first week has 4 or more days in it of the current month, then consider 10746 // that week 1. Otherwise, it is week 0. To make it week 1, move the week start 10747 // one week earlier. 10748 weekStart -= 7; 10749 } 10750 return (rd < weekStart) ? 0 : Math.floor((rd - weekStart) / 7) + 1; 10751 }; 10752 10753 /** 10754 * Return the era for this date as a number. The value for the era for Hebrew 10755 * calendars is -1 for "before the Hebrew era" and 1 for "the Hebrew era". 10756 * Hebrew era dates are any date after Tishri 1, 1, which is the same as 10757 * September 7, 3760 BC in the Gregorian calendar. 10758 * 10759 * @return {number} 1 if this date is in the Hebrew era, -1 if it is before the 10760 * Hebrew era 10761 */ 10762 HebrewDate.prototype.getEra = function() { 10763 return (this.year < 1) ? -1 : 1; 10764 }; 10765 10766 /** 10767 * Return the name of the calendar that governs this date. 10768 * 10769 * @return {string} a string giving the name of the calendar 10770 */ 10771 HebrewDate.prototype.getCalendar = function() { 10772 return "hebrew"; 10773 }; 10774 10775 // register with the factory method 10776 IDate._constructors["hebrew"] = HebrewDate; 10777 10778 10779 10780 /*< IslamicCal.js */ 10781 /* 10782 * islamic.js - Represent a Islamic calendar object. 10783 * 10784 * Copyright © 2012-2015, JEDLSoft 10785 * 10786 * Licensed under the Apache License, Version 2.0 (the "License"); 10787 * you may not use this file except in compliance with the License. 10788 * You may obtain a copy of the License at 10789 * 10790 * http://www.apache.org/licenses/LICENSE-2.0 10791 * 10792 * Unless required by applicable law or agreed to in writing, software 10793 * distributed under the License is distributed on an "AS IS" BASIS, 10794 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10795 * 10796 * See the License for the specific language governing permissions and 10797 * limitations under the License. 10798 */ 10799 10800 10801 /* !depends 10802 ilib.js 10803 Calendar.js 10804 MathUtils.js 10805 */ 10806 10807 10808 /** 10809 * @class 10810 * Construct a new Islamic calendar object. This class encodes information about 10811 * the civil Islamic calendar. The civil Islamic calendar is a tabular islamic 10812 * calendar where the dates are calculated by arithmetic rules. This differs from 10813 * the religious Islamic calendar which is used to mark the beginning of particular 10814 * holidays. The religious calendar depends on the first sighting of the new 10815 * crescent moon to determine the first day of the new month. Because humans and 10816 * weather are both involved, the actual time of sighting varies, so it is not 10817 * really possible to precalculate the religious calendar. Certain groups, such 10818 * as the Islamic Society of North America, decreed in in 2007 that they will use 10819 * a calendar based on calculations rather than observations to determine the 10820 * beginning of lunar months, and therefore the dates of holidays.<p> 10821 * 10822 * 10823 * @constructor 10824 * @extends Calendar 10825 */ 10826 var IslamicCal = function() { 10827 this.type = "islamic"; 10828 }; 10829 10830 /** 10831 * the lengths of each month 10832 * @private 10833 * @const 10834 * @type Array.<number> 10835 */ 10836 IslamicCal.monthLengths = [ 10837 30, /* Muharram */ 10838 29, /* Saffar */ 10839 30, /* Rabi'I */ 10840 29, /* Rabi'II */ 10841 30, /* Jumada I */ 10842 29, /* Jumada II */ 10843 30, /* Rajab */ 10844 29, /* Sha'ban */ 10845 30, /* Ramadan */ 10846 29, /* Shawwal */ 10847 30, /* Dhu al-Qa'da */ 10848 29 /* Dhu al-Hijja */ 10849 ]; 10850 10851 10852 /** 10853 * Return the number of months in the given year. The number of months in a year varies 10854 * for luni-solar calendars because in some years, an extra month is needed to extend the 10855 * days in a year to an entire solar year. The month is represented as a 1-based number 10856 * where 1=first month, 2=second month, etc. 10857 * 10858 * @param {number} year a year for which the number of months is sought 10859 */ 10860 IslamicCal.prototype.getNumMonths = function(year) { 10861 return 12; 10862 }; 10863 10864 /** 10865 * Return the number of days in a particular month in a particular year. This function 10866 * can return a different number for a month depending on the year because of things 10867 * like leap years. 10868 * 10869 * @param {number} month the month for which the length is sought 10870 * @param {number} year the year within which that month can be found 10871 * @return {number} the number of days within the given month in the given year 10872 */ 10873 IslamicCal.prototype.getMonLength = function(month, year) { 10874 if (month !== 12) { 10875 return IslamicCal.monthLengths[month-1]; 10876 } else { 10877 return this.isLeapYear(year) ? 30 : 29; 10878 } 10879 }; 10880 10881 /** 10882 * Return true if the given year is a leap year in the Islamic calendar. 10883 * The year parameter may be given as a number, or as a IslamicDate object. 10884 * @param {number} year the year for which the leap year information is being sought 10885 * @return {boolean} true if the given year is a leap year 10886 */ 10887 IslamicCal.prototype.isLeapYear = function(year) { 10888 return (MathUtils.mod((14 + 11 * year), 30) < 11); 10889 }; 10890 10891 /** 10892 * Return the type of this calendar. 10893 * 10894 * @return {string} the name of the type of this calendar 10895 */ 10896 IslamicCal.prototype.getType = function() { 10897 return this.type; 10898 }; 10899 10900 /** 10901 * Return a date instance for this calendar type using the given 10902 * options. 10903 * @param {Object} options options controlling the construction of 10904 * the date instance 10905 * @return {IslamicDate} a date appropriate for this calendar type 10906 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 10907 */ 10908 IslamicCal.prototype.newDateInstance = function (options) { 10909 return new IslamicDate(options); 10910 }; 10911 10912 /*register this calendar for the factory method */ 10913 Calendar._constructors["islamic"] = IslamicCal; 10914 10915 10916 /*< IslamicRataDie.js */ 10917 /* 10918 * IslamicRataDie.js - Represent an RD date in the Islamic calendar 10919 * 10920 * Copyright © 2012-2015, JEDLSoft 10921 * 10922 * Licensed under the Apache License, Version 2.0 (the "License"); 10923 * you may not use this file except in compliance with the License. 10924 * You may obtain a copy of the License at 10925 * 10926 * http://www.apache.org/licenses/LICENSE-2.0 10927 * 10928 * Unless required by applicable law or agreed to in writing, software 10929 * distributed under the License is distributed on an "AS IS" BASIS, 10930 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10931 * 10932 * See the License for the specific language governing permissions and 10933 * limitations under the License. 10934 */ 10935 10936 /* !depends 10937 IslamicCal.js 10938 RataDie.js 10939 */ 10940 10941 10942 /** 10943 * @class 10944 * Construct a new Islamic RD date number object. The constructor parameters can 10945 * contain any of the following properties: 10946 * 10947 * <ul> 10948 * <li><i>unixtime<i> - sets the time of this instance according to the given 10949 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 10950 * 10951 * <li><i>julianday</i> - sets the time of this instance according to the given 10952 * Julian Day instance or the Julian Day given as a float 10953 * 10954 * <li><i>year</i> - any integer, including 0 10955 * 10956 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 10957 * 10958 * <li><i>day</i> - 1 to 31 10959 * 10960 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 10961 * is always done with an unambiguous 24 hour representation 10962 * 10963 * <li><i>minute</i> - 0 to 59 10964 * 10965 * <li><i>second</i> - 0 to 59 10966 * 10967 * <li><i>millisecond</i> - 0 to 999 10968 * 10969 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 10970 * </ul> 10971 * 10972 * If the constructor is called with another Islamic date instance instead of 10973 * a parameter block, the other instance acts as a parameter block and its 10974 * settings are copied into the current instance.<p> 10975 * 10976 * If the constructor is called with no arguments at all or if none of the 10977 * properties listed above are present, then the RD is calculate based on 10978 * the current date at the time of instantiation. <p> 10979 * 10980 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 10981 * specified in the params, it is assumed that they have the smallest possible 10982 * value in the range for the property (zero or one).<p> 10983 * 10984 * 10985 * @private 10986 * @constructor 10987 * @extends RataDie 10988 * @param {Object=} params parameters that govern the settings and behaviour of this Islamic RD date 10989 */ 10990 var IslamicRataDie = function(params) { 10991 this.cal = params && params.cal || new IslamicCal(); 10992 this.rd = undefined; 10993 RataDie.call(this, params); 10994 }; 10995 10996 IslamicRataDie.prototype = new RataDie(); 10997 IslamicRataDie.prototype.parent = RataDie; 10998 IslamicRataDie.prototype.constructor = IslamicRataDie; 10999 11000 /** 11001 * The difference between a zero Julian day and the first Islamic date 11002 * of Friday, July 16, 622 CE Julian. 11003 * @private 11004 * @const 11005 * @type number 11006 */ 11007 IslamicRataDie.prototype.epoch = 1948439.5; 11008 11009 /** 11010 * Calculate the Rata Die (fixed day) number of the given date from the 11011 * date components. 11012 * 11013 * @protected 11014 * @param {Object} date the date components to calculate the RD from 11015 */ 11016 IslamicRataDie.prototype._setDateComponents = function(date) { 11017 var days = (date.year - 1) * 354 + 11018 Math.ceil(29.5 * (date.month - 1)) + 11019 date.day + 11020 Math.floor((3 + 11 * date.year) / 30) - 1; 11021 var time = (date.hour * 3600000 + 11022 date.minute * 60000 + 11023 date.second * 1000 + 11024 date.millisecond) / 11025 86400000; 11026 11027 //console.log("getRataDie: converting " + JSON.stringify(date)); 11028 //console.log("getRataDie: days is " + days); 11029 //console.log("getRataDie: time is " + time); 11030 //console.log("getRataDie: rd is " + (days + time)); 11031 11032 this.rd = days + time; 11033 }; 11034 11035 11036 /*< IslamicDate.js */ 11037 /* 11038 * islamicDate.js - Represent a date in the Islamic calendar 11039 * 11040 * Copyright © 2012-2015, JEDLSoft 11041 * 11042 * Licensed under the Apache License, Version 2.0 (the "License"); 11043 * you may not use this file except in compliance with the License. 11044 * You may obtain a copy of the License at 11045 * 11046 * http://www.apache.org/licenses/LICENSE-2.0 11047 * 11048 * Unless required by applicable law or agreed to in writing, software 11049 * distributed under the License is distributed on an "AS IS" BASIS, 11050 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11051 * 11052 * See the License for the specific language governing permissions and 11053 * limitations under the License. 11054 */ 11055 11056 /* !depends 11057 ilib.js 11058 Locale.js 11059 LocaleInfo.js 11060 TimeZone.js 11061 IDate.js 11062 MathUtils.js 11063 SearchUtils.js 11064 Calendar.js 11065 IslamicCal.js 11066 IslamicRataDie.js 11067 */ 11068 11069 11070 11071 11072 /** 11073 * @class 11074 * Construct a new civil Islamic date object. The constructor can be called 11075 * with a params object that can contain the following properties:<p> 11076 * 11077 * <ul> 11078 * <li><i>julianday</i> - the Julian Day to set into this date 11079 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year 11080 * <li><i>month</i> - 1 to 12, where 1 means Muharram, 2 means Saffar, etc. 11081 * <li><i>day</i> - 1 to 30 11082 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11083 * is always done with an unambiguous 24 hour representation 11084 * <li><i>minute</i> - 0 to 59 11085 * <li><i>second</i> - 0 to 59 11086 * <li><i>millisecond</i> - 0 to 999 11087 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 11088 * of this julian date. The date/time is kept in the local time. The time zone 11089 * is used later if this date is formatted according to a different time zone and 11090 * the difference has to be calculated, or when the date format has a time zone 11091 * component in it. 11092 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 11093 * given, it can be inferred from this locale. For locales that span multiple 11094 * time zones, the one with the largest population is chosen as the one that 11095 * represents the locale. 11096 * 11097 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11098 * </ul> 11099 * 11100 * If called with another Islamic date argument, the date components of the given 11101 * date are copied into the current one.<p> 11102 * 11103 * If the constructor is called with no arguments at all or if none of the 11104 * properties listed above 11105 * from <i>julianday</i> through <i>millisecond</i> are present, then the date 11106 * components are 11107 * filled in with the current date at the time of instantiation. Note that if 11108 * you do not give the time zone when defaulting to the current time and the 11109 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 11110 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 11111 * Mean Time").<p> 11112 * 11113 * 11114 * @constructor 11115 * @extends IDate 11116 * @param {Object=} params parameters that govern the settings and behaviour of this Islamic date 11117 */ 11118 var IslamicDate = function(params) { 11119 this.cal = new IslamicCal(); 11120 11121 if (params) { 11122 if (params.locale) { 11123 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 11124 var li = new LocaleInfo(this.locale); 11125 this.timezone = li.getTimeZone(); 11126 } 11127 if (params.timezone) { 11128 this.timezone = params.timezone; 11129 } 11130 11131 if (params.year || params.month || params.day || params.hour || 11132 params.minute || params.second || params.millisecond ) { 11133 /** 11134 * Year in the Islamic calendar. 11135 * @type number 11136 */ 11137 this.year = parseInt(params.year, 10) || 0; 11138 11139 /** 11140 * The month number, ranging from 1 to 12 (December). 11141 * @type number 11142 */ 11143 this.month = parseInt(params.month, 10) || 1; 11144 11145 /** 11146 * The day of the month. This ranges from 1 to 30. 11147 * @type number 11148 */ 11149 this.day = parseInt(params.day, 10) || 1; 11150 11151 /** 11152 * The hour of the day. This can be a number from 0 to 23, as times are 11153 * stored unambiguously in the 24-hour clock. 11154 * @type number 11155 */ 11156 this.hour = parseInt(params.hour, 10) || 0; 11157 11158 /** 11159 * The minute of the hours. Ranges from 0 to 59. 11160 * @type number 11161 */ 11162 this.minute = parseInt(params.minute, 10) || 0; 11163 11164 /** 11165 * The second of the minute. Ranges from 0 to 59. 11166 * @type number 11167 */ 11168 this.second = parseInt(params.second, 10) || 0; 11169 11170 /** 11171 * The millisecond of the second. Ranges from 0 to 999. 11172 * @type number 11173 */ 11174 this.millisecond = parseInt(params.millisecond, 10) || 0; 11175 11176 /** 11177 * The day of the year. Ranges from 1 to 355. 11178 * @type number 11179 */ 11180 this.dayOfYear = parseInt(params.dayOfYear, 10); 11181 11182 if (typeof(params.dst) === 'boolean') { 11183 this.dst = params.dst; 11184 } 11185 11186 this.rd = this.newRd(this); 11187 11188 // add the time zone offset to the rd to convert to UTC 11189 if (!this.tz) { 11190 this.tz = new TimeZone({id: this.timezone}); 11191 } 11192 // getOffsetMillis requires that this.year, this.rd, and this.dst 11193 // are set in order to figure out which time zone rules apply and 11194 // what the offset is at that point in the year 11195 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 11196 if (this.offset !== 0) { 11197 this.rd = this.newRd({ 11198 rd: this.rd.getRataDie() - this.offset 11199 }); 11200 } 11201 } 11202 } 11203 11204 if (!this.rd) { 11205 this.rd = this.newRd(params); 11206 this._calcDateComponents(); 11207 } 11208 }; 11209 11210 IslamicDate.prototype = new IDate({noinstance: true}); 11211 IslamicDate.prototype.parent = IDate; 11212 IslamicDate.prototype.constructor = IslamicDate; 11213 11214 /** 11215 * the cumulative lengths of each month, for a non-leap year 11216 * @private 11217 * @const 11218 * @type Array.<number> 11219 */ 11220 IslamicDate.cumMonthLengths = [ 11221 0, /* Muharram */ 11222 30, /* Saffar */ 11223 59, /* Rabi'I */ 11224 89, /* Rabi'II */ 11225 118, /* Jumada I */ 11226 148, /* Jumada II */ 11227 177, /* Rajab */ 11228 207, /* Sha'ban */ 11229 236, /* Ramadan */ 11230 266, /* Shawwal */ 11231 295, /* Dhu al-Qa'da */ 11232 325, /* Dhu al-Hijja */ 11233 354 11234 ]; 11235 11236 /** 11237 * Number of days difference between RD 0 of the Gregorian calendar and 11238 * RD 0 of the Islamic calendar. 11239 * @private 11240 * @const 11241 * @type number 11242 */ 11243 IslamicDate.GregorianDiff = 227015; 11244 11245 /** 11246 * Return a new RD for this date type using the given params. 11247 * @protected 11248 * @param {Object=} params the parameters used to create this rata die instance 11249 * @returns {RataDie} the new RD instance for the given params 11250 */ 11251 IslamicDate.prototype.newRd = function (params) { 11252 return new IslamicRataDie(params); 11253 }; 11254 11255 /** 11256 * Return the year for the given RD 11257 * @protected 11258 * @param {number} rd RD to calculate from 11259 * @returns {number} the year for the RD 11260 */ 11261 IslamicDate.prototype._calcYear = function(rd) { 11262 return Math.floor((30 * rd + 10646) / 10631); 11263 }; 11264 11265 /** 11266 * Calculate date components for the given RD date. 11267 * @protected 11268 */ 11269 IslamicDate.prototype._calcDateComponents = function () { 11270 var remainder, 11271 rd = this.rd.getRataDie(); 11272 11273 this.year = this._calcYear(rd); 11274 11275 if (typeof(this.offset) === "undefined") { 11276 this.year = this._calcYear(rd); 11277 11278 // now offset the RD by the time zone, then recalculate in case we were 11279 // near the year boundary 11280 if (!this.tz) { 11281 this.tz = new TimeZone({id: this.timezone}); 11282 } 11283 this.offset = this.tz.getOffsetMillis(this) / 86400000; 11284 } 11285 11286 if (this.offset !== 0) { 11287 rd += this.offset; 11288 this.year = this._calcYear(rd); 11289 } 11290 11291 //console.log("IslamicDate.calcComponent: calculating for rd " + rd); 11292 //console.log("IslamicDate.calcComponent: year is " + ret.year); 11293 var yearStart = this.newRd({ 11294 year: this.year, 11295 month: 1, 11296 day: 1, 11297 hour: 0, 11298 minute: 0, 11299 second: 0, 11300 millisecond: 0 11301 }); 11302 remainder = rd - yearStart.getRataDie() + 1; 11303 11304 this.dayOfYear = remainder; 11305 11306 //console.log("IslamicDate.calcComponent: remainder is " + remainder); 11307 11308 this.month = SearchUtils.bsearch(remainder, IslamicDate.cumMonthLengths); 11309 remainder -= IslamicDate.cumMonthLengths[this.month-1]; 11310 11311 //console.log("IslamicDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 11312 11313 this.day = Math.floor(remainder); 11314 remainder -= this.day; 11315 11316 //console.log("IslamicDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 11317 11318 // now convert to milliseconds for the rest of the calculation 11319 remainder = Math.round(remainder * 86400000); 11320 11321 this.hour = Math.floor(remainder/3600000); 11322 remainder -= this.hour * 3600000; 11323 11324 this.minute = Math.floor(remainder/60000); 11325 remainder -= this.minute * 60000; 11326 11327 this.second = Math.floor(remainder/1000); 11328 remainder -= this.second * 1000; 11329 11330 this.millisecond = remainder; 11331 }; 11332 11333 /** 11334 * Return the day of the week of this date. The day of the week is encoded 11335 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 11336 * 11337 * @return {number} the day of the week 11338 */ 11339 IslamicDate.prototype.getDayOfWeek = function() { 11340 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 11341 return MathUtils.mod(rd-2, 7); 11342 }; 11343 11344 /** 11345 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 11346 * 354 or 355, regardless of months or weeks, etc. That is, Muharran 1st is day 1, and 11347 * Dhu al-Hijja 29 is 354. 11348 * @return {number} the ordinal day of the year 11349 */ 11350 IslamicDate.prototype.getDayOfYear = function() { 11351 return IslamicDate.cumMonthLengths[this.month-1] + this.day; 11352 }; 11353 11354 /** 11355 * Return the era for this date as a number. The value for the era for Islamic 11356 * calendars is -1 for "before the Islamic era" and 1 for "the Islamic era". 11357 * Islamic era dates are any date after Muharran 1, 1, which is the same as 11358 * July 16, 622 CE in the Gregorian calendar. 11359 * 11360 * @return {number} 1 if this date is in the common era, -1 if it is before the 11361 * common era 11362 */ 11363 IslamicDate.prototype.getEra = function() { 11364 return (this.year < 1) ? -1 : 1; 11365 }; 11366 11367 /** 11368 * Return the name of the calendar that governs this date. 11369 * 11370 * @return {string} a string giving the name of the calendar 11371 */ 11372 IslamicDate.prototype.getCalendar = function() { 11373 return "islamic"; 11374 }; 11375 11376 //register with the factory method 11377 IDate._constructors["islamic"] = IslamicDate; 11378 11379 11380 /*< JulianCal.js */ 11381 /* 11382 * julian.js - Represent a Julian calendar object. 11383 * 11384 * Copyright © 2012-2015, JEDLSoft 11385 * 11386 * Licensed under the Apache License, Version 2.0 (the "License"); 11387 * you may not use this file except in compliance with the License. 11388 * You may obtain a copy of the License at 11389 * 11390 * http://www.apache.org/licenses/LICENSE-2.0 11391 * 11392 * Unless required by applicable law or agreed to in writing, software 11393 * distributed under the License is distributed on an "AS IS" BASIS, 11394 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11395 * 11396 * See the License for the specific language governing permissions and 11397 * limitations under the License. 11398 */ 11399 11400 11401 /* !depends ilib.js Calendar.js MathUtils.js */ 11402 11403 11404 /** 11405 * @class 11406 * Construct a new Julian calendar object. This class encodes information about 11407 * a Julian calendar.<p> 11408 * 11409 * 11410 * @constructor 11411 * @extends Calendar 11412 */ 11413 var JulianCal = function() { 11414 this.type = "julian"; 11415 }; 11416 11417 /* the lengths of each month */ 11418 JulianCal.monthLengths = [ 11419 31, /* Jan */ 11420 28, /* Feb */ 11421 31, /* Mar */ 11422 30, /* Apr */ 11423 31, /* May */ 11424 30, /* Jun */ 11425 31, /* Jul */ 11426 31, /* Aug */ 11427 30, /* Sep */ 11428 31, /* Oct */ 11429 30, /* Nov */ 11430 31 /* Dec */ 11431 ]; 11432 11433 /** 11434 * the cumulative lengths of each month, for a non-leap year 11435 * @private 11436 * @const 11437 * @type Array.<number> 11438 */ 11439 JulianCal.cumMonthLengths = [ 11440 0, /* Jan */ 11441 31, /* Feb */ 11442 59, /* Mar */ 11443 90, /* Apr */ 11444 120, /* May */ 11445 151, /* Jun */ 11446 181, /* Jul */ 11447 212, /* Aug */ 11448 243, /* Sep */ 11449 273, /* Oct */ 11450 304, /* Nov */ 11451 334, /* Dec */ 11452 365 11453 ]; 11454 11455 /** 11456 * the cumulative lengths of each month, for a leap year 11457 * @private 11458 * @const 11459 * @type Array.<number> 11460 */ 11461 JulianCal.cumMonthLengthsLeap = [ 11462 0, /* Jan */ 11463 31, /* Feb */ 11464 60, /* Mar */ 11465 91, /* Apr */ 11466 121, /* May */ 11467 152, /* Jun */ 11468 182, /* Jul */ 11469 213, /* Aug */ 11470 244, /* Sep */ 11471 274, /* Oct */ 11472 305, /* Nov */ 11473 335, /* Dec */ 11474 366 11475 ]; 11476 11477 /** 11478 * Return the number of months in the given year. The number of months in a year varies 11479 * for lunar calendars because in some years, an extra month is needed to extend the 11480 * days in a year to an entire solar year. The month is represented as a 1-based number 11481 * where 1=Jaunary, 2=February, etc. until 12=December. 11482 * 11483 * @param {number} year a year for which the number of months is sought 11484 */ 11485 JulianCal.prototype.getNumMonths = function(year) { 11486 return 12; 11487 }; 11488 11489 /** 11490 * Return the number of days in a particular month in a particular year. This function 11491 * can return a different number for a month depending on the year because of things 11492 * like leap years. 11493 * 11494 * @param {number} month the month for which the length is sought 11495 * @param {number} year the year within which that month can be found 11496 * @return {number} the number of days within the given month in the given year 11497 */ 11498 JulianCal.prototype.getMonLength = function(month, year) { 11499 if (month !== 2 || !this.isLeapYear(year)) { 11500 return JulianCal.monthLengths[month-1]; 11501 } else { 11502 return 29; 11503 } 11504 }; 11505 11506 /** 11507 * Return true if the given year is a leap year in the Julian calendar. 11508 * The year parameter may be given as a number, or as a JulDate object. 11509 * @param {number|JulianDate} year the year for which the leap year information is being sought 11510 * @return {boolean} true if the given year is a leap year 11511 */ 11512 JulianCal.prototype.isLeapYear = function(year) { 11513 var y = (typeof(year) === 'number' ? year : year.year); 11514 return MathUtils.mod(y, 4) === ((year > 0) ? 0 : 3); 11515 }; 11516 11517 /** 11518 * Return the type of this calendar. 11519 * 11520 * @return {string} the name of the type of this calendar 11521 */ 11522 JulianCal.prototype.getType = function() { 11523 return this.type; 11524 }; 11525 11526 /** 11527 * Return a date instance for this calendar type using the given 11528 * options. 11529 * @param {Object} options options controlling the construction of 11530 * the date instance 11531 * @return {IDate} a date appropriate for this calendar type 11532 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 11533 */ 11534 JulianCal.prototype.newDateInstance = function (options) { 11535 return new JulianDate(options); 11536 }; 11537 11538 /* register this calendar for the factory method */ 11539 Calendar._constructors["julian"] = JulianCal; 11540 11541 11542 /*< JulianRataDie.js */ 11543 /* 11544 * julianDate.js - Represent a date in the Julian calendar 11545 * 11546 * Copyright © 2012-2015, JEDLSoft 11547 * 11548 * Licensed under the Apache License, Version 2.0 (the "License"); 11549 * you may not use this file except in compliance with the License. 11550 * You may obtain a copy of the License at 11551 * 11552 * http://www.apache.org/licenses/LICENSE-2.0 11553 * 11554 * Unless required by applicable law or agreed to in writing, software 11555 * distributed under the License is distributed on an "AS IS" BASIS, 11556 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11557 * 11558 * See the License for the specific language governing permissions and 11559 * limitations under the License. 11560 */ 11561 11562 /* !depends 11563 JulianCal.js 11564 RataDie.js 11565 */ 11566 11567 11568 /** 11569 * @class 11570 * Construct a new Julian RD date number object. The constructor parameters can 11571 * contain any of the following properties: 11572 * 11573 * <ul> 11574 * <li><i>unixtime<i> - sets the time of this instance according to the given 11575 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 11576 * 11577 * <li><i>julianday</i> - sets the time of this instance according to the given 11578 * Julian Day instance or the Julian Day given as a float 11579 * 11580 * <li><i>year</i> - any integer, including 0 11581 * 11582 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 11583 * 11584 * <li><i>day</i> - 1 to 31 11585 * 11586 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11587 * is always done with an unambiguous 24 hour representation 11588 * 11589 * <li><i>minute</i> - 0 to 59 11590 * 11591 * <li><i>second</i> - 0 to 59 11592 * 11593 * <li><i>millisecond</i> - 0 to 999 11594 * 11595 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11596 * </ul> 11597 * 11598 * If the constructor is called with another Julian date instance instead of 11599 * a parameter block, the other instance acts as a parameter block and its 11600 * settings are copied into the current instance.<p> 11601 * 11602 * If the constructor is called with no arguments at all or if none of the 11603 * properties listed above are present, then the RD is calculate based on 11604 * the current date at the time of instantiation. <p> 11605 * 11606 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 11607 * specified in the params, it is assumed that they have the smallest possible 11608 * value in the range for the property (zero or one).<p> 11609 * 11610 * 11611 * @private 11612 * @constructor 11613 * @extends RataDie 11614 * @param {Object=} params parameters that govern the settings and behaviour of this Julian RD date 11615 */ 11616 var JulianRataDie = function(params) { 11617 this.cal = params && params.cal || new JulianCal(); 11618 this.rd = undefined; 11619 RataDie.call(this, params); 11620 }; 11621 11622 JulianRataDie.prototype = new RataDie(); 11623 JulianRataDie.prototype.parent = RataDie; 11624 JulianRataDie.prototype.constructor = JulianRataDie; 11625 11626 /** 11627 * The difference between a zero Julian day and the first Julian date 11628 * of Friday, July 16, 622 CE Julian. 11629 * @private 11630 * @const 11631 * @type number 11632 */ 11633 JulianRataDie.prototype.epoch = 1721422.5; 11634 11635 /** 11636 * Calculate the Rata Die (fixed day) number of the given date from the 11637 * date components. 11638 * 11639 * @protected 11640 * @param {Object} date the date components to calculate the RD from 11641 */ 11642 JulianRataDie.prototype._setDateComponents = function(date) { 11643 var year = date.year + ((date.year < 0) ? 1 : 0); 11644 var years = 365 * (year - 1) + Math.floor((year-1)/4); 11645 var dayInYear = (date.month > 1 ? JulianCal.cumMonthLengths[date.month-1] : 0) + 11646 date.day + 11647 (this.cal.isLeapYear(date.year) && date.month > 2 ? 1 : 0); 11648 var rdtime = (date.hour * 3600000 + 11649 date.minute * 60000 + 11650 date.second * 1000 + 11651 date.millisecond) / 11652 86400000; 11653 11654 /* 11655 console.log("calcRataDie: converting " + JSON.stringify(parts)); 11656 console.log("getRataDie: year is " + years); 11657 console.log("getRataDie: day in year is " + dayInYear); 11658 console.log("getRataDie: rdtime is " + rdtime); 11659 console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); 11660 */ 11661 11662 this.rd = years + dayInYear + rdtime; 11663 }; 11664 11665 11666 /*< JulianDate.js */ 11667 /* 11668 * JulianDate.js - Represent a date in the Julian calendar 11669 * 11670 * Copyright © 2012-2015, JEDLSoft 11671 * 11672 * Licensed under the Apache License, Version 2.0 (the "License"); 11673 * you may not use this file except in compliance with the License. 11674 * You may obtain a copy of the License at 11675 * 11676 * http://www.apache.org/licenses/LICENSE-2.0 11677 * 11678 * Unless required by applicable law or agreed to in writing, software 11679 * distributed under the License is distributed on an "AS IS" BASIS, 11680 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11681 * 11682 * See the License for the specific language governing permissions and 11683 * limitations under the License. 11684 */ 11685 11686 /* !depends 11687 ilib.js 11688 Locale.js 11689 IDate.js 11690 TimeZone.js 11691 Calendar.js 11692 JulianCal.js 11693 SearchUtils.js 11694 MathUtils.js 11695 LocaleInfo.js 11696 JulianRataDie.js 11697 */ 11698 11699 11700 11701 11702 /** 11703 * @class 11704 * Construct a new date object for the Julian Calendar. The constructor can be called 11705 * with a parameter object that contains any of the following properties: 11706 * 11707 * <ul> 11708 * <li><i>unixtime<i> - sets the time of this instance according to the given 11709 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 11710 * <li><i>julianday</i> - the Julian Day to set into this date 11711 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero 11712 * year which doesn't exist in the Julian calendar 11713 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 11714 * <li><i>day</i> - 1 to 31 11715 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11716 * is always done with an unambiguous 24 hour representation 11717 * <li><i>minute</i> - 0 to 59 11718 * <li><i>second</i> - 0 to 59 11719 * <li><i>millisecond<i> - 0 to 999 11720 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 11721 * of this julian date. The date/time is kept in the local time. The time zone 11722 * is used later if this date is formatted according to a different time zone and 11723 * the difference has to be calculated, or when the date format has a time zone 11724 * component in it. 11725 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 11726 * given, it can be inferred from this locale. For locales that span multiple 11727 * time zones, the one with the largest population is chosen as the one that 11728 * represents the locale. 11729 * 11730 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11731 * </ul> 11732 * 11733 * NB. The <a href="http://en.wikipedia.org/wiki/Julian_date">Julian Day</a> 11734 * (JulianDay) object is a <i>different</i> object than a 11735 * <a href="http://en.wikipedia.org/wiki/Julian_calendar">date in 11736 * the Julian calendar</a> and the two are not to be confused. The Julian Day 11737 * object represents time as a number of whole and fractional days since the 11738 * beginning of the epoch, whereas a date in the Julian 11739 * calendar is a regular date that signifies year, month, day, etc. using the rules 11740 * of the Julian calendar. The naming of Julian Days and the Julian calendar are 11741 * unfortunately close, and come from history.<p> 11742 * 11743 * If called with another Julian date argument, the date components of the given 11744 * date are copied into the current one.<p> 11745 * 11746 * If the constructor is called with no arguments at all or if none of the 11747 * properties listed above 11748 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 11749 * components are 11750 * filled in with the current date at the time of instantiation. Note that if 11751 * you do not give the time zone when defaulting to the current time and the 11752 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 11753 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 11754 * Mean Time").<p> 11755 * 11756 * 11757 * @constructor 11758 * @extends IDate 11759 * @param {Object=} params parameters that govern the settings and behaviour of this Julian date 11760 */ 11761 var JulianDate = function(params) { 11762 this.cal = new JulianCal(); 11763 11764 if (params) { 11765 if (params.locale) { 11766 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 11767 var li = new LocaleInfo(this.locale); 11768 this.timezone = li.getTimeZone(); 11769 } 11770 if (params.timezone) { 11771 this.timezone = params.timezone; 11772 } 11773 11774 if (params.year || params.month || params.day || params.hour || 11775 params.minute || params.second || params.millisecond ) { 11776 /** 11777 * Year in the Julian calendar. 11778 * @type number 11779 */ 11780 this.year = parseInt(params.year, 10) || 0; 11781 /** 11782 * The month number, ranging from 1 (January) to 12 (December). 11783 * @type number 11784 */ 11785 this.month = parseInt(params.month, 10) || 1; 11786 /** 11787 * The day of the month. This ranges from 1 to 31. 11788 * @type number 11789 */ 11790 this.day = parseInt(params.day, 10) || 1; 11791 /** 11792 * The hour of the day. This can be a number from 0 to 23, as times are 11793 * stored unambiguously in the 24-hour clock. 11794 * @type number 11795 */ 11796 this.hour = parseInt(params.hour, 10) || 0; 11797 /** 11798 * The minute of the hours. Ranges from 0 to 59. 11799 * @type number 11800 */ 11801 this.minute = parseInt(params.minute, 10) || 0; 11802 /** 11803 * The second of the minute. Ranges from 0 to 59. 11804 * @type number 11805 */ 11806 this.second = parseInt(params.second, 10) || 0; 11807 /** 11808 * The millisecond of the second. Ranges from 0 to 999. 11809 * @type number 11810 */ 11811 this.millisecond = parseInt(params.millisecond, 10) || 0; 11812 11813 /** 11814 * The day of the year. Ranges from 1 to 383. 11815 * @type number 11816 */ 11817 this.dayOfYear = parseInt(params.dayOfYear, 10); 11818 11819 if (typeof(params.dst) === 'boolean') { 11820 this.dst = params.dst; 11821 } 11822 11823 this.rd = this.newRd(this); 11824 11825 // add the time zone offset to the rd to convert to UTC 11826 if (!this.tz) { 11827 this.tz = new TimeZone({id: this.timezone}); 11828 } 11829 // getOffsetMillis requires that this.year, this.rd, and this.dst 11830 // are set in order to figure out which time zone rules apply and 11831 // what the offset is at that point in the year 11832 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 11833 if (this.offset !== 0) { 11834 this.rd = this.newRd({ 11835 rd: this.rd.getRataDie() - this.offset 11836 }); 11837 } 11838 } 11839 } 11840 11841 if (!this.rd) { 11842 this.rd = this.newRd(params); 11843 this._calcDateComponents(); 11844 } 11845 }; 11846 11847 JulianDate.prototype = new IDate({noinstance: true}); 11848 JulianDate.prototype.parent = IDate; 11849 JulianDate.prototype.constructor = JulianDate; 11850 11851 /** 11852 * Return a new RD for this date type using the given params. 11853 * @protected 11854 * @param {Object=} params the parameters used to create this rata die instance 11855 * @returns {RataDie} the new RD instance for the given params 11856 */ 11857 JulianDate.prototype.newRd = function (params) { 11858 return new JulianRataDie(params); 11859 }; 11860 11861 /** 11862 * Return the year for the given RD 11863 * @protected 11864 * @param {number} rd RD to calculate from 11865 * @returns {number} the year for the RD 11866 */ 11867 JulianDate.prototype._calcYear = function(rd) { 11868 var year = Math.floor((4*(Math.floor(rd)-1) + 1464)/1461); 11869 11870 return (year <= 0) ? year - 1 : year; 11871 }; 11872 11873 /** 11874 * Calculate date components for the given RD date. 11875 * @protected 11876 */ 11877 JulianDate.prototype._calcDateComponents = function () { 11878 var remainder, 11879 cumulative, 11880 rd = this.rd.getRataDie(); 11881 11882 this.year = this._calcYear(rd); 11883 11884 if (typeof(this.offset) === "undefined") { 11885 this.year = this._calcYear(rd); 11886 11887 // now offset the RD by the time zone, then recalculate in case we were 11888 // near the year boundary 11889 if (!this.tz) { 11890 this.tz = new TimeZone({id: this.timezone}); 11891 } 11892 this.offset = this.tz.getOffsetMillis(this) / 86400000; 11893 } 11894 11895 if (this.offset !== 0) { 11896 rd += this.offset; 11897 this.year = this._calcYear(rd); 11898 } 11899 11900 var jan1 = this.newRd({ 11901 year: this.year, 11902 month: 1, 11903 day: 1, 11904 hour: 0, 11905 minute: 0, 11906 second: 0, 11907 millisecond: 0 11908 }); 11909 remainder = rd + 1 - jan1.getRataDie(); 11910 11911 cumulative = this.cal.isLeapYear(this.year) ? 11912 JulianCal.cumMonthLengthsLeap : 11913 JulianCal.cumMonthLengths; 11914 11915 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 11916 remainder = remainder - cumulative[this.month-1]; 11917 11918 this.day = Math.floor(remainder); 11919 remainder -= this.day; 11920 // now convert to milliseconds for the rest of the calculation 11921 remainder = Math.round(remainder * 86400000); 11922 11923 this.hour = Math.floor(remainder/3600000); 11924 remainder -= this.hour * 3600000; 11925 11926 this.minute = Math.floor(remainder/60000); 11927 remainder -= this.minute * 60000; 11928 11929 this.second = Math.floor(remainder/1000); 11930 remainder -= this.second * 1000; 11931 11932 this.millisecond = remainder; 11933 }; 11934 11935 /** 11936 * Return the day of the week of this date. The day of the week is encoded 11937 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 11938 * 11939 * @return {number} the day of the week 11940 */ 11941 JulianDate.prototype.getDayOfWeek = function() { 11942 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 11943 return MathUtils.mod(rd-2, 7); 11944 }; 11945 11946 /** 11947 * Return the name of the calendar that governs this date. 11948 * 11949 * @return {string} a string giving the name of the calendar 11950 */ 11951 JulianDate.prototype.getCalendar = function() { 11952 return "julian"; 11953 }; 11954 11955 //register with the factory method 11956 IDate._constructors["julian"] = JulianDate; 11957 11958 11959 /*< ThaiSolarCal.js */ 11960 /* 11961 * thaisolar.js - Represent a Thai solar calendar object. 11962 * 11963 * Copyright © 2013-2015, JEDLSoft 11964 * 11965 * Licensed under the Apache License, Version 2.0 (the "License"); 11966 * you may not use this file except in compliance with the License. 11967 * You may obtain a copy of the License at 11968 * 11969 * http://www.apache.org/licenses/LICENSE-2.0 11970 * 11971 * Unless required by applicable law or agreed to in writing, software 11972 * distributed under the License is distributed on an "AS IS" BASIS, 11973 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11974 * 11975 * See the License for the specific language governing permissions and 11976 * limitations under the License. 11977 */ 11978 11979 11980 /* !depends ilib.js Calendar.js GregorianCal.js MathUtils.js */ 11981 11982 11983 /** 11984 * @class 11985 * Construct a new Thai solar calendar object. This class encodes information about 11986 * a Thai solar calendar.<p> 11987 * 11988 * 11989 * @constructor 11990 * @extends Calendar 11991 */ 11992 var ThaiSolarCal = function() { 11993 this.type = "thaisolar"; 11994 }; 11995 11996 ThaiSolarCal.prototype = new GregorianCal({noinstance: true}); 11997 ThaiSolarCal.prototype.parent = GregorianCal; 11998 ThaiSolarCal.prototype.constructor = ThaiSolarCal; 11999 12000 /** 12001 * Return true if the given year is a leap year in the Thai solar calendar. 12002 * The year parameter may be given as a number, or as a ThaiSolarDate object. 12003 * @param {number|ThaiSolarDate} year the year for which the leap year information is being sought 12004 * @return {boolean} true if the given year is a leap year 12005 */ 12006 ThaiSolarCal.prototype.isLeapYear = function(year) { 12007 var y = (typeof(year) === 'number' ? year : year.getYears()); 12008 y -= 543; 12009 var centuries = MathUtils.mod(y, 400); 12010 return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); 12011 }; 12012 12013 /** 12014 * Return a date instance for this calendar type using the given 12015 * options. 12016 * @param {Object} options options controlling the construction of 12017 * the date instance 12018 * @return {IDate} a date appropriate for this calendar type 12019 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 12020 */ 12021 ThaiSolarCal.prototype.newDateInstance = function (options) { 12022 return new ThaiSolarDate(options); 12023 }; 12024 12025 /* register this calendar for the factory method */ 12026 Calendar._constructors["thaisolar"] = ThaiSolarCal; 12027 12028 12029 /*< ThaiSolarDate.js */ 12030 /* 12031 * ThaiSolarDate.js - Represent a date in the ThaiSolar calendar 12032 * 12033 * Copyright © 2013-2015, JEDLSoft 12034 * 12035 * Licensed under the Apache License, Version 2.0 (the "License"); 12036 * you may not use this file except in compliance with the License. 12037 * You may obtain a copy of the License at 12038 * 12039 * http://www.apache.org/licenses/LICENSE-2.0 12040 * 12041 * Unless required by applicable law or agreed to in writing, software 12042 * distributed under the License is distributed on an "AS IS" BASIS, 12043 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12044 * 12045 * See the License for the specific language governing permissions and 12046 * limitations under the License. 12047 */ 12048 12049 /* !depends 12050 ilib.js 12051 IDate.js 12052 JSUtils.js 12053 GregorianDate.js 12054 ThaiSolarCal.js 12055 */ 12056 12057 12058 12059 12060 /** 12061 * @class 12062 * Construct a new Thai solar date object. The constructor parameters can 12063 * contain any of the following properties: 12064 * 12065 * <ul> 12066 * <li><i>unixtime<i> - sets the time of this instance according to the given 12067 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 12068 * 12069 * <li><i>julianday</i> - sets the time of this instance according to the given 12070 * Julian Day instance or the Julian Day given as a float 12071 * 12072 * <li><i>year</i> - any integer, including 0 12073 * 12074 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 12075 * 12076 * <li><i>day</i> - 1 to 31 12077 * 12078 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12079 * is always done with an unambiguous 24 hour representation 12080 * 12081 * <li><i>minute</i> - 0 to 59 12082 * 12083 * <li><i>second</i> - 0 to 59 12084 * 12085 * <li><i>millisecond</i> - 0 to 999 12086 * 12087 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 12088 * of this Thai solar date. The date/time is kept in the local time. The time zone 12089 * is used later if this date is formatted according to a different time zone and 12090 * the difference has to be calculated, or when the date format has a time zone 12091 * component in it. 12092 * 12093 * <li><i>locale</i> - locale for this Thai solar date. If the time zone is not 12094 * given, it can be inferred from this locale. For locales that span multiple 12095 * time zones, the one with the largest population is chosen as the one that 12096 * represents the locale. 12097 * </ul> 12098 * 12099 * If the constructor is called with another Thai solar date instance instead of 12100 * a parameter block, the other instance acts as a parameter block and its 12101 * settings are copied into the current instance.<p> 12102 * 12103 * If the constructor is called with no arguments at all or if none of the 12104 * properties listed above 12105 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 12106 * components are 12107 * filled in with the current date at the time of instantiation. Note that if 12108 * you do not give the time zone when defaulting to the current time and the 12109 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 12110 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 12111 * Mean Time").<p> 12112 * 12113 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 12114 * specified in the params, it is assumed that they have the smallest possible 12115 * value in the range for the property (zero or one).<p> 12116 * 12117 * 12118 * @constructor 12119 * @extends GregorianDate 12120 * @param {Object=} params parameters that govern the settings and behaviour of this Thai solar date 12121 */ 12122 var ThaiSolarDate = function(params) { 12123 var p = params; 12124 if (params) { 12125 // there is 198327 days difference between the Thai solar and 12126 // Gregorian epochs which is equivalent to 543 years 12127 p = {}; 12128 JSUtils.shallowCopy(params, p); 12129 if (typeof(p.year) !== 'undefined') { 12130 p.year -= 543; 12131 } 12132 if (typeof(p.rd) !== 'undefined') { 12133 p.rd -= 198327; 12134 } 12135 } 12136 this.rd = undefined; // clear these out so that the GregorianDate constructor can set it 12137 this.offset = undefined; 12138 //console.log("ThaiSolarDate.constructor: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 12139 GregorianDate.call(this, p); 12140 this.cal = new ThaiSolarCal(); 12141 // make sure the year is set correctly 12142 if (params && typeof(params.year) !== 'undefined') { 12143 this.year = parseInt(params.year, 10); 12144 } 12145 }; 12146 12147 ThaiSolarDate.prototype = new GregorianDate({noinstance: true}); 12148 ThaiSolarDate.prototype.parent = GregorianDate.prototype; 12149 ThaiSolarDate.prototype.constructor = ThaiSolarDate; 12150 12151 /** 12152 * the difference between a zero Julian day and the zero Thai Solar date. 12153 * This is some 543 years before the start of the Gregorian epoch. 12154 * @private 12155 * @const 12156 * @type number 12157 */ 12158 ThaiSolarDate.epoch = 1523097.5; 12159 12160 /** 12161 * Calculate the date components for the current time zone 12162 * @protected 12163 */ 12164 ThaiSolarDate.prototype._calcDateComponents = function () { 12165 // there is 198327 days difference between the Thai solar and 12166 // Gregorian epochs which is equivalent to 543 years 12167 // console.log("ThaiSolarDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 12168 this.parent._calcDateComponents.call(this); 12169 this.year += 543; 12170 }; 12171 12172 /** 12173 * Return the Rata Die (fixed day) number of this date. 12174 * 12175 * @protected 12176 * @return {number} the rd date as a number 12177 */ 12178 ThaiSolarDate.prototype.getRataDie = function() { 12179 // there is 198327 days difference between the Thai solar and 12180 // Gregorian epochs which is equivalent to 543 years 12181 return this.rd.getRataDie() + 198327; 12182 }; 12183 12184 /** 12185 * Return a new Gregorian date instance that represents the first instance of the 12186 * given day of the week before the current date. The day of the week is encoded 12187 * as a number where 0 = Sunday, 1 = Monday, etc. 12188 * 12189 * @param {number} dow the day of the week before the current date that is being sought 12190 * @return {IDate} the date being sought 12191 */ 12192 ThaiSolarDate.prototype.before = function (dow) { 12193 return new ThaiSolarDate({ 12194 rd: this.rd.before(dow, this.offset) + 198327, 12195 timezone: this.timezone 12196 }); 12197 }; 12198 12199 /** 12200 * Return a new Gregorian date instance that represents the first instance of the 12201 * given day of the week after the current date. The day of the week is encoded 12202 * as a number where 0 = Sunday, 1 = Monday, etc. 12203 * 12204 * @param {number} dow the day of the week after the current date that is being sought 12205 * @return {IDate} the date being sought 12206 */ 12207 ThaiSolarDate.prototype.after = function (dow) { 12208 return new ThaiSolarDate({ 12209 rd: this.rd.after(dow, this.offset) + 198327, 12210 timezone: this.timezone 12211 }); 12212 }; 12213 12214 /** 12215 * Return a new Gregorian date instance that represents the first instance of the 12216 * given day of the week on or before the current date. The day of the week is encoded 12217 * as a number where 0 = Sunday, 1 = Monday, etc. 12218 * 12219 * @param {number} dow the day of the week on or before the current date that is being sought 12220 * @return {IDate} the date being sought 12221 */ 12222 ThaiSolarDate.prototype.onOrBefore = function (dow) { 12223 return new ThaiSolarDate({ 12224 rd: this.rd.onOrBefore(dow, this.offset) + 198327, 12225 timezone: this.timezone 12226 }); 12227 }; 12228 12229 /** 12230 * Return a new Gregorian date instance that represents the first instance of the 12231 * given day of the week on or after the current date. The day of the week is encoded 12232 * as a number where 0 = Sunday, 1 = Monday, etc. 12233 * 12234 * @param {number} dow the day of the week on or after the current date that is being sought 12235 * @return {IDate} the date being sought 12236 */ 12237 ThaiSolarDate.prototype.onOrAfter = function (dow) { 12238 return new ThaiSolarDate({ 12239 rd: this.rd.onOrAfter(dow, this.offset) + 198327, 12240 timezone: this.timezone 12241 }); 12242 }; 12243 12244 /** 12245 * Return the name of the calendar that governs this date. 12246 * 12247 * @return {string} a string giving the name of the calendar 12248 */ 12249 ThaiSolarDate.prototype.getCalendar = function() { 12250 return "thaisolar"; 12251 }; 12252 12253 //register with the factory method 12254 IDate._constructors["thaisolar"] = ThaiSolarDate; 12255 12256 12257 12258 /*< Astro.js */ 12259 /* 12260 * astro.js - Static functions to support astronomical calculations 12261 * 12262 * Copyright © 2014-2015, JEDLSoft 12263 * 12264 * Licensed under the Apache License, Version 2.0 (the "License"); 12265 * you may not use this file except in compliance with the License. 12266 * You may obtain a copy of the License at 12267 * 12268 * http://www.apache.org/licenses/LICENSE-2.0 12269 * 12270 * Unless required by applicable law or agreed to in writing, software 12271 * distributed under the License is distributed on an "AS IS" BASIS, 12272 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12273 * 12274 * See the License for the specific language governing permissions and 12275 * limitations under the License. 12276 */ 12277 12278 /* !depends 12279 ilib.js 12280 IDate.js 12281 Utils.js 12282 MathUtils.js 12283 SearchUtils.js 12284 GregorianDate.js 12285 GregRataDie.js 12286 */ 12287 12288 // !data astro 12289 12290 /* 12291 * These routines were derived from a public domain set of JavaScript 12292 * functions for positional astronomy by John Walker of Fourmilab, 12293 * September 1999. 12294 */ 12295 12296 12297 12298 var Astro = {}; 12299 12300 /** 12301 * Load in all the data needed for astrological calculations. 12302 * 12303 * @private 12304 * @param {boolean} sync 12305 * @param {*} loadParams 12306 * @param {function(*)|undefined} callback 12307 */ 12308 Astro.initAstro = function(sync, loadParams, callback) { 12309 if (!ilib.data.astro) { 12310 Utils.loadData({ 12311 name: "astro.json", // countries in their own language 12312 locale: "-", // only need to load the root file 12313 nonLocale: true, 12314 sync: sync, 12315 loadParams: loadParams, 12316 callback: ilib.bind(this, /** @type function() */ function(astroData) { 12317 /** 12318 * @type {{ 12319 * _EquinoxpTerms:Array.<number>, 12320 * _JDE0tab1000:Array.<number>, 12321 * _JDE0tab2000:Array.<number>, 12322 * _deltaTtab:Array.<number>, 12323 * _oterms:Array.<number>, 12324 * _nutArgMult:Array.<number>, 12325 * _nutArgCoeff:Array.<number>, 12326 * _nutCoeffA:Array.<number>, 12327 * _nutCoeffB:Array.<number>, 12328 * _coeff19th:Array.<number>, 12329 * _coeff18th:Array.<number>, 12330 * _solarLongCoeff:Array.<number>, 12331 * _solarLongMultipliers:Array.<number>, 12332 * _solarLongAddends:Array.<number>, 12333 * _meanMoonCoeff:Array.<number>, 12334 * _elongationCoeff:Array.<number>, 12335 * _solarAnomalyCoeff:Array.<number>, 12336 * _lunarAnomalyCoeff:Array.<number>, 12337 * _moonFromNodeCoeff:Array.<number>, 12338 * _eCoeff:Array.<number>, 12339 * _lunarElongationLongCoeff:Array.<number>, 12340 * _solarAnomalyLongCoeff:Array.<number>, 12341 * _lunarAnomalyLongCoeff:Array.<number>, 12342 * _moonFromNodeLongCoeff:Array.<number>, 12343 * _sineCoeff:Array.<number>, 12344 * _nmApproxCoeff:Array.<number>, 12345 * _nmCapECoeff:Array.<number>, 12346 * _nmSolarAnomalyCoeff:Array.<number>, 12347 * _nmLunarAnomalyCoeff:Array.<number>, 12348 * _nmMoonArgumentCoeff:Array.<number>, 12349 * _nmCapOmegaCoeff:Array.<number>, 12350 * _nmEFactor:Array.<number>, 12351 * _nmSolarCoeff:Array.<number>, 12352 * _nmLunarCoeff:Array.<number>, 12353 * _nmMoonCoeff:Array.<number>, 12354 * _nmSineCoeff:Array.<number>, 12355 * _nmAddConst:Array.<number>, 12356 * _nmAddCoeff:Array.<number>, 12357 * _nmAddFactor:Array.<number>, 12358 * _nmExtra:Array.<number> 12359 * }} 12360 */ 12361 ilib.data.astro = astroData; 12362 if (callback && typeof(callback) === 'function') { 12363 callback(astroData); 12364 } 12365 }) 12366 }); 12367 } else { 12368 if (callback && typeof(callback) === 'function') { 12369 callback(ilib.data.astro); 12370 } 12371 } 12372 }; 12373 12374 /** 12375 * Convert degrees to radians. 12376 * 12377 * @static 12378 * @protected 12379 * @param {number} d angle in degrees 12380 * @return {number} angle in radians 12381 */ 12382 Astro._dtr = function(d) { 12383 return (d * Math.PI) / 180.0; 12384 }; 12385 12386 /** 12387 * Convert radians to degrees. 12388 * 12389 * @static 12390 * @protected 12391 * @param {number} r angle in radians 12392 * @return {number} angle in degrees 12393 */ 12394 Astro._rtd = function(r) { 12395 return (r * 180.0) / Math.PI; 12396 }; 12397 12398 /** 12399 * Return the cosine of an angle given in degrees. 12400 * @static 12401 * @protected 12402 * @param {number} d angle in degrees 12403 * @return {number} cosine of the angle. 12404 */ 12405 Astro._dcos = function(d) { 12406 return Math.cos(Astro._dtr(d)); 12407 }; 12408 12409 /** 12410 * Return the sine of an angle given in degrees. 12411 * @static 12412 * @protected 12413 * @param {number} d angle in degrees 12414 * @return {number} sine of the angle. 12415 */ 12416 Astro._dsin = function(d) { 12417 return Math.sin(Astro._dtr(d)); 12418 }; 12419 12420 /** 12421 * Return the tan of an angle given in degrees. 12422 * @static 12423 * @protected 12424 * @param {number} d angle in degrees 12425 * @return {number} tan of the angle. 12426 */ 12427 Astro._dtan = function(d) { 12428 return Math.tan(Astro._dtr(d)); 12429 }; 12430 12431 /** 12432 * Range reduce angle in degrees. 12433 * 12434 * @static 12435 * @param {number} a angle to reduce 12436 * @return {number} the reduced angle 12437 */ 12438 Astro._fixangle = function(a) { 12439 return a - 360.0 * (Math.floor(a / 360.0)); 12440 }; 12441 12442 /** 12443 * Range reduce angle in radians. 12444 * 12445 * @static 12446 * @protected 12447 * @param {number} a angle to reduce 12448 * @return {number} the reduced angle 12449 */ 12450 Astro._fixangr = function(a) { 12451 return a - (2 * Math.PI) * (Math.floor(a / (2 * Math.PI))); 12452 }; 12453 12454 /** 12455 * Determine the Julian Ephemeris Day of an equinox or solstice. The "which" 12456 * argument selects the item to be computed: 12457 * 12458 * <ul> 12459 * <li>0 March equinox 12460 * <li>1 June solstice 12461 * <li>2 September equinox 12462 * <li>3 December solstice 12463 * </ul> 12464 * 12465 * @static 12466 * @protected 12467 * @param {number} year Gregorian year to calculate for 12468 * @param {number} which Which equinox or solstice to calculate 12469 */ 12470 Astro._equinox = function(year, which) { 12471 var deltaL, i, j, JDE0, JDE, JDE0tab, S, T, W, Y; 12472 12473 /* Initialize terms for mean equinox and solstices. We 12474 have two sets: one for years prior to 1000 and a second 12475 for subsequent years. */ 12476 12477 if (year < 1000) { 12478 JDE0tab = ilib.data.astro._JDE0tab1000; 12479 Y = year / 1000; 12480 } else { 12481 JDE0tab = ilib.data.astro._JDE0tab2000; 12482 Y = (year - 2000) / 1000; 12483 } 12484 12485 JDE0 = JDE0tab[which][0] + (JDE0tab[which][1] * Y) 12486 + (JDE0tab[which][2] * Y * Y) + (JDE0tab[which][3] * Y * Y * Y) 12487 + (JDE0tab[which][4] * Y * Y * Y * Y); 12488 12489 //document.debug.log.value += "JDE0 = " + JDE0 + "\n"; 12490 12491 T = (JDE0 - 2451545.0) / 36525; 12492 //document.debug.log.value += "T = " + T + "\n"; 12493 W = (35999.373 * T) - 2.47; 12494 //document.debug.log.value += "W = " + W + "\n"; 12495 deltaL = 1 + (0.0334 * Astro._dcos(W)) + (0.0007 * Astro._dcos(2 * W)); 12496 //document.debug.log.value += "deltaL = " + deltaL + "\n"; 12497 12498 // Sum the periodic terms for time T 12499 12500 S = 0; 12501 j = 0; 12502 for (i = 0; i < 24; i++) { 12503 S += ilib.data.astro._EquinoxpTerms[j] 12504 * Astro._dcos(ilib.data.astro._EquinoxpTerms[j + 1] + (ilib.data.astro._EquinoxpTerms[j + 2] * T)); 12505 j += 3; 12506 } 12507 12508 //document.debug.log.value += "S = " + S + "\n"; 12509 //document.debug.log.value += "Corr = " + ((S * 0.00001) / deltaL) + "\n"; 12510 12511 JDE = JDE0 + ((S * 0.00001) / deltaL); 12512 12513 return JDE; 12514 }; 12515 12516 /* 12517 * The table of observed Delta T values at the beginning of 12518 * years from 1620 through 2014 as found in astro.json is taken from 12519 * http://www.staff.science.uu.nl/~gent0113/deltat/deltat.htm 12520 * and 12521 * ftp://maia.usno.navy.mil/ser7/deltat.data 12522 */ 12523 12524 /** 12525 * Determine the difference, in seconds, between dynamical time and universal time. 12526 * 12527 * @static 12528 * @protected 12529 * @param {number} year to calculate the difference for 12530 * @return {number} difference in seconds between dynamical time and universal time 12531 */ 12532 Astro._deltat = function (year) { 12533 var dt, f, i, t; 12534 12535 if ((year >= 1620) && (year <= 2014)) { 12536 i = Math.floor(year - 1620); 12537 f = (year - 1620) - i; /* Fractional part of year */ 12538 dt = ilib.data.astro._deltaTtab[i] + ((ilib.data.astro._deltaTtab[i + 1] - ilib.data.astro._deltaTtab[i]) * f); 12539 } else { 12540 t = (year - 2000) / 100; 12541 if (year < 948) { 12542 dt = 2177 + (497 * t) + (44.1 * t * t); 12543 } else { 12544 dt = 102 + (102 * t) + (25.3 * t * t); 12545 if ((year > 2000) && (year < 2100)) { 12546 dt += 0.37 * (year - 2100); 12547 } 12548 } 12549 } 12550 return dt; 12551 }; 12552 12553 /** 12554 * Calculate the obliquity of the ecliptic for a given 12555 * Julian date. This uses Laskar's tenth-degree 12556 * polynomial fit (J. Laskar, Astronomy and 12557 * Astrophysics, Vol. 157, page 68 [1986]) which is 12558 * accurate to within 0.01 arc second between AD 1000 12559 * and AD 3000, and within a few seconds of arc for 12560 * +/-10000 years around AD 2000. If we're outside the 12561 * range in which this fit is valid (deep time) we 12562 * simply return the J2000 value of the obliquity, which 12563 * happens to be almost precisely the mean. 12564 * 12565 * @static 12566 * @protected 12567 * @param {number} jd Julian Day to calculate the obliquity for 12568 * @return {number} the obliquity 12569 */ 12570 Astro._obliqeq = function (jd) { 12571 var eps, u, v, i; 12572 12573 v = u = (jd - 2451545.0) / 3652500.0; 12574 12575 eps = 23 + (26 / 60.0) + (21.448 / 3600.0); 12576 12577 if (Math.abs(u) < 1.0) { 12578 for (i = 0; i < 10; i++) { 12579 eps += (ilib.data.astro._oterms[i] / 3600.0) * v; 12580 v *= u; 12581 } 12582 } 12583 return eps; 12584 }; 12585 12586 /** 12587 * Return the position of the sun. We return 12588 * intermediate values because they are useful in a 12589 * variety of other contexts. 12590 * @static 12591 * @protected 12592 * @param {number} jd find the position of sun on this Julian Day 12593 * @return {Object} the position of the sun and many intermediate 12594 * values 12595 */ 12596 Astro._sunpos = function(jd) { 12597 var ret = {}, 12598 T, T2, T3, Omega, epsilon, epsilon0; 12599 12600 T = (jd - 2451545.0) / 36525.0; 12601 //document.debug.log.value += "Sunpos. T = " + T + "\n"; 12602 T2 = T * T; 12603 T3 = T * T2; 12604 ret.meanLongitude = Astro._fixangle(280.46646 + 36000.76983 * T + 0.0003032 * T2); 12605 //document.debug.log.value += "ret.meanLongitude = " + ret.meanLongitude + "\n"; 12606 ret.meanAnomaly = Astro._fixangle(357.52911 + (35999.05029 * T) - 0.0001537 * T2 - 0.00000048 * T3); 12607 //document.debug.log.value += "ret.meanAnomaly = " + ret.meanAnomaly + "\n"; 12608 ret.eccentricity = 0.016708634 - 0.000042037 * T - 0.0000001267 * T2; 12609 //document.debug.log.value += "e = " + e + "\n"; 12610 ret.equationOfCenter = ((1.914602 - 0.004817 * T - 0.000014 * T2) * Astro._dsin(ret.meanAnomaly)) 12611 + ((0.019993 - 0.000101 * T) * Astro._dsin(2 * ret.meanAnomaly)) 12612 + (0.000289 * Astro._dsin(3 * ret.meanAnomaly)); 12613 //document.debug.log.value += "ret.equationOfCenter = " + ret.equationOfCenter + "\n"; 12614 ret.sunLongitude = ret.meanLongitude + ret.equationOfCenter; 12615 //document.debug.log.value += "ret.sunLongitude = " + ret.sunLongitude + "\n"; 12616 //ret.sunAnomaly = ret.meanAnomaly + ret.equationOfCenter; 12617 //document.debug.log.value += "ret.sunAnomaly = " + ret.sunAnomaly + "\n"; 12618 // ret.sunRadius = (1.000001018 * (1 - (ret.eccentricity * ret.eccentricity))) / (1 + (ret.eccentricity * Astro._dcos(ret.sunAnomaly))); 12619 //document.debug.log.value += "ret.sunRadius = " + ret.sunRadius + "\n"; 12620 Omega = 125.04 - (1934.136 * T); 12621 //document.debug.log.value += "Omega = " + Omega + "\n"; 12622 ret.apparentLong = ret.sunLongitude + (-0.00569) + (-0.00478 * Astro._dsin(Omega)); 12623 //document.debug.log.value += "ret.apparentLong = " + ret.apparentLong + "\n"; 12624 epsilon0 = Astro._obliqeq(jd); 12625 //document.debug.log.value += "epsilon0 = " + epsilon0 + "\n"; 12626 epsilon = epsilon0 + (0.00256 * Astro._dcos(Omega)); 12627 //document.debug.log.value += "epsilon = " + epsilon + "\n"; 12628 //ret.rightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon0) * Astro._dsin(ret.sunLongitude), Astro._dcos(ret.sunLongitude)))); 12629 //document.debug.log.value += "ret.rightAscension = " + ret.rightAscension + "\n"; 12630 // ret.declination = Astro._rtd(Math.asin(Astro._dsin(epsilon0) * Astro._dsin(ret.sunLongitude))); 12631 ////document.debug.log.value += "ret.declination = " + ret.declination + "\n"; 12632 ret.inclination = Astro._fixangle(23.4392911 - 0.013004167 * T - 0.00000016389 * T2 + 0.0000005036 * T3); 12633 ret.apparentRightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon) * Astro._dsin(ret.apparentLong), Astro._dcos(ret.apparentLong)))); 12634 //document.debug.log.value += "ret.apparentRightAscension = " + ret.apparentRightAscension + "\n"; 12635 //ret.apparentDeclination = Astro._rtd(Math.asin(Astro._dsin(epsilon) * Astro._dsin(ret.apparentLong))); 12636 //document.debug.log.value += "ret.apparentDecliation = " + ret.apparentDecliation + "\n"; 12637 12638 // Angular quantities are expressed in decimal degrees 12639 return ret; 12640 }; 12641 12642 /** 12643 * Calculate the nutation in longitude, deltaPsi, and obliquity, 12644 * deltaEpsilon for a given Julian date jd. Results are returned as an object 12645 * giving deltaPsi and deltaEpsilon in degrees. 12646 * 12647 * @static 12648 * @protected 12649 * @param {number} jd calculate the nutation of this Julian Day 12650 * @return {Object} the deltaPsi and deltaEpsilon of the nutation 12651 */ 12652 Astro._nutation = function(jd) { 12653 var i, j, 12654 t = (jd - 2451545.0) / 36525.0, 12655 t2, t3, to10, 12656 ta = [], 12657 dp = 0, 12658 de = 0, 12659 ang, 12660 ret = {}; 12661 12662 t3 = t * (t2 = t * t); 12663 12664 /* 12665 * Calculate angles. The correspondence between the elements of our array 12666 * and the terms cited in Meeus are: 12667 * 12668 * ta[0] = D ta[0] = M ta[2] = M' ta[3] = F ta[4] = \Omega 12669 * 12670 */ 12671 12672 ta[0] = Astro._dtr(297.850363 + 445267.11148 * t - 0.0019142 * t2 + t3 / 189474.0); 12673 ta[1] = Astro._dtr(357.52772 + 35999.05034 * t - 0.0001603 * t2 - t3 / 300000.0); 12674 ta[2] = Astro._dtr(134.96298 + 477198.867398 * t + 0.0086972 * t2 + t3 / 56250.0); 12675 ta[3] = Astro._dtr(93.27191 + 483202.017538 * t - 0.0036825 * t2 + t3 / 327270); 12676 ta[4] = Astro._dtr(125.04452 - 1934.136261 * t + 0.0020708 * t2 + t3 / 450000.0); 12677 12678 /* 12679 * Range reduce the angles in case the sine and cosine functions don't do it 12680 * as accurately or quickly. 12681 */ 12682 12683 for (i = 0; i < 5; i++) { 12684 ta[i] = Astro._fixangr(ta[i]); 12685 } 12686 12687 to10 = t / 10.0; 12688 for (i = 0; i < 63; i++) { 12689 ang = 0; 12690 for (j = 0; j < 5; j++) { 12691 if (ilib.data.astro._nutArgMult[(i * 5) + j] != 0) { 12692 ang += ilib.data.astro._nutArgMult[(i * 5) + j] * ta[j]; 12693 } 12694 } 12695 dp += (ilib.data.astro._nutArgCoeff[(i * 4) + 0] + ilib.data.astro._nutArgCoeff[(i * 4) + 1] * to10) * Math.sin(ang); 12696 de += (ilib.data.astro._nutArgCoeff[(i * 4) + 2] + ilib.data.astro._nutArgCoeff[(i * 4) + 3] * to10) * Math.cos(ang); 12697 } 12698 12699 /* 12700 * Return the result, converting from ten thousandths of arc seconds to 12701 * radians in the process. 12702 */ 12703 12704 ret.deltaPsi = dp / (3600.0 * 10000.0); 12705 ret.deltaEpsilon = de / (3600.0 * 10000.0); 12706 12707 return ret; 12708 }; 12709 12710 /** 12711 * Returns the equation of time as a fraction of a day. 12712 * 12713 * @static 12714 * @protected 12715 * @param {number} jd the Julian Day of the day to calculate for 12716 * @return {number} the equation of time for the given day 12717 */ 12718 Astro._equationOfTime = function(jd) { 12719 var alpha, deltaPsi, E, epsilon, L0, tau, pos; 12720 12721 // 2451545.0 is the Julian day of J2000 epoch 12722 // 365250.0 is the number of days in a Julian millenium 12723 tau = (jd - 2451545.0) / 365250.0; 12724 //console.log("equationOfTime. tau = " + tau); 12725 L0 = 280.4664567 + (360007.6982779 * tau) + (0.03032028 * tau * tau) 12726 + ((tau * tau * tau) / 49931) 12727 + (-((tau * tau * tau * tau) / 15300)) 12728 + (-((tau * tau * tau * tau * tau) / 2000000)); 12729 //console.log("L0 = " + L0); 12730 L0 = Astro._fixangle(L0); 12731 //console.log("L0 = " + L0); 12732 pos = Astro._sunpos(jd); 12733 alpha = pos.apparentRightAscension; 12734 //console.log("alpha = " + alpha); 12735 var nut = Astro._nutation(jd); 12736 deltaPsi = nut.deltaPsi; 12737 //console.log("deltaPsi = " + deltaPsi); 12738 epsilon = Astro._obliqeq(jd) + nut.deltaEpsilon; 12739 //console.log("epsilon = " + epsilon); 12740 //console.log("L0 - 0.0057183 = " + (L0 - 0.0057183)); 12741 //console.log("L0 - 0.0057183 - alpha = " + (L0 - 0.0057183 - alpha)); 12742 //console.log("deltaPsi * cos(epsilon) = " + deltaPsi * Astro._dcos(epsilon)); 12743 12744 E = L0 - 0.0057183 - alpha + deltaPsi * Astro._dcos(epsilon); 12745 // if alpha and L0 are in different quadrants, then renormalize 12746 // so that the difference between them is in the right range 12747 if (E > 180) { 12748 E -= 360; 12749 } 12750 //console.log("E = " + E); 12751 // E = E - 20.0 * (Math.floor(E / 20.0)); 12752 E = E * 4; 12753 //console.log("Efixed = " + E); 12754 E = E / (24 * 60); 12755 //console.log("Eday = " + E); 12756 12757 return E; 12758 }; 12759 12760 /** 12761 * @private 12762 * @static 12763 */ 12764 Astro._poly = function(x, coefficients) { 12765 var result = coefficients[0]; 12766 var xpow = x; 12767 for (var i = 1; i < coefficients.length; i++) { 12768 result += coefficients[i] * xpow; 12769 xpow *= x; 12770 } 12771 return result; 12772 }; 12773 12774 /** 12775 * Calculate the UTC RD from the local RD given "zone" number of minutes 12776 * worth of offset. 12777 * 12778 * @static 12779 * @protected 12780 * @param {number} local RD of the locale time, given in any calendar 12781 * @param {number} zone number of minutes of offset from UTC for the time zone 12782 * @return {number} the UTC equivalent of the local RD 12783 */ 12784 Astro._universalFromLocal = function(local, zone) { 12785 return local - zone / 1440; 12786 }; 12787 12788 /** 12789 * Calculate the local RD from the UTC RD given "zone" number of minutes 12790 * worth of offset. 12791 * 12792 * @static 12793 * @protected 12794 * @param {number} local RD of the locale time, given in any calendar 12795 * @param {number} zone number of minutes of offset from UTC for the time zone 12796 * @return {number} the UTC equivalent of the local RD 12797 */ 12798 Astro._localFromUniversal = function(local, zone) { 12799 return local + zone / 1440; 12800 }; 12801 12802 /** 12803 * @private 12804 * @static 12805 * @param {number} c julian centuries of the date to calculate 12806 * @return {number} the aberration 12807 */ 12808 Astro._aberration = function(c) { 12809 return 9.74e-05 * Astro._dcos(177.63 + 35999.01847999999 * c) - 0.005575; 12810 }; 12811 12812 /** 12813 * @private 12814 * 12815 ilib.data.astro._nutCoeffA = [124.90, -1934.134, 0.002063]; 12816 ilib.data.astro._nutCoeffB q= [201.11, 72001.5377, 0.00057]; 12817 */ 12818 12819 /** 12820 * @private 12821 * @static 12822 * @param {number} c julian centuries of the date to calculate 12823 * @return {number} the nutation for the given julian century in radians 12824 */ 12825 Astro._nutation2 = function(c) { 12826 var a = Astro._poly(c, ilib.data.astro._nutCoeffA); 12827 var b = Astro._poly(c, ilib.data.astro._nutCoeffB); 12828 // return -0.0000834 * Astro._dsin(a) - 0.0000064 * Astro._dsin(b); 12829 return -0.004778 * Astro._dsin(a) - 0.0003667 * Astro._dsin(b); 12830 }; 12831 12832 /** 12833 * @static 12834 * @private 12835 */ 12836 Astro._ephemerisCorrection = function(jd) { 12837 var year = GregorianDate._calcYear(jd - 1721424.5); 12838 12839 if (1988 <= year && year <= 2019) { 12840 return (year - 1933) / 86400; 12841 } 12842 12843 if (1800 <= year && year <= 1987) { 12844 var jul1 = new GregRataDie({ 12845 year: year, 12846 month: 7, 12847 day: 1, 12848 hour: 0, 12849 minute: 0, 12850 second: 0 12851 }); 12852 // 693596 is the rd of Jan 1, 1900 12853 var theta = (jul1.getRataDie() - 693596) / 36525; 12854 return Astro._poly(theta, (1900 <= year) ? ilib.data.astro._coeff19th : ilib.data.astro._coeff18th); 12855 } 12856 12857 if (1620 <= year && year <= 1799) { 12858 year -= 1600; 12859 return (196.58333 - 4.0675 * year + 0.0219167 * year * year) / 86400; 12860 } 12861 12862 // 660724 is the rd of Jan 1, 1810 12863 var jan1 = new GregRataDie({ 12864 year: year, 12865 month: 1, 12866 day: 1, 12867 hour: 0, 12868 minute: 0, 12869 second: 0 12870 }); 12871 // var x = 0.5 + (jan1.getRataDie() - 660724); 12872 var x = 0.5 + (jan1.getRataDie() - 660724); 12873 12874 return ((x * x / 41048480) - 15) / 86400; 12875 }; 12876 12877 /** 12878 * @static 12879 * @private 12880 */ 12881 Astro._ephemerisFromUniversal = function(jd) { 12882 return jd + Astro._ephemerisCorrection(jd); 12883 }; 12884 12885 /** 12886 * @static 12887 * @private 12888 */ 12889 Astro._universalFromEphemeris = function(jd) { 12890 return jd - Astro._ephemerisCorrection(jd); 12891 }; 12892 12893 /** 12894 * @static 12895 * @private 12896 */ 12897 Astro._julianCenturies = function(jd) { 12898 // 2451545.0 is the Julian day of J2000 epoch 12899 // 730119.5 is the Gregorian RD of J2000 epoch 12900 // 36525.0 is the number of days in a Julian century 12901 return (Astro._ephemerisFromUniversal(jd) - 2451545.0) / 36525.0; 12902 }; 12903 12904 /** 12905 * Calculate the solar longitude 12906 * 12907 * @static 12908 * @protected 12909 * @param {number} jd julian day of the date to calculate the longitude for 12910 * @return {number} the solar longitude in degrees 12911 */ 12912 Astro._solarLongitude = function(jd) { 12913 var c = Astro._julianCenturies(jd), 12914 longitude = 0, 12915 len = ilib.data.astro._solarLongCoeff.length, 12916 row; 12917 12918 for (var i = 0; i < len; i++) { 12919 longitude += ilib.data.astro._solarLongCoeff[i] * 12920 Astro._dsin(ilib.data.astro._solarLongAddends[i] + ilib.data.astro._solarLongMultipliers[i] * c); 12921 } 12922 longitude *= 5.729577951308232e-06; 12923 longitude += 282.77718340000001 + 36000.769537439999 * c; 12924 longitude += Astro._aberration(c) + Astro._nutation2(c); 12925 return Astro._fixangle(longitude); 12926 }; 12927 12928 /** 12929 * @static 12930 * @protected 12931 * @param {number} jd 12932 * @return {number} 12933 */ 12934 Astro._lunarLongitude = function (jd) { 12935 var c = Astro._julianCenturies(jd), 12936 meanMoon = Astro._fixangle(Astro._poly(c, ilib.data.astro._meanMoonCoeff)), 12937 elongation = Astro._fixangle(Astro._poly(c, ilib.data.astro._elongationCoeff)), 12938 solarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._solarAnomalyCoeff)), 12939 lunarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._lunarAnomalyCoeff)), 12940 moonNode = Astro._fixangle(Astro._poly(c, ilib.data.astro._moonFromNodeCoeff)), 12941 e = Astro._poly(c, ilib.data.astro._eCoeff); 12942 12943 var sum = 0; 12944 for (var i = 0; i < ilib.data.astro._lunarElongationLongCoeff.length; i++) { 12945 var x = ilib.data.astro._solarAnomalyLongCoeff[i]; 12946 12947 sum += ilib.data.astro._sineCoeff[i] * Math.pow(e, Math.abs(x)) * 12948 Astro._dsin(ilib.data.astro._lunarElongationLongCoeff[i] * elongation + x * solarAnomaly + 12949 ilib.data.astro._lunarAnomalyLongCoeff[i] * lunarAnomaly + 12950 ilib.data.astro._moonFromNodeLongCoeff[i] * moonNode); 12951 } 12952 var longitude = sum / 1000000; 12953 var venus = 3958.0 / 1000000 * Astro._dsin(119.75 + c * 131.84899999999999); 12954 var jupiter = 318.0 / 1000000 * Astro._dsin(53.090000000000003 + c * 479264.28999999998); 12955 var flatEarth = 1962.0 / 1000000 * Astro._dsin(meanMoon - moonNode); 12956 12957 return Astro._fixangle(meanMoon + longitude + venus + jupiter + flatEarth + Astro._nutation2(c)); 12958 }; 12959 12960 /** 12961 * @static 12962 * @protected 12963 * @param {number} n 12964 * @return {number} julian day of the n'th new moon 12965 */ 12966 Astro._newMoonTime = function(n) { 12967 var k = n - 24724; 12968 var c = k / 1236.8499999999999; 12969 var approx = Astro._poly(c, ilib.data.astro._nmApproxCoeff); 12970 var capE = Astro._poly(c, ilib.data.astro._nmCapECoeff); 12971 var solarAnomaly = Astro._poly(c, ilib.data.astro._nmSolarAnomalyCoeff); 12972 var lunarAnomaly = Astro._poly(c, ilib.data.astro._nmLunarAnomalyCoeff); 12973 var moonArgument = Astro._poly(c, ilib.data.astro._nmMoonArgumentCoeff); 12974 var capOmega = Astro._poly(c, ilib.data.astro._nmCapOmegaCoeff); 12975 var correction = -0.00017 * Astro._dsin(capOmega); 12976 for (var i = 0; i < ilib.data.astro._nmSineCoeff.length; i++) { 12977 correction = correction + ilib.data.astro._nmSineCoeff[i] * Math.pow(capE, ilib.data.astro._nmEFactor[i]) * 12978 Astro._dsin(ilib.data.astro._nmSolarCoeff[i] * solarAnomaly + 12979 ilib.data.astro._nmLunarCoeff[i] * lunarAnomaly + 12980 ilib.data.astro._nmMoonCoeff[i] * moonArgument); 12981 } 12982 var additional = 0; 12983 for (var i = 0; i < ilib.data.astro._nmAddConst.length; i++) { 12984 additional = additional + ilib.data.astro._nmAddFactor[i] * 12985 Astro._dsin(ilib.data.astro._nmAddConst[i] + ilib.data.astro._nmAddCoeff[i] * k); 12986 } 12987 var extra = 0.000325 * Astro._dsin(Astro._poly(c, ilib.data.astro._nmExtra)); 12988 return Astro._universalFromEphemeris(approx + correction + extra + additional + RataDie.gregorianEpoch); 12989 }; 12990 12991 /** 12992 * @static 12993 * @protected 12994 * @param {number} jd 12995 * @return {number} 12996 */ 12997 Astro._lunarSolarAngle = function(jd) { 12998 var lunar = Astro._lunarLongitude(jd); 12999 var solar = Astro._solarLongitude(jd) 13000 return Astro._fixangle(lunar - solar); 13001 }; 13002 13003 /** 13004 * @static 13005 * @protected 13006 * @param {number} jd 13007 * @return {number} 13008 */ 13009 Astro._newMoonBefore = function (jd) { 13010 var phase = Astro._lunarSolarAngle(jd); 13011 // 11.450086114414322 is the julian day of the 0th full moon 13012 // 29.530588853000001 is the average length of a month 13013 var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360) - 1; 13014 var current, last; 13015 current = last = Astro._newMoonTime(guess); 13016 while (current < jd) { 13017 guess++; 13018 last = current; 13019 current = Astro._newMoonTime(guess); 13020 } 13021 return last; 13022 }; 13023 13024 /** 13025 * @static 13026 * @protected 13027 * @param {number} jd 13028 * @return {number} 13029 */ 13030 Astro._newMoonAtOrAfter = function (jd) { 13031 var phase = Astro._lunarSolarAngle(jd); 13032 // 11.450086114414322 is the julian day of the 0th full moon 13033 // 29.530588853000001 is the average length of a month 13034 var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360); 13035 var current; 13036 while ((current = Astro._newMoonTime(guess)) < jd) { 13037 guess++; 13038 } 13039 return current; 13040 }; 13041 13042 /** 13043 * @static 13044 * @protected 13045 * @param {number} jd JD to calculate from 13046 * @param {number} longitude longitude to seek 13047 * @returns {number} the JD of the next time that the solar longitude 13048 * is a multiple of the given longitude 13049 */ 13050 Astro._nextSolarLongitude = function(jd, longitude) { 13051 var rate = 365.242189 / 360.0; 13052 var tau = jd + rate * Astro._fixangle(longitude - Astro._solarLongitude(jd)); 13053 var start = Math.max(jd, tau - 5.0); 13054 var end = tau + 5.0; 13055 13056 return SearchUtils.bisectionSearch(0, start, end, 1e-6, function (l) { 13057 return 180 - Astro._fixangle(Astro._solarLongitude(l) - longitude); 13058 }); 13059 }; 13060 13061 /** 13062 * Floor the julian day to midnight of the current julian day. 13063 * 13064 * @static 13065 * @protected 13066 * @param {number} jd the julian to round 13067 * @return {number} the jd floored to the midnight of the julian day 13068 */ 13069 Astro._floorToJD = function(jd) { 13070 return Math.floor(jd - 0.5) + 0.5; 13071 }; 13072 13073 /** 13074 * Floor the julian day to midnight of the current julian day. 13075 * 13076 * @static 13077 * @protected 13078 * @param {number} jd the julian to round 13079 * @return {number} the jd floored to the midnight of the julian day 13080 */ 13081 Astro._ceilToJD = function(jd) { 13082 return Math.ceil(jd + 0.5) - 0.5; 13083 }; 13084 13085 13086 13087 /*< PersRataDie.js */ 13088 /* 13089 * persratadie.js - Represent a rata die date in the Persian calendar 13090 * 13091 * Copyright © 2014-2015, JEDLSoft 13092 * 13093 * Licensed under the Apache License, Version 2.0 (the "License"); 13094 * you may not use this file except in compliance with the License. 13095 * You may obtain a copy of the License at 13096 * 13097 * http://www.apache.org/licenses/LICENSE-2.0 13098 * 13099 * Unless required by applicable law or agreed to in writing, software 13100 * distributed under the License is distributed on an "AS IS" BASIS, 13101 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13102 * 13103 * See the License for the specific language governing permissions and 13104 * limitations under the License. 13105 */ 13106 13107 /* !depends 13108 ilib.js 13109 MathUtils.js 13110 RataDie.js 13111 Astro.js 13112 GregorianDate.js 13113 */ 13114 13115 13116 13117 13118 /** 13119 * @class 13120 * Construct a new Persian RD date number object. The constructor parameters can 13121 * contain any of the following properties: 13122 * 13123 * <ul> 13124 * <li><i>unixtime<i> - sets the time of this instance according to the given 13125 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 13126 * 13127 * <li><i>julianday</i> - sets the time of this instance according to the given 13128 * Julian Day instance or the Julian Day given as a float 13129 * 13130 * <li><i>year</i> - any integer, including 0 13131 * 13132 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 13133 * 13134 * <li><i>day</i> - 1 to 31 13135 * 13136 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 13137 * is always done with an unambiguous 24 hour representation 13138 * 13139 * <li><i>minute</i> - 0 to 59 13140 * 13141 * <li><i>second</i> - 0 to 59 13142 * 13143 * <li><i>millisecond</i> - 0 to 999 13144 * 13145 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 13146 * </ul> 13147 * 13148 * If the constructor is called with another Persian date instance instead of 13149 * a parameter block, the other instance acts as a parameter block and its 13150 * settings are copied into the current instance.<p> 13151 * 13152 * If the constructor is called with no arguments at all or if none of the 13153 * properties listed above are present, then the RD is calculate based on 13154 * the current date at the time of instantiation. <p> 13155 * 13156 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 13157 * specified in the params, it is assumed that they have the smallest possible 13158 * value in the range for the property (zero or one).<p> 13159 * 13160 * 13161 * @private 13162 * @constructor 13163 * @extends RataDie 13164 * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date 13165 */ 13166 var PersRataDie = function(params) { 13167 this.rd = undefined; 13168 Astro.initAstro( 13169 params && typeof(params.sync) === 'boolean' ? params.sync : true, 13170 params && params.loadParams, 13171 ilib.bind(this, function (x) { 13172 RataDie.call(this, params); 13173 if (params && typeof(params.callback) === 'function') { 13174 params.callback(this); 13175 } 13176 }) 13177 ); 13178 }; 13179 13180 PersRataDie.prototype = new RataDie(); 13181 PersRataDie.prototype.parent = RataDie; 13182 PersRataDie.prototype.constructor = PersRataDie; 13183 13184 /** 13185 * The difference between a zero Julian day and the first Persian date 13186 * @private 13187 * @const 13188 * @type number 13189 */ 13190 PersRataDie.prototype.epoch = 1948319.5; 13191 13192 /** 13193 * @protected 13194 */ 13195 PersRataDie.prototype._tehranEquinox = function(year) { 13196 var equJED, equJD, equAPP, equTehran, dtTehran, eot; 13197 13198 // March equinox in dynamical time 13199 equJED = Astro._equinox(year, 0); 13200 13201 // Correct for delta T to obtain Universal time 13202 equJD = equJED - (Astro._deltat(year) / (24 * 60 * 60)); 13203 13204 // Apply the equation of time to yield the apparent time at Greenwich 13205 eot = Astro._equationOfTime(equJED) * 360; 13206 eot = (eot - 20 * Math.floor(eot/20)) / 360; 13207 equAPP = equJD + eot; 13208 13209 /* 13210 * Finally, we must correct for the constant difference between 13211 * the Greenwich meridian and the time zone standard for Iran 13212 * Standard time, 52 degrees 30 minutes to the East. 13213 */ 13214 13215 dtTehran = 52.5 / 360; 13216 equTehran = equAPP + dtTehran; 13217 13218 return equTehran; 13219 }; 13220 13221 /** 13222 * Calculate the year based on the given Julian day. 13223 * @protected 13224 * @param {number} jd the Julian day to get the year for 13225 * @return {{year:number,equinox:number}} the year and the last equinox 13226 */ 13227 PersRataDie.prototype._getYear = function(jd) { 13228 var gd = new GregorianDate({julianday: jd}); 13229 var guess = gd.getYears() - 2, 13230 nexteq, 13231 ret = {}; 13232 13233 //ret.equinox = Math.floor(this._tehranEquinox(guess)); 13234 ret.equinox = this._tehranEquinox(guess); 13235 while (ret.equinox > jd) { 13236 guess--; 13237 // ret.equinox = Math.floor(this._tehranEquinox(guess)); 13238 ret.equinox = this._tehranEquinox(guess); 13239 } 13240 nexteq = ret.equinox - 1; 13241 // if the equinox falls after noon, then the day after that is the start of the 13242 // next year, so truncate the JD to get the noon of the day before the day with 13243 //the equinox on it, then add 0.5 to get the midnight of that day 13244 while (!(Math.floor(ret.equinox) + 0.5 <= jd && jd < Math.floor(nexteq) + 0.5)) { 13245 ret.equinox = nexteq; 13246 guess++; 13247 // nexteq = Math.floor(this._tehranEquinox(guess)); 13248 nexteq = this._tehranEquinox(guess); 13249 } 13250 13251 // Mean solar tropical year is 365.24219878 days 13252 ret.year = Math.round((ret.equinox - this.epoch - 1) / 365.24219878) + 1; 13253 13254 return ret; 13255 }; 13256 13257 /** 13258 * Calculate the Rata Die (fixed day) number of the given date from the 13259 * date components. 13260 * 13261 * @protected 13262 * @param {Object} date the date components to calculate the RD from 13263 */ 13264 PersRataDie.prototype._setDateComponents = function(date) { 13265 var adr, guess, jd; 13266 13267 // Mean solar tropical year is 365.24219878 days 13268 guess = this.epoch + 1 + 365.24219878 * (date.year - 2); 13269 adr = {year: date.year - 1, equinox: 0}; 13270 13271 while (adr.year < date.year) { 13272 adr = this._getYear(guess); 13273 guess = adr.equinox + (365.24219878 + 2); 13274 } 13275 13276 jd = Math.floor(adr.equinox) + 13277 ((date.month <= 7) ? 13278 ((date.month - 1) * 31) : 13279 (((date.month - 1) * 30) + 6) 13280 ) + 13281 (date.day - 1 + 0.5); // add 0.5 so that we convert JDs, which start at noon to RDs which start at midnight 13282 13283 jd += (date.hour * 3600000 + 13284 date.minute * 60000 + 13285 date.second * 1000 + 13286 date.millisecond) / 13287 86400000; 13288 13289 this.rd = jd - this.epoch; 13290 }; 13291 13292 /** 13293 * Return the rd number of the particular day of the week on or before the 13294 * given rd. eg. The Sunday on or before the given rd. 13295 * @private 13296 * @param {number} rd the rata die date of the reference date 13297 * @param {number} dayOfWeek the day of the week that is being sought relative 13298 * to the current date 13299 * @return {number} the rd of the day of the week 13300 */ 13301 PersRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 13302 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); 13303 }; 13304 13305 13306 /*< PersianCal.js */ 13307 /* 13308 * persianastro.js - Represent a Persian astronomical (Hijjri) calendar object. 13309 * 13310 * Copyright © 2014-2015, JEDLSoft 13311 * 13312 * Licensed under the Apache License, Version 2.0 (the "License"); 13313 * you may not use this file except in compliance with the License. 13314 * You may obtain a copy of the License at 13315 * 13316 * http://www.apache.org/licenses/LICENSE-2.0 13317 * 13318 * Unless required by applicable law or agreed to in writing, software 13319 * distributed under the License is distributed on an "AS IS" BASIS, 13320 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13321 * 13322 * See the License for the specific language governing permissions and 13323 * limitations under the License. 13324 */ 13325 13326 13327 /* !depends 13328 Calendar.js 13329 PersRataDie.js 13330 ilib.js 13331 MathUtils.js 13332 */ 13333 13334 13335 13336 13337 /** 13338 * @class 13339 * Construct a new Persian astronomical (Hijjri) calendar object. This class encodes 13340 * information about a Persian calendar. This class differs from the 13341 * Persian calendar in that the leap years are calculated based on the 13342 * astronomical observations of the sun in Teheran, instead of calculating 13343 * the leap years based on a regular cyclical rhythm algorithm.<p> 13344 * 13345 * 13346 * @constructor 13347 * @extends Calendar 13348 */ 13349 var PersianCal = function() { 13350 this.type = "persian"; 13351 }; 13352 13353 /** 13354 * @private 13355 * @const 13356 * @type Array.<number> 13357 * the lengths of each month 13358 */ 13359 PersianCal.monthLengths = [ 13360 31, // Farvardin 13361 31, // Ordibehesht 13362 31, // Khordad 13363 31, // Tir 13364 31, // Mordad 13365 31, // Shahrivar 13366 30, // Mehr 13367 30, // Aban 13368 30, // Azar 13369 30, // Dey 13370 30, // Bahman 13371 29 // Esfand 13372 ]; 13373 13374 /** 13375 * Return the number of months in the given year. The number of months in a year varies 13376 * for some luni-solar calendars because in some years, an extra month is needed to extend the 13377 * days in a year to an entire solar year. The month is represented as a 1-based number 13378 * where 1=first month, 2=second month, etc. 13379 * 13380 * @param {number} year a year for which the number of months is sought 13381 * @return {number} The number of months in the given year 13382 */ 13383 PersianCal.prototype.getNumMonths = function(year) { 13384 return 12; 13385 }; 13386 13387 /** 13388 * Return the number of days in a particular month in a particular year. This function 13389 * can return a different number for a month depending on the year because of things 13390 * like leap years. 13391 * 13392 * @param {number} month the month for which the length is sought 13393 * @param {number} year the year within which that month can be found 13394 * @return {number} the number of days within the given month in the given year 13395 */ 13396 PersianCal.prototype.getMonLength = function(month, year) { 13397 if (month !== 12 || !this.isLeapYear(year)) { 13398 return PersianCal.monthLengths[month-1]; 13399 } else { 13400 // Month 12, Esfand, has 30 days instead of 29 in leap years 13401 return 30; 13402 } 13403 }; 13404 13405 /** 13406 * Return true if the given year is a leap year in the Persian astronomical calendar. 13407 * @param {number} year the year for which the leap year information is being sought 13408 * @return {boolean} true if the given year is a leap year 13409 */ 13410 PersianCal.prototype.isLeapYear = function(year) { 13411 var rdNextYear = new PersRataDie({ 13412 cal: this, 13413 year: year + 1, 13414 month: 1, 13415 day: 1, 13416 hour: 0, 13417 minute: 0, 13418 second: 0, 13419 millisecond: 0 13420 }); 13421 var rdThisYear = new PersRataDie({ 13422 cal: this, 13423 year: year, 13424 month: 1, 13425 day: 1, 13426 hour: 0, 13427 minute: 0, 13428 second: 0, 13429 millisecond: 0 13430 }); 13431 return (rdNextYear.getRataDie() - rdThisYear.getRataDie()) > 365; 13432 }; 13433 13434 /** 13435 * Return the type of this calendar. 13436 * 13437 * @return {string} the name of the type of this calendar 13438 */ 13439 PersianCal.prototype.getType = function() { 13440 return this.type; 13441 }; 13442 13443 /** 13444 * Return a date instance for this calendar type using the given 13445 * options. 13446 * @param {Object} options options controlling the construction of 13447 * the date instance 13448 * @return {IDate} a date appropriate for this calendar type 13449 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 13450 */ 13451 PersianCal.prototype.newDateInstance = function (options) { 13452 return new PersianDate(options); 13453 }; 13454 13455 /* register this calendar for the factory method */ 13456 Calendar._constructors["persian"] = PersianCal; 13457 13458 13459 /*< PersianDate.js */ 13460 /* 13461 * PersianDate.js - Represent a date in the Persian astronomical (Hijjri) calendar 13462 * 13463 * Copyright © 2014-2015, JEDLSoft 13464 * 13465 * Licensed under the Apache License, Version 2.0 (the "License"); 13466 * you may not use this file except in compliance with the License. 13467 * You may obtain a copy of the License at 13468 * 13469 * http://www.apache.org/licenses/LICENSE-2.0 13470 * 13471 * Unless required by applicable law or agreed to in writing, software 13472 * distributed under the License is distributed on an "AS IS" BASIS, 13473 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13474 * 13475 * See the License for the specific language governing permissions and 13476 * limitations under the License. 13477 */ 13478 13479 /* !depends 13480 ilib.js 13481 Locale.js 13482 TimeZone.js 13483 IDate.js 13484 PersRataDie.js 13485 PersianCal.js 13486 SearchUtils.js 13487 MathUtils.js 13488 LocaleInfo.js 13489 Astro.js 13490 */ 13491 13492 // !data astro 13493 13494 13495 13496 13497 /** 13498 * @class 13499 * 13500 * Construct a new Persian astronomical date object. The constructor parameters can 13501 * contain any of the following properties: 13502 * 13503 * <ul> 13504 * <li><i>unixtime<i> - sets the time of this instance according to the given 13505 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 13506 * 13507 * <li><i>julianday</i> - sets the time of this instance according to the given 13508 * Julian Day instance or the Julian Day given as a float 13509 * 13510 * <li><i>year</i> - any integer, including 0 13511 * 13512 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 13513 * 13514 * <li><i>day</i> - 1 to 31 13515 * 13516 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 13517 * is always done with an unambiguous 24 hour representation 13518 * 13519 * <li><i>minute</i> - 0 to 59 13520 * 13521 * <li><i>second</i> - 0 to 59 13522 * 13523 * <li><i>millisecond</i> - 0 to 999 13524 * 13525 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 13526 * of this persian date. The date/time is kept in the local time. The time zone 13527 * is used later if this date is formatted according to a different time zone and 13528 * the difference has to be calculated, or when the date format has a time zone 13529 * component in it. 13530 * 13531 * <li><i>locale</i> - locale for this persian date. If the time zone is not 13532 * given, it can be inferred from this locale. For locales that span multiple 13533 * time zones, the one with the largest population is chosen as the one that 13534 * represents the locale. 13535 * 13536 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 13537 * </ul> 13538 * 13539 * If the constructor is called with another Persian date instance instead of 13540 * a parameter block, the other instance acts as a parameter block and its 13541 * settings are copied into the current instance.<p> 13542 * 13543 * If the constructor is called with no arguments at all or if none of the 13544 * properties listed above 13545 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 13546 * components are 13547 * filled in with the current date at the time of instantiation. Note that if 13548 * you do not give the time zone when defaulting to the current time and the 13549 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 13550 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 13551 * Mean Time").<p> 13552 * 13553 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 13554 * specified in the params, it is assumed that they have the smallest possible 13555 * value in the range for the property (zero or one).<p> 13556 * 13557 * 13558 * @constructor 13559 * @extends IDate 13560 * @param {Object=} params parameters that govern the settings and behaviour of this Persian date 13561 */ 13562 var PersianDate = function(params) { 13563 this.cal = new PersianCal(); 13564 this.timezone = "local"; 13565 13566 if (params) { 13567 if (params.locale) { 13568 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 13569 var li = new LocaleInfo(this.locale); 13570 this.timezone = li.getTimeZone(); 13571 } 13572 if (params.timezone) { 13573 this.timezone = params.timezone; 13574 } 13575 } 13576 13577 Astro.initAstro( 13578 params && typeof(params.sync) === 'boolean' ? params.sync : true, 13579 params && params.loadParams, 13580 ilib.bind(this, function (x) { 13581 if (params && (params.year || params.month || params.day || params.hour || 13582 params.minute || params.second || params.millisecond)) { 13583 /** 13584 * Year in the Persian calendar. 13585 * @type number 13586 */ 13587 this.year = parseInt(params.year, 10) || 0; 13588 13589 /** 13590 * The month number, ranging from 1 to 12 13591 * @type number 13592 */ 13593 this.month = parseInt(params.month, 10) || 1; 13594 13595 /** 13596 * The day of the month. This ranges from 1 to 31. 13597 * @type number 13598 */ 13599 this.day = parseInt(params.day, 10) || 1; 13600 13601 /** 13602 * The hour of the day. This can be a number from 0 to 23, as times are 13603 * stored unambiguously in the 24-hour clock. 13604 * @type number 13605 */ 13606 this.hour = parseInt(params.hour, 10) || 0; 13607 13608 /** 13609 * The minute of the hours. Ranges from 0 to 59. 13610 * @type number 13611 */ 13612 this.minute = parseInt(params.minute, 10) || 0; 13613 13614 /** 13615 * The second of the minute. Ranges from 0 to 59. 13616 * @type number 13617 */ 13618 this.second = parseInt(params.second, 10) || 0; 13619 13620 /** 13621 * The millisecond of the second. Ranges from 0 to 999. 13622 * @type number 13623 */ 13624 this.millisecond = parseInt(params.millisecond, 10) || 0; 13625 13626 /** 13627 * The day of the year. Ranges from 1 to 366. 13628 * @type number 13629 */ 13630 this.dayOfYear = parseInt(params.dayOfYear, 10); 13631 13632 if (typeof(params.dst) === 'boolean') { 13633 this.dst = params.dst; 13634 } 13635 13636 this.rd = this.newRd(this); 13637 13638 // add the time zone offset to the rd to convert to UTC 13639 if (!this.tz) { 13640 this.tz = new TimeZone({id: this.timezone}); 13641 } 13642 // getOffsetMillis requires that this.year, this.rd, and this.dst 13643 // are set in order to figure out which time zone rules apply and 13644 // what the offset is at that point in the year 13645 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 13646 if (this.offset !== 0) { 13647 this.rd = this.newRd({ 13648 rd: this.rd.getRataDie() - this.offset 13649 }); 13650 } 13651 } 13652 13653 if (!this.rd) { 13654 this.rd = this.newRd(params); 13655 this._calcDateComponents(); 13656 } 13657 13658 if (params && typeof(params.onLoad) === 'function') { 13659 params.onLoad(this); 13660 } 13661 }) 13662 ); 13663 }; 13664 13665 PersianDate.prototype = new IDate({noinstance: true}); 13666 PersianDate.prototype.parent = IDate; 13667 PersianDate.prototype.constructor = PersianDate; 13668 13669 /** 13670 * @private 13671 * @const 13672 * @type Array.<number> 13673 * the cumulative lengths of each month, for a non-leap year 13674 */ 13675 PersianDate.cumMonthLengths = [ 13676 0, // Farvardin 13677 31, // Ordibehesht 13678 62, // Khordad 13679 93, // Tir 13680 124, // Mordad 13681 155, // Shahrivar 13682 186, // Mehr 13683 216, // Aban 13684 246, // Azar 13685 276, // Dey 13686 306, // Bahman 13687 336, // Esfand 13688 366 13689 ]; 13690 13691 /** 13692 * Return a new RD for this date type using the given params. 13693 * @protected 13694 * @param {Object=} params the parameters used to create this rata die instance 13695 * @returns {RataDie} the new RD instance for the given params 13696 */ 13697 PersianDate.prototype.newRd = function (params) { 13698 return new PersRataDie(params); 13699 }; 13700 13701 /** 13702 * Return the year for the given RD 13703 * @protected 13704 * @param {number} rd RD to calculate from 13705 * @returns {number} the year for the RD 13706 */ 13707 PersianDate.prototype._calcYear = function(rd) { 13708 var julianday = rd + this.rd.epoch; 13709 return this.rd._getYear(julianday).year; 13710 }; 13711 13712 /** 13713 * @private 13714 * Calculate date components for the given RD date. 13715 */ 13716 PersianDate.prototype._calcDateComponents = function () { 13717 var remainder, 13718 rd = this.rd.getRataDie(); 13719 13720 this.year = this._calcYear(rd); 13721 13722 if (typeof(this.offset) === "undefined") { 13723 // now offset the RD by the time zone, then recalculate in case we were 13724 // near the year boundary 13725 if (!this.tz) { 13726 this.tz = new TimeZone({id: this.timezone}); 13727 } 13728 this.offset = this.tz.getOffsetMillis(this) / 86400000; 13729 } 13730 13731 if (this.offset !== 0) { 13732 rd += this.offset; 13733 this.year = this._calcYear(rd); 13734 } 13735 13736 //console.log("PersDate.calcComponent: calculating for rd " + rd); 13737 //console.log("PersDate.calcComponent: year is " + ret.year); 13738 var yearStart = this.newRd({ 13739 year: this.year, 13740 month: 1, 13741 day: 1, 13742 hour: 0, 13743 minute: 0, 13744 second: 0, 13745 millisecond: 0 13746 }); 13747 remainder = rd - yearStart.getRataDie() + 1; 13748 13749 this.dayOfYear = remainder; 13750 13751 //console.log("PersDate.calcComponent: remainder is " + remainder); 13752 13753 this.month = SearchUtils.bsearch(Math.floor(remainder), PersianDate.cumMonthLengths); 13754 remainder -= PersianDate.cumMonthLengths[this.month-1]; 13755 13756 //console.log("PersDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 13757 13758 this.day = Math.floor(remainder); 13759 remainder -= this.day; 13760 13761 //console.log("PersDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 13762 13763 // now convert to milliseconds for the rest of the calculation 13764 remainder = Math.round(remainder * 86400000); 13765 13766 this.hour = Math.floor(remainder/3600000); 13767 remainder -= this.hour * 3600000; 13768 13769 this.minute = Math.floor(remainder/60000); 13770 remainder -= this.minute * 60000; 13771 13772 this.second = Math.floor(remainder/1000); 13773 remainder -= this.second * 1000; 13774 13775 this.millisecond = remainder; 13776 }; 13777 13778 /** 13779 * Return the day of the week of this date. The day of the week is encoded 13780 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 13781 * 13782 * @return {number} the day of the week 13783 */ 13784 PersianDate.prototype.getDayOfWeek = function() { 13785 var rd = Math.floor(this.getRataDie()); 13786 return MathUtils.mod(rd-3, 7); 13787 }; 13788 13789 /** 13790 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 13791 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 13792 * December 31st is 365 in regular years, or 366 in leap years. 13793 * @return {number} the ordinal day of the year 13794 */ 13795 PersianDate.prototype.getDayOfYear = function() { 13796 return PersianDate.cumMonthLengths[this.month-1] + this.day; 13797 }; 13798 13799 /** 13800 * Return the era for this date as a number. The value for the era for Persian 13801 * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno 13802 * persico or AP). 13803 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, 13804 * there is a year 0, so any years that are negative or zero are BP. 13805 * @return {number} 1 if this date is in the common era, -1 if it is before the 13806 * common era 13807 */ 13808 PersianDate.prototype.getEra = function() { 13809 return (this.year < 1) ? -1 : 1; 13810 }; 13811 13812 /** 13813 * Return the name of the calendar that governs this date. 13814 * 13815 * @return {string} a string giving the name of the calendar 13816 */ 13817 PersianDate.prototype.getCalendar = function() { 13818 return "persian"; 13819 }; 13820 13821 // register with the factory method 13822 IDate._constructors["persian"] = PersianDate; 13823 13824 13825 /*< PersianAlgoCal.js */ 13826 /* 13827 * persian.js - Represent a Persian algorithmic calendar object. 13828 * 13829 * Copyright © 2014-2015, JEDLSoft 13830 * 13831 * Licensed under the Apache License, Version 2.0 (the "License"); 13832 * you may not use this file except in compliance with the License. 13833 * You may obtain a copy of the License at 13834 * 13835 * http://www.apache.org/licenses/LICENSE-2.0 13836 * 13837 * Unless required by applicable law or agreed to in writing, software 13838 * distributed under the License is distributed on an "AS IS" BASIS, 13839 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13840 * 13841 * See the License for the specific language governing permissions and 13842 * limitations under the License. 13843 */ 13844 13845 13846 /* !depends ilib.js Calendar.js MathUtils.js */ 13847 13848 13849 /** 13850 * @class 13851 * Construct a new Persian algorithmic calendar object. This class encodes information about 13852 * a Persian algorithmic calendar.<p> 13853 * 13854 * 13855 * @constructor 13856 * @extends Calendar 13857 */ 13858 var PersianAlgoCal = function() { 13859 this.type = "persian-algo"; 13860 }; 13861 13862 /** 13863 * @private 13864 * @const 13865 * @type Array.<number> 13866 * the lengths of each month 13867 */ 13868 PersianAlgoCal.monthLengths = [ 13869 31, // Farvardin 13870 31, // Ordibehesht 13871 31, // Khordad 13872 31, // Tir 13873 31, // Mordad 13874 31, // Shahrivar 13875 30, // Mehr 13876 30, // Aban 13877 30, // Azar 13878 30, // Dey 13879 30, // Bahman 13880 29 // Esfand 13881 ]; 13882 13883 /** 13884 * Return the number of months in the given year. The number of months in a year varies 13885 * for some luni-solar calendars because in some years, an extra month is needed to extend the 13886 * days in a year to an entire solar year. The month is represented as a 1-based number 13887 * where 1=first month, 2=second month, etc. 13888 * 13889 * @param {number} year a year for which the number of months is sought 13890 * @return {number} The number of months in the given year 13891 */ 13892 PersianAlgoCal.prototype.getNumMonths = function(year) { 13893 return 12; 13894 }; 13895 13896 /** 13897 * Return the number of days in a particular month in a particular year. This function 13898 * can return a different number for a month depending on the year because of things 13899 * like leap years. 13900 * 13901 * @param {number} month the month for which the length is sought 13902 * @param {number} year the year within which that month can be found 13903 * @return {number} the number of days within the given month in the given year 13904 */ 13905 PersianAlgoCal.prototype.getMonLength = function(month, year) { 13906 if (month !== 12 || !this.isLeapYear(year)) { 13907 return PersianAlgoCal.monthLengths[month-1]; 13908 } else { 13909 // Month 12, Esfand, has 30 days instead of 29 in leap years 13910 return 30; 13911 } 13912 }; 13913 13914 /** 13915 * Return the equivalent year in the 2820 year cycle that begins on 13916 * Far 1, 474. This particular cycle obeys the cycle-of-years formula 13917 * whereas the others do not specifically. This cycle can be used as 13918 * a proxy for other years outside of the cycle by shifting them into 13919 * the cycle. 13920 * @param {number} year year to find the equivalent cycle year for 13921 * @returns {number} the equivalent cycle year 13922 */ 13923 PersianAlgoCal.prototype.equivalentCycleYear = function(year) { 13924 var y = year - (year >= 0 ? 474 : 473); 13925 return MathUtils.mod(y, 2820) + 474; 13926 }; 13927 13928 /** 13929 * Return true if the given year is a leap year in the Persian calendar. 13930 * The year parameter may be given as a number, or as a PersAlgoDate object. 13931 * @param {number} year the year for which the leap year information is being sought 13932 * @return {boolean} true if the given year is a leap year 13933 */ 13934 PersianAlgoCal.prototype.isLeapYear = function(year) { 13935 return (MathUtils.mod((this.equivalentCycleYear(year) + 38) * 682, 2816) < 682); 13936 }; 13937 13938 /** 13939 * Return the type of this calendar. 13940 * 13941 * @return {string} the name of the type of this calendar 13942 */ 13943 PersianAlgoCal.prototype.getType = function() { 13944 return this.type; 13945 }; 13946 13947 /** 13948 * Return a date instance for this calendar type using the given 13949 * options. 13950 * @param {Object} options options controlling the construction of 13951 * the date instance 13952 * @return {IDate} a date appropriate for this calendar type 13953 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 13954 */ 13955 PersianAlgoCal.prototype.newDateInstance = function (options) { 13956 return new PersianAlgoDate(options); 13957 }; 13958 13959 /* register this calendar for the factory method */ 13960 Calendar._constructors["persian-algo"] = PersianAlgoCal; 13961 13962 13963 /*< PersAlgoRataDie.js */ 13964 /* 13965 * PersAlsoRataDie.js - Represent an RD date in the Persian algorithmic calendar 13966 * 13967 * Copyright © 2014-2015, JEDLSoft 13968 * 13969 * Licensed under the Apache License, Version 2.0 (the "License"); 13970 * you may not use this file except in compliance with the License. 13971 * You may obtain a copy of the License at 13972 * 13973 * http://www.apache.org/licenses/LICENSE-2.0 13974 * 13975 * Unless required by applicable law or agreed to in writing, software 13976 * distributed under the License is distributed on an "AS IS" BASIS, 13977 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13978 * 13979 * See the License for the specific language governing permissions and 13980 * limitations under the License. 13981 */ 13982 13983 /* !depends 13984 PersianAlgoCal.js 13985 MathUtils.js 13986 RataDie.js 13987 */ 13988 13989 13990 /** 13991 * @class 13992 * Construct a new Persian RD date number object. The constructor parameters can 13993 * contain any of the following properties: 13994 * 13995 * <ul> 13996 * <li><i>unixtime<i> - sets the time of this instance according to the given 13997 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 13998 * 13999 * <li><i>julianday</i> - sets the time of this instance according to the given 14000 * Julian Day instance or the Julian Day given as a float 14001 * 14002 * <li><i>year</i> - any integer, including 0 14003 * 14004 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 14005 * 14006 * <li><i>day</i> - 1 to 31 14007 * 14008 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 14009 * is always done with an unambiguous 24 hour representation 14010 * 14011 * <li><i>minute</i> - 0 to 59 14012 * 14013 * <li><i>second</i> - 0 to 59 14014 * 14015 * <li><i>millisecond</i> - 0 to 999 14016 * 14017 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14018 * </ul> 14019 * 14020 * If the constructor is called with another Persian date instance instead of 14021 * a parameter block, the other instance acts as a parameter block and its 14022 * settings are copied into the current instance.<p> 14023 * 14024 * If the constructor is called with no arguments at all or if none of the 14025 * properties listed above are present, then the RD is calculate based on 14026 * the current date at the time of instantiation. <p> 14027 * 14028 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14029 * specified in the params, it is assumed that they have the smallest possible 14030 * value in the range for the property (zero or one).<p> 14031 * 14032 * 14033 * @private 14034 * @constructor 14035 * @extends RataDie 14036 * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date 14037 */ 14038 var PersAlgoRataDie = function(params) { 14039 this.cal = params && params.cal || new PersianAlgoCal(); 14040 this.rd = undefined; 14041 RataDie.call(this, params); 14042 }; 14043 14044 PersAlgoRataDie.prototype = new RataDie(); 14045 PersAlgoRataDie.prototype.parent = RataDie; 14046 PersAlgoRataDie.prototype.constructor = PersAlgoRataDie; 14047 14048 /** 14049 * The difference between a zero Julian day and the first Persian date 14050 * @private 14051 * @const 14052 * @type number 14053 */ 14054 PersAlgoRataDie.prototype.epoch = 1948319.5; 14055 14056 /** 14057 * @private 14058 * @const 14059 * @type Array.<number> 14060 * the cumulative lengths of each month, for a non-leap year 14061 */ 14062 PersAlgoRataDie.cumMonthLengths = [ 14063 0, // Farvardin 14064 31, // Ordibehesht 14065 62, // Khordad 14066 93, // Tir 14067 124, // Mordad 14068 155, // Shahrivar 14069 186, // Mehr 14070 216, // Aban 14071 246, // Azar 14072 276, // Dey 14073 306, // Bahman 14074 336, // Esfand 14075 365 14076 ]; 14077 14078 /** 14079 * Calculate the Rata Die (fixed day) number of the given date from the 14080 * date components. 14081 * 14082 * @protected 14083 * @param {Object} date the date components to calculate the RD from 14084 */ 14085 PersAlgoRataDie.prototype._setDateComponents = function(date) { 14086 var year = this.cal.equivalentCycleYear(date.year); 14087 var y = date.year - (date.year >= 0 ? 474 : 473); 14088 var rdOfYears = 1029983 * Math.floor(y/2820) + 365 * (year - 1) + Math.floor((682 * year - 110) / 2816); 14089 var dayInYear = (date.month > 1 ? PersAlgoRataDie.cumMonthLengths[date.month-1] : 0) + date.day; 14090 var rdtime = (date.hour * 3600000 + 14091 date.minute * 60000 + 14092 date.second * 1000 + 14093 date.millisecond) / 14094 86400000; 14095 14096 /* 14097 // console.log("getRataDie: converting " + JSON.stringify(this)); 14098 console.log("getRataDie: year is " + year); 14099 console.log("getRataDie: rd of years is " + rdOfYears); 14100 console.log("getRataDie: day in year is " + dayInYear); 14101 console.log("getRataDie: rdtime is " + rdtime); 14102 console.log("getRataDie: rd is " + (rdOfYears + dayInYear + rdtime)); 14103 */ 14104 14105 this.rd = rdOfYears + dayInYear + rdtime; 14106 }; 14107 14108 /** 14109 * Return the rd number of the particular day of the week on or before the 14110 * given rd. eg. The Sunday on or before the given rd. 14111 * @private 14112 * @param {number} rd the rata die date of the reference date 14113 * @param {number} dayOfWeek the day of the week that is being sought relative 14114 * to the current date 14115 * @return {number} the rd of the day of the week 14116 */ 14117 PersAlgoRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 14118 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); 14119 }; 14120 14121 14122 /*< PersianAlgoDate.js */ 14123 /* 14124 * PersianAlgoDate.js - Represent a date in the Persian algorithmic calendar 14125 * 14126 * Copyright © 2014-2015, JEDLSoft 14127 * 14128 * Licensed under the Apache License, Version 2.0 (the "License"); 14129 * you may not use this file except in compliance with the License. 14130 * You may obtain a copy of the License at 14131 * 14132 * http://www.apache.org/licenses/LICENSE-2.0 14133 * 14134 * Unless required by applicable law or agreed to in writing, software 14135 * distributed under the License is distributed on an "AS IS" BASIS, 14136 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14137 * 14138 * See the License for the specific language governing permissions and 14139 * limitations under the License. 14140 */ 14141 14142 /* !depends 14143 ilib.js 14144 Locale.js 14145 LocaleInfo.js 14146 TimeZone.js 14147 IDate.js 14148 PersianAlgoCal.js 14149 SearchUtils.js 14150 MathUtils.js 14151 PersAlgoRataDie.js 14152 */ 14153 14154 14155 14156 14157 /** 14158 * @class 14159 * 14160 * Construct a new Persian date object. The constructor parameters can 14161 * contain any of the following properties: 14162 * 14163 * <ul> 14164 * <li><i>unixtime<i> - sets the time of this instance according to the given 14165 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 14166 * 14167 * <li><i>julianday</i> - sets the time of this instance according to the given 14168 * Julian Day instance or the Julian Day given as a float 14169 * 14170 * <li><i>year</i> - any integer, including 0 14171 * 14172 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 14173 * 14174 * <li><i>day</i> - 1 to 31 14175 * 14176 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 14177 * is always done with an unambiguous 24 hour representation 14178 * 14179 * <li><i>minute</i> - 0 to 59 14180 * 14181 * <li><i>second</i> - 0 to 59 14182 * 14183 * <li><i>millisecond</i> - 0 to 999 14184 * 14185 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 14186 * of this persian date. The date/time is kept in the local time. The time zone 14187 * is used later if this date is formatted according to a different time zone and 14188 * the difference has to be calculated, or when the date format has a time zone 14189 * component in it. 14190 * 14191 * <li><i>locale</i> - locale for this persian date. If the time zone is not 14192 * given, it can be inferred from this locale. For locales that span multiple 14193 * time zones, the one with the largest population is chosen as the one that 14194 * represents the locale. 14195 * 14196 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14197 * </ul> 14198 * 14199 * If the constructor is called with another Persian date instance instead of 14200 * a parameter block, the other instance acts as a parameter block and its 14201 * settings are copied into the current instance.<p> 14202 * 14203 * If the constructor is called with no arguments at all or if none of the 14204 * properties listed above 14205 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 14206 * components are 14207 * filled in with the current date at the time of instantiation. Note that if 14208 * you do not give the time zone when defaulting to the current time and the 14209 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 14210 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 14211 * Mean Time").<p> 14212 * 14213 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14214 * specified in the params, it is assumed that they have the smallest possible 14215 * value in the range for the property (zero or one).<p> 14216 * 14217 * 14218 * @constructor 14219 * @extends IDate 14220 * @param {Object=} params parameters that govern the settings and behaviour of this Persian date 14221 */ 14222 var PersianAlgoDate = function(params) { 14223 this.cal = new PersianAlgoCal(); 14224 this.timezone = "local"; 14225 14226 if (params) { 14227 if (params.locale) { 14228 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 14229 var li = new LocaleInfo(this.locale); 14230 this.timezone = li.getTimeZone(); 14231 } 14232 if (params.timezone) { 14233 this.timezone = params.timezone; 14234 } 14235 14236 if (params.year || params.month || params.day || params.hour || 14237 params.minute || params.second || params.millisecond ) { 14238 /** 14239 * Year in the Persian calendar. 14240 * @type number 14241 */ 14242 this.year = parseInt(params.year, 10) || 0; 14243 14244 /** 14245 * The month number, ranging from 1 to 12 14246 * @type number 14247 */ 14248 this.month = parseInt(params.month, 10) || 1; 14249 14250 /** 14251 * The day of the month. This ranges from 1 to 31. 14252 * @type number 14253 */ 14254 this.day = parseInt(params.day, 10) || 1; 14255 14256 /** 14257 * The hour of the day. This can be a number from 0 to 23, as times are 14258 * stored unambiguously in the 24-hour clock. 14259 * @type number 14260 */ 14261 this.hour = parseInt(params.hour, 10) || 0; 14262 14263 /** 14264 * The minute of the hours. Ranges from 0 to 59. 14265 * @type number 14266 */ 14267 this.minute = parseInt(params.minute, 10) || 0; 14268 14269 /** 14270 * The second of the minute. Ranges from 0 to 59. 14271 * @type number 14272 */ 14273 this.second = parseInt(params.second, 10) || 0; 14274 14275 /** 14276 * The millisecond of the second. Ranges from 0 to 999. 14277 * @type number 14278 */ 14279 this.millisecond = parseInt(params.millisecond, 10) || 0; 14280 14281 /** 14282 * The day of the year. Ranges from 1 to 366. 14283 * @type number 14284 */ 14285 this.dayOfYear = parseInt(params.dayOfYear, 10); 14286 14287 if (typeof(params.dst) === 'boolean') { 14288 this.dst = params.dst; 14289 } 14290 14291 this.rd = this.newRd(this); 14292 14293 // add the time zone offset to the rd to convert to UTC 14294 if (!this.tz) { 14295 this.tz = new TimeZone({id: this.timezone}); 14296 } 14297 // getOffsetMillis requires that this.year, this.rd, and this.dst 14298 // are set in order to figure out which time zone rules apply and 14299 // what the offset is at that point in the year 14300 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 14301 if (this.offset !== 0) { 14302 this.rd = this.newRd({ 14303 rd: this.rd.getRataDie() - this.offset 14304 }); 14305 } 14306 } 14307 } 14308 14309 if (!this.rd) { 14310 this.rd = this.newRd(params); 14311 this._calcDateComponents(); 14312 } 14313 }; 14314 14315 PersianAlgoDate.prototype = new IDate({noinstance: true}); 14316 PersianAlgoDate.prototype.parent = IDate; 14317 PersianAlgoDate.prototype.constructor = PersianAlgoDate; 14318 14319 /** 14320 * Return a new RD for this date type using the given params. 14321 * @protected 14322 * @param {Object=} params the parameters used to create this rata die instance 14323 * @returns {RataDie} the new RD instance for the given params 14324 */ 14325 PersianAlgoDate.prototype.newRd = function (params) { 14326 return new PersAlgoRataDie(params); 14327 }; 14328 14329 /** 14330 * Return the year for the given RD 14331 * @protected 14332 * @param {number} rd RD to calculate from 14333 * @returns {number} the year for the RD 14334 */ 14335 PersianAlgoDate.prototype._calcYear = function(rd) { 14336 var shiftedRd = rd - 173126; 14337 var numberOfCycles = Math.floor(shiftedRd / 1029983); 14338 var shiftedDayInCycle = MathUtils.mod(shiftedRd, 1029983); 14339 var yearInCycle = (shiftedDayInCycle === 1029982) ? 2820 : Math.floor((2816 * shiftedDayInCycle + 1031337) / 1028522); 14340 var year = 474 + 2820 * numberOfCycles + yearInCycle; 14341 return (year > 0) ? year : year - 1; 14342 }; 14343 14344 /** 14345 * @private 14346 * Calculate date components for the given RD date. 14347 */ 14348 PersianAlgoDate.prototype._calcDateComponents = function () { 14349 var remainder, 14350 rd = this.rd.getRataDie(); 14351 14352 this.year = this._calcYear(rd); 14353 14354 if (typeof(this.offset) === "undefined") { 14355 // now offset the RD by the time zone, then recalculate in case we were 14356 // near the year boundary 14357 if (!this.tz) { 14358 this.tz = new TimeZone({id: this.timezone}); 14359 } 14360 this.offset = this.tz.getOffsetMillis(this) / 86400000; 14361 } 14362 14363 if (this.offset !== 0) { 14364 rd += this.offset; 14365 this.year = this._calcYear(rd); 14366 } 14367 14368 //console.log("PersAlgoDate.calcComponent: calculating for rd " + rd); 14369 //console.log("PersAlgoDate.calcComponent: year is " + ret.year); 14370 var yearStart = this.newRd({ 14371 year: this.year, 14372 month: 1, 14373 day: 1, 14374 hour: 0, 14375 minute: 0, 14376 second: 0, 14377 millisecond: 0 14378 }); 14379 remainder = rd - yearStart.getRataDie() + 1; 14380 14381 this.dayOfYear = remainder; 14382 14383 //console.log("PersAlgoDate.calcComponent: remainder is " + remainder); 14384 14385 this.month = SearchUtils.bsearch(remainder, PersAlgoRataDie.cumMonthLengths); 14386 remainder -= PersAlgoRataDie.cumMonthLengths[this.month-1]; 14387 14388 //console.log("PersAlgoDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 14389 14390 this.day = Math.floor(remainder); 14391 remainder -= this.day; 14392 14393 //console.log("PersAlgoDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 14394 14395 // now convert to milliseconds for the rest of the calculation 14396 remainder = Math.round(remainder * 86400000); 14397 14398 this.hour = Math.floor(remainder/3600000); 14399 remainder -= this.hour * 3600000; 14400 14401 this.minute = Math.floor(remainder/60000); 14402 remainder -= this.minute * 60000; 14403 14404 this.second = Math.floor(remainder/1000); 14405 remainder -= this.second * 1000; 14406 14407 this.millisecond = remainder; 14408 }; 14409 14410 /** 14411 * Return the day of the week of this date. The day of the week is encoded 14412 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 14413 * 14414 * @return {number} the day of the week 14415 */ 14416 PersianAlgoDate.prototype.getDayOfWeek = function() { 14417 var rd = Math.floor(this.getRataDie()); 14418 return MathUtils.mod(rd-3, 7); 14419 }; 14420 14421 /** 14422 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 14423 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 14424 * December 31st is 365 in regular years, or 366 in leap years. 14425 * @return {number} the ordinal day of the year 14426 */ 14427 PersianAlgoDate.prototype.getDayOfYear = function() { 14428 return PersAlgoRataDie.cumMonthLengths[this.month-1] + this.day; 14429 }; 14430 14431 /** 14432 * Return the era for this date as a number. The value for the era for Persian 14433 * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno 14434 * persico or AP). 14435 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, 14436 * there is a year 0, so any years that are negative or zero are BP. 14437 * @return {number} 1 if this date is in the common era, -1 if it is before the 14438 * common era 14439 */ 14440 PersianAlgoDate.prototype.getEra = function() { 14441 return (this.year < 1) ? -1 : 1; 14442 }; 14443 14444 /** 14445 * Return the name of the calendar that governs this date. 14446 * 14447 * @return {string} a string giving the name of the calendar 14448 */ 14449 PersianAlgoDate.prototype.getCalendar = function() { 14450 return "persian-algo"; 14451 }; 14452 14453 // register with the factory method 14454 IDate._constructors["persian-algo"] = PersianAlgoDate; 14455 14456 14457 /*< HanCal.js */ 14458 /* 14459 * han.js - Represent a Han Chinese Lunar calendar object. 14460 * 14461 * Copyright © 2014-2015, JEDLSoft 14462 * 14463 * Licensed under the Apache License, Version 2.0 (the "License"); 14464 * you may not use this file except in compliance with the License. 14465 * You may obtain a copy of the License at 14466 * 14467 * http://www.apache.org/licenses/LICENSE-2.0 14468 * 14469 * Unless required by applicable law or agreed to in writing, software 14470 * distributed under the License is distributed on an "AS IS" BASIS, 14471 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14472 * 14473 * See the License for the specific language governing permissions and 14474 * limitations under the License. 14475 */ 14476 14477 /* !depends 14478 ilib.js 14479 Calendar.js 14480 MathUtils.js 14481 Astro.js 14482 GregorianDate.js 14483 GregRataDie.js 14484 RataDie.js 14485 */ 14486 14487 14488 14489 14490 /** 14491 * @class 14492 * Construct a new Han algorithmic calendar object. This class encodes information about 14493 * a Han algorithmic calendar.<p> 14494 * 14495 * 14496 * @constructor 14497 * @param {Object=} params optional parameters to load the calendrical data 14498 * @extends Calendar 14499 */ 14500 var HanCal = function(params) { 14501 this.type = "han"; 14502 var sync = params && typeof(params.sync) === 'boolean' ? params.sync : true; 14503 14504 Astro.initAstro(sync, params && params.loadParams, /** @type {function ((Object|null)=): ?} */ ilib.bind(this, function (x) { 14505 if (params && typeof(params.callback) === 'function') { 14506 params.callback(this); 14507 } 14508 })); 14509 }; 14510 14511 /** 14512 * @protected 14513 * @static 14514 * @param {number} year 14515 * @param {number=} cycle 14516 * @return {number} 14517 */ 14518 HanCal._getElapsedYear = function(year, cycle) { 14519 var elapsedYear = year || 0; 14520 if (typeof(year) !== 'undefined' && year < 61 && typeof(cycle) !== 'undefined') { 14521 elapsedYear = 60 * cycle + year; 14522 } 14523 return elapsedYear; 14524 }; 14525 14526 /** 14527 * @protected 14528 * @static 14529 * @param {number} jd julian day to calculate from 14530 * @param {number} longitude longitude to seek 14531 * @returns {number} the julian day of the next time that the solar longitude 14532 * is a multiple of the given longitude 14533 */ 14534 HanCal._hanNextSolarLongitude = function(jd, longitude) { 14535 var tz = HanCal._chineseTZ(jd); 14536 var uni = Astro._universalFromLocal(jd, tz); 14537 var sol = Astro._nextSolarLongitude(uni, longitude); 14538 return Astro._localFromUniversal(sol, tz); 14539 }; 14540 14541 /** 14542 * @protected 14543 * @static 14544 * @param {number} jd julian day to calculate from 14545 * @returns {number} the major solar term for the julian day 14546 */ 14547 HanCal._majorSTOnOrAfter = function(jd) { 14548 var tz = HanCal._chineseTZ(jd); 14549 var uni = Astro._universalFromLocal(jd, tz); 14550 var next = Astro._fixangle(30 * Math.ceil(Astro._solarLongitude(uni)/30)); 14551 return HanCal._hanNextSolarLongitude(jd, next); 14552 }; 14553 14554 /** 14555 * @protected 14556 * @static 14557 * @param {number} year the year for which the leap year information is being sought 14558 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 14559 * cycle is not given, then the year should be given as elapsed years since the beginning 14560 * of the epoch 14561 */ 14562 HanCal._solsticeBefore = function (year, cycle) { 14563 var elapsedYear = HanCal._getElapsedYear(year, cycle); 14564 var gregyear = elapsedYear - 2697; 14565 var rd = new GregRataDie({ 14566 year: gregyear-1, 14567 month: 12, 14568 day: 15, 14569 hour: 0, 14570 minute: 0, 14571 second: 0, 14572 millisecond: 0 14573 }); 14574 return HanCal._majorSTOnOrAfter(rd.getRataDie() + RataDie.gregorianEpoch); 14575 }; 14576 14577 /** 14578 * @protected 14579 * @static 14580 * @param {number} jd julian day to calculate from 14581 * @returns {number} the current major solar term 14582 */ 14583 HanCal._chineseTZ = function(jd) { 14584 var year = GregorianDate._calcYear(jd - RataDie.gregorianEpoch); 14585 return year < 1929 ? 465.6666666666666666 : 480; 14586 }; 14587 14588 /** 14589 * @protected 14590 * @static 14591 * @param {number} jd julian day to calculate from 14592 * @returns {number} the julian day of next new moon on or after the given julian day date 14593 */ 14594 HanCal._newMoonOnOrAfter = function(jd) { 14595 var tz = HanCal._chineseTZ(jd); 14596 var uni = Astro._universalFromLocal(jd, tz); 14597 var moon = Astro._newMoonAtOrAfter(uni); 14598 // floor to the start of the julian day 14599 return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); 14600 }; 14601 14602 /** 14603 * @protected 14604 * @static 14605 * @param {number} jd julian day to calculate from 14606 * @returns {number} the julian day of previous new moon before the given julian day date 14607 */ 14608 HanCal._newMoonBefore = function(jd) { 14609 var tz = HanCal._chineseTZ(jd); 14610 var uni = Astro._universalFromLocal(jd, tz); 14611 var moon = Astro._newMoonBefore(uni); 14612 // floor to the start of the julian day 14613 return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); 14614 }; 14615 14616 /** 14617 * @static 14618 * @protected 14619 * @param {number} year the year for which the leap year information is being sought 14620 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 14621 * cycle is not given, then the year should be given as elapsed years since the beginning 14622 * of the epoch 14623 */ 14624 HanCal._leapYearCalc = function(year, cycle) { 14625 var ret = { 14626 elapsedYear: HanCal._getElapsedYear(year, cycle) 14627 }; 14628 ret.solstice1 = HanCal._solsticeBefore(ret.elapsedYear); 14629 ret.solstice2 = HanCal._solsticeBefore(ret.elapsedYear+1); 14630 // ceil to the end of the julian day 14631 ret.m1 = HanCal._newMoonOnOrAfter(Astro._ceilToJD(ret.solstice1)); 14632 ret.m2 = HanCal._newMoonBefore(Astro._ceilToJD(ret.solstice2)); 14633 14634 return ret; 14635 }; 14636 14637 /** 14638 * @protected 14639 * @static 14640 * @param {number} jd julian day to calculate from 14641 * @returns {number} the current major solar term 14642 */ 14643 HanCal._currentMajorST = function(jd) { 14644 var s = Astro._solarLongitude(Astro._universalFromLocal(jd, HanCal._chineseTZ(jd))); 14645 return MathUtils.amod(2 + Math.floor(s/30), 12); 14646 }; 14647 14648 /** 14649 * @protected 14650 * @static 14651 * @param {number} jd julian day to calculate from 14652 * @returns {boolean} true if there is no major solar term in the same year 14653 */ 14654 HanCal._noMajorST = function(jd) { 14655 return HanCal._currentMajorST(jd) === HanCal._currentMajorST(HanCal._newMoonOnOrAfter(jd+1)); 14656 }; 14657 14658 /** 14659 * Return the number of months in the given year. The number of months in a year varies 14660 * for some luni-solar calendars because in some years, an extra month is needed to extend the 14661 * days in a year to an entire solar year. The month is represented as a 1-based number 14662 * where 1=first month, 2=second month, etc. 14663 * 14664 * @param {number} year a year for which the number of months is sought 14665 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 14666 * cycle is not given, then the year should be given as elapsed years since the beginning 14667 * of the epoch 14668 * @return {number} The number of months in the given year 14669 */ 14670 HanCal.prototype.getNumMonths = function(year, cycle) { 14671 return this.isLeapYear(year, cycle) ? 13 : 12; 14672 }; 14673 14674 /** 14675 * Return the number of days in a particular month in a particular year. This function 14676 * can return a different number for a month depending on the year because of things 14677 * like leap years. 14678 * 14679 * @param {number} month the elapsed month for which the length is sought 14680 * @param {number} year the elapsed year within which that month can be found 14681 * @return {number} the number of days within the given month in the given year 14682 */ 14683 HanCal.prototype.getMonLength = function(month, year) { 14684 // distance between two new moons in Nanjing China 14685 var calc = HanCal._leapYearCalc(year); 14686 var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + month * 29); 14687 var postNewMoon = HanCal._newMoonOnOrAfter(priorNewMoon + 1); 14688 return postNewMoon - priorNewMoon; 14689 }; 14690 14691 /** 14692 * Return the equivalent year in the 2820 year cycle that begins on 14693 * Far 1, 474. This particular cycle obeys the cycle-of-years formula 14694 * whereas the others do not specifically. This cycle can be used as 14695 * a proxy for other years outside of the cycle by shifting them into 14696 * the cycle. 14697 * @param {number} year year to find the equivalent cycle year for 14698 * @returns {number} the equivalent cycle year 14699 */ 14700 HanCal.prototype.equivalentCycleYear = function(year) { 14701 var y = year - (year >= 0 ? 474 : 473); 14702 return MathUtils.mod(y, 2820) + 474; 14703 }; 14704 14705 /** 14706 * Return true if the given year is a leap year in the Han calendar. 14707 * If the year is given as a year/cycle combination, then the year should be in the 14708 * range [1,60] and the given cycle is the cycle in which the year is located. If 14709 * the year is greater than 60, then 14710 * it represents the total number of years elapsed in the proleptic calendar since 14711 * the beginning of the Chinese epoch in on 15 Feb, -2636 (Gregorian). In this 14712 * case, the cycle parameter is ignored. 14713 * 14714 * @param {number} year the year for which the leap year information is being sought 14715 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 14716 * cycle is not given, then the year should be given as elapsed years since the beginning 14717 * of the epoch 14718 * @return {boolean} true if the given year is a leap year 14719 */ 14720 HanCal.prototype.isLeapYear = function(year, cycle) { 14721 var calc = HanCal._leapYearCalc(year, cycle); 14722 return Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 14723 }; 14724 14725 /** 14726 * Return the month of the year that is the leap month. If the given year is 14727 * not a leap year, then this method will return -1. 14728 * 14729 * @param {number} year the year for which the leap year information is being sought 14730 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 14731 * cycle is not given, then the year should be given as elapsed years since the beginning 14732 * of the epoch 14733 * @return {number} the number of the month that is doubled in this leap year, or -1 14734 * if this is not a leap year 14735 */ 14736 HanCal.prototype.getLeapMonth = function(year, cycle) { 14737 var calc = HanCal._leapYearCalc(year, cycle); 14738 14739 if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) != 12) { 14740 return -1; // no leap month 14741 } 14742 14743 // search between rd1 and rd2 for the first month with no major solar term. That is our leap month. 14744 var month = 0; 14745 var m = HanCal._newMoonOnOrAfter(calc.m1+1); 14746 while (!HanCal._noMajorST(m)) { 14747 month++; 14748 m = HanCal._newMoonOnOrAfter(m+1); 14749 } 14750 14751 // return the number of the month that is doubled 14752 return month; 14753 }; 14754 14755 /** 14756 * Return the date of Chinese New Years in the given calendar year. 14757 * 14758 * @param {number} year the Chinese year for which the new year information is being sought 14759 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 14760 * cycle is not given, then the year should be given as elapsed years since the beginning 14761 * of the epoch 14762 * @return {number} the julian day of the beginning of the given year 14763 */ 14764 HanCal.prototype.newYears = function(year, cycle) { 14765 var calc = HanCal._leapYearCalc(year, cycle); 14766 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 14767 if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12 && 14768 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { 14769 return HanCal._newMoonOnOrAfter(m2+1); 14770 } 14771 return m2; 14772 }; 14773 14774 /** 14775 * Return the type of this calendar. 14776 * 14777 * @return {string} the name of the type of this calendar 14778 */ 14779 HanCal.prototype.getType = function() { 14780 return this.type; 14781 }; 14782 14783 /** 14784 * Return a date instance for this calendar type using the given 14785 * options. 14786 * @param {Object} options options controlling the construction of 14787 * the date instance 14788 * @return {HanDate} a date appropriate for this calendar type 14789 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 14790 */ 14791 HanCal.prototype.newDateInstance = function (options) { 14792 return new HanDate(options); 14793 }; 14794 14795 /* register this calendar for the factory method */ 14796 Calendar._constructors["han"] = HanCal; 14797 14798 14799 /*< HanRataDie.js */ 14800 /* 14801 * HanDate.js - Represent a date in the Han algorithmic calendar 14802 * 14803 * Copyright © 2014-2015, JEDLSoft 14804 * 14805 * Licensed under the Apache License, Version 2.0 (the "License"); 14806 * you may not use this file except in compliance with the License. 14807 * You may obtain a copy of the License at 14808 * 14809 * http://www.apache.org/licenses/LICENSE-2.0 14810 * 14811 * Unless required by applicable law or agreed to in writing, software 14812 * distributed under the License is distributed on an "AS IS" BASIS, 14813 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14814 * 14815 * See the License for the specific language governing permissions and 14816 * limitations under the License. 14817 */ 14818 14819 /* !depends 14820 ilib.js 14821 HanCal.js 14822 MathUtils.js 14823 RataDie.js 14824 */ 14825 14826 14827 /** 14828 * Construct a new Han RD date number object. The constructor parameters can 14829 * contain any of the following properties: 14830 * 14831 * <ul> 14832 * <li><i>unixtime<i> - sets the time of this instance according to the given 14833 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 14834 * 14835 * <li><i>julianday</i> - sets the time of this instance according to the given 14836 * Julian Day instance or the Julian Day given as a float 14837 * 14838 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 14839 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 14840 * linear count of years since the beginning of the epoch, much like other calendars. This linear 14841 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 14842 * to 60 and treated as if it were a year in the regular 60-year cycle. 14843 * 14844 * <li><i>year</i> - any integer, including 0 14845 * 14846 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 14847 * 14848 * <li><i>day</i> - 1 to 31 14849 * 14850 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 14851 * is always done with an unambiguous 24 hour representation 14852 * 14853 * <li><i>minute</i> - 0 to 59 14854 * 14855 * <li><i>second</i> - 0 to 59 14856 * 14857 * <li><i>millisecond</i> - 0 to 999 14858 * 14859 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14860 * </ul> 14861 * 14862 * If the constructor is called with another Han date instance instead of 14863 * a parameter block, the other instance acts as a parameter block and its 14864 * settings are copied into the current instance.<p> 14865 * 14866 * If the constructor is called with no arguments at all or if none of the 14867 * properties listed above are present, then the RD is calculate based on 14868 * the current date at the time of instantiation. <p> 14869 * 14870 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14871 * specified in the params, it is assumed that they have the smallest possible 14872 * value in the range for the property (zero or one).<p> 14873 * 14874 * 14875 * @private 14876 * @class 14877 * @constructor 14878 * @extends RataDie 14879 * @param {Object=} params parameters that govern the settings and behaviour of this Han RD date 14880 */ 14881 var HanRataDie = function(params) { 14882 this.rd = undefined; 14883 if (params && params.cal) { 14884 this.cal = params.cal; 14885 RataDie.call(this, params); 14886 if (params && typeof(params.callback) === 'function') { 14887 params.callback(this); 14888 } 14889 } else { 14890 new HanCal({ 14891 sync: params && params.sync, 14892 loadParams: params && params.loadParams, 14893 callback: ilib.bind(this, function(c) { 14894 this.cal = c; 14895 RataDie.call(this, params); 14896 if (params && typeof(params.callback) === 'function') { 14897 params.callback(this); 14898 } 14899 }) 14900 }); 14901 } 14902 }; 14903 14904 HanRataDie.prototype = new RataDie(); 14905 HanRataDie.prototype.parent = RataDie; 14906 HanRataDie.prototype.constructor = HanRataDie; 14907 14908 /** 14909 * The difference between a zero Julian day and the first Han date 14910 * which is February 15, -2636 (Gregorian). 14911 * @private 14912 * @const 14913 * @type number 14914 */ 14915 HanRataDie.epoch = 758325.5; 14916 14917 /** 14918 * Calculate the Rata Die (fixed day) number of the given date from the 14919 * date components. 14920 * 14921 * @protected 14922 * @param {Object} date the date components to calculate the RD from 14923 */ 14924 HanRataDie.prototype._setDateComponents = function(date) { 14925 var calc = HanCal._leapYearCalc(date.year, date.cycle); 14926 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 14927 var newYears; 14928 this.leapYear = (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12); 14929 if (this.leapYear && (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { 14930 newYears = HanCal._newMoonOnOrAfter(m2+1); 14931 } else { 14932 newYears = m2; 14933 } 14934 14935 var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + date.month * 29); // this is a julian day 14936 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(priorNewMoon)); 14937 this.leapMonth = (this.leapYear && HanCal._noMajorST(priorNewMoon) && !this.priorLeapMonth); 14938 14939 var rdtime = (date.hour * 3600000 + 14940 date.minute * 60000 + 14941 date.second * 1000 + 14942 date.millisecond) / 14943 86400000; 14944 14945 /* 14946 console.log("getRataDie: converting " + JSON.stringify(date) + " to an RD"); 14947 console.log("getRataDie: year is " + date.year + " plus cycle " + date.cycle); 14948 console.log("getRataDie: isLeapYear is " + this.leapYear); 14949 console.log("getRataDie: priorNewMoon is " + priorNewMoon); 14950 console.log("getRataDie: day in month is " + date.day); 14951 console.log("getRataDie: rdtime is " + rdtime); 14952 console.log("getRataDie: rd is " + (priorNewMoon + date.day - 1 + rdtime)); 14953 */ 14954 14955 this.rd = priorNewMoon + date.day - 1 + rdtime - RataDie.gregorianEpoch; 14956 }; 14957 14958 /** 14959 * Return the rd number of the particular day of the week on or before the 14960 * given rd. eg. The Sunday on or before the given rd. 14961 * @private 14962 * @param {number} rd the rata die date of the reference date 14963 * @param {number} dayOfWeek the day of the week that is being sought relative 14964 * to the current date 14965 * @return {number} the rd of the day of the week 14966 */ 14967 HanRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 14968 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); 14969 }; 14970 14971 /** 14972 * @protected 14973 * @static 14974 * @param {number} jd1 first julian day 14975 * @param {number} jd2 second julian day 14976 * @returns {boolean} true if there is a leap month earlier in the same year 14977 * as the given months 14978 */ 14979 HanRataDie._priorLeapMonth = function(jd1, jd2) { 14980 return jd2 >= jd1 && 14981 (HanRataDie._priorLeapMonth(jd1, HanCal._newMoonBefore(jd2)) || 14982 HanCal._noMajorST(jd2)); 14983 }; 14984 14985 14986 14987 /*< HanDate.js */ 14988 /* 14989 * HanDate.js - Represent a date in the Han algorithmic calendar 14990 * 14991 * Copyright © 2014-2015, JEDLSoft 14992 * 14993 * Licensed under the Apache License, Version 2.0 (the "License"); 14994 * you may not use this file except in compliance with the License. 14995 * You may obtain a copy of the License at 14996 * 14997 * http://www.apache.org/licenses/LICENSE-2.0 14998 * 14999 * Unless required by applicable law or agreed to in writing, software 15000 * distributed under the License is distributed on an "AS IS" BASIS, 15001 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15002 * 15003 * See the License for the specific language governing permissions and 15004 * limitations under the License. 15005 */ 15006 15007 /* !depends 15008 ilib.js 15009 IDate.js 15010 GregorianDate.js 15011 HanCal.js 15012 Astro.js 15013 JSUtils.js 15014 MathUtils.js 15015 LocaleInfo.js 15016 Locale.js 15017 TimeZone.js 15018 HanRataDie.js 15019 RataDie.js 15020 */ 15021 15022 15023 15024 15025 /** 15026 * @class 15027 * 15028 * Construct a new Han date object. The constructor parameters can 15029 * contain any of the following properties: 15030 * 15031 * <ul> 15032 * <li><i>unixtime<i> - sets the time of this instance according to the given 15033 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 15034 * 15035 * <li><i>julianday</i> - sets the time of this instance according to the given 15036 * Julian Day instance or the Julian Day given as a float 15037 * 15038 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 15039 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 15040 * linear count of years since the beginning of the epoch, much like other calendars. This linear 15041 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 15042 * to 60 and treated as if it were a year in the regular 60-year cycle. 15043 * 15044 * <li><i>year</i> - any integer, including 0 15045 * 15046 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 15047 * 15048 * <li><i>day</i> - 1 to 31 15049 * 15050 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15051 * is always done with an unambiguous 24 hour representation 15052 * 15053 * <li><i>minute</i> - 0 to 59 15054 * 15055 * <li><i>second</i> - 0 to 59 15056 * 15057 * <li><i>millisecond</i> - 0 to 999 15058 * 15059 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 15060 * of this han date. The date/time is kept in the local time. The time zone 15061 * is used later if this date is formatted according to a different time zone and 15062 * the difference has to be calculated, or when the date format has a time zone 15063 * component in it. 15064 * 15065 * <li><i>locale</i> - locale for this han date. If the time zone is not 15066 * given, it can be inferred from this locale. For locales that span multiple 15067 * time zones, the one with the largest population is chosen as the one that 15068 * represents the locale. 15069 * 15070 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15071 * </ul> 15072 * 15073 * If the constructor is called with another Han date instance instead of 15074 * a parameter block, the other instance acts as a parameter block and its 15075 * settings are copied into the current instance.<p> 15076 * 15077 * If the constructor is called with no arguments at all or if none of the 15078 * properties listed above 15079 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 15080 * components are 15081 * filled in with the current date at the time of instantiation. Note that if 15082 * you do not give the time zone when defaulting to the current time and the 15083 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 15084 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 15085 * Mean Time").<p> 15086 * 15087 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 15088 * specified in the params, it is assumed that they have the smallest possible 15089 * value in the range for the property (zero or one).<p> 15090 * 15091 * 15092 * @constructor 15093 * @extends Date 15094 * @param {Object=} params parameters that govern the settings and behaviour of this Han date 15095 */ 15096 var HanDate = function(params) { 15097 this.timezone = "local"; 15098 if (params) { 15099 if (params.locale) { 15100 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 15101 var li = new LocaleInfo(this.locale); 15102 this.timezone = li.getTimeZone(); 15103 } 15104 if (params.timezone) { 15105 this.timezone = params.timezone; 15106 } 15107 } 15108 15109 new HanCal({ 15110 sync: params && typeof(params) === 'boolean' ? params.sync : true, 15111 loadParams: params && params.loadParams, 15112 callback: ilib.bind(this, function (cal) { 15113 this.cal = cal; 15114 15115 if (params && (params.year || params.month || params.day || params.hour || 15116 params.minute || params.second || params.millisecond || params.cycle || params.cycleYear)) { 15117 if (typeof(params.cycle) !== 'undefined') { 15118 /** 15119 * Cycle number in the Han calendar. 15120 * @type number 15121 */ 15122 this.cycle = parseInt(params.cycle, 10) || 0; 15123 15124 var year = (typeof(params.year) !== 'undefined' ? parseInt(params.year, 10) : parseInt(params.cycleYear, 10)) || 0; 15125 15126 /** 15127 * Year in the Han calendar. 15128 * @type number 15129 */ 15130 this.year = HanCal._getElapsedYear(year, this.cycle); 15131 } else { 15132 if (typeof(params.year) !== 'undefined') { 15133 this.year = parseInt(params.year, 10) || 0; 15134 this.cycle = Math.floor((this.year - 1) / 60); 15135 } else { 15136 this.year = this.cycle = 0; 15137 } 15138 } 15139 15140 /** 15141 * The month number, ranging from 1 to 13 15142 * @type number 15143 */ 15144 this.month = parseInt(params.month, 10) || 1; 15145 15146 /** 15147 * The day of the month. This ranges from 1 to 30. 15148 * @type number 15149 */ 15150 this.day = parseInt(params.day, 10) || 1; 15151 15152 /** 15153 * The hour of the day. This can be a number from 0 to 23, as times are 15154 * stored unambiguously in the 24-hour clock. 15155 * @type number 15156 */ 15157 this.hour = parseInt(params.hour, 10) || 0; 15158 15159 /** 15160 * The minute of the hours. Ranges from 0 to 59. 15161 * @type number 15162 */ 15163 this.minute = parseInt(params.minute, 10) || 0; 15164 15165 /** 15166 * The second of the minute. Ranges from 0 to 59. 15167 * @type number 15168 */ 15169 this.second = parseInt(params.second, 10) || 0; 15170 15171 /** 15172 * The millisecond of the second. Ranges from 0 to 999. 15173 * @type number 15174 */ 15175 this.millisecond = parseInt(params.millisecond, 10) || 0; 15176 15177 // derived properties 15178 15179 /** 15180 * Year in the cycle of the Han calendar 15181 * @type number 15182 */ 15183 this.cycleYear = MathUtils.amod(this.year, 60); 15184 15185 /** 15186 * The day of the year. Ranges from 1 to 384. 15187 * @type number 15188 */ 15189 this.dayOfYear = parseInt(params.dayOfYear, 10); 15190 15191 if (typeof(params.dst) === 'boolean') { 15192 this.dst = params.dst; 15193 } 15194 15195 this.newRd({ 15196 cal: this.cal, 15197 cycle: this.cycle, 15198 year: this.year, 15199 month: this.month, 15200 day: this.day, 15201 hour: this.hour, 15202 minute: this.minute, 15203 second: this.second, 15204 millisecond: this.millisecond, 15205 sync: params && typeof(params.sync) === 'boolean' ? params.sync : true, 15206 loadParams: params && params.loadParams, 15207 callback: ilib.bind(this, function (rd) { 15208 if (rd) { 15209 this.rd = rd; 15210 15211 // add the time zone offset to the rd to convert to UTC 15212 if (!this.tz) { 15213 this.tz = new TimeZone({id: this.timezone}); 15214 } 15215 // getOffsetMillis requires that this.year, this.rd, and this.dst 15216 // are set in order to figure out which time zone rules apply and 15217 // what the offset is at that point in the year 15218 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 15219 if (this.offset !== 0) { 15220 this.rd = this.newRd({ 15221 cal: this.cal, 15222 rd: this.rd.getRataDie() - this.offset 15223 }); 15224 this._calcLeap(); 15225 } else { 15226 // re-use the derived properties from the RD calculations 15227 this.leapMonth = this.rd.leapMonth; 15228 this.priorLeapMonth = this.rd.priorLeapMonth; 15229 this.leapYear = this.rd.leapYear; 15230 } 15231 } 15232 15233 if (!this.rd) { 15234 this.rd = this.newRd(JSUtils.merge(params || {}, { 15235 cal: this.cal 15236 })); 15237 this._calcDateComponents(); 15238 } 15239 15240 if (params && typeof(params.onLoad) === 'function') { 15241 params.onLoad(this); 15242 } 15243 }) 15244 }); 15245 } else { 15246 if (!this.rd) { 15247 this.rd = this.newRd(JSUtils.merge(params || {}, { 15248 cal: this.cal 15249 })); 15250 this._calcDateComponents(); 15251 } 15252 15253 if (params && typeof(params.onLoad) === 'function') { 15254 params.onLoad(this); 15255 } 15256 } 15257 }) 15258 }); 15259 15260 }; 15261 15262 HanDate.prototype = new IDate({noinstance: true}); 15263 HanDate.prototype.parent = IDate; 15264 HanDate.prototype.constructor = HanDate; 15265 15266 /** 15267 * Return a new RD for this date type using the given params. 15268 * @protected 15269 * @param {Object=} params the parameters used to create this rata die instance 15270 * @returns {RataDie} the new RD instance for the given params 15271 */ 15272 HanDate.prototype.newRd = function (params) { 15273 return new HanRataDie(params); 15274 }; 15275 15276 /** 15277 * Return the year for the given RD 15278 * @protected 15279 * @param {number} rd RD to calculate from 15280 * @returns {number} the year for the RD 15281 */ 15282 HanDate.prototype._calcYear = function(rd) { 15283 var gregdate = new GregorianDate({ 15284 rd: rd, 15285 timezone: this.timezone 15286 }); 15287 var hanyear = gregdate.year + 2697; 15288 var newYears = this.cal.newYears(hanyear); 15289 return hanyear - ((rd + RataDie.gregorianEpoch < newYears) ? 1 : 0); 15290 }; 15291 15292 /** 15293 * @private 15294 * Calculate the leap year and months from the RD. 15295 */ 15296 HanDate.prototype._calcLeap = function() { 15297 var jd = this.rd.getRataDie() + RataDie.gregorianEpoch; 15298 15299 var calc = HanCal._leapYearCalc(this.year); 15300 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15301 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15302 15303 var newYears = (this.leapYear && 15304 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 15305 HanCal._newMoonOnOrAfter(m2+1) : m2; 15306 15307 var m = HanCal._newMoonBefore(jd + 1); 15308 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); 15309 this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); 15310 }; 15311 15312 /** 15313 * @private 15314 * Calculate date components for the given RD date. 15315 */ 15316 HanDate.prototype._calcDateComponents = function () { 15317 var remainder, 15318 jd = this.rd.getRataDie() + RataDie.gregorianEpoch; 15319 15320 // console.log("HanDate._calcDateComponents: calculating for jd " + jd); 15321 15322 if (typeof(this.offset) === "undefined") { 15323 // now offset the jd by the time zone, then recalculate in case we were 15324 // near the year boundary 15325 if (!this.tz) { 15326 this.tz = new TimeZone({id: this.timezone}); 15327 } 15328 this.offset = this.tz.getOffsetMillis(this) / 86400000; 15329 } 15330 15331 if (this.offset !== 0) { 15332 jd += this.offset; 15333 } 15334 15335 // use the Gregorian calendar objects as a convenient way to short-cut some 15336 // of the date calculations 15337 15338 var gregyear = GregorianDate._calcYear(this.rd.getRataDie()); 15339 this.year = gregyear + 2697; 15340 var calc = HanCal._leapYearCalc(this.year); 15341 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15342 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15343 var newYears = (this.leapYear && 15344 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 15345 HanCal._newMoonOnOrAfter(m2+1) : m2; 15346 15347 // See if it's between Jan 1 and the Chinese new years of that Gregorian year. If 15348 // so, then the Han year is actually the previous one 15349 if (jd < newYears) { 15350 this.year--; 15351 calc = HanCal._leapYearCalc(this.year); 15352 m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15353 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15354 newYears = (this.leapYear && 15355 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 15356 HanCal._newMoonOnOrAfter(m2+1) : m2; 15357 } 15358 // month is elapsed month, not the month number + leap month boolean 15359 var m = HanCal._newMoonBefore(jd + 1); 15360 this.month = Math.round((m - calc.m1) / 29.530588853000001); 15361 15362 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); 15363 this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); 15364 15365 this.cycle = Math.floor((this.year - 1) / 60); 15366 this.cycleYear = MathUtils.amod(this.year, 60); 15367 this.day = Astro._floorToJD(jd) - m + 1; 15368 15369 /* 15370 console.log("HanDate._calcDateComponents: year is " + this.year); 15371 console.log("HanDate._calcDateComponents: isLeapYear is " + this.leapYear); 15372 console.log("HanDate._calcDateComponents: cycle is " + this.cycle); 15373 console.log("HanDate._calcDateComponents: cycleYear is " + this.cycleYear); 15374 console.log("HanDate._calcDateComponents: month is " + this.month); 15375 console.log("HanDate._calcDateComponents: isLeapMonth is " + this.leapMonth); 15376 console.log("HanDate._calcDateComponents: day is " + this.day); 15377 */ 15378 15379 // floor to the start of the julian day 15380 remainder = jd - Astro._floorToJD(jd); 15381 15382 // console.log("HanDate._calcDateComponents: time remainder is " + remainder); 15383 15384 // now convert to milliseconds for the rest of the calculation 15385 remainder = Math.round(remainder * 86400000); 15386 15387 this.hour = Math.floor(remainder/3600000); 15388 remainder -= this.hour * 3600000; 15389 15390 this.minute = Math.floor(remainder/60000); 15391 remainder -= this.minute * 60000; 15392 15393 this.second = Math.floor(remainder/1000); 15394 remainder -= this.second * 1000; 15395 15396 this.millisecond = remainder; 15397 }; 15398 15399 /** 15400 * Return the year within the Chinese cycle of this date. Cycles are 60 15401 * years long, and the value returned from this method is the number of the year 15402 * within this cycle. The year returned from getYear() is the total elapsed 15403 * years since the beginning of the Chinese epoch and does not include 15404 * the cycles. 15405 * 15406 * @return {number} the year within the current Chinese cycle 15407 */ 15408 HanDate.prototype.getCycleYears = function() { 15409 return this.cycleYear; 15410 }; 15411 15412 /** 15413 * Return the Chinese cycle number of this date. Cycles are 60 years long, 15414 * and the value returned from getCycleYear() is the number of the year 15415 * within this cycle. The year returned from getYear() is the total elapsed 15416 * years since the beginning of the Chinese epoch and does not include 15417 * the cycles. 15418 * 15419 * @return {number} the current Chinese cycle 15420 */ 15421 HanDate.prototype.getCycles = function() { 15422 return this.cycle; 15423 }; 15424 15425 /** 15426 * Return whether the year of this date is a leap year in the Chinese Han 15427 * calendar. 15428 * 15429 * @return {boolean} true if the year of this date is a leap year in the 15430 * Chinese Han calendar. 15431 */ 15432 HanDate.prototype.isLeapYear = function() { 15433 return this.leapYear; 15434 }; 15435 15436 /** 15437 * Return whether the month of this date is a leap month in the Chinese Han 15438 * calendar. 15439 * 15440 * @return {boolean} true if the month of this date is a leap month in the 15441 * Chinese Han calendar. 15442 */ 15443 HanDate.prototype.isLeapMonth = function() { 15444 return this.leapMonth; 15445 }; 15446 15447 /** 15448 * Return the day of the week of this date. The day of the week is encoded 15449 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 15450 * 15451 * @return {number} the day of the week 15452 */ 15453 HanDate.prototype.getDayOfWeek = function() { 15454 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 15455 return MathUtils.mod(rd, 7); 15456 }; 15457 15458 /** 15459 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 15460 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 15461 * December 31st is 365 in regular years, or 366 in leap years. 15462 * @return {number} the ordinal day of the year 15463 */ 15464 HanDate.prototype.getDayOfYear = function() { 15465 var newYears = this.cal.newYears(this.year); 15466 var priorNewMoon = HanCal._newMoonOnOrAfter(newYears + (this.month -1) * 29); 15467 return priorNewMoon - newYears + this.day; 15468 }; 15469 15470 /** 15471 * Return the era for this date as a number. The value for the era for Han 15472 * calendars is -1 for "before the han era" (BP) and 1 for "the han era" (anno 15473 * persico or AP). 15474 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Han calendar, 15475 * there is a year 0, so any years that are negative or zero are BP. 15476 * @return {number} 1 if this date is in the common era, -1 if it is before the 15477 * common era 15478 */ 15479 HanDate.prototype.getEra = function() { 15480 return (this.year < 1) ? -1 : 1; 15481 }; 15482 15483 /** 15484 * Return the name of the calendar that governs this date. 15485 * 15486 * @return {string} a string giving the name of the calendar 15487 */ 15488 HanDate.prototype.getCalendar = function() { 15489 return "han"; 15490 }; 15491 15492 // register with the factory method 15493 IDate._constructors["han"] = HanDate; 15494 15495 15496 /*< EthiopicCal.js */ 15497 /* 15498 * ethiopic.js - Represent a Ethiopic calendar object. 15499 * 15500 * Copyright © 2015, JEDLSoft 15501 * 15502 * Licensed under the Apache License, Version 2.0 (the "License"); 15503 * you may not use this file except in compliance with the License. 15504 * You may obtain a copy of the License at 15505 * 15506 * http://www.apache.org/licenses/LICENSE-2.0 15507 * 15508 * Unless required by applicable law or agreed to in writing, software 15509 * distributed under the License is distributed on an "AS IS" BASIS, 15510 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15511 * 15512 * See the License for the specific language governing permissions and 15513 * limitations under the License. 15514 */ 15515 15516 /* !depends ilib.js Calendar.js Utils.js MathUtils.js */ 15517 15518 15519 15520 /** 15521 * @class 15522 * Construct a new Ethiopic calendar object. This class encodes information about 15523 * a Ethiopic calendar.<p> 15524 * 15525 * 15526 * @constructor 15527 * @extends Calendar 15528 */ 15529 var EthiopicCal = function() { 15530 this.type = "ethiopic"; 15531 }; 15532 15533 /** 15534 * Return the number of months in the given year. The number of months in a year varies 15535 * for lunar calendars because in some years, an extra month is needed to extend the 15536 * days in a year to an entire solar year. The month is represented as a 1-based number 15537 * where 1=Maskaram, 2=Teqemt, etc. until 13=Paguemen. 15538 * 15539 * @param {number} year a year for which the number of months is sought 15540 */ 15541 EthiopicCal.prototype.getNumMonths = function(year) { 15542 return 13; 15543 }; 15544 15545 /** 15546 * Return the number of days in a particular month in a particular year. This function 15547 * can return a different number for a month depending on the year because of things 15548 * like leap years. 15549 * 15550 * @param {number|string} month the month for which the length is sought 15551 * @param {number} year the year within which that month can be found 15552 * @return {number} the number of days within the given month in the given year 15553 */ 15554 EthiopicCal.prototype.getMonLength = function(month, year) { 15555 var m = month; 15556 switch (typeof(m)) { 15557 case "string": 15558 m = parseInt(m, 10); 15559 break; 15560 case "function": 15561 case "object": 15562 case "undefined": 15563 return 30; 15564 break; 15565 } 15566 if (m < 13) { 15567 return 30; 15568 } else { 15569 return this.isLeapYear(year) ? 6 : 5; 15570 } 15571 }; 15572 15573 /** 15574 * Return true if the given year is a leap year in the Ethiopic calendar. 15575 * The year parameter may be given as a number, or as a JulDate object. 15576 * @param {number|EthiopicDate|string} year the year for which the leap year information is being sought 15577 * @return {boolean} true if the given year is a leap year 15578 */ 15579 EthiopicCal.prototype.isLeapYear = function(year) { 15580 var y = year; 15581 switch (typeof(y)) { 15582 case "string": 15583 y = parseInt(y, 10); 15584 break; 15585 case "object": 15586 if (typeof(y.year) !== "number") { // in case it is an ilib.Date object 15587 return false; 15588 } 15589 y = y.year; 15590 break; 15591 case "function": 15592 case "undefined": 15593 return false; 15594 break; 15595 } 15596 return MathUtils.mod(y, 4) === 3; 15597 }; 15598 15599 /** 15600 * Return the type of this calendar. 15601 * 15602 * @return {string} the name of the type of this calendar 15603 */ 15604 EthiopicCal.prototype.getType = function() { 15605 return this.type; 15606 }; 15607 15608 /** 15609 * Return a date instance for this calendar type using the given 15610 * options. 15611 * @param {Object} options options controlling the construction of 15612 * the date instance 15613 * @return {IDate} a date appropriate for this calendar type 15614 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 15615 */ 15616 EthiopicCal.prototype.newDateInstance = function (options) { 15617 return new EthiopicDate(options); 15618 }; 15619 15620 /* register this calendar for the factory method */ 15621 Calendar._constructors["ethiopic"] = EthiopicCal; 15622 15623 15624 /*< EthiopicRataDie.js */ 15625 /* 15626 * EthiopicRataDie.js - Represent an RD date in the Ethiopic calendar 15627 * 15628 * Copyright © 2015, JEDLSoft 15629 * 15630 * Licensed under the Apache License, Version 2.0 (the "License"); 15631 * you may not use this file except in compliance with the License. 15632 * You may obtain a copy of the License at 15633 * 15634 * http://www.apache.org/licenses/LICENSE-2.0 15635 * 15636 * Unless required by applicable law or agreed to in writing, software 15637 * distributed under the License is distributed on an "AS IS" BASIS, 15638 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15639 * 15640 * See the License for the specific language governing permissions and 15641 * limitations under the License. 15642 */ 15643 15644 /* !depends 15645 ilib.js 15646 EthiopicCal.js 15647 RataDie.js 15648 */ 15649 15650 15651 /** 15652 * @class 15653 * Construct a new Ethiopic RD date number object. The constructor parameters can 15654 * contain any of the following properties: 15655 * 15656 * <ul> 15657 * <li><i>unixtime<i> - sets the time of this instance according to the given 15658 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 15659 * 15660 * <li><i>julianday</i> - sets the time of this instance according to the given 15661 * Julian Day instance or the Julian Day given as a float 15662 * 15663 * <li><i>year</i> - any integer, including 0 15664 * 15665 * <li><i>month</i> - 1 to 12, where 1 means Maskaram, 2 means Teqemt, etc., and 13 means Paguemen 15666 * 15667 * <li><i>day</i> - 1 to 30 15668 * 15669 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15670 * is always done with an unambiguous 24 hour representation 15671 * 15672 * <li><i>minute</i> - 0 to 59 15673 * 15674 * <li><i>second</i> - 0 to 59 15675 * 15676 * <li><i>millisecond</i> - 0 to 999 15677 * 15678 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15679 * </ul> 15680 * 15681 * If the constructor is called with another Ethiopic date instance instead of 15682 * a parameter block, the other instance acts as a parameter block and its 15683 * settings are copied into the current instance.<p> 15684 * 15685 * If the constructor is called with no arguments at all or if none of the 15686 * properties listed above are present, then the RD is calculate based on 15687 * the current date at the time of instantiation. <p> 15688 * 15689 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 15690 * specified in the params, it is assumed that they have the smallest possible 15691 * value in the range for the property (zero or one).<p> 15692 * 15693 * 15694 * @private 15695 * @constructor 15696 * @extends RataDie 15697 * @param {Object=} params parameters that govern the settings and behaviour of this Ethiopic RD date 15698 */ 15699 var EthiopicRataDie = function(params) { 15700 this.cal = params && params.cal || new EthiopicCal(); 15701 this.rd = undefined; 15702 RataDie.call(this, params); 15703 }; 15704 15705 EthiopicRataDie.prototype = new RataDie(); 15706 EthiopicRataDie.prototype.parent = RataDie; 15707 EthiopicRataDie.prototype.constructor = EthiopicRataDie; 15708 15709 /** 15710 * The difference between the zero Julian day and the first Ethiopic date 15711 * of Friday, August 29, 8 CE Julian at 6:00am UTC.<p> 15712 * 15713 * See <a href="http://us.wow.com/wiki/Time_in_Ethiopia?s_chn=90&s_pt=aolsem&v_t=aolsem" 15714 * Time in Ethiopia</a> for information about how time is handled in Ethiopia. 15715 * 15716 * @protected 15717 * @type number 15718 */ 15719 EthiopicRataDie.prototype.epoch = 1724219.75; 15720 15721 /** 15722 * Calculate the Rata Die (fixed day) number of the given date from the 15723 * date components. 15724 * 15725 * @protected 15726 * @param {Object} date the date components to calculate the RD from 15727 */ 15728 EthiopicRataDie.prototype._setDateComponents = function(date) { 15729 var year = date.year; 15730 var years = 365 * (year - 1) + Math.floor(year/4); 15731 var dayInYear = (date.month-1) * 30 + date.day; 15732 var rdtime = (date.hour * 3600000 + 15733 date.minute * 60000 + 15734 date.second * 1000 + 15735 date.millisecond) / 15736 86400000; 15737 15738 /* 15739 console.log("calcRataDie: converting " + JSON.stringify(parts)); 15740 console.log("getRataDie: year is " + years); 15741 console.log("getRataDie: day in year is " + dayInYear); 15742 console.log("getRataDie: rdtime is " + rdtime); 15743 console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); 15744 */ 15745 15746 this.rd = years + dayInYear + rdtime; 15747 }; 15748 15749 15750 15751 /*< EthiopicDate.js */ 15752 /* 15753 * EthiopicDate.js - Represent a date in the Ethiopic calendar 15754 * 15755 * Copyright © 2015, JEDLSoft 15756 * 15757 * Licensed under the Apache License, Version 2.0 (the "License"); 15758 * you may not use this file except in compliance with the License. 15759 * You may obtain a copy of the License at 15760 * 15761 * http://www.apache.org/licenses/LICENSE-2.0 15762 * 15763 * Unless required by applicable law or agreed to in writing, software 15764 * distributed under the License is distributed on an "AS IS" BASIS, 15765 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15766 * 15767 * See the License for the specific language governing permissions and 15768 * limitations under the License. 15769 */ 15770 15771 /* !depends 15772 ilib.js 15773 IDate.js 15774 EthiopicCal.js 15775 MathUtils.js 15776 Locale.js 15777 LocaleInfo.js 15778 TimeZone.js 15779 EthiopicRataDie.js 15780 */ 15781 15782 15783 15784 /** 15785 * @class 15786 * Construct a new date object for the Ethiopic Calendar. The constructor can be called 15787 * with a parameter object that contains any of the following properties: 15788 * 15789 * <ul> 15790 * <li><i>unixtime<i> - sets the time of this instance according to the given 15791 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 15792 * <li><i>julianday</i> - the Julian Day to set into this date 15793 * <li><i>year</i> - any integer 15794 * <li><i>month</i> - 1 to 13, where 1 means Maskaram, 2 means Teqemt, etc., and 13 means Paguemen 15795 * <li><i>day</i> - 1 to 30 15796 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15797 * is always done with an unambiguous 24 hour representation 15798 * <li><i>minute</i> - 0 to 59 15799 * <li><i>second</i> - 0 to 59 15800 * <li><i>millisecond<i> - 0 to 999 15801 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 15802 * of this ethiopic date. The date/time is kept in the local time. The time zone 15803 * is used later if this date is formatted according to a different time zone and 15804 * the difference has to be calculated, or when the date format has a time zone 15805 * component in it. 15806 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 15807 * given, it can be inferred from this locale. For locales that span multiple 15808 * time zones, the one with the largest population is chosen as the one that 15809 * represents the locale. 15810 * 15811 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15812 * </ul> 15813 * 15814 * If called with another Ethiopic date argument, the date components of the given 15815 * date are copied into the current one.<p> 15816 * 15817 * If the constructor is called with no arguments at all or if none of the 15818 * properties listed above 15819 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 15820 * components are 15821 * filled in with the current date at the time of instantiation. Note that if 15822 * you do not give the time zone when defaulting to the current time and the 15823 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 15824 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 15825 * Mean Time").<p> 15826 * 15827 * 15828 * @constructor 15829 * @extends IDate 15830 * @param {Object=} params parameters that govern the settings and behaviour of this Ethiopic date 15831 */ 15832 var EthiopicDate = function(params) { 15833 this.cal = new EthiopicCal(); 15834 15835 if (params) { 15836 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 15837 // for doing inheritance, so don't need to fill in the data. The inheriting class only wants the methods. 15838 return; 15839 } 15840 if (params.locale) { 15841 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 15842 var li = new LocaleInfo(this.locale); 15843 this.timezone = li.getTimeZone(); 15844 } 15845 if (params.timezone) { 15846 this.timezone = params.timezone; 15847 } 15848 15849 if (params.year || params.month || params.day || params.hour || 15850 params.minute || params.second || params.millisecond ) { 15851 /** 15852 * Year in the Ethiopic calendar. 15853 * @type number 15854 */ 15855 this.year = parseInt(params.year, 10) || 0; 15856 /** 15857 * The month number, ranging from 1 (Maskaram) to 13 (Paguemen). 15858 * @type number 15859 */ 15860 this.month = parseInt(params.month, 10) || 1; 15861 /** 15862 * The day of the month. This ranges from 1 to 30. 15863 * @type number 15864 */ 15865 this.day = parseInt(params.day, 10) || 1; 15866 /** 15867 * The hour of the day. This can be a number from 0 to 23, as times are 15868 * stored unambiguously in the 24-hour clock. 15869 * @type number 15870 */ 15871 this.hour = parseInt(params.hour, 10) || 0; 15872 /** 15873 * The minute of the hours. Ranges from 0 to 59. 15874 * @type number 15875 */ 15876 this.minute = parseInt(params.minute, 10) || 0; 15877 /** 15878 * The second of the minute. Ranges from 0 to 59. 15879 * @type number 15880 */ 15881 this.second = parseInt(params.second, 10) || 0; 15882 /** 15883 * The millisecond of the second. Ranges from 0 to 999. 15884 * @type number 15885 */ 15886 this.millisecond = parseInt(params.millisecond, 10) || 0; 15887 15888 /** 15889 * The day of the year. Ranges from 1 to 366. 15890 * @type number 15891 */ 15892 this.dayOfYear = parseInt(params.dayOfYear, 10); 15893 15894 if (typeof(params.dst) === 'boolean') { 15895 this.dst = params.dst; 15896 } 15897 15898 this.rd = this.newRd(this); 15899 15900 // add the time zone offset to the rd to convert to UTC 15901 if (!this.tz) { 15902 this.tz = new TimeZone({id: this.timezone}); 15903 } 15904 // getOffsetMillis requires that this.year, this.rd, and this.dst 15905 // are set in order to figure out which time zone rules apply and 15906 // what the offset is at that point in the year 15907 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 15908 if (this.offset !== 0) { 15909 this.rd = this.newRd({ 15910 rd: this.rd.getRataDie() - this.offset 15911 }); 15912 } 15913 } 15914 } 15915 15916 if (!this.rd) { 15917 this.rd = this.newRd(params); 15918 this._calcDateComponents(); 15919 } 15920 }; 15921 15922 EthiopicDate.prototype = new IDate({ noinstance: true }); 15923 EthiopicDate.prototype.parent = IDate; 15924 EthiopicDate.prototype.constructor = EthiopicDate; 15925 15926 /** 15927 * Return a new RD for this date type using the given params. 15928 * @protected 15929 * @param {Object=} params the parameters used to create this rata die instance 15930 * @returns {RataDie} the new RD instance for the given params 15931 */ 15932 EthiopicDate.prototype.newRd = function (params) { 15933 return new EthiopicRataDie(params); 15934 }; 15935 15936 /** 15937 * Return the year for the given RD 15938 * @protected 15939 * @param {number} rd RD to calculate from 15940 * @returns {number} the year for the RD 15941 */ 15942 EthiopicDate.prototype._calcYear = function(rd) { 15943 var year = Math.floor((4*(Math.floor(rd)-1) + 1463)/1461); 15944 15945 return year; 15946 }; 15947 15948 /** 15949 * Calculate date components for the given RD date. 15950 * @protected 15951 */ 15952 EthiopicDate.prototype._calcDateComponents = function () { 15953 var remainder, 15954 cumulative, 15955 rd = this.rd.getRataDie(); 15956 15957 this.year = this._calcYear(rd); 15958 15959 if (typeof(this.offset) === "undefined") { 15960 this.year = this._calcYear(rd); 15961 15962 // now offset the RD by the time zone, then recalculate in case we were 15963 // near the year boundary 15964 if (!this.tz) { 15965 this.tz = new TimeZone({id: this.timezone}); 15966 } 15967 this.offset = this.tz.getOffsetMillis(this) / 86400000; 15968 } 15969 15970 if (this.offset !== 0) { 15971 rd += this.offset; 15972 this.year = this._calcYear(rd); 15973 } 15974 15975 var jan1 = this.newRd({ 15976 year: this.year, 15977 month: 1, 15978 day: 1, 15979 hour: 0, 15980 minute: 0, 15981 second: 0, 15982 millisecond: 0 15983 }); 15984 remainder = rd + 1 - jan1.getRataDie(); 15985 15986 this.month = Math.floor((remainder-1)/30) + 1; 15987 remainder = remainder - (this.month-1) * 30; 15988 15989 this.day = Math.floor(remainder); 15990 remainder -= this.day; 15991 // now convert to milliseconds for the rest of the calculation 15992 remainder = Math.round(remainder * 86400000); 15993 15994 this.hour = Math.floor(remainder/3600000); 15995 remainder -= this.hour * 3600000; 15996 15997 this.minute = Math.floor(remainder/60000); 15998 remainder -= this.minute * 60000; 15999 16000 this.second = Math.floor(remainder/1000); 16001 remainder -= this.second * 1000; 16002 16003 this.millisecond = remainder; 16004 }; 16005 16006 /** 16007 * Return the day of the week of this date. The day of the week is encoded 16008 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 16009 * 16010 * @return {number} the day of the week 16011 */ 16012 EthiopicDate.prototype.getDayOfWeek = function() { 16013 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 16014 return MathUtils.mod(rd-4, 7); 16015 }; 16016 16017 /** 16018 * Return the name of the calendar that governs this date. 16019 * 16020 * @return {string} a string giving the name of the calendar 16021 */ 16022 EthiopicDate.prototype.getCalendar = function() { 16023 return "ethiopic"; 16024 }; 16025 16026 //register with the factory method 16027 IDate._constructors["ethiopic"] = EthiopicDate; 16028 16029 16030 16031 /*< CopticCal.js */ 16032 /* 16033 * coptic.js - Represent a Coptic calendar object. 16034 * 16035 * Copyright © 2015, JEDLSoft 16036 * 16037 * Licensed under the Apache License, Version 2.0 (the "License"); 16038 * you may not use this file except in compliance with the License. 16039 * You may obtain a copy of the License at 16040 * 16041 * http://www.apache.org/licenses/LICENSE-2.0 16042 * 16043 * Unless required by applicable law or agreed to in writing, software 16044 * distributed under the License is distributed on an "AS IS" BASIS, 16045 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16046 * 16047 * See the License for the specific language governing permissions and 16048 * limitations under the License. 16049 */ 16050 16051 16052 /* !depends ilib.js Calendar.js Locale.js Utils.js EthiopicCal.js */ 16053 16054 16055 /** 16056 * @class 16057 * Construct a new Coptic calendar object. This class encodes information about 16058 * a Coptic calendar.<p> 16059 * 16060 * 16061 * @constructor 16062 * @extends EthiopicCal 16063 */ 16064 var CopticCal = function() { 16065 this.type = "coptic"; 16066 }; 16067 16068 CopticCal.prototype = new EthiopicCal(); 16069 CopticCal.prototype.parent = EthiopicCal; 16070 CopticCal.prototype.constructor = CopticCal; 16071 16072 /** 16073 * Return a date instance for this calendar type using the given 16074 * options. 16075 * @param {Object} options options controlling the construction of 16076 * the date instance 16077 * @return {IDate} a date appropriate for this calendar type 16078 * @deprecated Since 11.0.5. Use DateFactory({calendar: cal.getType(), ...}) instead 16079 */ 16080 CopticCal.prototype.newDateInstance = function (options) { 16081 return new CopticDate(options); 16082 }; 16083 16084 /* register this calendar for the factory method */ 16085 Calendar._constructors["coptic"] = CopticCal; 16086 16087 16088 /*< CopticRataDie.js */ 16089 /* 16090 * CopticRataDie.js - Represent an RD date in the Coptic calendar 16091 * 16092 * Copyright © 2015, JEDLSoft 16093 * 16094 * Licensed under the Apache License, Version 2.0 (the "License"); 16095 * you may not use this file except in compliance with the License. 16096 * You may obtain a copy of the License at 16097 * 16098 * http://www.apache.org/licenses/LICENSE-2.0 16099 * 16100 * Unless required by applicable law or agreed to in writing, software 16101 * distributed under the License is distributed on an "AS IS" BASIS, 16102 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16103 * 16104 * See the License for the specific language governing permissions and 16105 * limitations under the License. 16106 */ 16107 16108 /* !depends 16109 ilib.js 16110 CopticCal.js 16111 JSUtils.js 16112 EthiopicRataDie.js 16113 */ 16114 16115 16116 /** 16117 * @class 16118 * Construct a new Coptic RD date number object. The constructor parameters can 16119 * contain any of the following properties: 16120 * 16121 * <ul> 16122 * <li><i>unixtime<i> - sets the time of this instance according to the given 16123 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 16124 * 16125 * <li><i>julianday</i> - sets the time of this instance according to the given 16126 * Julian Day instance or the Julian Day given as a float 16127 * 16128 * <li><i>year</i> - any integer, including 0 16129 * 16130 * <li><i>month</i> - 1 to 13, where 1 means Thoout, 2 means Paope, etc., and 13 means Epagomene 16131 * 16132 * <li><i>day</i> - 1 to 30 16133 * 16134 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16135 * is always done with an unambiguous 24 hour representation 16136 * 16137 * <li><i>minute</i> - 0 to 59 16138 * 16139 * <li><i>second</i> - 0 to 59 16140 * 16141 * <li><i>millisecond</i> - 0 to 999 16142 * 16143 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16144 * </ul> 16145 * 16146 * If the constructor is called with another Coptic date instance instead of 16147 * a parameter block, the other instance acts as a parameter block and its 16148 * settings are copied into the current instance.<p> 16149 * 16150 * If the constructor is called with no arguments at all or if none of the 16151 * properties listed above are present, then the RD is calculate based on 16152 * the current date at the time of instantiation. <p> 16153 * 16154 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 16155 * specified in the params, it is assumed that they have the smallest possible 16156 * value in the range for the property (zero or one).<p> 16157 * 16158 * 16159 * @private 16160 * @constructor 16161 * @extends EthiopicRataDie 16162 * @param {Object=} params parameters that govern the settings and behaviour of this Coptic RD date 16163 */ 16164 var CopticRataDie = function(params) { 16165 this.cal = params && params.cal || new CopticCal(); 16166 this.rd = undefined; 16167 /** 16168 * The difference between the zero Julian day and the first Coptic date 16169 * of Friday, August 29, 284 CE Julian at 7:00am UTC. 16170 * @private 16171 * @const 16172 * @type number 16173 */ 16174 this.epoch = 1825028.5; 16175 16176 var tmp = {}; 16177 if (params) { 16178 JSUtils.shallowCopy(params, tmp); 16179 } 16180 tmp.cal = this.cal; // override the cal parameter that may be passed in 16181 EthiopicRataDie.call(this, tmp); 16182 }; 16183 16184 CopticRataDie.prototype = new EthiopicRataDie(); 16185 CopticRataDie.prototype.parent = EthiopicRataDie; 16186 CopticRataDie.prototype.constructor = CopticRataDie; 16187 16188 16189 /*< CopticDate.js */ 16190 /* 16191 * CopticDate.js - Represent a date in the Coptic calendar 16192 * 16193 * Copyright © 2015, JEDLSoft 16194 * 16195 * Licensed under the Apache License, Version 2.0 (the "License"); 16196 * you may not use this file except in compliance with the License. 16197 * You may obtain a copy of the License at 16198 * 16199 * http://www.apache.org/licenses/LICENSE-2.0 16200 * 16201 * Unless required by applicable law or agreed to in writing, software 16202 * distributed under the License is distributed on an "AS IS" BASIS, 16203 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16204 * 16205 * See the License for the specific language governing permissions and 16206 * limitations under the License. 16207 */ 16208 16209 /* !depends 16210 ilib.js 16211 IDate.js 16212 CopticCal.js 16213 MathUtils.js 16214 JSUtils.js 16215 Locale.js 16216 LocaleInfo.js 16217 TimeZone.js 16218 EthiopicDate.js 16219 CopticRataDie.js 16220 */ 16221 16222 16223 16224 16225 /** 16226 * @class 16227 * Construct a new date object for the Coptic 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 Thoout, 2 means Paope, etc., and 13 means Epagomene 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 coptic 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 Coptic 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 EthiopicDate 16271 * @param {Object=} params parameters that govern the settings and behaviour of this Coptic date 16272 */ 16273 var CopticDate = function(params) { 16274 this.rd = undefined; // clear these out so that the EthiopicDate constructor can set it 16275 EthiopicDate.call(this, params); 16276 this.cal = new CopticCal(); 16277 }; 16278 16279 CopticDate.prototype = new EthiopicDate({noinstance: true}); 16280 CopticDate.prototype.parent = EthiopicDate.prototype; 16281 CopticDate.prototype.constructor = CopticDate; 16282 16283 /** 16284 * Return a new RD for this date type using the given params. 16285 * @protected 16286 * @param {Object=} params the parameters used to create this rata die instance 16287 * @returns {RataDie} the new RD instance for the given params 16288 */ 16289 CopticDate.prototype.newRd = function (params) { 16290 return new CopticRataDie(params); 16291 }; 16292 16293 /** 16294 * Return the day of the week of this date. The day of the week is encoded 16295 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 16296 * 16297 * @return {number} the day of the week 16298 */ 16299 CopticDate.prototype.getDayOfWeek = function() { 16300 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 16301 return MathUtils.mod(rd-3, 7); 16302 }; 16303 16304 /** 16305 * Return the name of the calendar that governs this date. 16306 * 16307 * @return {string} a string giving the name of the calendar 16308 */ 16309 CopticDate.prototype.getCalendar = function() { 16310 return "coptic"; 16311 }; 16312 16313 //register with the factory method 16314 IDate._constructors["coptic"] = CopticDate; 16315 16316 16317 /*< CType.js */ 16318 /* 16319 * CType.js - Character type definitions 16320 * 16321 * Copyright © 2012-2015, JEDLSoft 16322 * 16323 * Licensed under the Apache License, Version 2.0 (the "License"); 16324 * you may not use this file except in compliance with the License. 16325 * You may obtain a copy of the License at 16326 * 16327 * http://www.apache.org/licenses/LICENSE-2.0 16328 * 16329 * Unless required by applicable law or agreed to in writing, software 16330 * distributed under the License is distributed on an "AS IS" BASIS, 16331 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16332 * 16333 * See the License for the specific language governing permissions and 16334 * limitations under the License. 16335 */ 16336 16337 // !depends ilib.js Locale.js SearchUtils.js Utils.js IString.js 16338 16339 // !data ctype 16340 16341 16342 /** 16343 * Provides a set of static routines that return information about characters. 16344 * These routines emulate the C-library ctype functions. The characters must be 16345 * encoded in utf-16, as no other charsets are currently supported. Only the first 16346 * character of the given string is tested. 16347 * @namespace 16348 */ 16349 var CType = {}; 16350 16351 16352 /** 16353 * Actual implementation for withinRange. Searches the given object for ranges. 16354 * The range names are taken from the Unicode range names in 16355 * http://www.unicode.org/Public/UNIDATA/extracted/DerivedGeneralCategory.txt 16356 * 16357 * <ul> 16358 * <li>Cn - Unassigned 16359 * <li>Lu - Uppercase_Letter 16360 * <li>Ll - Lowercase_Letter 16361 * <li>Lt - Titlecase_Letter 16362 * <li>Lm - Modifier_Letter 16363 * <li>Lo - Other_Letter 16364 * <li>Mn - Nonspacing_Mark 16365 * <li>Me - Enclosing_Mark 16366 * <li>Mc - Spacing_Mark 16367 * <li>Nd - Decimal_Number 16368 * <li>Nl - Letter_Number 16369 * <li>No - Other_Number 16370 * <li>Zs - Space_Separator 16371 * <li>Zl - Line_Separator 16372 * <li>Zp - Paragraph_Separator 16373 * <li>Cc - Control 16374 * <li>Cf - Format 16375 * <li>Co - Private_Use 16376 * <li>Cs - Surrogate 16377 * <li>Pd - Dash_Punctuation 16378 * <li>Ps - Open_Punctuation 16379 * <li>Pe - Close_Punctuation 16380 * <li>Pc - Connector_Punctuation 16381 * <li>Po - Other_Punctuation 16382 * <li>Sm - Math_Symbol 16383 * <li>Sc - Currency_Symbol 16384 * <li>Sk - Modifier_Symbol 16385 * <li>So - Other_Symbol 16386 * <li>Pi - Initial_Punctuation 16387 * <li>Pf - Final_Punctuation 16388 * </ul> 16389 * 16390 * @protected 16391 * @param {number} num code point of the character to examine 16392 * @param {string} rangeName the name of the range to check 16393 * @param {Object} obj object containing the character range data 16394 * @return {boolean} true if the first character is within the named 16395 * range 16396 */ 16397 CType._inRange = function(num, rangeName, obj) { 16398 var range, i; 16399 if (num < 0 || !rangeName || !obj) { 16400 return false; 16401 } 16402 16403 range = obj[rangeName]; 16404 if (!range) { 16405 return false; 16406 } 16407 16408 var compare = function(singlerange, target) { 16409 if (singlerange.length === 1) { 16410 return singlerange[0] - target; 16411 } else { 16412 return target < singlerange[0] ? singlerange[0] - target : 16413 (target > singlerange[1] ? singlerange[1] - target : 0); 16414 } 16415 }; 16416 var result = SearchUtils.bsearch(num, range, compare); 16417 return result < range.length && compare(range[result], num) === 0; 16418 }; 16419 16420 /** 16421 * Return whether or not the first character is within the named range 16422 * of Unicode characters. The valid list of range names are taken from 16423 * the Unicode 6.0 spec. Characters in all ranges of Unicode are supported, 16424 * including those supported in Javascript via UTF-16. Currently, this method 16425 * supports the following range names: 16426 * 16427 * <ul> 16428 * <li><i>ascii</i> - basic ASCII 16429 * <li><i>latin</i> - Latin, Latin Extended Additional, Latin Extended-C, Latin Extended-D 16430 * <li><i>armenian</i> 16431 * <li><i>greek</i> - Greek, Greek Extended 16432 * <li><i>cyrillic</i> - Cyrillic, Cyrillic Extended-A, Cyrillic Extended-B 16433 * <li><i>georgian</i> - Georgian, Georgian Supplement 16434 * <li><i>glagolitic</i> 16435 * <li><i>gothic</i> 16436 * <li><i>ogham</i> 16437 * <li><i>oldpersian</i> 16438 * <li><i>runic</i> 16439 * <li><i>ipa</i> - IPA, Phonetic Extensions, Phonetic Extensions Supplement 16440 * <li><i>phonetic</i> 16441 * <li><i>modifiertone</i> - Modifier Tone Letters 16442 * <li><i>spacing</i> 16443 * <li><i>diacritics</i> 16444 * <li><i>halfmarks</i> - Combining Half Marks 16445 * <li><i>small</i> - Small Form Variants 16446 * <li><i>bamum</i> - Bamum, Bamum Supplement 16447 * <li><i>ethiopic</i> - Ethiopic, Ethiopic Extended, Ethiopic Extended-A 16448 * <li><i>nko</i> 16449 * <li><i>osmanya</i> 16450 * <li><i>tifinagh</i> 16451 * <li><i>val</i> 16452 * <li><i>arabic</i> - Arabic, Arabic Supplement, Arabic Presentation Forms-A, 16453 * Arabic Presentation Forms-B 16454 * <li><i>carlan</i> 16455 * <li><i>hebrew</i> 16456 * <li><i>mandaic</i> 16457 * <li><i>samaritan</i> 16458 * <li><i>syriac</i> 16459 * <li><i>mongolian</i> 16460 * <li><i>phagspa</i> 16461 * <li><i>tibetan</i> 16462 * <li><i>bengali</i> 16463 * <li><i>devanagari</i> - Devanagari, Devanagari Extended 16464 * <li><i>gujarati</i> 16465 * <li><i>gurmukhi</i> 16466 * <li><i>kannada</i> 16467 * <li><i>lepcha</i> 16468 * <li><i>limbu</i> 16469 * <li><i>malayalam</i> 16470 * <li><i>meetaimayek</i> 16471 * <li><i>olchiki</i> 16472 * <li><i>oriya</i> 16473 * <li><i>saurashtra</i> 16474 * <li><i>sinhala</i> 16475 * <li><i>sylotinagri</i> - Syloti Nagri 16476 * <li><i>tamil</i> 16477 * <li><i>telugu</i> 16478 * <li><i>thaana</i> 16479 * <li><i>vedic</i> 16480 * <li><i>batak</i> 16481 * <li><i>balinese</i> 16482 * <li><i>buginese</i> 16483 * <li><i>cham</i> 16484 * <li><i>javanese</i> 16485 * <li><i>kayahli</i> 16486 * <li><i>khmer</i> 16487 * <li><i>lao</i> 16488 * <li><i>myanmar</i> - Myanmar, Myanmar Extended-A 16489 * <li><i>newtailue</i> 16490 * <li><i>rejang</i> 16491 * <li><i>sundanese</i> 16492 * <li><i>taile</i> 16493 * <li><i>taitham</i> 16494 * <li><i>taiviet</i> 16495 * <li><i>thai</i> 16496 * <li><i>buhld</i> 16497 * <li><i>hanunoo</i> 16498 * <li><i>tagalog</i> 16499 * <li><i>tagbanwa</i> 16500 * <li><i>bopomofo</i> - Bopomofo, Bopomofo Extended 16501 * <li><i>cjk</i> - the CJK unified ideographs (Han), CJK Unified Ideographs 16502 * Extension A, CJK Unified Ideographs Extension B, CJK Unified Ideographs 16503 * Extension C, CJK Unified Ideographs Extension D, Ideographic Description 16504 * Characters (=isIdeo()) 16505 * <li><i>cjkcompatibility</i> - CJK Compatibility, CJK Compatibility 16506 * Ideographs, CJK Compatibility Forms, CJK Compatibility Ideographs Supplement 16507 * <li><i>cjkradicals</i> - the CJK radicals, KangXi radicals 16508 * <li><i>hangul</i> - Hangul Jamo, Hangul Syllables, Hangul Jamo Extended-A, 16509 * Hangul Jamo Extended-B, Hangul Compatibility Jamo 16510 * <li><i>cjkpunct</i> - CJK symbols and punctuation 16511 * <li><i>cjkstrokes</i> - CJK strokes 16512 * <li><i>hiragana</i> 16513 * <li><i>katakana</i> - Katakana, Katakana Phonetic Extensions, Kana Supplement 16514 * <li><i>kanbun</i> 16515 * <li><i>lisu</i> 16516 * <li><i>yi</i> - Yi Syllables, Yi Radicals 16517 * <li><i>cherokee</i> 16518 * <li><i>canadian</i> - Unified Canadian Aboriginal Syllabics, Unified Canadian 16519 * Aboriginal Syllabics Extended 16520 * <li><i>presentation</i> - Alphabetic presentation forms 16521 * <li><i>vertical</i> - Vertical Forms 16522 * <li><i>width</i> - Halfwidth and Fullwidth Forms 16523 * <li><i>punctuation</i> - General punctuation, Supplemental Punctuation 16524 * <li><i>box</i> - Box Drawing 16525 * <li><i>block</i> - Block Elements 16526 * <li><i>letterlike</i> - Letterlike symbols 16527 * <li><i>mathematical</i> - Mathematical alphanumeric symbols, Miscellaneous 16528 * Mathematical Symbols-A, Miscellaneous Mathematical Symbols-B 16529 * <li><i>enclosedalpha</i> - Enclosed alphanumerics, Enclosed Alphanumeric Supplement 16530 * <li><i>enclosedcjk</i> - Enclosed CJK letters and months, Enclosed Ideographic Supplement 16531 * <li><i>cjkcompatibility</i> - CJK compatibility 16532 * <li><i>apl</i> - APL symbols 16533 * <li><i>controlpictures</i> - Control pictures 16534 * <li><i>misc</i> - Miscellaneous technical 16535 * <li><i>ocr</i> - Optical character recognition (OCR) 16536 * <li><i>combining</i> - Combining Diacritical Marks, Combining Diacritical Marks 16537 * for Symbols, Combining Diacritical Marks Supplement 16538 * <li><i>digits</i> - ASCII digits (=isDigit()) 16539 * <li><i>indicnumber</i> - Common Indic Number Forms 16540 * <li><i>numbers</i> - Number dorms 16541 * <li><i>supersub</i> - Super- and subscripts 16542 * <li><i>arrows</i> - Arrows, Miscellaneous Symbols and Arrows, Supplemental Arrows-A, 16543 * Supplemental Arrows-B 16544 * <li><i>operators</i> - Mathematical operators, supplemental 16545 * mathematical operators 16546 * <li><i>geometric</i> - Geometric shapes 16547 * <li><i>ancient</i> - Ancient symbols 16548 * <li><i>braille</i> - Braille patterns 16549 * <li><i>currency</i> - Currency symbols 16550 * <li><i>dingbats</i> 16551 * <li><i>gamesymbols</i> 16552 * <li><i>yijing</i> - Yijing Hexagram Symbols 16553 * <li><i>specials</i> 16554 * <li><i>variations</i> - Variation Selectors, Variation Selectors Supplement 16555 * <li><i>privateuse</i> - Private Use Area, Supplementary Private Use Area-A, 16556 * Supplementary Private Use Area-B 16557 * <li><i>supplementarya</i> - Supplementary private use area-A 16558 * <li><i>supplementaryb</i> - Supplementary private use area-B 16559 * <li><i>highsurrogates</i> - High Surrogates, High Private Use Surrogates 16560 * <li><i>lowsurrogates</i> 16561 * <li><i>reserved</i> 16562 * <li><i>noncharacters</i> 16563 * </ul><p> 16564 * 16565 * 16566 * @protected 16567 * @param {string|IString|number} ch character or code point to examine 16568 * @param {string} rangeName the name of the range to check 16569 * @return {boolean} true if the first character is within the named 16570 * range 16571 */ 16572 CType.withinRange = function(ch, rangeName) { 16573 if (!rangeName) { 16574 return false; 16575 } 16576 var num; 16577 switch (typeof(ch)) { 16578 case 'number': 16579 num = ch; 16580 break; 16581 case 'string': 16582 num = IString.toCodePoint(ch, 0); 16583 break; 16584 case 'undefined': 16585 return false; 16586 default: 16587 num = ch._toCodePoint(0); 16588 break; 16589 } 16590 16591 return CType._inRange(num, rangeName.toLowerCase(), ilib.data.ctype); 16592 }; 16593 16594 /** 16595 * @protected 16596 * @param {boolean} sync 16597 * @param {Object|undefined} loadParams 16598 * @param {function(*)|undefined} onLoad 16599 */ 16600 CType._init = function(sync, loadParams, onLoad) { 16601 CType._load("ctype", sync, loadParams, onLoad); 16602 }; 16603 16604 /** 16605 * @protected 16606 * @param {string} name 16607 * @param {boolean} sync 16608 * @param {Object|undefined} loadParams 16609 * @param {function(*)|undefined} onLoad 16610 */ 16611 CType._load = function (name, sync, loadParams, onLoad) { 16612 if (!ilib.data[name]) { 16613 var loadName = name ? name + ".json" : "CType.json"; 16614 Utils.loadData({ 16615 name: loadName, 16616 locale: "-", 16617 nonlocale: true, 16618 sync: sync, 16619 loadParams: loadParams, 16620 callback: /** @type function(Object=):undefined */ ilib.bind(this, /** @type function() */ function(ct) { 16621 ilib.data[name] = ct; 16622 if (onLoad && typeof(onLoad) === 'function') { 16623 onLoad(ilib.data[name]); 16624 } 16625 }) 16626 }); 16627 } else { 16628 if (onLoad && typeof(onLoad) === 'function') { 16629 onLoad(ilib.data[name]); 16630 } 16631 } 16632 }; 16633 16634 16635 16636 /*< isDigit.js */ 16637 /* 16638 * isDigit.js - Character type is digit 16639 * 16640 * Copyright © 2012-2015, JEDLSoft 16641 * 16642 * Licensed under the Apache License, Version 2.0 (the "License"); 16643 * you may not use this file except in compliance with the License. 16644 * You may obtain a copy of the License at 16645 * 16646 * http://www.apache.org/licenses/LICENSE-2.0 16647 * 16648 * Unless required by applicable law or agreed to in writing, software 16649 * distributed under the License is distributed on an "AS IS" BASIS, 16650 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16651 * 16652 * See the License for the specific language governing permissions and 16653 * limitations under the License. 16654 */ 16655 16656 // !depends CType.js IString.js ilib.js 16657 16658 // !data ctype 16659 16660 16661 /** 16662 * Return whether or not the first character is a digit character in the 16663 * Latin script.<p> 16664 * 16665 * @static 16666 * @param {string|IString|number} ch character or code point to examine 16667 * @return {boolean} true if the first character is a digit character in the 16668 * Latin script. 16669 */ 16670 var isDigit = function (ch) { 16671 var num; 16672 switch (typeof(ch)) { 16673 case 'number': 16674 num = ch; 16675 break; 16676 case 'string': 16677 num = IString.toCodePoint(ch, 0); 16678 break; 16679 case 'undefined': 16680 return false; 16681 default: 16682 num = ch._toCodePoint(0); 16683 break; 16684 } 16685 return CType._inRange(num, 'digit', ilib.data.ctype); 16686 }; 16687 16688 /** 16689 * @protected 16690 * @param {boolean} sync 16691 * @param {Object|undefined} loadParams 16692 * @param {function(*)|undefined} onLoad 16693 */ 16694 isDigit._init = function (sync, loadParams, onLoad) { 16695 CType._init(sync, loadParams, onLoad); 16696 }; 16697 16698 16699 16700 /*< isSpace.js */ 16701 /* 16702 * isSpace.js - Character type is space char 16703 * 16704 * Copyright © 2012-2015, JEDLSoft 16705 * 16706 * Licensed under the Apache License, Version 2.0 (the "License"); 16707 * you may not use this file except in compliance with the License. 16708 * You may obtain a copy of the License at 16709 * 16710 * http://www.apache.org/licenses/LICENSE-2.0 16711 * 16712 * Unless required by applicable law or agreed to in writing, software 16713 * distributed under the License is distributed on an "AS IS" BASIS, 16714 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16715 * 16716 * See the License for the specific language governing permissions and 16717 * limitations under the License. 16718 */ 16719 16720 // !depends CType.js IString.js 16721 16722 // !data ctype ctype_z 16723 16724 16725 16726 /** 16727 * Return whether or not the first character is a whitespace character.<p> 16728 * 16729 * @static 16730 * @param {string|IString|number} ch character or code point to examine 16731 * @return {boolean} true if the first character is a whitespace character. 16732 */ 16733 var isSpace = function (ch) { 16734 var num; 16735 switch (typeof(ch)) { 16736 case 'number': 16737 num = ch; 16738 break; 16739 case 'string': 16740 num = IString.toCodePoint(ch, 0); 16741 break; 16742 case 'undefined': 16743 return false; 16744 default: 16745 num = ch._toCodePoint(0); 16746 break; 16747 } 16748 16749 return CType._inRange(num, 'space', ilib.data.ctype) || 16750 CType._inRange(num, 'Zs', ilib.data.ctype_z) || 16751 CType._inRange(num, 'Zl', ilib.data.ctype_z) || 16752 CType._inRange(num, 'Zp', ilib.data.ctype_z); 16753 }; 16754 16755 /** 16756 * @protected 16757 * @param {boolean} sync 16758 * @param {Object|undefined} loadParams 16759 * @param {function(*)|undefined} onLoad 16760 */ 16761 isSpace._init = function (sync, loadParams, onLoad) { 16762 CType._load("ctype_z", sync, loadParams, function () { 16763 CType._init(sync, loadParams, onLoad); 16764 }); 16765 }; 16766 16767 16768 /*< Currency.js */ 16769 /* 16770 * Currency.js - Currency definition 16771 * 16772 * Copyright © 2012-2015, JEDLSoft 16773 * 16774 * Licensed under the Apache License, Version 2.0 (the "License"); 16775 * you may not use this file except in compliance with the License. 16776 * You may obtain a copy of the License at 16777 * 16778 * http://www.apache.org/licenses/LICENSE-2.0 16779 * 16780 * Unless required by applicable law or agreed to in writing, software 16781 * distributed under the License is distributed on an "AS IS" BASIS, 16782 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16783 * 16784 * See the License for the specific language governing permissions and 16785 * limitations under the License. 16786 */ 16787 16788 // !depends ilib.js Utils.js Locale.js LocaleInfo.js 16789 16790 // !data currency 16791 16792 16793 /** 16794 * @class 16795 * Create a new currency information instance. Instances of this class encode 16796 * information about a particular currency.<p> 16797 * 16798 * Note: that if you are looking to format currency for display, please see 16799 * the number formatting class {NumFmt}. This class only gives information 16800 * about currencies.<p> 16801 * 16802 * The options can contain any of the following properties: 16803 * 16804 * <ul> 16805 * <li><i>locale</i> - specify the locale for this instance 16806 * <li><i>code</i> - find info on a specific currency with the given ISO 4217 code 16807 * <li><i>sign</i> - search for a currency that uses this sign 16808 * <li><i>onLoad</i> - a callback function to call when the currency data is fully 16809 * loaded. When the onLoad option is given, this class will attempt to 16810 * load any missing locale data using the ilib loader callback. 16811 * When the constructor is done (even if the data is already preassembled), the 16812 * onLoad function is called with the current instance as a parameter, so this 16813 * callback can be used with preassembled or dynamic loading or a mix of the two. 16814 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 16815 * asynchronously. If this option is given as "false", then the "onLoad" 16816 * callback must be given, as the instance returned from this constructor will 16817 * not be usable for a while. 16818 * <li><i>loadParams</i> - an object containing parameters to pass to the 16819 * loader callback function when locale data is missing. The parameters are not 16820 * interpretted or modified in any way. They are simply passed along. The object 16821 * may contain any property/value pairs as long as the calling code is in 16822 * agreement with the loader callback function as to what those parameters mean. 16823 * </ul> 16824 * 16825 * When searching for a currency by its sign, this class cannot guarantee 16826 * that it will return info about a specific currency. The reason is that currency 16827 * signs are sometimes shared between different currencies and the sign is 16828 * therefore ambiguous. If you need a 16829 * guarantee, find the currency using the code instead.<p> 16830 * 16831 * The way this class finds a currency by sign is the following. If the sign is 16832 * unambiguous, then 16833 * the currency is returned. If there are multiple currencies that use the same 16834 * sign, and the current locale uses that sign, then the default currency for 16835 * the current locale is returned. If there are multiple, but the current locale 16836 * does not use that sign, then the currency with the largest circulation is 16837 * returned. For example, if you are in the en-GB locale, and the sign is "$", 16838 * then this class will notice that there are multiple currencies with that 16839 * sign (USD, CAD, AUD, HKD, MXP, etc.) Since "$" is not used in en-GB, it will 16840 * pick the one with the largest circulation, which in this case is the US Dollar 16841 * (USD).<p> 16842 * 16843 * If neither the code or sign property is set, the currency that is most common 16844 * for the locale 16845 * will be used instead. If the locale is not set, the default locale will be used. 16846 * If the code is given, but it is not found in the list of known currencies, this 16847 * constructor will throw an exception. If the sign is given, but it is not found, 16848 * this constructor will default to the currency for the current locale. If both 16849 * the code and sign properties are given, then the sign property will be ignored 16850 * and only the code property used. If the locale is given, but it is not a known 16851 * locale, this class will default to the default locale instead.<p> 16852 * 16853 * 16854 * @constructor 16855 * @param options {Object} a set of properties to govern how this instance is constructed. 16856 * @throws "currency xxx is unknown" when the given currency code is not in the list of 16857 * known currencies. xxx is replaced with the requested code. 16858 */ 16859 var Currency = function (options) { 16860 this.sync = true; 16861 16862 if (options) { 16863 if (options.code) { 16864 this.code = options.code; 16865 } 16866 if (options.locale) { 16867 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 16868 } 16869 if (options.sign) { 16870 this.sign = options.sign; 16871 } 16872 if (typeof(options.sync) !== 'undefined') { 16873 this.sync = options.sync; 16874 } 16875 if (options.loadParams) { 16876 this.loadParams = options.loadParams; 16877 } 16878 } 16879 16880 this.locale = this.locale || new Locale(); 16881 if (typeof(ilib.data.currency) === 'undefined') { 16882 Utils.loadData({ 16883 name: "currency.json", 16884 object: Currency, 16885 locale: "-", 16886 sync: this.sync, 16887 loadParams: this.loadParams, 16888 callback: /** @type function(Object=):undefined */ ilib.bind(this, /** @type function() */ function(currency) { 16889 ilib.data.currency = currency; 16890 this._loadLocinfo(options && options.onLoad); 16891 }) 16892 }); 16893 } else { 16894 this._loadLocinfo(options && options.onLoad); 16895 } 16896 }; 16897 16898 /** 16899 * Return an array of the ids for all ISO 4217 currencies that 16900 * this copy of ilib knows about. 16901 * 16902 * @static 16903 * @return {Array.<string>} an array of currency ids that this copy of ilib knows about. 16904 */ 16905 Currency.getAvailableCurrencies = function() { 16906 var ret = [], 16907 cur, 16908 currencies = new ResBundle({ 16909 name: "currency" 16910 }).getResObj(); 16911 16912 for (cur in currencies) { 16913 if (cur && currencies[cur]) { 16914 ret.push(cur); 16915 } 16916 } 16917 16918 return ret; 16919 }; 16920 16921 Currency.prototype = { 16922 /** 16923 * @private 16924 */ 16925 _loadLocinfo: function(onLoad) { 16926 new LocaleInfo(this.locale, { 16927 onLoad: ilib.bind(this, function (li) { 16928 var currInfo; 16929 16930 this.locinfo = li; 16931 if (this.code) { 16932 currInfo = ilib.data.currency[this.code]; 16933 if (!currInfo) { 16934 throw "currency " + this.code + " is unknown"; 16935 } 16936 } else if (this.sign) { 16937 currInfo = ilib.data.currency[this.sign]; // maybe it is really a code... 16938 if (typeof(currInfo) !== 'undefined') { 16939 this.code = this.sign; 16940 } else { 16941 this.code = this.locinfo.getCurrency(); 16942 currInfo = ilib.data.currency[this.code]; 16943 if (currInfo.sign !== this.sign) { 16944 // current locale does not use the sign, so search for it 16945 for (var cur in ilib.data.currency) { 16946 if (cur && ilib.data.currency[cur]) { 16947 currInfo = ilib.data.currency[cur]; 16948 if (currInfo.sign === this.sign) { 16949 // currency data is already ordered so that the currency with the 16950 // largest circulation is at the beginning, so all we have to do 16951 // is take the first one in the list that matches 16952 this.code = cur; 16953 break; 16954 } 16955 } 16956 } 16957 } 16958 } 16959 } 16960 16961 if (!currInfo || !this.code) { 16962 this.code = this.locinfo.getCurrency(); 16963 currInfo = ilib.data.currency[this.code]; 16964 } 16965 16966 this.name = currInfo.name; 16967 this.fractionDigits = currInfo.decimals; 16968 this.sign = currInfo.sign; 16969 16970 if (typeof(onLoad) === 'function') { 16971 onLoad(this); 16972 } 16973 }) 16974 }); 16975 }, 16976 16977 /** 16978 * Return the ISO 4217 currency code for this instance. 16979 * @return {string} the ISO 4217 currency code for this instance 16980 */ 16981 getCode: function () { 16982 return this.code; 16983 }, 16984 16985 /** 16986 * Return the default number of fraction digits that is typically used 16987 * with this type of currency. 16988 * @return {number} the number of fraction digits for this currency 16989 */ 16990 getFractionDigits: function () { 16991 return this.fractionDigits; 16992 }, 16993 16994 /** 16995 * Return the sign commonly used to represent this currency. 16996 * @return {string} the sign commonly used to represent this currency 16997 */ 16998 getSign: function () { 16999 return this.sign; 17000 }, 17001 17002 /** 17003 * Return the name of the currency in English. 17004 * @return {string} the name of the currency in English 17005 */ 17006 getName: function () { 17007 return this.name; 17008 }, 17009 17010 /** 17011 * Return the locale for this currency. If the options to the constructor 17012 * included a locale property in order to find the currency that is appropriate 17013 * for that locale, then the locale is returned here. If the options did not 17014 * include a locale, then this method returns undefined. 17015 * @return {Locale} the locale used in the constructor of this instance, 17016 * or undefined if no locale was given in the constructor 17017 */ 17018 getLocale: function () { 17019 return this.locale; 17020 } 17021 }; 17022 17023 17024 17025 /*< INumber.js */ 17026 /* 17027 * INumber.js - Parse a number in any locale 17028 * 17029 * Copyright © 2012-2015, JEDLSoft 17030 * 17031 * Licensed under the Apache License, Version 2.0 (the "License"); 17032 * you may not use this file except in compliance with the License. 17033 * You may obtain a copy of the License at 17034 * 17035 * http://www.apache.org/licenses/LICENSE-2.0 17036 * 17037 * Unless required by applicable law or agreed to in writing, software 17038 * distributed under the License is distributed on an "AS IS" BASIS, 17039 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17040 * 17041 * See the License for the specific language governing permissions and 17042 * limitations under the License. 17043 */ 17044 17045 /* 17046 !depends 17047 ilib.js 17048 Locale.js 17049 isDigit.js 17050 isSpace.js 17051 LocaleInfo.js 17052 Utils.js 17053 Currency.js 17054 */ 17055 17056 17057 17058 17059 17060 17061 /** 17062 * @class 17063 * Parse a string as a number, ignoring all locale-specific formatting.<p> 17064 * 17065 * This class is different from the standard Javascript parseInt() and parseFloat() 17066 * functions in that the number to be parsed can have formatting characters in it 17067 * that are not supported by those two 17068 * functions, and it handles numbers written in other locales properly. For example, 17069 * if you pass the string "203,231.23" to the parseFloat() function in Javascript, it 17070 * will return you the number 203. The INumber class will parse it correctly and 17071 * the value() function will return the number 203231.23. If you pass parseFloat() the 17072 * string "203.231,23" with the locale set to de-DE, it will return you 203 again. This 17073 * class will return the correct number 203231.23 again.<p> 17074 * 17075 * The options object may contain any of the following properties: 17076 * 17077 * <ul> 17078 * <li><i>locale</i> - specify the locale of the string to parse. This is used to 17079 * figure out what the decimal point character is. If not specified, the default locale 17080 * for the app or browser is used. 17081 * <li><i>type</i> - specify whether this string should be interpretted as a number, 17082 * currency, or percentage amount. When the number is interpretted as a currency 17083 * amount, the getCurrency() method will return something useful, otherwise it will 17084 * return undefined. If 17085 * the number is to be interpretted as percentage amount and there is a percentage sign 17086 * in the string, then the number will be returned 17087 * as a fraction from the valueOf() method. If there is no percentage sign, then the 17088 * number will be returned as a regular number. That is "58.3%" will be returned as the 17089 * number 0.583 but "58.3" will be returned as 58.3. Valid values for this property 17090 * are "number", "currency", and "percentage". Default if this is not specified is 17091 * "number". 17092 * <li><i>onLoad</i> - a callback function to call when the locale data is fully 17093 * loaded. When the onLoad option is given, this class will attempt to 17094 * load any missing locale data using the ilib loader callback. 17095 * When the constructor is done (even if the data is already preassembled), the 17096 * onLoad function is called with the current instance as a parameter, so this 17097 * callback can be used with preassembled or dynamic loading or a mix of the two. 17098 * 17099 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 17100 * asynchronously. If this option is given as "false", then the "onLoad" 17101 * callback must be given, as the instance returned from this constructor will 17102 * not be usable for a while. 17103 * 17104 * <li><i>loadParams</i> - an object containing parameters to pass to the 17105 * loader callback function when locale data is missing. The parameters are not 17106 * interpretted or modified in any way. They are simply passed along. The object 17107 * may contain any property/value pairs as long as the calling code is in 17108 * agreement with the loader callback function as to what those parameters mean. 17109 * </ul> 17110 * <p> 17111 * 17112 * This class is named INumber ("ilib number") so as not to conflict with the 17113 * built-in Javascript Number class. 17114 * 17115 * @constructor 17116 * @param {string|number|INumber|Number|undefined} str a string to parse as a number, or a number value 17117 * @param {Object=} options Options controlling how the instance should be created 17118 */ 17119 var INumber = function (str, options) { 17120 var i, stripped = "", 17121 sync = true, 17122 loadParams, 17123 onLoad; 17124 17125 this.locale = new Locale(); 17126 this.type = "number"; 17127 17128 if (options) { 17129 if (options.locale) { 17130 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 17131 } 17132 if (options.type) { 17133 switch (options.type) { 17134 case "number": 17135 case "currency": 17136 case "percentage": 17137 this.type = options.type; 17138 break; 17139 default: 17140 break; 17141 } 17142 } 17143 if (typeof(options.sync) !== 'undefined') { 17144 sync = (options.sync == true); 17145 } 17146 loadParams = options.loadParams; 17147 onLoad = options.onLoad; 17148 } 17149 17150 isDigit._init(sync, loadParams, /** @type {function()|undefined} */ ilib.bind(this, function() { 17151 isSpace._init(sync, loadParams, /** @type {function()|undefined} */ ilib.bind(this, function() { 17152 new LocaleInfo(this.locale, { 17153 sync: sync, 17154 onLoad: ilib.bind(this, function (li) { 17155 this.decimal = li.getDecimalSeparator(); 17156 17157 switch (typeof(str)) { 17158 case 'string': 17159 // stripping should work for all locales, because you just ignore all the 17160 // formatting except the decimal char 17161 var unary = true; // looking for the unary minus still? 17162 var lastNumericChar = 0; 17163 this.str = str || "0"; 17164 i = 0; 17165 for (i = 0; i < this.str.length; i++) { 17166 if (unary && this.str.charAt(i) === '-') { 17167 unary = false; 17168 stripped += this.str.charAt(i); 17169 lastNumericChar = i; 17170 } else if (isDigit(this.str.charAt(i))) { 17171 stripped += this.str.charAt(i); 17172 unary = false; 17173 lastNumericChar = i; 17174 } else if (this.str.charAt(i) === this.decimal) { 17175 stripped += "."; // always convert to period 17176 unary = false; 17177 lastNumericChar = i; 17178 } // else ignore 17179 } 17180 // record what we actually parsed 17181 this.parsed = this.str.substring(0, lastNumericChar+1); 17182 this.value = parseFloat(stripped); 17183 break; 17184 case 'number': 17185 this.str = "" + str; 17186 this.value = str; 17187 break; 17188 17189 case 'object': 17190 this.value = /** @type {number} */ str.valueOf(); 17191 this.str = "" + this.value; 17192 break; 17193 17194 case 'undefined': 17195 this.value = 0; 17196 this.str = "0"; 17197 break; 17198 } 17199 17200 switch (this.type) { 17201 default: 17202 // don't need to do anything special for other types 17203 break; 17204 case "percentage": 17205 if (this.str.indexOf(li.getPercentageSymbol()) !== -1) { 17206 this.value /= 100; 17207 } 17208 break; 17209 case "currency": 17210 stripped = ""; 17211 i = 0; 17212 while (i < this.str.length && 17213 !isDigit(this.str.charAt(i)) && 17214 !isSpace(this.str.charAt(i))) { 17215 stripped += this.str.charAt(i++); 17216 } 17217 if (stripped.length === 0) { 17218 while (i < this.str.length && 17219 isDigit(this.str.charAt(i)) || 17220 isSpace(this.str.charAt(i)) || 17221 this.str.charAt(i) === '.' || 17222 this.str.charAt(i) === ',' ) { 17223 i++; 17224 } 17225 while (i < this.str.length && 17226 !isDigit(this.str.charAt(i)) && 17227 !isSpace(this.str.charAt(i))) { 17228 stripped += this.str.charAt(i++); 17229 } 17230 } 17231 new Currency({ 17232 locale: this.locale, 17233 sign: stripped, 17234 sync: sync, 17235 onLoad: ilib.bind(this, function (cur) { 17236 this.currency = cur; 17237 if (options && typeof(options.onLoad) === 'function') { 17238 options.onLoad(this); 17239 } 17240 }) 17241 }); 17242 return; 17243 } 17244 17245 if (options && typeof(options.onLoad) === 'function') { 17246 options.onLoad(this); 17247 } 17248 }) 17249 }); 17250 })); 17251 })); 17252 }; 17253 17254 INumber.prototype = { 17255 /** 17256 * Return the locale for this formatter instance. 17257 * @return {Locale} the locale instance for this formatter 17258 */ 17259 getLocale: function () { 17260 return this.locale; 17261 }, 17262 17263 /** 17264 * Return the original string that this number instance was created with. 17265 * @return {string} the original string 17266 */ 17267 toString: function () { 17268 return this.str; 17269 }, 17270 17271 /** 17272 * If the type of this INumber instance is "currency", then the parser will attempt 17273 * to figure out which currency this amount represents. The amount can be written 17274 * with any of the currency signs or ISO 4217 codes that are currently 17275 * recognized by ilib, and the currency signs may occur before or after the 17276 * numeric portion of the string. If no currency can be recognized, then the 17277 * default currency for the locale is returned. If multiple currencies can be 17278 * recognized (for example if the currency sign is "$"), then this method 17279 * will prefer the one for the current locale. If multiple currencies can be 17280 * recognized, but none are used in the current locale, then the first currency 17281 * encountered will be used. This may produce random results, though the larger 17282 * currencies occur earlier in the list. For example, if the sign found in the 17283 * string is "$" and that is not the sign of the currency of the current locale 17284 * then the US dollar will be recognized, as it is the largest currency that uses 17285 * the "$" as its sign. 17286 * 17287 * @return {Currency|undefined} the currency instance for this amount, or 17288 * undefined if this INumber object is not of type currency 17289 */ 17290 getCurrency: function () { 17291 return this.currency; 17292 }, 17293 17294 /** 17295 * Return the value of this INumber object as a primitive number instance. 17296 * @return {number} the value of this number instance 17297 */ 17298 valueOf: function () { 17299 return this.value; 17300 } 17301 }; 17302 17303 17304 /*< NumFmt.js */ 17305 /* 17306 * NumFmt.js - Number formatter definition 17307 * 17308 * Copyright © 2012-2015, JEDLSoft 17309 * 17310 * Licensed under the Apache License, Version 2.0 (the "License"); 17311 * you may not use this file except in compliance with the License. 17312 * You may obtain a copy of the License at 17313 * 17314 * http://www.apache.org/licenses/LICENSE-2.0 17315 * 17316 * Unless required by applicable law or agreed to in writing, software 17317 * distributed under the License is distributed on an "AS IS" BASIS, 17318 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17319 * 17320 * See the License for the specific language governing permissions and 17321 * limitations under the License. 17322 */ 17323 17324 /* 17325 !depends 17326 ilib.js 17327 Locale.js 17328 LocaleInfo.js 17329 Utils.js 17330 MathUtils.js 17331 Currency.js 17332 IString.js 17333 JSUtils.js 17334 INumber.js 17335 */ 17336 17337 // !data localeinfo currency 17338 17339 17340 17341 /** 17342 * @class 17343 * Create a new number formatter instance. Locales differ in the way that digits 17344 * in a formatted number are grouped, in the way the decimal character is represented, 17345 * etc. Use this formatter to get it right for any locale.<p> 17346 * 17347 * This formatter can format plain numbers, currency amounts, and percentage amounts.<p> 17348 * 17349 * As with all formatters, the recommended 17350 * practice is to create one formatter and use it multiple times to format various 17351 * numbers.<p> 17352 * 17353 * The options can contain any of the following properties: 17354 * 17355 * <ul> 17356 * <li><i>locale</i> - use the conventions of the specified locale when figuring out how to 17357 * format a number. 17358 * <li><i>type</i> - the type of this formatter. Valid values are "number", "currency", or 17359 * "percentage". If this property is not specified, the default is "number". 17360 * <li><i>currency</i> - the ISO 4217 3-letter currency code to use when the formatter type 17361 * is "currency". This property is required for currency formatting. If the type property 17362 * is "currency" and the currency property is not specified, the constructor will throw a 17363 * an exception. 17364 * <li><i>maxFractionDigits</i> - the maximum number of digits that should appear in the 17365 * formatted output after the decimal. A value of -1 means unlimited, and 0 means only print 17366 * the integral part of the number. 17367 * <li><i>minFractionDigits</i> - the minimum number of fractional digits that should 17368 * appear in the formatted output. If the number does not have enough fractional digits 17369 * to reach this minimum, the number will be zero-padded at the end to get to the limit. 17370 * If the type of the formatter is "currency" and this 17371 * property is not specified, then the minimum fraction digits is set to the normal number 17372 * of digits used with that currency, which is almost always 0, 2, or 3 digits. 17373 * <li><i>useNative</i> - the flag used to determaine whether to use the native script settings 17374 * for formatting the numbers . 17375 * <li><i>roundingMode</i> - When the maxFractionDigits or maxIntegerDigits is specified, 17376 * this property governs how the least significant digits are rounded to conform to that 17377 * maximum. The value of this property is a string with one of the following values: 17378 * <ul> 17379 * <li><i>up</i> - round away from zero 17380 * <li><i>down</i> - round towards zero. This has the effect of truncating the number 17381 * <li><i>ceiling</i> - round towards positive infinity 17382 * <li><i>floor</i> - round towards negative infinity 17383 * <li><i>halfup</i> - round towards nearest neighbour. If equidistant, round up. 17384 * <li><i>halfdown</i> - round towards nearest neighbour. If equidistant, round down. 17385 * <li><i>halfeven</i> - round towards nearest neighbour. If equidistant, round towards the even neighbour 17386 * <li><i>halfodd</i> - round towards nearest neighbour. If equidistant, round towards the odd neighbour 17387 * </ul> 17388 * When the type of the formatter is "currency" and the <i>roundingMode</i> property is not 17389 * set, then the standard legal rounding rules for the locale are followed. If the type 17390 * is "number" or "percentage" and the <i>roundingMode</i> property is not set, then the 17391 * default mode is "halfdown".</i>. 17392 * 17393 * <li><i>style</i> - When the type of this formatter is "currency", the currency amount 17394 * can be formatted in the following styles: "common" and "iso". The common style is the 17395 * one commonly used in every day writing where the currency unit is represented using a 17396 * symbol. eg. "$57.35" for fifty-seven dollars and thirty five cents. The iso style is 17397 * the international style where the currency unit is represented using the ISO 4217 code. 17398 * eg. "USD 57.35" for the same amount. The default is "common" style if the style is 17399 * not specified.<p> 17400 * 17401 * When the type of this formatter is "number", the style can be one of the following: 17402 * <ul> 17403 * <li><i>standard - format a fully specified floating point number properly for the locale 17404 * <li><i>scientific</i> - use scientific notation for all numbers. That is, 1 integral 17405 * digit, followed by a number of fractional digits, followed by an "e" which denotes 17406 * exponentiation, followed digits which give the power of 10 in the exponent. 17407 * <li><i>native</i> - format a floating point number using the native digits and 17408 * formatting symbols for the script of the locale. 17409 * <li><i>nogrouping</i> - format a floating point number without grouping digits for 17410 * the integral portion of the number 17411 * </ul> 17412 * Note that if you specify a maximum number 17413 * of integral digits, the formatter with a standard style will give you standard 17414 * formatting for smaller numbers and scientific notation for larger numbers. The default 17415 * is standard style if this is not specified. 17416 * 17417 * <li><i>onLoad</i> - a callback function to call when the format data is fully 17418 * loaded. When the onLoad option is given, this class will attempt to 17419 * load any missing locale data using the ilib loader callback. 17420 * When the constructor is done (even if the data is already preassembled), the 17421 * onLoad function is called with the current instance as a parameter, so this 17422 * callback can be used with preassembled or dynamic loading or a mix of the two. 17423 * 17424 * <li>sync - tell whether to load any missing locale data synchronously or 17425 * asynchronously. If this option is given as "false", then the "onLoad" 17426 * callback must be given, as the instance returned from this constructor will 17427 * not be usable for a while. 17428 * 17429 * <li><i>loadParams</i> - an object containing parameters to pass to the 17430 * loader callback function when locale data is missing. The parameters are not 17431 * interpretted or modified in any way. They are simply passed along. The object 17432 * may contain any property/value pairs as long as the calling code is in 17433 * agreement with the loader callback function as to what those parameters mean. 17434 * </ul> 17435 * <p> 17436 * 17437 * 17438 * @constructor 17439 * @param {Object.<string,*>} options A set of options that govern how the formatter will behave 17440 */ 17441 var NumFmt = function (options) { 17442 var sync = true; 17443 this.locale = new Locale(); 17444 /** 17445 * @private 17446 * @type {string} 17447 */ 17448 this.type = "number"; 17449 var loadParams = undefined; 17450 17451 if (options) { 17452 if (options.locale) { 17453 this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; 17454 } 17455 17456 if (options.type) { 17457 if (options.type === 'number' || 17458 options.type === 'currency' || 17459 options.type === 'percentage') { 17460 this.type = options.type; 17461 } 17462 } 17463 17464 if (options.currency) { 17465 /** 17466 * @private 17467 * @type {string} 17468 */ 17469 this.currency = options.currency; 17470 } 17471 17472 if (typeof (options.maxFractionDigits) === 'number') { 17473 /** 17474 * @private 17475 * @type {number|undefined} 17476 */ 17477 this.maxFractionDigits = this._toPrimitive(options.maxFractionDigits); 17478 } 17479 if (typeof (options.minFractionDigits) === 'number') { 17480 /** 17481 * @private 17482 * @type {number|undefined} 17483 */ 17484 this.minFractionDigits = this._toPrimitive(options.minFractionDigits); 17485 // enforce the limits to avoid JS exceptions 17486 if (this.minFractionDigits < 0) { 17487 this.minFractionDigits = 0; 17488 } 17489 if (this.minFractionDigits > 20) { 17490 this.minFractionDigits = 20; 17491 } 17492 } 17493 if (options.style) { 17494 /** 17495 * @private 17496 * @type {string} 17497 */ 17498 this.style = options.style; 17499 } 17500 if (typeof(options.useNative) === 'boolean') { 17501 /** 17502 * @private 17503 * @type {boolean} 17504 * */ 17505 this.useNative = options.useNative; 17506 } 17507 /** 17508 * @private 17509 * @type {string} 17510 */ 17511 this.roundingMode = options.roundingMode; 17512 17513 if (typeof (options.sync) !== 'undefined') { 17514 /** @type {boolean} */ 17515 sync = (options.sync == true); 17516 } 17517 17518 loadParams = options.loadParams; 17519 } 17520 17521 /** 17522 * @private 17523 * @type {LocaleInfo|undefined} 17524 */ 17525 this.localeInfo = undefined; 17526 17527 new LocaleInfo(this.locale, { 17528 sync: sync, 17529 loadParams: loadParams, 17530 onLoad: ilib.bind(this, function (li) { 17531 /** 17532 * @private 17533 * @type {LocaleInfo|undefined} 17534 */ 17535 this.localeInfo = li; 17536 17537 if (this.type === "number") { 17538 this.templateNegative = new IString(this.localeInfo.getNegativeNumberFormat() || "-{n}"); 17539 } else if (this.type === "currency") { 17540 var templates; 17541 17542 if (!this.currency || typeof (this.currency) != 'string') { 17543 throw "A currency property is required in the options to the number formatter constructor when the type property is set to currency."; 17544 } 17545 17546 new Currency({ 17547 locale: this.locale, 17548 code: this.currency, 17549 sync: sync, 17550 loadParams: loadParams, 17551 onLoad: ilib.bind(this, function (cur) { 17552 this.currencyInfo = cur; 17553 if (this.style !== "common" && this.style !== "iso") { 17554 this.style = "common"; 17555 } 17556 17557 if (typeof(this.maxFractionDigits) !== 'number' && typeof(this.minFractionDigits) !== 'number') { 17558 this.minFractionDigits = this.maxFractionDigits = this.currencyInfo.getFractionDigits(); 17559 } 17560 17561 templates = this.localeInfo.getCurrencyFormats(); 17562 this.template = new IString(templates[this.style] || templates.common); 17563 this.templateNegative = new IString(templates[this.style + "Negative"] || templates["commonNegative"]); 17564 this.sign = (this.style === "iso") ? this.currencyInfo.getCode() : this.currencyInfo.getSign(); 17565 17566 if (!this.roundingMode) { 17567 this.roundingMode = this.currencyInfo && this.currencyInfo.roundingMode; 17568 } 17569 17570 this._init(); 17571 17572 if (options && typeof (options.onLoad) === 'function') { 17573 options.onLoad(this); 17574 } 17575 }) 17576 }); 17577 return; 17578 } else if (this.type === "percentage") { 17579 this.template = new IString(this.localeInfo.getPercentageFormat() || "{n}%"); 17580 this.templateNegative = new IString(this.localeInfo.getNegativePercentageFormat() || this.localeInfo.getNegativeNumberFormat() + "%"); 17581 } 17582 17583 this._init(); 17584 17585 if (options && typeof (options.onLoad) === 'function') { 17586 options.onLoad(this); 17587 } 17588 }) 17589 }); 17590 }; 17591 17592 /** 17593 * Return an array of available locales that this formatter can format 17594 * @static 17595 * @return {Array.<Locale>|undefined} an array of available locales 17596 */ 17597 NumFmt.getAvailableLocales = function () { 17598 return undefined; 17599 }; 17600 17601 /** 17602 * @private 17603 * @const 17604 * @type string 17605 */ 17606 NumFmt.zeros = "0000000000000000000000000000000000000000000000000000000000000000000000"; 17607 17608 NumFmt.prototype = { 17609 /** 17610 * Return true if this formatter uses native digits to format the number. If the useNative 17611 * option is given to the constructor, then this flag will be honoured. If the useNative 17612 * option is not given to the constructor, this this formatter will use native digits if 17613 * the locale typically uses native digits. 17614 * 17615 * @return {boolean} true if this formatter will format with native digits, false otherwise 17616 */ 17617 getUseNative: function() { 17618 if (typeof(this.useNative) === "boolean") { 17619 return this.useNative; 17620 } 17621 return (this.localeInfo.getDigitsStyle() === "native"); 17622 }, 17623 17624 /** 17625 * @private 17626 */ 17627 _init: function () { 17628 if (this.maxFractionDigits < this.minFractionDigits) { 17629 this.minFractionDigits = this.maxFractionDigits; 17630 } 17631 17632 if (!this.roundingMode) { 17633 this.roundingMode = this.localeInfo.getRoundingMode(); 17634 } 17635 17636 if (!this.roundingMode) { 17637 this.roundingMode = "halfdown"; 17638 } 17639 17640 // set up the function, so we only have to figure it out once 17641 // and not every time we do format() 17642 this.round = MathUtils[this.roundingMode]; 17643 if (!this.round) { 17644 this.roundingMode = "halfdown"; 17645 this.round = MathUtils[this.roundingMode]; 17646 } 17647 17648 if (this.style === "nogrouping") { 17649 this.prigroupSize = this.secgroupSize = 0; 17650 } else { 17651 this.prigroupSize = this.localeInfo.getPrimaryGroupingDigits(); 17652 this.secgroupSize = this.localeInfo.getSecondaryGroupingDigits(); 17653 this.groupingSeparator = this.getUseNative() ? this.localeInfo.getNativeGroupingSeparator() : this.localeInfo.getGroupingSeparator(); 17654 } 17655 this.decimalSeparator = this.getUseNative() ? this.localeInfo.getNativeDecimalSeparator() : this.localeInfo.getDecimalSeparator(); 17656 17657 if (this.getUseNative()) { 17658 var nd = this.localeInfo.getNativeDigits() || this.localeInfo.getDigits(); 17659 if (nd) { 17660 this.digits = nd.split(""); 17661 } 17662 } 17663 17664 this.exponentSymbol = this.localeInfo.getExponential() || "e"; 17665 }, 17666 17667 /* 17668 * @private 17669 */ 17670 _pad: function (str, length, left) { 17671 return (str.length >= length) ? 17672 str : 17673 (left ? 17674 NumFmt.zeros.substring(0, length - str.length) + str : 17675 str + NumFmt.zeros.substring(0, length - str.length)); 17676 }, 17677 17678 /** 17679 * @private 17680 * @param {INumber|Number|string|number} num object, string, or number to convert to a primitive number 17681 * @return {number} the primitive number equivalent of the argument 17682 */ 17683 _toPrimitive: function (num) { 17684 var n = 0; 17685 17686 switch (typeof (num)) { 17687 case 'number': 17688 n = num; 17689 break; 17690 case 'string': 17691 n = parseFloat(num); 17692 break; 17693 case 'object': 17694 // Number.valueOf() is incorrectly documented as being of type "string" rather than "number", so coerse 17695 // the type here to shut the type checker up 17696 n = /** @type {number} */ num.valueOf(); 17697 break; 17698 } 17699 17700 return n; 17701 }, 17702 17703 /** 17704 * Format the number using scientific notation as a positive number. Negative 17705 * formatting to be applied later. 17706 * @private 17707 * @param {number} num the number to format 17708 * @return {string} the formatted number 17709 */ 17710 _formatScientific: function (num) { 17711 var n = new Number(num); 17712 var formatted; 17713 17714 var factor, 17715 str = n.toExponential(), 17716 parts = str.split("e"), 17717 significant = parts[0], 17718 exponent = parts[1], 17719 numparts, 17720 integral, 17721 fraction; 17722 17723 if (this.maxFractionDigits > 0) { 17724 // if there is a max fraction digits setting, round the fraction to 17725 // the right length first by dividing or multiplying by powers of 10. 17726 // manipulate the fraction digits so as to 17727 // avoid the rounding errors of floating point numbers 17728 factor = Math.pow(10, this.maxFractionDigits); 17729 significant = this.round(significant * factor) / factor; 17730 } 17731 numparts = ("" + significant).split("."); 17732 integral = numparts[0]; 17733 fraction = numparts[1]; 17734 17735 if (typeof(this.maxFractionDigits) !== 'undefined') { 17736 fraction = fraction.substring(0, this.maxFractionDigits); 17737 } 17738 if (typeof(this.minFractionDigits) !== 'undefined') { 17739 fraction = this._pad(fraction || "", this.minFractionDigits, false); 17740 } 17741 formatted = integral; 17742 if (fraction.length) { 17743 formatted += this.decimalSeparator + fraction; 17744 } 17745 formatted += this.exponentSymbol + exponent; 17746 return formatted; 17747 }, 17748 17749 /** 17750 * Formats the number as a positive number. Negative formatting to be applied later. 17751 * @private 17752 * @param {number} num the number to format 17753 * @return {string} the formatted number 17754 */ 17755 _formatStandard: function (num) { 17756 var i; 17757 var k; 17758 17759 if (typeof(this.maxFractionDigits) !== 'undefined' && this.maxFractionDigits > -1) { 17760 var factor = Math.pow(10, this.maxFractionDigits); 17761 num = this.round(num * factor) / factor; 17762 } 17763 17764 num = Math.abs(num); 17765 17766 var parts = ("" + num).split("."), 17767 integral = parts[0], 17768 fraction = parts[1], 17769 cycle, 17770 formatted; 17771 17772 integral = integral.toString(); 17773 17774 if (this.minFractionDigits > 0) { 17775 fraction = this._pad(fraction || "", this.minFractionDigits, false); 17776 } 17777 17778 if (this.secgroupSize > 0) { 17779 if (integral.length > this.prigroupSize) { 17780 var size1 = this.prigroupSize; 17781 var size2 = integral.length; 17782 var size3 = size2 - size1; 17783 integral = integral.slice(0, size3) + this.groupingSeparator + integral.slice(size3); 17784 var num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); 17785 k = num_sec.length; 17786 while (k > this.secgroupSize) { 17787 var secsize1 = this.secgroupSize; 17788 var secsize2 = num_sec.length; 17789 var secsize3 = secsize2 - secsize1; 17790 integral = integral.slice(0, secsize3) + this.groupingSeparator + integral.slice(secsize3); 17791 num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); 17792 k = num_sec.length; 17793 } 17794 } 17795 17796 formatted = integral; 17797 } else if (this.prigroupSize !== 0) { 17798 cycle = MathUtils.mod(integral.length - 1, this.prigroupSize); 17799 17800 formatted = ""; 17801 17802 for (i = 0; i < integral.length - 1; i++) { 17803 formatted += integral.charAt(i); 17804 if (cycle === 0) { 17805 formatted += this.groupingSeparator; 17806 } 17807 cycle = MathUtils.mod(cycle - 1, this.prigroupSize); 17808 } 17809 formatted += integral.charAt(integral.length - 1); 17810 } else { 17811 formatted = integral; 17812 } 17813 17814 if (fraction && (typeof(this.maxFractionDigits) === 'undefined' || this.maxFractionDigits > 0)) { 17815 formatted += this.decimalSeparator; 17816 formatted += fraction; 17817 } 17818 17819 if (this.digits) { 17820 formatted = JSUtils.mapString(formatted, this.digits); 17821 } 17822 17823 return formatted; 17824 }, 17825 17826 /** 17827 * Format a number according to the settings of this number formatter instance. 17828 * @param num {number|string|INumber|Number} a floating point number to format 17829 * @return {string} a string containing the formatted number 17830 */ 17831 format: function (num) { 17832 var formatted, n; 17833 17834 if (typeof (num) === 'undefined') { 17835 return ""; 17836 } 17837 17838 // convert to a real primitive number type 17839 n = this._toPrimitive(num); 17840 17841 if (this.type === "number") { 17842 formatted = (this.style === "scientific") ? 17843 this._formatScientific(n) : 17844 this._formatStandard(n); 17845 17846 if (num < 0) { 17847 formatted = this.templateNegative.format({n: formatted}); 17848 } 17849 } else { 17850 formatted = this._formatStandard(n); 17851 var template = (n < 0) ? this.templateNegative : this.template; 17852 formatted = template.format({ 17853 n: formatted, 17854 s: this.sign 17855 }); 17856 } 17857 17858 return formatted; 17859 }, 17860 17861 /** 17862 * Return the type of formatter. Valid values are "number", "currency", and 17863 * "percentage". 17864 * 17865 * @return {string} the type of formatter 17866 */ 17867 getType: function () { 17868 return this.type; 17869 }, 17870 17871 /** 17872 * Return the locale for this formatter instance. 17873 * @return {Locale} the locale instance for this formatter 17874 */ 17875 getLocale: function () { 17876 return this.locale; 17877 }, 17878 17879 /** 17880 * Returns true if this formatter groups together digits in the integral 17881 * portion of a number, based on the options set up in the constructor. In 17882 * most western European cultures, this means separating every 3 digits 17883 * of the integral portion of a number with a particular character. 17884 * 17885 * @return {boolean} true if this formatter groups digits in the integral 17886 * portion of the number 17887 */ 17888 isGroupingUsed: function () { 17889 return (this.groupingSeparator !== 'undefined' && this.groupingSeparator.length > 0); 17890 }, 17891 17892 /** 17893 * Returns the maximum fraction digits set up in the constructor. 17894 * 17895 * @return {number} the maximum number of fractional digits this 17896 * formatter will format, or -1 for no maximum 17897 */ 17898 getMaxFractionDigits: function () { 17899 return typeof (this.maxFractionDigits) !== 'undefined' ? this.maxFractionDigits : -1; 17900 }, 17901 17902 /** 17903 * Returns the minimum fraction digits set up in the constructor. If 17904 * the formatter has the type "currency", then the minimum fraction 17905 * digits is the amount of digits that is standard for the currency 17906 * in question unless overridden in the options to the constructor. 17907 * 17908 * @return {number} the minimum number of fractional digits this 17909 * formatter will format, or -1 for no minimum 17910 */ 17911 getMinFractionDigits: function () { 17912 return typeof (this.minFractionDigits) !== 'undefined' ? this.minFractionDigits : -1; 17913 }, 17914 17915 /** 17916 * Returns the ISO 4217 code for the currency that this formatter formats. 17917 * IF the typeof this formatter is not "currency", then this method will 17918 * return undefined. 17919 * 17920 * @return {string} the ISO 4217 code for the currency that this formatter 17921 * formats, or undefined if this not a currency formatter 17922 */ 17923 getCurrency: function () { 17924 return this.currencyInfo && this.currencyInfo.getCode(); 17925 }, 17926 17927 /** 17928 * Returns the rounding mode set up in the constructor. The rounding mode 17929 * controls how numbers are rounded when the integral or fraction digits 17930 * of a number are limited. 17931 * 17932 * @return {string} the name of the rounding mode used in this formatter 17933 */ 17934 getRoundingMode: function () { 17935 return this.roundingMode; 17936 }, 17937 17938 /** 17939 * If this formatter is a currency formatter, then the style determines how the 17940 * currency is denoted in the formatted output. This method returns the style 17941 * that this formatter will produce. (See the constructor comment for more about 17942 * the styles.) 17943 * @return {string} the name of the style this formatter will use to format 17944 * currency amounts, or "undefined" if this formatter is not a currency formatter 17945 */ 17946 getStyle: function () { 17947 return this.style; 17948 } 17949 }; 17950 17951 17952 /*< DurationFmt.js */ 17953 /* 17954 * DurFmt.js - Date formatter definition 17955 * 17956 * Copyright © 2012-2015, JEDLSoft 17957 * 17958 * Licensed under the Apache License, Version 2.0 (the "License"); 17959 * you may not use this file except in compliance with the License. 17960 * You may obtain a copy of the License at 17961 * 17962 * http://www.apache.org/licenses/LICENSE-2.0 17963 * 17964 * Unless required by applicable law or agreed to in writing, software 17965 * distributed under the License is distributed on an "AS IS" BASIS, 17966 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17967 * 17968 * See the License for the specific language governing permissions and 17969 * limitations under the License. 17970 */ 17971 17972 /* 17973 !depends 17974 ilib.js 17975 Locale.js 17976 DateFmt.js 17977 IString.js 17978 ResBundle.js 17979 LocaleInfo.js 17980 JSUtils.js 17981 Utils.js 17982 */ 17983 17984 // !data dateformats sysres 17985 // !resbundle sysres 17986 17987 17988 /** 17989 * @class 17990 * Create a new duration formatter instance. The duration formatter is immutable once 17991 * it is created, but can format as many different durations as needed with the same 17992 * options. Create different duration formatter instances for different purposes 17993 * and then keep them cached for use later if you have more than one duration to 17994 * format.<p> 17995 * 17996 * Duration formatters format lengths of time. The duration formatter is meant to format 17997 * durations of such things as the length of a song or a movie or a meeting, or the 17998 * current position in that song or movie while playing it. If you wish to format a 17999 * period of time that has a specific start and end date/time, then use a 18000 * [DateRngFmt] instance instead and call its format method.<p> 18001 * 18002 * The options may contain any of the following properties: 18003 * 18004 * <ul> 18005 * <li><i>locale</i> - locale to use when formatting the duration. If the locale is 18006 * not specified, then the default locale of the app or web page will be used. 18007 * 18008 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 18009 * formatted string. 18010 * 18011 * <ul> 18012 * <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 18013 * <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 18014 * <li><i>long</i> - use a long representation of the duration. This is a fully specified format, but some of the textual 18015 * parts may still be abbreviated. eg. 1 yr 1 mo 1 wk 1 day 1 hr 1 min 1 sec 18016 * <li><i>full</i> - use a full representation of the duration. This is a fully specified format where all the textual 18017 * parts are spelled out completely. eg. 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute and 1 second 18018 * </ul> 18019 * 18020 * <li><i>style<i> - whether hours, minutes, and seconds should be formatted as a text string 18021 * or as a regular time as on a clock. eg. text is "1 hour, 15 minutes", whereas clock is "1:15:00". Valid 18022 * values for this property are "text" or "clock". Default if this property is not specified 18023 * is "text". 18024 * 18025 *<li><i>useNative</i> - the flag used to determaine whether to use the native script settings 18026 * for formatting the numbers . 18027 * 18028 * <li><i>onLoad</i> - a callback function to call when the format data is fully 18029 * loaded. When the onLoad option is given, this class will attempt to 18030 * load any missing locale data using the ilib loader callback. 18031 * When the constructor is done (even if the data is already preassembled), the 18032 * onLoad function is called with the current instance as a parameter, so this 18033 * callback can be used with preassembled or dynamic loading or a mix of the two. 18034 * 18035 * <li>sync - tell whether to load any missing locale data synchronously or 18036 * asynchronously. If this option is given as "false", then the "onLoad" 18037 * callback must be given, as the instance returned from this constructor will 18038 * not be usable for a while. 18039 * 18040 * <li><i>loadParams</i> - an object containing parameters to pass to the 18041 * loader callback function when locale data is missing. The parameters are not 18042 * interpretted or modified in any way. They are simply passed along. The object 18043 * may contain any property/value pairs as long as the calling code is in 18044 * agreement with the loader callback function as to what those parameters mean. 18045 * </ul> 18046 * <p> 18047 * 18048 * 18049 * @constructor 18050 * @param {?Object} options options governing the way this date formatter instance works 18051 */ 18052 var DurationFmt = function(options) { 18053 var sync = true; 18054 var loadParams = undefined; 18055 18056 this.locale = new Locale(); 18057 this.length = "short"; 18058 this.style = "text"; 18059 18060 if (options) { 18061 if (options.locale) { 18062 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 18063 } 18064 18065 if (options.length) { 18066 if (options.length === 'short' || 18067 options.length === 'medium' || 18068 options.length === 'long' || 18069 options.length === 'full') { 18070 this.length = options.length; 18071 } 18072 } 18073 18074 if (options.style) { 18075 if (options.style === 'text' || options.style === 'clock') { 18076 this.style = options.style; 18077 } 18078 } 18079 18080 if (typeof(options.sync) !== 'undefined') { 18081 sync = (options.sync == true); 18082 } 18083 18084 if (typeof(options.useNative) === 'boolean') { 18085 this.useNative = options.useNative; 18086 } 18087 18088 loadParams = options.loadParams; 18089 } 18090 18091 new ResBundle({ 18092 locale: this.locale, 18093 name: "sysres", 18094 sync: sync, 18095 loadParams: loadParams, 18096 onLoad: ilib.bind(this, function (sysres) { 18097 switch (this.length) { 18098 case 'short': 18099 this.components = { 18100 year: sysres.getString("#{num}y"), 18101 month: sysres.getString("#{num}m", "durationShortMonths"), 18102 week: sysres.getString("#{num}w"), 18103 day: sysres.getString("#{num}d"), 18104 hour: sysres.getString("#{num}h"), 18105 minute: sysres.getString("#{num}m", "durationShortMinutes"), 18106 second: sysres.getString("#{num}s"), 18107 millisecond: sysres.getString("#{num}m", "durationShortMillis"), 18108 separator: sysres.getString(" ", "separatorShort"), 18109 finalSeparator: "" // not used at this length 18110 }; 18111 break; 18112 18113 case 'medium': 18114 this.components = { 18115 year: sysres.getString("1#1 yr|#{num} yrs", "durationMediumYears"), 18116 month: sysres.getString("1#1 mo|#{num} mos"), 18117 week: sysres.getString("1#1 wk|#{num} wks", "durationMediumWeeks"), 18118 day: sysres.getString("1#1 dy|#{num} dys"), 18119 hour: sysres.getString("1#1 hr|#{num} hrs", "durationMediumHours"), 18120 minute: sysres.getString("1#1 mi|#{num} min"), 18121 second: sysres.getString("1#1 se|#{num} sec"), 18122 millisecond: sysres.getString("#{num} ms"), 18123 separator: sysres.getString(" ", "separatorMedium"), 18124 finalSeparator: "" // not used at this length 18125 }; 18126 break; 18127 18128 case 'long': 18129 this.components = { 18130 year: sysres.getString("1#1 yr|#{num} yrs"), 18131 month: sysres.getString("1#1 mon|#{num} mons"), 18132 week: sysres.getString("1#1 wk|#{num} wks"), 18133 day: sysres.getString("1#1 day|#{num} days", "durationLongDays"), 18134 hour: sysres.getString("1#1 hr|#{num} hrs"), 18135 minute: sysres.getString("1#1 min|#{num} min"), 18136 second: sysres.getString("1#1 sec|#{num} sec"), 18137 millisecond: sysres.getString("#{num} ms"), 18138 separator: sysres.getString(", ", "separatorLong"), 18139 finalSeparator: "" // not used at this length 18140 }; 18141 break; 18142 18143 case 'full': 18144 this.components = { 18145 year: sysres.getString("1#1 year|#{num} years"), 18146 month: sysres.getString("1#1 month|#{num} months"), 18147 week: sysres.getString("1#1 week|#{num} weeks"), 18148 day: sysres.getString("1#1 day|#{num} days"), 18149 hour: sysres.getString("1#1 hour|#{num} hours"), 18150 minute: sysres.getString("1#1 minute|#{num} minutes"), 18151 second: sysres.getString("1#1 second|#{num} seconds"), 18152 millisecond: sysres.getString("1#1 millisecond|#{num} milliseconds"), 18153 separator: sysres.getString(", ", "separatorFull"), 18154 finalSeparator: sysres.getString(" and ", "finalSeparatorFull") 18155 }; 18156 break; 18157 } 18158 18159 if (this.style === 'clock') { 18160 new DateFmt({ 18161 locale: this.locale, 18162 calendar: "gregorian", 18163 type: "time", 18164 time: "ms", 18165 sync: sync, 18166 loadParams: loadParams, 18167 useNative: this.useNative, 18168 onLoad: ilib.bind(this, function (fmtMS) { 18169 this.timeFmtMS = fmtMS; 18170 new DateFmt({ 18171 locale: this.locale, 18172 calendar: "gregorian", 18173 type: "time", 18174 time: "hm", 18175 sync: sync, 18176 loadParams: loadParams, 18177 useNative: this.useNative, 18178 onLoad: ilib.bind(this, function (fmtHM) { 18179 this.timeFmtHM = fmtHM; 18180 new DateFmt({ 18181 locale: this.locale, 18182 calendar: "gregorian", 18183 type: "time", 18184 time: "hms", 18185 sync: sync, 18186 loadParams: loadParams, 18187 useNative: this.useNative, 18188 onLoad: ilib.bind(this, function (fmtHMS) { 18189 this.timeFmtHMS = fmtHMS; 18190 18191 // munge with the template to make sure that the hours are not formatted mod 12 18192 this.timeFmtHM.template = this.timeFmtHM.template.replace(/hh?/, 'H'); 18193 this.timeFmtHM.templateArr = this.timeFmtHM._tokenize(this.timeFmtHM.template); 18194 this.timeFmtHMS.template = this.timeFmtHMS.template.replace(/hh?/, 'H'); 18195 this.timeFmtHMS.templateArr = this.timeFmtHMS._tokenize(this.timeFmtHMS.template); 18196 18197 this._init(this.timeFmtHM.locinfo, options && options.onLoad); 18198 }) 18199 }); 18200 }) 18201 }); 18202 }) 18203 }); 18204 return; 18205 } 18206 18207 new LocaleInfo(this.locale, { 18208 sync: sync, 18209 loadParams: loadParams, 18210 onLoad: ilib.bind(this, function (li) { 18211 this._init(li, options && options.onLoad); 18212 }) 18213 }); 18214 }) 18215 }); 18216 }; 18217 18218 /** 18219 * @private 18220 * @static 18221 */ 18222 DurationFmt.complist = { 18223 "text": ["year", "month", "week", "day", "hour", "minute", "second", "millisecond"], 18224 "clock": ["year", "month", "week", "day"] 18225 }; 18226 18227 /** 18228 * @private 18229 */ 18230 DurationFmt.prototype._mapDigits = function(str) { 18231 if (this.useNative && this.digits) { 18232 return JSUtils.mapString(str.toString(), this.digits); 18233 } 18234 return str; 18235 }; 18236 18237 /** 18238 * @private 18239 * @param {LocaleInfo} locinfo 18240 * @param {function(DurationFmt)|undefined} onLoad 18241 */ 18242 DurationFmt.prototype._init = function(locinfo, onLoad) { 18243 var digits; 18244 if (typeof(this.useNative) === 'boolean') { 18245 // if the caller explicitly said to use native or not, honour that despite what the locale data says... 18246 if (this.useNative) { 18247 digits = locinfo.getNativeDigits(); 18248 if (digits) { 18249 this.digits = digits; 18250 } 18251 } 18252 } else if (locinfo.getDigitsStyle() === "native") { 18253 // else if the locale usually uses native digits, then use them 18254 digits = locinfo.getNativeDigits(); 18255 if (digits) { 18256 this.useNative = true; 18257 this.digits = digits; 18258 } 18259 } // else use western digits always 18260 18261 if (typeof(onLoad) === 'function') { 18262 onLoad(this); 18263 } 18264 }; 18265 18266 /** 18267 * Format a duration according to the format template of this formatter instance.<p> 18268 * 18269 * The components parameter should be an object that contains any or all of these 18270 * numeric properties: 18271 * 18272 * <ul> 18273 * <li>year 18274 * <li>month 18275 * <li>week 18276 * <li>day 18277 * <li>hour 18278 * <li>minute 18279 * <li>second 18280 * </ul> 18281 * <p> 18282 * 18283 * When a property is left out of the components parameter or has a value of 0, it will not 18284 * be formatted into the output string, except for times that include 0 minutes and 0 seconds. 18285 * 18286 * This formatter will not ensure that numbers for each component property is within the 18287 * valid range for that component. This allows you to format durations that are longer 18288 * than normal range. For example, you could format a duration has being "33 hours" rather 18289 * than "1 day, 9 hours". 18290 * 18291 * @param {Object} components date/time components to be formatted into a duration string 18292 * @return {IString} a string with the duration formatted according to the style and 18293 * locale set up for this formatter instance. If the components parameter is empty or 18294 * undefined, an empty string is returned. 18295 */ 18296 DurationFmt.prototype.format = function (components) { 18297 var i, list, temp, fmt, secondlast = true, str = ""; 18298 18299 list = DurationFmt.complist[this.style]; 18300 //for (i = 0; i < list.length; i++) { 18301 for (i = list.length-1; i >= 0; i--) { 18302 //console.log("Now dealing with " + list[i]); 18303 if (typeof(components[list[i]]) !== 'undefined' && components[list[i]] != 0) { 18304 if (str.length > 0) { 18305 str = ((this.length === 'full' && secondlast) ? this.components.finalSeparator : this.components.separator) + str; 18306 secondlast = false; 18307 } 18308 str = this.components[list[i]].formatChoice(components[list[i]], {num: this._mapDigits(components[list[i]])}) + str; 18309 } 18310 } 18311 18312 if (this.style === 'clock') { 18313 if (typeof(components.hour) !== 'undefined') { 18314 fmt = (typeof(components.second) !== 'undefined') ? this.timeFmtHMS : this.timeFmtHM; 18315 } else { 18316 fmt = this.timeFmtMS; 18317 } 18318 18319 if (str.length > 0) { 18320 str += this.components.separator; 18321 } 18322 str += fmt._formatTemplate(components, fmt.templateArr); 18323 } 18324 18325 return new IString(str); 18326 }; 18327 18328 /** 18329 * Return the locale that was used to construct this duration formatter object. If the 18330 * locale was not given as parameter to the constructor, this method returns the default 18331 * locale of the system. 18332 * 18333 * @return {Locale} locale that this duration formatter was constructed with 18334 */ 18335 DurationFmt.prototype.getLocale = function () { 18336 return this.locale; 18337 }; 18338 18339 /** 18340 * Return the length that was used to construct this duration formatter object. If the 18341 * length was not given as parameter to the constructor, this method returns the default 18342 * length. Valid values are "short", "medium", "long", and "full". 18343 * 18344 * @return {string} length that this duration formatter was constructed with 18345 */ 18346 DurationFmt.prototype.getLength = function () { 18347 return this.length; 18348 }; 18349 18350 /** 18351 * Return the style that was used to construct this duration formatter object. Returns 18352 * one of "text" or "clock". 18353 * 18354 * @return {string} style that this duration formatter was constructed with 18355 */ 18356 DurationFmt.prototype.getStyle = function () { 18357 return this.style; 18358 }; 18359 18360 18361 /*< isAlpha.js */ 18362 /* 18363 * ctype.islpha.js - Character type is alphabetic 18364 * 18365 * Copyright © 2012-2015, JEDLSoft 18366 * 18367 * Licensed under the Apache License, Version 2.0 (the "License"); 18368 * you may not use this file except in compliance with the License. 18369 * You may obtain a copy of the License at 18370 * 18371 * http://www.apache.org/licenses/LICENSE-2.0 18372 * 18373 * Unless required by applicable law or agreed to in writing, software 18374 * distributed under the License is distributed on an "AS IS" BASIS, 18375 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18376 * 18377 * See the License for the specific language governing permissions and 18378 * limitations under the License. 18379 */ 18380 18381 // !depends CType.js IString.js ilib.js 18382 18383 // !data ctype_l 18384 18385 18386 /** 18387 * Return whether or not the first character is alphabetic.<p> 18388 * 18389 * @static 18390 * @param {string|IString|number} ch character or code point to examine 18391 * @return {boolean} true if the first character is alphabetic. 18392 */ 18393 var isAlpha = function (ch) { 18394 var num; 18395 switch (typeof(ch)) { 18396 case 'number': 18397 num = ch; 18398 break; 18399 case 'string': 18400 num = IString.toCodePoint(ch, 0); 18401 break; 18402 case 'undefined': 18403 return false; 18404 default: 18405 num = ch._toCodePoint(0); 18406 break; 18407 } 18408 return CType._inRange(num, 'Lu', ilib.data.ctype_l) || 18409 CType._inRange(num, 'Ll', ilib.data.ctype_l) || 18410 CType._inRange(num, 'Lt', ilib.data.ctype_l) || 18411 CType._inRange(num, 'Lm', ilib.data.ctype_l) || 18412 CType._inRange(num, 'Lo', ilib.data.ctype_l); 18413 }; 18414 18415 /** 18416 * @protected 18417 * @param {boolean} sync 18418 * @param {Object|undefined} loadParams 18419 * @param {function(*)|undefined} onLoad 18420 */ 18421 isAlpha._init = function (sync, loadParams, onLoad) { 18422 CType._load("ctype_l", sync, loadParams, onLoad); 18423 }; 18424 18425 18426 /*< isAlnum.js */ 18427 /* 18428 * isAlnum.js - Character type is alphanumeric 18429 * 18430 * Copyright © 2012-2015, JEDLSoft 18431 * 18432 * Licensed under the Apache License, Version 2.0 (the "License"); 18433 * you may not use this file except in compliance with the License. 18434 * You may obtain a copy of the License at 18435 * 18436 * http://www.apache.org/licenses/LICENSE-2.0 18437 * 18438 * Unless required by applicable law or agreed to in writing, software 18439 * distributed under the License is distributed on an "AS IS" BASIS, 18440 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18441 * 18442 * See the License for the specific language governing permissions and 18443 * limitations under the License. 18444 */ 18445 18446 // !depends CType.js IString.js isAlpha.js isDigit.js 18447 18448 18449 /** 18450 * Return whether or not the first character is alphabetic or numeric.<p> 18451 * 18452 * @static 18453 * @param {string|IString|number} ch character or code point to examine 18454 * @return {boolean} true if the first character is alphabetic or numeric 18455 */ 18456 var isAlnum = function (ch) { 18457 var num; 18458 switch (typeof(ch)) { 18459 case 'number': 18460 num = ch; 18461 break; 18462 case 'string': 18463 num = IString.toCodePoint(ch, 0); 18464 break; 18465 case 'undefined': 18466 return false; 18467 default: 18468 num = ch._toCodePoint(0); 18469 break; 18470 } 18471 return isAlpha(num) || isDigit(num); 18472 }; 18473 18474 /** 18475 * @protected 18476 * @param {boolean} sync 18477 * @param {Object|undefined} loadParams 18478 * @param {function(*)|undefined} onLoad 18479 */ 18480 isAlnum._init = function (sync, loadParams, onLoad) { 18481 isAlpha._init(sync, loadParams, function () { 18482 isDigit._init(sync, loadParams, onLoad); 18483 }); 18484 }; 18485 18486 18487 18488 /*< isAscii.js */ 18489 /* 18490 * isAscii.js - Character type is ASCII 18491 * 18492 * Copyright © 2012-2015, JEDLSoft 18493 * 18494 * Licensed under the Apache License, Version 2.0 (the "License"); 18495 * you may not use this file except in compliance with the License. 18496 * You may obtain a copy of the License at 18497 * 18498 * http://www.apache.org/licenses/LICENSE-2.0 18499 * 18500 * Unless required by applicable law or agreed to in writing, software 18501 * distributed under the License is distributed on an "AS IS" BASIS, 18502 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18503 * 18504 * See the License for the specific language governing permissions and 18505 * limitations under the License. 18506 */ 18507 18508 // !depends CType.js IString.js ilib.js 18509 18510 // !data ctype 18511 18512 18513 /** 18514 * Return whether or not the first character is in the ASCII range.<p> 18515 * 18516 * @static 18517 * @param {string|IString|number} ch character or code point to examine 18518 * @return {boolean} true if the first character is in the ASCII range. 18519 */ 18520 var isAscii = function (ch) { 18521 var num; 18522 switch (typeof(ch)) { 18523 case 'number': 18524 num = ch; 18525 break; 18526 case 'string': 18527 num = IString.toCodePoint(ch, 0); 18528 break; 18529 case 'undefined': 18530 return false; 18531 default: 18532 num = ch._toCodePoint(0); 18533 break; 18534 } 18535 return CType._inRange(num, 'ascii', ilib.data.ctype); 18536 }; 18537 18538 /** 18539 * @protected 18540 * @param {boolean} sync 18541 * @param {Object|undefined} loadParams 18542 * @param {function(*)|undefined} onLoad 18543 */ 18544 isAscii._init = function (sync, loadParams, onLoad) { 18545 CType._init(sync, loadParams, onLoad); 18546 }; 18547 18548 18549 /*< isBlank.js */ 18550 /* 18551 * isBlank.js - Character type is blank 18552 * 18553 * Copyright © 2012-2015, JEDLSoft 18554 * 18555 * Licensed under the Apache License, Version 2.0 (the "License"); 18556 * you may not use this file except in compliance with the License. 18557 * You may obtain a copy of the License at 18558 * 18559 * http://www.apache.org/licenses/LICENSE-2.0 18560 * 18561 * Unless required by applicable law or agreed to in writing, software 18562 * distributed under the License is distributed on an "AS IS" BASIS, 18563 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18564 * 18565 * See the License for the specific language governing permissions and 18566 * limitations under the License. 18567 */ 18568 18569 // !depends CType.js IString.js ilib.js 18570 18571 // !data ctype 18572 18573 18574 /** 18575 * Return whether or not the first character is a blank character.<p> 18576 * 18577 * @static 18578 * ie. a space or a tab. 18579 * @param {string|IString|number} ch character or code point to examine 18580 * @return {boolean} true if the first character is a blank character. 18581 */ 18582 var isBlank = function (ch) { 18583 var num; 18584 switch (typeof(ch)) { 18585 case 'number': 18586 num = ch; 18587 break; 18588 case 'string': 18589 num = IString.toCodePoint(ch, 0); 18590 break; 18591 case 'undefined': 18592 return false; 18593 default: 18594 num = ch._toCodePoint(0); 18595 break; 18596 } 18597 return CType._inRange(num, 'blank', ilib.data.ctype); 18598 }; 18599 18600 /** 18601 * @protected 18602 * @param {boolean} sync 18603 * @param {Object|undefined} loadParams 18604 * @param {function(*)|undefined} onLoad 18605 */ 18606 isBlank._init = function (sync, loadParams, onLoad) { 18607 CType._init(sync, loadParams, onLoad); 18608 }; 18609 18610 18611 /*< isCntrl.js */ 18612 /* 18613 * isCntrl.js - Character type is control character 18614 * 18615 * Copyright © 2012-2015, JEDLSoft 18616 * 18617 * Licensed under the Apache License, Version 2.0 (the "License"); 18618 * you may not use this file except in compliance with the License. 18619 * You may obtain a copy of the License at 18620 * 18621 * http://www.apache.org/licenses/LICENSE-2.0 18622 * 18623 * Unless required by applicable law or agreed to in writing, software 18624 * distributed under the License is distributed on an "AS IS" BASIS, 18625 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18626 * 18627 * See the License for the specific language governing permissions and 18628 * limitations under the License. 18629 */ 18630 18631 // !depends CType.js IString.js ilib.js 18632 18633 // !data ctype_c 18634 18635 18636 /** 18637 * Return whether or not the first character is a control character.<p> 18638 * 18639 * @static 18640 * @param {string|IString|number} ch character or code point to examine 18641 * @return {boolean} true if the first character is a control character. 18642 */ 18643 var isCntrl = function (ch) { 18644 var num; 18645 switch (typeof(ch)) { 18646 case 'number': 18647 num = ch; 18648 break; 18649 case 'string': 18650 num = IString.toCodePoint(ch, 0); 18651 break; 18652 case 'undefined': 18653 return false; 18654 default: 18655 num = ch._toCodePoint(0); 18656 break; 18657 } 18658 return CType._inRange(num, 'Cc', ilib.data.ctype_c); 18659 }; 18660 18661 /** 18662 * @protected 18663 * @param {boolean} sync 18664 * @param {Object|undefined} loadParams 18665 * @param {function(*)|undefined} onLoad 18666 */ 18667 isCntrl._init = function (sync, loadParams, onLoad) { 18668 CType._load("ctype_c", sync, loadParams, onLoad); 18669 }; 18670 18671 18672 /*< isGraph.js */ 18673 /* 18674 * isGraph.js - Character type is graph char 18675 * 18676 * Copyright © 2012-2015, JEDLSoft 18677 * 18678 * Licensed under the Apache License, Version 2.0 (the "License"); 18679 * you may not use this file except in compliance with the License. 18680 * You may obtain a copy of the License at 18681 * 18682 * http://www.apache.org/licenses/LICENSE-2.0 18683 * 18684 * Unless required by applicable law or agreed to in writing, software 18685 * distributed under the License is distributed on an "AS IS" BASIS, 18686 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18687 * 18688 * See the License for the specific language governing permissions and 18689 * limitations under the License. 18690 */ 18691 18692 // !depends IString.js isSpace.js isCntrl.js ilib.js 18693 18694 18695 /** 18696 * Return whether or not the first character is any printable character 18697 * other than space.<p> 18698 * 18699 * @static 18700 * @param {string|IString|number} ch character or code point to examine 18701 * @return {boolean} true if the first character is any printable character 18702 * other than space. 18703 */ 18704 var isGraph = function (ch) { 18705 var num; 18706 switch (typeof(ch)) { 18707 case 'number': 18708 num = ch; 18709 break; 18710 case 'string': 18711 num = IString.toCodePoint(ch, 0); 18712 break; 18713 case 'undefined': 18714 return false; 18715 default: 18716 num = ch._toCodePoint(0); 18717 break; 18718 } 18719 return typeof(ch) !== 'undefined' && ch.length > 0 && !isSpace(num) && !isCntrl(num); 18720 }; 18721 18722 /** 18723 * @protected 18724 * @param {boolean} sync 18725 * @param {Object|undefined} loadParams 18726 * @param {function(*)|undefined} onLoad 18727 */ 18728 isGraph._init = function (sync, loadParams, onLoad) { 18729 isSpace._init(sync, loadParams, function () { 18730 isCntrl._init(sync, loadParams, onLoad); 18731 }); 18732 }; 18733 18734 18735 18736 /*< isIdeo.js */ 18737 /* 18738 * CType.js - Character type definitions 18739 * 18740 * Copyright © 2012-2015, JEDLSoft 18741 * 18742 * Licensed under the Apache License, Version 2.0 (the "License"); 18743 * you may not use this file except in compliance with the License. 18744 * You may obtain a copy of the License at 18745 * 18746 * http://www.apache.org/licenses/LICENSE-2.0 18747 * 18748 * Unless required by applicable law or agreed to in writing, software 18749 * distributed under the License is distributed on an "AS IS" BASIS, 18750 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18751 * 18752 * See the License for the specific language governing permissions and 18753 * limitations under the License. 18754 */ 18755 18756 // !depends CType.js IString.js 18757 18758 // !data ctype 18759 18760 18761 /** 18762 * Return whether or not the first character is an ideographic character.<p> 18763 * 18764 * @static 18765 * @param {string|IString|number} ch character or code point to examine 18766 * @return {boolean} true if the first character is an ideographic character. 18767 */ 18768 var isIdeo = function (ch) { 18769 var num; 18770 switch (typeof(ch)) { 18771 case 'number': 18772 num = ch; 18773 break; 18774 case 'string': 18775 num = IString.toCodePoint(ch, 0); 18776 break; 18777 case 'undefined': 18778 return false; 18779 default: 18780 num = ch._toCodePoint(0); 18781 break; 18782 } 18783 18784 return CType._inRange(num, 'cjk', ilib.data.ctype) || 18785 CType._inRange(num, 'cjkradicals', ilib.data.ctype) || 18786 CType._inRange(num, 'enclosedcjk', ilib.data.ctype) || 18787 CType._inRange(num, 'cjkpunct', ilib.data.ctype) || 18788 CType._inRange(num, 'cjkcompatibility', ilib.data.ctype); 18789 }; 18790 18791 /** 18792 * @protected 18793 * @param {boolean} sync 18794 * @param {Object|undefined} loadParams 18795 * @param {function(*)|undefined} onLoad 18796 */ 18797 isIdeo._init = function (sync, loadParams, onLoad) { 18798 CType._init(sync, loadParams, onLoad); 18799 }; 18800 18801 18802 /*< isLower.js */ 18803 /* 18804 * isLower.js - Character type is lower case letter 18805 * 18806 * Copyright © 2012-2015, JEDLSoft 18807 * 18808 * Licensed under the Apache License, Version 2.0 (the "License"); 18809 * you may not use this file except in compliance with the License. 18810 * You may obtain a copy of the License at 18811 * 18812 * http://www.apache.org/licenses/LICENSE-2.0 18813 * 18814 * Unless required by applicable law or agreed to in writing, software 18815 * distributed under the License is distributed on an "AS IS" BASIS, 18816 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18817 * 18818 * See the License for the specific language governing permissions and 18819 * limitations under the License. 18820 */ 18821 18822 // !depends CType.js IString.js 18823 18824 // !data ctype_l 18825 18826 18827 18828 /** 18829 * Return whether or not the first character is lower-case. For alphabetic 18830 * characters in scripts that do not make a distinction between upper- and 18831 * lower-case, this function always returns true.<p> 18832 * 18833 * @static 18834 * @param {string|IString|number} ch character or code point to examine 18835 * @return {boolean} true if the first character is lower-case. 18836 */ 18837 var isLower = function (ch) { 18838 var num; 18839 switch (typeof(ch)) { 18840 case 'number': 18841 num = ch; 18842 break; 18843 case 'string': 18844 num = IString.toCodePoint(ch, 0); 18845 break; 18846 case 'undefined': 18847 return false; 18848 default: 18849 num = ch._toCodePoint(0); 18850 break; 18851 } 18852 18853 return CType._inRange(num, 'Ll', ilib.data.ctype_l); 18854 }; 18855 18856 /** 18857 * @protected 18858 * @param {boolean} sync 18859 * @param {Object|undefined} loadParams 18860 * @param {function(*)|undefined} onLoad 18861 */ 18862 isLower._init = function (sync, loadParams, onLoad) { 18863 CType._load("ctype_l", sync, loadParams, onLoad); 18864 }; 18865 18866 18867 /*< isPrint.js */ 18868 /* 18869 * isPrint.js - Character type is printable char 18870 * 18871 * Copyright © 2012-2015, JEDLSoft 18872 * 18873 * Licensed under the Apache License, Version 2.0 (the "License"); 18874 * you may not use this file except in compliance with the License. 18875 * You may obtain a copy of the License at 18876 * 18877 * http://www.apache.org/licenses/LICENSE-2.0 18878 * 18879 * Unless required by applicable law or agreed to in writing, software 18880 * distributed under the License is distributed on an "AS IS" BASIS, 18881 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18882 * 18883 * See the License for the specific language governing permissions and 18884 * limitations under the License. 18885 */ 18886 18887 // !depends CType.js isCntrl.js 18888 18889 18890 /** 18891 * Return whether or not the first character is any printable character, 18892 * including space.<p> 18893 * 18894 * @static 18895 * @param {string|IString|number} ch character or code point to examine 18896 * @return {boolean} true if the first character is printable. 18897 */ 18898 var isPrint = function (ch) { 18899 return typeof(ch) !== 'undefined' && ch.length > 0 && !isCntrl(ch); 18900 }; 18901 18902 /** 18903 * @protected 18904 * @param {boolean} sync 18905 * @param {Object|undefined} loadParams 18906 * @param {function(*)|undefined} onLoad 18907 */ 18908 isPrint._init = function (sync, loadParams, onLoad) { 18909 isCntrl._init(sync, loadParams, onLoad); 18910 }; 18911 18912 18913 /*< isPunct.js */ 18914 /* 18915 * isPunct.js - Character type is punctuation 18916 * 18917 * Copyright © 2012-2015, JEDLSoft 18918 * 18919 * Licensed under the Apache License, Version 2.0 (the "License"); 18920 * you may not use this file except in compliance with the License. 18921 * You may obtain a copy of the License at 18922 * 18923 * http://www.apache.org/licenses/LICENSE-2.0 18924 * 18925 * Unless required by applicable law or agreed to in writing, software 18926 * distributed under the License is distributed on an "AS IS" BASIS, 18927 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18928 * 18929 * See the License for the specific language governing permissions and 18930 * limitations under the License. 18931 */ 18932 18933 // !depends CType.js IString.js 18934 18935 // !data ctype_p 18936 18937 18938 18939 /** 18940 * Return whether or not the first character is punctuation.<p> 18941 * 18942 * @static 18943 * @param {string|IString|number} ch character or code point to examine 18944 * @return {boolean} true if the first character is punctuation. 18945 */ 18946 var isPunct = function (ch) { 18947 var num; 18948 switch (typeof(ch)) { 18949 case 'number': 18950 num = ch; 18951 break; 18952 case 'string': 18953 num = IString.toCodePoint(ch, 0); 18954 break; 18955 case 'undefined': 18956 return false; 18957 default: 18958 num = ch._toCodePoint(0); 18959 break; 18960 } 18961 18962 return CType._inRange(num, 'Pd', ilib.data.ctype_p) || 18963 CType._inRange(num, 'Ps', ilib.data.ctype_p) || 18964 CType._inRange(num, 'Pe', ilib.data.ctype_p) || 18965 CType._inRange(num, 'Pc', ilib.data.ctype_p) || 18966 CType._inRange(num, 'Po', ilib.data.ctype_p) || 18967 CType._inRange(num, 'Pi', ilib.data.ctype_p) || 18968 CType._inRange(num, 'Pf', ilib.data.ctype_p); 18969 }; 18970 18971 /** 18972 * @protected 18973 * @param {boolean} sync 18974 * @param {Object|undefined} loadParams 18975 * @param {function(*)|undefined} onLoad 18976 */ 18977 isPunct._init = function (sync, loadParams, onLoad) { 18978 CType._load("ctype_p", sync, loadParams, onLoad); 18979 }; 18980 18981 18982 18983 /*< isUpper.js */ 18984 /* 18985 * isUpper.js - Character type is upper-case letter 18986 * 18987 * Copyright © 2012-2015, JEDLSoft 18988 * 18989 * Licensed under the Apache License, Version 2.0 (the "License"); 18990 * you may not use this file except in compliance with the License. 18991 * You may obtain a copy of the License at 18992 * 18993 * http://www.apache.org/licenses/LICENSE-2.0 18994 * 18995 * Unless required by applicable law or agreed to in writing, software 18996 * distributed under the License is distributed on an "AS IS" BASIS, 18997 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18998 * 18999 * See the License for the specific language governing permissions and 19000 * limitations under the License. 19001 */ 19002 19003 // !depends CType.js IString.js 19004 19005 // !data ctype_l 19006 19007 19008 19009 /** 19010 * Return whether or not the first character is upper-case. For alphabetic 19011 * characters in scripts that do not make a distinction between upper- and 19012 * lower-case, this function always returns true.<p> 19013 * 19014 * @static 19015 * @param {string|IString|number} ch character or code point to examine 19016 * @return {boolean} true if the first character is upper-case. 19017 */ 19018 var isUpper = function (ch) { 19019 var num; 19020 switch (typeof(ch)) { 19021 case 'number': 19022 num = ch; 19023 break; 19024 case 'string': 19025 num = IString.toCodePoint(ch, 0); 19026 break; 19027 case 'undefined': 19028 return false; 19029 default: 19030 num = ch._toCodePoint(0); 19031 break; 19032 } 19033 19034 return CType._inRange(num, 'Lu', ilib.data.ctype_l); 19035 }; 19036 19037 /** 19038 * @protected 19039 * @param {boolean} sync 19040 * @param {Object|undefined} loadParams 19041 * @param {function(*)|undefined} onLoad 19042 */ 19043 isUpper._init = function (sync, loadParams, onLoad) { 19044 CType._load("ctype_l", sync, loadParams, onLoad); 19045 }; 19046 19047 19048 /*< isXdigit.js */ 19049 /* 19050 * isXdigit.js - Character type is hex digit 19051 * 19052 * Copyright © 2012-2015, JEDLSoft 19053 * 19054 * Licensed under the Apache License, Version 2.0 (the "License"); 19055 * you may not use this file except in compliance with the License. 19056 * You may obtain a copy of the License at 19057 * 19058 * http://www.apache.org/licenses/LICENSE-2.0 19059 * 19060 * Unless required by applicable law or agreed to in writing, software 19061 * distributed under the License is distributed on an "AS IS" BASIS, 19062 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19063 * 19064 * See the License for the specific language governing permissions and 19065 * limitations under the License. 19066 */ 19067 19068 // !depends CType.js IString.js 19069 19070 // !data ctype 19071 19072 19073 19074 /** 19075 * Return whether or not the first character is a hexadecimal digit written 19076 * in the Latin script. (0-9 or A-F)<p> 19077 * 19078 * @static 19079 * @param {string|IString|number} ch character or code point to examine 19080 * @return {boolean} true if the first character is a hexadecimal digit written 19081 * in the Latin script. 19082 */ 19083 var isXdigit = function (ch) { 19084 var num; 19085 switch (typeof(ch)) { 19086 case 'number': 19087 num = ch; 19088 break; 19089 case 'string': 19090 num = IString.toCodePoint(ch, 0); 19091 break; 19092 case 'undefined': 19093 return false; 19094 default: 19095 num = ch._toCodePoint(0); 19096 break; 19097 } 19098 19099 return CType._inRange(num, 'xdigit', ilib.data.ctype); 19100 }; 19101 19102 /** 19103 * @protected 19104 * @param {boolean} sync 19105 * @param {Object|undefined} loadParams 19106 * @param {function(*)|undefined} onLoad 19107 */ 19108 isXdigit._init = function (sync, loadParams, onLoad) { 19109 CType._init(sync, loadParams, onLoad); 19110 }; 19111 19112 19113 /*< isScript.js */ 19114 /* 19115 * isScript.js - Character type is script 19116 * 19117 * Copyright © 2012-2015, JEDLSoft 19118 * 19119 * Licensed under the Apache License, Version 2.0 (the "License"); 19120 * you may not use this file except in compliance with the License. 19121 * You may obtain a copy of the License at 19122 * 19123 * http://www.apache.org/licenses/LICENSE-2.0 19124 * 19125 * Unless required by applicable law or agreed to in writing, software 19126 * distributed under the License is distributed on an "AS IS" BASIS, 19127 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19128 * 19129 * See the License for the specific language governing permissions and 19130 * limitations under the License. 19131 */ 19132 19133 // !depends CType.js IString.js 19134 19135 // !data scriptToRange 19136 19137 19138 19139 /** 19140 * Return whether or not the first character in the given string is 19141 * in the given script. The script is given as the 4-letter ISO 19142 * 15924 script code.<p> 19143 * 19144 * @static 19145 * @param {string|IString|number} ch character or code point to examine 19146 * @param {string} script the 4-letter ISO 15924 to query against 19147 * @return {boolean} true if the first character is in the given script, and 19148 * false otherwise 19149 */ 19150 var isScript = function (ch, script) { 19151 var num; 19152 switch (typeof(ch)) { 19153 case 'number': 19154 num = ch; 19155 break; 19156 case 'string': 19157 num = IString.toCodePoint(ch, 0); 19158 break; 19159 case 'undefined': 19160 return false; 19161 default: 19162 num = ch._toCodePoint(0); 19163 break; 19164 } 19165 19166 return CType._inRange(num, script, ilib.data.scriptToRange); 19167 }; 19168 19169 /** 19170 * @protected 19171 * @param {boolean} sync 19172 * @param {Object|undefined} loadParams 19173 * @param {function(*)|undefined} onLoad 19174 */ 19175 isScript._init = function (sync, loadParams, onLoad) { 19176 CType._load("scriptToRange", sync, loadParams, onLoad); 19177 }; 19178 19179 19180 /*< ScriptInfo.js */ 19181 /* 19182 * ScriptInfo.js - information about scripts 19183 * 19184 * Copyright © 2012-2015, JEDLSoft 19185 * 19186 * Licensed under the Apache License, Version 2.0 (the "License"); 19187 * you may not use this file except in compliance with the License. 19188 * You may obtain a copy of the License at 19189 * 19190 * http://www.apache.org/licenses/LICENSE-2.0 19191 * 19192 * Unless required by applicable law or agreed to in writing, software 19193 * distributed under the License is distributed on an "AS IS" BASIS, 19194 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19195 * 19196 * See the License for the specific language governing permissions and 19197 * limitations under the License. 19198 */ 19199 19200 // !depends ilib.js Utils.js 19201 19202 // !data scripts 19203 19204 19205 /** 19206 * @class 19207 * Create a new script info instance. This class encodes information about 19208 * scripts, which are sets of characters used in a writing system.<p> 19209 * 19210 * The options object may contain any of the following properties: 19211 * 19212 * <ul> 19213 * <li><i>onLoad</i> - a callback function to call when the script info object is fully 19214 * loaded. When the onLoad option is given, the script info object will attempt to 19215 * load any missing locale data using the ilib loader callback. 19216 * When the constructor is done (even if the data is already preassembled), the 19217 * onLoad function is called with the current instance as a parameter, so this 19218 * callback can be used with preassembled or dynamic loading or a mix of the two. 19219 * 19220 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 19221 * asynchronously. If this option is given as "false", then the "onLoad" 19222 * callback must be given, as the instance returned from this constructor will 19223 * not be usable for a while. 19224 * 19225 * <li><i>loadParams</i> - an object containing parameters to pass to the 19226 * loader callback function when locale data is missing. The parameters are not 19227 * interpretted or modified in any way. They are simply passed along. The object 19228 * may contain any property/value pairs as long as the calling code is in 19229 * agreement with the loader callback function as to what those parameters mean. 19230 * </ul> 19231 * 19232 * 19233 * @constructor 19234 * @param {string} script The ISO 15924 4-letter identifier for the script 19235 * @param {Object} options parameters to initialize this matcher 19236 */ 19237 var ScriptInfo = function(script, options) { 19238 var sync = true, 19239 loadParams = undefined; 19240 19241 this.script = script; 19242 19243 if (options) { 19244 if (typeof(options.sync) !== 'undefined') { 19245 sync = (options.sync == true); 19246 } 19247 19248 if (typeof(options.loadParams) !== 'undefined') { 19249 loadParams = options.loadParams; 19250 } 19251 } 19252 19253 if (!ScriptInfo.cache) { 19254 ScriptInfo.cache = {}; 19255 } 19256 19257 if (!ilib.data.scripts) { 19258 Utils.loadData({ 19259 object: ScriptInfo, 19260 locale: "-", 19261 name: "scripts.json", 19262 sync: sync, 19263 loadParams: loadParams, 19264 callback: ilib.bind(this, function (info) { 19265 if (!info) { 19266 info = {"Latn":{"nb":215,"nm":"Latin","lid":"Latin","rtl":false,"ime":false,"casing":true}}; 19267 var spec = this.locale.getSpec().replace(/-/g, "_"); 19268 ScriptInfo.cache[spec] = info; 19269 } 19270 ilib.data.scripts = info; 19271 this.info = script && ilib.data.scripts[script]; 19272 if (options && typeof(options.onLoad) === 'function') { 19273 options.onLoad(this); 19274 } 19275 }) 19276 }); 19277 } else { 19278 this.info = ilib.data.scripts[script]; 19279 } 19280 19281 }; 19282 19283 /** 19284 * @private 19285 */ 19286 ScriptInfo._getScriptsArray = function() { 19287 var ret = [], 19288 script = undefined, 19289 scripts = ilib.data.scripts; 19290 19291 for (script in scripts) { 19292 if (script && scripts[script]) { 19293 ret.push(script); 19294 } 19295 } 19296 19297 return ret; 19298 }; 19299 19300 /** 19301 * Return an array of all ISO 15924 4-letter identifier script identifiers that 19302 * this copy of ilib knows about. 19303 * @static 19304 * @param {boolean} sync whether to find the available ids synchronously (true) or asynchronously (false) 19305 * @param {Object} loadParams arbitrary object full of properties to pass to the loader 19306 * @param {function(Array.<string>)} onLoad callback function to call when the data is finished loading 19307 * @return {Array.<string>} an array of all script identifiers that this copy of 19308 * ilib knows about 19309 */ 19310 ScriptInfo.getAllScripts = function(sync, loadParams, onLoad) { 19311 if (!ilib.data.scripts) { 19312 Utils.loadData({ 19313 object: ScriptInfo, 19314 locale: "-", 19315 name: "scripts.json", 19316 sync: sync, 19317 loadParams: loadParams, 19318 callback: ilib.bind(this, function (info) { 19319 ilib.data.scripts = info; 19320 19321 if (typeof(onLoad) === 'function') { 19322 onLoad(ScriptInfo._getScriptsArray()); 19323 } 19324 }) 19325 }); 19326 } 19327 19328 return ScriptInfo._getScriptsArray(); 19329 }; 19330 19331 ScriptInfo.prototype = { 19332 /** 19333 * Return the 4-letter ISO 15924 identifier associated 19334 * with this script. 19335 * @return {string} the 4-letter ISO code for this script 19336 */ 19337 getCode: function () { 19338 return this.info && this.script; 19339 }, 19340 19341 /** 19342 * Get the ISO 15924 code number associated with this 19343 * script. 19344 * 19345 * @return {number} the ISO 15924 code number 19346 */ 19347 getCodeNumber: function () { 19348 return this.info && this.info.nb || 0; 19349 }, 19350 19351 /** 19352 * Get the name of this script in English. 19353 * 19354 * @return {string} the name of this script in English 19355 */ 19356 getName: function () { 19357 return this.info && this.info.nm; 19358 }, 19359 19360 /** 19361 * Get the long identifier assciated with this script. 19362 * 19363 * @return {string} the long identifier of this script 19364 */ 19365 getLongCode: function () { 19366 return this.info && this.info.lid; 19367 }, 19368 19369 /** 19370 * Return the usual direction that text in this script is written 19371 * in. Possible return values are "rtl" for right-to-left, 19372 * "ltr" for left-to-right, and "ttb" for top-to-bottom. 19373 * 19374 * @return {string} the usual direction that text in this script is 19375 * written in 19376 */ 19377 getScriptDirection: function() { 19378 return (this.info && typeof(this.info.rtl) !== 'undefined' && this.info.rtl) ? "rtl" : "ltr"; 19379 }, 19380 19381 /** 19382 * Return true if this script typically requires an input method engine 19383 * to enter its characters. 19384 * 19385 * @return {boolean} true if this script typically requires an IME 19386 */ 19387 getNeedsIME: function () { 19388 return this.info && this.info.ime ? true : false; // converts undefined to false 19389 }, 19390 19391 /** 19392 * Return true if this script uses lower- and upper-case characters. 19393 * 19394 * @return {boolean} true if this script uses letter case 19395 */ 19396 getCasing: function () { 19397 return this.info && this.info.casing ? true : false; // converts undefined to false 19398 } 19399 }; 19400 19401 19402 /*< Name.js */ 19403 /* 19404 * Name.js - Person name parser 19405 * 19406 * Copyright © 2013-2015, JEDLSoft 19407 * 19408 * Licensed under the Apache License, Version 2.0 (the "License"); 19409 * you may not use this file except in compliance with the License. 19410 * You may obtain a copy of the License at 19411 * 19412 * http://www.apache.org/licenses/LICENSE-2.0 19413 * 19414 * Unless required by applicable law or agreed to in writing, software 19415 * distributed under the License is distributed on an "AS IS" BASIS, 19416 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19417 * 19418 * See the License for the specific language governing permissions and 19419 * limitations under the License. 19420 */ 19421 19422 /* !depends 19423 ilib.js 19424 Locale.js 19425 Utils.js 19426 isAlpha.js 19427 isIdeo.js 19428 isPunct.js 19429 isSpace.js 19430 JSUtils.js 19431 IString.js 19432 */ 19433 19434 // !data name 19435 19436 // notes: 19437 // icelandic given names: http://en.wiktionary.org/wiki/Appendix:Icelandic_given_names 19438 // danish approved given names: http://www.familiestyrelsen.dk/samliv/navne/ 19439 // http://www.mentalfloss.com/blogs/archives/59277 19440 // other countries with first name restrictions: Norway, China, New Zealand, Japan, Sweden, Germany, Hungary 19441 19442 19443 19444 /** 19445 * @class 19446 * A class to parse names of people. Different locales have different conventions when it 19447 * comes to naming people.<p> 19448 * 19449 * The options can contain any of the following properties: 19450 * 19451 * <ul> 19452 * <li><i>locale</i> - use the rules and conventions of the given locale in order to parse 19453 * the name 19454 * <li><i>style</i> - explicitly use the named style to parse the name. Valid values so 19455 * far are "western" and "asian". If this property is not specified, then the style will 19456 * be gleaned from the name itself. This class will count the total number of Latin or Asian 19457 * characters. If the majority of the characters are in one style, that style will be 19458 * used to parse the whole name. 19459 * <li><i>order</i> - explicitly use the given order for names. In some locales, such 19460 * as Russian, names may be written equally validly as "givenName familyName" or "familyName 19461 * givenName". This option tells the parser which order to prefer, and overrides the 19462 * default order for the locale. Valid values are "gf" (given-family) or "fg" (family-given). 19463 * <li><i>useSpaces</i> - explicitly specifies whether to use spaces or not between the given name , middle name 19464 * and family name. 19465 * <li>onLoad - a callback function to call when the name info is fully 19466 * loaded and the name has been parsed. When the onLoad option is given, the name object 19467 * will attempt to load any missing locale data using the ilib loader callback. 19468 * When the constructor is done (even if the data is already preassembled), the 19469 * onLoad function is called with the current instance as a parameter, so this 19470 * callback can be used with preassembled or dynamic loading or a mix of the two. 19471 * 19472 * <li>sync - tell whether to load any missing locale data synchronously or 19473 * asynchronously. If this option is given as "false", then the "onLoad" 19474 * callback must be given, as the instance returned from this constructor will 19475 * not be usable for a while. 19476 * 19477 * <li><i>loadParams</i> - an object containing parameters to pass to the 19478 * loader callback function when locale data is missing. The parameters are not 19479 * interpretted or modified in any way. They are simply passed along. The object 19480 * may contain any property/value pairs as long as the calling code is in 19481 * agreement with the loader callback function as to what those parameters mean. 19482 * </ul> 19483 * 19484 * When the parser has completed its parsing, it fills in the fields listed below.<p> 19485 * 19486 * For names that include auxilliary words, such as the family name "van der Heijden", all 19487 * of the auxilliary words ("van der") will be included in the field.<p> 19488 * 19489 * For names in Spanish locales, it is assumed that the family name is doubled. That is, 19490 * a person may have a paternal family name followed by a maternal family name. All 19491 * family names will be listed in the familyName field as normal, separated by spaces. 19492 * When formatting the short version of such names, only the paternal family name will 19493 * be used. 19494 * 19495 * 19496 * @constructor 19497 * @param {string|Name=} name the name to parse 19498 * @param {Object=} options Options governing the construction of this name instance 19499 */ 19500 var Name = function (name, options) { 19501 var sync = true; 19502 19503 if (!name || name.length === 0) { 19504 return; 19505 } 19506 19507 this.loadParams = {}; 19508 19509 if (options) { 19510 if (options.locale) { 19511 this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; 19512 } 19513 19514 if (options.style && (options.style === "asian" || options.style === "western")) { 19515 this.style = options.style; 19516 } 19517 19518 if (options.order && (options.order === "gmf" || options.order === "fmg" || options.order === "fgm")) { 19519 this.order = options.order; 19520 } 19521 19522 if (typeof (options.sync) !== 'undefined') { 19523 sync = (options.sync == true); 19524 } 19525 19526 if (typeof (options.loadParams) !== 'undefined') { 19527 this.loadParams = options.loadParams; 19528 } 19529 } 19530 19531 if (!Name.cache) { 19532 Name.cache = {}; 19533 } 19534 19535 this.locale = this.locale || new Locale(); 19536 19537 isAlpha._init(sync, this.loadParams, /** @type {function()|undefined} */ ilib.bind(this, function() { 19538 isIdeo._init(sync, this.loadParams, /** @type {function()|undefined} */ ilib.bind(this, function() { 19539 isPunct._init(sync, this.loadParams, /** @type {function()|undefined} */ ilib.bind(this, function() { 19540 isSpace._init(sync, this.loadParams, /** @type {function()|undefined} */ ilib.bind(this, function() { 19541 Utils.loadData({ 19542 object: Name, 19543 locale: this.locale, 19544 name: "name.json", 19545 sync: sync, 19546 loadParams: this.loadParams, 19547 callback: ilib.bind(this, function (info) { 19548 if (!info) { 19549 info = Name.defaultInfo; 19550 var spec = this.locale.getSpec().replace(/-/g, "_"); 19551 Name.cache[spec] = info; 19552 } 19553 if (typeof (name) === 'object') { 19554 // copy constructor 19555 /** 19556 * The prefixes for this name 19557 * @type {string|Array.<string>} 19558 */ 19559 this.prefix = name.prefix; 19560 /** 19561 * The given (personal) name in this name. 19562 * @type {string|Array.<string>} 19563 */ 19564 this.givenName = name.givenName; 19565 /** 19566 * The middle names used in this name. If there are multiple middle names, they all 19567 * appear in this field separated by spaces. 19568 * @type {string|Array.<string>} 19569 */ 19570 this.middleName = name.middleName; 19571 /** 19572 * The family names in this name. If there are multiple family names, they all 19573 * appear in this field separated by spaces. 19574 * @type {string|Array.<string>} 19575 */ 19576 this.familyName = name.familyName; 19577 /** 19578 * The suffixes for this name. If there are multiple suffixes, they all 19579 * appear in this field separated by spaces. 19580 * @type {string|Array.<string>} 19581 */ 19582 this.suffix = name.suffix; 19583 19584 // private properties 19585 this.locale = name.locale; 19586 this.style = name.style; 19587 this.order = name.order; 19588 this.useSpaces = name.useSpaces; 19589 this.isAsianName = name.isAsianName; 19590 return; 19591 } 19592 /** 19593 * @type {{ 19594 * nameStyle:string, 19595 * order:string, 19596 * prefixes:Array.<string>, 19597 * suffixes:Array.<string>, 19598 * auxillaries:Array.<string>, 19599 * honorifics:Array.<string>, 19600 * knownFamilyNames:Array.<string>, 19601 * noCompoundFamilyNames:boolean, 19602 * sortByHeadWord:boolean 19603 * }} */ 19604 this.info = info; 19605 this._init(name); 19606 if (options && typeof(options.onLoad) === 'function') { 19607 options.onLoad(this); 19608 } 19609 }) 19610 }); 19611 })); 19612 })); 19613 })); 19614 })); 19615 }; 19616 19617 Name.defaultInfo = ilib.data.name || { 19618 "components": { 19619 "short": { 19620 "g": 1, 19621 "f": 1 19622 }, 19623 "medium": { 19624 "g": 1, 19625 "m": 1, 19626 "f": 1 19627 }, 19628 "long": { 19629 "p": 1, 19630 "g": 1, 19631 "m": 1, 19632 "f": 1 19633 }, 19634 "full": { 19635 "p": 1, 19636 "g": 1, 19637 "m": 1, 19638 "f": 1, 19639 "s": 1 19640 } 19641 }, 19642 "format": "{prefix} {givenName} {middleName} {familyName}{suffix}", 19643 "sortByHeadWord": false, 19644 "nameStyle": "western", 19645 "conjunctions": { 19646 "and1": "and", 19647 "and2": "and", 19648 "or1": "or", 19649 "or2": "or" 19650 }, 19651 "auxillaries": { 19652 "von": 1, 19653 "von der": 1, 19654 "von den": 1, 19655 "van": 1, 19656 "van der": 1, 19657 "van de": 1, 19658 "van den": 1, 19659 "de": 1, 19660 "di": 1, 19661 "de": 1, 19662 "la": 1, 19663 "lo": 1, 19664 "des": 1, 19665 "le": 1, 19666 "les": 1, 19667 "du": 1, 19668 "de la": 1, 19669 "del": 1, 19670 "de los": 1, 19671 "de las": 1 19672 }, 19673 "prefixes": [ 19674 "doctor", 19675 "dr", 19676 "mr", 19677 "mrs", 19678 "ms", 19679 "mister", 19680 "madame", 19681 "madamoiselle", 19682 "miss", 19683 "monsieur", 19684 "señor", 19685 "señora", 19686 "señorita" 19687 ], 19688 "suffixes": [ 19689 ",", 19690 "junior", 19691 "jr", 19692 "senior", 19693 "sr", 19694 "i", 19695 "ii", 19696 "iii", 19697 "esq", 19698 "phd", 19699 "md" 19700 ], 19701 "patronymicName":[ ], 19702 "familyNames":[ ] 19703 }; 19704 19705 /** 19706 * Return true if the given character is in the range of the Han, Hangul, or kana 19707 * scripts. 19708 * @static 19709 * @protected 19710 */ 19711 Name._isAsianChar = function(c) { 19712 return isIdeo(c) || 19713 CType.withinRange(c, "hangul") || 19714 CType.withinRange(c, "hiragana") || 19715 CType.withinRange(c, "katakana"); 19716 }; 19717 19718 19719 /** 19720 * @static 19721 * @protected 19722 */ 19723 Name._isAsianName = function (name, language) { 19724 // the idea is to count the number of asian chars and the number 19725 // of latin chars. If one is greater than the other, choose 19726 // that style. 19727 var asian = 0, 19728 latin = 0, 19729 i; 19730 19731 if (name && name.length > 0) { 19732 for (i = 0; i < name.length; i++) { 19733 var c = name.charAt(i); 19734 19735 if (Name._isAsianChar(c)) { 19736 if (language =="ko" || language =="ja" || language =="zh") { 19737 return true; 19738 } 19739 asian++; 19740 } else if (isAlpha(c)) { 19741 if (!language =="ko" || !language =="ja" || !language =="zh") { 19742 return false; 19743 } 19744 latin++; 19745 } 19746 } 19747 19748 return latin < asian; 19749 } 19750 19751 return false; 19752 }; 19753 19754 /** 19755 * Return true if any Latin letters are found in the string. Return 19756 * false if all the characters are non-Latin. 19757 * @static 19758 * @protected 19759 */ 19760 Name._isEuroName = function (name, language) { 19761 var c, 19762 n = new IString(name), 19763 it = n.charIterator(); 19764 19765 while (it.hasNext()) { 19766 c = it.next(); 19767 19768 if (!Name._isAsianChar(c) && !isPunct(c) && !isSpace(c)) { 19769 return true; 19770 } else if (Name._isAsianChar(c) && (language =="ko" || language =="ja" || language =="zh")) { 19771 return false; 19772 } 19773 } 19774 return false; 19775 }; 19776 19777 Name.prototype = { 19778 /** 19779 * @protected 19780 */ 19781 _init: function (name) { 19782 var parts, prefixArray, prefix, prefixLower, 19783 suffixArray, suffix, suffixLower, 19784 i, info, hpSuffix; 19785 var currentLanguage = this.locale.getLanguage(); 19786 19787 if (name) { 19788 // for DFISH-12905, pick off the part that the LDAP server automatically adds to our names in HP emails 19789 i = name.search(/\s*[,\/\(\[\{<]/); 19790 if (i !== -1) { 19791 hpSuffix = name.substring(i); 19792 hpSuffix = hpSuffix.replace(/\s+/g, ' '); // compress multiple whitespaces 19793 suffixArray = hpSuffix.split(" "); 19794 var conjunctionIndex = this._findLastConjunction(suffixArray); 19795 if (conjunctionIndex > -1) { 19796 // it's got conjunctions in it, so this is not really a suffix 19797 hpSuffix = undefined; 19798 } else { 19799 name = name.substring(0, i); 19800 } 19801 } 19802 19803 this.isAsianName = Name._isAsianName(name, currentLanguage); 19804 if (this.info.nameStyle === "asian" || this.info.order === "fmg" || this.info.order === "fgm") { 19805 info = this.isAsianName ? this.info : ilib.data.name; 19806 } else { 19807 info = this.isAsianName ? ilib.data.name : this.info; 19808 } 19809 19810 if (this.isAsianName) { 19811 // all-asian names 19812 if (this.useSpaces == false) { 19813 name = name.replace(/\s+/g, ''); // eliminate all whitespaces 19814 } 19815 parts = name.trim().split(''); 19816 } 19817 //} 19818 else { 19819 name = name.replace(/, /g, ' , '); 19820 name = name.replace(/\s+/g, ' '); // compress multiple whitespaces 19821 parts = name.trim().split(' '); 19822 } 19823 19824 // check for prefixes 19825 if (parts.length > 1) { 19826 for (i = parts.length; i > 0; i--) { 19827 prefixArray = parts.slice(0, i); 19828 prefix = prefixArray.join(this.isAsianName ? '' : ' '); 19829 prefixLower = prefix.toLowerCase(); 19830 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 19831 if (ilib.isArray(this.info.prefixes) && 19832 (JSUtils.indexOf(this.info.prefixes, prefixLower) > -1 || this._isConjunction(prefixLower))) { 19833 if (this.prefix) { 19834 if (!this.isAsianName) { 19835 this.prefix += ' '; 19836 } 19837 this.prefix += prefix; 19838 } else { 19839 this.prefix = prefix; 19840 } 19841 parts = parts.slice(i); 19842 i = parts.length; 19843 } 19844 } 19845 } 19846 // check for suffixes 19847 if (parts.length > 1) { 19848 for (i = parts.length; i > 0; i--) { 19849 suffixArray = parts.slice(-i); 19850 suffix = suffixArray.join(this.isAsianName ? '' : ' '); 19851 suffixLower = suffix.toLowerCase(); 19852 suffixLower = suffixLower.replace(/[\.]/g, ''); // ignore periods 19853 if (ilib.isArray(this.info.suffixes) && JSUtils.indexOf(this.info.suffixes, suffixLower) > -1) { 19854 if (this.suffix) { 19855 if (!this.isAsianName && !isPunct(this.suffix.charAt(0))) { 19856 this.suffix = ' ' + this.suffix; 19857 } 19858 this.suffix = suffix + this.suffix; 19859 } else { 19860 this.suffix = suffix; 19861 } 19862 parts = parts.slice(0, parts.length - i); 19863 i = parts.length; 19864 } 19865 } 19866 } 19867 19868 if (hpSuffix) { 19869 this.suffix = (this.suffix && this.suffix + hpSuffix) || hpSuffix; 19870 } 19871 19872 // adjoin auxillary words to their headwords 19873 if (parts.length > 1 && !this.isAsianName) { 19874 parts = this._joinAuxillaries(parts, this.isAsianName); 19875 } 19876 19877 if (this.isAsianName) { 19878 this._parseAsianName(parts, currentLanguage); 19879 } else { 19880 this._parseWesternName(parts); 19881 } 19882 19883 this._joinNameArrays(); 19884 } 19885 }, 19886 19887 /** 19888 * @return {number} 19889 * 19890 _findSequence: function(parts, hash, isAsian) { 19891 var sequence, sequenceLower, sequenceArray, aux = [], i, ret = {}; 19892 19893 if (parts.length > 0 && hash) { 19894 //console.info("_findSequence: finding sequences"); 19895 for (var start = 0; start < parts.length-1; start++) { 19896 for ( i = parts.length; i > start; i-- ) { 19897 sequenceArray = parts.slice(start, i); 19898 sequence = sequenceArray.join(isAsian ? '' : ' '); 19899 sequenceLower = sequence.toLowerCase(); 19900 sequenceLower = sequenceLower.replace(/[,\.]/g, ''); // ignore commas and periods 19901 19902 //console.info("_findSequence: checking sequence: '" + sequenceLower + "'"); 19903 19904 if ( sequenceLower in hash ) { 19905 ret.match = sequenceArray; 19906 ret.start = start; 19907 ret.end = i; 19908 return ret; 19909 //console.info("_findSequence: Found sequence '" + sequence + "' New parts list is " + JSON.stringify(parts)); 19910 } 19911 } 19912 } 19913 } 19914 19915 return undefined; 19916 }, 19917 */ 19918 19919 /** 19920 * @protected 19921 * @param {Array} parts 19922 * @param {Array} names 19923 * @param {boolean} isAsian 19924 * @param {boolean=} noCompoundPrefix 19925 */ 19926 _findPrefix: function (parts, names, isAsian, noCompoundPrefix) { 19927 var i, prefix, prefixLower, prefixArray, aux = []; 19928 19929 if (parts.length > 0 && names) { 19930 for (i = parts.length; i > 0; i--) { 19931 prefixArray = parts.slice(0, i); 19932 prefix = prefixArray.join(isAsian ? '' : ' '); 19933 prefixLower = prefix.toLowerCase(); 19934 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 19935 19936 if (prefixLower in names) { 19937 aux = aux.concat(isAsian ? prefix : prefixArray); 19938 if (noCompoundPrefix) { 19939 // don't need to parse further. Just return it as is. 19940 return aux; 19941 } 19942 parts = parts.slice(i); 19943 i = parts.length + 1; 19944 } 19945 } 19946 } 19947 19948 return aux; 19949 }, 19950 19951 /** 19952 * @protected 19953 */ 19954 _findSuffix: function (parts, names, isAsian) { 19955 var i, j, seq = ""; 19956 19957 for (i = 0; i < names.length; i++) { 19958 if (parts.length >= names[i].length) { 19959 j = 0; 19960 while (j < names[i].length && parts[parts.length - j] === names[i][names[i].length - j]) { 19961 j++; 19962 } 19963 if (j >= names[i].length) { 19964 seq = parts.slice(parts.length - j).join(isAsian ? "" : " ") + (isAsian ? "" : " ") + seq; 19965 parts = parts.slice(0, parts.length - j); 19966 i = -1; // restart the search 19967 } 19968 } 19969 } 19970 19971 this.suffix = seq; 19972 return parts; 19973 }, 19974 19975 /** 19976 * @protected 19977 * Tell whether or not the given word is a conjunction in this language. 19978 * @param {string} word the word to test 19979 * @return {boolean} true if the word is a conjunction 19980 */ 19981 _isConjunction: function _isConjunction(word) { 19982 return (this.info.conjunctions.and1 === word || 19983 this.info.conjunctions.and2 === word || 19984 this.info.conjunctions.or1 === word || 19985 this.info.conjunctions.or2 === word || 19986 ("&" === word) || 19987 ("+" === word)); 19988 }, 19989 19990 /** 19991 * Find the last instance of 'and' in the name 19992 * @protected 19993 * @param {Array.<string>} parts 19994 * @return {number} 19995 */ 19996 _findLastConjunction: function _findLastConjunction(parts) { 19997 var conjunctionIndex = -1, 19998 index, part; 19999 20000 for (index = 0; index < parts.length; index++) { 20001 part = parts[index]; 20002 if (typeof (part) === 'string') { 20003 part = part.toLowerCase(); 20004 // also recognize English 20005 if ("and" === part || "or" === part || "&" === part || "+" === part) { 20006 conjunctionIndex = index; 20007 } 20008 if (this._isConjunction(part)) { 20009 conjunctionIndex = index; 20010 } 20011 } 20012 } 20013 return conjunctionIndex; 20014 }, 20015 20016 /** 20017 * @protected 20018 * @param {Array.<string>} parts the current array of name parts 20019 * @param {boolean} isAsian true if the name is being parsed as an Asian name 20020 * @return {Array.<string>} the remaining parts after the prefixes have been removed 20021 */ 20022 _extractPrefixes: function (parts, isAsian) { 20023 var i = this._findPrefix(parts, this.info.prefixes, isAsian); 20024 if (i > 0) { 20025 this.prefix = parts.slice(0, i).join(isAsian ? "" : " "); 20026 return parts.slice(i); 20027 } 20028 // prefixes not found, so just return the array unmodified 20029 return parts; 20030 }, 20031 20032 /** 20033 * @protected 20034 * @param {Array.<string>} parts the current array of name parts 20035 * @param {boolean} isAsian true if the name is being parsed as an Asian name 20036 * @return {Array.<string>} the remaining parts after the suffices have been removed 20037 */ 20038 _extractSuffixes: function (parts, isAsian) { 20039 var i = this._findSuffix(parts, this.info.suffixes, isAsian); 20040 if (i > 0) { 20041 this.suffix = parts.slice(i).join(isAsian ? "" : " "); 20042 return parts.slice(0, i); 20043 } 20044 // suffices not found, so just return the array unmodified 20045 return parts; 20046 }, 20047 20048 /** 20049 * Adjoin auxillary words to their head words. 20050 * @protected 20051 * @param {Array.<string>} parts the current array of name parts 20052 * @param {boolean} isAsian true if the name is being parsed as an Asian name 20053 * @return {Array.<string>} the parts after the auxillary words have been plucked onto their head word 20054 */ 20055 _joinAuxillaries: function (parts, isAsian) { 20056 var start, i, prefixArray, prefix, prefixLower; 20057 20058 if (this.info.auxillaries && (parts.length > 2 || this.prefix)) { 20059 for (start = 0; start < parts.length - 1; start++) { 20060 for (i = parts.length; i > start; i--) { 20061 prefixArray = parts.slice(start, i); 20062 prefix = prefixArray.join(' '); 20063 prefixLower = prefix.toLowerCase(); 20064 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 20065 20066 if (prefixLower in this.info.auxillaries) { 20067 parts.splice(start, i + 1 - start, prefixArray.concat(parts[i])); 20068 i = start; 20069 } 20070 } 20071 } 20072 } 20073 20074 return parts; 20075 }, 20076 20077 /** 20078 * Recursively join an array or string into a long string. 20079 * @protected 20080 */ 20081 _joinArrayOrString: function _joinArrayOrString(part) { 20082 var i; 20083 if (typeof (part) === 'object') { 20084 for (i = 0; i < part.length; i++) { 20085 part[i] = this._joinArrayOrString(part[i]); 20086 } 20087 var ret = ""; 20088 part.forEach(function (segment) { 20089 if (ret.length > 0 && !isPunct(segment.charAt(0))) { 20090 ret += ' '; 20091 } 20092 ret += segment; 20093 }); 20094 20095 return ret; 20096 } 20097 20098 return part; 20099 }, 20100 20101 /** 20102 * @protected 20103 */ 20104 _joinNameArrays: function _joinNameArrays() { 20105 var prop; 20106 for (prop in this) { 20107 20108 if (this[prop] !== undefined && typeof (this[prop]) === 'object' && this[prop] instanceof Array) { 20109 20110 this[prop] = this._joinArrayOrString(this[prop]); 20111 } 20112 } 20113 }, 20114 20115 /** 20116 * @protected 20117 */ 20118 _parseAsianName: function (parts, language) { 20119 var familyNameArray = this._findPrefix(parts, this.info.knownFamilyNames, true, this.info.noCompoundFamilyNames); 20120 var tempFullName = parts.join(''); 20121 20122 if (familyNameArray && familyNameArray.length > 0) { 20123 this.familyName = familyNameArray.join(''); 20124 this.givenName = parts.slice(this.familyName.length).join(''); 20125 20126 //Overide parsing rules if spaces are found in korean 20127 if (language === "ko" && tempFullName.search(/\s*[/\s]/) > -1 && !this.suffix) { 20128 this._parseKoreanName(tempFullName); 20129 } 20130 } else if (this.locale.getLanguage() === "ja") { 20131 this._parseJapaneseName(parts); 20132 } else if (this.suffix || this.prefix) { 20133 this.familyName = parts.join(''); 20134 } else { 20135 this.givenName = parts.join(''); 20136 } 20137 }, 20138 20139 /** 20140 * @protected 20141 */ 20142 _parseKoreanName: function (name) { 20143 var tempName = name; 20144 20145 var spaceSplit = tempName.split(" "); 20146 var spceCount = spaceSplit.length; 20147 var fistSpaceIndex = tempName.indexOf(" "); 20148 var lastSpaceIndex = tempName.lastIndexOf(" "); 20149 20150 if (spceCount === 2) { 20151 this.familyName = spaceSplit[0]; 20152 this.givenName = tempName.slice(fistSpaceIndex, tempName.length); 20153 } else { 20154 this.familyName = spaceSplit[0]; 20155 this.middleName = tempName.slice(fistSpaceIndex, lastSpaceIndex); 20156 this.givenName = tempName.slice(lastSpaceIndex, tempName.length); 20157 } 20158 20159 }, 20160 20161 /** 20162 * @protected 20163 */ 20164 _parseJapaneseName: function (parts) { 20165 if (this.suffix && this.suffix.length > 1 && this.info.honorifics.indexOf(this.suffix)>-1) { 20166 if (parts.length === 1) { 20167 if (CType.withinRange(parts[0], "cjk")) { 20168 this.familyName = parts[0]; 20169 } else { 20170 this.givenName = parts[0]; 20171 } 20172 return; 20173 } else if (parts.length === 2) { 20174 this.familyName = parts.slice(0,parts.length).join("") 20175 return; 20176 } 20177 } 20178 if (parts.length > 1) { 20179 var fn = ""; 20180 for (var i = 0; i < parts.length; i++) { 20181 if (CType.withinRange(parts[i], "cjk")) { 20182 fn += parts[i]; 20183 } else if (fn.length > 1 && CType.withinRange(parts[i], "hiragana")) { 20184 this.familyName = fn; 20185 this.givenName = parts.slice(i,parts.length).join(""); 20186 return; 20187 } else { 20188 break; 20189 } 20190 } 20191 } 20192 if (parts.length === 1) { 20193 this.familyName = parts[0]; 20194 } else if (parts.length === 2) { 20195 this.familyName = parts[0]; 20196 this.givenName = parts[1]; 20197 } else if (parts.length === 3) { 20198 this.familyName = parts[0]; 20199 this.givenName = parts.slice(1,parts.length).join(""); 20200 } else if (parts.length > 3) { 20201 this.familyName = parts.slice(0,2).join("") 20202 this.givenName = parts.slice(2,parts.length).join(""); 20203 } 20204 }, 20205 20206 /** 20207 * @protected 20208 */ 20209 _parseSpanishName: function (parts) { 20210 var conjunctionIndex; 20211 20212 if (parts.length === 1) { 20213 if (this.prefix || typeof (parts[0]) === 'object') { 20214 this.familyName = parts[0]; 20215 } else { 20216 this.givenName = parts[0]; 20217 } 20218 } else if (parts.length === 2) { 20219 // we do G F 20220 this.givenName = parts[0]; 20221 this.familyName = parts[1]; 20222 } else if (parts.length === 3) { 20223 conjunctionIndex = this._findLastConjunction(parts); 20224 // if there's an 'and' in the middle spot, put everything in the first name 20225 if (conjunctionIndex === 1) { 20226 this.givenName = parts; 20227 } else { 20228 // else, do G F F 20229 this.givenName = parts[0]; 20230 this.familyName = parts.slice(1); 20231 } 20232 } else if (parts.length > 3) { 20233 //there are at least 4 parts to this name 20234 20235 conjunctionIndex = this._findLastConjunction(parts); 20236 ////console.log("@@@@@@@@@@@@@@@@"+conjunctionIndex) 20237 if (conjunctionIndex > 0) { 20238 // if there's a conjunction that's not the first token, put everything up to and 20239 // including the token after it into the first name, the last 2 tokens into 20240 // the family name (if they exist) and everything else in to the middle name 20241 // 0 1 2 3 4 5 20242 // G A G 20243 // G A G F 20244 // G G A G 20245 // G A G F F 20246 // G G A G F 20247 // G G G A G 20248 // G A G M F F 20249 // G G A G F F 20250 // G G G A G F 20251 // G G G G A G 20252 this.givenName = parts.splice(0, conjunctionIndex + 2); 20253 if (parts.length > 1) { 20254 this.familyName = parts.splice(parts.length - 2, 2); 20255 if (parts.length > 0) { 20256 this.middleName = parts; 20257 } 20258 } else if (parts.length === 1) { 20259 this.familyName = parts[0]; 20260 } 20261 } else { 20262 this.givenName = parts.splice(0, 1); 20263 this.familyName = parts.splice(parts.length - 2, 2); 20264 this.middleName = parts; 20265 } 20266 } 20267 }, 20268 20269 /** 20270 * @protected 20271 */ 20272 _parseIndonesianName: function (parts) { 20273 var conjunctionIndex; 20274 20275 if (parts.length === 1) { 20276 //if (this.prefix || typeof(parts[0]) === 'object') { 20277 //this.familyName = parts[0]; 20278 //} else { 20279 this.givenName = parts[0]; 20280 //} 20281 //} else if (parts.length === 2) { 20282 // we do G F 20283 //this.givenName = parts[0]; 20284 //this.familyName = parts[1]; 20285 } else if (parts.length >= 2) { 20286 //there are at least 3 parts to this name 20287 20288 conjunctionIndex = this._findLastConjunction(parts); 20289 if (conjunctionIndex > 0) { 20290 // if there's a conjunction that's not the first token, put everything up to and 20291 // including the token after it into the first name, the last 2 tokens into 20292 // the family name (if they exist) and everything else in to the middle name 20293 // 0 1 2 3 4 5 20294 // G A G 20295 // G A G F 20296 // G G A G 20297 // G A G F F 20298 // G G A G F 20299 // G G G A G 20300 // G A G M F F 20301 // G G A G F F 20302 // G G G A G F 20303 // G G G G A G 20304 this.givenName = parts.splice(0, conjunctionIndex + 2); 20305 if (parts.length > 1) { 20306 //this.familyName = parts.splice(parts.length-2, 2); 20307 //if ( parts.length > 0 ) { 20308 this.middleName = parts; 20309 } 20310 //} else if (parts.length === 1) { 20311 // this.familyName = parts[0]; 20312 //} 20313 } else { 20314 this.givenName = parts.splice(0, 1); 20315 //this.familyName = parts.splice(parts.length-2, 2); 20316 this.middleName = parts; 20317 } 20318 } 20319 }, 20320 20321 /** 20322 * @protected 20323 */ 20324 _parseGenericWesternName: function (parts) { 20325 /* Western names are parsed as follows, and rules are applied in this 20326 * order: 20327 * 20328 * G 20329 * G F 20330 * G M F 20331 * G M M F 20332 * P F 20333 * P G F 20334 */ 20335 var conjunctionIndex; 20336 20337 if (parts.length === 1) { 20338 if (this.prefix || typeof (parts[0]) === 'object') { 20339 // already has a prefix, so assume it goes with the family name like "Dr. Roberts" or 20340 // it is a name with auxillaries, which is almost always a family name 20341 this.familyName = parts[0]; 20342 } else { 20343 this.givenName = parts[0]; 20344 } 20345 } else if (parts.length === 2) { 20346 // we do G F 20347 if (this.info.order == 'fgm') { 20348 this.givenName = parts[1]; 20349 this.familyName = parts[0]; 20350 } else if (this.info.order == "gmf" || typeof (this.info.order) == 'undefined') { 20351 this.givenName = parts[0]; 20352 this.familyName = parts[1]; 20353 } 20354 } else if (parts.length >= 3) { 20355 //find the first instance of 'and' in the name 20356 conjunctionIndex = this._findLastConjunction(parts); 20357 20358 if (conjunctionIndex > 0) { 20359 // if there's a conjunction that's not the first token, put everything up to and 20360 // including the token after it into the first name, the last token into 20361 // the family name (if it exists) and everything else in to the middle name 20362 // 0 1 2 3 4 5 20363 // G A G M M F 20364 // G G A G M F 20365 // G G G A G F 20366 // G G G G A G 20367 //if(this.order == "gmf") { 20368 this.givenName = parts.slice(0, conjunctionIndex + 2); 20369 20370 if (conjunctionIndex + 1 < parts.length - 1) { 20371 this.familyName = parts.splice(parts.length - 1, 1); 20372 ////console.log(this.familyName); 20373 if (conjunctionIndex + 2 < parts.length - 1) { 20374 this.middleName = parts.slice(conjunctionIndex + 2, parts.length - conjunctionIndex - 3); 20375 } 20376 } else if (this.order == "fgm") { 20377 this.familyName = parts.slice(0, conjunctionIndex + 2); 20378 if (conjunctionIndex + 1 < parts.length - 1) { 20379 this.middleName = parts.splice(parts.length - 1, 1); 20380 if (conjunctionIndex + 2 < parts.length - 1) { 20381 this.givenName = parts.slice(conjunctionIndex + 2, parts.length - conjunctionIndex - 3); 20382 } 20383 } 20384 } 20385 } else { 20386 this.givenName = parts[0]; 20387 20388 this.middleName = parts.slice(1, parts.length - 1); 20389 20390 this.familyName = parts[parts.length - 1]; 20391 } 20392 } 20393 }, 20394 20395 /** 20396 * parse patrinomic name from the russian names 20397 * @protected 20398 * @param {Array.<string>} parts the current array of name parts 20399 * @return number index of the part which contains patronymic name 20400 */ 20401 _findPatronymicName: function(parts) { 20402 var index, part; 20403 for (index = 0; index < parts.length; index++) { 20404 part = parts[index]; 20405 if (typeof (part) === 'string') { 20406 part = part.toLowerCase(); 20407 20408 var subLength = this.info.patronymicName.length; 20409 while(subLength--) { 20410 if(part.indexOf(this.info.patronymicName[subLength])!== -1 ) 20411 return index; 20412 } 20413 } 20414 } 20415 return -1; 20416 }, 20417 20418 /** 20419 * find if the given part is patronymic name 20420 * 20421 * @protected 20422 * @param {string} part string from name parts @ 20423 * @return number index of the part which contains familyName 20424 */ 20425 _isPatronymicName: function(part) { 20426 var pName; 20427 if ( typeof (part) === 'string') { 20428 pName = part.toLowerCase(); 20429 20430 var subLength = this.info.patronymicName.length; 20431 while (subLength--) { 20432 if (pName.indexOf(this.info.patronymicName[subLength]) !== -1) 20433 return true; 20434 } 20435 } 20436 return false; 20437 }, 20438 20439 /** 20440 * find family name from the russian name 20441 * 20442 * @protected 20443 * @param {Array.<string>} parts the current array of name parts 20444 * @return boolean true if patronymic, false otherwise 20445 */ 20446 _findFamilyName: function(parts) { 20447 var index, part, substring; 20448 for (index = 0; index < parts.length; index++) { 20449 part = parts[index]; 20450 20451 if ( typeof (part) === 'string') { 20452 part = part.toLowerCase(); 20453 var length = part.length - 1; 20454 20455 if (this.info.familyName.indexOf(part) !== -1) { 20456 return index; 20457 } else if (part[length] === 'в' || part[length] === 'н' || 20458 part[length] === 'й') { 20459 substring = part.slice(0, -1); 20460 if (this.info.familyName.indexOf(substring) !== -1) { 20461 return index; 20462 } 20463 } else if ((part[length - 1] === 'в' && part[length] === 'а') || 20464 (part[length - 1] === 'н' && part[length] === 'а') || 20465 (part[length - 1] === 'а' && part[length] === 'я')) { 20466 substring = part.slice(0, -2); 20467 if (this.info.familyName.indexOf(substring) !== -1) { 20468 return index; 20469 } 20470 } 20471 } 20472 } 20473 return -1; 20474 }, 20475 20476 /** 20477 * parse russian name 20478 * 20479 * @protected 20480 * @param {Array.<string>} parts the current array of name parts 20481 * @return 20482 */ 20483 _parseRussianName: function(parts) { 20484 var conjunctionIndex, familyIndex = -1; 20485 20486 if (parts.length === 1) { 20487 if (this.prefix || typeof (parts[0]) === 'object') { 20488 // already has a prefix, so assume it goes with the family name 20489 // like "Dr. Roberts" or 20490 // it is a name with auxillaries, which is almost always a 20491 // family name 20492 this.familyName = parts[0]; 20493 } else { 20494 this.givenName = parts[0]; 20495 } 20496 } else if (parts.length === 2) { 20497 // we do G F 20498 if (this.info.order === 'fgm') { 20499 this.givenName = parts[1]; 20500 this.familyName = parts[0]; 20501 } else if (this.info.order === "gmf") { 20502 this.givenName = parts[0]; 20503 this.familyName = parts[1]; 20504 } else if ( typeof (this.info.order) === 'undefined') { 20505 if (this._isPatronymicName(parts[1]) === true) { 20506 this.middleName = parts[1]; 20507 this.givenName = parts[0]; 20508 } else if ((familyIndex = this._findFamilyName(parts)) !== -1) { 20509 if (familyIndex === 1) { 20510 this.givenName = parts[0]; 20511 this.familyName = parts[1]; 20512 } else { 20513 this.familyName = parts[0]; 20514 this.givenName = parts[1]; 20515 } 20516 20517 } else { 20518 this.givenName = parts[0]; 20519 this.familyName = parts[1]; 20520 } 20521 20522 } 20523 } else if (parts.length >= 3) { 20524 // find the first instance of 'and' in the name 20525 conjunctionIndex = this._findLastConjunction(parts); 20526 var patronymicNameIndex = this._findPatronymicName(parts); 20527 if (conjunctionIndex > 0) { 20528 // if there's a conjunction that's not the first token, put 20529 // everything up to and 20530 // including the token after it into the first name, the last 20531 // token into 20532 // the family name (if it exists) and everything else in to the 20533 // middle name 20534 // 0 1 2 3 4 5 20535 // G A G M M F 20536 // G G A G M F 20537 // G G G A G F 20538 // G G G G A G 20539 // if(this.order == "gmf") { 20540 this.givenName = parts.slice(0, conjunctionIndex + 2); 20541 20542 if (conjunctionIndex + 1 < parts.length - 1) { 20543 this.familyName = parts.splice(parts.length - 1, 1); 20544 // //console.log(this.familyName); 20545 if (conjunctionIndex + 2 < parts.length - 1) { 20546 this.middleName = parts.slice(conjunctionIndex + 2, 20547 parts.length - conjunctionIndex - 3); 20548 } 20549 } else if (this.order == "fgm") { 20550 this.familyName = parts.slice(0, conjunctionIndex + 2); 20551 if (conjunctionIndex + 1 < parts.length - 1) { 20552 this.middleName = parts.splice(parts.length - 1, 1); 20553 if (conjunctionIndex + 2 < parts.length - 1) { 20554 this.givenName = parts.slice(conjunctionIndex + 2, 20555 parts.length - conjunctionIndex - 3); 20556 } 20557 } 20558 } 20559 } else if (patronymicNameIndex !== -1) { 20560 this.middleName = parts[patronymicNameIndex]; 20561 20562 if (patronymicNameIndex === (parts.length - 1)) { 20563 this.familyName = parts[0]; 20564 this.givenName = parts.slice(1, patronymicNameIndex); 20565 } else { 20566 this.givenName = parts.slice(0, patronymicNameIndex); 20567 20568 this.familyName = parts[parts.length - 1]; 20569 } 20570 } else { 20571 this.givenName = parts[0]; 20572 20573 this.middleName = parts.slice(1, parts.length - 1); 20574 20575 this.familyName = parts[parts.length - 1]; 20576 } 20577 } 20578 }, 20579 20580 20581 /** 20582 * @protected 20583 */ 20584 _parseWesternName: function (parts) { 20585 20586 if (this.locale.getLanguage() === "es" || this.locale.getLanguage() === "pt") { 20587 // in spain and mexico and portugal, we parse names differently than in the rest of the world 20588 // because of the double family names 20589 this._parseSpanishName(parts); 20590 } else if (this.locale.getLanguage() === "ru") { 20591 /* 20592 * In Russian, names can be given equally validly as given-family 20593 * or family-given. Use the value of the "order" property of the 20594 * constructor options to give the default when the order is ambiguous. 20595 */ 20596 this._parseRussianName(parts); 20597 } else if (this.locale.getLanguage() === "id") { 20598 // in indonesia, we parse names differently than in the rest of the world 20599 // because names don't have family names usually. 20600 this._parseIndonesianName(parts); 20601 } else { 20602 this._parseGenericWesternName(parts); 20603 } 20604 }, 20605 20606 /** 20607 * When sorting names with auxiliary words (like "van der" or "de los"), determine 20608 * which is the "head word" and return a string that can be easily sorted by head 20609 * word. In English, names are always sorted by initial characters. In places like 20610 * the Netherlands or Germany, family names are sorted by the head word of a list 20611 * of names rather than the first element of that name. 20612 * @return {string|undefined} a string containing the family name[s] to be used for sorting 20613 * in the current locale, or undefined if there is no family name in this object 20614 */ 20615 getSortFamilyName: function () { 20616 var name, 20617 auxillaries, 20618 auxString, 20619 parts, 20620 i; 20621 20622 // no name to sort by 20623 if (!this.familyName) { 20624 return undefined; 20625 } 20626 20627 // first break the name into parts 20628 if (this.info) { 20629 if (this.info.sortByHeadWord) { 20630 if (typeof (this.familyName) === 'string') { 20631 name = this.familyName.replace(/\s+/g, ' '); // compress multiple whitespaces 20632 parts = name.trim().split(' '); 20633 } else { 20634 // already split 20635 parts = /** @type Array */ this.familyName; 20636 } 20637 20638 auxillaries = this._findPrefix(parts, this.info.auxillaries, false); 20639 if (auxillaries && auxillaries.length > 0) { 20640 if (typeof (this.familyName) === 'string') { 20641 auxString = auxillaries.join(' '); 20642 name = this.familyName.substring(auxString.length + 1) + ', ' + auxString; 20643 } else { 20644 name = parts.slice(auxillaries.length).join(' ') + 20645 ', ' + 20646 parts.slice(0, auxillaries.length).join(' '); 20647 } 20648 } 20649 } else if (this.info.knownFamilyNames && this.familyName) { 20650 parts = this.familyName.split(''); 20651 var familyNameArray = this._findPrefix(parts, this.info.knownFamilyNames, true, this.info.noCompoundFamilyNames); 20652 name = ""; 20653 for (i = 0; i < familyNameArray.length; i++) { 20654 name += (this.info.knownFamilyNames[familyNameArray[i]] || ""); 20655 } 20656 } 20657 } 20658 20659 return name || this.familyName; 20660 }, 20661 20662 getHeadFamilyName: function () {}, 20663 20664 /** 20665 * @protected 20666 * Return a shallow copy of the current instance. 20667 */ 20668 clone: function () { 20669 return new Name(this); 20670 } 20671 }; 20672 20673 20674 /*< NameFmt.js */ 20675 /* 20676 * NameFmt.js - Format person names for display 20677 * 20678 * Copyright © 2013-2015, JEDLSoft 20679 * 20680 * Licensed under the Apache License, Version 2.0 (the "License"); 20681 * you may not use this file except in compliance with the License. 20682 * You may obtain a copy of the License at 20683 * 20684 * http://www.apache.org/licenses/LICENSE-2.0 20685 * 20686 * Unless required by applicable law or agreed to in writing, software 20687 * distributed under the License is distributed on an "AS IS" BASIS, 20688 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20689 * 20690 * See the License for the specific language governing permissions and 20691 * limitations under the License. 20692 */ 20693 20694 /* !depends 20695 ilib.js 20696 Locale.js 20697 IString.js 20698 Name.js 20699 isPunct.js 20700 Utils.js 20701 */ 20702 20703 // !data name 20704 20705 20706 20707 20708 /** 20709 * @class 20710 * Creates a formatter that can format person name instances (Name) for display to 20711 * a user. The options may contain the following properties: 20712 * 20713 * <ul> 20714 * <li><i>locale</i> - Use the conventions of the given locale to construct the name format. 20715 * <li><i>style</i> - Format the name with the given style. The value of this property 20716 * should be one of the following strings: 20717 * <ul> 20718 * <li><i>short</i> - Format a short name with just the given and family names. 20719 * <li><i>medium</i> - Format a medium-length name with the given, middle, and family names. 20720 * <li><i>long</i> - Format a long name with all names available in the given name object, including 20721 * prefixes. 20722 * <li><i>full</i> - Format a long name with all names available in the given name object, including 20723 * prefixes and suffixes. 20724 * </ul> 20725 * <li><i>components</i> - Format the name with the given components in the correct 20726 * order for those components. Components are encoded as a string of letters representing 20727 * the desired components: 20728 * <ul> 20729 * <li><i>p</i> - prefixes 20730 * <li><i>g</i> - given name 20731 * <li><i>m</i> - middle names 20732 * <li><i>f</i> - family name 20733 * <li><i>s</i> - suffixes 20734 * </ul> 20735 * <p> 20736 * 20737 * For example, the string "pf" would mean to only format any prefixes and family names 20738 * together and leave out all the other parts of the name.<p> 20739 * 20740 * The components can be listed in any order in the string. The <i>components</i> option 20741 * overrides the <i>style</i> option if both are specified. 20742 * 20743 * <li>onLoad - a callback function to call when the locale info object is fully 20744 * loaded. When the onLoad option is given, the localeinfo object will attempt to 20745 * load any missing locale data using the ilib loader callback. 20746 * When the constructor is done (even if the data is already preassembled), the 20747 * onLoad function is called with the current instance as a parameter, so this 20748 * callback can be used with preassembled or dynamic loading or a mix of the two. 20749 * 20750 * <li>sync - tell whether to load any missing locale data synchronously or 20751 * asynchronously. If this option is given as "false", then the "onLoad" 20752 * callback must be given, as the instance returned from this constructor will 20753 * not be usable for a while. 20754 * 20755 * <li><i>loadParams</i> - an object containing parameters to pass to the 20756 * loader callback function when locale data is missing. The parameters are not 20757 * interpretted or modified in any way. They are simply passed along. The object 20758 * may contain any property/value pairs as long as the calling code is in 20759 * agreement with the loader callback function as to what those parameters mean. 20760 * </ul> 20761 * 20762 * Formatting names is a locale-dependent function, as the order of the components 20763 * depends on the locale. The following explains some of the details:<p> 20764 * 20765 * <ul> 20766 * <li>In Western countries, the given name comes first, followed by a space, followed 20767 * by the family name. In Asian countries, the family name comes first, followed immediately 20768 * by the given name with no space. But, that format is only used with Asian names written 20769 * in ideographic characters. In Asian countries, especially ones where both an Asian and 20770 * a Western language are used (Hong Kong, Singapore, etc.), the convention is often to 20771 * follow the language of the name. That is, Asian names are written in Asian style, and 20772 * Western names are written in Western style. This class follows that convention as 20773 * well. 20774 * <li>In other Asian countries, Asian names 20775 * written in Latin script are written with Asian ordering. eg. "Xu Ping-an" instead 20776 * of the more Western order "Ping-an Xu", as the order is thought to go with the style 20777 * that is appropriate for the name rather than the style for the language being written. 20778 * <li>In some Spanish speaking countries, people often take both their maternal and 20779 * paternal last names as their own family name. When formatting a short or medium style 20780 * of that family name, only the paternal name is used. In the long style, all the names 20781 * are used. eg. "Juan Julio Raul Lopez Ortiz" took the name "Lopez" from his father and 20782 * the name "Ortiz" from his mother. His family name would be "Lopez Ortiz". The formatted 20783 * short style of his name would be simply "Juan Lopez" which only uses his paternal 20784 * family name of "Lopez". 20785 * <li>In many Western languages, it is common to use auxillary words in family names. For 20786 * example, the family name of "Ludwig von Beethoven" in German is "von Beethoven", not 20787 * "Beethoven". This class ensures that the family name is formatted correctly with 20788 * all auxillary words. 20789 * </ul> 20790 * 20791 * 20792 * @constructor 20793 * @param {Object} options A set of options that govern how the formatter will behave 20794 */ 20795 var NameFmt = function(options) { 20796 var sync = true; 20797 20798 this.style = "short"; 20799 this.loadParams = {}; 20800 20801 if (options) { 20802 if (options.locale) { 20803 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 20804 } 20805 20806 if (options.style) { 20807 this.style = options.style; 20808 } 20809 20810 if (options.components) { 20811 this.components = options.components; 20812 } 20813 20814 if (typeof(options.sync) !== 'undefined') { 20815 sync = (options.sync == true); 20816 } 20817 20818 if (typeof(options.loadParams) !== 'undefined') { 20819 this.loadParams = options.loadParams; 20820 } 20821 } 20822 20823 // set up defaults in case we need them 20824 this.defaultEuroTemplate = new IString("{prefix} {givenName} {middleName} {familyName}{suffix}"); 20825 this.defaultAsianTemplate = new IString("{prefix}{familyName}{givenName}{middleName}{suffix}"); 20826 this.useFirstFamilyName = false; 20827 20828 switch (this.style) { 20829 default: 20830 case "s": 20831 case "short": 20832 this.style = "short"; 20833 break; 20834 case "m": 20835 case "medium": 20836 this.style = "medium"; 20837 break; 20838 case "l": 20839 case "long": 20840 this.style = "long"; 20841 break; 20842 case "f": 20843 case "full": 20844 this.style = "full"; 20845 break; 20846 } 20847 20848 if (!Name.cache) { 20849 Name.cache = {}; 20850 } 20851 20852 this.locale = this.locale || new Locale(); 20853 20854 isPunct._init(sync, this.loadParams, /** @type {function()|undefined} */ ilib.bind(this, function() { 20855 Utils.loadData({ 20856 object: Name, 20857 locale: this.locale, 20858 name: "name.json", 20859 sync: sync, 20860 loadParams: this.loadParams, 20861 callback: ilib.bind(this, function (info) { 20862 if (!info) { 20863 info = Name.defaultInfo; 20864 var spec = this.locale.getSpec().replace(/-/g, "_"); 20865 Name.cache[spec] = info; 20866 } 20867 this.info = info; 20868 this._init(); 20869 if (options && typeof(options.onLoad) === 'function') { 20870 options.onLoad(this); 20871 } 20872 }) 20873 }); 20874 })); 20875 }; 20876 20877 NameFmt.prototype = { 20878 /** 20879 * @protected 20880 */ 20881 _init: function() { 20882 if (this.components) { 20883 var valids = {"p":1,"g":1,"m":1,"f":1,"s":1}, 20884 arr = this.components.split(""); 20885 this.comps = {}; 20886 for (var i = 0; i < arr.length; i++) { 20887 if (valids[arr[i].toLowerCase()]) { 20888 this.comps[arr[i].toLowerCase()] = true; 20889 } 20890 } 20891 } else { 20892 this.comps = this.info.components[this.style]; 20893 } 20894 20895 this.template = new IString(this.info.format); 20896 20897 if (this.locale.language === "es" && (this.style !== "long" && this.style !== "full")) { 20898 this.useFirstFamilyName = true; // in spanish, they have 2 family names, the maternal and paternal 20899 } 20900 20901 this.isAsianLocale = (this.info.nameStyle === "asian"); 20902 }, 20903 20904 /** 20905 * adjoin auxillary words to their head words 20906 * @protected 20907 */ 20908 _adjoinAuxillaries: function (parts, namePrefix) { 20909 var start, i, prefixArray, prefix, prefixLower; 20910 20911 //console.info("_adjoinAuxillaries: finding and adjoining aux words in " + parts.join(' ')); 20912 20913 if ( this.info.auxillaries && (parts.length > 2 || namePrefix) ) { 20914 for ( start = 0; start < parts.length-1; start++ ) { 20915 for ( i = parts.length; i > start; i-- ) { 20916 prefixArray = parts.slice(start, i); 20917 prefix = prefixArray.join(' '); 20918 prefixLower = prefix.toLowerCase(); 20919 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 20920 20921 //console.info("_adjoinAuxillaries: checking aux prefix: '" + prefixLower + "' which is " + start + " to " + i); 20922 20923 if ( prefixLower in this.info.auxillaries ) { 20924 //console.info("Found! Old parts list is " + JSON.stringify(parts)); 20925 parts.splice(start, i+1-start, prefixArray.concat(parts[i])); 20926 //console.info("_adjoinAuxillaries: Found! New parts list is " + JSON.stringify(parts)); 20927 i = start; 20928 } 20929 } 20930 } 20931 } 20932 20933 //console.info("_adjoinAuxillaries: done. Result is " + JSON.stringify(parts)); 20934 20935 return parts; 20936 }, 20937 20938 /** 20939 * Return the locale for this formatter instance. 20940 * @return {Locale} the locale instance for this formatter 20941 */ 20942 getLocale: function () { 20943 return this.locale; 20944 }, 20945 20946 /** 20947 * Return the style of names returned by this formatter 20948 * @return {string} the style of names returned by this formatter 20949 */ 20950 getStyle: function () { 20951 return this.style; 20952 }, 20953 20954 /** 20955 * Return the list of components used to format names in this formatter 20956 * @return {string} the list of components 20957 */ 20958 getComponents: function () { 20959 return this.components; 20960 }, 20961 20962 /** 20963 * Format the name for display in the current locale with the options set up 20964 * in the constructor of this formatter instance.<p> 20965 * 20966 * If the name does not contain all the parts required for the style, those parts 20967 * will be left blank.<p> 20968 * 20969 * There are two basic styles of formatting: European, and Asian. If this formatter object 20970 * is set for European style, but an Asian name is passed to the format method, then this 20971 * method will format the Asian name with a generic Asian template. Similarly, if the 20972 * formatter is set for an Asian style, and a European name is passed to the format method, 20973 * the formatter will use a generic European template.<p> 20974 * 20975 * This means it is always safe to format any name with a formatter for any locale. You should 20976 * always get something at least reasonable as output.<p> 20977 * 20978 * @param {Name} name the name to format 20979 * @return {string|undefined} the name formatted according to the style of this formatter instance 20980 */ 20981 format: function(name) { 20982 var formatted, temp, modified, isAsianName; 20983 var currentLanguage = this.locale.getLanguage(); 20984 20985 if (!name || typeof(name) !== 'object') { 20986 return undefined; 20987 } 20988 20989 if ((typeof(name.isAsianName) === 'boolean' && !name.isAsianName) || 20990 Name._isEuroName([name.givenName, name.middleName, name.familyName].join(""), currentLanguage)) { 20991 isAsianName = false; // this is a euro name, even if the locale is asian 20992 modified = name.clone(); 20993 20994 // handle the case where there is no space if there is punctuation in the suffix like ", Phd". 20995 // Otherwise, put a space in to transform "PhD" to " PhD" 20996 /* 20997 console.log("suffix is " + modified.suffix); 20998 if ( modified.suffix ) { 20999 console.log("first char is " + modified.suffix.charAt(0)); 21000 console.log("isPunct(modified.suffix.charAt(0)) is " + isPunct(modified.suffix.charAt(0))); 21001 } 21002 */ 21003 if (modified.suffix && isPunct(modified.suffix.charAt(0)) === false) { 21004 modified.suffix = ' ' + modified.suffix; 21005 } 21006 21007 if (this.useFirstFamilyName && name.familyName) { 21008 var familyNameParts = modified.familyName.trim().split(' '); 21009 if (familyNameParts.length > 1) { 21010 familyNameParts = this._adjoinAuxillaries(familyNameParts, name.prefix); 21011 } //in spain and mexico, we parse names differently than in the rest of the world 21012 21013 modified.familyName = familyNameParts[0]; 21014 } 21015 21016 modified._joinNameArrays(); 21017 } else { 21018 isAsianName = true; 21019 modified = name; 21020 if (modified.suffix && currentLanguage === "ko" && this.info.honorifics.indexOf(name.suffix) == -1) { 21021 modified.suffix = ' ' + modified.suffix; 21022 } 21023 } 21024 21025 if (!this.template || isAsianName !== this.isAsianLocale) { 21026 temp = isAsianName ? this.defaultAsianTemplate : this.defaultEuroTemplate; 21027 } else { 21028 temp = this.template; 21029 } 21030 21031 var parts = { 21032 prefix: this.comps["p"] && modified.prefix || "", 21033 givenName: this.comps["g"] && modified.givenName || "", 21034 middleName: this.comps["m"] && modified.middleName || "", 21035 familyName: this.comps["f"] && modified.familyName || "", 21036 suffix: this.comps["s"] && modified.suffix || "" 21037 }; 21038 21039 formatted = temp.format(parts); 21040 return formatted.replace(/\s+/g, ' ').trim(); 21041 } 21042 }; 21043 21044 21045 /*< Address.js */ 21046 /* 21047 * Address.js - Represent a mailing address 21048 * 21049 * Copyright © 2013-2015, JEDLSoft 21050 * 21051 * Licensed under the Apache License, Version 2.0 (the "License"); 21052 * you may not use this file except in compliance with the License. 21053 * You may obtain a copy of the License at 21054 * 21055 * http://www.apache.org/licenses/LICENSE-2.0 21056 * 21057 * Unless required by applicable law or agreed to in writing, software 21058 * distributed under the License is distributed on an "AS IS" BASIS, 21059 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21060 * 21061 * See the License for the specific language governing permissions and 21062 * limitations under the License. 21063 */ 21064 21065 /*globals console RegExp */ 21066 21067 /* !depends 21068 ilib.js 21069 Utils.js 21070 JSUtils.js 21071 Locale.js 21072 isIdeo.js 21073 isAscii.js 21074 isDigit.js 21075 IString.js 21076 */ 21077 21078 // !data address countries nativecountries ctrynames 21079 21080 21081 /** 21082 * @class 21083 * Create a new Address instance and parse a physical address.<p> 21084 * 21085 * This function parses a physical address written in a free-form string. 21086 * It returns an object with a number of properties from the list below 21087 * that it may have extracted from that address.<p> 21088 * 21089 * The following is a list of properties that the algorithm will return:<p> 21090 * 21091 * <ul> 21092 * <li><i>streetAddress</i>: The street address, including house numbers and all. 21093 * <li><i>locality</i>: The locality of this address (usually a city or town). 21094 * <li><i>region</i>: The region where the locality is located. In the US, this 21095 * corresponds to states. In other countries, this may be provinces, 21096 * cantons, prefectures, etc. In some smaller countries, there are no 21097 * such divisions. 21098 * <li><i>postalCode</i>: Country-specific code for expediting mail. In the US, 21099 * this is the zip code. 21100 * <li><i>country</i>: The country of the address. 21101 * <li><i>countryCode</i>: The ISO 3166 2-letter region code for the destination 21102 * country in this address. 21103 * </ul> 21104 * 21105 * The above properties will not necessarily appear in the instance. For 21106 * any individual property, if the free-form address does not contain 21107 * that property or it cannot be parsed out, the it is left out.<p> 21108 * 21109 * The options parameter may contain any of the following properties: 21110 * 21111 * <ul> 21112 * <li><i>locale</i> - locale or localeSpec to use to parse the address. If not 21113 * specified, this function will use the current ilib locale 21114 * 21115 * <li><i>onLoad</i> - a callback function to call when the address info for the 21116 * locale is fully loaded and the address has been parsed. When the onLoad 21117 * option is given, the address object 21118 * will attempt to load any missing locale data using the ilib loader callback. 21119 * When the constructor is done (even if the data is already preassembled), the 21120 * onLoad function is called with the current instance as a parameter, so this 21121 * callback can be used with preassembled or dynamic loading or a mix of the two. 21122 * 21123 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 21124 * asynchronously. If this option is given as "false", then the "onLoad" 21125 * callback must be given, as the instance returned from this constructor will 21126 * not be usable for a while. 21127 * 21128 * <li><i>loadParams</i> - an object containing parameters to pass to the 21129 * loader callback function when locale data is missing. The parameters are not 21130 * interpretted or modified in any way. They are simply passed along. The object 21131 * may contain any property/value pairs as long as the calling code is in 21132 * agreement with the loader callback function as to what those parameters mean. 21133 * </ul> 21134 * 21135 * When an address cannot be parsed properly, the entire address will be placed 21136 * into the streetAddress property.<p> 21137 * 21138 * When the freeformAddress is another Address, this will act like a copy 21139 * constructor.<p> 21140 * 21141 * 21142 * @constructor 21143 * @param {string|Address} freeformAddress free-form address to parse, or a 21144 * javascript object containing the fields 21145 * @param {Object} options options to the parser 21146 */ 21147 var Address = function (freeformAddress, options) { 21148 var address; 21149 21150 if (!freeformAddress) { 21151 return undefined; 21152 } 21153 21154 this.sync = true; 21155 this.loadParams = {}; 21156 21157 if (options) { 21158 if (options.locale) { 21159 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 21160 } 21161 21162 if (typeof(options.sync) !== 'undefined') { 21163 this.sync = (options.sync == true); 21164 } 21165 21166 if (options.loadParams) { 21167 this.loadParams = options.loadParams; 21168 } 21169 } 21170 21171 this.locale = this.locale || new Locale(); 21172 // initialize from an already parsed object 21173 if (typeof(freeformAddress) === 'object') { 21174 /** 21175 * The street address, including house numbers and all. 21176 * @expose 21177 * @type {string|undefined} 21178 */ 21179 this.streetAddress = freeformAddress.streetAddress; 21180 /** 21181 * The locality of this address (usually a city or town). 21182 * @expose 21183 * @type {string|undefined} 21184 */ 21185 this.locality = freeformAddress.locality; 21186 /** 21187 * The region (province, canton, prefecture, state, etc.) where the address is located. 21188 * @expose 21189 * @type {string|undefined} 21190 */ 21191 this.region = freeformAddress.region; 21192 /** 21193 * Country-specific code for expediting mail. In the US, this is the zip code. 21194 * @expose 21195 * @type {string|undefined} 21196 */ 21197 this.postalCode = freeformAddress.postalCode; 21198 /** 21199 * Optional city-specific code for a particular post office, used to expidite 21200 * delivery. 21201 * @expose 21202 * @type {string|undefined} 21203 */ 21204 this.postOffice = freeformAddress.postOffice; 21205 /** 21206 * The country of the address. 21207 * @expose 21208 * @type {string|undefined} 21209 */ 21210 this.country = freeformAddress.country; 21211 if (freeformAddress.countryCode) { 21212 /** 21213 * The 2 or 3 letter ISO 3166 region code for the destination country in this address. 21214 * @expose 21215 * @type {string} 21216 * 21217 */ 21218 this.countryCode = freeformAddress.countryCode; 21219 } 21220 if (freeformAddress.format) { 21221 /** 21222 * private 21223 * @type {string} 21224 */ 21225 this.format = freeformAddress.format; 21226 } 21227 return this; 21228 } 21229 21230 address = freeformAddress.replace(/[ \t\r]+/g, " ").trim(); 21231 address = address.replace(/[\s\n]+$/, ""); 21232 address = address.replace(/^[\s\n]+/, ""); 21233 //console.log("\n\n-------------\nAddress is '" + address + "'"); 21234 21235 this.lines = address.split(/[,,\n]/g); 21236 this.removeEmptyLines(this.lines); 21237 21238 isAscii._init(this.sync, this.loadParams, /** @type {function(*)|undefined} */ ilib.bind(this, function() { 21239 isIdeo._init(this.sync, this.loadParams, /** @type {function(*)|undefined} */ ilib.bind(this, function() { 21240 isDigit._init(this.sync, this.loadParams, /** @type {function(*)|undefined} */ ilib.bind(this, function() { 21241 if (typeof(ilib.data.nativecountries) === 'undefined') { 21242 Utils.loadData({ 21243 object: Address, 21244 name: "nativecountries.json", // countries in their own language 21245 locale: "-", // only need to load the root file 21246 nonlocale: true, 21247 sync: this.sync, 21248 loadParams: this.loadParams, 21249 callback: /** @type function(Object=):undefined */ ilib.bind(this, /** @type function() */ function(nativecountries) { 21250 ilib.data.nativecountries = nativecountries; 21251 this._loadCountries(options && options.onLoad); 21252 }) 21253 }); 21254 } else { 21255 this._loadCountries(options && options.onLoad); 21256 } 21257 })); 21258 })); 21259 })); 21260 }; 21261 21262 /** @protected */ 21263 Address.prototype = { 21264 /** 21265 * @private 21266 */ 21267 _loadCountries: function(onLoad) { 21268 if (typeof(ilib.data.countries) === 'undefined') { 21269 Utils.loadData({ 21270 object: Address, 21271 name: "countries.json", // countries in English 21272 locale: "-", // only need to load the root file 21273 nonlocale: true, 21274 sync: this.sync, 21275 loadParams: this.loadParams, 21276 callback: /** @type function(Object=):undefined */ ilib.bind(this, /** @type function() */ function(countries) { 21277 ilib.data.countries = countries; 21278 this._loadCtrynames(onLoad); 21279 }) 21280 }); 21281 } else { 21282 this._loadCtrynames(onLoad); 21283 } 21284 }, 21285 21286 /** 21287 * @private 21288 */ 21289 _loadCtrynames: function(onLoad) { 21290 Utils.loadData({ 21291 name: "ctrynames.json", 21292 object: Address, 21293 locale: this.locale, 21294 sync: this.sync, 21295 loadParams: this.loadParams, 21296 callback: /** @type function(Object=):undefined */ ilib.bind(this, /** @type function() */ function(ctrynames) { 21297 this._determineDest(ctrynames, onLoad); 21298 }) 21299 }); 21300 }, 21301 21302 /** 21303 * @private 21304 * @param {Object?} ctrynames 21305 */ 21306 _findDest: function (ctrynames) { 21307 var match; 21308 21309 for (var countryName in ctrynames) { 21310 if (countryName && countryName !== "generated") { 21311 // find the longest match in the current table 21312 // ctrynames contains the country names mapped to region code 21313 // for efficiency, only test for things longer than the current match 21314 if (!match || match.text.length < countryName.length) { 21315 var temp = this._findCountry(countryName); 21316 if (temp) { 21317 match = temp; 21318 this.country = match.text; 21319 this.countryCode = ctrynames[countryName]; 21320 } 21321 } 21322 } 21323 } 21324 return match; 21325 }, 21326 21327 /** 21328 * @private 21329 * @param {Object?} localizedCountries 21330 * @param {function(Address):undefined} callback 21331 */ 21332 _determineDest: function (localizedCountries, callback) { 21333 var match; 21334 21335 /* 21336 * First, find the name of the destination country, as that determines how to parse 21337 * the rest of the address. For any address, there are three possible ways 21338 * that the name of the country could be written: 21339 * 1. In the current language 21340 * 2. In its own native language 21341 * 3. In English 21342 * We'll try all three. 21343 */ 21344 var tables = []; 21345 if (localizedCountries) { 21346 tables.push(localizedCountries); 21347 } 21348 tables.push(ilib.data.nativecountries); 21349 tables.push(ilib.data.countries); 21350 21351 for (var i = 0; i < tables.length; i++) { 21352 match = this._findDest(tables[i]); 21353 21354 if (match) { 21355 this.lines[match.line] = this.lines[match.line].substring(0, match.start) + this.lines[match.line].substring(match.start + match.text.length); 21356 21357 this._init(callback); 21358 return; 21359 } 21360 } 21361 21362 // no country, so try parsing it as if we were in the same country 21363 this.country = undefined; 21364 this.countryCode = this.locale.getRegion(); 21365 this._init(callback); 21366 }, 21367 21368 /** 21369 * @private 21370 * @param {function(Address):undefined} callback 21371 */ 21372 _init: function(callback) { 21373 Utils.loadData({ 21374 object: Address, 21375 locale: new Locale(this.countryCode), 21376 name: "address.json", 21377 sync: this.sync, 21378 loadParams: this.loadParams, 21379 callback: /** @type function(Object=):undefined */ ilib.bind(this, function(info) { 21380 if (!info || JSUtils.isEmpty(info)) { 21381 // load the "unknown" locale instead 21382 Utils.loadData({ 21383 object: Address, 21384 locale: new Locale("XX"), 21385 name: "address.json", 21386 sync: this.sync, 21387 loadParams: this.loadParams, 21388 callback: /** @type function(Object=):undefined */ ilib.bind(this, function(info) { 21389 this.info = info; 21390 this._parseAddress(); 21391 if (typeof(callback) === 'function') { 21392 callback(this); 21393 } 21394 }) 21395 }); 21396 } else { 21397 this.info = info; 21398 this._parseAddress(); 21399 if (typeof(callback) === 'function') { 21400 callback(this); 21401 } 21402 } 21403 }) 21404 }); 21405 }, 21406 21407 /** 21408 * @private 21409 */ 21410 _parseAddress: function() { 21411 // clean it up first 21412 var i, 21413 asianChars = 0, 21414 latinChars = 0, 21415 startAt, 21416 infoFields, 21417 field, 21418 pattern, 21419 matchFunction, 21420 match, 21421 fieldNumber; 21422 21423 // for locales that support both latin and asian character addresses, 21424 // decide if we are parsing an asian or latin script address 21425 if (this.info && this.info.multiformat) { 21426 for (var j = 0; j < this.lines.length; j++) { 21427 var line = new IString(this.lines[j]); 21428 var it = line.charIterator(); 21429 while (it.hasNext()) { 21430 var c = it.next(); 21431 if (isIdeo(c) || CType.withinRange(c, "Hangul")) { 21432 asianChars++; 21433 } else if (isAscii(c) && !isDigit(c)) { 21434 latinChars++; 21435 } 21436 } 21437 } 21438 21439 this.format = (asianChars >= latinChars) ? "asian" : "latin"; 21440 startAt = this.info.startAt[this.format]; 21441 infoFields = this.info.fields[this.format]; 21442 // //console.log("multiformat locale: format is now " + this.format); 21443 } else { 21444 startAt = (this.info && this.info.startAt) || "end"; 21445 infoFields = this.info.fields; 21446 } 21447 this.compare = (startAt === "end") ? this.endsWith : this.startsWith; 21448 21449 //console.log("this.lines is: " + JSON.stringify(this.lines)); 21450 21451 for (i = 0; i < infoFields.length && this.lines.length > 0; i++) { 21452 /** @type {{name:string, line:string, pattern:(string|Array.<string>), matchGroup:number}} */ 21453 field = infoFields[i]; 21454 this.removeEmptyLines(this.lines); 21455 //console.log("Searching for field " + field.name); 21456 if (field.pattern) { 21457 if (typeof(field.pattern) === 'string') { 21458 pattern = new RegExp(field.pattern, "img"); 21459 matchFunction = this.matchRegExp; 21460 } else { 21461 pattern = field.pattern; 21462 matchFunction = this.matchPattern; 21463 } 21464 21465 switch (field.line) { 21466 case 'startAtFirst': 21467 for (fieldNumber = 0; fieldNumber < this.lines.length; fieldNumber++) { 21468 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21469 if (match) { 21470 break; 21471 } 21472 } 21473 break; 21474 case 'startAtLast': 21475 for (fieldNumber = this.lines.length-1; fieldNumber >= 0; fieldNumber--) { 21476 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21477 if (match) { 21478 break; 21479 } 21480 } 21481 break; 21482 case 'first': 21483 fieldNumber = 0; 21484 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21485 break; 21486 case 'last': 21487 default: 21488 fieldNumber = this.lines.length - 1; 21489 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 21490 break; 21491 } 21492 if (match) { 21493 // //console.log("found match for " + field.name + ": " + JSON.stringify(match)); 21494 // //console.log("remaining line is " + match.line); 21495 this.lines[fieldNumber] = match.line; 21496 this[field.name] = match.match; 21497 } 21498 } else { 21499 // if nothing is given, default to taking the whole field 21500 this[field.name] = this.lines.splice(fieldNumber,1)[0].trim(); 21501 //console.log("typeof(this[field.name]) is " + typeof(this[field.name]) + " and value is " + JSON.stringify(this[field.name])); 21502 } 21503 } 21504 21505 // all the left overs go in the street address field 21506 this.removeEmptyLines(this.lines); 21507 if (this.lines.length > 0) { 21508 //console.log("this.lines is " + JSON.stringify(this.lines) + " and splicing to get streetAddress"); 21509 // Korea uses spaces between words, despite being an "asian" locale 21510 var joinString = (this.info.joinString && this.info.joinString[this.format]) || ((this.format && this.format === "asian") ? "" : ", "); 21511 this.streetAddress = this.lines.join(joinString).trim(); 21512 } 21513 21514 this.lines = undefined; 21515 //console.log("final result is " + JSON.stringify(this)); 21516 }, 21517 21518 /** 21519 * @protected 21520 * Find the named country either at the end or the beginning of the address. 21521 */ 21522 _findCountry: function(name) { 21523 var start = -1, match, line = 0; 21524 21525 if (this.lines.length > 0) { 21526 start = this.startsWith(this.lines[line], name); 21527 if (start === -1) { 21528 line = this.lines.length-1; 21529 start = this.endsWith(this.lines[line], name); 21530 } 21531 if (start !== -1) { 21532 match = { 21533 text: this.lines[line].substring(start, start + name.length), 21534 line: line, 21535 start: start 21536 }; 21537 } 21538 } 21539 21540 return match; 21541 }, 21542 21543 endsWith: function (subject, query) { 21544 var start = subject.length-query.length, 21545 i, 21546 pat; 21547 //console.log("endsWith: checking " + query + " against " + subject); 21548 for (i = 0; i < query.length; i++) { 21549 // TODO: use case mapper instead of toLowerCase() 21550 if (subject.charAt(start+i).toLowerCase() !== query.charAt(i).toLowerCase()) { 21551 return -1; 21552 } 21553 } 21554 if (start > 0) { 21555 pat = /\s/; 21556 if (!pat.test(subject.charAt(start-1))) { 21557 // make sure if we are not at the beginning of the string, that the match is 21558 // not the end of some other word 21559 return -1; 21560 } 21561 } 21562 return start; 21563 }, 21564 21565 startsWith: function (subject, query) { 21566 var i; 21567 // //console.log("startsWith: checking " + query + " against " + subject); 21568 for (i = 0; i < query.length; i++) { 21569 // TODO: use case mapper instead of toLowerCase() 21570 if (subject.charAt(i).toLowerCase() !== query.charAt(i).toLowerCase()) { 21571 return -1; 21572 } 21573 } 21574 return 0; 21575 }, 21576 21577 removeEmptyLines: function (arr) { 21578 var i = 0; 21579 21580 while (i < arr.length) { 21581 if (arr[i]) { 21582 arr[i] = arr[i].trim(); 21583 if (arr[i].length === 0) { 21584 arr.splice(i,1); 21585 } else { 21586 i++; 21587 } 21588 } else { 21589 arr.splice(i,1); 21590 } 21591 } 21592 }, 21593 21594 matchRegExp: function(address, line, expression, matchGroup, startAt) { 21595 var lastMatch, 21596 match, 21597 ret = {}, 21598 last; 21599 21600 //console.log("searching for regexp " + expression.source + " in line " + line); 21601 21602 match = expression.exec(line); 21603 if (startAt === 'end') { 21604 while (match !== null && match.length > 0) { 21605 //console.log("found matches " + JSON.stringify(match)); 21606 lastMatch = match; 21607 match = expression.exec(line); 21608 } 21609 match = lastMatch; 21610 } 21611 21612 if (match && match !== null) { 21613 //console.log("found matches " + JSON.stringify(match)); 21614 matchGroup = matchGroup || 0; 21615 if (match[matchGroup] !== undefined) { 21616 ret.match = match[matchGroup].trim(); 21617 ret.match = ret.match.replace(/^\-|\-+$/, ''); 21618 ret.match = ret.match.replace(/\s+$/, ''); 21619 last = (startAt === 'end') ? line.lastIndexOf(match[matchGroup]) : line.indexOf(match[matchGroup]); 21620 //console.log("last is " + last); 21621 ret.line = line.slice(0,last); 21622 if (address.format !== "asian") { 21623 ret.line += " "; 21624 } 21625 ret.line += line.slice(last+match[matchGroup].length); 21626 ret.line = ret.line.trim(); 21627 //console.log("found match " + ret.match + " from matchgroup " + matchGroup + " and rest of line is " + ret.line); 21628 return ret; 21629 } 21630 //} else { 21631 //console.log("no match"); 21632 } 21633 21634 return undefined; 21635 }, 21636 21637 matchPattern: function(address, line, pattern, matchGroup) { 21638 var start, 21639 j, 21640 ret = {}; 21641 21642 //console.log("searching in line " + line); 21643 21644 // search an array of possible fixed strings 21645 //console.log("Using fixed set of strings."); 21646 for (j = 0; j < pattern.length; j++) { 21647 start = address.compare(line, pattern[j]); 21648 if (start !== -1) { 21649 ret.match = line.substring(start, start+pattern[j].length); 21650 if (start !== 0) { 21651 ret.line = line.substring(0,start).trim(); 21652 } else { 21653 ret.line = line.substring(pattern[j].length).trim(); 21654 } 21655 //console.log("found match " + ret.match + " and rest of line is " + ret.line); 21656 return ret; 21657 } 21658 } 21659 21660 return undefined; 21661 } 21662 }; 21663 21664 21665 21666 /*< AddressFmt.js */ 21667 /* 21668 * AddressFmt.js - Format an address 21669 * 21670 * Copyright © 2013-2015, JEDLSoft 21671 * 21672 * Licensed under the Apache License, Version 2.0 (the "License"); 21673 * you may not use this file except in compliance with the License. 21674 * You may obtain a copy of the License at 21675 * 21676 * http://www.apache.org/licenses/LICENSE-2.0 21677 * 21678 * Unless required by applicable law or agreed to in writing, software 21679 * distributed under the License is distributed on an "AS IS" BASIS, 21680 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21681 * 21682 * See the License for the specific language governing permissions and 21683 * limitations under the License. 21684 */ 21685 21686 /* !depends 21687 ilib.js 21688 Locale.js 21689 Address.js 21690 IString.js 21691 Utils.js 21692 JSUtils.js 21693 */ 21694 21695 // !data address 21696 21697 21698 21699 /** 21700 * @class 21701 * Create a new formatter object to format physical addresses in a particular way. 21702 * 21703 * The options object may contain the following properties, both of which are optional: 21704 * 21705 * <ul> 21706 * <li><i>locale</i> - the locale to use to format this address. If not specified, it uses the default locale 21707 * 21708 * <li><i>style</i> - the style of this address. The default style for each country usually includes all valid 21709 * fields for that country. 21710 * 21711 * <li><i>onLoad</i> - a callback function to call when the address info for the 21712 * locale is fully loaded and the address has been parsed. When the onLoad 21713 * option is given, the address formatter object 21714 * will attempt to load any missing locale data using the ilib loader callback. 21715 * When the constructor is done (even if the data is already preassembled), the 21716 * onLoad function is called with the current instance as a parameter, so this 21717 * callback can be used with preassembled or dynamic loading or a mix of the two. 21718 * 21719 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 21720 * asynchronously. If this option is given as "false", then the "onLoad" 21721 * callback must be given, as the instance returned from this constructor will 21722 * not be usable for a while. 21723 * 21724 * <li><i>loadParams</i> - an object containing parameters to pass to the 21725 * loader callback function when locale data is missing. The parameters are not 21726 * interpretted or modified in any way. They are simply passed along. The object 21727 * may contain any property/value pairs as long as the calling code is in 21728 * agreement with the loader callback function as to what those parameters mean. 21729 * </ul> 21730 * 21731 * 21732 * @constructor 21733 * @param {Object} options options that configure how this formatter should work 21734 * Returns a formatter instance that can format multiple addresses. 21735 */ 21736 var AddressFmt = function(options) { 21737 this.sync = true; 21738 this.styleName = 'default'; 21739 this.loadParams = {}; 21740 this.locale = new Locale(); 21741 21742 if (options) { 21743 if (options.locale) { 21744 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 21745 } 21746 21747 if (typeof(options.sync) !== 'undefined') { 21748 this.sync = (options.sync == true); 21749 } 21750 21751 if (options.style) { 21752 this.styleName = options.style; 21753 } 21754 21755 if (options.loadParams) { 21756 this.loadParams = options.loadParams; 21757 } 21758 } 21759 21760 // console.log("Creating formatter for region: " + this.locale.region); 21761 Utils.loadData({ 21762 name: "address.json", 21763 object: AddressFmt, 21764 locale: this.locale, 21765 sync: this.sync, 21766 loadParams: this.loadParams, 21767 callback: /** @type function(Object?):undefined */ ilib.bind(this, function(info) { 21768 if (!info || JSUtils.isEmpty(info)) { 21769 // load the "unknown" locale instead 21770 Utils.loadData({ 21771 name: "address.json", 21772 object: AddressFmt, 21773 locale: new Locale("XX"), 21774 sync: this.sync, 21775 loadParams: this.loadParams, 21776 callback: /** @type function(Object?):undefined */ ilib.bind(this, function(info) { 21777 this.info = info; 21778 this._init(); 21779 if (options && typeof(options.onLoad) === 'function') { 21780 options.onLoad(this); 21781 } 21782 }) 21783 }); 21784 } else { 21785 this.info = info; 21786 this._init(); 21787 if (options && typeof(options.onLoad) === 'function') { 21788 options.onLoad(this); 21789 } 21790 } 21791 }) 21792 }); 21793 }; 21794 21795 /** 21796 * @private 21797 */ 21798 AddressFmt.prototype._init = function () { 21799 this.style = this.info && this.info.formats && this.info.formats[this.styleName]; 21800 21801 // use generic default -- should not happen, but just in case... 21802 this.style = this.style || (this.info && this.info.formats["default"]) || "{streetAddress}\n{locality} {region} {postalCode}\n{country}"; 21803 }; 21804 21805 /** 21806 * This function formats a physical address (Address instance) for display. 21807 * Whitespace is trimmed from the beginning and end of final resulting string, and 21808 * multiple consecutive whitespace characters in the middle of the string are 21809 * compressed down to 1 space character. 21810 * 21811 * If the Address instance is for a locale that is different than the locale for this 21812 * formatter, then a hybrid address is produced. The country name is located in the 21813 * correct spot for the current formatter's locale, but the rest of the fields are 21814 * formatted according to the default style of the locale of the actual address. 21815 * 21816 * Example: a mailing address in China, but formatted for the US might produce the words 21817 * "People's Republic of China" in English at the last line of the address, and the 21818 * Chinese-style address will appear in the first line of the address. In the US, the 21819 * country is on the last line, but in China the country is usually on the first line. 21820 * 21821 * @param {Address} address Address to format 21822 * @eturns {string} Returns a string containing the formatted address 21823 */ 21824 AddressFmt.prototype.format = function (address) { 21825 var ret, template, other, format; 21826 21827 if (!address) { 21828 return ""; 21829 } 21830 // console.log("formatting address: " + JSON.stringify(address)); 21831 if (address.countryCode && 21832 address.countryCode !== this.locale.region && 21833 Locale._isRegionCode(this.locale.region) && 21834 this.locale.region !== "XX") { 21835 // we are formatting an address that is sent from this country to another country, 21836 // so only the country should be in this locale, and the rest should be in the other 21837 // locale 21838 // console.log("formatting for another locale. Loading in its settings: " + address.countryCode); 21839 other = new AddressFmt({ 21840 locale: new Locale(address.countryCode), 21841 style: this.styleName 21842 }); 21843 return other.format(address); 21844 } 21845 21846 if (typeof(this.style) === 'object') { 21847 format = this.style[address.format || "latin"]; 21848 } else { 21849 format = this.style; 21850 } 21851 21852 // console.log("Using format: " + format); 21853 // make sure we have a blank string for any missing parts so that 21854 // those template parts get blanked out 21855 var params = { 21856 country: address.country || "", 21857 region: address.region || "", 21858 locality: address.locality || "", 21859 streetAddress: address.streetAddress || "", 21860 postalCode: address.postalCode || "", 21861 postOffice: address.postOffice || "" 21862 }; 21863 template = new IString(format); 21864 ret = template.format(params); 21865 ret = ret.replace(/[ \t]+/g, ' '); 21866 ret = ret.replace("\n ", "\n"); 21867 ret = ret.replace(" \n", "\n"); 21868 return ret.replace(/\n+/g, '\n').trim(); 21869 }; 21870 21871 21872 21873 /*< GlyphString.js */ 21874 /* 21875 * GlyphString.js - ilib string subclass that allows you to access 21876 * whole glyphs at a time 21877 * 21878 * Copyright © 2015, JEDLSoft 21879 * 21880 * Licensed under the Apache License, Version 2.0 (the "License"); 21881 * you may not use this file except in compliance with the License. 21882 * You may obtain a copy of the License at 21883 * 21884 * http://www.apache.org/licenses/LICENSE-2.0 21885 * 21886 * Unless required by applicable law or agreed to in writing, software 21887 * distributed under the License is distributed on an "AS IS" BASIS, 21888 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21889 * 21890 * See the License for the specific language governing permissions and 21891 * limitations under the License. 21892 */ 21893 21894 // !depends IString.js CType.js Utils.js JSUtils.js 21895 // !data norm ctype_m 21896 21897 21898 21899 /** 21900 * @class 21901 * Create a new glyph string instance. This string inherits from 21902 * the IString class, and adds methods that allow you to access 21903 * whole glyphs at a time. <p> 21904 * 21905 * In Unicode, various accented characters can be created by using 21906 * a base character and one or more combining characters following 21907 * it. These appear on the screen to the user as a single glyph. 21908 * For example, the Latin character "a" (U+0061) followed by the 21909 * combining diaresis character "¨" (U+0308) combine together to 21910 * form the "a with diaresis" glyph "ä", which looks like a single 21911 * character on the screen.<p> 21912 * 21913 * The big problem with combining characters for web developers is 21914 * that many CSS engines do not ellipsize text between glyphs. They 21915 * only deal with single Unicode characters. So if a particular space 21916 * only allows for 4 characters, the CSS engine will truncate a 21917 * string at 4 Unicode characters and then add the ellipsis (...) 21918 * character. What if the fourth Unicode character is the "a" and 21919 * the fifth one is the diaresis? Then a string like "xxxäxxx" that 21920 * is ellipsized at 4 characters will appear as "xxxa..." on the 21921 * screen instead of "xxxä...".<p> 21922 * 21923 * In the Latin script as it is commonly used, it is not so common 21924 * to form accented characters using combining accents, so the above 21925 * example is mostly for illustrative purposes. It is not unheard of 21926 * however. The situation is much, much worse in scripts such as Thai and 21927 * Devanagari that normally make very heavy use of combining characters. 21928 * These scripts do so because Unicode does not include pre-composed 21929 * versions of the accented characters like it does for Latin, so 21930 * combining accents are the only way to create these accented and 21931 * combined versions of the characters.<p> 21932 * 21933 * The solution to thise problem is not to use the the CSS property 21934 * "text-overflow: ellipsis" in your web site, ever. Instead, use 21935 * a glyph string to truncate text between glyphs instead of between 21936 * characters.<p> 21937 * 21938 * Glyph strings are also useful for truncation, hyphenation, and 21939 * line wrapping, as all of these should be done between glyphs instead 21940 * of between characters.<p> 21941 * 21942 * The options parameter is optional, and may contain any combination 21943 * of the following properties:<p> 21944 * 21945 * <ul> 21946 * <li><i>onLoad</i> - a callback function to call when the locale data are 21947 * fully loaded. When the onLoad option is given, this object will attempt to 21948 * load any missing locale data using the ilib loader callback. 21949 * When the constructor is done (even if the data is already preassembled), the 21950 * onLoad function is called with the current instance as a parameter, so this 21951 * callback can be used with preassembled or dynamic loading or a mix of the two. 21952 * 21953 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 21954 * asynchronously. If this option is given as "false", then the "onLoad" 21955 * callback must be given, as the instance returned from this constructor will 21956 * not be usable for a while. 21957 * 21958 * <li><i>loadParams</i> - an object containing parameters to pass to the 21959 * loader callback function when locale data is missing. The parameters are not 21960 * interpretted or modified in any way. They are simply passed along. The object 21961 * may contain any property/value pairs as long as the calling code is in 21962 * agreement with the loader callback function as to what those parameters mean. 21963 * </ul> 21964 * 21965 * @constructor 21966 * @extends IString 21967 * @param {string|IString=} str initialize this instance with this string 21968 * @param {Object=} options options governing the way this instance works 21969 */ 21970 var GlyphString = function (str, options) { 21971 if (options && options.noinstance) { 21972 return; 21973 } 21974 21975 IString.call(this, str); 21976 21977 var sync = true; 21978 var loadParams = {}; 21979 if (options) { 21980 if (typeof(options.sync) === 'boolean') { 21981 sync = options.sync; 21982 } 21983 if (options.loadParams) { 21984 loadParams = options.loadParams; 21985 } 21986 } 21987 21988 CType._load("ctype_m", sync, loadParams, function() { 21989 if (!ilib.data.norm || JSUtils.isEmpty(ilib.data.norm.ccc)) { 21990 Utils.loadData({ 21991 object: GlyphString, 21992 locale: "-", 21993 name: "normdata.json", 21994 nonlocale: true, 21995 sync: sync, 21996 loadParams: loadParams, 21997 callback: ilib.bind(this, function (norm) { 21998 ilib.extend(ilib.data.norm, norm); 21999 if (options && typeof(options.onLoad) === 'function') { 22000 options.onLoad(this); 22001 } 22002 }) 22003 }); 22004 } else { 22005 if (options && typeof(options.onLoad) === 'function') { 22006 options.onLoad(this); 22007 } 22008 } 22009 }); 22010 }; 22011 22012 GlyphString.prototype = new IString(undefined); 22013 GlyphString.prototype.parent = IString; 22014 GlyphString.prototype.constructor = GlyphString; 22015 22016 /** 22017 * Return true if the given character is a leading Jamo (Choseong) character. 22018 * 22019 * @private 22020 * @static 22021 * @param {number} n code point to check 22022 * @return {boolean} true if the character is a leading Jamo character, 22023 * false otherwise 22024 */ 22025 GlyphString._isJamoL = function (n) { 22026 return (n >= 0x1100 && n <= 0x1112); 22027 }; 22028 22029 /** 22030 * Return true if the given character is a vowel Jamo (Jungseong) character. 22031 * 22032 * @private 22033 * @static 22034 * @param {number} n code point to check 22035 * @return {boolean} true if the character is a vowel Jamo character, 22036 * false otherwise 22037 */ 22038 GlyphString._isJamoV = function (n) { 22039 return (n >= 0x1161 && n <= 0x1175); 22040 }; 22041 22042 /** 22043 * Return true if the given character is a trailing Jamo (Jongseong) character. 22044 * 22045 * @private 22046 * @static 22047 * @param {number} n code point to check 22048 * @return {boolean} true if the character is a trailing Jamo character, 22049 * false otherwise 22050 */ 22051 GlyphString._isJamoT = function (n) { 22052 return (n >= 0x11A8 && n <= 0x11C2); 22053 }; 22054 22055 /** 22056 * Return true if the given character is a precomposed Hangul character. 22057 * 22058 * @private 22059 * @static 22060 * @param {number} n code point to check 22061 * @return {boolean} true if the character is a precomposed Hangul character, 22062 * false otherwise 22063 */ 22064 GlyphString._isHangul = function (n) { 22065 return (n >= 0xAC00 && n <= 0xD7A3); 22066 }; 22067 22068 /** 22069 * Algorithmically compose an L and a V combining Jamo characters into 22070 * a precomposed Korean syllabic Hangul character. Both should already 22071 * be in the proper ranges for L and V characters. 22072 * 22073 * @private 22074 * @static 22075 * @param {number} lead the code point of the lead Jamo character to compose 22076 * @param {number} trail the code point of the trailing Jamo character to compose 22077 * @return {string} the composed Hangul character 22078 */ 22079 GlyphString._composeJamoLV = function (lead, trail) { 22080 var lindex = lead - 0x1100; 22081 var vindex = trail - 0x1161; 22082 return IString.fromCodePoint(0xAC00 + (lindex * 21 + vindex) * 28); 22083 }; 22084 22085 /** 22086 * Algorithmically compose a Hangul LV and a combining Jamo T character 22087 * into a precomposed Korean syllabic Hangul character. 22088 * 22089 * @private 22090 * @static 22091 * @param {number} lead the code point of the lead Hangul character to compose 22092 * @param {number} trail the code point of the trailing Jamo T character to compose 22093 * @return {string} the composed Hangul character 22094 */ 22095 GlyphString._composeJamoLVT = function (lead, trail) { 22096 return IString.fromCodePoint(lead + (trail - 0x11A7)); 22097 }; 22098 22099 /** 22100 * Compose one character out of a leading character and a 22101 * trailing character. If the characters are Korean Jamo, they 22102 * will be composed algorithmically. If they are any other 22103 * characters, they will be looked up in the nfc tables. 22104 * 22105 * @private 22106 * @static 22107 * @param {string} lead leading character to compose 22108 * @param {string} trail the trailing character to compose 22109 * @return {string|null} the fully composed character, or undefined if 22110 * there is no composition for those two characters 22111 */ 22112 GlyphString._compose = function (lead, trail) { 22113 var first = lead.charCodeAt(0); 22114 var last = trail.charCodeAt(0); 22115 if (GlyphString._isHangul(first) && GlyphString._isJamoT(last)) { 22116 return GlyphString._composeJamoLVT(first, last); 22117 } else if (GlyphString._isJamoL(first) && GlyphString._isJamoV(last)) { 22118 return GlyphString._composeJamoLV(first, last); 22119 } 22120 22121 var c = lead + trail; 22122 return (ilib.data.norm.nfc && ilib.data.norm.nfc[c]); 22123 }; 22124 22125 /** 22126 * Return an iterator that will step through all of the characters 22127 * in the string one at a time, taking care to step through decomposed 22128 * characters and through surrogate pairs in the UTF-16 encoding 22129 * as single characters. <p> 22130 * 22131 * The GlyphString class will return decomposed Unicode characters 22132 * as a single unit that a user might see on the screen as a single 22133 * glyph. If the 22134 * next character in the iteration is a base character and it is 22135 * followed by combining characters, the base and all its following 22136 * combining characters are returned as a single unit.<p> 22137 * 22138 * The standard Javascript String's charAt() method only 22139 * returns information about a particular 16-bit character in the 22140 * UTF-16 encoding scheme. 22141 * If the index is pointing to a low- or high-surrogate character, 22142 * it will return that surrogate character rather 22143 * than the surrogate pair which represents a character 22144 * in the supplementary planes.<p> 22145 * 22146 * The iterator instance returned has two methods, hasNext() which 22147 * returns true if the iterator has more characters to iterate through, 22148 * and next() which returns the next character.<p> 22149 * 22150 * @override 22151 * @return {Object} an iterator 22152 * that iterates through all the characters in the string 22153 */ 22154 GlyphString.prototype.charIterator = function() { 22155 var it = IString.prototype.charIterator.call(this); 22156 22157 /** 22158 * @constructor 22159 */ 22160 function _chiterator (istring) { 22161 this.index = 0; 22162 this.spacingCombining = false; 22163 this.hasNext = function () { 22164 return !!this.nextChar || it.hasNext(); 22165 }; 22166 this.next = function () { 22167 var ch = this.nextChar || it.next(), 22168 prevCcc = ilib.data.norm.ccc[ch], 22169 nextCcc, 22170 composed = ch; 22171 22172 this.nextChar = undefined; 22173 this.spacingCombining = false; 22174 22175 if (ilib.data.norm.ccc && 22176 (typeof(ilib.data.norm.ccc[ch]) === 'undefined' || ilib.data.norm.ccc[ch] === 0)) { 22177 // found a starter... find all the non-starters until the next starter. Must include 22178 // the next starter because under some odd circumstances, two starters sometimes recompose 22179 // together to form another character 22180 var notdone = true; 22181 while (it.hasNext() && notdone) { 22182 this.nextChar = it.next(); 22183 nextCcc = ilib.data.norm.ccc[this.nextChar]; 22184 var codePoint = IString.toCodePoint(this.nextChar, 0); 22185 // Mn characters are Marks that are non-spacing. These do not take more room than an accent, so they should be 22186 // considered part of the on-screen glyph, even if they are non-combining. Mc are marks that are spacing 22187 // and combining, which means they are part of the glyph, but they cause the glyph to use up more space than 22188 // just the base character alone. 22189 var isMn = CType._inRange(codePoint, "Mn", ilib.data.ctype_m); 22190 var isMc = CType._inRange(codePoint, "Mc", ilib.data.ctype_m); 22191 if (isMn || isMc || (typeof(nextCcc) !== 'undefined' && nextCcc !== 0)) { 22192 if (isMc) { 22193 this.spacingCombining = true; 22194 } 22195 ch += this.nextChar; 22196 this.nextChar = undefined; 22197 } else { 22198 // found the next starter. See if this can be composed with the previous starter 22199 var testChar = GlyphString._compose(composed, this.nextChar); 22200 if (prevCcc === 0 && typeof(testChar) !== 'undefined') { 22201 // not blocked and there is a mapping 22202 composed = testChar; 22203 ch += this.nextChar; 22204 this.nextChar = undefined; 22205 } else { 22206 // finished iterating, leave this.nextChar for the next next() call 22207 notdone = false; 22208 } 22209 } 22210 prevCcc = nextCcc; 22211 } 22212 } 22213 return ch; 22214 }; 22215 // Returns true if the last character returned by the "next" method included 22216 // spacing combining characters. If it does, then the character was wider than 22217 // just the base character alone, and the truncation code will not add it. 22218 this.wasSpacingCombining = function() { 22219 return this.spacingCombining; 22220 }; 22221 }; 22222 return new _chiterator(this); 22223 }; 22224 22225 /** 22226 * Truncate the current string at the given number of whole glyphs and return 22227 * the resulting string. 22228 * 22229 * @param {number} length the number of whole glyphs to keep in the string 22230 * @return {string} a string truncated to the requested number of glyphs 22231 */ 22232 GlyphString.prototype.truncate = function(length) { 22233 var it = this.charIterator(); 22234 var tr = ""; 22235 for (var i = 0; i < length-1 && it.hasNext(); i++) { 22236 tr += it.next(); 22237 } 22238 22239 /* 22240 * handle the last character separately. If it contains spacing combining 22241 * accents, then we must assume that it uses up more horizontal space on 22242 * the screen than just the base character by itself, and therefore this 22243 * method will not truncate enough characters to fit in the given length. 22244 * In this case, we have to chop off not only the combining characters, 22245 * but also the base character as well because the base without the 22246 * combining accents is considered a different character. 22247 */ 22248 if (i < length && it.hasNext()) { 22249 var c = it.next(); 22250 if (!it.wasSpacingCombining()) { 22251 tr += c; 22252 } 22253 } 22254 return tr; 22255 }; 22256 22257 /** 22258 * Truncate the current string at the given number of glyphs and add an ellipsis 22259 * to indicate that is more to the string. The ellipsis forms the last character 22260 * in the string, so the string is actually truncated at length-1 glyphs. 22261 * 22262 * @param {number} length the number of whole glyphs to keep in the string 22263 * including the ellipsis 22264 * @return {string} a string truncated to the requested number of glyphs 22265 * with an ellipsis 22266 */ 22267 GlyphString.prototype.ellipsize = function(length) { 22268 return this.truncate(length > 0 ? length-1 : 0) + "…"; 22269 }; 22270 22271 22272 22273 /*< NormString.js */ 22274 /* 22275 * NormString.js - ilib normalized string subclass definition 22276 * 22277 * Copyright © 2013-2015, JEDLSoft 22278 * 22279 * Licensed under the Apache License, Version 2.0 (the "License"); 22280 * you may not use this file except in compliance with the License. 22281 * You may obtain a copy of the License at 22282 * 22283 * http://www.apache.org/licenses/LICENSE-2.0 22284 * 22285 * Unless required by applicable law or agreed to in writing, software 22286 * distributed under the License is distributed on an "AS IS" BASIS, 22287 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22288 * 22289 * See the License for the specific language governing permissions and 22290 * limitations under the License. 22291 */ 22292 22293 // !depends IString.js GlyphString.js Utils.js 22294 22295 22296 22297 /** 22298 * @class 22299 * Create a new normalized string instance. This string inherits from 22300 * the GlyphString class, and adds the normalize method. It can be 22301 * used anywhere that a normal Javascript string is used. <p> 22302 * 22303 * 22304 * @constructor 22305 * @extends GlyphString 22306 * @param {string|IString=} str initialize this instance with this string 22307 */ 22308 var NormString = function (str) { 22309 GlyphString.call(this, str); 22310 }; 22311 22312 NormString.prototype = new GlyphString("", {noinstance:true}); 22313 NormString.prototype.parent = GlyphString; 22314 NormString.prototype.constructor = NormString; 22315 22316 /** 22317 * Initialize the normalized string routines statically. This 22318 * is intended to be called in a dynamic-load version of ilib 22319 * to load the data need to normalize strings before any instances 22320 * of NormString are created.<p> 22321 * 22322 * The options parameter may contain any of the following properties: 22323 * 22324 * <ul> 22325 * <li><i>form</i> - {string} the normalization form to load 22326 * <li><i>script</i> - {string} load the normalization for this script. If the 22327 * script is given as "all" then the normalization data for all scripts 22328 * is loaded at the same time 22329 * <li><i>sync</i> - {boolean} whether to load the files synchronously or not 22330 * <li><i>loadParams</i> - {Object} parameters to the loader function 22331 * <li><i>onLoad</i> - {function()} a function to call when the 22332 * files are done being loaded 22333 * </ul> 22334 * 22335 * @param {Object} options an object containing properties that govern 22336 * how to initialize the data 22337 */ 22338 NormString.init = function(options) { 22339 if (!ilib._load || (typeof(ilib._load) !== 'function' && typeof(ilib._load.loadFiles) !== 'function')) { 22340 // can't do anything 22341 return; 22342 } 22343 var form = "nfkc"; 22344 var script = "all"; 22345 var sync = true; 22346 var onLoad = undefined; 22347 var loadParams = undefined; 22348 if (options) { 22349 form = options.form || "nfkc"; 22350 script = options.script || "all"; 22351 sync = typeof(options.sync) !== 'undefined' ? options.sync : true; 22352 onLoad = typeof(options.onLoad) === 'function' ? options.onLoad : undefined; 22353 if (options.loadParams) { 22354 loadParams = options.loadParams; 22355 } 22356 } 22357 var formDependencies = { 22358 "nfd": ["nfd"], 22359 "nfc": ["nfd"], 22360 "nfkd": ["nfkd", "nfd"], 22361 "nfkc": ["nfkd", "nfd"] 22362 }; 22363 var files = ["normdata.json"]; 22364 var forms = formDependencies[form]; 22365 for (var f in forms) { 22366 files.push(forms[f] + "/" + script + ".json"); 22367 } 22368 22369 if (JSUtils.isEmpty(ilib.data.norm.ccc) || JSUtils.isEmpty(ilib.data.norm.nfd) || JSUtils.isEmpty(ilib.data.norm.nfkd)) { 22370 //console.log("loading files " + JSON.stringify(files)); 22371 Utils._callLoadData(files, sync, loadParams, function(arr) { 22372 ilib.extend(ilib.data.norm, arr[0]); 22373 for (var i = 1; i < arr.length; i++) { 22374 if (typeof(arr[i]) !== 'undefined') { 22375 ilib.extend(ilib.data.norm[forms[i-1]], arr[i]); 22376 } 22377 } 22378 22379 if (onLoad) { 22380 onLoad(arr); 22381 } 22382 }); 22383 } 22384 }; 22385 22386 /** 22387 * Algorithmically decompose a precomposed Korean syllabic Hangul 22388 * character into its individual combining Jamo characters. The given 22389 * character must be in the range of Hangul characters U+AC00 to U+D7A3. 22390 * 22391 * @private 22392 * @static 22393 * @param {number} cp code point of a Korean Hangul character to decompose 22394 * @return {string} the decomposed string of Jamo characters 22395 */ 22396 NormString._decomposeHangul = function (cp) { 22397 var sindex = cp - 0xAC00; 22398 var result = String.fromCharCode(0x1100 + sindex / 588) + 22399 String.fromCharCode(0x1161 + (sindex % 588) / 28); 22400 var t = sindex % 28; 22401 if (t !== 0) { 22402 result += String.fromCharCode(0x11A7 + t); 22403 } 22404 return result; 22405 }; 22406 22407 /** 22408 * Expand one character according to the given canonical and 22409 * compatibility mappings. 22410 * 22411 * @private 22412 * @static 22413 * @param {string} ch character to map 22414 * @param {Object} canon the canonical mappings to apply 22415 * @param {Object=} compat the compatibility mappings to apply, or undefined 22416 * if only the canonical mappings are needed 22417 * @return {string} the mapped character 22418 */ 22419 NormString._expand = function (ch, canon, compat) { 22420 var i, 22421 expansion = "", 22422 n = ch.charCodeAt(0); 22423 if (GlyphString._isHangul(n)) { 22424 expansion = NormString._decomposeHangul(n); 22425 } else { 22426 var result = canon[ch]; 22427 if (!result && compat) { 22428 result = compat[ch]; 22429 } 22430 if (result && result !== ch) { 22431 for (i = 0; i < result.length; i++) { 22432 expansion += NormString._expand(result[i], canon, compat); 22433 } 22434 } else { 22435 expansion = ch; 22436 } 22437 } 22438 return expansion; 22439 }; 22440 22441 /** 22442 * Perform the Unicode Normalization Algorithm upon the string and return 22443 * the resulting new string. The current string is not modified. 22444 * 22445 * <h2>Forms</h2> 22446 * 22447 * The forms of possible normalizations are defined by the <a 22448 * href="http://www.unicode.org/reports/tr15/">Unicode Standard 22449 * Annex (UAX) 15</a>. The form parameter is a string that may have one 22450 * of the following values: 22451 * 22452 * <ul> 22453 * <li>nfd - Canonical decomposition. This decomposes characters into 22454 * their exactly equivalent forms. For example, "ü" would decompose 22455 * into a "u" followed by the combining diaeresis character. 22456 * <li>nfc - Canonical decomposition followed by canonical composition. 22457 * This decomposes and then recomposes character into their shortest 22458 * exactly equivalent forms by recomposing as many combining characters 22459 * as possible. For example, "ü" followed by a combining 22460 * macron character would decompose into a "u" followed by the combining 22461 * macron characters the combining diaeresis character, and then be recomposed into 22462 * the u with macron and diaeresis "ṻ" character. The reason that 22463 * the "nfc" form decomposes and then recomposes is that combining characters 22464 * have a specific order under the Unicode Normalization Algorithm, and 22465 * partly composed characters such as the "ü" followed by combining 22466 * marks may change the order of the combining marks when decomposed and 22467 * recomposed. 22468 * <li>nfkd - Compatibility decomposition. This decomposes characters 22469 * into compatible forms that may not be exactly equivalent semantically, 22470 * as well as performing canonical decomposition as well. 22471 * For example, the "œ" ligature character decomposes to the two 22472 * characters "oe" because they are compatible even though they are not 22473 * exactly the same semantically. 22474 * <li>nfkc - Compatibility decomposition followed by canonical composition. 22475 * This decomposes characters into compatible forms, then recomposes 22476 * characters using the canonical composition. That is, it breaks down 22477 * characters into the compatible forms, and then recombines all combining 22478 * marks it can with their base characters. For example, the character 22479 * "ǽ" would be normalized to "aé" by first decomposing 22480 * the character into "a" followed by "e" followed by the combining acute accent 22481 * combining mark, and then recomposed to an "a" followed by the "e" 22482 * with acute accent. 22483 * </ul> 22484 * 22485 * <h2>Operation</h2> 22486 * 22487 * Two strings a and b can be said to be canonically equivalent if 22488 * normalize(a) = normalize(b) 22489 * under the nfc normalization form. Two strings can be said to be compatible if 22490 * normalize(a) = normalize(b) under the nfkc normalization form.<p> 22491 * 22492 * The canonical normalization is often used to see if strings are 22493 * equivalent to each other, and thus is useful when implementing parsing 22494 * algorithms or exact matching algorithms. It can also be used to ensure 22495 * that any string output produces a predictable sequence of characters.<p> 22496 * 22497 * Compatibility normalization 22498 * does not always preserve the semantic meaning of all the characters, 22499 * although this is sometimes the behaviour that you are after. It is useful, 22500 * for example, when doing searches of user-input against text in documents 22501 * where the matches are supposed to "fuzzy". In this case, both the query 22502 * string and the document string would be mapped to their compatibility 22503 * normalized forms, and then compared.<p> 22504 * 22505 * Compatibility normalization also does not guarantee round-trip conversion 22506 * to and from legacy character sets as the normalization is "lossy". It is 22507 * akin to doing a lower- or upper-case conversion on text -- after casing, 22508 * you cannot tell what case each character is in the original string. It is 22509 * good for matching and searching, but it rarely good for output because some 22510 * distinctions or meanings in the original text have been lost.<p> 22511 * 22512 * Note that W3C normalization for HTML also escapes and unescapes 22513 * HTML character entities such as "ü" for u with diaeresis. This 22514 * method does not do such escaping or unescaping. If normalization is required 22515 * for HTML strings with entities, unescaping should be performed on the string 22516 * prior to calling this method.<p> 22517 * 22518 * <h2>Data</h2> 22519 * 22520 * Normalization requires a fair amount of mapping data, much of which you may 22521 * not need for the characters expected in your texts. It is possible to assemble 22522 * a copy of ilib that saves space by only including normalization data for 22523 * those scripts that you expect to encounter in your data.<p> 22524 * 22525 * The normalization data is organized by normalization form and within there 22526 * by script. To include the normalization data for a particular script with 22527 * a particular normalization form, use the directive: 22528 * 22529 * <pre><code> 22530 * !depends <form>/<script>.js 22531 * </code></pre> 22532 * 22533 * Where <form> is the normalization form ("nfd", "nfc", "nfkd", or "nfkc"), and 22534 * <script> is the ISO 15924 code for the script you would like to 22535 * support. Example: to load in the NFC data for Cyrillic, you would use: 22536 * 22537 * <pre><code> 22538 * !depends nfc/Cyrl.js 22539 * </code></pre> 22540 * 22541 * Note that because certain normalization forms include others in their algorithm, 22542 * their data also depends on the data for the other forms. For example, if you 22543 * include the "nfc" data for a script, you will automatically get the "nfd" data 22544 * for that same script as well because the NFC algorithm does NFD normalization 22545 * first. Here are the dependencies:<p> 22546 * 22547 * <ul> 22548 * <li>NFD -> no dependencies 22549 * <li>NFC -> NFD 22550 * <li>NFKD -> NFD 22551 * <li>NFKC -> NFKD, NFD, NFC 22552 * </ul> 22553 * 22554 * A special value for the script dependency is "all" which will cause the data for 22555 * all scripts 22556 * to be loaded for that normalization form. This would be useful if you know that 22557 * you are going to normalize a lot of multilingual text or cannot predict which scripts 22558 * will appear in the input. Because the NFKC form depends on all others, you can 22559 * get all of the data for all forms automatically by depending on "nfkc/all.js". 22560 * Note that the normalization data for practically all script automatically depend 22561 * on data for the Common script (code "Zyyy") which contains all of the characters 22562 * that are commonly used in many different scripts. Examples of characters in the 22563 * Common script are the ASCII punctuation characters, or the ASCII Arabic 22564 * numerals "0" through "9".<p> 22565 * 22566 * By default, none of the data for normalization is automatically 22567 * included in the preassembled iliball.js file. 22568 * If you would like to normalize strings, you must assemble 22569 * your own copy of ilib and explicitly include the normalization data 22570 * for those scripts as per the instructions above. This normalization method will 22571 * produce output, even without the normalization data. However, the output will be 22572 * simply the same thing as its input for all scripts 22573 * except Korean Hangul and Jamo, which are decomposed and recomposed 22574 * algorithmically and therefore do not rely on data.<p> 22575 * 22576 * If characters are encountered for which there are no normalization data, they 22577 * will be passed through to the output string unmodified. 22578 * 22579 * @param {string} form The normalization form requested 22580 * @return {IString} a new instance of an IString that has been normalized 22581 * according to the requested form. The current instance is not modified. 22582 */ 22583 NormString.prototype.normalize = function (form) { 22584 var i; 22585 22586 if (typeof(form) !== 'string' || this.str.length === 0) { 22587 return new IString(this.str); 22588 } 22589 22590 var nfc = false, 22591 nfkd = false; 22592 22593 switch (form) { 22594 default: 22595 break; 22596 22597 case "nfc": 22598 nfc = true; 22599 break; 22600 22601 case "nfkd": 22602 nfkd = true; 22603 break; 22604 22605 case "nfkc": 22606 nfkd = true; 22607 nfc = true; 22608 break; 22609 } 22610 22611 // decompose 22612 var decomp = ""; 22613 22614 if (nfkd) { 22615 var ch, it = IString.prototype.charIterator.call(this); 22616 while (it.hasNext()) { 22617 ch = it.next(); 22618 decomp += NormString._expand(ch, ilib.data.norm.nfd, ilib.data.norm.nfkd); 22619 } 22620 } else { 22621 var ch, it = IString.prototype.charIterator.call(this); 22622 while (it.hasNext()) { 22623 ch = it.next(); 22624 decomp += NormString._expand(ch, ilib.data.norm.nfd); 22625 } 22626 } 22627 22628 // now put the combining marks in a fixed order by 22629 // sorting on the combining class 22630 function compareByCCC(left, right) { 22631 return ilib.data.norm.ccc[left] - ilib.data.norm.ccc[right]; 22632 } 22633 22634 function ccc(c) { 22635 return ilib.data.norm.ccc[c] || 0; 22636 } 22637 22638 function sortChars(arr, comp) { 22639 // qt/qml's Javascript engine re-arranges entries that are equal to 22640 // each other. Technically, that is a correct behaviour, but it is 22641 // not desirable. All the other engines leave equivalent entries 22642 // where they are. This bubblesort emulates what the other engines 22643 // do. Fortunately, the arrays we are sorting are a max of 5 or 6 22644 // entries, so performance is not a big deal here. 22645 if (ilib._getPlatform() === "qt") { 22646 var tmp; 22647 for (var i = arr.length-1; i > 0; i--) { 22648 for (var j = 0; j < i; j++) { 22649 if (comp(arr[j], arr[j+1]) > 0) { 22650 tmp = arr[j]; 22651 arr[j] = arr[j+1]; 22652 arr[j+1] = tmp; 22653 } 22654 } 22655 } 22656 return arr; 22657 } else { 22658 return arr.sort(comp); 22659 } 22660 } 22661 22662 var dstr = new IString(decomp); 22663 var it = dstr.charIterator(); 22664 var cpArray = []; 22665 22666 // easier to deal with as an array of chars 22667 while (it.hasNext()) { 22668 cpArray.push(it.next()); 22669 } 22670 22671 i = 0; 22672 while (i < cpArray.length) { 22673 if (typeof(ilib.data.norm.ccc[cpArray[i]]) !== 'undefined' && ccc(cpArray[i]) !== 0) { 22674 // found a non-starter... rearrange all the non-starters until the next starter 22675 var end = i+1; 22676 while (end < cpArray.length && 22677 typeof(ilib.data.norm.ccc[cpArray[end]]) !== 'undefined' && 22678 ccc(cpArray[end]) !== 0) { 22679 end++; 22680 } 22681 22682 // simple sort of the non-starter chars 22683 if (end - i > 1) { 22684 cpArray = cpArray.slice(0,i).concat(sortChars(cpArray.slice(i, end), compareByCCC), cpArray.slice(end)); 22685 } 22686 } 22687 i++; 22688 } 22689 22690 if (nfc) { 22691 i = 0; 22692 while (i < cpArray.length) { 22693 if (typeof(ilib.data.norm.ccc[cpArray[i]]) === 'undefined' || ilib.data.norm.ccc[cpArray[i]] === 0) { 22694 // found a starter... find all the non-starters until the next starter. Must include 22695 // the next starter because under some odd circumstances, two starters sometimes recompose 22696 // together to form another character 22697 var end = i+1; 22698 var notdone = true; 22699 while (end < cpArray.length && notdone) { 22700 if (typeof(ilib.data.norm.ccc[cpArray[end]]) !== 'undefined' && 22701 ilib.data.norm.ccc[cpArray[end]] !== 0) { 22702 if (ccc(cpArray[end-1]) < ccc(cpArray[end])) { 22703 // not blocked 22704 var testChar = GlyphString._compose(cpArray[i], cpArray[end]); 22705 if (typeof(testChar) !== 'undefined') { 22706 cpArray[i] = testChar; 22707 22708 // delete the combining char 22709 cpArray.splice(end,1); 22710 22711 // restart the iteration, just in case there is more to recompose with the new char 22712 end = i; 22713 } 22714 } 22715 end++; 22716 } else { 22717 // found the next starter. See if this can be composed with the previous starter 22718 var testChar = GlyphString._compose(cpArray[i], cpArray[end]); 22719 if (ccc(cpArray[end-1]) === 0 && typeof(testChar) !== 'undefined') { 22720 // not blocked and there is a mapping 22721 cpArray[i] = testChar; 22722 22723 // delete the combining char 22724 cpArray.splice(end,1); 22725 22726 // restart the iteration, just in case there is more to recompose with the new char 22727 end = i+1; 22728 } else { 22729 // finished iterating 22730 notdone = false; 22731 } 22732 } 22733 } 22734 } 22735 i++; 22736 } 22737 } 22738 22739 return new IString(cpArray.length > 0 ? cpArray.join("") : ""); 22740 }; 22741 22742 /** 22743 * @override 22744 * Return an iterator that will step through all of the characters 22745 * in the string one at a time, taking care to step through decomposed 22746 * characters and through surrogate pairs in UTF-16 encoding 22747 * properly. <p> 22748 * 22749 * The NormString class will return decomposed Unicode characters 22750 * as a single unit that a user might see on the screen. If the 22751 * next character in the iteration is a base character and it is 22752 * followed by combining characters, the base and all its following 22753 * combining characters are returned as a single unit.<p> 22754 * 22755 * The standard Javascript String's charAt() method only 22756 * returns information about a particular 16-bit character in the 22757 * UTF-16 encoding scheme. 22758 * If the index is pointing to a low- or high-surrogate character, 22759 * it will return that surrogate character rather 22760 * than the surrogate pair which represents a character 22761 * in the supplementary planes.<p> 22762 * 22763 * The iterator instance returned has two methods, hasNext() which 22764 * returns true if the iterator has more characters to iterate through, 22765 * and next() which returns the next character.<p> 22766 * 22767 * @return {Object} an iterator 22768 * that iterates through all the characters in the string 22769 */ 22770 NormString.prototype.charIterator = function() { 22771 var it = IString.prototype.charIterator.call(this); 22772 22773 /** 22774 * @constructor 22775 */ 22776 function _chiterator (istring) { 22777 /** 22778 * @private 22779 */ 22780 var ccc = function(c) { 22781 return ilib.data.norm.ccc[c] || 0; 22782 }; 22783 22784 this.index = 0; 22785 this.hasNext = function () { 22786 return !!this.nextChar || it.hasNext(); 22787 }; 22788 this.next = function () { 22789 var ch = this.nextChar || it.next(), 22790 prevCcc = ccc(ch), 22791 nextCcc, 22792 composed = ch; 22793 22794 this.nextChar = undefined; 22795 22796 if (ilib.data.norm.ccc && 22797 (typeof(ilib.data.norm.ccc[ch]) === 'undefined' || ccc(ch) === 0)) { 22798 // found a starter... find all the non-starters until the next starter. Must include 22799 // the next starter because under some odd circumstances, two starters sometimes recompose 22800 // together to form another character 22801 var notdone = true; 22802 while (it.hasNext() && notdone) { 22803 this.nextChar = it.next(); 22804 nextCcc = ccc(this.nextChar); 22805 if (typeof(ilib.data.norm.ccc[this.nextChar]) !== 'undefined' && nextCcc !== 0) { 22806 ch += this.nextChar; 22807 this.nextChar = undefined; 22808 } else { 22809 // found the next starter. See if this can be composed with the previous starter 22810 var testChar = GlyphString._compose(composed, this.nextChar); 22811 if (prevCcc === 0 && typeof(testChar) !== 'undefined') { 22812 // not blocked and there is a mapping 22813 composed = testChar; 22814 ch += this.nextChar; 22815 this.nextChar = undefined; 22816 } else { 22817 // finished iterating, leave this.nextChar for the next next() call 22818 notdone = false; 22819 } 22820 } 22821 prevCcc = nextCcc; 22822 } 22823 } 22824 return ch; 22825 }; 22826 }; 22827 return new _chiterator(this); 22828 }; 22829 22830 22831 /*< CodePointSource.js */ 22832 /* 22833 * CodePointSource.js - Source of code points from a string 22834 * 22835 * Copyright © 2013-2015, JEDLSoft 22836 * 22837 * Licensed under the Apache License, Version 2.0 (the "License"); 22838 * you may not use this file except in compliance with the License. 22839 * You may obtain a copy of the License at 22840 * 22841 * http://www.apache.org/licenses/LICENSE-2.0 22842 * 22843 * Unless required by applicable law or agreed to in writing, software 22844 * distributed under the License is distributed on an "AS IS" BASIS, 22845 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22846 * 22847 * See the License for the specific language governing permissions and 22848 * limitations under the License. 22849 */ 22850 22851 // !depends isPunct.js NormString.js 22852 22853 22854 /** 22855 * @class 22856 * Represents a buffered source of code points. The input string is first 22857 * normalized so that combining characters come out in a standardized order. 22858 * If the "ignorePunctuation" flag is turned on, then punctuation 22859 * characters are skipped. 22860 * 22861 * @constructor 22862 * @private 22863 * @param {NormString|string} str a string to get code points from 22864 * @param {boolean} ignorePunctuation whether or not to ignore punctuation 22865 * characters 22866 */ 22867 var CodePointSource = function(str, ignorePunctuation) { 22868 this.chars = []; 22869 // first convert the string to a normalized sequence of characters 22870 var s = (typeof(str) === "string") ? new NormString(str) : str; 22871 this.it = s.charIterator(); 22872 this.ignorePunctuation = typeof(ignorePunctuation) === "boolean" && ignorePunctuation; 22873 }; 22874 22875 /** 22876 * Return the first num code points in the source without advancing the 22877 * source pointer. If there are not enough code points left in the 22878 * string to satisfy the request, this method will return undefined. 22879 * 22880 * @param {number} num the number of characters to peek ahead 22881 * @return {string|undefined} a string formed out of up to num code points from 22882 * the start of the string, or undefined if there are not enough character left 22883 * in the source to complete the request 22884 */ 22885 CodePointSource.prototype.peek = function(num) { 22886 if (num < 1) { 22887 return undefined; 22888 } 22889 if (this.chars.length < num && this.it.hasNext()) { 22890 for (var i = 0; this.chars.length < 4 && this.it.hasNext(); i++) { 22891 var c = this.it.next(); 22892 if (c && !this.ignorePunctuation || !isPunct(c)) { 22893 this.chars.push(c); 22894 } 22895 } 22896 } 22897 if (this.chars.length < num) { 22898 return undefined; 22899 } 22900 return this.chars.slice(0, num).join(""); 22901 }; 22902 /** 22903 * Advance the source pointer by the given number of code points. 22904 * @param {number} num number of code points to advance 22905 */ 22906 CodePointSource.prototype.consume = function(num) { 22907 if (num > 0) { 22908 this.peek(num); // for the iterator to go forward if needed 22909 if (num < this.chars.length) { 22910 this.chars = this.chars.slice(num); 22911 } else { 22912 this.chars = []; 22913 } 22914 } 22915 }; 22916 22917 22918 22919 /*< ElementIterator.js */ 22920 /* 22921 * ElementIterator.js - Iterate through a list of collation elements 22922 * 22923 * Copyright © 2013-2015, JEDLSoft 22924 * 22925 * Licensed under the Apache License, Version 2.0 (the "License"); 22926 * you may not use this file except in compliance with the License. 22927 * You may obtain a copy of the License at 22928 * 22929 * http://www.apache.org/licenses/LICENSE-2.0 22930 * 22931 * Unless required by applicable law or agreed to in writing, software 22932 * distributed under the License is distributed on an "AS IS" BASIS, 22933 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22934 * 22935 * See the License for the specific language governing permissions and 22936 * limitations under the License. 22937 */ 22938 22939 /** 22940 * @class 22941 * An iterator through a sequence of collation elements. This 22942 * iterator takes a source of code points, converts them into 22943 * collation elements, and allows the caller to get single 22944 * elements at a time. 22945 * 22946 * @constructor 22947 * @private 22948 * @param {CodePointSource} source source of code points to 22949 * convert to collation elements 22950 * @param {Object} map mapping from sequences of code points to 22951 * collation elements 22952 * @param {number} keysize size in bits of the collation elements 22953 */ 22954 var ElementIterator = function (source, map, keysize) { 22955 this.elements = []; 22956 this.source = source; 22957 this.map = map; 22958 this.keysize = keysize; 22959 }; 22960 22961 /** 22962 * @private 22963 */ 22964 ElementIterator.prototype._fillBuffer = function () { 22965 var str = undefined; 22966 22967 // peek ahead by up to 4 characters, which may combine 22968 // into 1 or more collation elements 22969 for (var i = 4; i > 0; i--) { 22970 str = this.source.peek(i); 22971 if (str && this.map[str]) { 22972 this.elements = this.elements.concat(this.map[str]); 22973 this.source.consume(i); 22974 return; 22975 } 22976 } 22977 22978 if (str) { 22979 // no mappings for the first code point, so just use its 22980 // Unicode code point as a proxy for its sort order. Shift 22981 // it by the key size so that everything unknown sorts 22982 // after things that have mappings 22983 this.elements.push(str.charCodeAt(0) << this.keysize); 22984 this.source.consume(1); 22985 } else { 22986 // end of the string 22987 return undefined; 22988 } 22989 }; 22990 22991 /** 22992 * Return true if there are more collation elements left to 22993 * iterate through. 22994 * @returns {boolean} true if there are more elements left to 22995 * iterate through, and false otherwise 22996 */ 22997 ElementIterator.prototype.hasNext = function () { 22998 if (this.elements.length < 1) { 22999 this._fillBuffer(); 23000 } 23001 return !!this.elements.length; 23002 }; 23003 23004 /** 23005 * Return the next collation element. If more than one collation 23006 * element is generated from a sequence of code points 23007 * (ie. an "expansion"), then this class will buffer the 23008 * other elements and return them on subsequent calls to 23009 * this method. 23010 * 23011 * @returns {number|undefined} the next collation element or 23012 * undefined for no more collation elements 23013 */ 23014 ElementIterator.prototype.next = function () { 23015 if (this.elements.length < 1) { 23016 this._fillBuffer(); 23017 } 23018 var ret = this.elements[0]; 23019 this.elements = this.elements.slice(1); 23020 return ret; 23021 }; 23022 23023 23024 23025 /*< Collator.js */ 23026 /* 23027 * Collator.js - Collation routines 23028 * 23029 * Copyright © 2013-2015, JEDLSoft 23030 * 23031 * Licensed under the Apache License, Version 2.0 (the "License"); 23032 * you may not use this file except in compliance with the License. 23033 * You may obtain a copy of the License at 23034 * 23035 * http://www.apache.org/licenses/LICENSE-2.0 23036 * 23037 * Unless required by applicable law or agreed to in writing, software 23038 * distributed under the License is distributed on an "AS IS" BASIS, 23039 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23040 * 23041 * See the License for the specific language governing permissions and 23042 * limitations under the License. 23043 */ 23044 23045 /* !depends 23046 Locale.js 23047 ilib.js 23048 INumber.js 23049 isPunct.js 23050 NormString.js 23051 MathUtils.js 23052 Utils.js 23053 LocaleInfo.js 23054 CodePointSource.js 23055 ElementIterator.js 23056 */ 23057 23058 // !data collation 23059 23060 23061 /** 23062 * @class 23063 * A class that implements a locale-sensitive comparator function 23064 * for use with sorting function. The comparator function 23065 * assumes that the strings it is comparing contain Unicode characters 23066 * encoded in UTF-16.<p> 23067 * 23068 * Collations usually depend only on the language, because most collation orders 23069 * are shared between locales that speak the same language. There are, however, a 23070 * number of instances where a locale collates differently than other locales 23071 * that share the same language. There are also a number of instances where a 23072 * locale collates differently based on the script used. This object can handle 23073 * these cases automatically if a full locale is specified in the options rather 23074 * than just a language code.<p> 23075 * 23076 * <h2>Options</h2> 23077 * 23078 * The options parameter can contain any of the following properties: 23079 * 23080 * <ul> 23081 * <li><i>locale</i> - String|Locale. The locale which the comparator function 23082 * will collate with. Default: the current iLib locale. 23083 * 23084 * <li><i>sensitivity</i> - String. Sensitivity or strength of collator. This is one of 23085 * "primary", "base", "secondary", "accent", "tertiary", "case", "quaternary", or 23086 * "variant". Default: "primary" 23087 * <ol> 23088 * <li>base or primary - Only the primary distinctions between characters are significant. 23089 * Another way of saying that is that the collator will be case-, accent-, and 23090 * variation-insensitive, and only distinguish between the base characters 23091 * <li>case or secondary - Both the primary and secondary distinctions between characters 23092 * are significant. That is, the collator will be accent- and variation-insensitive 23093 * and will distinguish between base characters and character case. 23094 * <li>accent or tertiary - The primary, secondary, and tertiary distinctions between 23095 * characters are all significant. That is, the collator will be 23096 * variation-insensitive, but accent-, case-, and base-character-sensitive. 23097 * <li>variant or quaternary - All distinctions between characters are significant. That is, 23098 * the algorithm is base character-, case-, accent-, and variation-sensitive. 23099 * </ol> 23100 * 23101 * <li><i>upperFirst</i> - boolean. When collating case-sensitively in a script that 23102 * has the concept of case, put upper-case 23103 * characters first, otherwise lower-case will come first. Warning: some browsers do 23104 * not implement this feature or at least do not implement it properly, so if you are 23105 * using the native collator with this option, you may get different results in different 23106 * browsers. To guarantee the same results, set useNative to false to use the ilib 23107 * collator implementation. This of course will be somewhat slower, but more 23108 * predictable. Default: true 23109 * 23110 * <li><i>reverse</i> - boolean. Return the list sorted in reverse order. When the 23111 * upperFirst option is also set to true, upper-case characters would then come at 23112 * the end of the list. Default: false. 23113 * 23114 * <li><i>scriptOrder</i> - string. When collating strings in multiple scripts, 23115 * this property specifies what order those scripts should be sorted. The default 23116 * Unicode Collation Algorithm (UCA) already has a default order for scripts, but 23117 * this can be tailored via this property. The value of this option is a 23118 * space-separated list of ISO 15924 scripts codes. If a code is specified in this 23119 * property, its default data must be included using the JS assembly tool. If the 23120 * data is not included, the ordering for the script will be ignored. Default: 23121 * the default order defined by the UCA. 23122 * 23123 * <li><i>style</i> - The value of the style parameter is dependent on the locale. 23124 * For some locales, there are different styles of collating strings depending 23125 * on what kind of strings are being collated or what the preference of the user 23126 * is. For example, in German, there is a phonebook order and a dictionary ordering 23127 * that sort the same array of strings slightly differently. 23128 * The static method {@link Collator#getAvailableStyles} will return a list of styles that ilib 23129 * currently knows about for any given locale. If the value of the style option is 23130 * not recognized for a locale, it will be ignored. Default style is "standard".<p> 23131 * 23132 * <li><i>usage</i> - Whether this collator will be used for searching or sorting. 23133 * Valid values are simply the strings "sort" or "search". When used for sorting, 23134 * it is good idea if a collator produces a stable sort. That is, the order of the 23135 * sorted array of strings should not depend on the order of the strings in the 23136 * input array. As such, when a collator is supposed to act case insensitively, 23137 * it nonetheless still distinguishes between case after all other criteria 23138 * are satisfied so that strings that are distinguished only by case do not sort 23139 * randomly. For searching, we would like to match two strings that different only 23140 * by case, so the collator must return equals in that situation instead of 23141 * further distinguishing by case. Default is "sort". 23142 * 23143 * <li><i>numeric</i> - Treat the left and right strings as if they started with 23144 * numbers and sort them numerically rather than lexically. 23145 * 23146 * <li><i>ignorePunctuation</i> - Skip punctuation characters when comparing the 23147 * strings. 23148 * 23149 * <li>onLoad - a callback function to call when the collator object is fully 23150 * loaded. When the onLoad option is given, the collator object will attempt to 23151 * load any missing locale data using the ilib loader callback. 23152 * When the constructor is done (even if the data is already preassembled), the 23153 * onLoad function is called with the current instance as a parameter, so this 23154 * callback can be used with preassembled or dynamic loading or a mix of the two. 23155 * 23156 * <li>sync - tell whether to load any missing locale data synchronously or 23157 * asynchronously. If this option is given as "false", then the "onLoad" 23158 * callback must be given, as the instance returned from this constructor will 23159 * not be usable for a while. 23160 * 23161 * <li><i>loadParams</i> - an object containing parameters to pass to the 23162 * loader callback function when locale data is missing. The parameters are not 23163 * interpretted or modified in any way. They are simply passed along. The object 23164 * may contain any property/value pairs as long as the calling code is in 23165 * agreement with the loader callback function as to what those parameters mean. 23166 * 23167 * <li><i>useNative</i> - when this option is true, use the native Intl object 23168 * provided by the Javascript engine, if it exists, to implement this class. If 23169 * it doesn't exist, or if this parameter is false, then this class uses a pure 23170 * Javascript implementation, which is slower and uses a lot more memory, but 23171 * works everywhere that ilib works. Default is "true". 23172 * </ul> 23173 * 23174 * <h2>Operation</h2> 23175 * 23176 * The Collator constructor returns a collator object tailored with the above 23177 * options. The object contains an internal compare() method which compares two 23178 * strings according to those options. This can be used directly to compare 23179 * two strings, but is not useful for passing to the javascript sort function 23180 * because then it will not have its collation data available. Instead, use the 23181 * getComparator() method to retrieve a function that is bound to the collator 23182 * object. (You could also bind it yourself using ilib.bind()). The bound function 23183 * can be used with the standard Javascript array sorting algorithm, or as a 23184 * comparator with your own sorting algorithm.<p> 23185 * 23186 * Example using the standard Javascript array sorting call with the bound 23187 * function:<p> 23188 * 23189 * <code> 23190 * <pre> 23191 * var arr = ["ö", "oe", "ü", "o", "a", "ae", "u", "ß", "ä"]; 23192 * var collator = new Collator({locale: 'de-DE', style: "dictionary"}); 23193 * arr.sort(collator.getComparator()); 23194 * console.log(JSON.stringify(arr)); 23195 * </pre> 23196 * </code> 23197 * <p> 23198 * 23199 * Would give the output:<p> 23200 * 23201 * <code> 23202 * <pre> 23203 * ["a", "ae", "ä", "o", "oe", "ö", "ß", "u", "ü"] 23204 * </pre> 23205 * </code> 23206 * 23207 * When sorting an array of Javascript objects according to one of the 23208 * string properties of the objects, wrap the collator's compare function 23209 * in your own comparator function that knows the structure of the objects 23210 * being sorted:<p> 23211 * 23212 * <code> 23213 * <pre> 23214 * var collator = new Collator({locale: 'de-DE'}); 23215 * var myComparator = function (collator) { 23216 * var comparator = collator.getComparator(); 23217 * // left and right are your own objects 23218 * return function (left, right) { 23219 * return comparator(left.x.y.textProperty, right.x.y.textProperty); 23220 * }; 23221 * }; 23222 * arr.sort(myComparator(collator)); 23223 * </pre> 23224 * </code> 23225 * <p> 23226 * 23227 * <h2>Sort Keys</h2> 23228 * 23229 * The collator class also has a method to retrieve the sort key for a 23230 * string. The sort key is an array of values that represent how each 23231 * character in the string should be collated according to the characteristics 23232 * of the collation algorithm and the given options. Thus, sort keys can be 23233 * compared directly value-for-value with other sort keys that were generated 23234 * by the same collator, and the resulting ordering is guaranteed to be the 23235 * same as if the original strings were compared by the collator. 23236 * Sort keys generated by different collators are not guaranteed to give 23237 * any reasonable results when compared together unless the two collators 23238 * were constructed with 23239 * exactly the same options and therefore end up representing the exact same 23240 * collation sequence.<p> 23241 * 23242 * A good rule of thumb is that you would use a sort key if you had 10 or more 23243 * items to sort or if your array might be resorted arbitrarily. For example, if your 23244 * user interface was displaying a table with 100 rows in it, and each row had 23245 * 4 sortable text columns which could be sorted in acending or descending order, 23246 * the recommended practice would be to generate a sort key for each of the 4 23247 * sortable fields in each row and store that in the Javascript representation of the 23248 * table data. Then, when the user clicks on a column header to resort the 23249 * table according to that column, the resorting would be relatively quick 23250 * because it would only be comparing arrays of values, and not recalculating 23251 * the collation values for each character in each string for every comparison.<p> 23252 * 23253 * For tables that are large, it is usually a better idea to do the sorting 23254 * on the server side, especially if the table is the result of a database 23255 * query. In this case, the table is usually a view of the cursor of a large 23256 * results set, and only a few entries are sent to the front end at a time. 23257 * In order to sort the set efficiently, it should be done on the database 23258 * level instead. 23259 * 23260 * <h2>Data</h2> 23261 * 23262 * Doing correct collation entails a huge amount of mapping data, much of which is 23263 * not necessary when collating in one language with one script, which is the most 23264 * common case. Thus, ilib implements a number of ways to include the data you 23265 * need or leave out the data you don't need using the JS assembly tool: 23266 * 23267 * <ol> 23268 * <li>Full multilingual data - if you are sorting multilingual data and need to collate 23269 * text written in multiple scripts, you can use the directive "!data collation/ducet" to 23270 * load in the full collation data. This allows the collator to perform the entire 23271 * Unicode Collation Algorithm (UCA) based on the Default Unicode Collation Element 23272 * Table (DUCET). The data is very large, on the order of multiple megabytes, but 23273 * sometimes it is necessary. 23274 * <li>A few scripts - if you are sorting text written in only a few scripts, you may 23275 * want to include only the data for those scripts. Each ISO 15924 script code has its 23276 * own data available in a separate file, so you can use the data directive to include 23277 * only the data for the scripts you need. For example, use 23278 * "!data collation/Latn" to retrieve the collation information for the Latin script. 23279 * Because the "ducet" table mentioned in the previous point is a superset of the 23280 * tables for all other scripts, you do not need to include explicitly the data for 23281 * any particular script when using "ducet". That is, you either include "ducet" or 23282 * you include a specific list of scripts. 23283 * <li>Only one script - if you are sorting text written only in one script, you can 23284 * either include the data directly as in the previous point, or you can rely on the 23285 * locale to include the correct data for you. In this case, you can use the directive 23286 * "!data collate" to load in the locale's collation data for its most common script. 23287 * </ol> 23288 * 23289 * With any of the above ways of including the data, the collator will only perform the 23290 * correct language-sensitive sorting for the given locale. All other scripts will be 23291 * sorted in the default manner according to the UCA. For example, if you include the 23292 * "ducet" data and pass in "de-DE" (German for Germany) as the locale spec, then 23293 * only the Latin script (the default script for German) will be sorted according to 23294 * German rules. All other scripts in the DUCET, such as Japanese or Arabic, will use 23295 * the default UCA collation rules.<p> 23296 * 23297 * If this collator encounters a character for which it has no collation data, it will 23298 * sort those characters by pure Unicode value after all characters for which it does have 23299 * collation data. For example, if you only loaded in the German collation data (ie. the 23300 * data for the Latin script tailored to German) to sort a list of person names, but that 23301 * list happens to include the names of a few Japanese people written in Japanese 23302 * characters, the Japanese names will sort at the end of the list after all German names, 23303 * and will sort according to the Unicode values of the characters. 23304 * 23305 * @constructor 23306 * @param {Object} options options governing how the resulting comparator 23307 * function will operate 23308 */ 23309 var Collator = function(options) { 23310 var sync = true, 23311 loadParams = undefined, 23312 useNative = true; 23313 23314 // defaults 23315 /** 23316 * @private 23317 * @type {Locale} 23318 */ 23319 this.locale = new Locale(ilib.getLocale()); 23320 23321 /** @private */ 23322 this.caseFirst = "upper"; 23323 /** @private */ 23324 this.sensitivity = "variant"; 23325 /** @private */ 23326 this.level = 4; 23327 /** @private */ 23328 this.usage = "sort"; 23329 /** @private */ 23330 this.reverse = false; 23331 /** @private */ 23332 this.numeric = false; 23333 /** @private */ 23334 this.style = "default"; 23335 /** @private */ 23336 this.ignorePunctuation = false; 23337 23338 if (options) { 23339 if (options.locale) { 23340 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 23341 } 23342 if (options.sensitivity) { 23343 switch (options.sensitivity) { 23344 case 'primary': 23345 case 'base': 23346 this.sensitivity = "base"; 23347 this.level = 1; 23348 break; 23349 case 'secondary': 23350 case 'accent': 23351 this.sensitivity = "accent"; 23352 this.level = 2; 23353 break; 23354 case 'tertiary': 23355 case 'case': 23356 this.sensitivity = "case"; 23357 this.level = 3; 23358 break; 23359 case 'quaternary': 23360 case 'variant': 23361 this.sensitivity = "variant"; 23362 this.level = 4; 23363 break; 23364 } 23365 } 23366 if (typeof(options.upperFirst) !== 'undefined') { 23367 this.caseFirst = options.upperFirst ? "upper" : "lower"; 23368 } 23369 23370 if (typeof(options.ignorePunctuation) !== 'undefined') { 23371 this.ignorePunctuation = options.ignorePunctuation; 23372 } 23373 if (typeof(options.sync) !== 'undefined') { 23374 sync = (options.sync == true); 23375 } 23376 23377 loadParams = options.loadParams; 23378 if (typeof(options.useNative) !== 'undefined') { 23379 useNative = options.useNative; 23380 } 23381 23382 if (options.usage === "sort" || options.usage === "search") { 23383 this.usage = options.usage; 23384 } 23385 23386 if (typeof(options.reverse) === 'boolean') { 23387 this.reverse = options.reverse; 23388 } 23389 23390 if (typeof(options.numeric) === 'boolean') { 23391 this.numeric = options.numeric; 23392 } 23393 23394 if (typeof(options.style) === 'string') { 23395 this.style = options.style; 23396 } 23397 } 23398 23399 if (this.usage === "sort") { 23400 // produces a stable sort 23401 this.level = 4; 23402 } 23403 23404 if (useNative && typeof(Intl) !== 'undefined' && Intl) { 23405 // this engine is modern and supports the new Intl object! 23406 //console.log("implemented natively"); 23407 /** 23408 * @private 23409 * @type {{compare:function(string,string)}} 23410 */ 23411 this.collator = new Intl.Collator(this.locale.getSpec(), this); 23412 23413 if (options && typeof(options.onLoad) === 'function') { 23414 options.onLoad(this); 23415 } 23416 } else { 23417 //console.log("implemented in pure JS"); 23418 if (!Collator.cache) { 23419 Collator.cache = {}; 23420 } 23421 23422 // else implement in pure Javascript 23423 Utils.loadData({ 23424 object: Collator, 23425 locale: this.locale, 23426 name: "collation.json", 23427 sync: sync, 23428 loadParams: loadParams, 23429 callback: ilib.bind(this, function (collation) { 23430 if (!collation) { 23431 collation = ilib.data.collation; 23432 var spec = this.locale.getSpec().replace(/-/g, '_'); 23433 Collator.cache[spec] = collation; 23434 } 23435 this._init(collation); 23436 new LocaleInfo(this.locale, { 23437 sync: sync, 23438 loadParams: loadParams, 23439 onLoad: ilib.bind(this, function(li) { 23440 this.li = li; 23441 if (this.ignorePunctuation) { 23442 isPunct._init(sync, loadParams, ilib.bind(this, function() { 23443 if (options && typeof(options.onLoad) === 'function') { 23444 options.onLoad(this); 23445 } 23446 })); 23447 } else { 23448 if (options && typeof(options.onLoad) === 'function') { 23449 options.onLoad(this); 23450 } 23451 } 23452 }) 23453 }); 23454 }) 23455 }); 23456 } 23457 }; 23458 23459 Collator.prototype = { 23460 /** 23461 * @private 23462 * Bit pack an array of values into a single number 23463 * @param {number|null|Array.<number>} arr array of values to bit pack 23464 * @param {number} offset offset for the start of this map 23465 */ 23466 _pack: function (arr, offset) { 23467 var value = 0; 23468 if (arr) { 23469 if (typeof(arr) === 'number') { 23470 arr = [ arr ]; 23471 } 23472 for (var i = 0; i < this.level; i++) { 23473 var thisLevel = (typeof(arr[i]) !== "undefined" ? arr[i] : 0); 23474 if (i === 0) { 23475 thisLevel += offset; 23476 } 23477 if (i > 0) { 23478 value <<= this.collation.bits[i]; 23479 } 23480 if (i === 2 && this.caseFirst === "lower") { 23481 // sort the lower case first instead of upper 23482 value = value | (1 - thisLevel); 23483 } else { 23484 value = value | thisLevel; 23485 } 23486 } 23487 } 23488 return value; 23489 }, 23490 23491 /** 23492 * @private 23493 * Return the rule packed into an array of collation elements. 23494 * @param {Array.<number|null|Array.<number>>} rule 23495 * @param {number} offset 23496 * @return {Array.<number>} a bit-packed array of numbers 23497 */ 23498 _packRule: function(rule, offset) { 23499 if (ilib.isArray(rule[0])) { 23500 var ret = []; 23501 for (var i = 0; i < rule.length; i++) { 23502 ret.push(this._pack(rule[i], offset)); 23503 } 23504 return ret; 23505 } else { 23506 return [ this._pack(rule, offset) ]; 23507 } 23508 }, 23509 23510 /** 23511 * @private 23512 */ 23513 _addChars: function (str, offset) { 23514 var gs = new GlyphString(str); 23515 var it = gs.charIterator(); 23516 var c; 23517 23518 while (it.hasNext()) { 23519 c = it.next(); 23520 if (c === "'") { 23521 // escape a sequence of chars as one collation element 23522 c = ""; 23523 var x = ""; 23524 while (it.hasNext() && x !== "'") { 23525 c += x; 23526 x = it.next(); 23527 } 23528 } 23529 this.lastMap++; 23530 this.map[c] = this._packRule([this.lastMap], offset); 23531 } 23532 }, 23533 23534 /** 23535 * @private 23536 */ 23537 _addRules: function(rules, start) { 23538 var p; 23539 for (var r in rules.map) { 23540 if (r) { 23541 this.map[r] = this._packRule(rules.map[r], start); 23542 p = typeof(rules.map[r][0]) === 'number' ? rules.map[r][0] : rules.map[r][0][0]; 23543 this.lastMap = Math.max(p + start, this.lastMap); 23544 } 23545 } 23546 23547 if (typeof(rules.ranges) !== 'undefined') { 23548 // for each range, everything in the range goes in primary sequence from the start 23549 for (var i = 0; i < rules.ranges.length; i++) { 23550 var range = rules.ranges[i]; 23551 23552 this.lastMap = range.start; 23553 if (typeof(range.chars) === "string") { 23554 this._addChars(range.chars, start); 23555 } else { 23556 for (var k = 0; k < range.chars.length; k++) { 23557 this._addChars(range.chars[k], start); 23558 } 23559 } 23560 } 23561 } 23562 }, 23563 23564 /** 23565 * @private 23566 */ 23567 _init: function(rules) { 23568 var rule = this.style; 23569 while (typeof(rule) === 'string') { 23570 rule = rules[rule]; 23571 } 23572 if (!rule) { 23573 rule = "default"; 23574 while (typeof(rule) === 'string') { 23575 rule = rules[rule]; 23576 } 23577 } 23578 if (!rule) { 23579 this.map = {}; 23580 return; 23581 } 23582 23583 /** 23584 * @private 23585 * @type {{scripts:Array.<string>,bits:Array.<number>,maxes:Array.<number>,bases:Array.<number>,map:Object.<string,Array.<number|null|Array.<number>>>}} 23586 */ 23587 this.collation = rule; 23588 this.map = {}; 23589 this.lastMap = -1; 23590 this.keysize = this.collation.keysize[this.level-1]; 23591 23592 if (typeof(this.collation.inherit) !== 'undefined') { 23593 for (var i = 0; i < this.collation.inherit.length; i++) { 23594 var col = this.collation.inherit[i]; 23595 rule = typeof(col) === 'object' ? col.name : col; 23596 if (rules[rule]) { 23597 this._addRules(rules[rule], col.start || this.lastMap+1); 23598 } 23599 } 23600 } 23601 this._addRules(this.collation, this.lastMap+1); 23602 }, 23603 23604 /** 23605 * @private 23606 */ 23607 _basicCompare: function(left, right) { 23608 var l = (left instanceof NormString) ? left : new NormString(left), 23609 r = (right instanceof NormString) ? right : new NormString(right), 23610 lchar, 23611 rchar, 23612 lelements, 23613 relements; 23614 23615 if (this.numeric) { 23616 var lvalue = new INumber(left, {locale: this.locale}); 23617 var rvalue = new INumber(right, {locale: this.locale}); 23618 if (!isNaN(lvalue.valueOf()) && !isNaN(rvalue.valueOf())) { 23619 var diff = lvalue.valueOf() - rvalue.valueOf(); 23620 if (diff) { 23621 return diff; 23622 } else { 23623 // skip the numeric part and compare the rest lexically 23624 l = new NormString(left.substring(lvalue.parsed.length)); 23625 r = new NormString(right.substring(rvalue.parsed.length)); 23626 } 23627 } 23628 // else if they aren't both numbers, then let the code below take care of the lexical comparison instead 23629 } 23630 23631 lelements = new ElementIterator(new CodePointSource(l, this.ignorePunctuation), this.map, this.keysize); 23632 relements = new ElementIterator(new CodePointSource(r, this.ignorePunctuation), this.map, this.keysize); 23633 23634 while (lelements.hasNext() && relements.hasNext()) { 23635 var diff = lelements.next() - relements.next(); 23636 if (diff) { 23637 return diff; 23638 } 23639 } 23640 if (!lelements.hasNext() && !relements.hasNext()) { 23641 return 0; 23642 } else if (lelements.hasNext()) { 23643 return 1; 23644 } else { 23645 return -1; 23646 } 23647 }, 23648 23649 /** 23650 * Compare two strings together according to the rules of this 23651 * collator instance. Do not use this function directly with 23652 * Array.sort, as it will not have its collation data available 23653 * and therefore will not function properly. Use the function 23654 * returned by getComparator() instead. 23655 * 23656 * @param {string} left the left string to compare 23657 * @param {string} right the right string to compare 23658 * @return {number} a negative number if left comes before right, a 23659 * positive number if right comes before left, and zero if left and 23660 * right are equivalent according to this collator 23661 */ 23662 compare: function (left, right) { 23663 // last resort: use the "C" locale 23664 if (this.collator) { 23665 // implemented by the core engine 23666 return this.collator.compare(left, right); 23667 } 23668 23669 var ret = this._basicCompare(left, right); 23670 return this.reverse ? -ret : ret; 23671 }, 23672 23673 /** 23674 * Return a comparator function that can compare two strings together 23675 * according to the rules of this collator instance. The function 23676 * returns a negative number if the left 23677 * string comes before right, a positive number if the right string comes 23678 * before the left, and zero if left and right are equivalent. If the 23679 * reverse property was given as true to the collator constructor, this 23680 * function will 23681 * switch the sign of those values to cause sorting to happen in the 23682 * reverse order. 23683 * 23684 * @return {function(...)|undefined} a comparator function that 23685 * can compare two strings together according to the rules of this 23686 * collator instance 23687 */ 23688 getComparator: function() { 23689 // bind the function to this instance so that we have the collation 23690 // rules available to do the work 23691 if (this.collator) { 23692 // implemented by the core engine 23693 return this.collator.compare; 23694 } 23695 23696 return /** @type function(string,string):number */ ilib.bind(this, this.compare); 23697 }, 23698 23699 /** 23700 * Return a sort key string for the given string. The sort key 23701 * string is a list of values that represent each character 23702 * in the original string. The sort key 23703 * values for any particular character consists of 3 numbers that 23704 * encode the primary, secondary, and tertiary characteristics 23705 * of that character. The values of each characteristic are 23706 * modified according to the strength of this collator instance 23707 * to give the correct collation order. The idea is that this 23708 * sort key string is directly comparable byte-for-byte to 23709 * other sort key strings generated by this collator without 23710 * any further knowledge of the collation rules for the locale. 23711 * More formally, if a < b according to the rules of this collation, 23712 * then it is guaranteed that sortkey(a) < sortkey(b) when compared 23713 * byte-for-byte. The sort key string can therefore be used 23714 * without the collator to sort an array of strings efficiently 23715 * because the work of determining the applicability of various 23716 * collation rules is done once up-front when generating 23717 * the sort key.<p> 23718 * 23719 * The sort key string can be treated as a regular, albeit somewhat 23720 * odd-looking, string. That is, it can be pass to regular 23721 * Javascript functions without problems. 23722 * 23723 * @param {string} str the original string to generate the sort key for 23724 * @return {string} a sort key string for the given string 23725 */ 23726 sortKey: function (str) { 23727 if (!str) { 23728 return ""; 23729 } 23730 23731 if (this.collator) { 23732 // native, no sort keys available 23733 return str; 23734 } 23735 23736 function pad(str, limit) { 23737 return "0000000000000000".substring(0, limit - str.length) + str; 23738 } 23739 23740 if (this.numeric) { 23741 var v = new INumber(str, {locale: this.locale}); 23742 var s = isNaN(v.valueOf()) ? "" : v.valueOf().toString(16); 23743 return pad(s, 16); 23744 } else { 23745 var n = (typeof(str) === "string") ? new NormString(str) : str, 23746 ret = "", 23747 lelements = new ElementIterator(new CodePointSource(n, this.ignorePunctuation), this.map, this.keysize), 23748 element; 23749 23750 while (lelements.hasNext()) { 23751 element = lelements.next(); 23752 if (this.reverse) { 23753 // for reverse, take the bitwise inverse 23754 element = (1 << this.keysize) - element; 23755 } 23756 ret += pad(element.toString(16), this.keysize/4); 23757 } 23758 } 23759 return ret; 23760 } 23761 }; 23762 23763 /** 23764 * Retrieve the list of collation style names that are available for the 23765 * given locale. This list varies depending on the locale, and depending 23766 * on whether or not the data for that locale was assembled into this copy 23767 * of ilib. 23768 * 23769 * @param {Locale|string=} locale The locale for which the available 23770 * styles are being sought 23771 * @return Array.<string> an array of style names that are available for 23772 * the given locale 23773 */ 23774 Collator.getAvailableStyles = function (locale) { 23775 return [ "standard" ]; 23776 }; 23777 23778 /** 23779 * Retrieve the list of ISO 15924 script codes that are available in this 23780 * copy of ilib. This list varies depending on whether or not the data for 23781 * various scripts was assembled into this copy of ilib. If the "ducet" 23782 * data is assembled into this copy of ilib, this method will report the 23783 * entire list of scripts as being available. If a collator instance is 23784 * instantiated with a script code that is not on the list returned by this 23785 * function, it will be ignored and text in that script will be sorted by 23786 * numeric Unicode values of the characters. 23787 * 23788 * @return Array.<string> an array of ISO 15924 script codes that are 23789 * available 23790 */ 23791 Collator.getAvailableScripts = function () { 23792 return [ "Latn" ]; 23793 }; 23794 23795 23796 23797 /*< nfd/all.js */ 23798 /* 23799 * all.js - include file for normalization data for a particular script 23800 * 23801 * Copyright © 2013, JEDLSoft 23802 * 23803 * Licensed under the Apache License, Version 2.0 (the "License"); 23804 * you may not use this file except in compliance with the License. 23805 * You may obtain a copy of the License at 23806 * 23807 * http://www.apache.org/licenses/LICENSE-2.0 23808 * 23809 * Unless required by applicable law or agreed to in writing, software 23810 * distributed under the License is distributed on an "AS IS" BASIS, 23811 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23812 * 23813 * See the License for the specific language governing permissions and 23814 * limitations under the License. 23815 */ 23816 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 23817 // !data normdata nfd/all 23818 ilib.extend(ilib.data.norm, ilib.data.normdata); 23819 ilib.extend(ilib.data.norm.nfd, ilib.data.nfd_all); 23820 ilib.data.normdata = undefined; 23821 ilib.data.nfd_all = undefined; 23822 /*< nfkd/all.js */ 23823 /* 23824 * all.js - include file for normalization data for a particular script 23825 * 23826 * Copyright © 2013, JEDLSoft 23827 * 23828 * Licensed under the Apache License, Version 2.0 (the "License"); 23829 * you may not use this file except in compliance with the License. 23830 * You may obtain a copy of the License at 23831 * 23832 * http://www.apache.org/licenses/LICENSE-2.0 23833 * 23834 * Unless required by applicable law or agreed to in writing, software 23835 * distributed under the License is distributed on an "AS IS" BASIS, 23836 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23837 * 23838 * See the License for the specific language governing permissions and 23839 * limitations under the License. 23840 */ 23841 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 23842 // !depends nfd/all.js 23843 // !data normdata nfkd/all 23844 ilib.extend(ilib.data.norm, ilib.data.normdata); 23845 ilib.extend(ilib.data.norm.nfkd, ilib.data.nfkd_all); 23846 ilib.data.normdata = undefined; 23847 ilib.data.nfkd_all = undefined; 23848 /*< nfkc/all.js */ 23849 /* 23850 * all.js - include file for normalization data for a particular script 23851 * 23852 * Copyright © 2013, JEDLSoft 23853 * 23854 * Licensed under the Apache License, Version 2.0 (the "License"); 23855 * you may not use this file except in compliance with the License. 23856 * You may obtain a copy of the License at 23857 * 23858 * http://www.apache.org/licenses/LICENSE-2.0 23859 * 23860 * Unless required by applicable law or agreed to in writing, software 23861 * distributed under the License is distributed on an "AS IS" BASIS, 23862 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23863 * 23864 * See the License for the specific language governing permissions and 23865 * limitations under the License. 23866 */ 23867 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 23868 // !depends nfd/all.js nfkd/all.js 23869 // !data norm 23870 ilib.extend(ilib.data.norm, ilib.data.normdata); 23871 ilib.data.normdata = undefined; 23872 23873 /*< LocaleMatcher.js */ 23874 /* 23875 * LocaleMatcher.js - Locale matcher definition 23876 * 23877 * Copyright © 2013-2015, JEDLSoft 23878 * 23879 * Licensed under the Apache License, Version 2.0 (the "License"); 23880 * you may not use this file except in compliance with the License. 23881 * You may obtain a copy of the License at 23882 * 23883 * http://www.apache.org/licenses/LICENSE-2.0 23884 * 23885 * Unless required by applicable law or agreed to in writing, software 23886 * distributed under the License is distributed on an "AS IS" BASIS, 23887 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23888 * 23889 * See the License for the specific language governing permissions and 23890 * limitations under the License. 23891 */ 23892 23893 // !depends ilib.js Locale.js Utils.js 23894 // !data likelylocales 23895 23896 23897 /** 23898 * @class 23899 * Create a new locale matcher instance. This is used 23900 * to see which locales can be matched with each other in 23901 * various ways.<p> 23902 * 23903 * The options object may contain any of the following properties: 23904 * 23905 * <ul> 23906 * <li><i>locale</i> - the locale to match 23907 * 23908 * <li><i>onLoad</i> - a callback function to call when the locale matcher object is fully 23909 * loaded. When the onLoad option is given, the locale matcher object will attempt to 23910 * load any missing locale data using the ilib loader callback. 23911 * When the constructor is done (even if the data is already preassembled), the 23912 * onLoad function is called with the current instance as a parameter, so this 23913 * callback can be used with preassembled or dynamic loading or a mix of the two. 23914 * 23915 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 23916 * asynchronously. If this option is given as "false", then the "onLoad" 23917 * callback must be given, as the instance returned from this constructor will 23918 * not be usable for a while. 23919 * 23920 * <li><i>loadParams</i> - an object containing parameters to pass to the 23921 * loader callback function when locale data is missing. The parameters are not 23922 * interpretted or modified in any way. They are simply passed along. The object 23923 * may contain any property/value pairs as long as the calling code is in 23924 * agreement with the loader callback function as to what those parameters mean. 23925 * </ul> 23926 * 23927 * 23928 * @constructor 23929 * @param {Object} options parameters to initialize this matcher 23930 */ 23931 var LocaleMatcher = function(options) { 23932 var sync = true, 23933 loadParams = undefined; 23934 23935 this.locale = new Locale(); 23936 23937 if (options) { 23938 if (typeof(options.locale) !== 'undefined') { 23939 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 23940 } 23941 23942 if (typeof(options.sync) !== 'undefined') { 23943 sync = (options.sync == true); 23944 } 23945 23946 if (typeof(options.loadParams) !== 'undefined') { 23947 loadParams = options.loadParams; 23948 } 23949 } 23950 23951 if (!LocaleMatcher.cache) { 23952 LocaleMatcher.cache = {}; 23953 } 23954 23955 if (typeof(ilib.data.likelylocales) === 'undefined') { 23956 Utils.loadData({ 23957 object: LocaleMatcher, 23958 locale: "-", 23959 name: "likelylocales.json", 23960 sync: sync, 23961 loadParams: loadParams, 23962 callback: ilib.bind(this, function (info) { 23963 if (!info) { 23964 info = {}; 23965 var spec = this.locale.getSpec().replace(/-/g, "_"); 23966 LocaleMatcher.cache[spec] = info; 23967 } 23968 /** @type {Object.<string,string>} */ 23969 this.info = info; 23970 if (options && typeof(options.onLoad) === 'function') { 23971 options.onLoad(this); 23972 } 23973 }) 23974 }); 23975 } else { 23976 this.info = /** @type {Object.<string,string>} */ ilib.data.likelylocales; 23977 } 23978 }; 23979 23980 23981 LocaleMatcher.prototype = { 23982 /** 23983 * Return the locale used to construct this instance. 23984 * @return {Locale|undefined} the locale for this matcher 23985 */ 23986 getLocale: function() { 23987 return this.locale; 23988 }, 23989 23990 /** 23991 * Return an Locale instance that is fully specified based on partial information 23992 * given to the constructor of this locale matcher instance. For example, if the locale 23993 * spec given to this locale matcher instance is simply "ru" (for the Russian language), 23994 * then it will fill in the missing region and script tags and return a locale with 23995 * the specifier "ru-Cyrl-RU". (ie. Russian language, Cyrillic, Russian Federation). 23996 * Any one or two of the language, script, or region parts may be left unspecified, 23997 * and the other one or two parts will be filled in automatically. If this 23998 * class has no information about the given locale, then the locale of this 23999 * locale matcher instance is returned unchanged. 24000 * 24001 * @returns {Locale} the most likely completion of the partial locale given 24002 * to the constructor of this locale matcher instance 24003 */ 24004 getLikelyLocale: function () { 24005 if (typeof(this.info[this.locale.getSpec()]) === 'undefined') { 24006 return this.locale; 24007 } 24008 24009 return new Locale(this.info[this.locale.getSpec()]); 24010 } 24011 }; 24012 24013 24014 /*< CaseMapper.js */ 24015 /* 24016 * caseMapper.js - define upper- and lower-case mapper 24017 * 24018 * Copyright © 2014-2015, JEDLSoft 24019 * 24020 * Licensed under the Apache License, Version 2.0 (the "License"); 24021 * you may not use this file except in compliance with the License. 24022 * You may obtain a copy of the License at 24023 * 24024 * http://www.apache.org/licenses/LICENSE-2.0 24025 * 24026 * Unless required by applicable law or agreed to in writing, software 24027 * distributed under the License is distributed on an "AS IS" BASIS, 24028 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24029 * 24030 * See the License for the specific language governing permissions and 24031 * limitations under the License. 24032 */ 24033 24034 // !depends Locale.js IString.js 24035 24036 24037 24038 /** 24039 * @class 24040 * Create a new string mapper instance that maps strings to upper or 24041 * lower case. This mapping will work for any string as characters 24042 * that have no case will be returned unchanged.<p> 24043 * 24044 * The options may contain any of the following properties: 24045 * 24046 * <ul> 24047 * <li><i>locale</i> - locale to use when loading the mapper. Some maps are 24048 * locale-dependent, and this locale selects the right one. Default if this is 24049 * not specified is the current locale. 24050 * 24051 * <li><i>direction</i> - "toupper" for upper-casing, or "tolower" for lower-casing. 24052 * Default if not specified is "toupper". 24053 * </ul> 24054 * 24055 * 24056 * @constructor 24057 * @param {Object=} options options to initialize this mapper 24058 */ 24059 var CaseMapper = function (options) { 24060 this.up = true; 24061 this.locale = new Locale(); 24062 24063 if (options) { 24064 if (typeof(options.locale) !== 'undefined') { 24065 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 24066 } 24067 24068 this.up = (!options.direction || options.direction === "toupper"); 24069 } 24070 24071 this.mapData = this.up ? { 24072 "ß": "SS", // German 24073 'ΐ': 'Ι', // Greek 24074 'ά': 'Α', 24075 'έ': 'Ε', 24076 'ή': 'Η', 24077 'ί': 'Ι', 24078 'ΰ': 'Υ', 24079 'ϊ': 'Ι', 24080 'ϋ': 'Υ', 24081 'ό': 'Ο', 24082 'ύ': 'Υ', 24083 'ώ': 'Ω', 24084 'Ӏ': 'Ӏ', // Russian and slavic languages 24085 'ӏ': 'Ӏ' 24086 } : { 24087 'Ӏ': 'Ӏ' // Russian and slavic languages 24088 }; 24089 24090 switch (this.locale.getLanguage()) { 24091 case "az": 24092 case "tr": 24093 case "crh": 24094 case "kk": 24095 case "krc": 24096 case "tt": 24097 var lower = "iı"; 24098 var upper = "İI"; 24099 this._setUpMap(lower, upper); 24100 break; 24101 case "fr": 24102 if (this.up && this.locale.getRegion() !== "CA") { 24103 this._setUpMap("àáâãäçèéêëìíîïñòóôöùúûü", "AAAAACEEEEIIIINOOOOUUUU"); 24104 } 24105 break; 24106 } 24107 24108 if (ilib._getBrowser() === "ie") { 24109 // IE is missing these mappings for some reason 24110 if (this.up) { 24111 this.mapData['ς'] = 'Σ'; 24112 } 24113 this._setUpMap("ⲁⲃⲅⲇⲉⲋⲍⲏⲑⲓⲕⲗⲙⲛⲝⲟⲡⲣⲥⲧⲩⲫⲭⲯⲱⳁⳉⳋ", "ⲀⲂⲄⲆⲈⲊⲌⲎⲐⲒⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⳀⳈⳊ"); // Coptic 24114 // Georgian Nuskhuri <-> Asomtavruli 24115 this._setUpMap("ⴀⴁⴂⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌⴍⴎⴏⴐⴑⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜⴝⴞⴟⴠⴡⴢⴣⴤⴥ", "ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀჁჂჃჄჅ"); 24116 } 24117 }; 24118 24119 CaseMapper.prototype = { 24120 /** 24121 * @private 24122 */ 24123 _charMapper: function(string) { 24124 if (!string) { 24125 return string; 24126 } 24127 var input = (typeof(string) === 'string') ? new IString(string) : string.toString(); 24128 var ret = ""; 24129 var it = input.charIterator(); 24130 var c; 24131 24132 while (it.hasNext()) { 24133 c = it.next(); 24134 if (!this.up && c === 'Σ') { 24135 if (it.hasNext()) { 24136 c = it.next(); 24137 var code = c.charCodeAt(0); 24138 // if the next char is not a greek letter, this is the end of the word so use the 24139 // final form of sigma. Otherwise, use the mid-word form. 24140 ret += ((code < 0x0388 && code !== 0x0386) || code > 0x03CE) ? 'ς' : 'σ'; 24141 ret += c.toLowerCase(); 24142 } else { 24143 // no next char means this is the end of the word, so use the final form of sigma 24144 ret += 'ς'; 24145 } 24146 } else { 24147 if (this.mapData[c]) { 24148 ret += this.mapData[c]; 24149 } else { 24150 ret += this.up ? c.toUpperCase() : c.toLowerCase(); 24151 } 24152 } 24153 } 24154 24155 return ret; 24156 }, 24157 24158 /** @private */ 24159 _setUpMap: function(lower, upper) { 24160 var from, to; 24161 if (this.up) { 24162 from = lower; 24163 to = upper; 24164 } else { 24165 from = upper; 24166 to = lower; 24167 } 24168 for (var i = 0; i < upper.length; i++) { 24169 this.mapData[from[i]] = to[i]; 24170 } 24171 }, 24172 24173 /** 24174 * Return the locale that this mapper was constructed with. 24175 * @returns {Locale} the locale that this mapper was constructed with 24176 */ 24177 getLocale: function () { 24178 return this.locale; 24179 }, 24180 24181 /** 24182 * Map a string to lower case in a locale-sensitive manner. 24183 * 24184 * @param {string|undefined} string 24185 * @return {string|undefined} 24186 */ 24187 map: function (string) { 24188 return this._charMapper(string); 24189 } 24190 }; 24191 24192 24193 /*< NumberingPlan.js */ 24194 /* 24195 * NumPlan.js - Represent a phone numbering plan. 24196 * 24197 * Copyright © 2014-2015, JEDLSoft 24198 * 24199 * Licensed under the Apache License, Version 2.0 (the "License"); 24200 * you may not use this file except in compliance with the License. 24201 * You may obtain a copy of the License at 24202 * 24203 * http://www.apache.org/licenses/LICENSE-2.0 24204 * 24205 * Unless required by applicable law or agreed to in writing, software 24206 * distributed under the License is distributed on an "AS IS" BASIS, 24207 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24208 * 24209 * See the License for the specific language governing permissions and 24210 * limitations under the License. 24211 */ 24212 24213 /* 24214 !depends 24215 ilib.js 24216 Locale.js 24217 Utils.js 24218 JSUtils.js 24219 */ 24220 24221 // !data numplan 24222 24223 24224 /** 24225 * @class 24226 * Create a numbering plan information instance for a particular country's plan.<p> 24227 * 24228 * The options may contain any of the following properties: 24229 * 24230 * <ul> 24231 * <li><i>locale</i> - locale for which the numbering plan is sought. This locale 24232 * will be mapped to the actual numbering plan, which may be shared amongst a 24233 * number of countries. 24234 * 24235 * <li>onLoad - a callback function to call when the date format object is fully 24236 * loaded. When the onLoad option is given, the DateFmt object will attempt to 24237 * load any missing locale data using the ilib loader callback. 24238 * When the constructor is done (even if the data is already preassembled), the 24239 * onLoad function is called with the current instance as a parameter, so this 24240 * callback can be used with preassembled or dynamic loading or a mix of the two. 24241 * 24242 * <li>sync - tell whether to load any missing locale data synchronously or 24243 * asynchronously. If this option is given as "false", then the "onLoad" 24244 * callback must be given, as the instance returned from this constructor will 24245 * not be usable for a while. 24246 * 24247 * <li><i>loadParams</i> - an object containing parameters to pass to the 24248 * loader callback function when locale data is missing. The parameters are not 24249 * interpretted or modified in any way. They are simply passed along. The object 24250 * may contain any property/value pairs as long as the calling code is in 24251 * agreement with the loader callback function as to what those parameters mean. 24252 * </ul> 24253 * 24254 * @private 24255 * @constructor 24256 * @param {Object} options options governing the way this plan is loaded 24257 */ 24258 var NumberingPlan = function (options) { 24259 var sync = true, 24260 loadParams = {}; 24261 24262 this.locale = new Locale(); 24263 24264 if (options) { 24265 if (options.locale) { 24266 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 24267 } 24268 24269 if (typeof(options.sync) !== 'undefined') { 24270 sync = (options.sync == true); 24271 } 24272 24273 if (options.loadParams) { 24274 loadParams = options.loadParams; 24275 } 24276 } 24277 24278 Utils.loadData({ 24279 name: "numplan.json", 24280 object: NumberingPlan, 24281 locale: this.locale, 24282 sync: sync, 24283 loadParams: loadParams, 24284 callback: ilib.bind(this, function (npdata) { 24285 if (!npdata) { 24286 npdata = { 24287 "region": "XX", 24288 "skipTrunk": false, 24289 "trunkCode": "0", 24290 "iddCode": "00", 24291 "dialingPlan": "closed", 24292 "commonFormatChars": " ()-./", 24293 "fieldLengths": { 24294 "areaCode": 0, 24295 "cic": 0, 24296 "mobilePrefix": 0, 24297 "serviceCode": 0 24298 } 24299 }; 24300 } 24301 24302 /** 24303 * @type {{ 24304 * region:string, 24305 * skipTrunk:boolean, 24306 * trunkCode:string, 24307 * iddCode:string, 24308 * dialingPlan:string, 24309 * commonFormatChars:string, 24310 * fieldLengths:Object.<string,number>, 24311 * contextFree:boolean, 24312 * findExtensions:boolean, 24313 * trunkRequired:boolean, 24314 * extendedAreaCodes:boolean 24315 * }} 24316 */ 24317 this.npdata = npdata; 24318 if (options && typeof(options.onLoad) === 'function') { 24319 options.onLoad(this); 24320 } 24321 }) 24322 }); 24323 }; 24324 24325 NumberingPlan.prototype = { 24326 /** 24327 * Return the name of this plan. This may be different than the 24328 * name of the region because sometimes multiple countries share 24329 * the same plan. 24330 * @return {string} the name of the plan 24331 */ 24332 getName: function() { 24333 return this.npdata.region; 24334 }, 24335 24336 /** 24337 * Return the trunk code of the current plan as a string. 24338 * @return {string|undefined} the trunk code of the plan or 24339 * undefined if there is no trunk code in this plan 24340 */ 24341 getTrunkCode: function() { 24342 return this.npdata.trunkCode; 24343 }, 24344 24345 /** 24346 * Return the international direct dialing code of this plan. 24347 * @return {string} the IDD code of this plan 24348 */ 24349 getIDDCode: function() { 24350 return this.npdata.iddCode; 24351 }, 24352 24353 /** 24354 * Return the plan style for this plan. The plan style may be 24355 * one of: 24356 * 24357 * <ul> 24358 * <li>"open" - area codes may be left off if the caller is 24359 * dialing to another number within the same area code 24360 * <li>"closed" - the area code must always be specified, even 24361 * if calling another number within the same area code 24362 * </ul> 24363 * 24364 * @return {string} the plan style, "open" or "closed" 24365 */ 24366 getPlanStyle: function() { 24367 return this.npdata.dialingPlan; 24368 }, 24369 /** [Need Comment] 24370 * Return a contextFree 24371 * 24372 * @return {boolean} 24373 */ 24374 getContextFree: function() { 24375 return this.npdata.contextFree; 24376 }, 24377 /** [Need Comment] 24378 * Return a findExtensions 24379 * 24380 * @return {boolean} 24381 */ 24382 getFindExtensions: function() { 24383 return this.npdata.findExtensions; 24384 }, 24385 /** [Need Comment] 24386 * Return a skipTrunk 24387 * 24388 * @return {boolean} 24389 */ 24390 getSkipTrunk: function() { 24391 return this.npdata.skipTrunk; 24392 }, 24393 /** [Need Comment] 24394 * Return a skipTrunk 24395 * 24396 * @return {boolean} 24397 */ 24398 getTrunkRequired: function() { 24399 return this.npdata.trunkRequired; 24400 }, 24401 /** 24402 * Return true if this plan uses extended area codes. 24403 * @return {boolean} true if the plan uses extended area codes 24404 */ 24405 getExtendedAreaCode: function() { 24406 return this.npdata.extendedAreaCodes; 24407 }, 24408 /** 24409 * Return a string containing all of the common format characters 24410 * used to format numbers. 24411 * @return {string} the common format characters fused in this locale 24412 */ 24413 getCommonFormatChars: function() { 24414 return this.npdata.commonFormatChars; 24415 }, 24416 24417 /** 24418 * Return the length of the field with the given name. If the length 24419 * is returned as 0, this means it is variable length. 24420 * 24421 * @param {string} field name of the field for which the length is 24422 * being sought 24423 * @return {number} if positive, this gives the length of the given 24424 * field. If zero, the field is variable length. If negative, the 24425 * field is not known. 24426 */ 24427 getFieldLength: function (field) { 24428 var dataField = this.npdata.fieldLengths; 24429 24430 return dataField[field]; 24431 } 24432 }; 24433 24434 24435 /*< PhoneLocale.js */ 24436 /* 24437 * phoneloc.js - Represent a phone locale object. 24438 * 24439 * Copyright © 2014-2015, JEDLSoft 24440 * 24441 * Licensed under the Apache License, Version 2.0 (the "License"); 24442 * you may not use this file except in compliance with the License. 24443 * You may obtain a copy of the License at 24444 * 24445 * http://www.apache.org/licenses/LICENSE-2.0 24446 * 24447 * Unless required by applicable law or agreed to in writing, software 24448 * distributed under the License is distributed on an "AS IS" BASIS, 24449 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24450 * 24451 * See the License for the specific language governing permissions and 24452 * limitations under the License. 24453 */ 24454 24455 /* 24456 !depends 24457 ilib.js 24458 Locale.js 24459 Utils.js 24460 */ 24461 24462 // !data phoneloc 24463 24464 24465 /** 24466 * @class 24467 * Extension of the locale class that has extra methods to map various numbers 24468 * related to phone number parsing. 24469 * 24470 * @param {Object} options Options that govern how this phone locale works 24471 * 24472 * @private 24473 * @constructor 24474 * @extends Locale 24475 */ 24476 var PhoneLocale = function(options) { 24477 var region, 24478 mcc, 24479 cc, 24480 sync = true, 24481 loadParams = {}, 24482 locale; 24483 24484 locale = (options && options.locale) || ilib.getLocale(); 24485 24486 this.parent.call(this, locale); 24487 24488 region = this.region; 24489 24490 if (options) { 24491 if (typeof(options.mcc) !== 'undefined') { 24492 mcc = options.mcc; 24493 } 24494 24495 if (typeof(options.countryCode) !== 'undefined') { 24496 cc = options.countryCode; 24497 } 24498 24499 if (typeof(options.sync) !== 'undefined') { 24500 sync = (options.sync == true); 24501 } 24502 24503 if (options.loadParams) { 24504 loadParams = options.loadParams; 24505 } 24506 } 24507 24508 Utils.loadData({ 24509 name: "phoneloc.json", 24510 object: PhoneLocale, 24511 nonlocale: true, 24512 sync: sync, 24513 loadParams: loadParams, 24514 callback: ilib.bind(this, function (data) { 24515 /** @type {{mcc2reg:Object.<string,string>,cc2reg:Object.<string,string>,reg2cc:Object.<string,string>,area2reg:Object.<string,string>}} */ 24516 this.mappings = data; 24517 24518 if (typeof(mcc) !== 'undefined') { 24519 region = this.mappings.mcc2reg[mcc]; 24520 } 24521 24522 if (typeof(cc) !== 'undefined') { 24523 region = this.mappings.cc2reg[cc]; 24524 } 24525 24526 if (!region) { 24527 region = "XX"; 24528 } 24529 24530 this.region = this._normPhoneReg(region); 24531 this._genSpec(); 24532 24533 if (options && typeof(options.onLoad) === 'function') { 24534 options.onLoad(this); 24535 } 24536 }) 24537 }); 24538 }; 24539 24540 PhoneLocale.prototype = new Locale(); 24541 PhoneLocale.prototype.parent = Locale; 24542 PhoneLocale.prototype.constructor = PhoneLocale; 24543 24544 /** 24545 * Map a mobile carrier code to a region code. 24546 * 24547 * @static 24548 * @package 24549 * @param {string|undefined} mcc the MCC to map 24550 * @return {string|undefined} the region code 24551 */ 24552 24553 PhoneLocale.prototype._mapMCCtoRegion = function(mcc) { 24554 if (!mcc) { 24555 return undefined; 24556 } 24557 return this.mappings.mcc2reg && this.mappings.mcc2reg[mcc] || "XX"; 24558 }; 24559 24560 /** 24561 * Map a country code to a region code. 24562 * 24563 * @static 24564 * @package 24565 * @param {string|undefined} cc the country code to map 24566 * @return {string|undefined} the region code 24567 */ 24568 PhoneLocale.prototype._mapCCtoRegion = function(cc) { 24569 if (!cc) { 24570 return undefined; 24571 } 24572 return this.mappings.cc2reg && this.mappings.cc2reg[cc] || "XX"; 24573 }; 24574 24575 /** 24576 * Map a region code to a country code. 24577 * 24578 * @static 24579 * @package 24580 * @param {string|undefined} region the region code to map 24581 * @return {string|undefined} the country code 24582 */ 24583 PhoneLocale.prototype._mapRegiontoCC = function(region) { 24584 if (!region) { 24585 return undefined; 24586 } 24587 return this.mappings.reg2cc && this.mappings.reg2cc[region] || "0"; 24588 }; 24589 24590 /** 24591 * Map a country code to a region code. 24592 * 24593 * @static 24594 * @package 24595 * @param {string|undefined} cc the country code to map 24596 * @param {string|undefined} area the area code within the country code's numbering plan 24597 * @return {string|undefined} the region code 24598 */ 24599 PhoneLocale.prototype._mapAreatoRegion = function(cc, area) { 24600 if (!cc) { 24601 return undefined; 24602 } 24603 if (cc in this.mappings.area2reg) { 24604 return this.mappings.area2reg[cc][area] || this.mappings.area2reg[cc]["default"]; 24605 } else { 24606 return this.mappings.cc2reg[cc]; 24607 } 24608 }; 24609 24610 /** 24611 * Return the region that controls the dialing plan in the given 24612 * region. (ie. the "normalized phone region".) 24613 * 24614 * @static 24615 * @package 24616 * @param {string} region the region code to normalize 24617 * @return {string} the normalized region code 24618 */ 24619 PhoneLocale.prototype._normPhoneReg = function(region) { 24620 var norm; 24621 24622 // Map all NANP regions to the right region, so that they get parsed using the 24623 // correct state table 24624 switch (region) { 24625 case "US": // usa 24626 case "CA": // canada 24627 case "AG": // antigua and barbuda 24628 case "BS": // bahamas 24629 case "BB": // barbados 24630 case "DM": // dominica 24631 case "DO": // dominican republic 24632 case "GD": // grenada 24633 case "JM": // jamaica 24634 case "KN": // st. kitts and nevis 24635 case "LC": // st. lucia 24636 case "VC": // st. vincent and the grenadines 24637 case "TT": // trinidad and tobago 24638 case "AI": // anguilla 24639 case "BM": // bermuda 24640 case "VG": // british virgin islands 24641 case "KY": // cayman islands 24642 case "MS": // montserrat 24643 case "TC": // turks and caicos 24644 case "AS": // American Samoa 24645 case "VI": // Virgin Islands, U.S. 24646 case "PR": // Puerto Rico 24647 case "MP": // Northern Mariana Islands 24648 case "T:": // East Timor 24649 case "GU": // Guam 24650 norm = "US"; 24651 break; 24652 24653 // these all use the Italian dialling plan 24654 case "IT": // italy 24655 case "SM": // san marino 24656 case "VA": // vatican city 24657 norm = "IT"; 24658 break; 24659 24660 // all the French dependencies are on the French dialling plan 24661 case "FR": // france 24662 case "GF": // french guiana 24663 case "MQ": // martinique 24664 case "GP": // guadeloupe, 24665 case "BL": // saint barthélemy 24666 case "MF": // saint martin 24667 case "RE": // réunion, mayotte 24668 norm = "FR"; 24669 break; 24670 default: 24671 norm = region; 24672 break; 24673 } 24674 return norm; 24675 }; 24676 24677 24678 /*< PhoneHandlerFactory.js */ 24679 /* 24680 * handler.js - Handle phone number parse states 24681 * 24682 * Copyright © 2014-2015, JEDLSoft 24683 * 24684 * Licensed under the Apache License, Version 2.0 (the "License"); 24685 * you may not use this file except in compliance with the License. 24686 * You may obtain a copy of the License at 24687 * 24688 * http://www.apache.org/licenses/LICENSE-2.0 24689 * 24690 * Unless required by applicable law or agreed to in writing, software 24691 * distributed under the License is distributed on an "AS IS" BASIS, 24692 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24693 * 24694 * See the License for the specific language governing permissions and 24695 * limitations under the License. 24696 */ 24697 24698 24699 /** 24700 * @class 24701 * @private 24702 * @constructor 24703 */ 24704 var PhoneHandler = function () { 24705 return this; 24706 }; 24707 24708 PhoneHandler.prototype = { 24709 /** 24710 * @private 24711 * @param {string} number phone number 24712 * @param {Object} fields the fields that have been extracted so far 24713 * @param {Object} regionSettings settings used to parse the rest of the number 24714 */ 24715 processSubscriberNumber: function(number, fields, regionSettings) { 24716 var last; 24717 24718 last = number.search(/[xwtp,;]/i); // last digit of the local number 24719 24720 if (last > -1) { 24721 if (last > 0) { 24722 fields.subscriberNumber = number.substring(0, last); 24723 } 24724 // strip x's which are there to indicate a break between the local subscriber number and the extension, but 24725 // are not themselves a dialable character 24726 fields.extension = number.substring(last).replace('x', ''); 24727 } else { 24728 if (number.length) { 24729 fields.subscriberNumber = number; 24730 } 24731 } 24732 24733 if (regionSettings.plan.getFieldLength('maxLocalLength') && 24734 fields.subscriberNumber && 24735 fields.subscriberNumber.length > regionSettings.plan.getFieldLength('maxLocalLength')) { 24736 fields.invalid = true; 24737 } 24738 }, 24739 /** 24740 * @private 24741 * @param {string} fieldName 24742 * @param {number} length length of phone number 24743 * @param {string} number phone number 24744 * @param {number} currentChar currentChar to be parsed 24745 * @param {Object} fields the fields that have been extracted so far 24746 * @param {Object} regionSettings settings used to parse the rest of the number 24747 * @param {boolean} noExtractTrunk 24748 */ 24749 processFieldWithSubscriberNumber: function(fieldName, length, number, currentChar, fields, regionSettings, noExtractTrunk) { 24750 var ret, end; 24751 24752 if (length !== undefined && length > 0) { 24753 // fixed length 24754 end = length; 24755 if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { 24756 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 24757 } 24758 } else { 24759 // variable length 24760 // the setting is the negative of the length to add, so subtract to make it positive 24761 end = currentChar + 1 - length; 24762 } 24763 24764 if (fields[fieldName] !== undefined) { 24765 // we have a spurious recognition, because this number already contains that field! So, just put 24766 // everything into the subscriberNumber as the default 24767 this.processSubscriberNumber(number, fields, regionSettings); 24768 } else { 24769 fields[fieldName] = number.substring(0, end); 24770 if (number.length > end) { 24771 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 24772 } 24773 } 24774 24775 ret = { 24776 number: "" 24777 }; 24778 24779 return ret; 24780 }, 24781 /** 24782 * @private 24783 * @param {string} fieldName 24784 * @param {number} length length of phone number 24785 * @param {string} number phone number 24786 * @param {number} currentChar currentChar to be parsed 24787 * @param {Object} fields the fields that have been extracted so far 24788 * @param {Object} regionSettings settings used to parse the rest of the number 24789 */ 24790 processField: function(fieldName, length, number, currentChar, fields, regionSettings) { 24791 var ret = {}, end; 24792 24793 if (length !== undefined && length > 0) { 24794 // fixed length 24795 end = length; 24796 if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { 24797 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 24798 } 24799 } else { 24800 // variable length 24801 // the setting is the negative of the length to add, so subtract to make it positive 24802 end = currentChar + 1 - length; 24803 } 24804 24805 if (fields[fieldName] !== undefined) { 24806 // we have a spurious recognition, because this number already contains that field! So, just put 24807 // everything into the subscriberNumber as the default 24808 this.processSubscriberNumber(number, fields, regionSettings); 24809 ret.number = ""; 24810 } else { 24811 fields[fieldName] = number.substring(0, end); 24812 ret.number = (number.length > end) ? number.substring(end) : ""; 24813 } 24814 24815 return ret; 24816 }, 24817 /** 24818 * @private 24819 * @param {string} number phone number 24820 * @param {number} currentChar currentChar to be parsed 24821 * @param {Object} fields the fields that have been extracted so far 24822 * @param {Object} regionSettings settings used to parse the rest of the number 24823 */ 24824 trunk: function(number, currentChar, fields, regionSettings) { 24825 var ret, trunkLength; 24826 24827 if (fields.trunkAccess !== undefined) { 24828 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 24829 this.processSubscriberNumber(number, fields, regionSettings); 24830 number = ""; 24831 } else { 24832 trunkLength = regionSettings.plan.getTrunkCode().length; 24833 fields.trunkAccess = number.substring(0, trunkLength); 24834 number = (number.length > trunkLength) ? number.substring(trunkLength) : ""; 24835 } 24836 24837 ret = { 24838 number: number 24839 }; 24840 24841 return ret; 24842 }, 24843 /** 24844 * @private 24845 * @param {string} number phone number 24846 * @param {number} currentChar currentChar to be parsed 24847 * @param {Object} fields the fields that have been extracted so far 24848 * @param {Object} regionSettings settings used to parse the rest of the number 24849 */ 24850 plus: function(number, currentChar, fields, regionSettings) { 24851 var ret = {}; 24852 24853 if (fields.iddPrefix !== undefined) { 24854 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 24855 this.processSubscriberNumber(number, fields, regionSettings); 24856 ret.number = ""; 24857 } else { 24858 // found the idd prefix, so save it and cause the function to parse the next part 24859 // of the number with the idd table 24860 fields.iddPrefix = number.substring(0, 1); 24861 24862 ret = { 24863 number: number.substring(1), 24864 table: 'idd' // shared subtable that parses the country code 24865 }; 24866 } 24867 return ret; 24868 }, 24869 /** 24870 * @private 24871 * @param {string} number phone number 24872 * @param {number} currentChar currentChar to be parsed 24873 * @param {Object} fields the fields that have been extracted so far 24874 * @param {Object} regionSettings settings used to parse the rest of the number 24875 */ 24876 idd: function(number, currentChar, fields, regionSettings) { 24877 var ret = {}; 24878 24879 if (fields.iddPrefix !== undefined) { 24880 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 24881 this.processSubscriberNumber(number, fields, regionSettings); 24882 ret.number = ""; 24883 } else { 24884 // found the idd prefix, so save it and cause the function to parse the next part 24885 // of the number with the idd table 24886 fields.iddPrefix = number.substring(0, currentChar+1); 24887 24888 ret = { 24889 number: number.substring(currentChar+1), 24890 table: 'idd' // shared subtable that parses the country code 24891 }; 24892 } 24893 24894 return ret; 24895 }, 24896 /** 24897 * @private 24898 * @param {string} number phone number 24899 * @param {number} currentChar currentChar to be parsed 24900 * @param {Object} fields the fields that have been extracted so far 24901 * @param {Object} regionSettings settings used to parse the rest of the number 24902 */ 24903 country: function(number, currentChar, fields, regionSettings) { 24904 var ret, cc; 24905 24906 // found the country code of an IDD number, so save it and cause the function to 24907 // parse the rest of the number with the regular table for this locale 24908 fields.countryCode = number.substring(0, currentChar+1); 24909 cc = fields.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 24910 // console.log("Found country code " + fields.countryCode + ". Switching to country " + locale.region + " to parse the rest of the number"); 24911 24912 ret = { 24913 number: number.substring(currentChar+1), 24914 countryCode: cc 24915 }; 24916 24917 return ret; 24918 }, 24919 /** 24920 * @private 24921 * @param {string} number phone number 24922 * @param {number} currentChar currentChar to be parsed 24923 * @param {Object} fields the fields that have been extracted so far 24924 * @param {Object} regionSettings settings used to parse the rest of the number 24925 */ 24926 cic: function(number, currentChar, fields, regionSettings) { 24927 return this.processField('cic', regionSettings.plan.getFieldLength('cic'), number, currentChar, fields, regionSettings); 24928 }, 24929 /** 24930 * @private 24931 * @param {string} number phone number 24932 * @param {number} currentChar currentChar to be parsed 24933 * @param {Object} fields the fields that have been extracted so far 24934 * @param {Object} regionSettings settings used to parse the rest of the number 24935 */ 24936 service: function(number, currentChar, fields, regionSettings) { 24937 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('serviceCode'), number, currentChar, fields, regionSettings, false); 24938 }, 24939 /** 24940 * @private 24941 * @param {string} number phone number 24942 * @param {number} currentChar currentChar to be parsed 24943 * @param {Object} fields the fields that have been extracted so far 24944 * @param {Object} regionSettings settings used to parse the rest of the number 24945 */ 24946 area: function(number, currentChar, fields, regionSettings) { 24947 var ret, last, end, localLength; 24948 24949 last = number.search(/[xwtp]/i); // last digit of the local number 24950 localLength = (last > -1) ? last : number.length; 24951 24952 if (regionSettings.plan.getFieldLength('areaCode') > 0) { 24953 // fixed length 24954 end = regionSettings.plan.getFieldLength('areaCode'); 24955 if (regionSettings.plan.getTrunkCode() === number.charAt(0)) { 24956 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 24957 localLength -= regionSettings.plan.getTrunkCode().length; 24958 } 24959 } else { 24960 // variable length 24961 // the setting is the negative of the length to add, so subtract to make it positive 24962 end = currentChar + 1 - regionSettings.plan.getFieldLength('areaCode'); 24963 } 24964 24965 // substring() extracts the part of the string up to but not including the end character, 24966 // so add one to compensate 24967 if (regionSettings.plan.getFieldLength('maxLocalLength') !== undefined) { 24968 if (fields.trunkAccess !== undefined || fields.mobilePrefix !== undefined || 24969 fields.countryCode !== undefined || 24970 localLength > regionSettings.plan.getFieldLength('maxLocalLength')) { 24971 // too long for a local number by itself, or a different final state already parsed out the trunk 24972 // or mobile prefix, then consider the rest of this number to be an area code + part of the subscriber number 24973 fields.areaCode = number.substring(0, end); 24974 if (number.length > end) { 24975 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 24976 } 24977 } else { 24978 // shorter than the length needed for a local number, so just consider it a local number 24979 this.processSubscriberNumber(number, fields, regionSettings); 24980 } 24981 } else { 24982 fields.areaCode = number.substring(0, end); 24983 if (number.length > end) { 24984 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 24985 } 24986 } 24987 24988 // extensions are separated from the number by a dash in Germany 24989 if (regionSettings.plan.getFindExtensions() !== undefined && fields.subscriberNumber !== undefined) { 24990 var dash = fields.subscriberNumber.indexOf("-"); 24991 if (dash > -1) { 24992 fields.subscriberNumber = fields.subscriberNumber.substring(0, dash); 24993 fields.extension = fields.subscriberNumber.substring(dash+1); 24994 } 24995 } 24996 24997 ret = { 24998 number: "" 24999 }; 25000 25001 return ret; 25002 }, 25003 /** 25004 * @private 25005 * @param {string} number phone number 25006 * @param {number} currentChar currentChar to be parsed 25007 * @param {Object} fields the fields that have been extracted so far 25008 * @param {Object} regionSettings settings used to parse the rest of the number 25009 */ 25010 none: function(number, currentChar, fields, regionSettings) { 25011 var ret; 25012 25013 // this is a last resort function that is called when nothing is recognized. 25014 // When this happens, just put the whole stripped number into the subscriber number 25015 25016 if (number.length > 0) { 25017 this.processSubscriberNumber(number, fields, regionSettings); 25018 if (currentChar > 0 && currentChar < number.length) { 25019 // if we were part-way through parsing, and we hit an invalid digit, 25020 // indicate that the number could not be parsed properly 25021 fields.invalid = true; 25022 } 25023 } 25024 25025 ret = { 25026 number:"" 25027 }; 25028 25029 return ret; 25030 }, 25031 /** 25032 * @private 25033 * @param {string} number phone number 25034 * @param {number} currentChar currentChar to be parsed 25035 * @param {Object} fields the fields that have been extracted so far 25036 * @param {Object} regionSettings settings used to parse the rest of the number 25037 */ 25038 vsc: function(number, currentChar, fields, regionSettings) { 25039 var ret, length, end; 25040 25041 if (fields.vsc === undefined) { 25042 length = regionSettings.plan.getFieldLength('vsc') || 0; 25043 if (length !== undefined && length > 0) { 25044 // fixed length 25045 end = length; 25046 } else { 25047 // variable length 25048 // the setting is the negative of the length to add, so subtract to make it positive 25049 end = currentChar + 1 - length; 25050 } 25051 25052 // found a VSC code (ie. a "star code"), so save it and cause the function to 25053 // parse the rest of the number with the same table for this locale 25054 fields.vsc = number.substring(0, end); 25055 number = (number.length > end) ? "^" + number.substring(end) : ""; 25056 } else { 25057 // got it twice??? Okay, this is a bogus number then. Just put everything else into the subscriber number as the default 25058 this.processSubscriberNumber(number, fields, regionSettings); 25059 number = ""; 25060 } 25061 25062 // treat the rest of the number as if it were a completely new number 25063 ret = { 25064 number: number 25065 }; 25066 25067 return ret; 25068 }, 25069 /** 25070 * @private 25071 * @param {string} number phone number 25072 * @param {number} currentChar currentChar to be parsed 25073 * @param {Object} fields the fields that have been extracted so far 25074 * @param {Object} regionSettings settings used to parse the rest of the number 25075 */ 25076 cell: function(number, currentChar, fields, regionSettings) { 25077 return this.processFieldWithSubscriberNumber('mobilePrefix', regionSettings.plan.getFieldLength('mobilePrefix'), number, currentChar, fields, regionSettings, false); 25078 }, 25079 /** 25080 * @private 25081 * @param {string} number phone number 25082 * @param {number} currentChar currentChar to be parsed 25083 * @param {Object} fields the fields that have been extracted so far 25084 * @param {Object} regionSettings settings used to parse the rest of the number 25085 */ 25086 personal: function(number, currentChar, fields, regionSettings) { 25087 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('personal'), number, currentChar, fields, regionSettings, false); 25088 }, 25089 /** 25090 * @private 25091 * @param {string} number phone number 25092 * @param {number} currentChar currentChar to be parsed 25093 * @param {Object} fields the fields that have been extracted so far 25094 * @param {Object} regionSettings settings used to parse the rest of the number 25095 */ 25096 emergency: function(number, currentChar, fields, regionSettings) { 25097 return this.processFieldWithSubscriberNumber('emergency', regionSettings.plan.getFieldLength('emergency'), number, currentChar, fields, regionSettings, true); 25098 }, 25099 /** 25100 * @private 25101 * @param {string} number phone number 25102 * @param {number} currentChar currentChar to be parsed 25103 * @param {Object} fields the fields that have been extracted so far 25104 * @param {Object} regionSettings settings used to parse the rest of the number 25105 */ 25106 premium: function(number, currentChar, fields, regionSettings) { 25107 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('premium'), number, currentChar, fields, regionSettings, false); 25108 }, 25109 /** 25110 * @private 25111 * @param {string} number phone number 25112 * @param {number} currentChar currentChar to be parsed 25113 * @param {Object} fields the fields that have been extracted so far 25114 * @param {Object} regionSettings settings used to parse the rest of the number 25115 */ 25116 special: function(number, currentChar, fields, regionSettings) { 25117 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('special'), number, currentChar, fields, regionSettings, false); 25118 }, 25119 /** 25120 * @private 25121 * @param {string} number phone number 25122 * @param {number} currentChar currentChar to be parsed 25123 * @param {Object} fields the fields that have been extracted so far 25124 * @param {Object} regionSettings settings used to parse the rest of the number 25125 */ 25126 service2: function(number, currentChar, fields, regionSettings) { 25127 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service2'), number, currentChar, fields, regionSettings, false); 25128 }, 25129 /** 25130 * @private 25131 * @param {string} number phone number 25132 * @param {number} currentChar currentChar to be parsed 25133 * @param {Object} fields the fields that have been extracted so far 25134 * @param {Object} regionSettings settings used to parse the rest of the number 25135 */ 25136 service3: function(number, currentChar, fields, regionSettings) { 25137 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service3'), number, currentChar, fields, regionSettings, false); 25138 }, 25139 /** 25140 * @private 25141 * @param {string} number phone number 25142 * @param {number} currentChar currentChar to be parsed 25143 * @param {Object} fields the fields that have been extracted so far 25144 * @param {Object} regionSettings settings used to parse the rest of the number 25145 */ 25146 service4: function(number, currentChar, fields, regionSettings) { 25147 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service4'), number, currentChar, fields, regionSettings, false); 25148 }, 25149 /** 25150 * @private 25151 * @param {string} number phone number 25152 * @param {number} currentChar currentChar to be parsed 25153 * @param {Object} fields the fields that have been extracted so far 25154 * @param {Object} regionSettings settings used to parse the rest of the number 25155 */ 25156 cic2: function(number, currentChar, fields, regionSettings) { 25157 return this.processField('cic', regionSettings.plan.getFieldLength('cic2'), number, currentChar, fields, regionSettings); 25158 }, 25159 /** 25160 * @private 25161 * @param {string} number phone number 25162 * @param {number} currentChar currentChar to be parsed 25163 * @param {Object} fields the fields that have been extracted so far 25164 * @param {Object} regionSettings settings used to parse the rest of the number 25165 */ 25166 cic3: function(number, currentChar, fields, regionSettings) { 25167 return this.processField('cic', regionSettings.plan.getFieldLength('cic3'), number, currentChar, fields, regionSettings); 25168 }, 25169 /** 25170 * @private 25171 * @param {string} number phone number 25172 * @param {number} currentChar currentChar to be parsed 25173 * @param {Object} fields the fields that have been extracted so far 25174 * @param {Object} regionSettings settings used to parse the rest of the number 25175 */ 25176 start: function(number, currentChar, fields, regionSettings) { 25177 // don't do anything except transition to the next state 25178 return { 25179 number: number 25180 }; 25181 }, 25182 /** 25183 * @private 25184 * @param {string} number phone number 25185 * @param {number} currentChar currentChar to be parsed 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 local: function(number, currentChar, fields, regionSettings) { 25190 // in open dialling plans, we can tell that this number is a local subscriber number because it 25191 // starts with a digit that indicates as such 25192 this.processSubscriberNumber(number, fields, regionSettings); 25193 return { 25194 number: "" 25195 }; 25196 } 25197 }; 25198 25199 // context-sensitive handler 25200 /** 25201 * @class 25202 * @private 25203 * @extends PhoneHandler 25204 * @constructor 25205 */ 25206 var CSStateHandler = function () { 25207 return this; 25208 }; 25209 25210 CSStateHandler.prototype = new PhoneHandler(); 25211 CSStateHandler.prototype.special = function (number, currentChar, fields, regionSettings) { 25212 var ret; 25213 25214 // found a special area code that is both a node and a leaf. In 25215 // this state, we have found the leaf, so chop off the end 25216 // character to make it a leaf. 25217 if (number.charAt(0) === "0") { 25218 fields.trunkAccess = number.charAt(0); 25219 fields.areaCode = number.substring(1, currentChar); 25220 } else { 25221 fields.areaCode = number.substring(0, currentChar); 25222 } 25223 this.processSubscriberNumber(number.substring(currentChar), fields, regionSettings); 25224 25225 ret = { 25226 number: "" 25227 }; 25228 25229 return ret; 25230 }; 25231 25232 /** 25233 * @class 25234 * @private 25235 * @extends PhoneHandler 25236 * @constructor 25237 */ 25238 var USStateHandler = function () { 25239 return this; 25240 }; 25241 25242 USStateHandler.prototype = new PhoneHandler(); 25243 USStateHandler.prototype.vsc = function (number, currentChar, fields, regionSettings) { 25244 var ret; 25245 25246 // found a VSC code (ie. a "star code") 25247 fields.vsc = number; 25248 25249 // treat the rest of the number as if it were a completely new number 25250 ret = { 25251 number: "" 25252 }; 25253 25254 return ret; 25255 }; 25256 25257 /** 25258 * Creates a phone handler instance that is correct for the locale. Phone handlers are 25259 * used to handle parsing of the various fields in a phone number. 25260 * 25261 * @protected 25262 * @static 25263 * @return {PhoneHandler} the correct phone handler for the locale 25264 */ 25265 var PhoneHandlerFactory = function (locale, plan) { 25266 if (plan.getContextFree() !== undefined && typeof(plan.getContextFree()) === 'boolean' && plan.getContextFree() === false) { 25267 return new CSStateHandler(); 25268 } 25269 var region = (locale && locale.getRegion()) || "ZZ"; 25270 switch (region) { 25271 case 'US': 25272 return new USStateHandler(); 25273 break; 25274 default: 25275 return new PhoneHandler(); 25276 } 25277 }; 25278 25279 25280 /*< PhoneNumber.js */ 25281 /* 25282 * phonenum.js - Represent a phone number. 25283 * 25284 * Copyright © 2014-2015, JEDLSoft 25285 * 25286 * Licensed under the Apache License, Version 2.0 (the "License"); 25287 * you may not use this file except in compliance with the License. 25288 * You may obtain a copy of the License at 25289 * 25290 * http://www.apache.org/licenses/LICENSE-2.0 25291 * 25292 * Unless required by applicable law or agreed to in writing, software 25293 * distributed under the License is distributed on an "AS IS" BASIS, 25294 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25295 * 25296 * See the License for the specific language governing permissions and 25297 * limitations under the License. 25298 */ 25299 25300 /* 25301 !depends 25302 ilib.js 25303 NumberingPlan.js 25304 PhoneLocale.js 25305 PhoneHandlerFactory.js 25306 Utils.js 25307 JSUtils.js 25308 */ 25309 25310 // !data states idd mnc 25311 25312 25313 /** 25314 * @class 25315 * Create a new phone number instance that parses the phone number parameter for its 25316 * constituent parts, and store them as separate fields in the returned object. 25317 * 25318 * The options object may include any of these properties: 25319 * 25320 * <ul> 25321 * <li><i>locale</i> The locale with which to parse the number. This gives a clue as to which 25322 * numbering plan to use. 25323 * <li><i>mcc</i> The mobile carrier code (MCC) associated with the carrier that the phone is 25324 * currently connected to, if known. This also can give a clue as to which numbering plan to 25325 * use 25326 * <li>onLoad - a callback function to call when this instance is fully 25327 * loaded. When the onLoad option is given, this class will attempt to 25328 * load any missing locale data using the ilib loader callback. 25329 * When the constructor is done (even if the data is already preassembled), the 25330 * onLoad function is called with the current instance as a parameter, so this 25331 * callback can be used with preassembled or dynamic loading or a mix of the two. 25332 * <li>sync - tell whether to load any missing locale data synchronously or 25333 * asynchronously. If this option is given as "false", then the "onLoad" 25334 * callback must be given, as the instance returned from this constructor will 25335 * not be usable for a while. 25336 * <li><i>loadParams</i> - an object containing parameters to pass to the 25337 * loader callback function when locale data is missing. The parameters are not 25338 * interpretted or modified in any way. They are simply passed along. The object 25339 * may contain any property/value pairs as long as the calling code is in 25340 * agreement with the loader callback function as to what those parameters mean. 25341 * </ul> 25342 * 25343 * This function is locale-sensitive, and will assume any number passed to it is 25344 * appropriate for the given locale. If the MCC is given, this method will assume 25345 * that numbers without an explicit country code have been dialled within the country 25346 * given by the MCC. This affects how things like area codes are parsed. If the MCC 25347 * is not given, this method will use the given locale to determine the country 25348 * code. If the locale is not explicitly given either, then this function uses the 25349 * region of current locale as the default.<p> 25350 * 25351 * The input number may contain any formatting characters for the given locale. Each 25352 * field that is returned in the json object is a simple string of digits with 25353 * all formatting and whitespace characters removed.<p> 25354 * 25355 * The number is decomposed into its parts, regardless if the number 25356 * contains formatting characters. If a particular part cannot be extracted from given 25357 * number, the field will not be returned as a field in the object. If no fields can be 25358 * extracted from the number at all, then all digits found in the string will be 25359 * returned in the subscriberNumber field. If the number parameter contains no 25360 * digits, an empty object is returned.<p> 25361 * 25362 * This instance can contain any of the following fields after parsing is done: 25363 * 25364 * <ul> 25365 * <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 25366 * <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 25367 * <li>countryCode - if this number is an international direct dial number, this is the country code 25368 * <li>cic - for "dial-around" services (access to other carriers), this is the prefix used as the carrier identification code 25369 * <li>emergency - an emergency services number 25370 * <li>mobilePrefix - prefix that introduces a mobile phone number 25371 * <li>trunkAccess - trunk access code (long-distance access) 25372 * <li>serviceCode - like a geographic area code, but it is a required prefix for various services 25373 * <li>areaCode - geographic area codes 25374 * <li>subscriberNumber - the unique number of the person or company that pays for this phone line 25375 * <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. 25376 * <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 25377 * </ul> 25378 * 25379 * The following rules determine how the number is parsed: 25380 * 25381 * <ol> 25382 * <li>If the number starts with a character that is alphabetic instead of numeric, do 25383 * not parse the number at all. There is a good chance that it is not really a phone number. 25384 * In this case, an empty instance will be returned. 25385 * <li>If the phone number uses the plus notation or explicitly uses the international direct 25386 * dialing prefix for the given locale, then the country code is identified in 25387 * the number. The rules of given locale are used to parse the IDD prefix, and then the rules 25388 * of the country in the prefix are used to parse the rest of the number. 25389 * <li>If a country code is provided as an argument to the function call, use that country's 25390 * parsing rules for the number. This is intended for programs like a Contacts application that 25391 * know what the country is of the person that owns the phone number and can pass that on as 25392 * a hint. 25393 * <li>If the appropriate locale cannot be easily determined, default to using the rules 25394 * for the current user's region. 25395 * </ol> 25396 * 25397 * Example: parsing the number "+49 02101345345-78" will give the following properties in the 25398 * resulting phone number instance: 25399 * 25400 * <pre> 25401 * { 25402 * iddPrefix: "+", 25403 * countryCode: "49", 25404 * areaCode: "02101", 25405 * subscriberNumber: "345345", 25406 * extension: "78" 25407 * } 25408 * </pre> 25409 * 25410 * Note that in this example, because international direct dialing is explicitly used 25411 * in the number, the part of this number after the IDD prefix and country code will be 25412 * parsed exactly the same way in all locales with German rules (country code 49). 25413 * 25414 * Regions currently supported are: 25415 * 25416 * <ul> 25417 * <li>NANP (North American Numbering Plan) countries - USA, Canada, Bermuda, various Caribbean nations 25418 * <li>UK 25419 * <li>Republic of Ireland 25420 * <li>Germany 25421 * <li>France 25422 * <li>Spain 25423 * <li>Italy 25424 * <li>Mexico 25425 * <li>India 25426 * <li>People's Republic of China 25427 * <li>Netherlands 25428 * <li>Belgium 25429 * <li>Luxembourg 25430 * <li>Australia 25431 * <li>New Zealand 25432 * <li>Singapore 25433 * <li>Korea 25434 * <li>Japan 25435 * <li>Russia 25436 * <li>Brazil 25437 * </ul> 25438 * 25439 * @constructor 25440 * @param {!string|PhoneNumber} number A free-form phone number to be parsed, or another phone 25441 * number instance to copy 25442 * @param {Object=} options options that guide the parser in parsing the number 25443 */ 25444 var PhoneNumber = function(number, options) { 25445 var stateData, 25446 regionSettings; 25447 25448 this.sync = true; 25449 this.loadParams = {}; 25450 25451 if (!number || (typeof number === "string" && number.length === 0)) { 25452 return this; 25453 } 25454 25455 if (options) { 25456 if (typeof(options.sync) === 'boolean') { 25457 this.sync = options.sync; 25458 } 25459 25460 if (options.loadParams) { 25461 this.loadParams = options.loadParams; 25462 } 25463 25464 if (typeof(options.onLoad) === 'function') { 25465 /** 25466 * @private 25467 * @type {function(PhoneNumber)} 25468 */ 25469 this.onLoad = options.onLoad; 25470 } 25471 } 25472 25473 if (typeof number === "object") { 25474 /** 25475 * The vertical service code. These are codes that typically 25476 * start with a star or hash, like "*69" for "dial back the 25477 * last number that called me". 25478 * @type {string|undefined} 25479 */ 25480 this.vsc = number.vsc; 25481 25482 /** 25483 * The international direct dialing prefix. This is always 25484 * followed by the country code. 25485 * @type {string} 25486 */ 25487 this.iddPrefix = number.iddPrefix; 25488 25489 /** 25490 * The unique IDD country code for the country where the 25491 * phone number is serviced. 25492 * @type {string|undefined} 25493 */ 25494 this.countryCode = number.countryCode; 25495 25496 /** 25497 * The digits required to access the trunk. 25498 * @type {string|undefined} 25499 */ 25500 this.trunkAccess = number.trunkAccess; 25501 25502 /** 25503 * The carrier identification code used to identify 25504 * alternate long distance or international carriers. 25505 * @type {string|undefined} 25506 */ 25507 this.cic = number.cic; 25508 25509 /** 25510 * Identifies an emergency number that is typically 25511 * short, such as "911" in North America or "112" in 25512 * many other places in the world. 25513 * @type {string|undefined} 25514 */ 25515 this.emergency = number.emergency; 25516 25517 /** 25518 * The prefix of the subscriber number that indicates 25519 * that this is the number of a mobile phone. 25520 * @type {string|undefined} 25521 */ 25522 this.mobilePrefix = number.mobilePrefix; 25523 25524 /** 25525 * The prefix that identifies this number as commercial 25526 * service number. 25527 * @type {string|undefined} 25528 */ 25529 this.serviceCode = number.serviceCode; 25530 25531 /** 25532 * The area code prefix of a land line number. 25533 * @type {string|undefined} 25534 */ 25535 this.areaCode = number.areaCode; 25536 25537 /** 25538 * The unique number associated with the subscriber 25539 * of this phone. 25540 * @type {string|undefined} 25541 */ 25542 this.subscriberNumber = number.subscriberNumber; 25543 25544 /** 25545 * The direct dial extension number. 25546 * @type {string|undefined} 25547 */ 25548 this.extension = number.extension; 25549 25550 /** 25551 * @private 25552 * @type {boolean} 25553 */ 25554 this.invalid = number.invalid; 25555 25556 if (number.plan && number.locale) { 25557 /** 25558 * @private 25559 * @type {NumberingPlan} 25560 */ 25561 this.plan = number.plan; 25562 25563 /** 25564 * @private 25565 * @type {PhoneLocale} 25566 */ 25567 this.locale = number.locale; 25568 25569 /** 25570 * @private 25571 * @type {NumberingPlan} 25572 */ 25573 this.destinationPlan = number.destinationPlan; 25574 25575 /** 25576 * @private 25577 * @type {PhoneLocale} 25578 */ 25579 this.destinationLocale = number.destinationLocale; 25580 25581 if (options && typeof(options.onLoad) === 'function') { 25582 options.onLoad(this); 25583 } 25584 return; 25585 } 25586 } 25587 25588 new PhoneLocale({ 25589 locale: options && options.locale, 25590 mcc: options && options.mcc, 25591 sync: this.sync, 25592 loadParams: this.loadParams, 25593 onLoad: ilib.bind(this, function(loc) { 25594 this.locale = this.destinationLocale = loc; 25595 new NumberingPlan({ 25596 locale: this.locale, 25597 sync: this.sync, 25598 loadParms: this.loadParams, 25599 onLoad: ilib.bind(this, function (plan) { 25600 this.plan = this.destinationPlan = plan; 25601 25602 if (typeof number === "object") { 25603 // the copy constructor code above did not find the locale 25604 // or plan before, but now they are loaded, so we can return 25605 // already without going further 25606 return; 25607 } 25608 Utils.loadData({ 25609 name: "states.json", 25610 object: PhoneNumber, 25611 locale: this.locale, 25612 sync: this.sync, 25613 loadParams: JSUtils.merge(this.loadParams, { 25614 returnOne: true 25615 }), 25616 callback: ilib.bind(this, function (stdata) { 25617 if (!stdata) { 25618 stdata = PhoneNumber._defaultStates; 25619 } 25620 25621 stateData = stdata; 25622 25623 regionSettings = { 25624 stateData: stateData, 25625 plan: plan, 25626 handler: PhoneHandlerFactory(this.locale, plan) 25627 }; 25628 25629 // use ^ to indicate the beginning of the number, because certain things only match at the beginning 25630 number = "^" + number.replace(/\^/g, ''); 25631 number = PhoneNumber._stripFormatting(number); 25632 25633 this._parseNumber(number, regionSettings, options); 25634 }) 25635 }); 25636 }) 25637 }); 25638 }) 25639 }); 25640 }; 25641 25642 /** 25643 * Parse an International Mobile Subscriber Identity (IMSI) number into its 3 constituent parts: 25644 * 25645 * <ol> 25646 * <li>mcc - Mobile Country Code, which identifies the country where the phone is currently receiving 25647 * service. 25648 * <li>mnc - Mobile Network Code, which identifies the carrier which is currently providing service to the phone 25649 * <li>msin - Mobile Subscription Identifier Number. This is a unique number identifying the mobile phone on 25650 * the network, which usually maps to an account/subscriber in the carrier's database. 25651 * </ol> 25652 * 25653 * Because this function may need to load data to identify the above parts, you can pass an options 25654 * object that controls how the data is loaded. The options may contain any of the following properties: 25655 * 25656 * <ul> 25657 * <li>onLoad - a callback function to call when the parsing is done. When the onLoad option is given, 25658 * this method will attempt to load the locale data using the ilib loader callback. When it is done 25659 * (even if the data is already preassembled), the onLoad function is called with the parsing results 25660 * as a parameter, so this callback can be used with preassembled or dynamic, synchronous or 25661 * asynchronous loading or a mix of the above. 25662 * <li>sync - tell whether to load any missing locale data synchronously or asynchronously. If this 25663 * option is given as "false", then the "onLoad" callback must be given, as the results returned from 25664 * this constructor will not be usable for a while. 25665 * <li><i>loadParams</i> - an object containing parameters to pass to the loader callback function 25666 * when locale data is missing. The parameters are not interpretted or modified in any way. They are 25667 * simply passed along. The object may contain any property/value pairs as long as the calling code is in 25668 * agreement with the loader callback function as to what those parameters mean. 25669 * </ul> 25670 * 25671 * @static 25672 * @param {string} imsi IMSI number to parse 25673 * @param {Object} options options controlling the loading of the locale data 25674 * @return {{mcc:string,mnc:string,msin:string}|undefined} components of the IMSI number, when the locale data 25675 * is loaded synchronously, or undefined if asynchronous 25676 */ 25677 PhoneNumber.parseImsi = function(imsi, options) { 25678 var sync = true, 25679 loadParams = {}, 25680 fields = {}; 25681 25682 if (!imsi) { 25683 return undefined; 25684 } 25685 25686 if (options) { 25687 if (typeof(options.sync) !== 'undefined') { 25688 sync = (options.sync == true); 25689 } 25690 25691 if (options.loadParams) { 25692 loadParams = options.loadParams; 25693 } 25694 } 25695 25696 if (ilib.data.mnc) { 25697 fields = PhoneNumber._parseImsi(ilib.data.mnc, imsi); 25698 25699 if (options && typeof(options.onLoad) === 'function') { 25700 options.onLoad(fields); 25701 } 25702 } else { 25703 Utils.loadData({ 25704 name: "mnc.json", 25705 object: PhoneNumber, 25706 nonlocale: true, 25707 sync: sync, 25708 loadParams: loadParams, 25709 callback: ilib.bind(this, function(data) { 25710 ilib.data.mnc = data; 25711 fields = PhoneNumber._parseImsi(data, imsi); 25712 25713 if (options && typeof(options.onLoad) === 'function') { 25714 options.onLoad(fields); 25715 } 25716 }) 25717 }); 25718 } 25719 return fields; 25720 }; 25721 25722 25723 /** 25724 * @static 25725 * @protected 25726 */ 25727 PhoneNumber._parseImsi = function(data, imsi) { 25728 var ch, 25729 i, 25730 currentState, 25731 end, 25732 handlerMethod, 25733 state = 0, 25734 newState, 25735 fields = {}, 25736 lastLeaf, 25737 consumed = 0; 25738 25739 currentState = data; 25740 if (!currentState) { 25741 // can't parse anything 25742 return undefined; 25743 } 25744 25745 i = 0; 25746 while (i < imsi.length) { 25747 ch = PhoneNumber._getCharacterCode(imsi.charAt(i)); 25748 // console.info("parsing char " + imsi.charAt(i) + " code: " + ch); 25749 if (ch >= 0) { 25750 newState = currentState.s && currentState.s[ch]; 25751 25752 if (typeof(newState) === 'object') { 25753 if (typeof(newState.l) !== 'undefined') { 25754 // save for latter if needed 25755 lastLeaf = newState; 25756 consumed = i; 25757 } 25758 // console.info("recognized digit " + ch + " continuing..."); 25759 // recognized digit, so continue parsing 25760 currentState = newState; 25761 i++; 25762 } else { 25763 if ((typeof(newState) === 'undefined' || newState === 0 || 25764 (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && 25765 lastLeaf) { 25766 // this is possibly a look-ahead and it didn't work... 25767 // so fall back to the last leaf and use that as the 25768 // final state 25769 newState = lastLeaf; 25770 i = consumed; 25771 } 25772 25773 if ((typeof(newState) === 'number' && newState) || 25774 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 25775 // final state 25776 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 25777 handlerMethod = PhoneNumber._states[stateNumber]; 25778 25779 // console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); 25780 25781 // deal with syntactic ambiguity by using the "special" end state instead of "area" 25782 if ( handlerMethod === "area" ) { 25783 end = i+1; 25784 } else { 25785 // unrecognized imsi, so just assume the mnc is 3 digits 25786 end = 6; 25787 } 25788 25789 fields.mcc = imsi.substring(0,3); 25790 fields.mnc = imsi.substring(3,end); 25791 fields.msin = imsi.substring(end); 25792 25793 return fields; 25794 } else { 25795 // parse error 25796 if (imsi.length >= 6) { 25797 fields.mcc = imsi.substring(0,3); 25798 fields.mnc = imsi.substring(3,6); 25799 fields.msin = imsi.substring(6); 25800 } 25801 return fields; 25802 } 25803 } 25804 } else if (ch === -1) { 25805 // non-transition character, continue parsing in the same state 25806 i++; 25807 } else { 25808 // should not happen 25809 // console.info("skipping character " + ch); 25810 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 25811 i++; 25812 } 25813 } 25814 25815 if (i >= imsi.length && imsi.length >= 6) { 25816 // we reached the end of the imsi, but did not finish recognizing anything. 25817 // Default to last resort and assume 3 digit mnc 25818 fields.mcc = imsi.substring(0,3); 25819 fields.mnc = imsi.substring(3,6); 25820 fields.msin = imsi.substring(6); 25821 } else { 25822 // unknown or not enough characters for a real imsi 25823 fields = undefined; 25824 } 25825 25826 // console.info("Globalization.Phone.parseImsi: final result is: " + JSON.stringify(fields)); 25827 return fields; 25828 }; 25829 25830 /** 25831 * @static 25832 * @private 25833 */ 25834 PhoneNumber._stripFormatting = function(str) { 25835 var ret = ""; 25836 var i; 25837 25838 for (i = 0; i < str.length; i++) { 25839 if (PhoneNumber._getCharacterCode(str.charAt(i)) >= -1) { 25840 ret += str.charAt(i); 25841 } 25842 } 25843 return ret; 25844 }; 25845 25846 /** 25847 * @static 25848 * @protected 25849 */ 25850 PhoneNumber._getCharacterCode = function(ch) { 25851 if (ch >= '0' && ch <= '9') { 25852 return ch - '0'; 25853 } 25854 switch (ch) { 25855 case '+': 25856 return 10; 25857 case '*': 25858 return 11; 25859 case '#': 25860 return 12; 25861 case '^': 25862 return 13; 25863 case 'p': // pause chars 25864 case 'P': 25865 case 't': 25866 case 'T': 25867 case 'w': 25868 case 'W': 25869 return -1; 25870 case 'x': 25871 case 'X': 25872 case ',': 25873 case ';': // extension char 25874 return -1; 25875 } 25876 return -2; 25877 }; 25878 25879 /** 25880 * @private 25881 */ 25882 PhoneNumber._states = [ 25883 "none", 25884 "unknown", 25885 "plus", 25886 "idd", 25887 "cic", 25888 "service", 25889 "cell", 25890 "area", 25891 "vsc", 25892 "country", 25893 "personal", 25894 "special", 25895 "trunk", 25896 "premium", 25897 "emergency", 25898 "service2", 25899 "service3", 25900 "service4", 25901 "cic2", 25902 "cic3", 25903 "start", 25904 "local" 25905 ]; 25906 25907 /** 25908 * @private 25909 */ 25910 PhoneNumber._fieldOrder = [ 25911 "vsc", 25912 "iddPrefix", 25913 "countryCode", 25914 "trunkAccess", 25915 "cic", 25916 "emergency", 25917 "mobilePrefix", 25918 "serviceCode", 25919 "areaCode", 25920 "subscriberNumber", 25921 "extension" 25922 ]; 25923 25924 PhoneNumber._defaultStates = { 25925 "s": [ 25926 0, 25927 21, // 1 -> local 25928 21, // 2 -> local 25929 21, // 3 -> local 25930 21, // 4 -> local 25931 21, // 5 -> local 25932 21, // 6 -> local 25933 21, // 7 -> local 25934 21, // 8 -> local 25935 21, // 9 -> local 25936 0,0,0, 25937 { // ^ 25938 "s": [ 25939 { // ^0 25940 "s": [3], // ^00 -> idd 25941 "l": 12 // ^0 -> trunk 25942 }, 25943 21, // ^1 -> local 25944 21, // ^2 -> local 25945 21, // ^3 -> local 25946 21, // ^4 -> local 25947 21, // ^5 -> local 25948 21, // ^6 -> local 25949 21, // ^7 -> local 25950 21, // ^8 -> local 25951 21, // ^9 -> local 25952 2 // ^+ -> plus 25953 ] 25954 } 25955 ] 25956 }; 25957 25958 PhoneNumber.prototype = { 25959 /** 25960 * @protected 25961 * @param {string} number 25962 * @param {Object} regionData 25963 * @param {Object} options 25964 * @param {string} countryCode 25965 */ 25966 _parseOtherCountry: function(number, regionData, options, countryCode) { 25967 new PhoneLocale({ 25968 locale: this.locale, 25969 countryCode: countryCode, 25970 sync: this.sync, 25971 loadParms: this.loadParams, 25972 onLoad: ilib.bind(this, function (loc) { 25973 /* 25974 * this.locale is the locale where this number is being parsed, 25975 * and is used to parse the IDD prefix, if any, and this.destinationLocale is 25976 * the locale of the rest of this number after the IDD prefix. 25977 */ 25978 /** @type {PhoneLocale} */ 25979 this.destinationLocale = loc; 25980 25981 Utils.loadData({ 25982 name: "states.json", 25983 object: PhoneNumber, 25984 locale: this.destinationLocale, 25985 sync: this.sync, 25986 loadParams: JSUtils.merge(this.loadParams, { 25987 returnOne: true 25988 }), 25989 callback: ilib.bind(this, function (stateData) { 25990 if (!stateData) { 25991 stateData = PhoneNumber._defaultStates; 25992 } 25993 25994 new NumberingPlan({ 25995 locale: this.destinationLocale, 25996 sync: this.sync, 25997 loadParms: this.loadParams, 25998 onLoad: ilib.bind(this, function (plan) { 25999 /* 26000 * this.plan is the plan where this number is being parsed, 26001 * and is used to parse the IDD prefix, if any, and this.destinationPlan is 26002 * the plan of the rest of this number after the IDD prefix in the 26003 * destination locale. 26004 */ 26005 /** @type {NumberingPlan} */ 26006 this.destinationPlan = plan; 26007 26008 var regionSettings = { 26009 stateData: stateData, 26010 plan: plan, 26011 handler: PhoneHandlerFactory(this.destinationLocale, plan) 26012 }; 26013 26014 // for plans that do not skip the trunk code when dialing from 26015 // abroad, we need to treat the number from here on in as if it 26016 // were parsing a local number from scratch. That way, the parser 26017 // does not get confused between parts of the number at the 26018 // beginning of the number, and parts in the middle. 26019 if (!plan.getSkipTrunk()) { 26020 number = '^' + number; 26021 } 26022 26023 // recursively call the parser with the new states data 26024 // to finish the parsing 26025 this._parseNumber(number, regionSettings, options); 26026 }) 26027 }); 26028 }) 26029 }); 26030 }) 26031 }); 26032 }, 26033 26034 /** 26035 * @protected 26036 * @param {string} number 26037 * @param {Object} regionData 26038 * @param {Object} options 26039 */ 26040 _parseNumber: function(number, regionData, options) { 26041 var i, ch, 26042 regionSettings, 26043 newState, 26044 dot, 26045 handlerMethod, 26046 result, 26047 currentState = regionData.stateData, 26048 lastLeaf = undefined, 26049 consumed = 0; 26050 26051 regionSettings = regionData; 26052 dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java 26053 26054 i = 0; 26055 while (i < number.length) { 26056 ch = PhoneNumber._getCharacterCode(number.charAt(i)); 26057 if (ch >= 0) { 26058 // newState = stateData.states[state][ch]; 26059 newState = currentState.s && currentState.s[ch]; 26060 26061 if (!newState && currentState.s && currentState.s[dot]) { 26062 newState = currentState.s[dot]; 26063 } 26064 26065 if (typeof(newState) === 'object' && i+1 < number.length) { 26066 if (typeof(newState.l) !== 'undefined') { 26067 // this is a leaf node, so save that for later if needed 26068 lastLeaf = newState; 26069 consumed = i; 26070 } 26071 // console.info("recognized digit " + ch + " continuing..."); 26072 // recognized digit, so continue parsing 26073 currentState = newState; 26074 i++; 26075 } else { 26076 if ((typeof(newState) === 'undefined' || newState === 0 || 26077 (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && 26078 lastLeaf) { 26079 // this is possibly a look-ahead and it didn't work... 26080 // so fall back to the last leaf and use that as the 26081 // final state 26082 newState = lastLeaf; 26083 i = consumed; 26084 consumed = 0; 26085 lastLeaf = undefined; 26086 } 26087 26088 if ((typeof(newState) === 'number' && newState) || 26089 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 26090 // final state 26091 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 26092 handlerMethod = PhoneNumber._states[stateNumber]; 26093 26094 if (number.charAt(0) === '^') { 26095 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 26096 } else { 26097 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 26098 } 26099 26100 // reparse whatever is left 26101 number = result.number; 26102 i = consumed = 0; 26103 lastLeaf = undefined; 26104 26105 //console.log("reparsing with new number: " + number); 26106 currentState = regionSettings.stateData; 26107 // if the handler requested a special sub-table, use it for this round of parsing, 26108 // otherwise, set it back to the regular table to continue parsing 26109 26110 if (result.countryCode !== undefined) { 26111 this._parseOtherCountry(number, regionData, options, result.countryCode); 26112 // don't process any further -- let the work be done in the onLoad callbacks 26113 return; 26114 } else if (result.table !== undefined) { 26115 Utils.loadData({ 26116 name: result.table + ".json", 26117 object: PhoneNumber, 26118 nonlocale: true, 26119 sync: this.sync, 26120 loadParams: this.loadParams, 26121 callback: ilib.bind(this, function (data) { 26122 if (!data) { 26123 data = PhoneNumber._defaultStates; 26124 } 26125 26126 regionSettings = { 26127 stateData: data, 26128 plan: regionSettings.plan, 26129 handler: regionSettings.handler 26130 }; 26131 26132 // recursively call the parser with the new states data 26133 // to finish the parsing 26134 this._parseNumber(number, regionSettings, options); 26135 }) 26136 }); 26137 // don't process any further -- let the work be done in the onLoad callbacks 26138 return; 26139 } else if (result.skipTrunk !== undefined) { 26140 ch = PhoneNumber._getCharacterCode(regionSettings.plan.getTrunkCode()); 26141 currentState = currentState.s && currentState.s[ch]; 26142 } 26143 } else { 26144 handlerMethod = (typeof(newState) === 'number') ? "none" : "local"; 26145 // failed parse. Either no last leaf to fall back to, or there was an explicit 26146 // zero in the table. Put everything else in the subscriberNumber field as the 26147 // default place 26148 if (number.charAt(0) === '^') { 26149 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 26150 } else { 26151 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 26152 } 26153 break; 26154 } 26155 } 26156 } else if (ch === -1) { 26157 // non-transition character, continue parsing in the same state 26158 i++; 26159 } else { 26160 // should not happen 26161 // console.info("skipping character " + ch); 26162 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 26163 i++; 26164 } 26165 } 26166 if (i >= number.length && currentState !== regionData.stateData) { 26167 handlerMethod = (typeof(currentState.l) === 'undefined' || currentState === 0) ? "none" : "local"; 26168 // we reached the end of the phone number, but did not finish recognizing anything. 26169 // Default to last resort and put everything that is left into the subscriber number 26170 //console.log("Reached end of number before parsing was complete. Using handler for method none.") 26171 if (number.charAt(0) === '^') { 26172 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 26173 } else { 26174 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 26175 } 26176 } 26177 26178 // let the caller know we are done parsing 26179 if (this.onLoad) { 26180 this.onLoad(this); 26181 } 26182 }, 26183 /** 26184 * @protected 26185 */ 26186 _getPrefix: function() { 26187 return this.areaCode || this.serviceCode || this.mobilePrefix || ""; 26188 }, 26189 26190 /** 26191 * @protected 26192 */ 26193 _hasPrefix: function() { 26194 return (this._getPrefix() !== ""); 26195 }, 26196 26197 /** 26198 * Exclusive or -- return true, if one is defined and the other isn't 26199 * @protected 26200 */ 26201 _xor : function(left, right) { 26202 if ((left === undefined && right === undefined ) || (left !== undefined && right !== undefined)) { 26203 return false; 26204 } else { 26205 return true; 26206 } 26207 }, 26208 26209 /** 26210 * return a version of the phone number that contains only the dialable digits in the correct order 26211 * @protected 26212 */ 26213 _join: function () { 26214 var fieldName, formatted = ""; 26215 26216 try { 26217 for (var field in PhoneNumber._fieldOrder) { 26218 if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string') { 26219 fieldName = PhoneNumber._fieldOrder[field]; 26220 // console.info("normalize: formatting field " + fieldName); 26221 if (this[fieldName] !== undefined) { 26222 formatted += this[fieldName]; 26223 } 26224 } 26225 } 26226 } catch ( e ) { 26227 //console.warn("caught exception in _join: " + e); 26228 throw e; 26229 } 26230 return formatted; 26231 }, 26232 26233 /** 26234 * This routine will compare the two phone numbers in an locale-sensitive 26235 * manner to see if they possibly reference the same phone number.<p> 26236 * 26237 * In many places, 26238 * there are multiple ways to reach the same phone number. In North America for 26239 * example, you might have a number with the trunk access code of "1" and another 26240 * without, and they reference the exact same phone number. This is considered a 26241 * strong match. For a different pair of numbers, one may be a local number and 26242 * the other a full phone number with area code, which may reference the same 26243 * phone number if the local number happens to be located in that area code. 26244 * However, you cannot say for sure if it is in that area code, so it will 26245 * be considered a somewhat weaker match.<p> 26246 * 26247 * Similarly, in other countries, there are sometimes different ways of 26248 * reaching the same destination, and the way that numbers 26249 * match depends on the locale.<p> 26250 * 26251 * The various phone number fields are handled differently for matches. There 26252 * are various fields that do not need to match at all. For example, you may 26253 * type equally enter "00" or "+" into your phone to start international direct 26254 * dialling, so the iddPrefix field does not need to match at all.<p> 26255 * 26256 * Typically, fields that require matches need to match exactly if both sides have a value 26257 * for that field. If both sides specify a value and those values differ, that is 26258 * a strong non-match. If one side does not have a value and the other does, that 26259 * causes a partial match, because the number with the missing field may possibly 26260 * have an implied value that matches the other number. For example, the numbers 26261 * "650-555-1234" and "555-1234" have a partial match as the local number "555-1234" 26262 * might possibly have the same 650 area code as the first number, and might possibly 26263 * not. If both side do not specify a value for a particular field, that field is 26264 * considered matching.<p> 26265 * 26266 * The values of following fields are ignored when performing matches: 26267 * 26268 * <ul> 26269 * <li>vsc 26270 * <li>iddPrefix 26271 * <li>cic 26272 * <li>trunkAccess 26273 * </ul> 26274 * 26275 * The values of the following fields matter if they do not match: 26276 * 26277 * <ul> 26278 * <li>countryCode - A difference causes a moderately strong problem except for 26279 * certain countries where there is a way to access the same subscriber via IDD 26280 * and via intranetwork dialling 26281 * <li>mobilePrefix - A difference causes a possible non-match 26282 * <li>serviceCode - A difference causes a possible non-match 26283 * <li>areaCode - A difference causes a possible non-match 26284 * <li>subscriberNumber - A difference causes a very strong non-match 26285 * <li>extension - A difference causes a minor non-match 26286 * </ul> 26287 * 26288 * @param {string|PhoneNumber} other other phone number to compare this one to 26289 * @return {number} non-negative integer describing the percentage quality of the 26290 * match. 100 means a very strong match (100%), and lower numbers are less and 26291 * less strong, down to 0 meaning not at all a match. 26292 */ 26293 compare: function (other) { 26294 var match = 100, 26295 FRdepartments = {"590":1, "594":1, "596":1, "262":1}, 26296 ITcountries = {"378":1, "379":1}, 26297 thisPrefix, 26298 otherPrefix, 26299 currentCountryCode = 0; 26300 26301 if (typeof this.locale.region === "string") { 26302 currentCountryCode = this.locale._mapRegiontoCC(this.locale.region); 26303 } 26304 26305 // subscriber number must be present and must match 26306 if (!this.subscriberNumber || !other.subscriberNumber || this.subscriberNumber !== other.subscriberNumber) { 26307 return 0; 26308 } 26309 26310 // extension must match if it is present 26311 if (this._xor(this.extension, other.extension) || this.extension !== other.extension) { 26312 return 0; 26313 } 26314 26315 if (this._xor(this.countryCode, other.countryCode)) { 26316 // if one doesn't have a country code, give it some demerit points, but if the 26317 // one that has the country code has something other than the current country 26318 // add even more. Ignore the special cases where you can dial the same number internationally or via 26319 // the local numbering system 26320 switch (this.locale.getRegion()) { 26321 case 'FR': 26322 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { 26323 if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { 26324 match -= 100; 26325 } 26326 } else { 26327 match -= 16; 26328 } 26329 break; 26330 case 'IT': 26331 if (this.countryCode in ITcountries || other.countryCode in ITcountries) { 26332 if (this.areaCode !== other.areaCode) { 26333 match -= 100; 26334 } 26335 } else { 26336 match -= 16; 26337 } 26338 break; 26339 default: 26340 match -= 16; 26341 if ((this.countryCode !== undefined && this.countryCode !== currentCountryCode) || 26342 (other.countryCode !== undefined && other.countryCode !== currentCountryCode)) { 26343 match -= 16; 26344 } 26345 } 26346 } else if (this.countryCode !== other.countryCode) { 26347 // ignore the special cases where you can dial the same number internationally or via 26348 // the local numbering system 26349 if (other.countryCode === '33' || this.countryCode === '33') { 26350 // france 26351 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { 26352 if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { 26353 match -= 100; 26354 } 26355 } else { 26356 match -= 100; 26357 } 26358 } else if (this.countryCode === '39' || other.countryCode === '39') { 26359 // italy 26360 if (this.countryCode in ITcountries || other.countryCode in ITcountries) { 26361 if (this.areaCode !== other.areaCode) { 26362 match -= 100; 26363 } 26364 } else { 26365 match -= 100; 26366 } 26367 } else { 26368 match -= 100; 26369 } 26370 } 26371 26372 if (this._xor(this.serviceCode, other.serviceCode)) { 26373 match -= 20; 26374 } else if (this.serviceCode !== other.serviceCode) { 26375 match -= 100; 26376 } 26377 26378 if (this._xor(this.mobilePrefix, other.mobilePrefix)) { 26379 match -= 20; 26380 } else if (this.mobilePrefix !== other.mobilePrefix) { 26381 match -= 100; 26382 } 26383 26384 if (this._xor(this.areaCode, other.areaCode)) { 26385 // one has an area code, the other doesn't, so dock some points. It could be a match if the local 26386 // number in the one number has the same implied area code as the explicit area code in the other number. 26387 match -= 12; 26388 } else if (this.areaCode !== other.areaCode) { 26389 match -= 100; 26390 } 26391 26392 thisPrefix = this._getPrefix(); 26393 otherPrefix = other._getPrefix(); 26394 26395 if (thisPrefix && otherPrefix && thisPrefix !== otherPrefix) { 26396 match -= 100; 26397 } 26398 26399 // make sure we are between 0 and 100 26400 if (match < 0) { 26401 match = 0; 26402 } else if (match > 100) { 26403 match = 100; 26404 } 26405 26406 return match; 26407 }, 26408 26409 /** 26410 * Determine whether or not the other phone number is exactly equal to the current one.<p> 26411 * 26412 * The difference between the compare method and the equals method is that the compare 26413 * method compares normalized numbers with each other and returns the degree of match, 26414 * whereas the equals operator returns true iff the two numbers contain the same fields 26415 * and the fields are exactly the same. Functions and other non-phone number properties 26416 * are not compared. 26417 * @param {string|PhoneNumber} other another phone number to compare to this one 26418 * @return {boolean} true if the numbers are the same, false otherwise 26419 */ 26420 equals: function equals(other) { 26421 if (other.locale && this.locale && !this.locale.equals(other.locale) && (!this.countryCode || !other.countryCode)) { 26422 return false; 26423 } 26424 26425 for (var p in other) { 26426 if (p !== undefined && this[p] !== undefined && typeof(this[p]) !== 'object') { 26427 if (other[p] === undefined) { 26428 /*console.error("PhoneNumber.equals: other is missing property " + p + " which has the value " + this[p] + " in this"); 26429 console.error("this is : " + JSON.stringify(this)); 26430 console.error("other is: " + JSON.stringify(other));*/ 26431 return false; 26432 } 26433 if (this[p] !== other[p]) { 26434 /*console.error("PhoneNumber.equals: difference in property " + p); 26435 console.error("this is : " + JSON.stringify(this)); 26436 console.error("other is: " + JSON.stringify(other));*/ 26437 return false; 26438 } 26439 } 26440 } 26441 for (var p in other) { 26442 if (p !== undefined && other[p] !== undefined && typeof(other[p]) !== 'object') { 26443 if (this[p] === undefined) { 26444 /*console.error("PhoneNumber.equals: this is missing property " + p + " which has the value " + other[p] + " in the other"); 26445 console.error("this is : " + JSON.stringify(this)); 26446 console.error("other is: " + JSON.stringify(other));*/ 26447 return false; 26448 } 26449 if (this[p] !== other[p]) { 26450 /*console.error("PhoneNumber.equals: difference in property " + p); 26451 console.error("this is : " + JSON.stringify(this)); 26452 console.error("other is: " + JSON.stringify(other));*/ 26453 return false; 26454 } 26455 } 26456 } 26457 return true; 26458 }, 26459 26460 26461 /** 26462 * @private 26463 * @param {{ 26464 * mcc:string, 26465 * defaultAreaCode:string, 26466 * country:string, 26467 * networkType:string, 26468 * assistedDialing:boolean, 26469 * sms:boolean, 26470 * manualDialing:boolean 26471 * }} options an object containing options to help in normalizing. 26472 * @param {PhoneNumber} norm 26473 * @param {PhoneLocale} homeLocale 26474 * @param {PhoneLocale} currentLocale 26475 * @param {NumberingPlan} currentPlan 26476 * @param {PhoneLocale} destinationLocale 26477 * @param {NumberingPlan} destinationPlan 26478 * @param {boolean} sync 26479 * @param {Object|undefined} loadParams 26480 */ 26481 _doNormalize: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams) { 26482 var formatted = ""; 26483 26484 if (!norm.invalid && options && options.assistedDialing) { 26485 // don't normalize things that don't have subscriber numbers. Also, don't normalize 26486 // manually dialed local numbers. Do normalize local numbers in contact entries. 26487 if (norm.subscriberNumber && 26488 (!options.manualDialing || 26489 norm.iddPrefix || 26490 norm.countryCode || 26491 norm.trunkAccess)) { 26492 // console.log("normalize: assisted dialling normalization of " + JSON.stringify(norm)); 26493 if (currentLocale.getRegion() !== destinationLocale.getRegion()) { 26494 // we are currently calling internationally 26495 if (!norm._hasPrefix() && 26496 options.defaultAreaCode && 26497 destinationLocale.getRegion() === homeLocale.getRegion() && 26498 (!destinationPlan.getFieldLength("minLocalLength") || 26499 norm.subscriberNumber.length >= destinationPlan.getFieldLength("minLocalLength"))) { 26500 // area code is required when dialling from international, but only add it if we are dialing 26501 // to our home area. Otherwise, the default area code is not valid! 26502 norm.areaCode = options.defaultAreaCode; 26503 if (!destinationPlan.getSkipTrunk() && destinationPlan.getTrunkCode()) { 26504 // some phone systems require the trunk access code, even when dialling from international 26505 norm.trunkAccess = destinationPlan.getTrunkCode(); 26506 } 26507 } 26508 26509 if (norm.trunkAccess && destinationPlan.getSkipTrunk()) { 26510 // on some phone systems, the trunk access code is dropped when dialling from international 26511 delete norm.trunkAccess; 26512 } 26513 26514 // make sure to get the country code for the destination region, not the current region! 26515 if (options.sms) { 26516 if (homeLocale.getRegion() === "US" && currentLocale.getRegion() !== "US") { 26517 if (destinationLocale.getRegion() !== "US") { 26518 norm.iddPrefix = "011"; // non-standard code to make it go through the US first 26519 norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.getRegion()); 26520 } else if (options.networkType === "cdma") { 26521 delete norm.iddPrefix; 26522 delete norm.countryCode; 26523 if (norm.areaCode) { 26524 norm.trunkAccess = "1"; 26525 } 26526 } else if (norm.areaCode) { 26527 norm.iddPrefix = "+"; 26528 norm.countryCode = "1"; 26529 delete norm.trunkAccess; 26530 } 26531 } else { 26532 norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; 26533 norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.region); 26534 } 26535 } else if (norm._hasPrefix() && !norm.countryCode) { 26536 norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.region); 26537 } 26538 26539 if (norm.countryCode && !options.sms) { 26540 // for CDMA, make sure to get the international dialling access code for the current region, not the destination region 26541 // all umts carriers support plus dialing 26542 norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; 26543 } 26544 } else { 26545 // console.log("normalize: dialing within the country"); 26546 if (options.defaultAreaCode) { 26547 if (destinationPlan.getPlanStyle() === "open") { 26548 if (!norm.trunkAccess && norm._hasPrefix() && destinationPlan.getTrunkCode()) { 26549 // call is not local to this area code, so you have to dial the trunk code and the area code 26550 norm.trunkAccess = destinationPlan.getTrunkCode(); 26551 } 26552 } else { 26553 // In closed plans, you always have to dial the area code, even if the call is local. 26554 if (!norm._hasPrefix()) { 26555 if (destinationLocale.getRegion() === homeLocale.getRegion()) { 26556 norm.areaCode = options.defaultAreaCode; 26557 if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { 26558 norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); 26559 } 26560 } 26561 } else { 26562 if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { 26563 norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); 26564 } 26565 } 26566 } 26567 } 26568 26569 if (options.sms && 26570 homeLocale.getRegion() === "US" && 26571 currentLocale.getRegion() !== "US") { 26572 norm.iddPrefix = "011"; // make it go through the US first 26573 if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { 26574 delete norm.trunkAccess; 26575 } 26576 } else if (norm.iddPrefix || norm.countryCode) { 26577 // we are in our destination country, so strip the international dialling prefixes 26578 delete norm.iddPrefix; 26579 delete norm.countryCode; 26580 26581 if ((destinationPlan.getPlanStyle() === "open" || destinationPlan.getTrunkRequired()) && destinationPlan.getTrunkCode()) { 26582 norm.trunkAccess = destinationPlan.getTrunkCode(); 26583 } 26584 } 26585 } 26586 } 26587 } else if (!norm.invalid) { 26588 // console.log("normalize: non-assisted normalization"); 26589 if (!norm._hasPrefix() && options && options.defaultAreaCode && destinationLocale.getRegion() === homeLocale.region) { 26590 norm.areaCode = options.defaultAreaCode; 26591 } 26592 26593 if (!norm.countryCode && norm._hasPrefix()) { 26594 norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.getRegion()); 26595 } 26596 26597 if (norm.countryCode) { 26598 if (options && options.networkType && options.networkType === "cdma") { 26599 norm.iddPrefix = currentPlan.getIDDCode(); 26600 } else { 26601 // all umts carriers support plus dialing 26602 norm.iddPrefix = "+"; 26603 } 26604 26605 if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { 26606 delete norm.trunkAccess; 26607 } else if (!destinationPlan.getSkipTrunk() && !norm.trunkAccess && destinationPlan.getTrunkCode()) { 26608 norm.trunkAccess = destinationPlan.getTrunkCode(); 26609 } 26610 } 26611 } 26612 26613 // console.info("normalize: after normalization, the normalized phone number is: " + JSON.stringify(norm)); 26614 formatted = norm._join(); 26615 26616 return formatted; 26617 }, 26618 26619 /** 26620 * @private 26621 * @param {{ 26622 * mcc:string, 26623 * defaultAreaCode:string, 26624 * country:string, 26625 * networkType:string, 26626 * assistedDialing:boolean, 26627 * sms:boolean, 26628 * manualDialing:boolean 26629 * }} options an object containing options to help in normalizing. 26630 * @param {PhoneNumber} norm 26631 * @param {PhoneLocale} homeLocale 26632 * @param {PhoneLocale} currentLocale 26633 * @param {NumberingPlan} currentPlan 26634 * @param {PhoneLocale} destinationLocale 26635 * @param {NumberingPlan} destinationPlan 26636 * @param {boolean} sync 26637 * @param {Object|undefined} loadParams 26638 * @param {function(string)} callback 26639 */ 26640 _doReparse: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams, callback) { 26641 var formatted, 26642 tempRegion; 26643 26644 if (options && 26645 options.assistedDialing && 26646 !norm.trunkAccess && 26647 !norm.iddPrefix && 26648 norm.subscriberNumber && 26649 norm.subscriberNumber.length > destinationPlan.getFieldLength("maxLocalLength")) { 26650 26651 // numbers that are too long are sometimes international direct dialed numbers that 26652 // are missing the IDD prefix. So, try reparsing it using a plus in front to see if that works. 26653 new PhoneNumber("+" + this._join(), { 26654 locale: this.locale, 26655 sync: sync, 26656 loadParms: loadParams, 26657 onLoad: ilib.bind(this, function (data) { 26658 tempRegion = (data.countryCode && data.locale._mapCCtoRegion(data.countryCode)); 26659 26660 if (tempRegion && tempRegion !== "unknown" && tempRegion !== "SG") { 26661 // only use it if it is a recognized country code. Singapore (SG) is a special case. 26662 norm = data; 26663 destinationLocale = data.destinationLocale; 26664 destinationPlan = data.destinationPlan; 26665 } 26666 26667 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 26668 if (typeof(callback) === 'function') { 26669 callback(formatted); 26670 } 26671 }) 26672 }); 26673 } else if (options && options.assistedDialing && norm.invalid && currentLocale.region !== norm.locale.region) { 26674 // if this number is not valid for the locale it was parsed with, try again with the current locale 26675 // console.log("norm is invalid. Attempting to reparse with the current locale"); 26676 26677 new PhoneNumber(this._join(), { 26678 locale: currentLocale, 26679 sync: sync, 26680 loadParms: loadParams, 26681 onLoad: ilib.bind(this, function (data) { 26682 if (data && !data.invalid) { 26683 norm = data; 26684 } 26685 26686 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 26687 if (typeof(callback) === 'function') { 26688 callback(formatted); 26689 } 26690 }) 26691 }); 26692 } else { 26693 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 26694 if (typeof(callback) === 'function') { 26695 callback(formatted); 26696 } 26697 } 26698 }, 26699 26700 /** 26701 * This function normalizes the current phone number to a canonical format and returns a 26702 * string with that phone number. If parts are missing, this function attempts to fill in 26703 * those parts.<p> 26704 * 26705 * The options object contains a set of properties that can possibly help normalize 26706 * this number by providing "extra" information to the algorithm. The options 26707 * parameter may be null or an empty object if no hints can be determined before 26708 * this call is made. If any particular hint is not 26709 * available, it does not need to be present in the options object.<p> 26710 * 26711 * The following is a list of hints that the algorithm will look for in the options 26712 * object: 26713 * 26714 * <ul> 26715 * <li><i>mcc</i> the mobile carrier code of the current network upon which this 26716 * phone is operating. This is translated into an IDD country code. This is 26717 * useful if the number being normalized comes from CNAP (callerid) and the 26718 * MCC is known. 26719 * <li><i>defaultAreaCode</i> the area code of the phone number of the current 26720 * device, if available. Local numbers in a person's contact list are most 26721 * probably in this same area code. 26722 * <li><i>country</i> the 2 letter ISO 3166 code of the country if it is 26723 * known from some other means such as parsing the physical address of the 26724 * person associated with the phone number, or the from the domain name 26725 * of the person's email address 26726 * <li><i>networkType</i> specifies whether the phone is currently connected to a 26727 * CDMA network or a UMTS network. Valid values are the strings "cdma" and "umts". 26728 * If one of those two strings are not specified, or if this property is left off 26729 * completely, this method will assume UMTS. 26730 * </ul> 26731 * 26732 * The following are a list of options that control the behaviour of the normalization: 26733 * 26734 * <ul> 26735 * <li><i>assistedDialing</i> if this is set to true, the number will be normalized 26736 * so that it can dialled directly on the type of network this phone is 26737 * currently connected to. This allows customers to dial numbers or use numbers 26738 * in their contact list that are specific to their "home" region when they are 26739 * roaming and those numbers would not otherwise work with the current roaming 26740 * carrier as they are. The home region is 26741 * specified as the phoneRegion system preference that is settable in the 26742 * regional settings app. With assisted dialling, this method will add or 26743 * remove international direct dialling prefixes and country codes, as well as 26744 * national trunk access codes, as required by the current roaming carrier and the 26745 * home region in order to dial the number properly. If it is not possible to 26746 * construct a full international dialling sequence from the options and hints given, 26747 * this function will not modify the phone number, and will return "undefined". 26748 * If assisted dialling is false or not specified, then this method will attempt 26749 * to add all the information it can to the number so that it is as fully 26750 * specified as possible. This allows two numbers to be compared more easily when 26751 * those two numbers were otherwise only partially specified. 26752 * <li><i>sms</i> set this option to true for the following conditions: 26753 * <ul> 26754 * <li>assisted dialing is turned on 26755 * <li>the phone number represents the destination of an SMS message 26756 * <li>the phone is UMTS 26757 * <li>the phone is SIM-locked to its carrier 26758 * </ul> 26759 * This enables special international direct dialling codes to route the SMS message to 26760 * the correct carrier. If assisted dialling is not turned on, this option has no 26761 * affect. 26762 * <li><i>manualDialing</i> set this option to true if the user is entering this number on 26763 * the keypad directly, and false when the number comes from a stored location like a 26764 * contact entry or a call log entry. When true, this option causes the normalizer to 26765 * not perform any normalization on numbers that look like local numbers in the home 26766 * country. If false, all numbers go through normalization. This option only has an effect 26767 * when the assistedDialing option is true as well, otherwise it is ignored. 26768 * </ul> 26769 * 26770 * If both a set of options and a locale are given, and they offer conflicting 26771 * information, the options will take precedence. The idea is that the locale 26772 * tells you the region setting that the user has chosen (probably in 26773 * firstuse), whereas the the hints are more current information such as 26774 * where the phone is currently operating (the MCC).<p> 26775 * 26776 * This function performs the following types of normalizations with assisted 26777 * dialling turned on: 26778 * 26779 * <ol> 26780 * <li>If the current location of the phone matches the home country, this is a 26781 * domestic call. 26782 * <ul> 26783 * <li>Remove any iddPrefix and countryCode fields, as they are not needed 26784 * <li>Add in a trunkAccess field that may be necessary to call a domestic numbers 26785 * in the home country 26786 * </ul> 26787 * <li> If the current location of the phone does not match the home country, 26788 * attempt to form a whole international number. 26789 * <ul> 26790 * <li>Add in the area code if it is missing from the phone number and the area code 26791 * of the current phone is available in the hints 26792 * <li>Add the country dialling code for the home country if it is missing from the 26793 * phone number 26794 * <li>Add or replace the iddPrefix with the correct one for the current country. The 26795 * phone number will have been parsed with the settings for the home country, so 26796 * the iddPrefix may be incorrect for the 26797 * current country. The iddPrefix for the current country can be "+" if the phone 26798 * is connected to a UMTS network, and either a "+" or a country-dependent 26799 * sequences of digits for CDMA networks. 26800 * </ul> 26801 * </ol> 26802 * 26803 * This function performs the following types of normalization with assisted 26804 * dialling turned off: 26805 * 26806 * <ul> 26807 * <li>Normalize the international direct dialing prefix to be a plus or the 26808 * international direct dialling access code for the current country, depending 26809 * on the network type. 26810 * <li>If a number is a local number (ie. it is missing its area code), 26811 * use a default area code from the hints if available. CDMA phones always know their area 26812 * code, and GSM/UMTS phones know their area code in many instances, but not always 26813 * (ie. not on Vodaphone or Telcel phones). If the default area code is not available, 26814 * do not add it. 26815 * <li>In assisted dialling mode, if a number is missing its country code, 26816 * use the current MCC number if 26817 * it is available to figure out the current country code, and prepend that 26818 * to the number. If it is not available, leave it off. Also, use that 26819 * country's settings to parse the number instead of the current format 26820 * locale. 26821 * <li>For North American numbers with an area code but no trunk access 26822 * code, add in the trunk access code. 26823 * <li>For other countries, if the country code is added in step 3, remove the 26824 * trunk access code when required by that country's conventions for 26825 * international calls. If the country requires a trunk access code for 26826 * international calls and it doesn't exist, add one. 26827 * </ul> 26828 * 26829 * This method modifies the current object, and also returns a string 26830 * containing the normalized phone number that can be compared directly against 26831 * other normalized numbers. The canonical format for phone numbers that is 26832 * returned from thhomeLocaleis method is simply an uninterrupted and unformatted string 26833 * of dialable digits. 26834 * 26835 * @param {{ 26836 * mcc:string, 26837 * defaultAreaCode:string, 26838 * country:string, 26839 * networkType:string, 26840 * assistedDialing:boolean, 26841 * sms:boolean, 26842 * manualDialing:boolean 26843 * }} options an object containing options to help in normalizing. 26844 * @return {string|undefined} the normalized string, or undefined if the number 26845 * could not be normalized 26846 */ 26847 normalize: function(options) { 26848 var norm, 26849 sync = true, 26850 loadParams = {}; 26851 26852 26853 if (options) { 26854 if (typeof(options.sync) !== 'undefined') { 26855 sync = (options.sync == true); 26856 } 26857 26858 if (options.loadParams) { 26859 loadParams = options.loadParams; 26860 } 26861 } 26862 26863 // Clone this number, so we don't mess with the original. 26864 // No need to do this asynchronously because it's a copy constructor which doesn't 26865 // load any extra files. 26866 norm = new PhoneNumber(this); 26867 26868 var normalized; 26869 26870 if (options && (typeof(options.mcc) !== 'undefined' || typeof(options.country) !== 'undefined')) { 26871 new PhoneLocale({ 26872 mcc: options.mcc, 26873 countryCode: options.countryCode, 26874 locale: this.locale, 26875 sync: sync, 26876 loadParams: loadParams, 26877 onLoad: ilib.bind(this, function(loc) { 26878 new NumberingPlan({ 26879 locale: loc, 26880 sync: sync, 26881 loadParms: loadParams, 26882 onLoad: ilib.bind(this, function (plan) { 26883 this._doReparse(options, norm, this.locale, loc, plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { 26884 normalized = fmt; 26885 26886 if (options && typeof(options.onLoad) === 'function') { 26887 options.onLoad(fmt); 26888 } 26889 }); 26890 }) 26891 }); 26892 }) 26893 }); 26894 } else { 26895 this._doReparse(options, norm, this.locale, this.locale, this.plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { 26896 normalized = fmt; 26897 26898 if (options && typeof(options.onLoad) === 'function') { 26899 options.onLoad(fmt); 26900 } 26901 }); 26902 } 26903 26904 // return the value for the synchronous case 26905 return normalized; 26906 } 26907 }; 26908 26909 26910 /*< PhoneFmt.js */ 26911 /* 26912 * phonefmt.js - Represent a phone number formatter. 26913 * 26914 * Copyright © 2014-2015, JEDLSoft 26915 * 26916 * Licensed under the Apache License, Version 2.0 (the "License"); 26917 * you may not use this file except in compliance with the License. 26918 * You may obtain a copy of the License at 26919 * 26920 * http://www.apache.org/licenses/LICENSE-2.0 26921 * 26922 * Unless required by applicable law or agreed to in writing, software 26923 * distributed under the License is distributed on an "AS IS" BASIS, 26924 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26925 * 26926 * See the License for the specific language governing permissions and 26927 * limitations under the License. 26928 */ 26929 26930 /* 26931 !depends 26932 ilib.js 26933 Locale.js 26934 NumberingPlan.js 26935 PhoneNumber.js 26936 PhoneLocale.js 26937 Utils.js 26938 JSUtils.js 26939 */ 26940 26941 // !data phonefmt 26942 26943 26944 /** 26945 * @class 26946 * Create a new phone number formatter object that formats numbers according to the parameters.<p> 26947 * 26948 * The options object can contain zero or more of the following parameters: 26949 * 26950 * <ul> 26951 * <li><i>locale</i> locale to use to format this number, or undefined to use the default locale 26952 * <li><i>style</i> the name of style to use to format numbers, or undefined to use the default style 26953 * <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 26954 * 26955 * <li><i>onLoad</i> - a callback function to call when the locale data is fully loaded and the address has been 26956 * parsed. When the onLoad option is given, the address formatter object 26957 * will attempt to load any missing locale data using the ilib loader callback. 26958 * When the constructor is done (even if the data is already preassembled), the 26959 * onLoad function is called with the current instance as a parameter, so this 26960 * callback can be used with preassembled or dynamic loading or a mix of the two. 26961 * 26962 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 26963 * asynchronously. If this option is given as "false", then the "onLoad" 26964 * callback must be given, as the instance returned from this constructor will 26965 * not be usable for a while. 26966 * 26967 * <li><i>loadParams</i> - an object containing parameters to pass to the 26968 * loader callback function when locale data is missing. The parameters are not 26969 * interpretted or modified in any way. They are simply passed along. The object 26970 * may contain any property/value pairs as long as the calling code is in 26971 * agreement with the loader callback function as to what those parameters mean. 26972 * </ul> 26973 * 26974 * Some regions have more than one style of formatting, and the style parameter 26975 * selects which style the user prefers. An array of style names that this locale 26976 * supports can be found by calling {@link PhoneFmt.getAvailableStyles}. 26977 * Example phone numbers can be retrieved for each style by calling 26978 * {@link PhoneFmt.getStyleExample}. 26979 * <p> 26980 * 26981 * If the MCC is given, numbers will be formatted in the manner of the country 26982 * specified by the MCC. If it is not given, but the locale is, the manner of 26983 * the country in the locale will be used. If neither the locale or MCC are not given, 26984 * then the country of the current ilib locale is used. 26985 * 26986 * @constructor 26987 * @param {Object} options properties that control how this formatter behaves 26988 */ 26989 var PhoneFmt = function(options) { 26990 this.sync = true; 26991 this.styleName = 'default', 26992 this.loadParams = {}; 26993 26994 var locale = new Locale(); 26995 26996 if (options) { 26997 if (options.locale) { 26998 locale = options.locale; 26999 } 27000 27001 if (typeof(options.sync) !== 'undefined') { 27002 this.sync = (options.sync == true); 27003 } 27004 27005 if (options.loadParams) { 27006 this.loadParams = options.loadParams; 27007 } 27008 27009 if (options.style) { 27010 this.style = options.style; 27011 } 27012 } 27013 27014 new PhoneLocale({ 27015 locale: locale, 27016 mcc: options && options.mcc, 27017 countryCode: options && options.countryCode, 27018 onLoad: ilib.bind(this, function (data) { 27019 /** @type {PhoneLocale} */ 27020 this.locale = data; 27021 27022 new NumberingPlan({ 27023 locale: this.locale, 27024 sync: this.sync, 27025 loadParms: this.loadParams, 27026 onLoad: ilib.bind(this, function (plan) { 27027 /** @type {NumberingPlan} */ 27028 this.plan = plan; 27029 27030 Utils.loadData({ 27031 name: "phonefmt.json", 27032 object: PhoneFmt, 27033 locale: this.locale, 27034 sync: this.sync, 27035 loadParams: JSUtils.merge(this.loadParams, { 27036 returnOne: true 27037 }), 27038 callback: ilib.bind(this, function (fmtdata) { 27039 this.fmtdata = fmtdata; 27040 27041 if (options && typeof(options.onLoad) === 'function') { 27042 options.onLoad(this); 27043 } 27044 }) 27045 }); 27046 }) 27047 }); 27048 }) 27049 }); 27050 }; 27051 27052 PhoneFmt.prototype = { 27053 /** 27054 * 27055 * @protected 27056 * @param {string} part 27057 * @param {Object} formats 27058 * @param {boolean} mustUseAll 27059 */ 27060 _substituteDigits: function(part, formats, mustUseAll) { 27061 var formatString, 27062 formatted = "", 27063 partIndex = 0, 27064 templates, 27065 i; 27066 27067 // console.info("Globalization.Phone._substituteDigits: typeof(formats) is " + typeof(formats)); 27068 if (!part) { 27069 return formatted; 27070 } 27071 27072 if (typeof(formats) === "object") { 27073 templates = (typeof(formats.template) !== 'undefined') ? formats.template : formats; 27074 if (part.length > templates.length) { 27075 // too big, so just use last resort rule. 27076 throw "part " + part + " is too big. We do not have a format template to format it."; 27077 } 27078 // use the format in this array that corresponds to the digit length of this 27079 // part of the phone number 27080 formatString = templates[part.length-1]; 27081 // console.info("Globalization.Phone._substituteDigits: formats is an Array: " + JSON.stringify(formats)); 27082 } else { 27083 formatString = formats; 27084 } 27085 27086 for (i = 0; i < formatString.length; i++) { 27087 if (formatString.charAt(i) === "X") { 27088 formatted += part.charAt(partIndex); 27089 partIndex++; 27090 } else { 27091 formatted += formatString.charAt(i); 27092 } 27093 } 27094 27095 if (mustUseAll && partIndex < part.length-1) { 27096 // didn't use the whole thing in this format? Hmm... go to last resort rule 27097 throw "too many digits in " + part + " for format " + formatString; 27098 } 27099 27100 return formatted; 27101 }, 27102 27103 /** 27104 * Returns the style with the given name, or the default style if there 27105 * is no style with that name. 27106 * @protected 27107 * @return {{example:string,whole:Object.<string,string>,partial:Object.<string,string>}|Object.<string,string>} 27108 */ 27109 _getStyle: function (name, fmtdata) { 27110 return fmtdata[name] || fmtdata["default"]; 27111 }, 27112 27113 /** 27114 * Do the actual work of formatting the phone number starting at the given 27115 * field in the regular field order. 27116 * 27117 * @param {!PhoneNumber} number 27118 * @param {{ 27119 * partial:boolean, 27120 * style:string, 27121 * mcc:string, 27122 * locale:(string|Locale), 27123 * sync:boolean, 27124 * loadParams:Object, 27125 * onLoad:function(string) 27126 * }} options Parameters which control how to format the number 27127 * @param {number} startField 27128 */ 27129 _doFormat: function(number, options, startField, locale, fmtdata, callback) { 27130 var sync = true, 27131 loadParams = {}, 27132 temp, 27133 templates, 27134 fieldName, 27135 countryCode, 27136 isWhole, 27137 style, 27138 formatted = "", 27139 styleTemplates, 27140 lastFieldName; 27141 27142 if (options) { 27143 if (typeof(options.sync) !== 'undefined') { 27144 sync = (options.sync == true); 27145 } 27146 27147 if (options.loadParams) { 27148 loadParams = options.loadParams; 27149 } 27150 } 27151 27152 style = this.style; // default style for this formatter 27153 27154 // figure out what style to use for this type of number 27155 if (number.countryCode) { 27156 // dialing from outside the country 27157 // check to see if it to a mobile number because they are often formatted differently 27158 style = (number.mobilePrefix) ? "internationalmobile" : "international"; 27159 } else if (number.mobilePrefix !== undefined) { 27160 style = "mobile"; 27161 } else if (number.serviceCode !== undefined && typeof(fmtdata["service"]) !== 'undefined') { 27162 // if there is a special format for service numbers, then use it 27163 style = "service"; 27164 } 27165 27166 isWhole = (!options || !options.partial); 27167 styleTemplates = this._getStyle(style, fmtdata); 27168 27169 // console.log("Style ends up being " + style + " and using subtype " + (isWhole ? "whole" : "partial")); 27170 styleTemplates = (isWhole ? styleTemplates.whole : styleTemplates.partial) || styleTemplates; 27171 27172 for (var i = startField; i < PhoneNumber._fieldOrder.length; i++) { 27173 fieldName = PhoneNumber._fieldOrder[i]; 27174 // console.info("format: formatting field " + fieldName + " value: " + number[fieldName]); 27175 if (number[fieldName] !== undefined) { 27176 if (styleTemplates[fieldName] !== undefined) { 27177 templates = styleTemplates[fieldName]; 27178 if (fieldName === "trunkAccess") { 27179 if (number.areaCode === undefined && number.serviceCode === undefined && number.mobilePrefix === undefined) { 27180 templates = "X"; 27181 } 27182 } 27183 if (lastFieldName && typeof(styleTemplates[lastFieldName].suffix) !== 'undefined') { 27184 if (fieldName !== "extension" && number[fieldName].search(/[xwtp,;]/i) <= -1) { 27185 formatted += styleTemplates[lastFieldName].suffix; 27186 } 27187 } 27188 lastFieldName = fieldName; 27189 27190 // console.info("format: formatting field " + fieldName + " with templates " + JSON.stringify(templates)); 27191 temp = this._substituteDigits(number[fieldName], templates, (fieldName === "subscriberNumber")); 27192 // console.info("format: formatted is: " + temp); 27193 formatted += temp; 27194 27195 if (fieldName === "countryCode") { 27196 // switch to the new country to format the rest of the number 27197 countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 27198 27199 new PhoneLocale({ 27200 locale: this.locale, 27201 sync: sync, 27202 loadParms: loadParams, 27203 countryCode: countryCode, 27204 onLoad: ilib.bind(this, function (/** @type {PhoneLocale} */ locale) { 27205 Utils.loadData({ 27206 name: "phonefmt.json", 27207 object: PhoneFmt, 27208 locale: locale, 27209 sync: sync, 27210 loadParams: JSUtils.merge(loadParams, { 27211 returnOne: true 27212 }), 27213 callback: ilib.bind(this, function (fmtdata) { 27214 // console.info("format: switching to region " + locale.region + " and style " + style + " to format the rest of the number "); 27215 27216 var subfmt = ""; 27217 27218 this._doFormat(number, options, i+1, locale, fmtdata, function (subformat) { 27219 subfmt = subformat; 27220 if (typeof(callback) === 'function') { 27221 callback(formatted + subformat); 27222 } 27223 }); 27224 27225 formatted += subfmt; 27226 }) 27227 }); 27228 }) 27229 }); 27230 return formatted; 27231 } 27232 } else { 27233 //console.warn("PhoneFmt.format: cannot find format template for field " + fieldName + ", region " + locale.region + ", style " + style); 27234 // use default of "minimal formatting" so we don't miss parts because of bugs in the format templates 27235 formatted += number[fieldName]; 27236 } 27237 } 27238 } 27239 27240 if (typeof(callback) === 'function') { 27241 callback(formatted); 27242 } 27243 27244 return formatted; 27245 }, 27246 27247 /** 27248 * Format the parts of a phone number appropriately according to the settings in 27249 * this formatter instance. 27250 * 27251 * The options can contain zero or more of these properties: 27252 * 27253 * <ul> 27254 * <li><i>partial</i> boolean which tells whether or not this phone number 27255 * represents a partial number or not. The default is false, which means the number 27256 * represents a whole number. 27257 * <li><i>style</i> style to use to format the number, if different from the 27258 * default style or the style specified in the constructor 27259 * <li><i>locale</i> The locale with which to parse the number. This gives a clue as to which 27260 * numbering plan to use. 27261 * <li><i>mcc</i> The mobile carrier code (MCC) associated with the carrier that the phone is 27262 * currently connected to, if known. This also can give a clue as to which numbering plan to 27263 * use 27264 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 27265 * loaded. When the onLoad option is given, the DateFmt object will attempt to 27266 * load any missing locale data using the ilib loader callback. 27267 * When the constructor is done (even if the data is already preassembled), the 27268 * onLoad function is called with the current instance as a parameter, so this 27269 * callback can be used with preassembled or dynamic loading or a mix of the two. 27270 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 27271 * asynchronously. If this option is given as "false", then the "onLoad" 27272 * callback must be given, as the instance returned from this constructor will 27273 * not be usable for a while. 27274 * <li><i>loadParams</i> - an object containing parameters to pass to the 27275 * loader callback function when locale data is missing. The parameters are not 27276 * interpretted or modified in any way. They are simply passed along. The object 27277 * may contain any property/value pairs as long as the calling code is in 27278 * agreement with the loader callback function as to what those parameters mean. 27279 * </ul> 27280 * 27281 * The partial parameter specifies whether or not the phone number contains 27282 * a partial phone number or if it is a whole phone number. A partial 27283 * number is usually a number as the user is entering it with a dial pad. The 27284 * reason is that certain types of phone numbers should be formatted differently 27285 * depending on whether or not it represents a whole number. Specifically, SMS 27286 * short codes are formatted differently.<p> 27287 * 27288 * Example: a subscriber number of "48773" in the US would get formatted as: 27289 * 27290 * <ul> 27291 * <li>partial: 487-73 (perhaps the user is in the process of typing a whole phone 27292 * number such as 487-7379) 27293 * <li>whole: 48773 (this is the entire SMS short code) 27294 * </ul> 27295 * 27296 * Any place in the UI where the user types in phone numbers, such as the keypad in 27297 * the phone app, should pass in partial: true to this formatting routine. All other 27298 * places, such as the call log in the phone app, should pass in partial: false, or 27299 * leave the partial flag out of the parameters entirely. 27300 * 27301 * @param {!PhoneNumber} number object containing the phone number to format 27302 * @param {{ 27303 * partial:boolean, 27304 * style:string, 27305 * mcc:string, 27306 * locale:(string|Locale), 27307 * sync:boolean, 27308 * loadParams:Object, 27309 * onLoad:function(string) 27310 * }} options Parameters which control how to format the number 27311 * @return {string} Returns the formatted phone number as a string. 27312 */ 27313 format: function (number, options) { 27314 var formatted = "", 27315 callback; 27316 27317 callback = options && options.onLoad; 27318 27319 try { 27320 this._doFormat(number, options, 0, this.locale, this.fmtdata, function (fmt) { 27321 formatted = fmt; 27322 27323 if (typeof(callback) === 'function') { 27324 callback(fmt); 27325 } 27326 }); 27327 } catch (e) { 27328 if (typeof(e) === 'string') { 27329 // console.warn("caught exception: " + e + ". Using last resort rule."); 27330 // if there was some exception, use this last resort rule 27331 formatted = ""; 27332 for (var field in PhoneNumber._fieldOrder) { 27333 if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string' && number[PhoneNumber._fieldOrder[field]] !== undefined) { 27334 // just concatenate without any formatting 27335 formatted += number[PhoneNumber._fieldOrder[field]]; 27336 if (PhoneNumber._fieldOrder[field] === 'countryCode') { 27337 formatted += ' '; // fix for NOV-107894 27338 } 27339 } 27340 } 27341 } else { 27342 throw e; 27343 } 27344 27345 if (typeof(callback) === 'function') { 27346 callback(formatted); 27347 } 27348 } 27349 return formatted; 27350 }, 27351 27352 /** 27353 * Return an array of names of all available styles that can be used with the current 27354 * formatter. 27355 * @return {Array.<string>} an array of names of styles that are supported by this formatter 27356 */ 27357 getAvailableStyles: function () { 27358 var ret = [], 27359 style; 27360 27361 if (this.fmtdata) { 27362 for (style in this.fmtdata) { 27363 if (this.fmtdata[style].example) { 27364 ret.push(style); 27365 } 27366 } 27367 } 27368 return ret; 27369 }, 27370 27371 /** 27372 * Return an example phone number formatted with the given style. 27373 * 27374 * @param {string|undefined} style style to get an example of, or undefined to use 27375 * the current default style for this formatter 27376 * @return {string|undefined} an example phone number formatted according to the 27377 * given style, or undefined if the style is not recognized or does not have an 27378 * example 27379 */ 27380 getStyleExample: function (style) { 27381 return this.fmtdata[style].example || undefined; 27382 } 27383 }; 27384 27385 27386 /*< PhoneGeoLocator.js */ 27387 /* 27388 * phonegeo.js - Represent a phone number geolocator object. 27389 * 27390 * Copyright © 2014-2015, JEDLSoft 27391 * 27392 * Licensed under the Apache License, Version 2.0 (the "License"); 27393 * you may not use this file except in compliance with the License. 27394 * You may obtain a copy of the License at 27395 * 27396 * http://www.apache.org/licenses/LICENSE-2.0 27397 * 27398 * Unless required by applicable law or agreed to in writing, software 27399 * distributed under the License is distributed on an "AS IS" BASIS, 27400 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27401 * 27402 * See the License for the specific language governing permissions and 27403 * limitations under the License. 27404 */ 27405 27406 /* 27407 !depends 27408 ilib.js 27409 NumberingPlan.js 27410 PhoneLocale.js 27411 PhoneNumber.js 27412 Utils.js 27413 JSUtils.js 27414 ResBundle.js 27415 */ 27416 27417 // !data iddarea area extarea extstates phoneres 27418 27419 27420 27421 /** 27422 * @class 27423 * Create an instance that can geographically locate a phone number.<p> 27424 * 27425 * The location of the number is calculated according to the following rules: 27426 * 27427 * <ol> 27428 * <li>If the areaCode property is undefined or empty, or if the number specifies a 27429 * country code for which we do not have information, then the area property may be 27430 * missing from the returned object. In this case, only the country object will be returned. 27431 * 27432 * <li>If there is no area code, but there is a mobile prefix, service code, or emergency 27433 * code, then a fixed string indicating the type of number will be returned. 27434 * 27435 * <li>The country object is filled out according to the countryCode property of the phone 27436 * number. 27437 * 27438 * <li>If the phone number does not have an explicit country code, the MCC will be used if 27439 * it is available. The country code can be gleaned directly from the MCC. If the MCC 27440 * of the carrier to which the phone is currently connected is available, it should be 27441 * passed in so that local phone numbers will look correct. 27442 * 27443 * <li>If the country's dialling plan mandates a fixed length for phone numbers, and a 27444 * particular number exceeds that length, then the area code will not be given on the 27445 * assumption that the number has problems in the first place and we cannot guess 27446 * correctly. 27447 * </ol> 27448 * 27449 * The returned area property varies in specificity according 27450 * to the locale. In North America, the area is no finer than large parts of states 27451 * or provinces. In Germany and the UK, the area can be as fine as small towns.<p> 27452 * 27453 * If the number passed in is invalid, no geolocation will be performed. If the location 27454 * information about the country where the phone number is located is not available, 27455 * then the area information will be missing and only the country will be available.<p> 27456 * 27457 * The options parameter can contain any one of the following properties: 27458 * 27459 * <ul> 27460 * <li><i>locale</i> The locale parameter is used to load translations of the names of regions and 27461 * areas if available. For example, if the locale property is given as "en-US" (English for USA), 27462 * but the phone number being geolocated is in Germany, then this class would return the the names 27463 * of the country (Germany) and region inside of Germany in English instead of German. That is, a 27464 * phone number in Munich and return the country "Germany" and the area code "Munich" 27465 * instead of "Deutschland" and "München". The default display locale is the current ilib locale. 27466 * If translations are not available, the region and area names are given in English, which should 27467 * always be available. 27468 * <li><i>mcc</i> The mcc of the current mobile carrier, if known. 27469 * 27470 * <li><i>onLoad</i> - a callback function to call when the data for the 27471 * locale is fully loaded. When the onLoad option is given, this object 27472 * will attempt to load any missing locale data using the ilib loader callback. 27473 * When the constructor is done (even if the data is already preassembled), the 27474 * onLoad function is called with the current instance as a parameter, so this 27475 * callback can be used with preassembled or dynamic loading or a mix of the two. 27476 * 27477 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 27478 * asynchronously. If this option is given as "false", then the "onLoad" 27479 * callback must be given, as the instance returned from this constructor will 27480 * not be usable for a while. 27481 * 27482 * <li><i>loadParams</i> - an object containing parameters to pass to the 27483 * loader callback function when locale data is missing. The parameters are not 27484 * interpretted or modified in any way. They are simply passed along. The object 27485 * may contain any property/value pairs as long as the calling code is in 27486 * agreement with the loader callback function as to what those parameters mean. 27487 * </ul> 27488 * 27489 * @constructor 27490 * @param {Object} options parameters controlling the geolocation of the phone number. 27491 */ 27492 var PhoneGeoLocator = function(options) { 27493 var sync = true, 27494 loadParams = {}, 27495 locale = ilib.getLocale(); 27496 27497 if (options) { 27498 if (options.locale) { 27499 locale = options.locale; 27500 } 27501 27502 if (typeof(options.sync) === 'boolean') { 27503 sync = options.sync; 27504 } 27505 27506 if (options.loadParams) { 27507 loadParams = options.loadParams; 27508 } 27509 } 27510 27511 new PhoneLocale({ 27512 locale: locale, 27513 mcc: options && options.mcc, 27514 countryCode: options && options.countryCode, 27515 sync: sync, 27516 loadParams: loadParams, 27517 onLoad: ilib.bind(this, function (loc) { 27518 this.locale = loc; 27519 new NumberingPlan({ 27520 locale: this.locale, 27521 sync: sync, 27522 loadParams: loadParams, 27523 onLoad: ilib.bind(this, function (plan) { 27524 this.plan = plan; 27525 27526 new ResBundle({ 27527 locale: this.locale, 27528 name: "phoneres", 27529 sync: sync, 27530 loadParams: loadParams, 27531 onLoad: ilib.bind(this, function (rb) { 27532 this.rb = rb; 27533 27534 Utils.loadData({ 27535 name: "iddarea.json", 27536 object: PhoneGeoLocator, 27537 nonlocale: true, 27538 sync: sync, 27539 loadParams: loadParams, 27540 callback: ilib.bind(this, function (data) { 27541 this.regiondata = data; 27542 Utils.loadData({ 27543 name: "area.json", 27544 object: PhoneGeoLocator, 27545 locale: this.locale, 27546 sync: sync, 27547 loadParams: JSUtils.merge(loadParams, { 27548 returnOne: true 27549 }), 27550 callback: ilib.bind(this, function (areadata) { 27551 this.areadata = areadata; 27552 27553 if (options && typeof(options.onLoad) === 'function') { 27554 options.onLoad(this); 27555 } 27556 }) 27557 }); 27558 }) 27559 }); 27560 }) 27561 }); 27562 }) 27563 }); 27564 }) 27565 }); 27566 }; 27567 27568 PhoneGeoLocator.prototype = { 27569 /** 27570 * @private 27571 * 27572 * Used for locales where the area code is very general, and you need to add in 27573 * the initial digits of the subscriber number in order to get the area 27574 * 27575 * @param {string} number 27576 * @param {Object} stateTable 27577 */ 27578 _parseAreaAndSubscriber: function (number, stateTable) { 27579 var ch, 27580 i, 27581 handlerMethod, 27582 newState, 27583 prefix = "", 27584 consumed, 27585 lastLeaf, 27586 currentState, 27587 dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java 27588 27589 if (!number || !stateTable) { 27590 // can't parse anything 27591 return undefined; 27592 } 27593 27594 //console.log("GeoLocator._parseAreaAndSubscriber: parsing number " + number); 27595 27596 currentState = stateTable; 27597 i = 0; 27598 while (i < number.length) { 27599 ch = PhoneNumber._getCharacterCode(number.charAt(i)); 27600 if (ch >= 0) { 27601 // newState = stateData.states[state][ch]; 27602 newState = currentState.s && currentState.s[ch]; 27603 27604 if (!newState && currentState.s && currentState.s[dot]) { 27605 newState = currentState.s[dot]; 27606 } 27607 27608 if (typeof(newState) === 'object') { 27609 if (typeof(newState.l) !== 'undefined') { 27610 // save for latter if needed 27611 lastLeaf = newState; 27612 consumed = i; 27613 } 27614 // console.info("recognized digit " + ch + " continuing..."); 27615 // recognized digit, so continue parsing 27616 currentState = newState; 27617 i++; 27618 } else { 27619 if (typeof(newState) === 'undefined' || newState === 0) { 27620 // this is possibly a look-ahead and it didn't work... 27621 // so fall back to the last leaf and use that as the 27622 // final state 27623 newState = lastLeaf; 27624 i = consumed; 27625 } 27626 27627 if ((typeof(newState) === 'number' && newState) || 27628 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 27629 // final state 27630 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 27631 handlerMethod = PhoneNumber._states[stateNumber]; 27632 27633 //console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); 27634 27635 return (handlerMethod === "area") ? number.substring(0, i+1) : undefined; 27636 } else { 27637 // failed parse. Either no last leaf to fall back to, or there was an explicit 27638 // zero in the table 27639 break; 27640 } 27641 } 27642 } else if (ch === -1) { 27643 // non-transition character, continue parsing in the same state 27644 i++; 27645 } else { 27646 // should not happen 27647 // console.info("skipping character " + ch); 27648 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 27649 i++; 27650 } 27651 } 27652 return undefined; 27653 }, 27654 /** 27655 * @private 27656 * @param prefix 27657 * @param table 27658 * @returns 27659 */ 27660 _matchPrefix: function(prefix, table) { 27661 var i, matchedDot, matchesWithDots = []; 27662 27663 if (table[prefix]) { 27664 return table[prefix]; 27665 } 27666 for (var entry in table) { 27667 if (entry && typeof(entry) === 'string') { 27668 i = 0; 27669 matchedDot = false; 27670 while (i < entry.length && (entry.charAt(i) === prefix.charAt(i) || entry.charAt(i) === '.')) { 27671 if (entry.charAt(i) === '.') { 27672 matchedDot = true; 27673 } 27674 i++; 27675 } 27676 if (i >= entry.length) { 27677 if (matchedDot) { 27678 matchesWithDots.push(entry); 27679 } else { 27680 return table[entry]; 27681 } 27682 } 27683 } 27684 } 27685 27686 // match entries with dots last, so sort the matches so that the entry with the 27687 // most dots sorts last. The entry that ends up at the beginning of the list is 27688 // the best match because it has the fewest dots 27689 if (matchesWithDots.length > 0) { 27690 matchesWithDots.sort(function (left, right) { 27691 return (right < left) ? -1 : ((left < right) ? 1 : 0); 27692 }); 27693 return table[matchesWithDots[0]]; 27694 } 27695 27696 return undefined; 27697 }, 27698 /** 27699 * @private 27700 * @param number 27701 * @param data 27702 * @param locale 27703 * @param plan 27704 * @param options 27705 * @returns {Object} 27706 */ 27707 _getAreaInfo: function(number, data, locale, plan, options) { 27708 var sync = true, 27709 ret = {}, 27710 countryCode, 27711 areaInfo, 27712 temp, 27713 areaCode, 27714 geoTable, 27715 tempNumber, 27716 prefix; 27717 27718 if (options && typeof(options.sync) === 'boolean') { 27719 sync = options.sync; 27720 } 27721 27722 prefix = number.areaCode || number.serviceCode; 27723 geoTable = data; 27724 27725 if (prefix !== undefined) { 27726 if (plan.getExtendedAreaCode()) { 27727 // for countries where the area code is very general and large, and you need a few initial 27728 // digits of the subscriber number in order find the actual area 27729 tempNumber = prefix + number.subscriberNumber; 27730 tempNumber = tempNumber.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 27731 27732 Utils.loadData({ 27733 name: "extarea.json", 27734 object: PhoneGeoLocator, 27735 locale: locale, 27736 sync: sync, 27737 loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), 27738 callback: ilib.bind(this, function (data) { 27739 this.extarea = data; 27740 Utils.loadData({ 27741 name: "extstates.json", 27742 object: PhoneGeoLocator, 27743 locale: locale, 27744 sync: sync, 27745 loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), 27746 callback: ilib.bind(this, function (data) { 27747 this.extstates = data; 27748 geoTable = this.extarea; 27749 if (this.extarea && this.extstates) { 27750 prefix = this._parseAreaAndSubscriber(tempNumber, this.extstates); 27751 } 27752 27753 if (!prefix) { 27754 // not a recognized prefix, so now try the general table 27755 geoTable = this.areadata; 27756 prefix = number.areaCode || number.serviceCode; 27757 } 27758 27759 if ((!plan.fieldLengths || 27760 plan.getFieldLength('maxLocalLength') === undefined || 27761 !number.subscriberNumber || 27762 number.subscriberNumber.length <= plan.fieldLengths('maxLocalLength'))) { 27763 areaInfo = this._matchPrefix(prefix, geoTable); 27764 if (areaInfo && areaInfo.sn && areaInfo.ln) { 27765 //console.log("Found areaInfo " + JSON.stringify(areaInfo)); 27766 ret.area = { 27767 sn: this.rb.getString(areaInfo.sn).toString(), 27768 ln: this.rb.getString(areaInfo.ln).toString() 27769 }; 27770 } 27771 } 27772 }) 27773 }); 27774 }) 27775 }); 27776 27777 } else if (!plan || 27778 plan.getFieldLength('maxLocalLength') === undefined || 27779 !number.subscriberNumber || 27780 number.subscriberNumber.length <= plan.getFieldLength('maxLocalLength')) { 27781 if (geoTable) { 27782 areaCode = prefix.replace(/[wWpPtT\+#\*]/g, ''); 27783 areaInfo = this._matchPrefix(areaCode, geoTable); 27784 27785 if (areaInfo && areaInfo.sn && areaInfo.ln) { 27786 ret.area = { 27787 sn: this.rb.getString(areaInfo.sn).toString(), 27788 ln: this.rb.getString(areaInfo.ln).toString() 27789 }; 27790 } else if (number.serviceCode) { 27791 ret.area = { 27792 sn: this.rb.getString("Service Number").toString(), 27793 ln: this.rb.getString("Service Number").toString() 27794 }; 27795 } 27796 } else { 27797 countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); 27798 if (countryCode !== "0" && this.regiondata) { 27799 temp = this.regiondata[countryCode]; 27800 if (temp && temp.sn) { 27801 ret.country = { 27802 sn: this.rb.getString(temp.sn).toString(), 27803 ln: this.rb.getString(temp.ln).toString(), 27804 code: this.locale.getRegion() 27805 }; 27806 } 27807 } 27808 } 27809 } else { 27810 countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); 27811 if (countryCode !== "0" && this.regiondata) { 27812 temp = this.regiondata[countryCode]; 27813 if (temp && temp.sn) { 27814 ret.country = { 27815 sn: this.rb.getString(temp.sn).toString(), 27816 ln: this.rb.getString(temp.ln).toString(), 27817 code: this.locale.getRegion() 27818 }; 27819 } 27820 } 27821 } 27822 27823 } else if (number.mobilePrefix) { 27824 ret.area = { 27825 sn: this.rb.getString("Mobile Number").toString(), 27826 ln: this.rb.getString("Mobile Number").toString() 27827 }; 27828 } else if (number.emergency) { 27829 ret.area = { 27830 sn: this.rb.getString("Emergency Services Number").toString(), 27831 ln: this.rb.getString("Emergency Services Number").toString() 27832 }; 27833 } 27834 27835 return ret; 27836 }, 27837 /** 27838 * Returns a the location of the given phone number, if known. 27839 * The returned object has 2 properties, each of which has an sn (short name) 27840 * and an ln (long name) string. Additionally, the country code, if given, 27841 * includes the 2 letter ISO code for the recognized country. 27842 * { 27843 * "country": { 27844 * "sn": "North America", 27845 * "ln": "North America and the Caribbean Islands", 27846 * "code": "us" 27847 * }, 27848 * "area": { 27849 * "sn": "California", 27850 * "ln": "Central California: San Jose, Los Gatos, Milpitas, Sunnyvale, Cupertino, Gilroy" 27851 * } 27852 * } 27853 * 27854 * The location name is subject to the following rules: 27855 * 27856 * If the areaCode property is undefined or empty, or if the number specifies a 27857 * country code for which we do not have information, then the area property may be 27858 * missing from the returned object. In this case, only the country object will be returned. 27859 * 27860 * If there is no area code, but there is a mobile prefix, service code, or emergency 27861 * code, then a fixed string indicating the type of number will be returned. 27862 * 27863 * The country object is filled out according to the countryCode property of the phone 27864 * number. 27865 * 27866 * If the phone number does not have an explicit country code, the MCC will be used if 27867 * it is available. The country code can be gleaned directly from the MCC. If the MCC 27868 * of the carrier to which the phone is currently connected is available, it should be 27869 * passed in so that local phone numbers will look correct. 27870 * 27871 * If the country's dialling plan mandates a fixed length for phone numbers, and a 27872 * particular number exceeds that length, then the area code will not be given on the 27873 * assumption that the number has problems in the first place and we cannot guess 27874 * correctly. 27875 * 27876 * The returned area property varies in specificity according 27877 * to the locale. In North America, the area is no finer than large parts of states 27878 * or provinces. In Germany and the UK, the area can be as fine as small towns. 27879 * 27880 * The strings returned from this function are already localized to the 27881 * given locale, and thus are ready for display to the user. 27882 * 27883 * If the number passed in is invalid, an empty object is returned. If the location 27884 * information about the country where the phone number is located is not available, 27885 * then the area information will be missing and only the country will be returned. 27886 * 27887 * The options parameter can contain any one of the following properties: 27888 * 27889 * <ul> 27890 * <li><i>locale</i> The locale parameter is used to load translations of the names of regions and 27891 * areas if available. For example, if the locale property is given as "en-US" (English for USA), 27892 * but the phone number being geolocated is in Germany, then this class would return the the names 27893 * of the country (Germany) and region inside of Germany in English instead of German. That is, a 27894 * phone number in Munich and return the country "Germany" and the area code "Munich" 27895 * instead of "Deutschland" and "München". The default display locale is the current ilib locale. 27896 * If translations are not available, the region and area names are given in English, which should 27897 * always be available. 27898 * <li><i>mcc</i> The mcc of the current mobile carrier, if known. 27899 * 27900 * <li><i>onLoad</i> - a callback function to call when the data for the 27901 * locale is fully loaded. When the onLoad option is given, this object 27902 * will attempt to load any missing locale data using the ilib loader callback. 27903 * When the constructor is done (even if the data is already preassembled), the 27904 * onLoad function is called with the current instance as a parameter, so this 27905 * callback can be used with preassembled or dynamic loading or a mix of the two. 27906 * 27907 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 27908 * asynchronously. If this option is given as "false", then the "onLoad" 27909 * callback must be given, as the instance returned from this constructor will 27910 * not be usable for a while. 27911 * 27912 * <li><i>loadParams</i> - an object containing parameters to pass to the 27913 * loader callback function when locale data is missing. The parameters are not 27914 * interpretted or modified in any way. They are simply passed along. The object 27915 * may contain any property/value pairs as long as the calling code is in 27916 * agreement with the loader callback function as to what those parameters mean. 27917 * </ul> 27918 * 27919 * @param {PhoneNumber} number phone number to locate 27920 * @param {Object} options options governing the way this ares is loaded 27921 * @return {Object} an object 27922 * that describes the country and the area in that country corresponding to this 27923 * phone number. Each of the country and area contain a short name (sn) and long 27924 * name (ln) that describes the location. 27925 */ 27926 locate: function(number, options) { 27927 var loadParams = {}, 27928 ret = {}, 27929 region, 27930 countryCode, 27931 temp, 27932 plan, 27933 areaResult, 27934 phoneLoc = this.locale, 27935 sync = true; 27936 27937 if (number === undefined || typeof(number) !== 'object' || !(number instanceof PhoneNumber)) { 27938 return ret; 27939 } 27940 27941 if (options) { 27942 if (typeof(options.sync) !== 'undefined') { 27943 sync = (options.sync == true); 27944 } 27945 27946 if (options.loadParams) { 27947 loadParams = options.loadParams; 27948 } 27949 } 27950 27951 // console.log("GeoLocator.locate: looking for geo for number " + JSON.stringify(number)); 27952 region = this.locale.getRegion(); 27953 if (number.countryCode !== undefined && this.regiondata) { 27954 countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); 27955 temp = this.regiondata[countryCode]; 27956 phoneLoc = number.destinationLocale; 27957 plan = number.destinationPlan; 27958 ret.country = { 27959 sn: this.rb.getString(temp.sn).toString(), 27960 ln: this.rb.getString(temp.ln).toString(), 27961 code: phoneLoc.getRegion() 27962 }; 27963 } 27964 27965 if (!plan) { 27966 plan = this.plan; 27967 } 27968 27969 Utils.loadData({ 27970 name: "area.json", 27971 object: PhoneGeoLocator, 27972 locale: phoneLoc, 27973 sync: sync, 27974 loadParams: JSUtils.merge(loadParams, { 27975 returnOne: true 27976 }), 27977 callback: ilib.bind(this, function (areadata) { 27978 if (areadata) { 27979 this.areadata = areadata; 27980 } 27981 areaResult = this._getAreaInfo(number, this.areadata, phoneLoc, plan, options); 27982 ret = JSUtils.merge(ret, areaResult); 27983 27984 if (ret.country === undefined) { 27985 countryCode = number.locale._mapRegiontoCC(region); 27986 27987 if (countryCode !== "0" && this.regiondata) { 27988 temp = this.regiondata[countryCode]; 27989 if (temp && temp.sn) { 27990 ret.country = { 27991 sn: this.rb.getString(temp.sn).toString(), 27992 ln: this.rb.getString(temp.ln).toString(), 27993 code: this.locale.getRegion() 27994 }; 27995 } 27996 } 27997 } 27998 }) 27999 }); 28000 28001 return ret; 28002 }, 28003 28004 /** 28005 * Returns a string that describes the ISO-3166-2 country code of the given phone 28006 * number.<p> 28007 * 28008 * If the phone number is a local phone number and does not contain 28009 * any country information, this routine will return the region for the current 28010 * formatter instance. 28011 * 28012 * @param {PhoneNumber} number An PhoneNumber instance 28013 * @return {string} 28014 */ 28015 country: function(number) { 28016 var countryCode, 28017 region, 28018 phoneLoc; 28019 28020 if (!number || !(number instanceof PhoneNumber)) { 28021 return ""; 28022 } 28023 28024 phoneLoc = number.locale; 28025 28026 region = (number.countryCode && phoneLoc._mapCCtoRegion(number.countryCode)) || 28027 (number.locale && number.locale.region) || 28028 phoneLoc.locale.getRegion() || 28029 this.locale.getRegion(); 28030 28031 countryCode = number.countryCode || phoneLoc._mapRegiontoCC(region); 28032 28033 if (number.areaCode) { 28034 region = phoneLoc._mapAreatoRegion(countryCode, number.areaCode); 28035 } else if (countryCode === "33" && number.serviceCode) { 28036 // french departments are in the service code, not the area code 28037 region = phoneLoc._mapAreatoRegion(countryCode, number.serviceCode); 28038 } 28039 return region; 28040 } 28041 }; 28042 28043 28044 /*< Measurement.js */ 28045 /* 28046 * Measurement.js - Measurement unit superclass 28047 * 28048 * Copyright © 2014-2015, JEDLSoft 28049 * 28050 * Licensed under the Apache License, Version 2.0 (the "License"); 28051 * you may not use this file except in compliance with the License. 28052 * You may obtain a copy of the License at 28053 * 28054 * http://www.apache.org/licenses/LICENSE-2.0 28055 * 28056 * Unless required by applicable law or agreed to in writing, software 28057 * distributed under the License is distributed on an "AS IS" BASIS, 28058 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28059 * 28060 * See the License for the specific language governing permissions and 28061 * limitations under the License. 28062 */ 28063 28064 /** 28065 * @class 28066 * Superclass for measurement instances that contains shared functionality 28067 * and defines the interface. <p> 28068 * 28069 * This class is never instantiated on its own. Instead, measurements should 28070 * be created using the {@link MeasurementFactory} function, which creates the 28071 * correct subclass based on the given parameters.<p> 28072 * 28073 * @private 28074 * @constructor 28075 */ 28076 var Measurement = function() { 28077 }; 28078 28079 /** 28080 * @private 28081 */ 28082 Measurement._constructors = {}; 28083 28084 Measurement.prototype = { 28085 /** 28086 * Return the normalized name of the given units. If the units are 28087 * not recognized, this method returns its parameter unmodified.<p> 28088 * 28089 * Examples: 28090 * 28091 * <ui> 28092 * <li>"metres" gets normalized to "meter"<br> 28093 * <li>"ml" gets normalized to "milliliter"<br> 28094 * <li>"foobar" gets normalized to "foobar" (no change because it is not recognized) 28095 * </ul> 28096 * 28097 * @param {string} name name of the units to normalize. 28098 * @returns {string} normalized name of the units 28099 */ 28100 normalizeUnits: function(name) { 28101 return this.aliases[name] || name; 28102 }, 28103 28104 /** 28105 * Return the normalized units used in this measurement. 28106 * @return {string} name of the unit of measurement 28107 */ 28108 getUnit: function() { 28109 return this.unit; 28110 }, 28111 28112 /** 28113 * Return the units originally used to construct this measurement 28114 * before it was normalized. 28115 * @return {string} name of the unit of measurement 28116 */ 28117 getOriginalUnit: function() { 28118 return this.originalUnit; 28119 }, 28120 28121 /** 28122 * Return the numeric amount of this measurement. 28123 * @return {number} the numeric amount of this measurement 28124 */ 28125 getAmount: function() { 28126 return this.amount; 28127 }, 28128 28129 /** 28130 * Return the type of this measurement. Examples are "mass", 28131 * "length", "speed", etc. Measurements can only be converted 28132 * to measurements of the same type.<p> 28133 * 28134 * The type of the units is determined automatically from the 28135 * units. For example, the unit "grams" is type "mass". Use the 28136 * static call {@link Measurement.getAvailableUnits} 28137 * to find out what units this version of ilib supports. 28138 * 28139 * @abstract 28140 * @return {string} the name of the type of this measurement 28141 */ 28142 getMeasure: function() {}, 28143 28144 /** 28145 * Return a new measurement instance that is converted to a new 28146 * measurement unit. Measurements can only be converted 28147 * to measurements of the same type.<p> 28148 * 28149 * @abstract 28150 * @param {string} to The name of the units to convert to 28151 * @return {Measurement|undefined} the converted measurement 28152 * or undefined if the requested units are for a different 28153 * measurement type 28154 */ 28155 convert: function(to) {}, 28156 28157 /** 28158 * Scale the measurement unit to an acceptable level. The scaling 28159 * happens so that the integer part of the amount is as small as 28160 * possible without being below zero. This will result in the 28161 * largest units that can represent this measurement without 28162 * fractions. Measurements can only be scaled to other measurements 28163 * of the same type. 28164 * 28165 * @abstract 28166 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 28167 * or undefined if the system can be inferred from the current measure 28168 * @return {Measurement} a new instance that is scaled to the 28169 * right level 28170 */ 28171 scale: function(measurementsystem) {}, 28172 28173 /** 28174 * Localize the measurement to the commonly used measurement in that locale, for example 28175 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 28176 * the formatted number should be automatically converted to the most appropriate 28177 * measure in the other system, in this case, mph. The formatted result should 28178 * appear as "37.3 mph". 28179 * 28180 * @abstract 28181 * @param {string} locale current locale string 28182 * @returns {Measurement} a new instance that is converted to locale 28183 */ 28184 localize: function(locale) {} 28185 }; 28186 28187 28188 28189 /*< UnknownUnit.js */ 28190 /* 28191 * Unknown.js - Dummy unit conversions for unknown types 28192 * 28193 * Copyright © 2014-2015, JEDLSoft 28194 * 28195 * Licensed under the Apache License, Version 2.0 (the "License"); 28196 * you may not use this file except in compliance with the License. 28197 * You may obtain a copy of the License at 28198 * 28199 * http://www.apache.org/licenses/LICENSE-2.0 28200 * 28201 * Unless required by applicable law or agreed to in writing, software 28202 * distributed under the License is distributed on an "AS IS" BASIS, 28203 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28204 * 28205 * See the License for the specific language governing permissions and 28206 * limitations under the License. 28207 */ 28208 28209 // !depends Measurement.js 28210 28211 28212 /** 28213 * @class 28214 * Create a new unknown measurement instance. 28215 * 28216 * @constructor 28217 * @extends Measurement 28218 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 28219 * the construction of this instance 28220 */ 28221 var UnknownUnit = function (options) { 28222 if (options) { 28223 this.unit = options.unit; 28224 this.amount = options.amount; 28225 } 28226 }; 28227 28228 UnknownUnit.prototype = new Measurement(); 28229 UnknownUnit.prototype.parent = Measurement; 28230 UnknownUnit.prototype.constructor = UnknownUnit; 28231 28232 UnknownUnit.aliases = { 28233 "unknown":"unknown" 28234 }; 28235 28236 /** 28237 * Return the type of this measurement. Examples are "mass", 28238 * "length", "speed", etc. Measurements can only be converted 28239 * to measurements of the same type.<p> 28240 * 28241 * The type of the units is determined automatically from the 28242 * units. For example, the unit "grams" is type "mass". Use the 28243 * static call {@link Measurement.getAvailableUnits} 28244 * to find out what units this version of ilib supports. 28245 * 28246 * @return {string} the name of the type of this measurement 28247 */ 28248 UnknownUnit.prototype.getMeasure = function() { 28249 return "unknown"; 28250 }; 28251 28252 /** 28253 * Return a new measurement instance that is converted to a new 28254 * measurement unit. Measurements can only be converted 28255 * to measurements of the same type.<p> 28256 * 28257 * @param {string} to The name of the units to convert to 28258 * @return {Measurement|undefined} the converted measurement 28259 * or undefined if the requested units are for a different 28260 * measurement type 28261 */ 28262 UnknownUnit.prototype.convert = function(to) { 28263 return undefined; 28264 }; 28265 28266 /** 28267 * Convert a unknown to another measure. 28268 * @static 28269 * @param {string} to unit to convert to 28270 * @param {string} from unit to convert from 28271 * @param {number} unknown amount to be convert 28272 * @returns {number|undefined} the converted amount 28273 */ 28274 UnknownUnit.convert = function(to, from, unknown) { 28275 return undefined; 28276 }; 28277 28278 /** 28279 * Localize the measurement to the commonly used measurement in that locale. For example 28280 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 28281 * the formatted number should be automatically converted to the most appropriate 28282 * measure in the other system, in this case, mph. The formatted result should 28283 * appear as "37.3 mph". 28284 * 28285 * @abstract 28286 * @param {string} locale current locale string 28287 * @returns {Measurement} a new instance that is converted to locale 28288 */ 28289 UnknownUnit.prototype.localize = function(locale) { 28290 return new UnknownUnit({ 28291 unit: this.unit, 28292 amount: this.amount 28293 }); 28294 }; 28295 28296 /** 28297 * Scale the measurement unit to an acceptable level. The scaling 28298 * happens so that the integer part of the amount is as small as 28299 * possible without being below zero. This will result in the 28300 * largest units that can represent this measurement without 28301 * fractions. Measurements can only be scaled to other measurements 28302 * of the same type. 28303 * 28304 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 28305 * or undefined if the system can be inferred from the current measure 28306 * @return {Measurement} a new instance that is scaled to the 28307 * right level 28308 */ 28309 UnknownUnit.prototype.scale = function(measurementsystem) { 28310 return new UnknownUnit({ 28311 unit: this.unit, 28312 amount: this.amount 28313 }); 28314 }; 28315 28316 /** 28317 * @private 28318 * @static 28319 */ 28320 UnknownUnit.getMeasures = function () { 28321 return []; 28322 }; 28323 28324 28325 /*< AreaUnit.js */ 28326 /* 28327 * area.js - Unit conversions for Area 28328 * 28329 * Copyright © 2014-2015, JEDLSoft 28330 * 28331 * Licensed under the Apache License, Version 2.0 (the "License"); 28332 * you may not use this file except in compliance with the License. 28333 * You may obtain a copy of the License at 28334 * 28335 * http://www.apache.org/licenses/LICENSE-2.0 28336 * 28337 * Unless required by applicable law or agreed to in writing, software 28338 * distributed under the License is distributed on an "AS IS" BASIS, 28339 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28340 * 28341 * See the License for the specific language governing permissions and 28342 * limitations under the License. 28343 */ 28344 28345 /* 28346 !depends 28347 Measurement.js 28348 */ 28349 28350 28351 /** 28352 * @class 28353 * Create a new area measurement instance. 28354 * @constructor 28355 * @extends Measurement 28356 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 28357 * the construction of this instance 28358 */ 28359 var AreaUnit = function (options) { 28360 this.unit = "square meter"; 28361 this.amount = 0; 28362 this.aliases = AreaUnit.aliases; // share this table in all instances 28363 28364 if (options) { 28365 if (typeof(options.unit) !== 'undefined') { 28366 this.originalUnit = options.unit; 28367 this.unit = this.aliases[options.unit] || options.unit; 28368 } 28369 28370 if (typeof(options.amount) === 'object') { 28371 if (options.amount.getMeasure() === "area") { 28372 this.amount = AreaUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 28373 } else { 28374 throw "Cannot convert unit " + options.amount.unit + " to area"; 28375 } 28376 } else if (typeof(options.amount) !== 'undefined') { 28377 this.amount = parseFloat(options.amount); 28378 } 28379 } 28380 28381 if (typeof(AreaUnit.ratios[this.unit]) === 'undefined') { 28382 throw "Unknown unit: " + options.unit; 28383 } 28384 }; 28385 28386 AreaUnit.prototype = new Measurement(); 28387 AreaUnit.prototype.parent = Measurement; 28388 AreaUnit.prototype.constructor = AreaUnit; 28389 28390 AreaUnit.ratios = { 28391 /* index square cm, square meter, hectare, square km, , square inch square foot, square yard, acre, square mile */ 28392 "square centimeter":[1, 1, 0.0001, 1e-8, 1e-10, 0.15500031, 0.00107639104, 0.000119599005, 2.47105381e-8, 3.86102159e-11 ], 28393 "square meter": [2, 10000, 1, 1e-4, 1e-6, 1550, 10.7639, 1.19599, 0.000247105, 3.861e-7 ], 28394 "hectare": [3, 100000000, 10000, 1, 0.01, 1.55e+7, 107639, 11959.9, 2.47105 , 0.00386102 ], 28395 "square km": [4, 10000000000, 1e+6, 100, 1, 1.55e+9, 1.076e+7, 1.196e+6, 247.105 , 0.386102 ], 28396 "square inch": [5, 6.4516, 0.00064516, 6.4516e-8, 6.4516e-10, 1, 0.000771605, 0.0007716051, 1.5942e-7, 2.491e-10 ], 28397 "square foot": [6, 929.0304, 0.092903, 9.2903e-6, 9.2903e-8, 144, 1, 0.111111, 2.2957e-5, 3.587e-8 ], 28398 "square yard": [7, 8361.2736, 0.836127, 8.3613e-5, 8.3613e-7, 1296, 9, 1, 0.000206612, 3.2283e-7 ], 28399 "acre": [8, 40468564.2, 4046.86, 0.404686, 0.00404686, 6.273e+6, 43560, 4840, 1, 0.0015625 ], 28400 "square mile": [9, 2.58998811e+10, 2.59e+6, 258.999, 2.58999, 4.014e+9, 2.788e+7, 3.098e+6, 640, 1 ] 28401 } 28402 28403 /** 28404 * Return the type of this measurement. Examples are "mass", 28405 * "length", "speed", etc. Measurements can only be converted 28406 * to measurements of the same type.<p> 28407 * 28408 * The type of the units is determined automatically from the 28409 * units. For example, the unit "grams" is type "mass". Use the 28410 * static call {@link Measurement.getAvailableUnits} 28411 * to find out what units this version of ilib supports. 28412 * 28413 * @return {string} the name of the type of this measurement 28414 */ 28415 AreaUnit.prototype.getMeasure = function() { 28416 return "area"; 28417 }; 28418 28419 /** 28420 * Return a new measurement instance that is converted to a new 28421 * measurement unit. Measurements can only be converted 28422 * to measurements of the same type.<p> 28423 * 28424 * @param {string} to The name of the units to convert to 28425 * @return {Measurement|undefined} the converted measurement 28426 * or undefined if the requested units are for a different 28427 * measurement type 28428 * 28429 */ 28430 AreaUnit.prototype.convert = function(to) { 28431 if (!to || typeof(AreaUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 28432 return undefined; 28433 } 28434 return new AreaUnit({ 28435 unit: to, 28436 amount: this 28437 }); 28438 }; 28439 28440 AreaUnit.aliases = { 28441 "square centimeter":"square centimeter", 28442 "square cm":"square centimeter", 28443 "sq cm":"square centimeter", 28444 "Square Cm":"square centimeter", 28445 "square Centimeters":"square centimeter", 28446 "square Centimeter":"square centimeter", 28447 "square Centimetre":"square centimeter", 28448 "square Centimetres":"square centimeter", 28449 "square centimeters":"square centimeter", 28450 "Square km": "square km", 28451 "Square kilometre":"square km", 28452 "square kilometer":"square km", 28453 "square kilometre":"square km", 28454 "square kilometers":"square km", 28455 "square kilometres":"square km", 28456 "square km":"square km", 28457 "sq km":"square km", 28458 "km2":"square km", 28459 "Hectare":"hectare", 28460 "hectare":"hectare", 28461 "ha":"hectare", 28462 "Square meter": "square meter", 28463 "Square meters":"square meter", 28464 "square meter": "square meter", 28465 "square meters":"square meter", 28466 "Square metre": "square meter", 28467 "Square metres":"square meter", 28468 "square metres": "square meter", 28469 "Square Metres":"square meter", 28470 "sqm":"square meter", 28471 "m2": "square meter", 28472 "Square mile":"square mile", 28473 "Square miles":"square mile", 28474 "square mile":"square mile", 28475 "square miles":"square mile", 28476 "square mi":"square mile", 28477 "Square mi":"square mile", 28478 "sq mi":"square mile", 28479 "mi2":"square mile", 28480 "Acre": "acre", 28481 "acre": "acre", 28482 "Acres":"acre", 28483 "acres":"acre", 28484 "Square yard": "square yard", 28485 "Square yards":"square yard", 28486 "square yard": "square yard", 28487 "square yards":"square yard", 28488 "yd2":"square yard", 28489 "Square foot": "square foot", 28490 "square foot": "square foot", 28491 "Square feet": "square foot", 28492 "Square Feet": "square foot", 28493 "sq ft":"square foot", 28494 "ft2":"square foot", 28495 "Square inch":"square inch", 28496 "square inch":"square inch", 28497 "Square inches":"square inch", 28498 "square inches":"square inch", 28499 "in2":"square inch" 28500 }; 28501 28502 /** 28503 * Convert a Area to another measure. 28504 * @static 28505 * @param to {string} unit to convert to 28506 * @param from {string} unit to convert from 28507 * @param area {number} amount to be convert 28508 * @returns {number|undefined} the converted amount 28509 */ 28510 AreaUnit.convert = function(to, from, area) { 28511 from = AreaUnit.aliases[from] || from; 28512 to = AreaUnit.aliases[to] || to; 28513 var fromRow = AreaUnit.ratios[from]; 28514 var toRow = AreaUnit.ratios[to]; 28515 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 28516 return undefined; 28517 } 28518 return area* fromRow[toRow[0]]; 28519 }; 28520 28521 /** 28522 * @private 28523 * @static 28524 */ 28525 AreaUnit.getMeasures = function () { 28526 var ret = []; 28527 for (var m in AreaUnit.ratios) { 28528 ret.push(m); 28529 } 28530 return ret; 28531 }; 28532 28533 AreaUnit.metricSystem = { 28534 "square centimeter" : 1, 28535 "square meter" : 2, 28536 "hectare" : 3, 28537 "square km" : 4 28538 }; 28539 AreaUnit.imperialSystem = { 28540 "square inch" : 5, 28541 "square foot" : 6, 28542 "square yard" : 7, 28543 "acre" : 8, 28544 "square mile" : 9 28545 }; 28546 AreaUnit.uscustomarySystem = { 28547 "square inch" : 5, 28548 "square foot" : 6, 28549 "square yard" : 7, 28550 "acre" : 8, 28551 "square mile" : 9 28552 }; 28553 28554 AreaUnit.metricToUScustomary = { 28555 "square centimeter" : "square inch", 28556 "square meter" : "square yard", 28557 "hectare" : "acre", 28558 "square km" : "square mile" 28559 }; 28560 AreaUnit.usCustomaryToMetric = { 28561 "square inch" : "square centimeter", 28562 "square foot" : "square meter", 28563 "square yard" : "square meter", 28564 "acre" : "hectare", 28565 "square mile" : "square km" 28566 }; 28567 28568 28569 /** 28570 * Scale the measurement unit to an acceptable level. The scaling 28571 * happens so that the integer part of the amount is as small as 28572 * possible without being below zero. This will result in the 28573 * largest units that can represent this measurement without 28574 * fractions. Measurements can only be scaled to other measurements 28575 * of the same type. 28576 * 28577 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 28578 * or undefined if the system can be inferred from the current measure 28579 * @return {Measurement} a new instance that is scaled to the 28580 * right level 28581 */ 28582 AreaUnit.prototype.scale = function(measurementsystem) { 28583 var fromRow = AreaUnit.ratios[this.unit]; 28584 var mSystem; 28585 28586 if (measurementsystem === "metric" || (typeof(measurementsystem) === 'undefined' 28587 && typeof(AreaUnit.metricSystem[this.unit]) !== 'undefined')) { 28588 mSystem = AreaUnit.metricSystem; 28589 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 28590 && typeof(AreaUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 28591 mSystem = AreaUnit.uscustomarySystem; 28592 } else if (measurementsystem === "imperial" || (typeof(measurementsystem) === 'undefined' 28593 && typeof(AreaUnit.imperialSystem[this.unit]) !== 'undefined')) { 28594 mSystem = AreaUnit.imperialSystem; 28595 } 28596 28597 var area = this.amount; 28598 var munit = this.unit; 28599 28600 area = 18446744073709551999; 28601 28602 for (var m in mSystem) { 28603 var tmp = this.amount * fromRow[mSystem[m]]; 28604 if (tmp >= 1 && tmp < area) { 28605 area = tmp; 28606 munit = m; 28607 } 28608 } 28609 28610 return new AreaUnit({ 28611 unit: munit, 28612 amount: area 28613 }); 28614 }; 28615 28616 /** 28617 * Localize the measurement to the commonly used measurement in that locale. For example 28618 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 28619 * the formatted number should be automatically converted to the most appropriate 28620 * measure in the other system, in this case, mph. The formatted result should 28621 * appear as "37.3 mph". 28622 * 28623 * @abstract 28624 * @param {string} locale current locale string 28625 * @returns {Measurement} a new instance that is converted to locale 28626 */ 28627 AreaUnit.prototype.localize = function(locale) { 28628 var to; 28629 if (locale === "en-US" || locale === "en-GB") { 28630 to = AreaUnit.metricToUScustomary[this.unit] || this.unit; 28631 } else { 28632 to = AreaUnit.usCustomaryToMetric[this.unit] || this.unit; 28633 } 28634 return new AreaUnit({ 28635 unit: to, 28636 amount: this 28637 }); 28638 }; 28639 28640 28641 //register with the factory method 28642 Measurement._constructors["area"] = AreaUnit; 28643 28644 28645 /*< DigitalStorageUnit.js */ 28646 /* 28647 * digitalStorage.js - Unit conversions for Digital Storage 28648 * 28649 * Copyright © 2014-2015, JEDLSoft 28650 * 28651 * Licensed under the Apache License, Version 2.0 (the "License"); 28652 * you may not use this file except in compliance with the License. 28653 * You may obtain a copy of the License at 28654 * 28655 * http://www.apache.org/licenses/LICENSE-2.0 28656 * 28657 * Unless required by applicable law or agreed to in writing, software 28658 * distributed under the License is distributed on an "AS IS" BASIS, 28659 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28660 * 28661 * See the License for the specific language governing permissions and 28662 * limitations under the License. 28663 */ 28664 28665 /* 28666 !depends 28667 Measurement.js 28668 */ 28669 28670 28671 /** 28672 * @class 28673 * Create a new DigitalStorage measurement instance. 28674 * 28675 * @constructor 28676 * @extends Measurement 28677 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 28678 * the construction of this instance 28679 */ 28680 var DigitalStorageUnit = function (options) { 28681 this.unit = "byte"; 28682 this.amount = 0; 28683 this.aliases = DigitalStorageUnit.aliases; // share this table in all instances 28684 28685 if (options) { 28686 if (typeof(options.unit) !== 'undefined') { 28687 this.originalUnit = options.unit; 28688 this.unit = this.aliases[options.unit] || options.unit; 28689 } 28690 28691 if (typeof(options.amount) === 'object') { 28692 if (options.amount.getMeasure() === "digitalStorage") { 28693 this.amount = DigitalStorageUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 28694 } else { 28695 throw "Cannot convert unit " + options.amount.unit + " to a digitalStorage"; 28696 } 28697 } else if (typeof(options.amount) !== 'undefined') { 28698 this.amount = parseFloat(options.amount); 28699 } 28700 } 28701 28702 if (typeof(DigitalStorageUnit.ratios[this.unit]) === 'undefined') { 28703 throw "Unknown unit: " + options.unit; 28704 } 28705 }; 28706 28707 DigitalStorageUnit.prototype = new Measurement(); 28708 DigitalStorageUnit.prototype.parent = Measurement; 28709 DigitalStorageUnit.prototype.constructor = DigitalStorageUnit; 28710 28711 DigitalStorageUnit.ratios = { 28712 /* # bit byte kb kB mb mB gb gB tb tB pb pB */ 28713 "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 ], 28714 "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 ], 28715 "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 ], 28716 "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 ], 28717 "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 ], 28718 "megabyte": [ 6, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10 ], 28719 "gigabit": [ 7, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7 ], 28720 "gigabyte": [ 8, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7 ], 28721 "terabit": [ 9, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4 ], 28722 "terabyte": [ 10, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625 ], 28723 "petabit": [ 11, 1.125899907e15, 1.407374884e14, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125 ], 28724 "petabyte": [ 12, 9.007199255e15, 1.125899907e15, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1 ] 28725 }; 28726 28727 DigitalStorageUnit.bitSystem = { 28728 "bit": 1, 28729 "kilobit": 3, 28730 "megabit": 5, 28731 "gigabit": 7, 28732 "terabit": 9, 28733 "petabit": 11 28734 }; 28735 DigitalStorageUnit.byteSystem = { 28736 "byte": 2, 28737 "kilobyte": 4, 28738 "megabyte": 6, 28739 "gigabyte": 8, 28740 "terabyte": 10, 28741 "petabyte": 12 28742 }; 28743 28744 /** 28745 * Return the type of this measurement. Examples are "mass", 28746 * "length", "speed", etc. Measurements can only be converted 28747 * to measurements of the same type.<p> 28748 * 28749 * The type of the units is determined automatically from the 28750 * units. For example, the unit "grams" is type "mass". Use the 28751 * static call {@link Measurement.getAvailableUnits} 28752 * to find out what units this version of ilib supports. 28753 * 28754 * @return {string} the name of the type of this measurement 28755 */ 28756 DigitalStorageUnit.prototype.getMeasure = function() { 28757 return "digitalStorage"; 28758 }; 28759 28760 /** 28761 * Return a new measurement instance that is converted to a new 28762 * measurement unit. Measurements can only be converted 28763 * to measurements of the same type.<p> 28764 * 28765 * @param {string} to The name of the units to convert to 28766 * @return {Measurement|undefined} the converted measurement 28767 * or undefined if the requested units are for a different 28768 * measurement type 28769 * 28770 */ 28771 DigitalStorageUnit.prototype.convert = function(to) { 28772 if (!to || typeof(DigitalStorageUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 28773 return undefined; 28774 } 28775 return new DigitalStorageUnit({ 28776 unit: to, 28777 amount: this 28778 }); 28779 }; 28780 28781 /** 28782 * Localize the measurement to the commonly used measurement in that locale. For example 28783 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 28784 * the formatted number should be automatically converted to the most appropriate 28785 * measure in the other system, in this case, mph. The formatted result should 28786 * appear as "37.3 mph". 28787 * 28788 * @abstract 28789 * @param {string} locale current locale string 28790 * @returns {Measurement} a new instance that is converted to locale 28791 */ 28792 DigitalStorageUnit.prototype.localize = function(locale) { 28793 return new DigitalStorageUnit({ 28794 unit: this.unit, 28795 amount: this.amount 28796 }); 28797 }; 28798 28799 /** 28800 * Scale the measurement unit to an acceptable level. The scaling 28801 * happens so that the integer part of the amount is as small as 28802 * possible without being below zero. This will result in the 28803 * largest units that can represent this measurement without 28804 * fractions. Measurements can only be scaled to other measurements 28805 * of the same type. 28806 * 28807 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 28808 * or undefined if the system can be inferred from the current measure 28809 * @return {Measurement} a new instance that is scaled to the 28810 * right level 28811 */ 28812 DigitalStorageUnit.prototype.scale = function(measurementsystem) { 28813 var mSystem; 28814 if (this.unit in DigitalStorageUnit.bitSystem) { 28815 mSystem = DigitalStorageUnit.bitSystem; 28816 } else { 28817 mSystem = DigitalStorageUnit.byteSystem; 28818 } 28819 28820 var dStorage = this.amount; 28821 var munit = this.unit; 28822 var fromRow = DigitalStorageUnit.ratios[this.unit]; 28823 28824 dStorage = 18446744073709551999; 28825 for (var m in mSystem) { 28826 var tmp = this.amount * fromRow[mSystem[m]]; 28827 if (tmp >= 1 && tmp < dStorage) { 28828 dStorage = tmp; 28829 munit = m; 28830 } 28831 } 28832 28833 return new DigitalStorageUnit({ 28834 unit: munit, 28835 amount: dStorage 28836 }); 28837 }; 28838 28839 DigitalStorageUnit.aliases = { 28840 "bits": "bit", 28841 "bit": "bit", 28842 "Bits": "bit", 28843 "Bit": "bit", 28844 "byte": "byte", 28845 "bytes": "byte", 28846 "Byte": "byte", 28847 "Bytes": "byte", 28848 "kilobits": "kilobit", 28849 "Kilobits": "kilobit", 28850 "KiloBits": "kilobit", 28851 "kiloBits": "kilobit", 28852 "kilobit": "kilobit", 28853 "Kilobit": "kilobit", 28854 "kiloBit": "kilobit", 28855 "KiloBit": "kilobit", 28856 "kb": "kilobit", 28857 "Kb": "kilobit", 28858 "kilobyte": "kilobyte", 28859 "Kilobyte": "kilobyte", 28860 "kiloByte": "kilobyte", 28861 "KiloByte": "kilobyte", 28862 "kilobytes": "kilobyte", 28863 "Kilobytes": "kilobyte", 28864 "kiloBytes": "kilobyte", 28865 "KiloBytes": "kilobyte", 28866 "kB": "kilobyte", 28867 "KB": "kilobyte", 28868 "megabit": "megabit", 28869 "Megabit": "megabit", 28870 "megaBit": "megabit", 28871 "MegaBit": "megabit", 28872 "megabits": "megabit", 28873 "Megabits": "megabit", 28874 "megaBits": "megabit", 28875 "MegaBits": "megabit", 28876 "Mb": "megabit", 28877 "mb": "megabit", 28878 "megabyte": "megabyte", 28879 "Megabyte": "megabyte", 28880 "megaByte": "megabyte", 28881 "MegaByte": "megabyte", 28882 "megabytes": "megabyte", 28883 "Megabytes": "megabyte", 28884 "megaBytes": "megabyte", 28885 "MegaBytes": "megabyte", 28886 "MB": "megabyte", 28887 "mB": "megabyte", 28888 "gigabit": "gigabit", 28889 "Gigabit": "gigabit", 28890 "gigaBit": "gigabit", 28891 "GigaBit": "gigabit", 28892 "gigabits": "gigabit", 28893 "Gigabits": "gigabit", 28894 "gigaBits": "gigabyte", 28895 "GigaBits": "gigabit", 28896 "Gb": "gigabit", 28897 "gb": "gigabit", 28898 "gigabyte": "gigabyte", 28899 "Gigabyte": "gigabyte", 28900 "gigaByte": "gigabyte", 28901 "GigaByte": "gigabyte", 28902 "gigabytes": "gigabyte", 28903 "Gigabytes": "gigabyte", 28904 "gigaBytes": "gigabyte", 28905 "GigaBytes": "gigabyte", 28906 "GB": "gigabyte", 28907 "gB": "gigabyte", 28908 "terabit": "terabit", 28909 "Terabit": "terabit", 28910 "teraBit": "terabit", 28911 "TeraBit": "terabit", 28912 "terabits": "terabit", 28913 "Terabits": "terabit", 28914 "teraBits": "terabit", 28915 "TeraBits": "terabit", 28916 "tb": "terabit", 28917 "Tb": "terabit", 28918 "terabyte": "terabyte", 28919 "Terabyte": "terabyte", 28920 "teraByte": "terabyte", 28921 "TeraByte": "terabyte", 28922 "terabytes": "terabyte", 28923 "Terabytes": "terabyte", 28924 "teraBytes": "terabyte", 28925 "TeraBytes": "terabyte", 28926 "TB": "terabyte", 28927 "tB": "terabyte", 28928 "petabit": "petabit", 28929 "Petabit": "petabit", 28930 "petaBit": "petabit", 28931 "PetaBit": "petabit", 28932 "petabits": "petabit", 28933 "Petabits": "petabit", 28934 "petaBits": "petabit", 28935 "PetaBits": "petabit", 28936 "pb": "petabit", 28937 "Pb": "petabit", 28938 "petabyte": "petabyte", 28939 "Petabyte": "petabyte", 28940 "petaByte": "petabyte", 28941 "PetaByte": "petabyte", 28942 "petabytes": "petabyte", 28943 "Petabytes": "petabyte", 28944 "petaBytes": "petabyte", 28945 "PetaBytes": "petabyte", 28946 "PB": "petabyte", 28947 "pB": "petabyte" 28948 }; 28949 28950 /** 28951 * Convert a digitalStorage to another measure. 28952 * @static 28953 * @param to {string} unit to convert to 28954 * @param from {string} unit to convert from 28955 * @param digitalStorage {number} amount to be convert 28956 * @returns {number|undefined} the converted amount 28957 */ 28958 DigitalStorageUnit.convert = function(to, from, digitalStorage) { 28959 from = DigitalStorageUnit.aliases[from] || from; 28960 to = DigitalStorageUnit.aliases[to] || to; 28961 var fromRow = DigitalStorageUnit.ratios[from]; 28962 var toRow = DigitalStorageUnit.ratios[to]; 28963 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 28964 return undefined; 28965 } 28966 var result = digitalStorage * fromRow[toRow[0]]; 28967 return result; 28968 }; 28969 28970 /** 28971 * @private 28972 * @static 28973 */ 28974 DigitalStorageUnit.getMeasures = function () { 28975 var ret = []; 28976 for (var m in DigitalStorageUnit.ratios) { 28977 ret.push(m); 28978 } 28979 return ret; 28980 }; 28981 28982 //register with the factory method 28983 Measurement._constructors["digitalStorage"] = DigitalStorageUnit; 28984 28985 28986 /*< EnergyUnit.js */ 28987 /* 28988 * Energy.js - Unit conversions for Energys/energys 28989 * 28990 * Copyright © 2014-2015, JEDLSoft 28991 * 28992 * Licensed under the Apache License, Version 2.0 (the "License"); 28993 * you may not use this file except in compliance with the License. 28994 * You may obtain a copy of the License at 28995 * 28996 * http://www.apache.org/licenses/LICENSE-2.0 28997 * 28998 * Unless required by applicable law or agreed to in writing, software 28999 * distributed under the License is distributed on an "AS IS" BASIS, 29000 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29001 * 29002 * See the License for the specific language governing permissions and 29003 * limitations under the License. 29004 */ 29005 29006 /* 29007 !depends 29008 Measurement.js 29009 */ 29010 29011 29012 /** 29013 * @class 29014 * Create a new energy measurement instance. 29015 * 29016 * @constructor 29017 * @extends Measurement 29018 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 29019 * the construction of this instance 29020 */ 29021 var EnergyUnit = function (options) { 29022 this.unit = "joule"; 29023 this.amount = 0; 29024 this.aliases = EnergyUnit.aliases; // share this table in all instances 29025 29026 if (options) { 29027 if (typeof(options.unit) !== 'undefined') { 29028 this.originalUnit = options.unit; 29029 this.unit = this.aliases[options.unit] || options.unit; 29030 } 29031 29032 if (typeof(options.amount) === 'object') { 29033 if (options.amount.getMeasure() === "energy") { 29034 this.amount = EnergyUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 29035 } else { 29036 throw "Cannot convert units " + options.amount.unit + " to a energy"; 29037 } 29038 } else if (typeof(options.amount) !== 'undefined') { 29039 this.amount = parseFloat(options.amount); 29040 } 29041 } 29042 29043 if (typeof(EnergyUnit.ratios[this.unit]) === 'undefined') { 29044 throw "Unknown unit: " + options.unit; 29045 } 29046 }; 29047 29048 EnergyUnit.prototype = new Measurement(); 29049 EnergyUnit.prototype.parent = Measurement; 29050 EnergyUnit.prototype.constructor = EnergyUnit; 29051 29052 EnergyUnit.ratios = { 29053 /* index mJ J BTU kJ Wh Cal MJ kWh gJ MWh GWh */ 29054 "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 ], 29055 "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 ], 29056 "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 ], 29057 "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 ], 29058 "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 ], 29059 "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 ], 29060 "megajoule": [ 7, 1e+9, 1e+6, 947.81707775, 1000, 277.77777778, 238.84589663, 1, 0.27777777778, 0.001, 2.7777777778e-4, 2.7777777778e-7 ], 29061 "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 ], 29062 "gigajoule": [ 9, 1e+12, 1e+9, 947817.07775, 1e+6, 277777.77778, 238845.89663, 1000, 277.77777778, 1, 0.27777777778, 2.7777777778e-4 ], 29063 "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 ], 29064 "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 ] 29065 }; 29066 29067 /** 29068 * Return the type of this measurement. Examples are "mass", 29069 * "length", "speed", etc. Measurements can only be converted 29070 * to measurements of the same type.<p> 29071 * 29072 * The type of the units is determined automatically from the 29073 * units. For example, the unit "grams" is type "mass". Use the 29074 * static call {@link Measurement.getAvailableUnits} 29075 * to find out what units this version of ilib supports. 29076 * 29077 * @return {string} the name of the type of this measurement 29078 */ 29079 EnergyUnit.prototype.getMeasure = function() { 29080 return "energy"; 29081 }; 29082 29083 /** 29084 * Return a new measurement instance that is converted to a new 29085 * measurement unit. Measurements can only be converted 29086 * to measurements of the same type.<p> 29087 * 29088 * @param {string} to The name of the units to convert to 29089 * @return {Measurement|undefined} the converted measurement 29090 * or undefined if the requested units are for a different 29091 * measurement type 29092 */ 29093 EnergyUnit.prototype.convert = function(to) { 29094 if (!to || typeof(EnergyUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 29095 return undefined; 29096 } 29097 return new EnergyUnit({ 29098 unit: to, 29099 amount: this 29100 }); 29101 }; 29102 29103 EnergyUnit.aliases = { 29104 "milli joule": "millijoule", 29105 "millijoule": "millijoule", 29106 "MilliJoule": "millijoule", 29107 "milliJ": "millijoule", 29108 "joule": "joule", 29109 "J": "joule", 29110 "j": "joule", 29111 "Joule": "joule", 29112 "Joules": "joule", 29113 "joules": "joule", 29114 "BTU": "BTU", 29115 "btu": "BTU", 29116 "British thermal unit": "BTU", 29117 "british thermal unit": "BTU", 29118 "kilo joule": "kilojoule", 29119 "kJ": "kilojoule", 29120 "kj": "kilojoule", 29121 "Kj": "kilojoule", 29122 "kiloJoule": "kilojoule", 29123 "kilojoule": "kilojoule", 29124 "kjoule": "kilojoule", 29125 "watt hour": "watt hour", 29126 "Wh": "watt hour", 29127 "wh": "watt hour", 29128 "watt-hour": "watt hour", 29129 "calorie": "calorie", 29130 "Cal": "calorie", 29131 "cal": "calorie", 29132 "Calorie": "calorie", 29133 "calories": "calorie", 29134 "mega joule": "megajoule", 29135 "MJ": "megajoule", 29136 "megajoule": "megajoule", 29137 "megajoules": "megajoule", 29138 "Megajoules": "megajoule", 29139 "megaJoules": "megajoule", 29140 "MegaJoules": "megajoule", 29141 "megaJoule": "megajoule", 29142 "MegaJoule": "megajoule", 29143 "kilo Watt hour": "kilowatt hour", 29144 "kWh": "kilowatt hour", 29145 "kiloWh": "kilowatt hour", 29146 "KiloWh": "kilowatt hour", 29147 "KiloWatt-hour": "kilowatt hour", 29148 "kilowatt hour": "kilowatt hour", 29149 "kilowatt-hour": "kilowatt hour", 29150 "KiloWatt-hours": "kilowatt hour", 29151 "kilowatt-hours": "kilowatt hour", 29152 "Kilo Watt-hour": "kilowatt hour", 29153 "Kilo Watt-hours": "kilowatt hour", 29154 "giga joule": "gigajoule", 29155 "gJ": "gigajoule", 29156 "GJ": "gigajoule", 29157 "GigaJoule": "gigajoule", 29158 "gigaJoule": "gigajoule", 29159 "gigajoule": "gigajoule", 29160 "GigaJoules": "gigajoule", 29161 "gigaJoules": "gigajoule", 29162 "Gigajoules": "gigajoule", 29163 "gigajoules": "gigajoule", 29164 "mega watt hour": "megawatt hour", 29165 "MWh": "megawatt hour", 29166 "MegaWh": "megawatt hour", 29167 "megaWh": "megawatt hour", 29168 "megaWatthour": "megawatt hour", 29169 "megaWatt-hour": "megawatt hour", 29170 "mega Watt-hour": "megawatt hour", 29171 "megaWatt hour": "megawatt hour", 29172 "megawatt hour": "megawatt hour", 29173 "mega Watt hour": "megawatt hour", 29174 "giga watt hour": "gigawatt hour", 29175 "gWh": "gigawatt hour", 29176 "GWh": "gigawatt hour", 29177 "gigaWh": "gigawatt hour", 29178 "gigaWatt-hour": "gigawatt hour", 29179 "gigawatt-hour": "gigawatt hour", 29180 "gigaWatt hour": "gigawatt hour", 29181 "gigawatt hour": "gigawatt hour", 29182 "gigawatthour": "gigawatt hour" 29183 }; 29184 29185 /** 29186 * Convert a energy to another measure. 29187 * @static 29188 * @param to {string} unit to convert to 29189 * @param from {string} unit to convert from 29190 * @param energy {number} amount to be convert 29191 * @returns {number|undefined} the converted amount 29192 */ 29193 EnergyUnit.convert = function(to, from, energy) { 29194 from = EnergyUnit.aliases[from] || from; 29195 to = EnergyUnit.aliases[to] || to; 29196 var fromRow = EnergyUnit.ratios[from]; 29197 var toRow = EnergyUnit.ratios[to]; 29198 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 29199 return undefined; 29200 } 29201 return energy * fromRow[toRow[0]]; 29202 }; 29203 29204 /** 29205 * @private 29206 * @static 29207 */ 29208 EnergyUnit.getMeasures = function () { 29209 var ret = []; 29210 for (var m in EnergyUnit.ratios) { 29211 ret.push(m); 29212 } 29213 return ret; 29214 }; 29215 29216 EnergyUnit.metricJouleSystem = { 29217 "millijoule": 1, 29218 "joule": 2, 29219 "kilojoule": 4, 29220 "megajoule": 7, 29221 "gigajoule": 9 29222 }; 29223 EnergyUnit.metricWattHourSystem = { 29224 "watt hour": 5, 29225 "kilowatt hour": 8, 29226 "megawatt hour": 10, 29227 "gigawatt hour": 11 29228 }; 29229 29230 EnergyUnit.imperialSystem = { 29231 "BTU": 3 29232 }; 29233 EnergyUnit.uscustomarySystem = { 29234 "calorie": 6 29235 }; 29236 29237 EnergyUnit.metricToImperial = { 29238 "millijoule": "BTU", 29239 "joule": "BTU", 29240 "kilojoule": "BTU", 29241 "megajoule": "BTU", 29242 "gigajoule": "BTU" 29243 }; 29244 EnergyUnit.imperialToMetric = { 29245 "BTU": "joule" 29246 }; 29247 29248 /** 29249 * Localize the measurement to the commonly used measurement in that locale. For example 29250 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29251 * the formatted number should be automatically converted to the most appropriate 29252 * measure in the other system, in this case, mph. The formatted result should 29253 * appear as "37.3 mph". 29254 * 29255 * @abstract 29256 * @param {string} locale current locale string 29257 * @returns {Measurement} a new instance that is converted to locale 29258 */ 29259 EnergyUnit.prototype.localize = function(locale) { 29260 var to; 29261 if (locale === "en-GB") { 29262 to = EnergyUnit.metricToImperial[this.unit] || this.unit; 29263 } else { 29264 to = EnergyUnit.imperialToMetric[this.unit] || this.unit; 29265 } 29266 29267 return new EnergyUnit({ 29268 unit: to, 29269 amount: this 29270 }); 29271 }; 29272 29273 /** 29274 * Scale the measurement unit to an acceptable level. The scaling 29275 * happens so that the integer part of the amount is as small as 29276 * possible without being below zero. This will result in the 29277 * largest units that can represent this measurement without 29278 * fractions. Measurements can only be scaled to other measurements 29279 * of the same type. 29280 * 29281 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 29282 * or undefined if the system can be inferred from the current measure 29283 * @return {Measurement} a new instance that is scaled to the 29284 * right level 29285 */ 29286 EnergyUnit.prototype.scale = function(measurementsystem) { 29287 var fromRow = EnergyUnit.ratios[this.unit]; 29288 var mSystem; 29289 29290 if ((measurementsystem === "metric" && typeof(EnergyUnit.metricJouleSystem[this.unit]) !== 'undefined')|| (typeof(measurementsystem) === 'undefined' 29291 && typeof(EnergyUnit.metricJouleSystem[this.unit]) !== 'undefined')) { 29292 mSystem = EnergyUnit.metricJouleSystem; 29293 } 29294 else if ((measurementsystem === "metric" && typeof(EnergyUnit.metricWattHourSystem[this.unit]) !== 'undefined')|| (typeof(measurementsystem) === 'undefined' 29295 && typeof(EnergyUnit.metricWattHourSystem[this.unit]) !== 'undefined')) { 29296 mSystem = EnergyUnit.metricWattHourSystem; 29297 } 29298 29299 else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 29300 && typeof(EnergyUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 29301 mSystem = EnergyUnit.uscustomarySystem; 29302 } 29303 else if (measurementsystem === "imperial"|| (typeof(measurementsystem) === 'undefined' 29304 && typeof(EnergyUnit.imperialSystem[this.unit]) !== 'undefined')) { 29305 mSystem = EnergyUnit.imperialSystem; 29306 } 29307 29308 var energy = this.amount; 29309 var munit = this.unit; 29310 29311 energy = 18446744073709551999; 29312 29313 for (var m in mSystem) { 29314 var tmp = this.amount * fromRow[mSystem[m]]; 29315 if (tmp >= 1 && tmp < energy) { 29316 energy = tmp; 29317 munit = m; 29318 } 29319 } 29320 29321 return new EnergyUnit({ 29322 unit: munit, 29323 amount: energy 29324 }); 29325 }; 29326 //register with the factory method 29327 Measurement._constructors["energy"] = EnergyUnit; 29328 29329 29330 /*< FuelConsumptionUnit.js */ 29331 /* 29332 * fuelconsumption.js - Unit conversions for FuelConsumption 29333 * 29334 * Copyright © 2014-2015, JEDLSoft 29335 * 29336 * Licensed under the Apache License, Version 2.0 (the "License"); 29337 * you may not use this file except in compliance with the License. 29338 * You may obtain a copy of the License at 29339 * 29340 * http://www.apache.org/licenses/LICENSE-2.0 29341 * 29342 * Unless required by applicable law or agreed to in writing, software 29343 * distributed under the License is distributed on an "AS IS" BASIS, 29344 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29345 * 29346 * See the License for the specific language governing permissions and 29347 * limitations under the License. 29348 */ 29349 29350 /* 29351 !depends 29352 Measurement.js 29353 */ 29354 29355 29356 /** 29357 * @class 29358 * Create a new fuelconsumption measurement instance. 29359 * 29360 * @constructor 29361 * @extends Measurement 29362 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 29363 * the construction of this instance 29364 */ 29365 var FuelConsumptionUnit = function(options) { 29366 this.unit = "km/liter"; 29367 this.amount = 0; 29368 this.aliases = FuelConsumptionUnit.aliases; // share this table in all instances 29369 29370 if (options) { 29371 if (typeof(options.unit) !== 'undefined') { 29372 this.originalUnit = options.unit; 29373 this.unit = this.aliases[options.unit] || options.unit; 29374 } 29375 29376 if (typeof(options.amount) === 'object') { 29377 if (options.amount.getMeasure() === "fuelconsumption") { 29378 this.amount = FuelConsumptionUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 29379 } else { 29380 throw "Cannot convert unit " + options.amount.unit + " to fuelconsumption"; 29381 } 29382 } else if (typeof(options.amount) !== 'undefined') { 29383 this.amount = parseFloat(options.amount); 29384 } 29385 } 29386 }; 29387 29388 FuelConsumptionUnit.prototype = new Measurement(); 29389 FuelConsumptionUnit.prototype.parent = Measurement; 29390 FuelConsumptionUnit.prototype.constructor = FuelConsumptionUnit; 29391 29392 FuelConsumptionUnit.ratios = [ 29393 "km/liter", 29394 "liter/100km", 29395 "mpg", 29396 "mpg(imp)" 29397 ]; 29398 29399 /** 29400 * Return the type of this measurement. Examples are "mass", 29401 * "length", "speed", etc. Measurements can only be converted 29402 * to measurements of the same type.<p> 29403 * 29404 * The type of the units is determined automatically from the 29405 * units. For example, the unit "grams" is type "mass". Use the 29406 * static call {@link Measurement.getAvailableUnits} 29407 * to find out what units this version of ilib supports. 29408 * 29409 * @return {string} the name of the type of this measurement 29410 */ 29411 FuelConsumptionUnit.prototype.getMeasure = function() { 29412 return "fuelconsumption"; 29413 }; 29414 29415 /** 29416 * Return a new measurement instance that is converted to a new 29417 * measurement unit. Measurements can only be converted 29418 * to measurements of the same type.<p> 29419 * 29420 * @param {string} to The name of the units to convert to 29421 * @return {Measurement|undefined} the converted measurement 29422 * or undefined if the requested units are for a different 29423 * measurement type 29424 */ 29425 FuelConsumptionUnit.prototype.convert = function(to) { 29426 if (!to || typeof(FuelConsumptionUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 29427 return undefined; 29428 } 29429 return new FuelConsumptionUnit({ 29430 unit: to, 29431 amount: this 29432 }); 29433 }; 29434 /*["km/liter", "liter/100km", "mpg", "mpg(imp)"*/ 29435 FuelConsumptionUnit.aliases = { 29436 "Km/liter": "km/liter", 29437 "KM/Liter": "km/liter", 29438 "KM/L": "km/liter", 29439 "Kilometers Per Liter": "km/liter", 29440 "kilometers per liter": "km/liter", 29441 "km/l": "km/liter", 29442 "Kilometers/Liter": "km/liter", 29443 "Kilometer/Liter": "km/liter", 29444 "kilometers/liter": "km/liter", 29445 "kilometer/liter": "km/liter", 29446 "km/liter": "km/liter", 29447 "Liter/100km": "liter/100km", 29448 "Liters/100km": "liter/100km", 29449 "Liter/100kms": "liter/100km", 29450 "Liters/100kms": "liter/100km", 29451 "liter/100km": "liter/100km", 29452 "liters/100kms": "liter/100km", 29453 "liters/100km": "liter/100km", 29454 "liter/100kms": "liter/100km", 29455 "Liter/100KM": "liter/100km", 29456 "Liters/100KM": "liter/100km", 29457 "L/100km": "liter/100km", 29458 "L/100KM": "liter/100km", 29459 "l/100KM": "liter/100km", 29460 "l/100km": "liter/100km", 29461 "l/100kms": "liter/100km", 29462 "MPG(US)": "mpg", 29463 "USMPG ": "mpg", 29464 "mpg": "mpg", 29465 "mpgUS": "mpg", 29466 "mpg(US)": "mpg", 29467 "mpg(us)": "mpg", 29468 "mpg-us": "mpg", 29469 "mpg Imp": "mpg(imp)", 29470 "MPG(imp)": "mpg(imp)", 29471 "mpg(imp)": "mpg(imp)", 29472 "mpg-imp": "mpg(imp)" 29473 }; 29474 29475 FuelConsumptionUnit.metricToUScustomary = { 29476 "km/liter": "mpg", 29477 "liter/100km": "mpg" 29478 }; 29479 FuelConsumptionUnit.metricToImperial = { 29480 "km/liter": "mpg(imp)", 29481 "liter/100km": "mpg(imp)" 29482 }; 29483 29484 FuelConsumptionUnit.imperialToMetric = { 29485 "mpg(imp)": "km/liter" 29486 }; 29487 FuelConsumptionUnit.imperialToUScustomary = { 29488 "mpg(imp)": "mpg" 29489 }; 29490 29491 FuelConsumptionUnit.uScustomaryToImperial = { 29492 "mpg": "mpg(imp)" 29493 }; 29494 FuelConsumptionUnit.uScustomarylToMetric = { 29495 "mpg": "km/liter" 29496 }; 29497 29498 /** 29499 * Localize the measurement to the commonly used measurement in that locale. For example 29500 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29501 * the formatted number should be automatically converted to the most appropriate 29502 * measure in the other system, in this case, mph. The formatted result should 29503 * appear as "37.3 mph". 29504 * 29505 * @abstract 29506 * @param {string} locale current locale string 29507 * @returns {Measurement} a new instance that is converted to locale 29508 */ 29509 FuelConsumptionUnit.prototype.localize = function(locale) { 29510 var to; 29511 if (locale === "en-US") { 29512 to = FuelConsumptionUnit.metricToUScustomary[this.unit] || 29513 FuelConsumptionUnit.imperialToUScustomary[this.unit] || 29514 this.unit; 29515 } else if (locale === "en-GB") { 29516 to = FuelConsumptionUnit.metricToImperial[this.unit] || 29517 FuelConsumptionUnit.uScustomaryToImperial[this.unit] || 29518 this.unit; 29519 } else { 29520 to = FuelConsumptionUnit.uScustomarylToMetric[this.unit] || 29521 FuelConsumptionUnit.imperialToUScustomary[this.unit] || 29522 this.unit; 29523 } 29524 return new FuelConsumptionUnit({ 29525 unit: to, 29526 amount: this 29527 }); 29528 }; 29529 29530 /** 29531 * Convert a FuelConsumption to another measure. 29532 * 29533 * @static 29534 * @param to {string} unit to convert to 29535 * @param from {string} unit to convert from 29536 * @param fuelConsumption {number} amount to be convert 29537 * @returns {number|undefined} the converted amount 29538 */ 29539 FuelConsumptionUnit.convert = function(to, from, fuelConsumption) { 29540 from = FuelConsumptionUnit.aliases[from] || from; 29541 to = FuelConsumptionUnit.aliases[to] || to; 29542 var returnValue = 0; 29543 29544 switch (from) { 29545 case "km/liter": 29546 switch (to) { 29547 case "km/liter": 29548 returnValue = fuelConsumption * 1; 29549 break; 29550 case "liter/100km": 29551 returnValue = 100 / fuelConsumption; 29552 break; 29553 case "mpg": 29554 returnValue = fuelConsumption * 2.35215; 29555 break; 29556 case "mpg(imp)": 29557 returnValue = fuelConsumption * 2.82481; 29558 break; 29559 } 29560 break; 29561 case "liter/100km": 29562 switch (to) { 29563 case "km/liter": 29564 returnValue = 100 / fuelConsumption; 29565 break; 29566 case "liter/100km": 29567 returnValue = fuelConsumption * 1; 29568 break; 29569 case "mpg": 29570 returnValue = 235.215 / fuelConsumption; 29571 break; 29572 case "mpg(imp)": 29573 returnValue = 282.481 / fuelConsumption; 29574 break; 29575 } 29576 break; 29577 case "mpg": 29578 switch (to) { 29579 case "km/liter": 29580 returnValue = fuelConsumption * 0.425144; 29581 break; 29582 case "liter/100km": 29583 returnValue = 235.215 / fuelConsumption; 29584 break; 29585 case "mpg": 29586 returnValue = 1 * fuelConsumption; 29587 break; 29588 case "mpg(imp)": 29589 returnValue = 1.20095 * fuelConsumption; 29590 break; 29591 } 29592 break; 29593 case "mpg(imp)": 29594 switch (to) { 29595 case "km/liter": 29596 returnValue = fuelConsumption * 0.354006; 29597 break; 29598 case "liter/100km": 29599 returnValue = 282.481 / fuelConsumption; 29600 break; 29601 case "mpg": 29602 returnValue = 0.832674 * fuelConsumption; 29603 break; 29604 case "mpg(imp)": 29605 returnValue = 1 * fuelConsumption; 29606 break; 29607 } 29608 break; 29609 } 29610 return returnValue; 29611 }; 29612 29613 /** 29614 * Scale the measurement unit to an acceptable level. The scaling 29615 * happens so that the integer part of the amount is as small as 29616 * possible without being below zero. This will result in the 29617 * largest units that can represent this measurement without 29618 * fractions. Measurements can only be scaled to other measurements 29619 * of the same type. 29620 * 29621 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 29622 * or undefined if the system can be inferred from the current measure 29623 * @return {Measurement} a new instance that is scaled to the 29624 * right level 29625 */ 29626 FuelConsumptionUnit.prototype.scale = function(measurementsystem) { 29627 return new FuelConsumptionUnit({ 29628 unit: this.unit, 29629 amount: this.amount 29630 }); 29631 }; 29632 29633 /** 29634 * @private 29635 * @static 29636 */ 29637 FuelConsumptionUnit.getMeasures = function() { 29638 var ret = []; 29639 ret.push("km/liter"); 29640 ret.push("liter/100km"); 29641 ret.push("mpg"); 29642 ret.push("mpg(imp)"); 29643 29644 return ret; 29645 }; 29646 29647 //register with the factory method 29648 Measurement._constructors["fuelconsumption"] = FuelConsumptionUnit; 29649 29650 29651 /*< LengthUnit.js */ 29652 /* 29653 * LengthUnit.js - Unit conversions for Lengths/lengths 29654 * 29655 * Copyright © 2014-2015, JEDLSoft 29656 * 29657 * Licensed under the Apache License, Version 2.0 (the "License"); 29658 * you may not use this file except in compliance with the License. 29659 * You may obtain a copy of the License at 29660 * 29661 * http://www.apache.org/licenses/LICENSE-2.0 29662 * 29663 * Unless required by applicable law or agreed to in writing, software 29664 * distributed under the License is distributed on an "AS IS" BASIS, 29665 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29666 * 29667 * See the License for the specific language governing permissions and 29668 * limitations under the License. 29669 */ 29670 29671 /* 29672 !depends 29673 Measurement.js 29674 */ 29675 29676 29677 /** 29678 * @class 29679 * Create a new length measurement instance. 29680 * 29681 * @constructor 29682 * @extends Measurement 29683 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 29684 * the construction of this instance 29685 */ 29686 var LengthUnit = function (options) { 29687 this.unit = "meter"; 29688 this.amount = 0; 29689 this.aliases = LengthUnit.aliases; // share this table in all instances 29690 29691 if (options) { 29692 if (typeof(options.unit) !== 'undefined') { 29693 this.originalUnit = options.unit; 29694 this.unit = this.aliases[options.unit] || options.unit; 29695 } 29696 29697 if (typeof(options.amount) === 'object') { 29698 if (options.amount.getMeasure() === "length") { 29699 this.amount = LengthUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 29700 } else { 29701 throw "Cannot convert unit " + options.amount.unit + " to a length"; 29702 } 29703 } else if (typeof(options.amount) !== 'undefined') { 29704 this.amount = parseFloat(options.amount); 29705 } 29706 } 29707 29708 if (typeof(LengthUnit.ratios[this.unit]) === 'undefined') { 29709 throw "Unknown unit: " + options.unit; 29710 } 29711 }; 29712 29713 LengthUnit.prototype = new Measurement(); 29714 LengthUnit.prototype.parent = Measurement; 29715 LengthUnit.prototype.constructor = LengthUnit; 29716 29717 LengthUnit.ratios = { 29718 /* index, µm mm cm inch dm foot yard m dam hm km mile nm Mm Gm */ 29719 "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 ], 29720 "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 ], 29721 "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 ], 29722 "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 ], 29723 "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 ], 29724 "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 ], 29725 "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 ], 29726 "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 ], 29727 "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 ], 29728 "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 ], 29729 "kilometer": [ 11, 1e9, 1e6, 1e5, 39370.1, 1e4, 3280.84, 1093.61, 1000, 100, 10, 1, 0.621373, 0.539957, 0.001, 1e-4 ], 29730 "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 ], 29731 "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 ], 29732 "megameter": [ 14, 1e12, 1e9, 1e6, 3.93701e7, 1e5, 3.28084e6, 1.09361e6, 1e4, 1000, 100, 10, 621.373, 539.957, 1, 0.001 ], 29733 "gigameter": [ 15, 1e15, 1e12, 1e9, 3.93701e10, 1e8, 3.28084e9, 1.09361e9, 1e7, 1e6, 1e5, 1e4, 621373.0, 539957.0, 1000, 1 ] 29734 }; 29735 29736 LengthUnit.metricSystem = { 29737 "micrometer": 1, 29738 "millimeter": 2, 29739 "centimeter": 3, 29740 "decimeter": 5, 29741 "meter": 8, 29742 "decameter": 9, 29743 "hectometer": 10, 29744 "kilometer": 11, 29745 "megameter": 14, 29746 "gigameter": 15 29747 }; 29748 LengthUnit.imperialSystem = { 29749 "inch": 4, 29750 "foot": 6, 29751 "yard": 7, 29752 "mile": 12, 29753 "nauticalmile": 13 29754 }; 29755 LengthUnit.uscustomarySystem = { 29756 "inch": 4, 29757 "foot": 6, 29758 "yard": 7, 29759 "mile": 12, 29760 "nauticalmile": 13 29761 }; 29762 29763 LengthUnit.metricToUScustomary = { 29764 "micrometer": "inch", 29765 "millimeter": "inch", 29766 "centimeter": "inch", 29767 "decimeter": "inch", 29768 "meter": "yard", 29769 "decameter": "yard", 29770 "hectometer": "mile", 29771 "kilometer": "mile", 29772 "megameter": "nauticalmile", 29773 "gigameter": "nauticalmile" 29774 }; 29775 LengthUnit.usCustomaryToMetric = { 29776 "inch": "centimeter", 29777 "foot": "centimeter", 29778 "yard": "meter", 29779 "mile": "kilometer", 29780 "nauticalmile": "kilometer" 29781 }; 29782 29783 /** 29784 * Return the type of this measurement. Examples are "mass", 29785 * "length", "speed", etc. Measurements can only be converted 29786 * to measurements of the same type.<p> 29787 * 29788 * The type of the units is determined automatically from the 29789 * units. For example, the unit "grams" is type "mass". Use the 29790 * static call {@link Measurement.getAvailableUnits} 29791 * to find out what units this version of ilib supports. 29792 * 29793 * @return {string} the name of the type of this measurement 29794 */ 29795 LengthUnit.prototype.getMeasure = function() { 29796 return "length"; 29797 }; 29798 29799 /** 29800 * Localize the measurement to the commonly used measurement in that locale. For example 29801 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29802 * the formatted number should be automatically converted to the most appropriate 29803 * measure in the other system, in this case, mph. The formatted result should 29804 * appear as "37.3 mph". 29805 * 29806 * @abstract 29807 * @param {string} locale current locale string 29808 * @returns {Measurement} a new instance that is converted to locale 29809 */ 29810 LengthUnit.prototype.localize = function(locale) { 29811 var to; 29812 if (locale === "en-US" || locale === "en-GB") { 29813 to = LengthUnit.metricToUScustomary[this.unit] || this.unit; 29814 } else { 29815 to = LengthUnit.usCustomaryToMetric[this.unit] || this.unit; 29816 } 29817 return new LengthUnit({ 29818 unit: to, 29819 amount: this 29820 }); 29821 }; 29822 29823 /** 29824 * Return a new measurement instance that is converted to a new 29825 * measurement unit. Measurements can only be converted 29826 * to measurements of the same type.<p> 29827 * 29828 * @param {string} to The name of the units to convert to 29829 * @return {Measurement|undefined} the converted measurement 29830 * or undefined if the requested units are for a different 29831 * measurement type 29832 */ 29833 LengthUnit.prototype.convert = function(to) { 29834 if (!to || typeof(LengthUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 29835 return undefined; 29836 } 29837 return new LengthUnit({ 29838 unit: to, 29839 amount: this 29840 }); 29841 }; 29842 29843 /** 29844 * Scale the measurement unit to an acceptable level. The scaling 29845 * happens so that the integer part of the amount is as small as 29846 * possible without being below zero. This will result in the 29847 * largest units that can represent this measurement without 29848 * fractions. Measurements can only be scaled to other measurements 29849 * of the same type. 29850 * 29851 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 29852 * or undefined if the system can be inferred from the current measure 29853 * @return {Measurement} a new instance that is scaled to the 29854 * right level 29855 */ 29856 LengthUnit.prototype.scale = function(measurementsystem) { 29857 var mSystem; 29858 if (measurementsystem === "metric" || (typeof(measurementsystem) === 'undefined' 29859 && typeof(LengthUnit.metricSystem[this.unit]) !== 'undefined')) { 29860 mSystem = LengthUnit.metricSystem; 29861 } else if (measurementsystem === "imperial" || (typeof(measurementsystem) === 'undefined' 29862 && typeof(LengthUnit.imperialSystem[this.unit]) !== 'undefined')) { 29863 mSystem = LengthUnit.imperialSystem; 29864 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 29865 && typeof(LengthUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 29866 mSystem = LengthUnit.uscustomarySystem; 29867 } else { 29868 return new LengthUnit({ 29869 unit: this.unit, 29870 amount: this.amount 29871 }); 29872 } 29873 29874 var length = this.amount; 29875 var munit = this.unit; 29876 var fromRow = LengthUnit.ratios[this.unit]; 29877 29878 length = 18446744073709551999; 29879 for (var m in mSystem) { 29880 var tmp = this.amount * fromRow[mSystem[m]]; 29881 if (tmp >= 1 && tmp < length) { 29882 length = tmp; 29883 munit = m; 29884 } 29885 } 29886 29887 return new LengthUnit({ 29888 unit: munit, 29889 amount: length 29890 }); 29891 }; 29892 29893 LengthUnit.aliases = { 29894 "miles": "mile", 29895 "mile":"mile", 29896 "nauticalmiles": "nauticalmile", 29897 "nautical mile": "nauticalmile", 29898 "nautical miles": "nauticalmile", 29899 "nauticalmile":"nauticalmile", 29900 "yards": "yard", 29901 "yard": "yard", 29902 "feet": "foot", 29903 "foot": "foot", 29904 "inches": "inch", 29905 "inch": "inch", 29906 "meters": "meter", 29907 "metre": "meter", 29908 "metres": "meter", 29909 "m": "meter", 29910 "meter": "meter", 29911 "micrometers": "micrometer", 29912 "micrometres": "micrometer", 29913 "micrometre": "micrometer", 29914 "µm": "micrometer", 29915 "micrometer": "micrometer", 29916 "millimeters": "millimeter", 29917 "millimetres": "millimeter", 29918 "millimetre": "millimeter", 29919 "mm": "millimeter", 29920 "millimeter": "millimeter", 29921 "centimeters": "centimeter", 29922 "centimetres": "centimeter", 29923 "centimetre": "centimeter", 29924 "cm": "centimeter", 29925 "centimeter": "centimeter", 29926 "decimeters": "decimeter", 29927 "decimetres": "decimeter", 29928 "decimetre": "decimeter", 29929 "dm": "decimeter", 29930 "decimeter": "decimeter", 29931 "decameters": "decameter", 29932 "decametres": "decameter", 29933 "decametre": "decameter", 29934 "dam": "decameter", 29935 "decameter": "decameter", 29936 "hectometers": "hectometer", 29937 "hectometres": "hectometer", 29938 "hectometre": "hectometer", 29939 "hm": "hectometer", 29940 "hectometer": "hectometer", 29941 "kilometers": "kilometer", 29942 "kilometres": "kilometer", 29943 "kilometre": "kilometer", 29944 "km": "kilometer", 29945 "kilometer": "kilometer", 29946 "megameters": "megameter", 29947 "megametres": "megameter", 29948 "megametre": "megameter", 29949 "Mm": "megameter", 29950 "megameter": "megameter", 29951 "gigameters": "gigameter", 29952 "gigametres": "gigameter", 29953 "gigametre": "gigameter", 29954 "Gm": "gigameter", 29955 "gigameter": "gigameter" 29956 }; 29957 29958 /** 29959 * Convert a length to another measure. 29960 * @static 29961 * @param to {string} unit to convert to 29962 * @param from {string} unit to convert from 29963 * @param length {number} amount to be convert 29964 * @returns {number|undefined} the converted amount 29965 */ 29966 LengthUnit.convert = function(to, from, length) { 29967 from = LengthUnit.aliases[from] || from; 29968 to = LengthUnit.aliases[to] || to; 29969 var fromRow = LengthUnit.ratios[from]; 29970 var toRow = LengthUnit.ratios[to]; 29971 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 29972 return undefined; 29973 } 29974 return length * fromRow[toRow[0]]; 29975 }; 29976 29977 /** 29978 * @private 29979 * @static 29980 */ 29981 LengthUnit.getMeasures = function () { 29982 var ret = []; 29983 for (var m in LengthUnit.ratios) { 29984 ret.push(m); 29985 } 29986 return ret; 29987 }; 29988 29989 //register with the factory method 29990 Measurement._constructors["length"] = LengthUnit; 29991 29992 29993 /*< MassUnit.js */ 29994 /* 29995 * MassUnit.js - Unit conversions for Mass/mass 29996 * 29997 * Copyright © 2014-2015, JEDLSoft 29998 * 29999 * Licensed under the Apache License, Version 2.0 (the "License"); 30000 * you may not use this file except in compliance with the License. 30001 * You may obtain a copy of the License at 30002 * 30003 * http://www.apache.org/licenses/LICENSE-2.0 30004 * 30005 * Unless required by applicable law or agreed to in writing, software 30006 * distributed under the License is distributed on an "AS IS" BASIS, 30007 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30008 * 30009 * See the License for the specific language governing permissions and 30010 * limitations under the License. 30011 */ 30012 30013 /* 30014 !depends 30015 Measurement.js 30016 */ 30017 30018 30019 /** 30020 * @class 30021 * Create a new mass measurement instance. 30022 * 30023 * @constructor 30024 * @extends Measurement 30025 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30026 * the construction of this instance 30027 */ 30028 var MassUnit = function (options) { 30029 this.unit = "gram"; 30030 this.amount = 0; 30031 this.aliases = MassUnit.aliases; // share this table in all instances 30032 30033 if (options) { 30034 if (typeof(options.unit) !== 'undefined') { 30035 this.originalUnit = options.unit; 30036 this.unit = this.aliases[options.unit] || options.unit; 30037 } 30038 30039 if (typeof(options.amount) === 'object') { 30040 if (options.amount.getMeasure() === "mass") { 30041 this.amount = MassUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30042 } else { 30043 throw "Cannot convert units " + options.amount.unit + " to a mass"; 30044 } 30045 } else if (typeof(options.amount) !== 'undefined') { 30046 this.amount = parseFloat(options.amount); 30047 } 30048 } 30049 30050 if (typeof(MassUnit.ratios[this.unit]) === 'undefined') { 30051 throw "Unknown unit: " + options.unit; 30052 } 30053 }; 30054 30055 MassUnit.prototype = new Measurement(); 30056 MassUnit.prototype.parent = Measurement; 30057 MassUnit.prototype.constructor = MassUnit; 30058 30059 MassUnit.ratios = { 30060 /* index µg mg g oz lp kg st sh ton mt ton ln ton */ 30061 "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 ], 30062 "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 ], 30063 "gram": [ 3, 1e+6, 1000, 1, 0.035274, 0.00220462, 0.001, 0.000157473, 1.1023e-6, 1e-6, 9.8421e-7 ], 30064 "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 ], 30065 "pound": [ 5, 4.536e+8, 453592, 453.592, 16, 1, 0.453592, 0.0714286, 0.0005, 0.000453592, 0.000446429 ], 30066 "kilogram": [ 6, 1e+9, 1e+6, 1000, 35.274, 2.20462, 1, 0.157473, 0.00110231, 0.001, 0.000984207 ], 30067 "stone": [ 7, 6.35e+9, 6.35e+6, 6350.29, 224, 14, 6.35029, 1, 0.007, 0.00635029, 0.00625 ], 30068 "short ton": [ 8, 9.072e+11, 9.072e+8, 907185, 32000, 2000, 907.185, 142.857, 1, 0.907185, 0.892857 ], 30069 "metric ton": [ 9, 1e+12, 1e+9, 1e+6, 35274, 2204.62, 1000, 157.473, 1.10231, 1, 0.984207 ], 30070 "long ton": [ 10, 1.016e+12, 1.016e+9, 1.016e+6, 35840, 2240, 1016.05, 160, 1.12, 1.01605, 1 ] 30071 }; 30072 30073 MassUnit.metricSystem = { 30074 "microgram": 1, 30075 "milligram": 2, 30076 "gram": 3, 30077 "kilogram": 6, 30078 "metric ton": 9 30079 }; 30080 MassUnit.imperialSystem = { 30081 "ounce": 4, 30082 "pound": 5, 30083 "stone": 7, 30084 "long ton": 10 30085 }; 30086 MassUnit.uscustomarySystem = { 30087 "ounce": 4, 30088 "pound": 5, 30089 "short ton": 8 30090 }; 30091 30092 MassUnit.metricToUScustomary = { 30093 "microgram": "ounce", 30094 "milligram": "ounce", 30095 "gram": "ounce", 30096 "kilogram": "pound", 30097 "metric ton": "long ton" 30098 }; 30099 MassUnit.metricToImperial = { 30100 "microgram": "ounce", 30101 "milligram": "ounce", 30102 "gram": "ounce", 30103 "kilogram": "pound", 30104 "metric ton": "short ton" 30105 }; 30106 30107 MassUnit.imperialToMetric = { 30108 "ounce": "gram", 30109 "pound": "kilogram", 30110 "stone": "kilogram", 30111 "short ton": "metric ton" 30112 }; 30113 MassUnit.imperialToUScustomary = { 30114 "ounce": "ounce", 30115 "pound": "pound", 30116 "stone": "stone", 30117 "short ton": "long ton" 30118 }; 30119 30120 MassUnit.uScustomaryToImperial = { 30121 "ounce": "ounce", 30122 "pound": "pound", 30123 "stone": "stone", 30124 "long ton": "short ton" 30125 }; 30126 MassUnit.uScustomarylToMetric = { 30127 "ounce": "gram", 30128 "pound": "kilogram", 30129 "stone": "kilogram", 30130 "long ton": "metric ton" 30131 }; 30132 30133 /** 30134 * Localize the measurement to the commonly used measurement in that locale. For example 30135 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30136 * the formatted number should be automatically converted to the most appropriate 30137 * measure in the other system, in this case, mph. The formatted result should 30138 * appear as "37.3 mph". 30139 * 30140 * @abstract 30141 * @param {string} locale current locale string 30142 * @returns {Measurement} a new instance that is converted to locale 30143 */ 30144 MassUnit.prototype.localize = function(locale) { 30145 var to; 30146 if (locale === "en-US") { 30147 to = MassUnit.metricToUScustomary[this.unit] || 30148 MassUnit.imperialToUScustomary[this.unit] || this.unit; 30149 } else if (locale === "en-GB") { 30150 to = MassUnit.metricToImperial[this.unit] || 30151 MassUnit.uScustomaryToImperial[this.unit] || this.unit; 30152 } else { 30153 to = MassUnit.uScustomarylToMetric[this.unit] || 30154 MassUnit.imperialToUScustomary[this.unit] || this.unit; 30155 } 30156 return new MassUnit({ 30157 unit: to, 30158 amount: this 30159 }); 30160 }; 30161 30162 /** 30163 * Return the type of this measurement. Examples are "mass", 30164 * "length", "speed", etc. Measurements can only be converted 30165 * to measurements of the same type.<p> 30166 * 30167 * The type of the units is determined automatically from the 30168 * units. For example, the unit "grams" is type "mass". Use the 30169 * static call {@link Measurement.getAvailableUnits} 30170 * to find out what units this version of ilib supports. 30171 * 30172 * @return {string} the name of the type of this measurement 30173 */ 30174 MassUnit.prototype.getMeasure = function() { 30175 return "mass"; 30176 }; 30177 30178 /** 30179 * Return a new measurement instance that is converted to a new 30180 * measurement unit. Measurements can only be converted 30181 * to measurements of the same type.<p> 30182 * 30183 * @param {string} to The name of the units to convert to 30184 * @return {Measurement|undefined} the converted measurement 30185 * or undefined if the requested units are for a different 30186 * measurement type 30187 */ 30188 MassUnit.prototype.convert = function(to) { 30189 if (!to || typeof(MassUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30190 return undefined; 30191 } 30192 return new MassUnit({ 30193 unit: to, 30194 amount: this 30195 }); 30196 }; 30197 30198 MassUnit.aliases = { 30199 "µg":"microgram", 30200 "microgram":"microgram", 30201 "mcg":"microgram", 30202 "milligram":"milligram", 30203 "mg":"milligram", 30204 "milligrams":"milligram", 30205 "Milligram":"milligram", 30206 "Milligrams":"milligram", 30207 "MilliGram":"milligram", 30208 "MilliGrams":"milligram", 30209 "g":"gram", 30210 "gram":"gram", 30211 "grams":"gram", 30212 "Gram":"gram", 30213 "Grams":"gram", 30214 "ounce":"ounce", 30215 "oz":"ounce", 30216 "Ounce":"ounce", 30217 "℥":"ounce", 30218 "pound":"pound", 30219 "poundm":"pound", 30220 "℔":"pound", 30221 "lb":"pound", 30222 "pounds":"pound", 30223 "Pound":"pound", 30224 "Pounds":"pound", 30225 "kilogram":"kilogram", 30226 "kg":"kilogram", 30227 "kilograms":"kilogram", 30228 "kilo grams":"kilogram", 30229 "kilo gram":"kilogram", 30230 "Kilogram":"kilogram", 30231 "Kilograms":"kilogram", 30232 "KiloGram":"kilogram", 30233 "KiloGrams":"kilogram", 30234 "Kilo gram":"kilogram", 30235 "Kilo grams":"kilogram", 30236 "Kilo Gram":"kilogram", 30237 "Kilo Grams":"kilogram", 30238 "stone":"stone", 30239 "st":"stone", 30240 "stones":"stone", 30241 "Stone":"stone", 30242 "short ton":"short ton", 30243 "Short ton":"short ton", 30244 "Short Ton":"short ton", 30245 "metric ton":"metric ton", 30246 "metricton":"metric ton", 30247 "t":"metric ton", 30248 "tonne":"metric ton", 30249 "Tonne":"metric ton", 30250 "Metric Ton":"metric ton", 30251 "MetricTon":"metric ton", 30252 "long ton":"long ton", 30253 "longton":"long ton", 30254 "Longton":"long ton", 30255 "Long ton":"long ton", 30256 "Long Ton":"long ton", 30257 "ton":"long ton", 30258 "Ton":"long ton" 30259 }; 30260 30261 /** 30262 * Convert a mass to another measure. 30263 * @static 30264 * @param to {string} unit to convert to 30265 * @param from {string} unit to convert from 30266 * @param mass {number} amount to be convert 30267 * @returns {number|undefined} the converted amount 30268 */ 30269 MassUnit.convert = function(to, from, mass) { 30270 from = MassUnit.aliases[from] || from; 30271 to = MassUnit.aliases[to] || to; 30272 var fromRow = MassUnit.ratios[from]; 30273 var toRow = MassUnit.ratios[to]; 30274 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 30275 return undefined; 30276 } 30277 return mass * fromRow[toRow[0]]; 30278 }; 30279 30280 /** 30281 * Scale the measurement unit to an acceptable level. The scaling 30282 * happens so that the integer part of the amount is as small as 30283 * possible without being below zero. This will result in the 30284 * largest units that can represent this measurement without 30285 * fractions. Measurements can only be scaled to other measurements 30286 * of the same type. 30287 * 30288 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30289 * or undefined if the system can be inferred from the current measure 30290 * @return {Measurement} a new instance that is scaled to the 30291 * right level 30292 */ 30293 MassUnit.prototype.scale = function(measurementsystem) { 30294 var mSystem; 30295 if (measurementsystem === "metric" || (typeof(measurementsystem) === 'undefined' 30296 && typeof(MassUnit.metricSystem[this.unit]) !== 'undefined')) { 30297 mSystem = MassUnit.metricSystem; 30298 } else if (measurementsystem === "imperial" || (typeof(measurementsystem) === 'undefined' 30299 && typeof(MassUnit.imperialSystem[this.unit]) !== 'undefined')) { 30300 mSystem = MassUnit.imperialSystem; 30301 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 30302 && typeof(MassUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 30303 mSystem = MassUnit.uscustomarySystem; 30304 } else { 30305 return new MassUnit({ 30306 unit: this.unit, 30307 amount: this.amount 30308 }); 30309 } 30310 30311 var mass = this.amount; 30312 var munit = this.amount; 30313 var fromRow = MassUnit.ratios[this.unit]; 30314 30315 mass = 18446744073709551999; 30316 30317 for (var m in mSystem) { 30318 var tmp = this.amount * fromRow[mSystem[m]]; 30319 if (tmp >= 1 && tmp < mass) { 30320 mass = tmp; 30321 munit = m; 30322 } 30323 } 30324 30325 return new MassUnit({ 30326 unit: munit, 30327 amount: mass 30328 }); 30329 }; 30330 30331 /** 30332 * @private 30333 * @static 30334 */ 30335 MassUnit.getMeasures = function () { 30336 var ret = []; 30337 for (var m in MassUnit.ratios) { 30338 ret.push(m); 30339 } 30340 return ret; 30341 }; 30342 30343 //register with the factory method 30344 Measurement._constructors["mass"] = MassUnit; 30345 30346 30347 /*< TemperatureUnit.js */ 30348 /* 30349 * temperature.js - Unit conversions for Temperature/temperature 30350 * 30351 * Copyright © 2014-2015, JEDLSoft 30352 * 30353 * Licensed under the Apache License, Version 2.0 (the "License"); 30354 * you may not use this file except in compliance with the License. 30355 * You may obtain a copy of the License at 30356 * 30357 * http://www.apache.org/licenses/LICENSE-2.0 30358 * 30359 * Unless required by applicable law or agreed to in writing, software 30360 * distributed under the License is distributed on an "AS IS" BASIS, 30361 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30362 * 30363 * See the License for the specific language governing permissions and 30364 * limitations under the License. 30365 */ 30366 30367 /* 30368 !depends 30369 Measurement.js 30370 */ 30371 30372 30373 /** 30374 * @class 30375 * Create a new Temperature measurement instance. 30376 * 30377 * @constructor 30378 * @extends Measurement 30379 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30380 * the construction of this instance 30381 */ 30382 var TemperatureUnit = function (options) { 30383 this.unit = "celsius"; 30384 this.amount = 0; 30385 this.aliases = TemperatureUnit.aliases; // share this table in all instances 30386 30387 if (options) { 30388 if (typeof(options.unit) !== 'undefined') { 30389 this.originalUnit = options.unit; 30390 this.unit = this.aliases[options.unit] || options.unit; 30391 } 30392 30393 if (typeof(options.amount) === 'object') { 30394 if (options.amount.getMeasure() === "temperature") { 30395 this.amount = TemperatureUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30396 } else { 30397 throw "Cannot convert unit " + options.amount.unit + " to a temperature"; 30398 } 30399 } else if (typeof(options.amount) !== 'undefined') { 30400 this.amount = parseFloat(options.amount); 30401 } 30402 } 30403 }; 30404 30405 TemperatureUnit.prototype = new Measurement(); 30406 TemperatureUnit.prototype.parent = Measurement; 30407 TemperatureUnit.prototype.constructor = TemperatureUnit; 30408 30409 /** 30410 * Return the type of this measurement. Examples are "mass", 30411 * "length", "speed", etc. Measurements can only be converted 30412 * to measurements of the same type.<p> 30413 * 30414 * The type of the units is determined automatically from the 30415 * units. For example, the unit "grams" is type "mass". Use the 30416 * static call {@link Measurement.getAvailableUnits} 30417 * to find out what units this version of ilib supports. 30418 * 30419 * @return {string} the name of the type of this measurement 30420 */ 30421 TemperatureUnit.prototype.getMeasure = function() { 30422 return "temperature"; 30423 }; 30424 30425 TemperatureUnit.aliases = { 30426 "Celsius": "celsius", 30427 "celsius": "celsius", 30428 "C": "celsius", 30429 "centegrade": "celsius", 30430 "Centegrade": "celsius", 30431 "centigrade": "celsius", 30432 "Centigrade": "celsius", 30433 "fahrenheit": "fahrenheit", 30434 "Fahrenheit": "fahrenheit", 30435 "F": "fahrenheit", 30436 "kelvin": "kelvin", 30437 "K": "kelvin", 30438 "Kelvin": "kelvin", 30439 "°F": "fahrenheit", 30440 "℉": "fahrenheit", 30441 "℃": "celsius", 30442 "°C": "celsius" 30443 }; 30444 30445 /** 30446 * Return a new measurement instance that is converted to a new 30447 * measurement unit. Measurements can only be converted 30448 * to measurements of the same type.<p> 30449 * 30450 * @param {string} to The name of the units to convert to 30451 * @return {Measurement|undefined} the converted measurement 30452 * or undefined if the requested units are for a different 30453 * measurement type 30454 */ 30455 TemperatureUnit.prototype.convert = function(to) { 30456 if (!to || typeof(TemperatureUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30457 return undefined; 30458 } 30459 return new TemperatureUnit({ 30460 unit: to, 30461 amount: this 30462 }); 30463 }; 30464 30465 /** 30466 * Convert a temperature to another measure. 30467 * @static 30468 * @param to {string} unit to convert to 30469 * @param from {string} unit to convert from 30470 * @param temperature {number} amount to be convert 30471 * @returns {number|undefined} the converted amount 30472 */ 30473 TemperatureUnit.convert = function(to, from, temperature) { 30474 var result = 0; 30475 from = TemperatureUnit.aliases[from] || from; 30476 to = TemperatureUnit.aliases[to] || to; 30477 if (from === to) 30478 return temperature; 30479 30480 else if (from === "celsius") { 30481 if (to === "fahrenheit") { 30482 result = ((temperature * 9 / 5) + 32); 30483 } else if (to === "kelvin") { 30484 result = (temperature + 273.15); 30485 } 30486 30487 } else if (from === "fahrenheit") { 30488 if (to === "celsius") { 30489 result = ((5 / 9 * (temperature - 32))); 30490 } else if (to === "kelvin") { 30491 result = ((temperature + 459.67) * 5 / 9); 30492 } 30493 } else if (from === "kelvin") { 30494 if (to === "celsius") { 30495 result = (temperature - 273.15); 30496 } else if (to === "fahrenheit") { 30497 result = ((temperature * 9 / 5) - 459.67); 30498 } 30499 } 30500 30501 return result; 30502 }; 30503 30504 /** 30505 * Scale the measurement unit to an acceptable level. The scaling 30506 * happens so that the integer part of the amount is as small as 30507 * possible without being below zero. This will result in the 30508 * largest units that can represent this measurement without 30509 * fractions. Measurements can only be scaled to other measurements 30510 * of the same type. 30511 * 30512 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30513 * or undefined if the system can be inferred from the current measure 30514 * @return {Measurement} a new instance that is scaled to the 30515 * right level 30516 */ 30517 TemperatureUnit.prototype.scale = function(measurementsystem) { 30518 return new TemperatureUnit({ 30519 unit: this.unit, 30520 amount: this.amount 30521 }); 30522 }; 30523 30524 /** 30525 * @private 30526 * @static 30527 */ 30528 TemperatureUnit.getMeasures = function () { 30529 return ["celsius", "kelvin", "fahrenheit"]; 30530 }; 30531 TemperatureUnit.metricToUScustomary = { 30532 "celsius": "fahrenheit" 30533 }; 30534 TemperatureUnit.usCustomaryToMetric = { 30535 "fahrenheit": "celsius" 30536 }; 30537 30538 /** 30539 * Localize the measurement to the commonly used measurement in that locale. For example 30540 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30541 * the formatted number should be automatically converted to the most appropriate 30542 * measure in the other system, in this case, mph. The formatted result should 30543 * appear as "37.3 mph". 30544 * 30545 * @abstract 30546 * @param {string} locale current locale string 30547 * @returns {Measurement} a new instance that is converted to locale 30548 */ 30549 TemperatureUnit.prototype.localize = function(locale) { 30550 var to; 30551 if (locale === "en-US" ) { 30552 to = TemperatureUnit.metricToUScustomary[this.unit] || this.unit; 30553 } else { 30554 to = TemperatureUnit.usCustomaryToMetric[this.unit] || this.unit; 30555 } 30556 return new TemperatureUnit({ 30557 unit: to, 30558 amount: this 30559 }); 30560 }; 30561 //register with the factory method 30562 Measurement._constructors["temperature"] = TemperatureUnit; 30563 30564 30565 /*< TimeUnit.js */ 30566 /* 30567 * Time.js - Unit conversions for Times/times 30568 * 30569 * Copyright © 2014-2015, JEDLSoft 30570 * 30571 * Licensed under the Apache License, Version 2.0 (the "License"); 30572 * you may not use this file except in compliance with the License. 30573 * You may obtain a copy of the License at 30574 * 30575 * http://www.apache.org/licenses/LICENSE-2.0 30576 * 30577 * Unless required by applicable law or agreed to in writing, software 30578 * distributed under the License is distributed on an "AS IS" BASIS, 30579 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30580 * 30581 * See the License for the specific language governing permissions and 30582 * limitations under the License. 30583 */ 30584 30585 /* 30586 !depends 30587 Measurement.js 30588 */ 30589 30590 30591 /** 30592 * @class 30593 * Create a new time measurement instance. 30594 * 30595 * @constructor 30596 * @extends Measurement 30597 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30598 * the construction of this instance 30599 */ 30600 var TimeUnit = function (options) { 30601 this.unit = "second"; 30602 this.amount = 0; 30603 this.aliases = TimeUnit.aliases; // share this table in all instances 30604 30605 if (options) { 30606 if (typeof(options.unit) !== 'undefined') { 30607 this.originalUnit = options.unit; 30608 this.unit = this.aliases[options.unit] || options.unit; 30609 } 30610 30611 if (typeof(options.amount) === 'object') { 30612 if (options.amount.getMeasure() === "time") { 30613 this.amount = TimeUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30614 } else { 30615 throw "Cannot convert units " + options.amount.unit + " to a time"; 30616 } 30617 } else if (typeof(options.amount) !== 'undefined') { 30618 this.amount = parseFloat(options.amount); 30619 } 30620 } 30621 30622 if (typeof(TimeUnit.ratios[this.unit]) === 'undefined') { 30623 throw "Unknown unit: " + options.unit; 30624 } 30625 }; 30626 30627 TimeUnit.prototype = new Measurement(); 30628 TimeUnit.prototype.parent = Measurement; 30629 TimeUnit.prototype.constructor = TimeUnit; 30630 30631 TimeUnit.ratios = { 30632 /* index nsec msec mlsec sec min hour day week month year decade century */ 30633 "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 ], 30634 "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 ], 30635 "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 ], 30636 "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 ], 30637 "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 ], 30638 "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 ], 30639 "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 ], 30640 "week": [ 8, 6.048e+14, 6.048e+11, 6.048e+8, 604800, 10080, 168, 7, 1, 0.229984, 0.0191654, 0.00191654, 0.000191654 ], 30641 "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 ], 30642 "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 ], 30643 "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 ], 30644 "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 ] 30645 }; 30646 30647 /** 30648 * Return the type of this measurement. Examples are "mass", 30649 * "length", "speed", etc. Measurements can only be converted 30650 * to measurements of the same type.<p> 30651 * 30652 * The type of the units is determined automatically from the 30653 * units. For example, the unit "grams" is type "mass". Use the 30654 * static call {@link Measurement.getAvailableUnits} 30655 * to find out what units this version of ilib supports. 30656 * 30657 * @return {string} the name of the type of this measurement 30658 */ 30659 TimeUnit.prototype.getMeasure = function() { 30660 return "time"; 30661 }; 30662 30663 /** 30664 * Return a new measurement instance that is converted to a new 30665 * measurement unit. Measurements can only be converted 30666 * to measurements of the same type.<p> 30667 * 30668 * @param {string} to The name of the units to convert to 30669 * @return {Measurement|undefined} the converted measurement 30670 * or undefined if the requested units are for a different 30671 * measurement type 30672 */ 30673 TimeUnit.prototype.convert = function(to) { 30674 if (!to || typeof(TimeUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30675 return undefined; 30676 } 30677 return new TimeUnit({ 30678 unit: to, 30679 amount: this 30680 }); 30681 }; 30682 30683 TimeUnit.aliases = { 30684 "ns": "nanosecond", 30685 "NS": "nanosecond", 30686 "nS": "nanosecond", 30687 "Ns": "nanosecond", 30688 "Nanosecond": "nanosecond", 30689 "Nanoseconds": "nanosecond", 30690 "nanosecond": "nanosecond", 30691 "nanoseconds": "nanosecond", 30692 "NanoSecond": "nanosecond", 30693 "NanoSeconds": "nanosecond", 30694 "μs": "microsecond", 30695 "μS": "microsecond", 30696 "microsecond": "microsecond", 30697 "microseconds": "microsecond", 30698 "Microsecond": "microsecond", 30699 "Microseconds": "microsecond", 30700 "MicroSecond": "microsecond", 30701 "MicroSeconds": "microsecond", 30702 "ms": "millisecond", 30703 "MS": "millisecond", 30704 "mS": "millisecond", 30705 "Ms": "millisecond", 30706 "millisecond": "millisecond", 30707 "milliseconds": "millisecond", 30708 "Millisecond": "millisecond", 30709 "Milliseconds": "millisecond", 30710 "MilliSecond": "millisecond", 30711 "MilliSeconds": "millisecond", 30712 "s": "second", 30713 "S": "second", 30714 "sec": "second", 30715 "second": "second", 30716 "seconds": "second", 30717 "Second": "second", 30718 "Seconds": "second", 30719 "min": "minute", 30720 "Min": "minute", 30721 "minute": "minute", 30722 "minutes": "minute", 30723 "Minute": "minute", 30724 "Minutes": "minute", 30725 "h": "hour", 30726 "H": "hour", 30727 "hr": "hour", 30728 "Hr": "hour", 30729 "hR": "hour", 30730 "HR": "hour", 30731 "hour": "hour", 30732 "hours": "hour", 30733 "Hour": "hour", 30734 "Hours": "hour", 30735 "Hrs": "hour", 30736 "hrs": "hour", 30737 "day": "day", 30738 "days": "day", 30739 "Day": "day", 30740 "Days": "day", 30741 "week": "week", 30742 "weeks": "week", 30743 "Week": "week", 30744 "Weeks": "week", 30745 "month": "month", 30746 "Month": "month", 30747 "months": "month", 30748 "Months": "month", 30749 "year": "year", 30750 "years": "year", 30751 "Year": "year", 30752 "Years": "year", 30753 "yr": "year", 30754 "Yr": "year", 30755 "yrs": "year", 30756 "Yrs": "year", 30757 "decade": "decade", 30758 "decades": "decade", 30759 "Decade": "decade", 30760 "Decades": "decade", 30761 "century": "century", 30762 "centuries": "century", 30763 "Century": "century", 30764 "Centuries": "century" 30765 }; 30766 30767 /** 30768 * Convert a time to another measure. 30769 * @static 30770 * @param to {string} unit to convert to 30771 * @param from {string} unit to convert from 30772 * @param time {number} amount to be convert 30773 * @returns {number|undefined} the converted amount 30774 */ 30775 TimeUnit.convert = function(to, from, time) { 30776 from = TimeUnit.aliases[from] || from; 30777 to = TimeUnit.aliases[to] || to; 30778 var fromRow = TimeUnit.ratios[from]; 30779 var toRow = TimeUnit.ratios[to]; 30780 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 30781 return undefined; 30782 } 30783 return time * fromRow[toRow[0]]; 30784 }; 30785 30786 /** 30787 * Localize the measurement to the commonly used measurement in that locale. For example 30788 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30789 * the formatted number should be automatically converted to the most appropriate 30790 * measure in the other system, in this case, mph. The formatted result should 30791 * appear as "37.3 mph". 30792 * 30793 * @abstract 30794 * @param {string} locale current locale string 30795 * @returns {Measurement} a new instance that is converted to locale 30796 */ 30797 TimeUnit.prototype.localize = function(locale) { 30798 return new TimeUnit({ 30799 unit: this.unit, 30800 amount: this.amount 30801 }); 30802 }; 30803 30804 /** 30805 * Scale the measurement unit to an acceptable level. The scaling 30806 * happens so that the integer part of the amount is as small as 30807 * possible without being below zero. This will result in the 30808 * largest units that can represent this measurement without 30809 * fractions. Measurements can only be scaled to other measurements 30810 * of the same type. 30811 * 30812 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30813 * or undefined if the system can be inferred from the current measure 30814 * @return {Measurement} a new instance that is scaled to the 30815 * right level 30816 */ 30817 TimeUnit.prototype.scale = function(measurementsystem) { 30818 30819 var fromRow = TimeUnit.ratios[this.unit]; 30820 var time = this.amount; 30821 var munit = this.unit; 30822 var i; 30823 30824 time = 18446744073709551999; 30825 for (var m in TimeUnit.ratios) { 30826 i = TimeUnit.ratios[m][0]; 30827 var tmp = this.amount * fromRow[i]; 30828 if (tmp >= 1 && tmp < time) { 30829 time = tmp; 30830 munit = m; 30831 } 30832 } 30833 30834 return new TimeUnit({ 30835 unit: munit, 30836 amount: time 30837 }); 30838 }; 30839 /** 30840 * @private 30841 * @static 30842 */ 30843 TimeUnit.getMeasures = function () { 30844 var ret = []; 30845 for (var m in TimeUnit.ratios) { 30846 ret.push(m); 30847 } 30848 return ret; 30849 }; 30850 30851 //register with the factory method 30852 Measurement._constructors["time"] = TimeUnit; 30853 30854 30855 /*< VelocityUnit.js */ 30856 /* 30857 * VelocityUnit.js - Unit conversions for VelocityUnits/speeds 30858 * 30859 * Copyright © 2014-2015, JEDLSoft 30860 * 30861 * Licensed under the Apache License, Version 2.0 (the "License"); 30862 * you may not use this file except in compliance with the License. 30863 * You may obtain a copy of the License at 30864 * 30865 * http://www.apache.org/licenses/LICENSE-2.0 30866 * 30867 * Unless required by applicable law or agreed to in writing, software 30868 * distributed under the License is distributed on an "AS IS" BASIS, 30869 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30870 * 30871 * See the License for the specific language governing permissions and 30872 * limitations under the License. 30873 */ 30874 30875 /* 30876 !depends 30877 Measurement.js 30878 */ 30879 30880 30881 /** 30882 * @class 30883 * Create a new speed measurement instance. 30884 * 30885 * @constructor 30886 * @extends Measurement 30887 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30888 * the construction of this instance 30889 */ 30890 var VelocityUnit = function (options) { 30891 this.unit = "meters/second"; 30892 this.amount = 0; 30893 this.aliases = VelocityUnit.aliases; // share this table in all instances 30894 30895 if (options) { 30896 if (typeof(options.unit) !== 'undefined') { 30897 this.originalUnit = options.unit; 30898 this.unit = this.aliases[options.unit] || options.unit; 30899 } 30900 30901 if (typeof(options.amount) === 'object') { 30902 if (options.amount.getMeasure() === "speed") { 30903 this.amount = VelocityUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 30904 } else { 30905 throw "Cannot convert units " + options.amount.unit + " to a speed"; 30906 } 30907 } else if (typeof(options.amount) !== 'undefined') { 30908 this.amount = parseFloat(options.amount); 30909 } 30910 } 30911 30912 if (typeof(VelocityUnit.ratios[this.unit]) === 'undefined') { 30913 throw "Unknown unit: " + options.unit; 30914 } 30915 }; 30916 30917 VelocityUnit.prototype = new Measurement(); 30918 VelocityUnit.prototype.parent = Measurement; 30919 VelocityUnit.prototype.constructor = VelocityUnit; 30920 30921 VelocityUnit.ratios = { 30922 /* index, k/h f/s miles/h knot m/s km/s miles/s */ 30923 "kilometer/hour": [ 1, 1, 0.911344, 0.621371, 0.539957, 0.277778, 2.77778e-4, 1.72603109e-4 ], 30924 "feet/second": [ 2, 1.09728, 1, 0.681818, 0.592484, 0.3048, 3.048e-4, 1.89393939e-4 ], 30925 "miles/hour": [ 3, 1.60934, 1.46667, 1, 0.868976, 0.44704, 4.4704e-4, 2.77777778e-4 ], 30926 "knot": [ 4, 1.852, 1.68781, 1.15078, 1, 0.514444, 5.14444e-4, 3.19660958e-4 ], 30927 "meters/second": [ 5, 3.6, 3.28084, 2.236936, 1.94384, 1, 0.001, 6.21371192e-4 ], 30928 "kilometer/second": [ 6, 3600, 3280.8399, 2236.93629, 1943.84449, 1000, 1, 0.621371192 ], 30929 "miles/second": [ 7, 5793.6384, 5280, 3600, 3128.31447, 1609.344, 1.609344, 1 ] 30930 }; 30931 30932 VelocityUnit.metricSystem = { 30933 "kilometer/hour": 1, 30934 "meters/second": 5, 30935 "kilometer/second": 6 30936 }; 30937 VelocityUnit.imperialSystem = { 30938 "feet/second": 2, 30939 "miles/hour": 3, 30940 "knot": 4, 30941 "miles/second": 7 30942 }; 30943 VelocityUnit.uscustomarySystem = { 30944 "feet/second": 2, 30945 "miles/hour": 3, 30946 "knot": 4, 30947 "miles/second": 7 30948 }; 30949 30950 VelocityUnit.metricToUScustomary = { 30951 "kilometer/hour": "miles/hour", 30952 "meters/second": "feet/second", 30953 "kilometer/second": "miles/second" 30954 }; 30955 VelocityUnit.UScustomaryTometric = { 30956 "miles/hour": "kilometer/hour", 30957 "feet/second": "meters/second", 30958 "miles/second": "kilometer/second", 30959 "knot": "kilometer/hour" 30960 }; 30961 30962 /** 30963 * Return the type of this measurement. Examples are "mass", 30964 * "length", "speed", etc. Measurements can only be converted 30965 * to measurements of the same type.<p> 30966 * 30967 * The type of the units is determined automatically from the 30968 * units. For example, the unit "grams" is type "mass". Use the 30969 * static call {@link Measurement.getAvailableUnits} 30970 * to find out what units this version of ilib supports. 30971 * 30972 * @return {string} the name of the type of this measurement 30973 */ 30974 VelocityUnit.prototype.getMeasure = function() { 30975 return "speed"; 30976 }; 30977 30978 /** 30979 * Return a new measurement instance that is converted to a new 30980 * measurement unit. Measurements can only be converted 30981 * to measurements of the same type.<p> 30982 * 30983 * @param {string} to The name of the units to convert to 30984 * @return {Measurement|undefined} the converted measurement 30985 * or undefined if the requested units are for a different 30986 * measurement type 30987 */ 30988 VelocityUnit.prototype.convert = function(to) { 30989 if (!to || typeof(VelocityUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 30990 return undefined; 30991 } 30992 return new VelocityUnit({ 30993 unit: to, 30994 amount: this 30995 }); 30996 }; 30997 30998 /** 30999 * Scale the measurement unit to an acceptable level. The scaling 31000 * happens so that the integer part of the amount is as small as 31001 * possible without being below zero. This will result in the 31002 * largest units that can represent this measurement without 31003 * fractions. Measurements can only be scaled to other measurements 31004 * of the same type. 31005 * 31006 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31007 * or undefined if the system can be inferred from the current measure 31008 * @return {Measurement} a new instance that is scaled to the 31009 * right level 31010 */ 31011 VelocityUnit.prototype.scale = function(measurementsystem) { 31012 var mSystem; 31013 if (measurementsystem === "metric" || 31014 (typeof (measurementsystem) === 'undefined' && typeof (VelocityUnit.metricSystem[this.unit]) !== 'undefined')) { 31015 mSystem = VelocityUnit.metricSystem; 31016 } else if (measurementsystem === "imperial" || 31017 (typeof (measurementsystem) === 'undefined' && typeof (VelocityUnit.imperialSystem[this.unit]) !== 'undefined')) { 31018 mSystem = VelocityUnit.imperialSystem; 31019 } else if (measurementsystem === "uscustomary" || 31020 (typeof (measurementsystem) === 'undefined' && typeof (VelocityUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 31021 mSystem = VelocityUnit.uscustomarySystem; 31022 } else { 31023 return new VelocityUnit({ 31024 unit: this.unit, 31025 amount: this.amount 31026 }); 31027 } 31028 31029 var speed = this.amount; 31030 var munit = this.unit; 31031 var fromRow = VelocityUnit.ratios[this.unit]; 31032 31033 speed = 18446744073709551999; 31034 31035 for ( var m in mSystem) { 31036 var tmp = this.amount * fromRow[mSystem[m]]; 31037 if (tmp >= 1 && tmp < speed) { 31038 speed = tmp; 31039 munit = m; 31040 } 31041 } 31042 31043 return new VelocityUnit({ 31044 unit: munit, 31045 amount: speed 31046 }); 31047 }; 31048 31049 /** 31050 * Localize the measurement to the commonly used measurement in that locale. For example 31051 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31052 * the formatted number should be automatically converted to the most appropriate 31053 * measure in the other system, in this case, mph. The formatted result should 31054 * appear as "37.3 mph". 31055 * 31056 * @abstract 31057 * @param {string} locale current locale string 31058 * @returns {Measurement} a new instance that is converted to locale 31059 */ 31060 VelocityUnit.prototype.localize = function(locale) { 31061 var to; 31062 if (locale === "en-US" || locale === "en-GB") { 31063 to = VelocityUnit.metricToUScustomary[this.unit] || this.unit; 31064 } else { 31065 to = VelocityUnit.UScustomaryTometric[this.unit] || this.unit; 31066 } 31067 return new VelocityUnit({ 31068 unit: to, 31069 amount: this 31070 }); 31071 }; 31072 31073 VelocityUnit.aliases = { 31074 "foot/sec": "feet/second", 31075 "foot/s": "feet/second", 31076 "feet/s": "feet/second", 31077 "f/s": "feet/second", 31078 "feet/second": "feet/second", 31079 "feet/sec": "feet/second", 31080 "meter/sec": "meters/second", 31081 "meter/s": "meters/second", 31082 "meters/s": "meters/second", 31083 "metre/sec": "meters/second", 31084 "metre/s": "meters/second", 31085 "metres/s": "meters/second", 31086 "mt/sec": "meters/second", 31087 "m/sec": "meters/second", 31088 "mt/s": "meters/second", 31089 "m/s": "meters/second", 31090 "mps": "meters/second", 31091 "meters/second": "meters/second", 31092 "meters/sec": "meters/second", 31093 "kilometer/hour": "kilometer/hour", 31094 "km/hour": "kilometer/hour", 31095 "kilometers/hour": "kilometer/hour", 31096 "kmh": "kilometer/hour", 31097 "km/h": "kilometer/hour", 31098 "kilometer/h": "kilometer/hour", 31099 "kilometers/h": "kilometer/hour", 31100 "km/hr": "kilometer/hour", 31101 "kilometer/hr": "kilometer/hour", 31102 "kilometers/hr": "kilometer/hour", 31103 "kilometre/hour": "kilometer/hour", 31104 "mph": "miles/hour", 31105 "mile/hour": "miles/hour", 31106 "mile/hr": "miles/hour", 31107 "mile/h": "miles/hour", 31108 "miles/h": "miles/hour", 31109 "miles/hr": "miles/hour", 31110 "miles/hour": "miles/hour", 31111 "kn": "knot", 31112 "kt": "knot", 31113 "kts": "knot", 31114 "knots": "knot", 31115 "nm/h": "knot", 31116 "nm/hr": "knot", 31117 "nauticalmile/h": "knot", 31118 "nauticalmile/hr": "knot", 31119 "nauticalmile/hour": "knot", 31120 "nauticalmiles/hr": "knot", 31121 "nauticalmiles/hour": "knot", 31122 "knot": "knot", 31123 "kilometer/second": "kilometer/second", 31124 "kilometer/sec": "kilometer/second", 31125 "kilometre/sec": "kilometer/second", 31126 "Kilometre/sec": "kilometer/second", 31127 "kilometers/second": "kilometer/second", 31128 "kilometers/sec": "kilometer/second", 31129 "kilometres/sec": "kilometer/second", 31130 "Kilometres/sec": "kilometer/second", 31131 "km/sec": "kilometer/second", 31132 "Km/s": "kilometer/second", 31133 "km/s": "kilometer/second", 31134 "miles/second": "miles/second", 31135 "miles/sec": "miles/second", 31136 "miles/s": "miles/second", 31137 "mile/s": "miles/second", 31138 "mile/sec": "miles/second", 31139 "Mile/s": "miles/second" 31140 }; 31141 31142 /** 31143 * Convert a speed to another measure. 31144 * @static 31145 * @param to {string} unit to convert to 31146 * @param from {string} unit to convert from 31147 * @param speed {number} amount to be convert 31148 * @returns {number|undefined} the converted amount 31149 */ 31150 VelocityUnit.convert = function(to, from, speed) { 31151 from = VelocityUnit.aliases[from] || from; 31152 to = VelocityUnit.aliases[to] || to; 31153 var fromRow = VelocityUnit.ratios[from]; 31154 var toRow = VelocityUnit.ratios[to]; 31155 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31156 return undefined; 31157 } 31158 var result = speed * fromRow[toRow[0]]; 31159 return result; 31160 }; 31161 31162 /** 31163 * @private 31164 * @static 31165 */ 31166 VelocityUnit.getMeasures = function () { 31167 var ret = []; 31168 for (var m in VelocityUnit.ratios) { 31169 ret.push(m); 31170 } 31171 return ret; 31172 }; 31173 31174 //register with the factory method 31175 Measurement._constructors["speed"] = VelocityUnit; 31176 Measurement._constructors["velocity"] = VelocityUnit; 31177 31178 31179 /*< VolumeUnit.js */ 31180 /* 31181 * volume.js - Unit conversions for volume 31182 * 31183 * Copyright © 2014-2015, JEDLSoft 31184 * 31185 * Licensed under the Apache License, Version 2.0 (the "License"); 31186 * you may not use this file except in compliance with the License. 31187 * You may obtain a copy of the License at 31188 * 31189 * http://www.apache.org/licenses/LICENSE-2.0 31190 * 31191 * 31192 * Unless required by applicable law or agreed to in writing, software 31193 * distributed under the License is distributed on an "AS IS" BASIS, 31194 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31195 * 31196 * See the License for the specific language governing permissions and 31197 * limitations under the License. 31198 */ 31199 31200 /* 31201 !depends 31202 Measurement.js 31203 */ 31204 31205 31206 /** 31207 * @class 31208 * Create a new Volume measurement instance. 31209 * 31210 * @constructor 31211 * @extends Measurement 31212 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 31213 * the construction of this instance 31214 */ 31215 var VolumeUnit = function (options) { 31216 this.unit = "cubic meter"; 31217 this.amount = 0; 31218 this.aliases = VolumeUnit.aliases; // share this table in all instances 31219 31220 if (options) { 31221 if (typeof(options.unit) !== 'undefined') { 31222 this.originalUnit = options.unit; 31223 this.unit = this.aliases[options.unit] || options.unit; 31224 } 31225 31226 if (typeof(options.amount) === 'object') { 31227 if (options.amount.getMeasure() === "volume") { 31228 this.amount = VolumeUnit.convert(this.unit, options.amount.getUnit(), options.amount.getAmount()); 31229 } else { 31230 throw "Cannot convert unit " + options.amount.unit + " to a volume"; 31231 } 31232 } else if (typeof(options.amount) !== 'undefined') { 31233 this.amount = parseFloat(options.amount); 31234 } 31235 } 31236 31237 if (typeof(VolumeUnit.ratios[this.unit]) === 'undefined') { 31238 throw "Unknown unit: " + options.unit; 31239 } 31240 }; 31241 31242 VolumeUnit.prototype = new Measurement(); 31243 VolumeUnit.prototype.parent = Measurement; 31244 VolumeUnit.prototype.constructor = VolumeUnit; 31245 31246 VolumeUnit.ratios = { 31247 /* 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, */ 31248 "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 ], 31249 "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 ], 31250 "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 ], 31251 "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 ], 31252 "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 ], 31253 "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 ], 31254 "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 ], 31255 "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 ], 31256 "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 ], 31257 "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 ], 31258 "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 ], 31259 "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 ], 31260 "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 ], 31261 "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 ], 31262 "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 ], 31263 "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 ], 31264 "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 ], 31265 "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 ] 31266 }; 31267 31268 /** 31269 * Return the type of this measurement. Examples are "mass", 31270 * "length", "speed", etc. Measurements can only be converted 31271 * to measurements of the same type.<p> 31272 * 31273 * The type of the units is determined automatically from the 31274 * units. For example, the unit "grams" is type "mass". Use the 31275 * static call {@link Measurement.getAvailableUnits} 31276 * to find out what units this version of ilib supports. 31277 * 31278 * @return {string} the name of the type of this measurement 31279 */ 31280 VolumeUnit.prototype.getMeasure = function() { 31281 return "volume"; 31282 }; 31283 31284 /** 31285 * Return a new measurement instance that is converted to a new 31286 * measurement unit. Measurements can only be converted 31287 * to measurements of the same type.<p> 31288 * 31289 * @param {string} to The name of the units to convert to 31290 * @return {Measurement|undefined} the converted measurement 31291 * or undefined if the requested units are for a different 31292 * measurement type 31293 */ 31294 VolumeUnit.prototype.convert = function(to) { 31295 if (!to || typeof(VolumeUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 31296 return undefined; 31297 } 31298 return new VolumeUnit({ 31299 unit: to, 31300 amount: this 31301 }); 31302 }; 31303 31304 VolumeUnit.aliases = { 31305 "US gal": "gallon", 31306 "US gallon": "gallon", 31307 "US Gal": "gallon", 31308 "US Gallons": "gallon", 31309 "Gal(US)": "gallon", 31310 "gal(US)": "gallon", 31311 "gallon": "gallon", 31312 "quart": "quart", 31313 "US quart": "quart", 31314 "US quarts": "quart", 31315 "US Quart": "quart", 31316 "US Quarts": "quart", 31317 "US qt": "quart", 31318 "Qt(US)": "quart", 31319 "qt(US)": "quart", 31320 "US pint": "pint", 31321 "US Pint": "pint", 31322 "pint": "pint", 31323 "pint(US)": "pint", 31324 "Pint(US)": "pint", 31325 "US cup": "cup", 31326 "US Cup": "cup", 31327 "cup(US)": "cup", 31328 "Cup(US)": "cup", 31329 "cup": "cup", 31330 "us ounce": "us ounce", 31331 "US ounce": "us ounce", 31332 "℥": "us ounce", 31333 "US Oz": "us ounce", 31334 "oz(US)": "us ounce", 31335 "Oz(US)": "us ounce", 31336 "US tbsp": "tbsp", 31337 "tbsp": "tbsp", 31338 "tbsp(US)": "tbsp", 31339 "US tablespoon": "tbsp", 31340 "US tsp": "tsp", 31341 "tsp(US)": "tsp", 31342 "tsp": "tsp", 31343 "Cubic meter": "cubic meter", 31344 "cubic meter": "cubic meter", 31345 "Cubic metre": "cubic meter", 31346 "cubic metre": "cubic meter", 31347 "m3": "cubic meter", 31348 "Liter": "liter", 31349 "Liters": "liter", 31350 "liter": "liter", 31351 "L": "liter", 31352 "l": "liter", 31353 "Milliliter": "milliliter", 31354 "ML": "milliliter", 31355 "ml": "milliliter", 31356 "milliliter": "milliliter", 31357 "mL": "milliliter", 31358 "Imperial gal": "imperial gallon", 31359 "imperial gallon": "imperial gallon", 31360 "Imperial gallon": "imperial gallon", 31361 "gallon(imperial)": "imperial gallon", 31362 "gal(imperial)": "imperial gallon", 31363 "Imperial quart": "imperial quart", 31364 "imperial quart": "imperial quart", 31365 "Imperial Quart": "imperial quart", 31366 "IMperial qt": "imperial quart", 31367 "qt(Imperial)": "imperial quart", 31368 "quart(imperial)": "imperial quart", 31369 "Imperial pint": "imperial pint", 31370 "imperial pint": "imperial pint", 31371 "pint(Imperial)": "imperial pint", 31372 "imperial oz": "imperial ounce", 31373 "imperial ounce": "imperial ounce", 31374 "Imperial Ounce": "imperial ounce", 31375 "Imperial tbsp": "imperial tbsp", 31376 "imperial tbsp": "imperial tbsp", 31377 "tbsp(Imperial)": "imperial tbsp", 31378 "Imperial tsp": "imperial tsp", 31379 "imperial tsp": "imperial tsp", 31380 "tsp(Imperial)": "imperial tsp", 31381 "Cubic foot": "cubic foot", 31382 "cubic foot": "cubic foot", 31383 "Cubic Foot": "cubic foot", 31384 "Cubic feet": "cubic foot", 31385 "cubic Feet": "cubic foot", 31386 "cubic ft": "cubic foot", 31387 "ft3": "cubic foot", 31388 "Cubic inch": "cubic inch", 31389 "Cubic inches": "cubic inch", 31390 "cubic inches": "cubic inch", 31391 "cubic inch": "cubic inch", 31392 "cubic in": "cubic inch", 31393 "cu in": "cubic inch", 31394 "cu inch": "cubic inch", 31395 "inch³": "cubic inch", 31396 "in³": "cubic inch", 31397 "inch^3": "cubic inch", 31398 "in^3": "cubic inch", 31399 "c.i": "cubic inch", 31400 "CI": "cubic inch", 31401 "cui": "cubic inch" 31402 }; 31403 31404 /** 31405 * Convert a volume to another measure. 31406 * @static 31407 * @param to {string} unit to convert to 31408 * @param from {string} unit to convert from 31409 * @param volume {number} amount to be convert 31410 * @returns {number|undefined} the converted amount 31411 */ 31412 VolumeUnit.convert = function(to, from, volume) { 31413 from = VolumeUnit.aliases[from] || from; 31414 to = VolumeUnit.aliases[to] || to; 31415 var fromRow = VolumeUnit.ratios[from]; 31416 var toRow = VolumeUnit.ratios[to]; 31417 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31418 return undefined; 31419 } 31420 var result = volume * fromRow[toRow[0]]; 31421 return result; 31422 }; 31423 31424 /** 31425 * @private 31426 * @static 31427 */ 31428 VolumeUnit.getMeasures = function () { 31429 var ret = []; 31430 for (var m in VolumeUnit.ratios) { 31431 ret.push(m); 31432 } 31433 return ret; 31434 }; 31435 VolumeUnit.metricSystem = { 31436 "milliliter": 10, 31437 "liter": 11, 31438 "cubic meter": 12 31439 }; 31440 VolumeUnit.imperialSystem = { 31441 "imperial tsp": 13, 31442 "imperial tbsp": 14, 31443 "imperial ounce": 15, 31444 "imperial pint": 16, 31445 "imperial quart": 17, 31446 "imperial gallon": 18 31447 }; 31448 VolumeUnit.uscustomarySystem = { 31449 "tsp": 1, 31450 "tbsp": 2, 31451 "cubic inch": 3, 31452 "us ounce": 4, 31453 "cup": 5, 31454 "pint": 6, 31455 "quart": 7, 31456 "gallon": 8, 31457 "cubic foot": 9 31458 }; 31459 31460 VolumeUnit.metricToUScustomary = { 31461 "milliliter": "tsp", 31462 "liter": "quart", 31463 "cubic meter": "cubic foot" 31464 }; 31465 VolumeUnit.metricToImperial = { 31466 "milliliter": "imperial tsp", 31467 "liter": "imperial quart", 31468 "cubic meter": "imperial gallon" 31469 }; 31470 31471 VolumeUnit.imperialToMetric = { 31472 "imperial tsp": "milliliter", 31473 "imperial tbsp": "milliliter", 31474 "imperial ounce": "milliliter", 31475 "imperial pint": "liter", 31476 "imperial quart": "liter", 31477 "imperial gallon": "cubic meter" 31478 }; 31479 VolumeUnit.imperialToUScustomary = { 31480 "imperial tsp": "tsp", 31481 "imperial tbsp": "tbsp", 31482 "imperial ounce": "us ounce", 31483 "imperial pint": "pint", 31484 "imperial quart": "quart", 31485 "imperial gallon": "gallon" 31486 }; 31487 31488 VolumeUnit.uScustomaryToImperial = { 31489 "tsp": "imperial tsp", 31490 "tbsp": "imperial tbsp", 31491 "cubic inch": "imperial tbsp", 31492 "us ounce": "imperial ounce", 31493 "cup": "imperial ounce", 31494 "pint": "imperial pint", 31495 "quart": "imperial quart", 31496 "gallon": "imperial gallon", 31497 "cubic foot": "imperial gallon" 31498 }; 31499 VolumeUnit.uScustomarylToMetric = { 31500 "tsp": "milliliter", 31501 "tbsp": "milliliter", 31502 "cubic inch": "milliliter", 31503 "us ounce": "milliliter", 31504 "cup": "milliliter", 31505 "pint": "liter", 31506 "quart": "liter", 31507 "gallon": "cubic meter", 31508 "cubic foot": "cubic meter" 31509 }; 31510 31511 /** 31512 * Localize the measurement to the commonly used measurement in that locale. For example 31513 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31514 * the formatted number should be automatically converted to the most appropriate 31515 * measure in the other system, in this case, mph. The formatted result should 31516 * appear as "37.3 mph". 31517 * 31518 * @abstract 31519 * @param {string} locale current locale string 31520 * @returns {Measurement} a new instance that is converted to locale 31521 */ 31522 VolumeUnit.prototype.localize = function(locale) { 31523 var to; 31524 if (locale === "en-US") { 31525 to = VolumeUnit.metricToUScustomary[this.unit] || 31526 VolumeUnit.imperialToUScustomary[this.unit] || 31527 this.unit; 31528 } else if (locale === "en-GB") { 31529 to = VolumeUnit.metricToImperial[this.unit] || 31530 VolumeUnit.uScustomaryToImperial[this.unit] || 31531 this.unit; 31532 } else { 31533 to = VolumeUnit.uScustomarylToMetric[this.unit] || 31534 VolumeUnit.imperialToUScustomary[this.unit] || 31535 this.unit; 31536 } 31537 return new VolumeUnit({ 31538 unit: to, 31539 amount: this 31540 }); 31541 }; 31542 31543 /** 31544 * Scale the measurement unit to an acceptable level. The scaling 31545 * happens so that the integer part of the amount is as small as 31546 * possible without being below zero. This will result in the 31547 * largest units that can represent this measurement without 31548 * fractions. Measurements can only be scaled to other measurements 31549 * of the same type. 31550 * 31551 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31552 * or undefined if the system can be inferred from the current measure 31553 * @return {Measurement} a new instance that is scaled to the 31554 * right level 31555 */ 31556 VolumeUnit.prototype.scale = function(measurementsystem) { 31557 var fromRow = VolumeUnit.ratios[this.unit]; 31558 var mSystem; 31559 31560 if (measurementsystem === "metric"|| (typeof(measurementsystem) === 'undefined' 31561 && typeof(VolumeUnit.metricSystem[this.unit]) !== 'undefined')) { 31562 mSystem = VolumeUnit.metricSystem; 31563 } else if (measurementsystem === "uscustomary" || (typeof(measurementsystem) === 'undefined' 31564 && typeof(VolumeUnit.uscustomarySystem[this.unit]) !== 'undefined')) { 31565 mSystem = VolumeUnit.uscustomarySystem; 31566 } else if (measurementsystem === "imperial"|| (typeof(measurementsystem) === 'undefined' 31567 && typeof(VolumeUnit.imperialSystem[this.unit]) !== 'undefined')) { 31568 mSystem = VolumeUnit.imperialSystem; 31569 } 31570 31571 var volume = this.amount; 31572 var munit = this.unit; 31573 31574 volume = 18446744073709551999; 31575 31576 for (var m in mSystem) { 31577 var tmp = this.amount * fromRow[mSystem[m]]; 31578 if (tmp >= 1 && tmp < volume) { 31579 volume = tmp; 31580 munit = m; 31581 } 31582 } 31583 31584 return new VolumeUnit({ 31585 unit: munit, 31586 amount: volume 31587 }); 31588 }; 31589 31590 //register with the factory method 31591 Measurement._constructors["volume"] = VolumeUnit; 31592 31593 31594 31595 /*< MeasurementFactory.js */ 31596 /* 31597 * MeasurementFactory.js - Function to instantiate the appropriate subclasses of 31598 * the Measurement class. 31599 * 31600 * Copyright © 2015, JEDLSoft 31601 * 31602 * Licensed under the Apache License, Version 2.0 (the "License"); 31603 * you may not use this file except in compliance with the License. 31604 * You may obtain a copy of the License at 31605 * 31606 * http://www.apache.org/licenses/LICENSE-2.0 31607 * 31608 * Unless required by applicable law or agreed to in writing, software 31609 * distributed under the License is distributed on an "AS IS" BASIS, 31610 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31611 * 31612 * See the License for the specific language governing permissions and 31613 * limitations under the License. 31614 */ 31615 31616 /* 31617 !depends 31618 UnknownUnit.js 31619 AreaUnit.js 31620 DigitalStorageUnit.js 31621 EnergyUnit.js 31622 FuelConsumptionUnit.js 31623 LengthUnit.js 31624 MassUnit.js 31625 TemperatureUnit.js 31626 TimeUnit.js 31627 VelocityUnit.js 31628 VolumeUnit.js 31629 Measurement.js 31630 */ 31631 31632 // TODO: make these dependencies dynamic or at least generate them in the build 31633 // These will each add themselves to Measurement._constructors[] 31634 31635 31636 /** 31637 * Create a measurement subclass instance based on a particular measure 31638 * required. The measurement is immutable once 31639 * it is created, but it can be converted to other measurements later.<p> 31640 * 31641 * The options may contain any of the following properties: 31642 * 31643 * <ul> 31644 * <li><i>amount</i> - either a numeric amount for this measurement given 31645 * as a number of the specified units, or another Measurement instance 31646 * to convert to the requested units. If converting to new units, the type 31647 * of measure between the other instance's units and the current units 31648 * must be the same. That is, you can only convert one unit of mass to 31649 * another. You cannot convert a unit of mass into a unit of length. 31650 * 31651 * <li><i>unit</i> - units of this measurement. Use the 31652 * static call {@link MeasurementFactory.getAvailableUnits} 31653 * to find out what units this version of ilib supports. If the given unit 31654 * is not a base unit, the amount will be normalized to the number of base units 31655 * and stored as that number of base units. 31656 * For example, if an instance is constructed with 1 kg, this will be converted 31657 * automatically into 1000 g, as grams are the base unit and kg is merely a 31658 * commonly used scale of grams. 31659 * </ul> 31660 * 31661 * Here are some examples of converting a length into new units. 31662 * The first method is via this factory function by passing the old measurement 31663 * in as the "amount" property.<p> 31664 * 31665 * <pre> 31666 * var measurement1 = MeasurementFactory({ 31667 * amount: 5, 31668 * units: "kilometers" 31669 * }); 31670 * var measurement2 = MeasurementFactory({ 31671 * amount: measurement1, 31672 * units: "miles" 31673 * }); 31674 * </pre> 31675 * 31676 * The value in measurement2 will end up being about 3.125 miles.<p> 31677 * 31678 * The second method uses the convert method.<p> 31679 * 31680 * <pre> 31681 * var measurement1 = MeasurementFactory({ 31682 * amount: 5, 31683 * units: "kilometers" 31684 * }); 31685 * var measurement2 = measurement1.convert("miles"); 31686 * }); 31687 * </pre> 31688 * 31689 * The value in measurement2 will again end up being about 3.125 miles. 31690 * 31691 * @static 31692 * @param {Object=} options options that control the construction of this instance 31693 */ 31694 var MeasurementFactory = function(options) { 31695 if (!options || typeof(options.unit) === 'undefined') { 31696 return undefined; 31697 } 31698 31699 var measure = undefined; 31700 31701 for (var c in Measurement._constructors) { 31702 var measurement = Measurement._constructors[c]; 31703 if (typeof(measurement.aliases[options.unit]) !== 'undefined') { 31704 measure = c; 31705 break; 31706 } 31707 } 31708 31709 if (!measure || typeof(measure) === 'undefined') { 31710 return new UnknownUnit({ 31711 unit: options.unit, 31712 amount: options.amount 31713 }); 31714 } else { 31715 return new Measurement._constructors[measure](options); 31716 } 31717 }; 31718 31719 /** 31720 * Return a list of all possible units that this version of ilib supports. 31721 * Typically, the units are given as their full names in English. Unit names 31722 * are case-insensitive. 31723 * 31724 * @static 31725 * @return {Array.<string>} an array of strings containing names of measurement 31726 * units available 31727 */ 31728 MeasurementFactory.getAvailableUnits = function () { 31729 var units = []; 31730 for (var c in Measurement._constructors) { 31731 var measure = Measurement._constructors[c]; 31732 units = units.concat(measure.getMeasures()); 31733 } 31734 return units; 31735 }; 31736 31737 31738 31739 /*< UnitFmt.js */ 31740 /* 31741 * UnitFmt.js - Unit formatter class 31742 * 31743 * Copyright © 2014-2015, JEDLSoft 31744 * 31745 * Licensed under the Apache License, Version 2.0 (the "License"); 31746 * you may not use this file except in compliance with the License. 31747 * You may obtain a copy of the License at 31748 * 31749 * http://www.apache.org/licenses/LICENSE-2.0 31750 * 31751 * Unless required by applicable law or agreed to in writing, software 31752 * distributed under the License is distributed on an "AS IS" BASIS, 31753 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31754 * 31755 * See the License for the specific language governing permissions and 31756 * limitations under the License. 31757 */ 31758 31759 /* 31760 !depends 31761 ilib.js 31762 Locale.js 31763 ResBundle.js 31764 LocaleInfo.js 31765 IString.js 31766 NumFmt.js 31767 Utils.js 31768 */ 31769 31770 // !data unitfmt 31771 31772 31773 31774 /** 31775 * @class 31776 * Create a new unit formatter instance. The unit formatter is immutable once 31777 * it is created, but can format as many different strings with different values 31778 * as needed with the same options. Create different unit formatter instances 31779 * for different purposes and then keep them cached for use later if you have 31780 * more than one unit string to format.<p> 31781 * 31782 * The options may contain any of the following properties: 31783 * 31784 * <ul> 31785 * <li><i>locale</i> - locale to use when formatting the units. The locale also 31786 * controls the translation of the names of the units. If the locale is 31787 * not specified, then the default locale of the app or web page will be used. 31788 * 31789 * <li><i>autoScale</i> - when true, automatically scale the amount to get the smallest 31790 * number greater than 1, where possible, possibly by converting units within the locale's 31791 * measurement system. For example, if the current locale is "en-US", and we have 31792 * a measurement containing 278 fluid ounces, then the number "278" can be scaled down 31793 * by converting the units to a larger one such as gallons. The scaled size would be 31794 * 2.17188 gallons. Since iLib does not have a US customary measure larger than gallons, 31795 * it cannot scale it down any further. If the amount is less than the smallest measure 31796 * already, it cannot be scaled down any further and no autoscaling will be applied. 31797 * Default for the autoScale property is "true", so it only needs to be specified when 31798 * you want to turn off autoscaling. 31799 * 31800 * <li><i>autoConvert</i> - automatically convert the units to the nearest appropriate 31801 * measure of the same type in the measurement system used by the locale. For example, 31802 * if a measurement of length is given in meters, but the current locale is "en-US" 31803 * which uses the US Customary system, then the nearest appropriate measure would be 31804 * "yards", and the amount would be converted from meters to yards automatically before 31805 * being formatted. Default for the autoConvert property is "true", so it only needs to 31806 * be specified when you want to turn off autoconversion. 31807 * 31808 * <li><i>maxFractionDigits</i> - the maximum number of digits that should appear in the 31809 * formatted output after the decimal. A value of -1 means unlimited, and 0 means only print 31810 * the integral part of the number. 31811 * 31812 * <li><i>minFractionDigits</i> - the minimum number of fractional digits that should 31813 * appear in the formatted output. If the number does not have enough fractional digits 31814 * to reach this minimum, the number will be zero-padded at the end to get to the limit. 31815 * 31816 * <li><i>roundingMode</i> - When the maxFractionDigits or maxIntegerDigits is specified, 31817 * this property governs how the least significant digits are rounded to conform to that 31818 * maximum. The value of this property is a string with one of the following values: 31819 * <ul> 31820 * <li><i>up</i> - round away from zero 31821 * <li><i>down</i> - round towards zero. This has the effect of truncating the number 31822 * <li><i>ceiling</i> - round towards positive infinity 31823 * <li><i>floor</i> - round towards negative infinity 31824 * <li><i>halfup</i> - round towards nearest neighbour. If equidistant, round up. 31825 * <li><i>halfdown</i> - round towards nearest neighbour. If equidistant, round down. 31826 * <li><i>halfeven</i> - round towards nearest neighbour. If equidistant, round towards the even neighbour 31827 * <li><i>halfodd</i> - round towards nearest neighbour. If equidistant, round towards the odd neighbour 31828 * </ul> 31829 * 31830 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 31831 * loaded. When the onLoad option is given, the UnitFmt object will attempt to 31832 * load any missing locale data using the ilib loader callback. 31833 * When the constructor is done (even if the data is already preassembled), the 31834 * onLoad function is called with the current instance as a parameter, so this 31835 * callback can be used with preassembled or dynamic loading or a mix of the two. 31836 * 31837 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 31838 * asynchronously. If this option is given as "false", then the "onLoad" 31839 * callback must be given, as the instance returned from this constructor will 31840 * not be usable for a while. 31841 * 31842 * <li><i>loadParams</i> - an object containing parameters to pass to the 31843 * loader callback function when locale data is missing. The parameters are not 31844 * interpretted or modified in any way. They are simply passed along. The object 31845 * may contain any property/value pairs as long as the calling code is in 31846 * agreement with the loader callback function as to what those parameters mean. 31847 * </ul> 31848 * 31849 * Here is an example of how you might use the unit formatter to format a string with 31850 * the correct units.<p> 31851 * 31852 * 31853 * @constructor 31854 * @param {Object} options options governing the way this date formatter instance works 31855 */ 31856 var UnitFmt = function(options) { 31857 var sync = true, 31858 loadParams = undefined; 31859 31860 this.length = "long"; 31861 this.scale = true; 31862 this.measurementType = 'undefined'; 31863 this.convert = true; 31864 this.locale = new Locale(); 31865 31866 if (options) { 31867 if (options.locale) { 31868 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 31869 } 31870 31871 if (typeof(options.sync) === 'boolean') { 31872 sync = options.sync; 31873 } 31874 31875 if (typeof(options.loadParams) !== 'undefined') { 31876 loadParams = options.loadParams; 31877 } 31878 31879 if (options.length) { 31880 this.length = options.length; 31881 } 31882 31883 if (typeof(options.autoScale) === 'boolean') { 31884 this.scale = options.autoScale; 31885 } 31886 31887 if (typeof(options.autoConvert) === 'boolean') { 31888 this.convert = options.autoConvert; 31889 } 31890 31891 if (typeof(options.useNative) === 'boolean') { 31892 this.useNative = options.useNative; 31893 } 31894 31895 if (options.measurementSystem) { 31896 this.measurementSystem = options.measurementSystem; 31897 } 31898 31899 if (typeof (options.maxFractionDigits) === 'number') { 31900 /** 31901 * @private 31902 * @type {number|undefined} 31903 */ 31904 this.maxFractionDigits = options.maxFractionDigits; 31905 } 31906 if (typeof (options.minFractionDigits) === 'number') { 31907 /** 31908 * @private 31909 * @type {number|undefined} 31910 */ 31911 this.minFractionDigits = options.minFractionDigits; 31912 } 31913 /** 31914 * @private 31915 * @type {string} 31916 */ 31917 this.roundingMode = options.roundingMode; 31918 } 31919 31920 if (!UnitFmt.cache) { 31921 UnitFmt.cache = {}; 31922 } 31923 31924 Utils.loadData({ 31925 object: UnitFmt, 31926 locale: this.locale, 31927 name: "unitfmt.json", 31928 sync: sync, 31929 loadParams: loadParams, 31930 callback: ilib.bind(this, function (format) { 31931 var formatted = format; 31932 this.template = formatted["unitfmt"][this.length]; 31933 31934 new NumFmt({ 31935 locale: this.locale, 31936 useNative: this.useNative, 31937 maxFractionDigits: this.maxFractionDigits, 31938 minFractionDigits: this.minFractionDigits, 31939 roundingMode: this.roundingMode, 31940 sync: sync, 31941 loadParams: loadParams, 31942 onLoad: ilib.bind(this, function (numfmt) { 31943 this.numFmt = numfmt; 31944 31945 if (options && typeof(options.onLoad) === 'function') { 31946 options.onLoad(this); 31947 } 31948 }) 31949 }); 31950 }) 31951 }); 31952 }; 31953 31954 UnitFmt.prototype = { 31955 31956 /** 31957 * Return the locale used with this formatter instance. 31958 * @return {Locale} the Locale instance for this formatter 31959 */ 31960 getLocale: function() { 31961 return this.locale; 31962 }, 31963 31964 /** 31965 * Return the template string that is used to format date/times for this 31966 * formatter instance. This will work, even when the template property is not explicitly 31967 * given in the options to the constructor. Without the template option, the constructor 31968 * will build the appropriate template according to the options and use that template 31969 * in the format method. 31970 * 31971 * @return {string} the format template for this formatter 31972 */ 31973 getTemplate: function() { 31974 return this.template; 31975 }, 31976 31977 /** 31978 * Convert this formatter to a string representation by returning the 31979 * format template. This method delegates to getTemplate. 31980 * 31981 * @return {string} the format template 31982 */ 31983 toString: function() { 31984 return this.getTemplate(); 31985 }, 31986 31987 /** 31988 * Return whether or not this formatter will auto-scale the units while formatting. 31989 * @returns {boolean} true if auto-scaling is turned on 31990 */ 31991 getScale: function() { 31992 return this.scale; 31993 }, 31994 31995 /** 31996 * Return the measurement system that is used for this formatter. 31997 * @returns {string} the measurement system used in this formatter 31998 */ 31999 getMeasurementSystem: function() { 32000 return this.measurementSystem; 32001 }, 32002 32003 /** 32004 * Format a particular unit instance according to the settings of this 32005 * formatter object. 32006 * 32007 * @param {Measurement} measurement measurement to format 32008 * @return {string} the formatted version of the given date instance 32009 */ 32010 format: function (measurement) { 32011 var u = this.convert ? measurement.localize(this.locale.getSpec()) : measurement; 32012 u = this.scale ? u.scale(this.measurementSystem) : u; 32013 var formatted = new IString(this.template[u.getUnit()]); 32014 // make sure to use the right plural rules 32015 formatted.setLocale(this.locale, true, undefined, undefined); 32016 formatted = formatted.formatChoice(u.amount,{n:this.numFmt.format(u.amount)}); 32017 return formatted.length > 0 ? formatted : u.amount +" " + u.unit; 32018 } 32019 }; 32020 32021 32022 /*< /mnt/Terasaur/root/home/edwin/src/ilib-sf-trunk/js/lib/ilib-full-inc.js */ 32023 /** 32024 * @license 32025 * Copyright © 2012-2015, JEDLSoft 32026 * 32027 * Licensed under the Apache License, Version 2.0 (the "License"); 32028 * you may not use this file except in compliance with the License. 32029 * You may obtain a copy of the License at 32030 * 32031 * http://www.apache.org/licenses/LICENSE-2.0 32032 * 32033 * Unless required by applicable law or agreed to in writing, software 32034 * distributed under the License is distributed on an "AS IS" BASIS, 32035 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32036 * 32037 * See the License for the specific language governing permissions and 32038 * limitations under the License. 32039 */ 32040 32041 /* 32042 * ilib-full-inc.js - metafile that includes all other js files 32043 */ 32044 32045 /* !depends 32046 ilib.js 32047 DateRngFmt.js 32048 IDate.js 32049 DateFactory.js 32050 HebrewDate.js 32051 HebrewCal.js 32052 IslamicCal.js 32053 IslamicDate.js 32054 JulianCal.js 32055 JulianDate.js 32056 GregorianCal.js 32057 GregorianDate.js 32058 ThaiSolarCal.js 32059 ThaiSolarDate.js 32060 PersianCal.js 32061 PersianDate.js 32062 PersianAlgoCal.js 32063 PersianAlgoDate.js 32064 HanCal.js 32065 HanDate.js 32066 EthiopicCal.js 32067 EthiopicDate.js 32068 CopticCal.js 32069 CopticDate.js 32070 INumber.js 32071 NumFmt.js 32072 JulianDay.js 32073 DateFmt.js 32074 Calendar.js 32075 CalendarFactory.js 32076 Utils.js 32077 Locale.js 32078 IString.js 32079 DurationFmt.js 32080 ResBundle.js 32081 CType.js 32082 LocaleInfo.js 32083 DateRngFmt.js 32084 isAlnum.js 32085 isAlpha.js 32086 isAscii.js 32087 isBlank.js 32088 isCntrl.js 32089 isDigit.js 32090 isGraph.js 32091 isIdeo.js 32092 isLower.js 32093 isPrint.js 32094 isPunct.js 32095 isSpace.js 32096 isUpper.js 32097 isXdigit.js 32098 isScript.js 32099 ScriptInfo.js 32100 Name.js 32101 NameFmt.js 32102 Address.js 32103 AddressFmt.js 32104 Collator.js 32105 nfkc/all.js 32106 LocaleMatcher.js 32107 NormString.js 32108 CaseMapper.js 32109 GlyphString.js 32110 PhoneFmt.js 32111 PhoneGeoLocator.js 32112 PhoneNumber.js 32113 Measurement.js 32114 MeasurementFactory.js 32115 UnitFmt.js 32116 LengthUnit.js 32117 VelocityUnit.js 32118 DigitalStorageUnit.js 32119 TemperatureUnit.js 32120 UnknownUnit.js 32121 TimeUnit.js 32122 MassUnit.js 32123 AreaUnit.js 32124 FuelConsumptionUnit.js 32125 VolumeUnit.js 32126 EnergyUnit.js 32127 */ 32128 32129