API Docs for:
Show:

File: chart_utils.js

if (!window.ChartUtils){
	/**
		Module ChartUtils
        
Extends Object class with several useful methods to allow better encapsulation mechanisms.<br>
Exposes a few utility functions
        
        @module ChartUtils       
        
	*/
	var ChartUtils = (function(){
		"use strict";

		//Add a init method to Object
		if (!Object.hasOwnProperty("init")) {
        
                          /** 
                                Creates an object inheriting from a given prototype and then, if required, 
                                inits it with a list of properties tha can be passed as its second argument.
                                
                                @method init
                                @for Object
                                @param {Object} proto  The protetype to inherit from;
                                @param {Object} [properties] A dictionary of key-value properties to be used for the new object's initialization;
                                @return {Object} The newly created object.
                                @throws Error, if the wrong number of arguments is passed.
                            */
			Object.init = function (proto, properties){
				if (arguments.length !== 2){
					throw new Error('Object.init implementation only accepts 2 parameters.');
				}
				var key, new_obj = Object.create(proto);
				
				if (properties){
					for (key in properties){
						if (properties.hasHownProperty(key)){
							Object.defineProperty(new_obj, key, {
									value: properties[key],
									writable: true,
									enumerable: true,
									configurable: false
								}
							);
						}
					}
				}
				return new_obj;
			};
		}
		
		if (Object.prototype.clear){
                                        /**
                                            Deletes every property from an object
                                            
                                            @method clear
                                            @for Object
                                            @chainable
                                            @return {Object} The same object on whom this method is called.
                                          */
			Object.prototype.clear = 	function(){
											for (var prop in this){
												if (this.hasOwnProperty(prop)){
													delete this[prop]; 
												}
											}
											return this;
										};
		
		}

        
        /** addPublicMethod(methodName, method)
            
            Shortcut for defyning a method which will be considered
            public by createSafeProxy;<br>
            Usage: obj.addPublicMethod("name", method)<br>
            to add function method to obj as property obj["name"].
            
            @method addPublicMethod
            @for Object
            @chainable
            @param  {String} methodName    The name of the new property to be added to this object<br>
                                          <b>WARNING</b>: if Object[methodName] exists, then it will
                                                          be overwritten.
            @param method {Function}       The method body.
            @return {Object} This object, to enable method chaining
            @throws   
                    <ul>
                        <li>    Wrong number of arguments Exception, if either is missing or null;</li>
                        <li>    Illegal Argument Exception, if methodName is not a String;</li>
                        <li>    Illegal Argument Exception, if method is not a Function.</li>
                    </ul>     
          */
        function addPublicMethod(methodName, method){
            
            if (!methodName || !method){
                throw "Wrong number of arguments Exception";
            }
            if (!Object.isString(methodName)){
                throw "Illegal Argument Exception: methodName must be a string";
            }                                                            
            if (!Object.isFunction(method)){
                throw "Illegal Argument Exception: method must be a function";
            }
            Object.defineProperty(this, methodName, {
                value: method,
                writable: false,
                enumerable: true,
                configurable:false
            });
            return this;    //Chainable
        }   
        
        if (!Object.prototype.addPublicMethod){
            Object.defineProperty(Object.prototype, "addPublicMethod", {
                                    value: addPublicMethod,
                                    writable: false,
                                    enumerable: false,
                                    configurable: false
                                });
        }
        
	
        /** addProtectedMethod(methodName, method)
            
            Shortcut for defyning a method which will be considered
            protected by createSafeProxy;<br>
            Usage: obj.addProtectedMethod("name", method)<br>
            to add function method to obj as property obj["name"].
            
            @method addProtectedMethod
            @for Object
            @chainable
            @param  {String} methodName    The name of the new property to be added to this object<br>
                                          <b>WARNING</b>: if Object[methodName] exists, then it will
                                                          be overwritten.
            @param method {Function}       The method body.
            @return {Object} This object, to enable method chaining
            @throws   
                    <ul>
                        <li>    Wrong number of arguments Exception, if either is missing or null;</li>
                        <li>    Illegal Argument Exception, if methodName is not a String;</li>
                        <li>    Illegal Argument Exception, if method is not a Function.</li>
                    </ul>     
          */
        function addProtectedMethod(methodName, method){
            
            if (!methodName || !method){
                throw "Wrong number of arguments Exception";
            }
            if (!Object.isString(methodName)){
                throw "Illegal Argument Exception: methodName must be a string";
            }                                                            
            if (!Object.isFunction(method)){
                throw "Illegal Argument Exception: method must be a function";
            }
            Object.defineProperty(this, methodName, {
                value: method,
                writable: false,
                enumerable: false,
                configurable:false
            });
        }    
        
        if (!Object.prototype.addProtectedMethod){
            Object.defineProperty(Object.prototype, "addProtectedMethod", {
                                    value: addProtectedMethod,
                                    writable: false,
                                    enumerable: false,
                                    configurable: false
                                });
        }        
                    
        if (!Object.prototype.createSafeProxy){
                                                /** createSafeProxy()
                                                
                                                    Creates and returns a safe proxy for the object passed, 
                                                    that will wrap around it and expose only those methods
                                                    marked as public (i.e. those that are declared as enumerable).
                                                    
                                                    @method createSafeProxy
                                                    @for Object
                                                    @chainable
                                                    @param {Boolean} [canDestroy=false] States if the proxy consumer has the authority 
                                                                     to call destroy on the original object;<br>
                                                                     We assume the convention that object's uses destroy method
                                                                     as their destructor.
                                                    @return {Object} A proxy wrapping this object.
                                                    @throws  Any exception the original object pseudo-constructor might throw.
                                                  */
            Object.prototype.createSafeProxy = function(canDestroy){
                var property;
                var proxy = Object.create(null);
                var obj = this; //We must retain the "this" pointer to the current object to use it inside different contexts
                
                for (property in obj){
                    //DO NOT check hasOwnProperty: the proxy must work for obj's prototype methods as well
                    //ONLY enumerable methods will be added to proxy's interface
                    if (Object.isFunction(obj[property])){
                        
                        //If it's a method not marked as protected, it is added to the proxy interface;
                        proxy[property] = ( function(p){
                                                return  function(){
                                                            var result;
                                                            if (obj){
                                                                result = obj[p].apply(obj, Array.prototype.slice.apply(arguments, [0]));
                                                                //Special care is needed to support method chaining
                                                                if (result === obj){
                                                                    //obj.property returns obj itself, but we must return this proxy instead;
                                                                    return proxy;
                                                                }else{
                                                                    return result;
                                                                }
                                                            }else{
                                                                throw "Null reference: the object has been already destroyed";
                                                            }
                                                        };
                                            })(property);
                    }
                }
                
                //Adds a wrapping destroy method to allow withdrawal of the privileges given up introducing 
                //the consumer to obj;
                proxy.destroy = function(){
                                    try{
                                        if (canDestroy){
                                            obj.destroy();  //Destroys the original object only if authorized to
                                        }
                                    }finally{
                                        obj = null;
                                    }
                                };
                                    
                return proxy;
            };
        }        
      
        /** superMethod(methodName)
           
           Checks if the super object of this object has a method (i.e. a property which is a function) whose name is methodName, 
           and then calls it. Otherwise checks recursively its super object, i.e. its prototype.
           
           @method superMethod
           @for Object
           @param {String} methodName The name of the method to look up for in this object's super objects.
           @param [args]* The arguments to be passed to the super method, if any is needed;
           @return The result of the call to the method named methodName of this object's super object.
           @throws
                    <ul>
                        <li>Wrong number of arguments Exception, if methodName is missing or null;</li>
                        <li>Illegal Argument Exception, if methodName is not a String;</li>                                                                    
                        <li>Method not found Exception, if there isn't such a method in the whole inheritance chain.</li>
                    </ul>
         */
        function superMethod(methodName /*, args*/){
            if (!methodName){
                throw "Wrong number of arguments Exception";
            }
            if (!Object.isString(methodName)){
                throw "Illegal Argument Exception: methodName must be a string";
            }    
            
            //Looks up for this object's prototype
            var proto = this.prototype && this.prototype[methodName] ? this.prototype : this.__proto__;
            if (proto && proto[methodName] && Object.isFunction(proto[methodName])){
                return proto[methodName].apply(proto, Array.prototype.slice.apply(arguments, [1]));
            }else{
                throw "Super object has no method " + methodName;
            }
        }
        
        if (!Object.prototype.superMethod){
            Object.defineProperty(Object.prototype, "superMethod", {
                                    value: superMethod,
                                    writable: false,
                                    enumerable: false,
                                    configurable: false
                                });
        }                    
      
      
        /** setProperty(property, value)
           
           Assign the value "value" to the property "property" of the current object.<br>
           "property" MUST be an existing property of current object or of its ancestors:
           if this[property] is undefined, it recursively checks along its inheritance chain. 
           
           @method setProperty
           @for Object
           @chainable
           @param {String} property The name of the property to look up for in this object and its super object.
           @param value The value to be assigned to the property.
           @return This object, to allow for method chaining
           @throws
                    <ul>
                        <li>Wrong number of arguments Exception, if property is missing or null; (undefined is accepted for value)</li>
                        <li>Illegal Argument Exception, if property is not a String;</li>                                                                    
                        <li>Method not found Exception, if neither this object or its super object has such a property.</li>
                        <li>TypeError, if property exists but it isn't writable.</li>
                    </ul>
         */
        function setProperty(property, value){
            if (!property){
                throw "Wrong number of arguments Exception";
            }
            if (!Object.isString(property)){
                throw "Illegal Argument Exception: property must be a string";
            }    
            
            if (this.hasOwnProperty(property)){
                this[property] = value;
                return this;
            }
            
            //Looks up for this object's prototype
            var proto = this.prototype && this.prototype[property] ? this.prototype : this.__proto__;
            if (proto && !Object.isUndefined(proto[property])){
                proto.setProperty(property, value);
                return this;
            }else{
                throw "Super object has no property " + property;
            }
            
        }
        
        if (!Object.prototype.setProperty){
            Object.defineProperty(Object.prototype, "setProperty", {
                                    value: setProperty,
                                    writable: false,
                                    enumerable: false,
                                    configurable: false
                                });
        }
        
		if (Array.prototype.clear){
                /** 
                    Clears an Array, removing all its elements;
                    @method clear
                    @for Array
                    @param {Boolean} [deepClear=false] If it is required for all the array elements to be cleared,
                                                       this paramenter should be true; can  be omitted otherwise; <br>
                                                       If deep clearance is required, thie method will try to 
                                                       call the clear method for each and every element of this array
                                                       (Object class has already been extended with a clear method).
                    @return {Array} This array.
                  */
			Array.prototype.clear = function(deepClear){
                                        if (deepClear){
                                            //If a deep clear is required, try to execute clear method of every element
                                            for (var i=0; i<this.length; i++){
                                                if (Object.isFunction(this[i].clear)){
                                                    this[i].clear();
                                                }
                                            }
                                        }
                                        //Clear this array (the efficient way)
										this.length = 0;
										return this;
									};	
		}	
		
		if (!Array.prototype.map){
                                /**
                                    Maps a function on every element of the array, creting a new array populated
                                    with the results of these calls.
                                    
                                    @method map
                                    @for Array
                                    @param {Function} f The function to map on the array;
                                    @param {Object} [contest=window] The new this pointer for the function to map, if needed;
                                    @return {Array} The array of the results of mapping f over the elements this array.
                                    @throws TypeError if f isn't a function.
                                  */
			Array.prototype.map = function(f /*,contest*/){
				if (!Function.isFunction(f)){
					throw new TypeError();
				}else{
					var len = this.length;
					var res = new Array(len);
					var contest = arguments[1];
					for (var i = 0; i < len; i++){
						if (i in this){
							res[i] = f.call(contest, this[i], i, this);
						}
					}

					return res;
				}
			};	
		}

		if (!Array.prototype.sum){
                                /**
                                    Sums the elements of an array.

                                    @method sum
                                    @for Array
                                    @return {Number|String} The sum of the elements in the array;<br>
                                    <br>
                                    <b>WARNING</b>: 
                                        <ul>
                                            <li>If all the elements in the array are numbers, then returs their arithmetic sum</li>
                                            <li>If any element of the array isn't a number, returns a string obtained by concatenating
                                                the partial arithmetic sum until that element, and the concatenation
                                                of the string conversion of every other element in the array.</li>
                                        </ul>                                    
                                  */        
			Array.prototype.sum = function(){
                var len = this.length;
                var res = 0;
                for (var i = 0; i < len; i++){
                    res += this[i];
                }

                return res;
				
			};	
		}       

        
		if (!Array.prototype.shallowCopy){
                                        /** Array.shallowCopy([n])
                                            Creates a new array (shallow) copying the elements of the current one
                                            
                                            @method shallowCopy
                                            @for Array
                                            @param [n] If defined, the max number of elements to copy from the current array
                                            @return {Array} A new array, with a shallow copy of all the elements in the original one.
                                        */
			Array.prototype.shallowCopy =  function(n){
                                                var len = this.length;
                                                if (Object.isUndefined(n)){
                                                    n = len;
                                                }else{
                                                    n = parseInt(n, 10);
                                                    if (isNaN(n) || n <= 0){
                                                        throw "Invalid argument: n";
                                                    }else{
                                                        n = Math.min(n, len);
                                                    }
                                                }
                                                
                                                var res = new Array(n);
                                                for (var i = 0; i < n; i++){
                                                    res[i] = this[i];
                                                }

                                                return res;
                                            };	
		}	        
		
		if (!Array.prototype.max){
                                    /** 
                                        Return the maximum element of an array.
                                        
                                        @method max
                                        @for Array
                                        @param [extract_element_key] A function that, given any element of the array, will produce
                                                                     a numeric value used for ranking the element itself (its sorting key)
                                        @return {Number|NaN} The maximum value in the array, if all elements (or their keys)
                                                             are Number;<br>
                                                             NaN otherwise
                                    */
			Array.prototype.max = function(extract_element_key){
										if (Function.isFunction(extract_element_key)){
											return Math.max.apply(Math, this.map(extract_element_key));
										}else{
											return Math.max.apply(Math, this);
										}
									};	
		}	
		
		if (!Array.prototype.min){
                                    /** 
                                        Return the minimum element of an array.
                                        
                                        @method min
                                        @for Array
                                        @param [extract_element_key]  A function that, given any element of the array, will produce
                                                                      a numeric value used for ranking the element itself (its sorting key)
                                        @return {Number|NaN} The minimum value in the array, if all elements (or their keys)
                                                             are Number;<br>
                                                             NaN otherwise
                                    */        
			Array.prototype.min = function(extract_element_key){
										if (Function.isFunction(extract_element_key)){
											return Math.min.apply(Math, this.map(extract_element_key));
										}else{
											return Math.min.apply(Math, this);
										}
									};	
		}	

        if (!Object.prototype.isArray){
            /** 
                Checks if its argument is an array.
                
                @method isArray
                @for Object
                @param {Object} obj The argument to be checked.
                @return {Boolean} true <=> the object is an Array.
            */         
        	Object.prototype.isArray = function(obj) {
				return obj && (obj.constructor === Array);
			};
        }
        
        if (!Object.prototype.isString){
            /** 
                Checks if its argument is a string.
                
                @method isString
                @for Object
                @param {Object} obj The argument to be checked.
                @return {Boolean} true <=> the object is a String.
            */          
        	Object.prototype.isString = function(arg) {
				return typeof(arg)==='string';
			};
        }    
        
        if (!Object.prototype.isFunction){
            /** 
                Checks if its argument is a Function.
                
                @method isFunction
                @for Object
                @param {Object} arg The argument to be checked.
                @return {Boolean} true <=> the object is a Function.
            */              
        	Object.prototype.isFunction = function(arg){
				return typeof(arg) === 'function';
			};
        }        
		
        if (!Object.prototype.isNumber){
            /** 
                Checks if its argument is a Number.
                
                @method isNumber
                @for Object
                @param {Object} obj The argument to be checked.
                @return {Boolean} true <=> the object is a Number.
            */              
            Object.prototype.isNumber = function(n){
              return !isNaN(parseFloat(n)) && isFinite(n);
            };
        } 

        if (!Object.prototype.isUndefined){
            /** 
                Checks if its argument is undefined.
                
                @method isUndefined
                @for Object
                @param {Object} arg The argument to be checked.
                @return {Boolean} true <=> the argument is undefined.
            */              
        	Object.prototype.isUndefined = function(arg){
				return typeof(arg) === "undefined";
			};
        }         
           
		if (!Object.prototype.shallowCopy){
                                        /** Array.shallowCopy()
                                            Creates a new object (shallow)copying the elements of the current one.
                                            
                                            @method shallowCopy
                                            @for Object
                                            @return {Object} A new object, with a shallow copy of all the properties in the original one.
                                        */
			Object.prototype.shallowCopy =  function(){
                                                var res = {};
                                                for (var key in this){
                                                    if (this.hasOwnProperty(key)){
                                                        res[key] = this[key];
                                                    }
                                                }

                                                return res;
                                            };	
		}           
		
        var TIME_REGEXP = /^\d\d?:\d\d?$/;
            
            
            /** formatTimeString(HH, MM)
                Format a hours, minutes couple into a proper time string<br>
                <br>
                <b>INVARIANT</b>: HH and MM must be valid, positive integers 
                                  (since it's a private method, defensive programming is avoided<br>
                           If the overcome their range, proper formatting is enforced:
                           F.i. HH=23, MM=60 -> "00:00"
                @method formatTimeString
                @for ChartUtils
                @private
                @param {Number} HH An int value (between 0 and 23), representing the hours
                @param {Number} MM An int value (between 0 and 59), representing the minutes
                            
                @return {String} The properly formatted time string.
              */                        
            function formatTimeString(HH, MM){ 
                HH = (HH + Math.floor(MM/60)) % 24;
                MM = MM % 60;
                return (HH < 10 ? "0" + HH : HH) + ":" + (MM < 10 ? "0" + MM : MM);
            } 

        /** 
            Abbreviations suffixes for large numbers;
            @property SUFFIXES
            @for ChartUtils
            @type {Array}
            @final
            @private     
          */    
        var SUFFIXES = ["", "K", "M", "G", "T", "P", "E"];              
                              
		var utils = {

                                /** abbreviateNumber(val)
                                    Takes a value and returns it's abbreviated text representation.<br>
                                    <ul>
                                        <li>If abs(val) > 1, the following standard abbreviations will be used:
                                            <ul>
                                                <li><b>K</b> thousands</li>
                                                <li><b>M</b> million</li>
                                                <li><b>G</b> billion</li>
                                                <li><b>T</b> trillion</li>
                                                <li><b>P</b> quadrillion</li>
                                                <li><b>E</b> quintillion</li>
                                            </ul>
                                            One decimal place is always kept.<br>
                                            F.i.:
                                            <ul>
                                                <li>123 -> "123"</li>
                                                <li>1234 -> "1.2K"</li>
                                                <li>12345 -> "12.3K"</li>
                                                <li>123456789 -> "123.4M"</li>
                                            </ul>                                            
                                        </li>
                                        <li> If abs(val)
                                        </li>
                                    </ul>
                                    <br>
                                    <b>WARNING</b>:   since shallow copy is used, only works for
                                                      primitive (immutable) values                            
                                    @method abbreviateNumber
                                    @for ChartUtils
                                    @param {String|Number} value The value to assign to each element of the newly created array.
                                                                 If value is a function, it is called  n times, with no parameters
                                    @param {Number} n The size of the final array;<br>
                                                      Must be a positive integer.
                                    @return {Array} The newly created array.
                                    @throws {Invalid Argument Exception} if n isn't passed, it's not a number, or it's not positive.

                                */
            abbreviateNumber:   function (val){
                                    var index;
                                    if (val === 0){
                                        return "0";
                                    }
                                    if (Math.abs(val) < 1){
                                        //Very small number
                                        index = 0;  //WARNING: Reusing the variable index as the exponent of scientific notation
                                        while (Math.abs(val) < 1){
                                            index += 1;
                                            val *= 10;
                                        }                                              
                                        return (Math.round(val * 100) / 100.0) + "e-" + index;
                                    } else{
                                        index = 0;  //WARNING: Reusing the variable index as the index of the suffixes array
                                        while (index < SUFFIXES.length - 1){
                                            if (Math.abs(val) < 1000){
                                                if (index === 0){
                                                    val = Math.round(val * 100.0) / 100.0;   //Keeps only two decimal digits for numbers smaller than 100
                                                }                                            
                                                return val + SUFFIXES[index];
                                            }else{
                                                index += 1;
                                                val = Math.round(val / 100.0) / 10.0;   //Keep one decimal digits when dividing by 1K
                                            }
                                        }
                                        //Here, index == SUFFIXES.length - 1
                                        if (Math.abs(val) < 1000){
                                            return val + SUFFIXES[index];
                                        }else{
                                            //If it reaches here, the number is too long for the suffixes list: we abbreviate as much as we can anyway
                                            return val + SUFFIXES[SUFFIXES.length - 1];
                                        }                
                                    }
                                },        
                        /** fillArray(value, n)
                            Takes a value and a positive integer n and returns an Array of n copies of that value.<br>
                            <br>
                            <b>WARNING</b>:   since shallow copy is used, only works for
                                              primitive (immutable) values                            
                            @method fillArray
                            @for ChartUtils
                            @param {String|Number} value The value to assign to each element of the newly created array.
                                                         If value is a function, it is called  n times, with no parameters
                            @param {Number} n The size of the final array;<br>
                                              Must be a positive integer.
                            @return {Array} The newly created array.
                            @throws {Invalid Argument Exception} if n isn't passed, it's not a number, or it's not positive.

                        */
			fillArray:  function(value, n){
                            n = parseInt(n, 10);
                            if (isNaN(n) || n <= 0){
                                throw "Invalid Argument: n";
                            }

                            var i, res = new Array(n);
                            if (Function.isFunction(value)){
                                for (i = 0; i < n; i++){
                                    res[i] = value();
                                }                            
                            }else{
                                for (i = 0; i < n; i++){
                                    res[i] = value;
                                }
                            }

                            return res;
                        },
            /** validateTimeString(timeString)
            
                Takes a string as input and checks if it is a valid
                time representation, according to the format HH:MM
                
                @method validateTimeString
                @for ChartUtils
                @param {String} timeString The string to be evaluated
                @return {Array|null} <ul>
                                        <li>An array with two integers, the values for hours and minutes
                                            <=> The input string validates successfully</li>
                                        <li>null <-> Otherwise</li>
                                    </ul>
              */                        
            validateTimeString:     function(timeString){
                                        if (!TIME_REGEXP.test(timeString)){
                                            return null;
                                        }//else, the formatting is fine, but we MUST check the values (i.e.: HH < 24, MM < 60
                                        var v = timeString.split(":").map(function(s){return parseInt(s, 10);});
                                        if (v[0] >= 24 || v[1] >= 60){
                                            return null;
                                        }else{
                                            return  v;
                                        }
                                    },
                        
            /** addTimeStrings(timeString_1, timeString_2)
                Takes two strings as input and checks if they are valid
                time representations, according to the format HH:MM<br>
                If it is so, sums them and returns a new string representing
                the resulting time;
                
                @method addTimeStrings
                @for ChartUtils
                @param {String} timeString_1 The first time string to be added
                @param {String} timeString_2 The second time string to be added                                                    
                @return {String} A string representation of the sum of the two timeStamps, in the format HH:MM (modulo 24 hours)
                                <=> Both strings validates successfully
                                
                @throws {Invalid Argument Exception} if either input fails to validate.
              */
            addTimeStrings:     function(timeString_1, timeString_2){
                                    var t1 = this.validateTimeString(timeString_1);
                                    
                                    if (!t1){
                                            throw "Invalid Argument: timeString_1";
                                    }
                                    var t2 = this.validateTimeString(timeString_2);
                                    
                                    if (!t2){
                                            throw "Invalid Argument: timeString_2";
                                    }
                                    //INVARIANT: either validateTimeString returns null, or it returns an array with integers;
                                    t1[1] += t2[1];
                                    t1[0] += t2[0];

                                    return formatTimeString(t1[0], t1[1]);   
                                },
                                
            /** addIntToTimeString(timeString, minutes)
            
                Takes a string and an int value and checks if it is a valid
                time representation, according to the format HH:MM
                
                @method addIntToTimeString
                @for ChartUtils                
                @param {String} timeString The time string taken as initial time
                @param {Number} minutes How many minutes needs to be added to the time string;                                  
                @return {String} A string representation of the sum of the two time values, in the format HH:MM (modulo 24 hours)
                                <=> Both inputs validates successfully
                @throws {Invalid Argument Exception} if either input fails to validate
              */
            addIntToTimeString:     function(timeString, minutes){
                                    var t = this.validateTimeString(timeString);
                                    
                                    if (!t){
                                        throw "Invalid Argument: timeString";
                                    }
                                    
                                    if (minutes < 0){
                                        throw "Invalid Argument: minutes";
                                    }
                                    //INVARIANT: either validateTimeString returns null, or it returns an array with integers;
                                    t[1] += minutes;
                                    
                                    return formatTimeString(t[0], t[1]);  
                                }                                 
		};
		Object.freeze(utils);
		return utils;
	})();
}