UNPKG

pat

Version:

Formats data described by format strings

1,443 lines (1,416 loc) 54.3 kB
/** * Represents a data formatter. Data to be formatted is described by format * specifiers of a certain flavor. * * Supported format specifiers: * * + Java (java.util.Formatter) * * @module pat * * * @license FreeBSD License * @date 2012-06-27 * @author Michael Pecherstorfer */ /*global define, module*/ /*jslint nomen:true plusplus:true maxlen:85*/ (function(root) { 'use strict'; var mod = null, //private section __ = { cultures: {}, //culture module cache flavors: {} //flavor module cache }, Formatter = null; /* Directory containing the culture modules */ __.DIR_CULTURES = 'cultures/'; /* Directory containing the flavor modules */ __.DIR_FLAVORS = 'flavors/'; /* Default formatter instance */ __.instance = null; /* Path to this module, relative to require's baseUrl */ __.modulePath = ''; /* This module's inital state */ __.initialState = { cultures: {}, flavors: {}, modulePath: '', set: function() { this.cultures = __.deepCopy(__.cultures); this.flavors = __.deepCopy(__.flavors); this.modulePath = __.modulePath; }, establish: function() { Formatter.defaultOptions = __.initialDefaultFormatterOptions(); __.instance = new Formatter(); __.cultures = __.deepCopy(this.cultures); __.flavors = __.deepCopy(this.flavors); __.modulePath = this.modulePath; } }; /* * Returns the initial default options for a new Formatter. */ __.initialDefaultFormatterOptions = function() { return { flavorId: 'java', flavor: null, cultureId: 'enUS', culture: null, lineSeparator: '\n' }; }; /* * Conveniance function throwing an error with the given message. */ __.err = function(msg) { throw new Error(msg); }; /* * Binds the given context to the given function. */ __.bind = function(fn, context) { return function() { fn.apply(context, Array.prototype.slice.call(arguments)); }; }; /* * Counts the given object's own properties. */ __.countOwnProperties = function(obj) { var r = 0, key; for (key in obj) { if (obj.hasOwnProperty(key)) { r++; } } return r; }; /* * Returns the given object's own property names. */ __.ownPropertyNames = function(obj) { var r = [], key; for (key in obj) { if (obj.hasOwnProperty(key)) { r.push(key); } } return r; }; /* * Returns a deep copy of the argument. * Properties of the prototype chain are not considered. */ __.deepCopy = function(arg) { if (typeof arg !== 'object') { return arg; } var key, result = {}; for (key in arg) { if (arg.hasOwnProperty(key)) { result[key] = __.deepCopy(arg[key]); } } return result; }; /* * Returns an accessor for subject. The accessor behaves like a setter or * a getter, depending whether it is called with or without an argument. * * When called as a setter, the given new value is passed to the accessor's * 'syncSetter' or 'asyncSetter', depending whether one of those properties * is defined. * * If 'syncSetter' is defined, the result of this function represents the * value to be set. If 'syncSetter' is not defined, but 'asyncSetter' is * defined, the new value to be set is passed as a parameter to the callback * function of 'asyncSetter'. Otherwise the new value is set without * delegating it to another function. */ __.accessor = function(subject) { var result, syncSetter, asyncSetter; result = function(newval, fn) { //getter if (arguments.length === 0) { return subject; } //delegate synchronously if (typeof syncSetter === 'function') { subject = syncSetter(newval); } //delegate asynchronously else if (typeof asyncSetter === 'function') { asyncSetter(newval, function(newval) { subject = newval; if (typeof fn === 'function') { fn(); } }); } //set new value without delegating it else { subject = newval; } }; result.syncSetter = function(fn) { if (arguments.length === 0) { return syncSetter; } syncSetter = fn; }; result.asyncSetter = function(fn) { if (arguments.length === 0) { return asyncSetter; } asyncSetter = fn; }; return result; }; /* * Loads the specified module (Node or AMD environment) and applies the * given callback afterwards. */ __.loadModule = function(path, fn) { if (typeof module !== 'undefined') { //node.js fn(require('./' + path)); } else if (typeof define !== 'undefined' && define.amd) { //AMD //include relative to require's baseUrl require(['./' + (__.modulePath === '' ? path : __.modulePath + '/' + path)], function(m) { fn(m); }); } else { //global scope __.err('Include the necessary script.'); } }; /* * Loads the culture module with the specified ID and applies the given * callback afterwards (if defined). Note that the module has to be named * exactly after the given ID. */ __.loadCulture = function(cultureId, fn) { var culture = __.cultures[cultureId]; if (!culture) { try { __.loadModule(__.DIR_CULTURES + cultureId, function(culture) { Formatter.validateCulture(culture); __.cultures[cultureId] = culture; if (typeof fn === "function") { fn(culture); } }); } catch (e) { __.err('Failed to load culture ' + cultureId + ': ' + e.message); } } else { fn(culture); } }; /* * Loads the flavor module with the specified ID and applies the given * callback. Note that the module file has to be named exactly after the * given ID. */ __.loadFlavor = function(flavorId, fn) { var flavor = __.flavors[flavorId]; if (!flavor) { try { __.loadModule(__.DIR_FLAVORS + flavorId, function(flavor) { __.flavors[flavorId] = flavor; if (typeof fn === "function") { fn(flavor); } }); } catch (e) { __.err('Failed to load flavor ' + flavorId + ': ' + e.message); } } else { fn(flavor); } }; /** * Allocates a new formatter with the specified options. * @constructor * @class Formatter * @param {Object} [options] * @return {Formatter} */ Formatter = function(options) { this.options(options); }; /** * Default options for a new Formatter. * Overwrite this property if you intend to allocate several formatters with * default options different to those initially specified by this module. * @static * @property defaultOptions * @type {Object} */ Formatter.defaultOptions = __.initialDefaultFormatterOptions(); /** * Resets the Formatter. * @static * @chainable * @method reset * @return {Formatter} */ Formatter.reset = function() { __.initialState.establish(); return Formatter; }; /** * Tests if the given argument represents a valid culture object. * Throws an error if it is not valid. * @static * @chainable * @method validateCulture * @param {Object} culture * @return {Formatter} */ Formatter.validateCulture = function(culture) { var msg = 'Invalid culture: '; if (typeof culture !== 'object') { __.err(msg + 'Not an object'); } if (culture.id === undefined) { __.err(msg + 'Missing property "id"'); } /* Numbers */ if (culture.zeroDigit === undefined) { __.err(msg + 'Missing property "zeroDigit"'); } if (culture.decimalSeparator === undefined) { __.err(msg + 'Missing property "decimalSeparator"'); } if (culture.groupingSeparator === undefined) { __.err(msg + 'Missing property "groupingSeparator"'); } if (culture.groupingSize === undefined) { __.err(msg + 'Missing property "groupingSize"'); } /* Currency */ if (culture.currencySymbol === undefined) { __.err(msg + 'Missing property "currencySymbol"'); } if (culture.currencyToken === undefined) { __.err(msg + 'Missing property "currencyToken"'); } /* Weekday names */ if (culture.weekdays === undefined) { __.err(msg + 'Missing property "weekdays"'); } if (culture.weekdaysAbbr === undefined) { __.err(msg + 'Missing property "weekdaysAbbr"'); } if (culture.firstDayOfWeek === undefined) { __.err(msg + 'Missing property "firstDayOfWeek"'); } /* Month names */ if (culture.months === undefined) { __.err(msg + 'Missing property "months"'); } if (culture.monthsAbbr === undefined) { __.err(msg + 'Missing property "monthsAbbr"'); } /* Morning/afternoon tokens */ if (culture.amToken === undefined) { __.err(msg + 'Missing property "amToken"'); } if (culture.pmToken === undefined) { __.err(msg + 'Missing property "pmToken"'); } return Formatter; }; /** * Sets the Formatter options or returns them if called without an argument. * @static * @chainable * @method options * @param {Object} [options] Formatter options: * { path: './', flavorId: 'flavorId', cultureId: 'cultureId', lineSeparator: '\n' } * @return {Formatter|Object} * Formatter if called as setter, Formatter options if called as getter */ Formatter.options = function(options, fn) { //delegate to the default formatter instance if (arguments.length > 0) { __.instance.options(options, fn); return Formatter; } return __.instance.options(); }; /** * Formats the given arguments described by the given formatstring. * @static * @method format * @param {String} fstr Format string * @param {any} [data]* Data to be formatted * @return {String} Formatted data */ Formatter.format = function(fstr) { //delegate to the default formatter instance return __.instance.format.apply(__.instance, arguments); }; /** * Sets this Formatter's options or returns them if called without an * argument. * @chainable * @method options * @param {Object} [options] Formatter options: { path: './', flavorId: 'flavorId', cultureId: 'cultureId', lineSeparator: '\n' } * @return {Object} This Formatter's options if called as a getter, `this` * if called as a setter */ Formatter.prototype.options = function(options, fn) { var key, opt, nAsyncReturns = 0, done = function() { if (--nAsyncReturns === 0 && typeof fn === 'function') { fn(); } }; if (arguments.length === 0) { return this._options; } if (options) { //count options to be delegated asynchronously before setting them for (key in options) { if (options.hasOwnProperty(key) && this._options.hasOwnProperty(key) && this._options[key].asyncSetter() !== undefined) { nAsyncReturns++; } } //only set properties specified by the given options for (key in options) { if (options.hasOwnProperty(key) && this._options.hasOwnProperty(key)) { this._options[key](options[key], done); } } } else { //set default options opt = __.deepCopy(Formatter.defaultOptions); //turn option properties into accessors for (key in opt) { if (opt.hasOwnProperty(key)) { opt[key] = __.accessor(opt[key]); } } //hook module loaders for flavor and culture changes opt.flavorId.asyncSetter(__.bind(function(flavorId, fn) { __.loadFlavor(flavorId, __.bind(function(flavor) { this.options({ flavor: flavor }); fn(flavorId); }, this)); }, this)); opt.cultureId.asyncSetter(__.bind(function(cultureId, fn) { __.loadCulture(cultureId, __.bind(function(culture) { this.options({ culture: culture }); fn(cultureId); }, this)); }, this)); this._options = opt; } return this; }; /** * Formats the given arguments described by the given formatstring. * @method format * @param {String} fstr Format string * @param {any} [data]* Data to be formatted * @return {String} Formatted data */ Formatter.prototype.format = function(fstr) { if (arguments.length === 0) { return undefined; } if (!__.cultures[this._options.cultureId()]) { __.err('Define a culture first'); } if (!__.flavors[this._options.flavorId()]) { __.err('Define a flavor first'); } //format return __.flavors[this._options.flavorId()].format( typeof fstr === 'string' ? fstr.split('') : fstr, Formatter, Array.prototype.slice.call(arguments, 1), this.options()); }; /** * Utility functions. * @static * @class Formatter.util */ Formatter.util = {}; /** * Returns true if the given arg is an Array, false otherwise. * @static * @method isArray * @param {any} arg * @return {Boolean} */ Formatter.util.isArray = function(arg) { return Object.prototype.toString.call(arg) === '[object Array]'; }; /** * Returns true if the given arg is a String, false otherwise. * @static * @method isString * @param {any} arg * @return {Boolean} */ Formatter.util.isString = function(arg) { return Object.prototype.toString.call(arg) === '[object String]'; }; /** * Returns the argument, a character array or an array of length 1 * containing the argument depending whether the argument is an array, * a string or any other value. * @static * @method toArray * @param {any} arg * @return {Array} */ Formatter.util.toArray = function(arg) { if (this.isArray(arg)) { return arg; } if (this.isString(arg)) { return arg.split(''); } return [arg]; }; /** * Concatenates the given argument n-1 times with itself and returns the * resulting string. * @static * @method concat * @param {String} arg * @param {Number} [n = 1] * @return {String} */ Formatter.util.concat = function(arg, n) { n = n || 1; return new Array(n + 1).join(arg); }; /** * Appends or prepends the given character to the given string until the * resulting string has the specified length. * @static * @method pad * @param {String} str Append or prepend to this string * @param {String} [ch = ' '] Character to be appended or prepended * @param {Number} [len = String(str).length] Length of the resulting string * @param {Boolean} [left = false] Prepend if true, append otherwise * @return {String} */ Formatter.util.pad = function pad(str, ch, len, left) { str = String(str); ch = ch || ' '; len = len || str.length; left = Boolean(left); var delta = len - str.length; if (delta <= 0) { return str; } return (left ? this.concat(ch, delta) + str : str + this.concat(ch, delta)); }; /** * Prepends the given character to the given string until the resulting * string has the specified length. * @static * @method padLeft * @param {String} str Prepend to this string * @param {String} [ch = ' '] Character to be prepended * @param {Number} [len = String(str).length] Length of the resulting string * @return {String} */ Formatter.util.padLeft = function(str, ch, len) { return this.pad(str, ch, len, true); }; /** * Appends the given character to the given string until the resulting * string has the specified length. * @static * @method padRight * @param {String} str Append to this string * @param {String} [ch = ' '] Character to be appended * @param {Number} [len = String(str).length] Length of the resulting string * @return {String} */ Formatter.util.padRight = function(str, ch, len) { return this.pad(str, ch, len); }; /** * Number utility functions. * @static * @class Formatter.util.number */ Formatter.util.number = { /** * Greatest precise integer value in JavaScript. * @final * @static * @property MAX_INT * @type {Number} */ MAX_INT: Math.pow(2, 53), /** * Greatest precise integer value in two's complement range. * @final * @static * @property MAX_SIGNED_INT * @type {Number} */ MAX_SIGNED_INT: Math.pow(2, 52) - 1, /** * Smallest precise integer value in two's complement range. * @final * @static * @property MIN_SIGNED_INT * @type {Number} */ MIN_SIGNED_INT: -Math.pow(2, 52) }; /** * Returns true if the given number is less than zero or negative zero. * @static * @method isSigned * @param {Number} arg * @return {Boolean} */ Formatter.util.number.isSigned = function(arg) { if (arg === 0) { return 1/arg < 0; } return arg < 0; }; /** * Returns the given argument rounded to the given precision. * @static * @method round * @param {Number} arg Number to be rounded * @param {Number} [precision=0] Number of precise fractional digits. A * falsy value specifies fractional precision of 0. * @return {Number} */ Formatter.util.number.round = function(arg, precision) { if (!precision || precision < 0) { precision = 0; } var fac = Math.pow(10, precision); return Math.round(arg * fac) / fac; }; /** * Returns the given argument as a Number within the range * [Formatter.util.number.MIN_SIGNED_INT, Formatter.util.number.MAX_SIGNED_INT]. * @static * @method signedInt * @param {Number} arg * @return {Number} */ Formatter.util.number.signedInt = function(arg) { var r = Number(arg); if (r < this.MIN_SIGNED_INT) { return this.MIN_SIGNED_INT; } if (r > this.MAX_SIGNED_INT) { return this.MAX_SIGNED_INT; } return r; }; /** * Returns a decimal integer representing two's complement of the given * number. * * A JavaScript Number is a double-precision floating-point as specified by * the IEEE 754 standard. All positive integers up to 2^53 are represented * precisely, numbers beyond that threshold get their least significant bits * clipped (((Math.pow(2,53) + 1) - Math.pow(2,53) results to 0, not 1). * * The argument is therefore interpreted as an integer within the range * [-2^52, 2^52-1]. A floating point argument is truncated, an argument out * of the expected range is set to the smallest or to the greatest precise * value depending on whether the argument is smaller than -2^52 or greater * than 2^52-1. * * @static * @method twosComplement * @param {Number} arg * @return {Number} */ Formatter.util.number.twosComplement = function(arg) { var r = this.signedInt(arg); if (r < 0) { return r + this.MAX_INT; } return this.MAX_INT - r; }; /** * Returns true if the given argument represents a symbolic number (NaN, * POSITIVE_INFINITY, NEGATIVE_INFINITY), false otherwise. * @static * @method isSymbolicNumber * @param {Number} arg * @return {Boolean} */ Formatter.util.number.isSymbolicNumber = function(arg) { return String(Number(arg)) === "NaN" || Number(arg) === Number.POSITIVE_INFINITY || Number(arg) === Number.NEGATIVE_INFINITY; }; /** * Date utility functions for the Gregorian calendar. * @static * @class Formatter.util.date */ Formatter.util.date = { /** * Milliseconds per hour. * @final * @static * @property MILLISECONDS_PER_HOUR * @type Number */ MILLISECONDS_PER_HOUR: 3600000, /** * Milliseconds per day. * @final * @static * @property MILLISECONDS_PER_DAY * @type Number */ MILLISECONDS_PER_DAY: 86400000, /** * Milliseconds per week. * @final * @static * @property MILLISECONDS_PER_WEEK * @type Number */ MILLISECONDS_PER_WEEK: 604800000 }; /** * Returns the UNIX timestamp of the given date. The UNIX timestamp * describes a UTC date as number of seconds elapsed since the beginning * of the UNIX epoche (Midnight, 1970-01-01). Milliseconds of the given * date are truncated. * @static * @method timestamp * @param {Date} date Interpreted as a UTC value * @return {Number} */ Formatter.util.date.timestamp = function(date) { return Math.floor(date.valueOf() / 1000); }; /** * Returns the number of days for the specified month. * @static * @method daysOfMonth * @param {Date} date Interpreted as a UTC value * @return {Number} */ Formatter.util.date.daysOfMonth = function(date) { var d = new Date(date.valueOf()); d.setUTCDate(1); d.setUTCMonth(d.getUTCMonth() + 1); d.setUTCDate(0); return d.getUTCDate(); }; /** * Returns the number of days for the specified year. * @static * @method daysOfYear * @param {Date|Number} arg (Date is interpreted as a UTC value) * @return {Number} */ Formatter.util.date.daysOfYear = function(arg) { return this.isLeapYear(arg) ? 366 : 365; }; /** * Returns true if the specified year is a leap year, false otherwise. * @static * @method isLeapYear * @param {Date|Number} arg (Date is interpreted as a UTC value) * @return {Boolean} */ Formatter.util.date.isLeapYear = function(arg) { if (arg instanceof Date) { arg = arg.getUTCFullYear(); } return arg % 4 === 0 && (arg % 100 !== 0 || arg % 400 === 0); }; /** * Returns the culture-specific weekday of the given date. The first day * of the week corresponds to 0, the last day to 6. * @static * @method dayOfWeek * @param {Date} date Interpreted as a UTC value * @param {Object} culture Culture information * @return {Number} */ Formatter.util.date.dayOfWeek = function(date, culture) { return (date.getUTCDay() + 7 - culture.firstDayOfWeek) % 7; }; /** * Returns the day of the year specified by the given date. The first day * of the year corresponds to 1. * @static * @method dayOfYear * @param {Date} date Interpreted as a UTC value * @return {Number} */ Formatter.util.date.dayOfYear = function(date) { var result = date.getUTCDate(), year = date.getUTCFullYear(), month = date.getUTCMonth() - 1; while (month >= 0) { result += this.daysOfMonth(new Date(Date.UTC(year, month))); month--; } return result; }; /** * Returns a date representing the n-th day of the week specified by the * given date. * * @example var d = new Date('2012-07-04T00:00Z'), //Wednesday c = { firstDayOfWeek: 1 }; //culture with Monday as first weekday Formatter.util.date.nthDayOfWeek( d, c, 0); //Date representing '2012-07-02T00:00' Formatter.util.date.nthDayOfWeek( d, c, 6); //Date representing '2012-07-08T00:00' * @static * @method nthDayOfWeek * @param {Date} date Interpreted as a UTC value * @param {Object} culture Culture information * @param {Number} n In [0,6] * @return {Date} */ Formatter.util.date.nthDayOfWeek = function(date, culture, n) { var d = new Date(date.valueOf()); d.setUTCDate(d.getUTCDate() - this.dayOfWeek(date, culture) + n); return d; }; /** * Returns a date representing the first day of the week specified by * the given date. * @static * @method firstDayOfWeek * @param {Date} date Interpreted as a UTC value * @param {Object} culture Culture information * @return {Date} */ Formatter.util.date.firstDayOfWeek = function(date, culture) { return this.nthDayOfWeek(date, culture, 0); }; /** * Returns a date representing the first day of the week specified by * the given date. * @static * @method lastDayOfWeek * @param {Date} date Interpreted as a UTC value * @param {Object} culture Culture information * @return {Date} */ Formatter.util.date.lastDayOfWeek = function(date, culture) { return this.nthDayOfWeek(date, culture, 6); }; /** * Returns a date representing the first day of the month specified by * the given date. * @static * @method firstDayOfMonth * @param {Date} date Interpreted as a UTC value * @return {Date} */ Formatter.util.date.firstDayOfMonth = function(date) { return new Date(new Date(date.valueOf()).setUTCDate(1)); }; /** * Returns a date representing the last day of the month specified by the * given date. * @static * @method lastDayOfMonth * @param {Date} date Interpreted as a UTC value * @return {Date} */ Formatter.util.date.lastDayOfMonth = function(date) { var d = new Date(date.valueOf()); d.setUTCDate(1); d.setUTCMonth(d.getUTCMonth() + 1); d.setUTCDate(0); return d; }; /** * Returns a date representing the first day of the specified year. * @static * @method firstDayOfYear * @param {Date|Number} arg (Date is interpreted as a UTC value) * @return {Date} */ Formatter.util.date.firstDayOfYear = function(arg) { if (!(arg instanceof Date)) { return new Date(Date.UTC(arg, 0, 1)); } var d = new Date(arg.valueOf()); d.setUTCDate(1); d.setUTCMonth(0); return d; }; /** * Returns a date representing the last day of the specified year. * @static * @method lastDayOfYear * @param {Date|Number} arg (Date is interpreted as a UTC value) * @return {Date} */ Formatter.util.date.lastDayOfYear = function(arg) { var d = (arg instanceof Date ? new Date(arg.valueOf()) : new Date(Date.UTC(arg, 0))); d.setUTCDate(1); d.setUTCMonth(0); d.setUTCFullYear(d.getUTCFullYear() + 1); d.setUTCDate(0); return d; }; /** * Returns the ISO-8601 week specified by the given date. * @static * @method isoWeek * @param {Date} date Interpreted as a UTC value * @return {Number} */ Formatter.util.date.isoWeek = function(date) { var d = new Date(Date.UTC(date.getUTCFullYear(), 0, 4)), m, //monday, first calendar week result; m = this.nthDayOfWeek(d, {firstDayOfWeek: 1}, 0); if (date.valueOf() < m.valueOf()) { //date before monday (1-3 Jan.) d.setUTCFullYear(d.getUTCFullYear() - 1); m = this.nthDayOfWeek(d, {firstDayOfWeek: 1}, 0); } return Math.floor( (date.valueOf() - m.valueOf()) / this.MILLISECONDS_PER_WEEK) + 1; }; /** * Returns the century specified by the given date. * @static * @method century * @param {Date} date Interpreted as a UTC value * @return {Number} */ Formatter.util.date.century = function(date) { return Math.floor(date.getUTCFullYear() / 100) + 1; }; /** * Returns the number of past centuries specified by the given date. * @static * @method pastCenturies * @param {Date} date Interpreted as a UTC value * @return {Number} */ Formatter.util.date.pastCenturies = function(date) { return Math.floor(date.getUTCFullYear() / 100); }; /** * Returns true if the time specified by the given date is in the range * [00:00, 12:00). Returns false otherwise. * @static * @method isAM * @param {Date} date Interpreted as a UTC value * @return {Boolean} */ Formatter.util.date.isAM = function(date) { var d = new Date(Date.UTC( date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() )); return (date.valueOf() - d.valueOf()) < (12 * this.MILLISECONDS_PER_HOUR); }; /** * Returns true if the time specified by the given date is in the range * [12:00, 00:00). Returns false otherwise. * @static * @method isPM * @param {Date} date Interpreted as a UTC value * @return {Boolean} */ Formatter.util.date.isPM = function(date) { var d = new Date(Date.UTC( date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() )); return (date.valueOf() - d.valueOf()) >= (12 * this.MILLISECONDS_PER_HOUR); }; /** * Number formatter. * @static * @class Formatter.number */ Formatter.number = {}; /** * Returns the given number in hexadecimal exponential form. * <br/><br/> * Details on hexadecimal exponential encoding: * <ul> * <li><a href="http://en.wikipedia.org/wiki/Hexadecimal#Hexadecimal_exponential_notation">http://en.wikipedia.org/wiki/Hexadecimal#Hexadecimal_exponential_notation</a></li> * <li><a href="http://de.wikipedia.org/wiki/IEEE_754">http://de.wikipedia.org/wiki/IEEE_754</a></li> * <li><a href="http://www.2ality.com/2012/04/number-encoding.html">http://www.2ality.com/2012/04/number-encoding.html</a></li> * <li><a href="http://osr507doc.sco.com/en/topics/FltPtOps_DeNormNums.html">http://osr507doc.sco.com/en/topics/FltPtOps_DeNormNums.html</a></li> * </ul> * @static * @method toHexExp * @param {any} arg Number compatibel argument * @return {String} Hex exponential form of the given string */ Formatter.number.toHexExp = function(arg) { var r, //result b, //string representing the given number in base 2 len, //b's length pos, //floating point position in b sign, //is the given number signed? m, //mantissa exp, //exponent i, ieee754_64_bias = 1023; //IEEE 754 (double precision) exponent bias arg = Number(arg); //return NaN, Infinity, -Infinity unchanged if (Formatter.util.number.isSymbolicNumber(arg)) { return String(arg); } //distinct negative zero from zero if (arg === 0) { return Formatter.util.number.isSigned(arg) ? '-0x0.0p0' : '0x0.0p0'; } sign = arg < 0; arg = Math.abs(arg); b = arg.toString(2); pos = b.indexOf('.'); if (pos < 0) { //integer exp = b.length - 1; } else { if (pos === 1 && Number(b.charAt(0)) === 0) { //negative exponent //find first fractional 1-bit len = b.length; i = 2; while (i < len && b.charAt(i) !== '1') { i++; } //consider exponent bias specified by IEEE 754 (double precision) exp = i >= ieee754_64_bias ? -(ieee754_64_bias - 1) : -(i - 1); } else { //positive exponent exp = b.slice(0, pos).length - 1; } } m = Number(arg / Math.pow(2, exp)).toString(16); if (m.indexOf('.') < 0) { m = m + '.0'; } r = '0x' + m + 'p' + exp; return sign ? '-' + r : r; }; /** * Returns a string representing the given number in decimal form. * @static * @method toDecimal * @param {any} arg Number compatible value to be formatted * @param {Object} [options] Formatting options. Default values: * { precision: undefined, // Number of significant fractional digits. Data // type limited for falsy values other than 0. considerZeroSign: false // Whether to return a sign for negative zero or not } * @return {String} */ Formatter.number.toDecimal = function(arg, options) { options = options || {}; options.considerZeroSign = Boolean(options.considerZeroSign); var r = '', numStr, sign, m, exp, //exponent pos; //position of 'e' and '.' //return NaN, Infinity, -Infinity unchanged if (Formatter.util.number.isSymbolicNumber(arg)) { return String(arg); } //round to precision fractional digits if (options.precision || options.precision === 0) { arg = Formatter.util.number.round(Number(arg), options.precision); } else { arg = Number(arg); } //signed argument? sign = options.considerZeroSign ? Formatter.util.number.isSigned(arg) : arg < 0; //since Number.toString returns decimal notation for small numbers and //scientific notation for numbers greater than a certain threshold, //parsing is done based on the exponential form of the given number. numStr = arg.toExponential(); pos = numStr.indexOf('e'); m = numStr.slice((numStr.charAt(0) === '-' ? 1 : 0), pos); exp = Number(numStr.substr(pos + 1)); pos = m.indexOf('.'); if (pos < 0) { //integer mantissa r = m; pos = r.length; } else { r = m.slice(0, pos) + m.substr(pos + 1); } pos = pos + exp; if (0 < pos && pos < r.length) { r = r.slice(0, pos) + '.' + r.substr(pos); } else if (pos > r.length) { r = Formatter.util.padRight(r, '0', pos); } else if (pos <= 0) { r = '0.' + Formatter.util.padLeft(r, '0', r.length - pos); pos = 1; } //add fractional zero digits if the result's number of fractional digits //is less than the given precision if (options.precision && options.precision > 0 && r.length - pos - 1 < options.precision) { if (pos === r.length) { r += '.'; } r = Formatter.util.padRight(r, '0', pos + options.precision + 1); } //add sign if (sign) { r = '-' + r; } return r; }; /** * Returns a string representing the given number in scientific notation. * @static * @method toScientific * @param {any} arg Number compatible value to be formatted * @param {Object} [options] Formatting options. Default values: * { precision: undefined, // Mantissa precision. Data type limited for falsy // values other than 0. expMinWidth: 1, // Min width of the exponent (excl. 'e' and sign). upperCase: false, // Whether to use 'e' or 'E' for the exponent. considerZeroSign: false // Whether to return a sign for negative zero or not } * @return {String} */ Formatter.number.toScientific = function(arg, options) { options = options || {}; options.expMinWidth = options.expMinWidth || 1; options.upperCase = Boolean(options.upperCase); options.considerZeroSign = Boolean(options.considerZeroSign); var r, numStr, sign, m, //mantissa exp, expStr = options.upperCase ? 'E' : 'e', len, pos, i; arg = Number(arg); numStr = String(arg); if (Formatter.util.number.isSymbolicNumber(arg)) { return numStr; } sign = options.considerZeroSign ? Formatter.util.number.isSigned(Number(arg)) : Number(arg) < 0; //mantissa pos = numStr.indexOf('e'); if (pos < 0) { numStr = arg.toExponential(); pos = numStr.indexOf('e'); } m = Number(numStr.slice(0, pos)); //exponent expStr += numStr.charAt(pos + 1); //sign exp = numStr.substr(pos + 2); //skip 'e', skip sign len = options.expMinWidth - exp.length; for (i = len; i > 0; i--) { expStr += '0'; } expStr += exp; //mantissa precision if (options.precision || options.precision === 0) { r = String(Formatter.util.number.round(m, options.precision)); if (options.precision > 0) { pos = r.indexOf('.'); if (pos < 0) { r += '.'; r = Formatter.util.padRight(r, '0', r.length + options.precision); } else { r = Formatter.util.padRight(r, '0', options.precision + pos + 1); } } } else { r = String(m); } if (m === 0 && sign && options.considerZeroSign) { r = '-' + r; } return r + expStr; }; /** * Functions ought to format date components. * @static * @class Formatter.date */ Formatter.date = {}; /** * Returns a string representing the specified year. * * Negative years are formatted with the prefix '-' by default. Set the * option property `bcPrefix` or `bcPostfix` to change the default behavior. * Note that setting a non-falsy postfix implies the prefix ''. * * The number of digits in the resulting string depends on the option * properties `maxDigits` and `leadingZeros`. * The resulting year is zero padded if `maxDigits` is greater than the * number of year digits and `leadingZeros` is set to true. * Most significant digits of the resulting year are truncated if `maxDigits` * is less than the number of year digits. In that case leading zeros are * also truncated except `leadingZeros` is set to true. * * @example var d = new Date('2012-01-01T00:00Z'); Formatter.date.year(d); //'2012' Formatter.date.year(d, {maxDigits:3}); //'12' Formatter.date.year(d, {maxDigits:3, leadingZeros:true}); //'012' Formatter.date.year(d, {maxDigits:5, leadingZeros:true}); //'02012' d = new Date(Date.UTC(-2012, 1)); Formatter.date.year(d); //'-2012' Formatter.date.year(d, {bcPostfix: ' BC.'}); //'2012 BC.' Formatter.date.year(d, {bcPrefix: 'BC.'}); //'BC.2012' * @static * @method year * @param {Date} date Interpreted as a UTC value * @param {Object} [options] Format options. Default values are: * { bcPrefix: '-', bcPostfix: '', leadingZeros: false maxDigits: number of year digits } * * @return {String} */ Formatter.date.year = function(date, options) { var y = date.getUTCFullYear(), bc = (y < 0), opt = options || {}; opt.bcPostfix = opt.bcPostfix || ''; opt.bcPrefix = (opt.bcPostfix ? '' : (opt.bcPrefix || '-')); opt.leadingZeros = opt.leadingZeros || false; opt.maxDigits = opt.maxDigits || (bc ? String(y).length - 1 : String(y).length); y = String(Math.abs(y % Math.pow(10, opt.maxDigits))); if (opt.leadingZeros) { y = Formatter.util.padLeft(y, '0', opt.maxDigits); } return (bc ? [opt.bcPrefix, y, opt.bcPostfix].join('') : y); }; /** * Returns a string representing the day of the year specified by the * given date. * @static * @method dayOfYear * @param {Date} date Interpreted as a UTC value * @param {Boolean} leadingZeros Zero padded result? * @return {String} */ Formatter.date.dayOfYear = function(date, leadingZeros) { return leadingZeros ? Formatter.util.padLeft(Formatter.util.date.dayOfYear(date), '0', 3) : String(Formatter.util.date.dayOfYear(date)); }; /** * Returns a string representing the month specified by the given date. * @static * @method month * @param {Date} date Interpreted as a UTC value * @param {Boolean} leadingZero Zero padded result? * @return {String} */ Formatter.date.month = function(date, leadingZero) { var m = String(date.getUTCMonth() + 1); if (leadingZero && m.length < 2) { return '0' + m; } return m; }; /** * Returns a string representing the day of the month specified by the * given date. * @static * @method dayOfMonth * @param {Date} date Interpreted as a UTC value * @param {Boolean} leadingZero Zero padded result? * @return {String} */ Formatter.date.dayOfMonth = function(date, leadingZero) { var d = String(date.getUTCDate()); if (leadingZero && d.length < 2) { return '0' + d; } return d; }; /** * Returns the culture-specific month name specfied by the given date. * @static * @method monthName * @param {Date} date Interpreted as a UTC value * @param {Object} culture Culture information * @return {String} */ Formatter.date.monthName = function(date, culture) { return culture.months[date.getUTCMonth()]; }; /** * Returns the culture-specific abbreviated month name specfied by the * given date. * @static * @method abbreviatedMonthName * @param {Date} date Interpreted as a UTC value * @param {Object} culture Culture information * @return {String} */ Formatter.date.abbreviatedMonthName = function(date, culture) { return culture.monthsAbbr[date.getUTCMonth()]; }; /** * Returns the culture-specific weekday name specfied by the given date. * @static * @method weekdayName * @param {Date} date Interpreted as a UTC value * @param {Object} culture Culture information * @return {String} */ Formatter.date.weekdayName = function(date, culture) { return culture.weekdays[date.getUTCDay()]; }; /** * Returns the culture-specific abbreviated weekday name specfied by * the given date. * @static * @method abbreviatedWeekdayName * @param {Date} date Interpreted as a UTC value * @param {Object} culture Culture information * @return {String} */ Formatter.date.abbreviatedWeekdayName = function(date, culture) { return culture.weekdaysAbbr[date.getUTCDay()]; }; /** * Returns the formatted hours specified by the given date. * @static * @method hours * @param {Date} date Interpreted as a UTC value * @param {Boolean} leadingZero Zero padded result? * @param {Boolean} h12 Hours in [1,12]? * @return {String} */ Formatter.date.hours = function(date, leadingZero, h12) { var h = date.getUTCHours(); if (h12) { h = h % 12; if (h === 0) { h = 12; } } return String(leadingZero && h < 10 ? ['0', h].join('') : h); }; /** * Returns the formatted minutes specified by the given date. * @static * @method minutes * @param {Date} date Interpreted as a UTC value * @param {Boolean} leadingZero Zero padded result? * @return {String} */ Formatter.date.minutes = function(date, leadingZero) { var m = date.getUTCMinutes(); return String(leadingZero && m < 10 ? ['0', m].join('') : m); }; /** * Returns the formatted seconds specified by the given date. * @static * @method seconds * @param {Date} date Interpreted as a UTC value * @param {Boolean} leadingZero Zero padded result? * @return {String} */ Formatter.date.seconds = function(date, leadingZero) { var s = date.getUTCSeconds(); return String(leadingZero && s < 10 ? ['0', s].join('') : s); }; /** * Returns the formatted milliseconds specified by the given date. * @static * @method milliseconds * @param {Date} date Interpreted as a UTC value * @param {Boolean} leadingZeros Zero padded result? * @return {String} */ Formatter.date.milliseconds = function(date, leadingZeros) { var ms = date.getUTCMilliseconds(); if (leadingZeros) { return Formatter.util.pad(ms, '0', 3, true); } return String(ms); }; /** * Returns a string representing the morning/afternoon designator for the * time specified by the given date. * @static * @method timeDesignator * @param {Date} date Interpreted as a UTC value * @param {Object} culture Culture information * @return {String} */ Formatter.date.timeDesignator = function(date, culture) { return (Formatter.util.date.isAM(date) ? culture.amToken : culture.pmToken); }; /** * Returns a string representing the given date's time zone offset from UTC. * @static * @method timezoneOffset * @param {Date} date Interpreted as a date with the same timezone as * provided by the host OS. * @return {String} */ Formatter.date.timezoneOffset = function(date) { var tz = date.toTimeString().match(/GMT((?:\+|\-)\d{4})/); return (Formatter.util.isArray(tz) && tz.length > 1 ? tz[1] : undefined); }; /** * Returns a string representing the time zone abbreviation specified by * the given date. * @static * @method abbreviatedTimezone * @param {Date} date Interpreted as a date with the same timezone as * provided by the host OS. * @return {String} */ Formatter.date.abbreviatedTimezone = function(date) { var tz = date.toTimeString().match(/\((\w+)\)/); return (Formatter.util.isArray(tz) && tz.length > 1 ? tz[1] : undefined); }; /** * Returns a string representing the 24h time specified by the given date. * @static * @method time * @param {Date} date Interpreted as a UTC value * @param {Boolean} leadingZeros Zero padded time components? * @return {String} */ Formatter.date.time = function(date, leadingZeros) { return this.hours(date, leadingZeros) + ':' + this.minutes(date, leadingZeros) + ':' + this.seconds(date, leadingZeros); }; /** * Returns a string representing the 12h time specified by the given date. * @static * @method time12 * @param {Date} date Interpreted as a UTC value * @param {Boolean} leadingZeros Zero padded time components? * @param {Boolean} designator Including time designator (AM/PM)? * @return {String} */ Formatter.date.time12 = function(date, leadingZeros, designator, culture) { var t = this.hours(date, leadingZeros, true) + ':' + this.minutes(date, leadingZeros) + ':' + this.seconds(date, leadingZeros); if (designator) { t += (' ' + this.timeDesignator(date, culture)); } return t; }; /* * Exports this module to node. */ function exportNode(mod) { //load default culture and flavor module mod.Formatter.options({ cultureId: mod.Formatter.options().cultureId(), flavorId: mod.Formatter.o