text: //! moment-timezone-utils.js
  //! version : 0.5.5
  //! author : Tim Wood
  //! license : MIT
  //! github.com/moment/moment-timezone
  
  (function (root, factory) {
  	"use strict";
  
  	/*global define*/
  	if (typeof define === 'function' && define.amd) {
  		define(['moment'], factory);                 // AMD
  	} else if (typeof module === 'object' && module.exports) {
  		module.exports = factory(require('./'));     // Node
  	} else {
  		factory(root.moment);                        // Browser
  	}
  }(this, function (moment) {
  	"use strict";
  
  	if (!moment.tz) {
  		throw new Error("moment-timezone-utils.js must be loaded after moment-timezone.js");
  	}
  
  	/************************************
  		Pack Base 60
  	************************************/
  
  	var BASE60 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX',
  		EPSILON = 0.000001; // Used to fix floating point rounding errors
  
  	function packBase60Fraction(fraction, precision) {
  		var buffer = '.',
  			output = '',
  			current;
  
  		while (precision > 0) {
  			precision  -= 1;
  			fraction   *= 60;
  			current     = Math.floor(fraction + EPSILON);
  			buffer     += BASE60[current];
  			fraction   -= current;
  
  			// Only add buffer to output once we have a non-zero value.
  			// This makes '.000' output '', and '.100' output '.1'
  			if (current) {
  				output += buffer;
  				buffer  = '';
  			}
  		}
  
  		return output;
  	}
  
  	function packBase60(number, precision) {
  		var output = '',
  			absolute = Math.abs(number),
  			whole = Math.floor(absolute),
  			fraction = packBase60Fraction(absolute - whole, Math.min(~~precision, 10));
  
  		while (whole > 0) {
  			output = BASE60[whole % 60] + output;
  			whole = Math.floor(whole / 60);
  		}
  
  		if (number < 0) {
  			output = '-' + output;
  		}
  
  		if (output && fraction) {
  			return output + fraction;
  		}
  
  		if (!fraction && output === '-') {
  			return '0';
  		}
  
  		return output || fraction || '0';
  	}
  
  	/************************************
  		Pack
  	************************************/
  
  	function packUntils(untils) {
  		var out = [],
  			last = 0,
  			i;
  
  		for (i = 0; i < untils.length - 1; i++) {
  			out[i] = packBase60(Math.round((untils[i] - last) / 1000) / 60, 1);
  			last = untils[i];
  		}
  
  		return out.join(' ');
  	}
  
  	function packAbbrsAndOffsets(source) {
  		var index = 0,
  			abbrs = [],
  			offsets = [],
  			indices = [],
  			map = {},
  			i, key;
  
  		for (i = 0; i < source.abbrs.length; i++) {
  			key = source.abbrs[i] + '|' + source.offsets[i];
  			if (map[key] === undefined) {
  				map[key] = index;
  				abbrs[index] = source.abbrs[i];
  				offsets[index] = packBase60(Math.round(source.offsets[i] * 60) / 60, 1);
  				index++;
  			}
  			indices[i] = packBase60(map[key], 0);
  		}
  
  		return abbrs.join(' ') + '|' + offsets.join(' ') + '|' + indices.join('');
  	}
  
  	function packPopulation (number) {
  		if (!number) {
  			return '';
  		}
  		if (number < 1000) {
  			return '|' + number;
  		}
  		var exponent = String(number | 0).length - 2;
  		var precision = Math.round(number / Math.pow(10, exponent));
  		return '|' + precision + 'e' + exponent;
  	}
  
  	function validatePackData (source) {
  		if (!source.name)    { throw new Error("Missing name"); }
  		if (!source.abbrs)   { throw new Error("Missing abbrs"); }
  		if (!source.untils)  { throw new Error("Missing untils"); }
  		if (!source.offsets) { throw new Error("Missing offsets"); }
  		if (
  			source.offsets.length !== source.untils.length ||
  			source.offsets.length !== source.abbrs.length
  		) {
  			throw new Error("Mismatched array lengths");
  		}
  	}
  
  	function pack (source) {
  		validatePackData(source);
  		return [
  			source.name,
  			packAbbrsAndOffsets(source),
  			packUntils(source.untils) + packPopulation(source.population)
  		].join('|');
  	}
  
  	/************************************
  		Create Links
  	************************************/
  
  	function arraysAreEqual(a, b) {
  		var i;
  
  		if (a.length !== b.length) { return false; }
  
  		for (i = 0; i < a.length; i++) {
  			if (a[i] !== b[i]) {
  				return false;
  			}
  		}
  		return true;
  	}
  
  	function zonesAreEqual(a, b) {
  		return arraysAreEqual(a.offsets, b.offsets) && arraysAreEqual(a.abbrs, b.abbrs) && arraysAreEqual(a.untils, b.untils);
  	}
  
  	function findAndCreateLinks (input, output, links) {
  		var i, j, a, b, group, foundGroup, groups = [];
  
  		for (i = 0; i < input.length; i++) {
  			foundGroup = false;
  			a = input[i];
  
  			for (j = 0; j < groups.length; j++) {
  				group = groups[j];
  				b = group[0];
  				if (zonesAreEqual(a, b)) {
  					if (a.population > b.population) {
  						group.unshift(a);
  					} else {
  						group.push(a);
  					}
  					foundGroup = true;
  				}
  			}
  
  			if (!foundGroup) {
  				groups.push([a]);
  			}
  		}
  
  		for (i = 0; i < groups.length; i++) {
  			group = groups[i];
  			output.push(group[0]);
  			for (j = 1; j < group.length; j++) {
  				links.push(group[0].name + '|' + group[j].name);
  			}
  		}
  	}
  
  	function createLinks (source) {
  		var zones = [],
  			links = [];
  
  		if (source.links) {
  			links = source.links.slice();
  		}
  
  		findAndCreateLinks(source.zones, zones, links);
  
  		return {
  			version : source.version,
  			zones   : zones,
  			links   : links.sort()
  		};
  	}
  
  	/************************************
  		Filter Years
  	************************************/
  
  	function findStartAndEndIndex (untils, start, end) {
  		var startI = 0,
  			endI = untils.length + 1,
  			untilYear,
  			i;
  
  		if (!end) {
  			end = start;
  		}
  
  		if (start > end) {
  			i = start;
  			start = end;
  			end = i;
  		}
  
  		for (i = 0; i < untils.length; i++) {
  			if (untils[i] == null) {
  				continue;
  			}
  			untilYear = new Date(untils[i]).getUTCFullYear();
  			if (untilYear < start) {
  				startI = i + 1;
  			}
  			if (untilYear > end) {
  				endI = Math.min(endI, i + 1);
  			}
  		}
  
  		return [startI, endI];
  	}
  
  	function filterYears (source, start, end) {
  		var slice     = Array.prototype.slice,
  			indices   = findStartAndEndIndex(source.untils, start, end),
  			untils    = slice.apply(source.untils, indices);
  
  		untils[untils.length - 1] = null;
  
  		return {
  			name       : source.name,
  			abbrs      : slice.apply(source.abbrs, indices),
  			untils     : untils,
  			offsets    : slice.apply(source.offsets, indices),
  			population : source.population
  		};
  	}
  
  	/************************************
  		Filter, Link, and Pack
  	************************************/
  
  	function filterLinkPack (input, start, end) {
  		var i,
  			inputZones = input.zones,
  			outputZones = [],
  			output;
  
  		for (i = 0; i < inputZones.length; i++) {
  			outputZones[i] = filterYears(inputZones[i], start, end);
  		}
  
  		output = createLinks({
  			zones : outputZones,
  			links : input.links.slice(),
  			version : input.version
  		});
  
  		for (i = 0; i < output.zones.length; i++) {
  			output.zones[i] = pack(output.zones[i]);
  		}
  
  		return output;
  	}
  
  	/************************************
  		Exports
  	************************************/
  
  	moment.tz.pack           = pack;
  	moment.tz.packBase60     = packBase60;
  	moment.tz.createLinks    = createLinks;
  	moment.tz.filterYears    = filterYears;
  	moment.tz.filterLinkPack = filterLinkPack;
  
  	return moment;
  }));
  