<!DOCTYPE html>
<html ng-app="test">

<head>
  <script data-require="angular.js@1.3.14" data-semver="1.3.14" src="https://code.angularjs.org/1.3.14/angular.js"></script>
  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
  <script src="position.js"></script>
  <script src="dateparser.js"></script>
  <script src="datepicker_nofix.js"></script>
  <script src="script.js"></script>
  <script src="timezone.js"></script>
</head>

<body id="approot" ng-controller="MainCtrl as header">
  <div ng-controller="TimeZoneCtrl as tz">
    <h1>Hello From {{ tz.selectedTimeZone }}!</h1>
    <input type="text" id="shipDate" class="form-control" datepicker-popup="shortDate" 
    datepicker-options="header.dp.options" required="true" ng-model-options="{ updateOn: 'default blur' }" 
    ng-click="header.dp.opened.dt['shipDate'] = !header.dp.opened.dt['shipDate']"
    is-open="header.dp.opened.dt['shipDate']" min-date="header.dp.minDate" 
    ng-model="header.shipDate" show-button-bar="false" />

    <br />
    <label for="regionPicker">Pick a time zone region:</label>
    <br />
    <select id="regionPicker" class="form-control" ng-if="tz.tzData.all" 
      ng-options="value as key for (key, value) in tz.tzData.all" ng-model="tz.tzData.forCountry"></select>
    <br />
    <label for="cityPicker" ng-if="tz.tzData.forCountry">Pick a time zone sub-region:</label>
    <br />
    <select id="cityPicker" class="form-control" ng-if="tz.tzData.forCountry" 
      ng-options="value as key for (key, value) in tz.tzData.forCountry" ng-model="tz.tzData.selected"></select>
    <br />
    <pre>{{ header.tzData.selected | json }}</pre>
  </div>
</body>

</html>
// Code goes here
const moduleName = 'test';
const ctrlName = 'MainCtrl';

var app = null;
var def = {};
def.module = {
  name: moduleName,
  dependencies: ['ui.bootstrap.datepicker']
};
def.controller = {
  name: ctrlName,
  dependencies: [],
  definition: function() {
    var self = this;
    self.dp = {
      options: {
        datepickerMode: 'day',
        showWeeks: false,
        startingDay: 1,
        showButtonBar: false
      },
      opened: {},
      minDate: new Date(new Date().toISOString().substr(0, 10))
    };
  }
};

//bootstrap it up
app = angular.module(def.module.name, def.module.dependencies);
app.controller(def.controller.name, def.controller.dependencies.concat(def.controller.definition));
//EOF
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position'])

.constant('datepickerConfig', {
  formatDay: 'dd',
  formatMonth: 'MMMM',
  formatYear: 'yyyy',
  formatDayHeader: 'EEE',
  formatDayTitle: 'MMMM yyyy',
  formatMonthTitle: 'yyyy',
  datepickerMode: 'day',
  minMode: 'day',
  maxMode: 'year',
  showWeeks: true,
  startingDay: 0,
  yearRange: 20,
  minDate: null,
  maxDate: null
})

.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) {
  var self = this,
      ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;

  // Modes chain
  this.modes = ['day', 'month', 'year'];

  // Configuration attributes
  angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
                   'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) {
    self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
  });

  // Watchable date attributes
  angular.forEach(['minDate', 'maxDate'], function( key ) {
    if ( $attrs[key] ) {
      $scope.$parent.$watch($parse($attrs[key]), function(value) {
        self[key] = value ? new Date(value) : null;
        self.refreshView();
      });
    } else {
      self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
    }
  });

  $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
  $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
  this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date();

  $scope.isActive = function(dateObject) {
    if (self.compare(dateObject.date, self.activeDate) === 0) {
      $scope.activeDateId = dateObject.uid;
      return true;
    }
    return false;
  };

  this.init = function( ngModelCtrl_ ) {
    ngModelCtrl = ngModelCtrl_;

    ngModelCtrl.$render = function() {
      self.render();
    };
  };

  this.render = function() {
    if ( ngModelCtrl.$modelValue ) {
      var date = new Date( ngModelCtrl.$modelValue ),
          isValid = !isNaN(date);

      if ( isValid ) {
        this.activeDate = date;
      } else {
        $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
      }
      ngModelCtrl.$setValidity('date', isValid);
    }
    this.refreshView();
  };

  this.refreshView = function() {
    if ( this.element ) {
      this._refreshView();

      var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
      ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date)));
    }
  };

  this.createDateObject = function(date, format) {
    var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
    return {
      date: date,
      label: dateFilter(date, format),
      selected: model && this.compare(date, model) === 0,
      disabled: this.isDisabled(date),
      current: this.compare(date, new Date()) === 0
    };
  };

  this.isDisabled = function( date ) {
    return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode})));
  };

  // Split array into smaller arrays
  this.split = function(arr, size) {
    var arrays = [];
    while (arr.length > 0) {
      arrays.push(arr.splice(0, size));
    }
    return arrays;
  };

  $scope.select = function( date ) {
    if ( $scope.datepickerMode === self.minMode ) {
      var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0);
      dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
      ngModelCtrl.$setViewValue( dt );
      ngModelCtrl.$render();
    } else {
	  self.activeDate = date;
      $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ];
    }
	//$timeout(function () { self.activeDate = date; });
  };

  $scope.move = function( direction ) {
    var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
        month = self.activeDate.getMonth() + direction * (self.step.months || 0);
    self.activeDate.setFullYear(year, month, 1);
    self.refreshView();
  };

  $scope.toggleMode = function( direction ) {
    direction = direction || 1;

    if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
      return;
    }

    $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ];
  };

  // Key event mapper
  $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' };

  var focusElement = function() {
    $timeout(function() {
      self.element[0].focus();
    }, 0 , false);
  };

  // Listen for focus requests from popup directive
  $scope.$on('datepicker.focus', focusElement);

  $scope.keydown = function( evt ) {
    var key = $scope.keys[evt.which];

    if ( !key || evt.shiftKey || evt.altKey ) {
      return;
    }

    evt.preventDefault();
    evt.stopPropagation();

    if (key === 'enter' || key === 'space') {
      if ( self.isDisabled(self.activeDate)) {
        return; // do nothing
      }
      $scope.select(self.activeDate);
      focusElement();
    } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
      $scope.toggleMode(key === 'up' ? 1 : -1);
      focusElement();
    } else {
      self.handleKeyDown(key, evt);
      self.refreshView();
    }
  };
}])

.directive( 'datepicker', function () {
  return {
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/datepicker/datepicker.html',
    scope: {
      datepickerMode: '=?',
      dateDisabled: '&'
    },
    require: ['datepicker', '?^ngModel'],
    controller: 'DatepickerController',
    link: function(scope, element, attrs, ctrls) {
      var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];

      if ( ngModelCtrl ) {
        datepickerCtrl.init( ngModelCtrl );
      }
    }
  };
})

.directive('daypicker', ['dateFilter', function (dateFilter) {
  return {
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/datepicker/day.html',
    require: '^datepicker',
    link: function(scope, element, attrs, ctrl) {
      scope.showWeeks = ctrl.showWeeks;

      ctrl.step = { months: 1 };
      ctrl.element = element;

      var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
      function getDaysInMonth( year, month ) {
        return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month];
      }

      function getDates(startDate, n) {
        var dates = new Array(n), current = new Date(startDate), i = 0;
        current.setHours(12); // Prevent repeated dates because of timezone bug
        while ( i < n ) {
          dates[i++] = new Date(current);
          current.setDate( current.getDate() + 1 );
        }
        return dates;
      }

      ctrl._refreshView = function() {
        var year = ctrl.activeDate.getFullYear(),
          month = ctrl.activeDate.getMonth(),
          firstDayOfMonth = new Date(year, month, 1),
          difference = ctrl.startingDay - firstDayOfMonth.getDay(),
          numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
          firstDate = new Date(firstDayOfMonth);

        if ( numDisplayedFromPreviousMonth > 0 ) {
          firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
        }

        // 42 is the number of days on a six-month calendar
        var days = getDates(firstDate, 42);
        for (var i = 0; i < 42; i ++) {
          days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), {
            secondary: days[i].getMonth() !== month,
            uid: scope.uniqueId + '-' + i
          });
        }

        scope.labels = new Array(7);
        for (var j = 0; j < 7; j++) {
          scope.labels[j] = {
            abbr: dateFilter(days[j].date, ctrl.formatDayHeader),
            full: dateFilter(days[j].date, 'EEEE')
          };
        }

        scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle);
        scope.rows = ctrl.split(days, 7);

        if ( scope.showWeeks ) {
          scope.weekNumbers = [];
          var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ),
              numWeeks = scope.rows.length;
          while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {}
        }
      };

      ctrl.compare = function(date1, date2) {
        return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
      };

      function getISO8601WeekNumber(date) {
        var checkDate = new Date(date);
        checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
        var time = checkDate.getTime();
        checkDate.setMonth(0); // Compare with Jan 1
        checkDate.setDate(1);
        return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
      }

      ctrl.handleKeyDown = function( key, evt ) {
        var date = ctrl.activeDate.getDate();

        if (key === 'left') {
          date = date - 1;   // up
        } else if (key === 'up') {
          date = date - 7;   // down
        } else if (key === 'right') {
          date = date + 1;   // down
        } else if (key === 'down') {
          date = date + 7;
        } else if (key === 'pageup' || key === 'pagedown') {
          var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
          ctrl.activeDate.setMonth(month, 1);
          date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date);
        } else if (key === 'home') {
          date = 1;
        } else if (key === 'end') {
          date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth());
        }
        ctrl.activeDate.setDate(date);
      };

      ctrl.refreshView();
    }
  };
}])

.directive('monthpicker', ['dateFilter', function (dateFilter) {
  return {
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/datepicker/month.html',
    require: '^datepicker',
    link: function(scope, element, attrs, ctrl) {
      ctrl.step = { years: 1 };
      ctrl.element = element;

      ctrl._refreshView = function() {
        var months = new Array(12),
            year = ctrl.activeDate.getFullYear();

        for ( var i = 0; i < 12; i++ ) {
          months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), {
            uid: scope.uniqueId + '-' + i
          });
        }

        scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle);
        scope.rows = ctrl.split(months, 3);
      };

      ctrl.compare = function(date1, date2) {
        return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
      };

      ctrl.handleKeyDown = function( key, evt ) {
        var date = ctrl.activeDate.getMonth();

        if (key === 'left') {
          date = date - 1;   // up
        } else if (key === 'up') {
          date = date - 3;   // down
        } else if (key === 'right') {
          date = date + 1;   // down
        } else if (key === 'down') {
          date = date + 3;
        } else if (key === 'pageup' || key === 'pagedown') {
          var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
          ctrl.activeDate.setFullYear(year);
        } else if (key === 'home') {
          date = 0;
        } else if (key === 'end') {
          date = 11;
        }
        ctrl.activeDate.setMonth(date);
      };

      ctrl.refreshView();
    }
  };
}])

.directive('yearpicker', ['dateFilter', function (dateFilter) {
  return {
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/datepicker/year.html',
    require: '^datepicker',
    link: function(scope, element, attrs, ctrl) {
      var range = ctrl.yearRange;

      ctrl.step = { years: range };
      ctrl.element = element;

      function getStartingYear( year ) {
        return parseInt((year - 1) / range, 10) * range + 1;
      }

      ctrl._refreshView = function() {
        var years = new Array(range);

        for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) {
          years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), {
            uid: scope.uniqueId + '-' + i
          });
        }

        scope.title = [years[0].label, years[range - 1].label].join(' - ');
        scope.rows = ctrl.split(years, 5);
      };

      ctrl.compare = function(date1, date2) {
        return date1.getFullYear() - date2.getFullYear();
      };

      ctrl.handleKeyDown = function( key, evt ) {
        var date = ctrl.activeDate.getFullYear();

        if (key === 'left') {
          date = date - 1;   // up
        } else if (key === 'up') {
          date = date - 5;   // down
        } else if (key === 'right') {
          date = date + 1;   // down
        } else if (key === 'down') {
          date = date + 5;
        } else if (key === 'pageup' || key === 'pagedown') {
          date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years;
        } else if (key === 'home') {
          date = getStartingYear( ctrl.activeDate.getFullYear() );
        } else if (key === 'end') {
          date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1;
        }
        ctrl.activeDate.setFullYear(date);
      };

      ctrl.refreshView();
    }
  };
}])

.constant('datepickerPopupConfig', {
  datepickerPopup: 'yyyy-MM-dd',
  currentText: 'Today',
  clearText: 'Clear',
  closeText: 'Done',
  closeOnDateSelection: true,
  appendToBody: false,
  showButtonBar: true
})

.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig',
function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) {
  return {
    restrict: 'EA',
    require: 'ngModel',
    scope: {
      isOpen: '=?',
      currentText: '@',
      clearText: '@',
      closeText: '@',
      dateDisabled: '&'
    },
    link: function(scope, element, attrs, ngModel) {
      var dateFormat,
          closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection,
          appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;

      scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;

      scope.getText = function( key ) {
        return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
      };

      attrs.$observe('datepickerPopup', function(value) {
          dateFormat = value || datepickerPopupConfig.datepickerPopup;
          ngModel.$render();
      });

      // popup element used to display calendar
      var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
      popupEl.attr({
        'ng-model': 'date',
        'ng-change': 'dateSelection()'
      });

      function cameltoDash( string ){
        return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
      }

      // datepicker element
      var datepickerEl = angular.element(popupEl.children()[0]);
      if ( attrs.datepickerOptions ) {
        angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) {
          datepickerEl.attr( cameltoDash(option), value );
        });
      }

      scope.watchData = {};
      angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) {
        if ( attrs[key] ) {
          var getAttribute = $parse(attrs[key]);
          scope.$parent.$watch(getAttribute, function(value){
            scope.watchData[key] = value;
          });
          datepickerEl.attr(cameltoDash(key), 'watchData.' + key);

          // Propagate changes from datepicker to outside
          if ( key === 'datepickerMode' ) {
            var setAttribute = getAttribute.assign;
            scope.$watch('watchData.' + key, function(value, oldvalue) {
              if ( value !== oldvalue ) {
                setAttribute(scope.$parent, value);
              }
            });
          }
        }
      });
      if (attrs.dateDisabled) {
        datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
      }

      function parseDate(viewValue) {
        if (!viewValue) {
          ngModel.$setValidity('date', true);
          return null;
        } else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
          ngModel.$setValidity('date', true);
          return viewValue;
        } else if (angular.isString(viewValue)) {
          var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue);
          if (isNaN(date)) {
            ngModel.$setValidity('date', false);
            return undefined;
          } else {
            ngModel.$setValidity('date', true);
            return date;
          }
        } else {
          ngModel.$setValidity('date', false);
          return undefined;
        }
      }
      ngModel.$parsers.unshift(parseDate);

      ngModel.$formatters.push(function (value) {
         return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
      });

      // Inner change
      scope.dateSelection = function(dt) {
        if (angular.isDefined(dt)) {
          scope.date = dt;
        }
        ngModel.$setViewValue(scope.date);
        ngModel.$render();

        if ( closeOnDateSelection ) {
          scope.isOpen = false;
          element[0].focus();
        }
      };

      element.bind('input change keyup', function() {
        scope.$apply(function() {
          scope.date = ngModel.$modelValue;
        });
      });

      // Outer change
      ngModel.$render = function() {
        var date = ngModel.$viewValue ? dateFilter(parseDate(ngModel.$viewValue), dateFormat) : '';
        element.val(date);
        scope.date = parseDate( ngModel.$modelValue );
      };

      var documentClickBind = function(event) {
        if (scope.isOpen && event.target !== element[0]) {
          scope.$apply(function() {
            scope.isOpen = false;
          });
        }
      };

      var keydown = function(evt, noApply) {
        scope.keydown(evt);
      };
      element.bind('keydown', keydown);

      scope.keydown = function(evt) {
        if (evt.which === 27) {
          evt.preventDefault();
          evt.stopPropagation();
          scope.close();
        } else if (evt.which === 40 && !scope.isOpen) {
          scope.isOpen = true;
        }
      };

      scope.$watch('isOpen', function(value) {
        if (value) {
          scope.$broadcast('datepicker.focus');
          scope.position = appendToBody ? $position.offset(element) : $position.position(element);
          scope.position.top = scope.position.top + element.prop('offsetHeight');

          $document.bind('click', documentClickBind);
        } else {
          $document.unbind('click', documentClickBind);
        }
      });

      scope.select = function( date ) {
        if (date === 'today') {
          var today = new Date();
          if (angular.isDate(ngModel.$modelValue)) {
            date = new Date(ngModel.$modelValue);
            date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
          } else {
            date = new Date(today.setHours(0, 0, 0, 0));
          }
        }
        scope.dateSelection( date );
      };

      scope.close = function() {
        scope.isOpen = false;
        element[0].focus();
      };

      var $popup = $compile(popupEl)(scope);
      // Prevent jQuery cache memory leak (template is now redundant after linking)
      popupEl.remove();

      if ( appendToBody ) {
        $document.find('body').append($popup);
      } else {
        element.after($popup);
      }

      scope.$on('$destroy', function() {
        $popup.remove();
        element.unbind('keydown', keydown);
        $document.unbind('click', documentClickBind);
      });
    }
  };
}])

.directive('datepickerPopupWrap', function() {
  return {
    restrict:'EA',
    replace: true,
    transclude: true,
    templateUrl: 'template/datepicker/popup.html',
    link:function (scope, element, attrs) {
      element.bind('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
      });
    }
  };
});
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position'])

.constant('datepickerConfig', {
  formatDay: 'dd',
  formatMonth: 'MMMM',
  formatYear: 'yyyy',
  formatDayHeader: 'EEE',
  formatDayTitle: 'MMMM yyyy',
  formatMonthTitle: 'yyyy',
  datepickerMode: 'day',
  minMode: 'day',
  maxMode: 'year',
  showWeeks: true,
  startingDay: 0,
  yearRange: 20,
  minDate: null,
  maxDate: null
})

.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) {
  var self = this,
      ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;

  // Modes chain
  this.modes = ['day', 'month', 'year'];

  // Configuration attributes
  angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
                   'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) {
    self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
  });

  // Watchable date attributes
  angular.forEach(['minDate', 'maxDate'], function( key ) {
    if ( $attrs[key] ) {
      $scope.$parent.$watch($parse($attrs[key]), function(value) {
        self[key] = value ? new Date(value) : null;
        self.refreshView();
      });
    } else {
      self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
    }
  });

  $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
  $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
  this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date();

  $scope.isActive = function(dateObject) {
    if (self.compare(dateObject.date, self.activeDate) === 0) {
      $scope.activeDateId = dateObject.uid;
      return true;
    }
    return false;
  };

  this.init = function( ngModelCtrl_ ) {
    ngModelCtrl = ngModelCtrl_;

    ngModelCtrl.$render = function() {
      self.render();
    };
  };

  this.render = function() {
    if ( ngModelCtrl.$modelValue ) {
      var date = new Date( ngModelCtrl.$modelValue ),
          isValid = !isNaN(date);

      if ( isValid ) {
        this.activeDate = date;
      } else {
        $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
      }
      ngModelCtrl.$setValidity('date', isValid);
    }
    this.refreshView();
  };

  this.refreshView = function() {
    if ( this.element ) {
      this._refreshView();

      var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
      ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date)));
    }
  };

  this.createDateObject = function(date, format) {
    var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
    return {
      date: date,
      label: dateFilter(date, format),
      selected: model && this.compare(date, model) === 0,
      disabled: this.isDisabled(date),
      current: this.compare(date, new Date()) === 0
    };
  };

  this.isDisabled = function( date ) {
    return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode})));
  };

  // Split array into smaller arrays
  this.split = function(arr, size) {
    var arrays = [];
    while (arr.length > 0) {
      arrays.push(arr.splice(0, size));
    }
    return arrays;
  };

  $scope.select = function( date ) {
    if ( $scope.datepickerMode === self.minMode ) {
      var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0);
      dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
      ngModelCtrl.$setViewValue( dt );
      ngModelCtrl.$render();
    } else {
	  self.activeDate = date;
      $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ];
    }
	  $timeout(function () { self.activeDate = date; });
  };

  $scope.move = function( direction ) {
    var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
        month = self.activeDate.getMonth() + direction * (self.step.months || 0);
    self.activeDate.setFullYear(year, month, 1);
    self.refreshView();
  };

  $scope.toggleMode = function( direction ) {
    direction = direction || 1;

    if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
      return;
    }

    $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ];
  };

  // Key event mapper
  $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' };

  var focusElement = function() {
    $timeout(function() {
      self.element[0].focus();
    }, 0 , false);
  };

  // Listen for focus requests from popup directive
  $scope.$on('datepicker.focus', focusElement);

  $scope.keydown = function( evt ) {
    var key = $scope.keys[evt.which];

    if ( !key || evt.shiftKey || evt.altKey ) {
      return;
    }

    evt.preventDefault();
    evt.stopPropagation();

    if (key === 'enter' || key === 'space') {
      if ( self.isDisabled(self.activeDate)) {
        return; // do nothing
      }
      $scope.select(self.activeDate);
      focusElement();
    } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
      $scope.toggleMode(key === 'up' ? 1 : -1);
      focusElement();
    } else {
      self.handleKeyDown(key, evt);
      self.refreshView();
    }
  };
}])

.directive( 'datepicker', function () {
  return {
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/datepicker/datepicker.html',
    scope: {
      datepickerMode: '=?',
      dateDisabled: '&'
    },
    require: ['datepicker', '?^ngModel'],
    controller: 'DatepickerController',
    link: function(scope, element, attrs, ctrls) {
      var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];

      if ( ngModelCtrl ) {
        datepickerCtrl.init( ngModelCtrl );
      }
    }
  };
})

.directive('daypicker', ['dateFilter', function (dateFilter) {
  return {
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/datepicker/day.html',
    require: '^datepicker',
    link: function(scope, element, attrs, ctrl) {
      scope.showWeeks = ctrl.showWeeks;

      ctrl.step = { months: 1 };
      ctrl.element = element;

      var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
      function getDaysInMonth( year, month ) {
        return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month];
      }

      function getDates(startDate, n) {
        var dates = new Array(n), current = new Date(startDate), i = 0;
        current.setHours(12); // Prevent repeated dates because of timezone bug
        while ( i < n ) {
          dates[i++] = new Date(current);
          current.setDate( current.getDate() + 1 );
        }
        return dates;
      }

      ctrl._refreshView = function() {
        var year = ctrl.activeDate.getFullYear(),
          month = ctrl.activeDate.getMonth(),
          firstDayOfMonth = new Date(year, month, 1),
          difference = ctrl.startingDay - firstDayOfMonth.getDay(),
          numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
          firstDate = new Date(firstDayOfMonth);

        if ( numDisplayedFromPreviousMonth > 0 ) {
          firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
        }

        // 42 is the number of days on a six-month calendar
        var days = getDates(firstDate, 42);
        for (var i = 0; i < 42; i ++) {
          days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), {
            secondary: days[i].getMonth() !== month,
            uid: scope.uniqueId + '-' + i
          });
        }

        scope.labels = new Array(7);
        for (var j = 0; j < 7; j++) {
          scope.labels[j] = {
            abbr: dateFilter(days[j].date, ctrl.formatDayHeader),
            full: dateFilter(days[j].date, 'EEEE')
          };
        }

        scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle);
        scope.rows = ctrl.split(days, 7);

        if ( scope.showWeeks ) {
          scope.weekNumbers = [];
          var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ),
              numWeeks = scope.rows.length;
          while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {}
        }
      };

      ctrl.compare = function(date1, date2) {
        return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
      };

      function getISO8601WeekNumber(date) {
        var checkDate = new Date(date);
        checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
        var time = checkDate.getTime();
        checkDate.setMonth(0); // Compare with Jan 1
        checkDate.setDate(1);
        return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
      }

      ctrl.handleKeyDown = function( key, evt ) {
        var date = ctrl.activeDate.getDate();

        if (key === 'left') {
          date = date - 1;   // up
        } else if (key === 'up') {
          date = date - 7;   // down
        } else if (key === 'right') {
          date = date + 1;   // down
        } else if (key === 'down') {
          date = date + 7;
        } else if (key === 'pageup' || key === 'pagedown') {
          var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
          ctrl.activeDate.setMonth(month, 1);
          date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date);
        } else if (key === 'home') {
          date = 1;
        } else if (key === 'end') {
          date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth());
        }
        ctrl.activeDate.setDate(date);
      };

      ctrl.refreshView();
    }
  };
}])

.directive('monthpicker', ['dateFilter', function (dateFilter) {
  return {
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/datepicker/month.html',
    require: '^datepicker',
    link: function(scope, element, attrs, ctrl) {
      ctrl.step = { years: 1 };
      ctrl.element = element;

      ctrl._refreshView = function() {
        var months = new Array(12),
            year = ctrl.activeDate.getFullYear();

        for ( var i = 0; i < 12; i++ ) {
          months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), {
            uid: scope.uniqueId + '-' + i
          });
        }

        scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle);
        scope.rows = ctrl.split(months, 3);
      };

      ctrl.compare = function(date1, date2) {
        return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
      };

      ctrl.handleKeyDown = function( key, evt ) {
        var date = ctrl.activeDate.getMonth();

        if (key === 'left') {
          date = date - 1;   // up
        } else if (key === 'up') {
          date = date - 3;   // down
        } else if (key === 'right') {
          date = date + 1;   // down
        } else if (key === 'down') {
          date = date + 3;
        } else if (key === 'pageup' || key === 'pagedown') {
          var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
          ctrl.activeDate.setFullYear(year);
        } else if (key === 'home') {
          date = 0;
        } else if (key === 'end') {
          date = 11;
        }
        ctrl.activeDate.setMonth(date);
      };

      ctrl.refreshView();
    }
  };
}])

.directive('yearpicker', ['dateFilter', function (dateFilter) {
  return {
    restrict: 'EA',
    replace: true,
    templateUrl: 'template/datepicker/year.html',
    require: '^datepicker',
    link: function(scope, element, attrs, ctrl) {
      var range = ctrl.yearRange;

      ctrl.step = { years: range };
      ctrl.element = element;

      function getStartingYear( year ) {
        return parseInt((year - 1) / range, 10) * range + 1;
      }

      ctrl._refreshView = function() {
        var years = new Array(range);

        for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) {
          years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), {
            uid: scope.uniqueId + '-' + i
          });
        }

        scope.title = [years[0].label, years[range - 1].label].join(' - ');
        scope.rows = ctrl.split(years, 5);
      };

      ctrl.compare = function(date1, date2) {
        return date1.getFullYear() - date2.getFullYear();
      };

      ctrl.handleKeyDown = function( key, evt ) {
        var date = ctrl.activeDate.getFullYear();

        if (key === 'left') {
          date = date - 1;   // up
        } else if (key === 'up') {
          date = date - 5;   // down
        } else if (key === 'right') {
          date = date + 1;   // down
        } else if (key === 'down') {
          date = date + 5;
        } else if (key === 'pageup' || key === 'pagedown') {
          date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years;
        } else if (key === 'home') {
          date = getStartingYear( ctrl.activeDate.getFullYear() );
        } else if (key === 'end') {
          date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1;
        }
        ctrl.activeDate.setFullYear(date);
      };

      ctrl.refreshView();
    }
  };
}])

.constant('datepickerPopupConfig', {
  datepickerPopup: 'yyyy-MM-dd',
  currentText: 'Today',
  clearText: 'Clear',
  closeText: 'Done',
  closeOnDateSelection: true,
  appendToBody: false,
  showButtonBar: true
})

.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig',
function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) {
  return {
    restrict: 'EA',
    require: 'ngModel',
    scope: {
      isOpen: '=?',
      currentText: '@',
      clearText: '@',
      closeText: '@',
      dateDisabled: '&'
    },
    link: function(scope, element, attrs, ngModel) {
      var dateFormat,
          closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection,
          appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;

      scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;

      scope.getText = function( key ) {
        return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
      };

      attrs.$observe('datepickerPopup', function(value) {
          dateFormat = value || datepickerPopupConfig.datepickerPopup;
          ngModel.$render();
      });

      // popup element used to display calendar
      var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
      popupEl.attr({
        'ng-model': 'date',
        'ng-change': 'dateSelection()'
      });

      function cameltoDash( string ){
        return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
      }

      // datepicker element
      var datepickerEl = angular.element(popupEl.children()[0]);
      if ( attrs.datepickerOptions ) {
        angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) {
          datepickerEl.attr( cameltoDash(option), value );
        });
      }

      scope.watchData = {};
      angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) {
        if ( attrs[key] ) {
          var getAttribute = $parse(attrs[key]);
          scope.$parent.$watch(getAttribute, function(value){
            scope.watchData[key] = value;
          });
          datepickerEl.attr(cameltoDash(key), 'watchData.' + key);

          // Propagate changes from datepicker to outside
          if ( key === 'datepickerMode' ) {
            var setAttribute = getAttribute.assign;
            scope.$watch('watchData.' + key, function(value, oldvalue) {
              if ( value !== oldvalue ) {
                setAttribute(scope.$parent, value);
              }
            });
          }
        }
      });
      if (attrs.dateDisabled) {
        datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
      }

      function parseDate(viewValue) {
        if (!viewValue) {
          ngModel.$setValidity('date', true);
          return null;
        } else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
          ngModel.$setValidity('date', true);
          return viewValue;
        } else if (angular.isString(viewValue)) {
          var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue);
          if (isNaN(date)) {
            ngModel.$setValidity('date', false);
            return undefined;
          } else {
            ngModel.$setValidity('date', true);
            return date;
          }
        } else {
          ngModel.$setValidity('date', false);
          return undefined;
        }
      }
      ngModel.$parsers.unshift(parseDate);

      ngModel.$formatters.push(function (value) {
         return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
      });

      // Inner change
      scope.dateSelection = function(dt) {
        if (angular.isDefined(dt)) {
          scope.date = dt;
        }
        ngModel.$setViewValue(scope.date);
        ngModel.$render();

        if ( closeOnDateSelection ) {
          scope.isOpen = false;
          element[0].focus();
        }
      };

      element.bind('input change keyup', function() {
        scope.$apply(function() {
          scope.date = ngModel.$modelValue;
        });
      });

      // Outer change
      ngModel.$render = function() {
        var date = ngModel.$viewValue ? dateFilter(parseDate(ngModel.$viewValue), dateFormat) : '';
        element.val(date);
        scope.date = parseDate( ngModel.$modelValue );
      };

      var documentClickBind = function(event) {
        if (scope.isOpen && event.target !== element[0]) {
          scope.$apply(function() {
            scope.isOpen = false;
          });
        }
      };

      var keydown = function(evt, noApply) {
        scope.keydown(evt);
      };
      element.bind('keydown', keydown);

      scope.keydown = function(evt) {
        if (evt.which === 27) {
          evt.preventDefault();
          evt.stopPropagation();
          scope.close();
        } else if (evt.which === 40 && !scope.isOpen) {
          scope.isOpen = true;
        }
      };

      scope.$watch('isOpen', function(value) {
        if (value) {
          scope.$broadcast('datepicker.focus');
          scope.position = appendToBody ? $position.offset(element) : $position.position(element);
          scope.position.top = scope.position.top + element.prop('offsetHeight');

          $document.bind('click', documentClickBind);
        } else {
          $document.unbind('click', documentClickBind);
        }
      });

      scope.select = function( date ) {
        if (date === 'today') {
          var today = new Date();
          if (angular.isDate(ngModel.$modelValue)) {
            date = new Date(ngModel.$modelValue);
            date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
          } else {
            date = new Date(today.setHours(0, 0, 0, 0));
          }
        }
        scope.dateSelection( date );
      };

      scope.close = function() {
        scope.isOpen = false;
        element[0].focus();
      };

      var $popup = $compile(popupEl)(scope);
      // Prevent jQuery cache memory leak (template is now redundant after linking)
      popupEl.remove();

      if ( appendToBody ) {
        $document.find('body').append($popup);
      } else {
        element.after($popup);
      }

      scope.$on('$destroy', function() {
        $popup.remove();
        element.unbind('keydown', keydown);
        $document.unbind('click', documentClickBind);
      });
    }
  };
}])

.directive('datepickerPopupWrap', function() {
  return {
    restrict:'EA',
    replace: true,
    transclude: true,
    templateUrl: 'template/datepicker/popup.html',
    link:function (scope, element, attrs) {
      element.bind('click', function(event) {
        event.preventDefault();
        event.stopPropagation();
      });
    }
  };
});
angular.module('ui.bootstrap.dateparser', [])

.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) {

  this.parsers = {};

  var formatCodeToRegex = {
    'yyyy': {
      regex: '\\d{4}',
      apply: function(value) { this.year = +value; }
    },
    'yy': {
      regex: '\\d{2}',
      apply: function(value) { this.year = +value + 2000; }
    },
    'y': {
      regex: '\\d{1,4}',
      apply: function(value) { this.year = +value; }
    },
    'MMMM': {
      regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
      apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
    },
    'MMM': {
      regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
      apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
    },
    'MM': {
      regex: '0[1-9]|1[0-2]',
      apply: function(value) { this.month = value - 1; }
    },
    'M': {
      regex: '[1-9]|1[0-2]',
      apply: function(value) { this.month = value - 1; }
    },
    'dd': {
      regex: '[0-2][0-9]{1}|3[0-1]{1}',
      apply: function(value) { this.date = +value; }
    },
    'd': {
      regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
      apply: function(value) { this.date = +value; }
    },
    'EEEE': {
      regex: $locale.DATETIME_FORMATS.DAY.join('|')
    },
    'EEE': {
      regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
    }
  };

  function createParser(format) {
    var map = [], regex = format.split('');

    angular.forEach(formatCodeToRegex, function(data, code) {
      var index = format.indexOf(code);

      if (index > -1) {
        format = format.split('');

        regex[index] = '(' + data.regex + ')';
        format[index] = '$'; // Custom symbol to define consumed part of format
        for (var i = index + 1, n = index + code.length; i < n; i++) {
          regex[i] = '';
          format[i] = '$';
        }
        format = format.join('');

        map.push({ index: index, apply: data.apply });
      }
    });

    return {
      regex: new RegExp('^' + regex.join('') + '$'),
      map: orderByFilter(map, 'index')
    };
  }

  this.parse = function(input, format) {
    if ( !angular.isString(input) || !format ) {
      return input;
    }

    format = $locale.DATETIME_FORMATS[format] || format;

    if ( !this.parsers[format] ) {
      this.parsers[format] = createParser(format);
    }

    var parser = this.parsers[format],
        regex = parser.regex,
        map = parser.map,
        results = input.match(regex);

    if ( results && results.length ) {
      var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt;

      for( var i = 1, n = results.length; i < n; i++ ) {
        var mapper = map[i-1];
        if ( mapper.apply ) {
          mapper.apply.call(fields, results[i]);
        }
      }

      if ( isValid(fields.year, fields.month, fields.date) ) {
        dt = new Date( fields.year, fields.month, fields.date, fields.hours);
      }

      return dt;
    }
  };

  // Check if date is valid for specific month (and year for February).
  // Month: 0 = Jan, 1 = Feb, etc
  function isValid(year, month, date) {
    if ( month === 1 && date > 28) {
        return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
    }

    if ( month === 3 || month === 5 || month === 8 || month === 10) {
        return date < 31;
    }

    return true;
  }
}]);
angular.module('ui.bootstrap.position', [])

/**
 * A set of utility methods that can be use to retrieve position of DOM elements.
 * It is meant to be used where we need to absolute-position DOM elements in
 * relation to other, existing elements (this is the case for tooltips, popovers,
 * typeahead suggestions etc.).
 */
  .factory('$position', ['$document', '$window', function ($document, $window) {

    function getStyle(el, cssprop) {
      if (el.currentStyle) { //IE
        return el.currentStyle[cssprop];
      } else if ($window.getComputedStyle) {
        return $window.getComputedStyle(el)[cssprop];
      }
      // finally try and get inline style
      return el.style[cssprop];
    }

    /**
     * Checks if a given element is statically positioned
     * @param element - raw DOM element
     */
    function isStaticPositioned(element) {
      return (getStyle(element, 'position') || 'static' ) === 'static';
    }

    /**
     * returns the closest, non-statically positioned parentOffset of a given element
     * @param element
     */
    var parentOffsetEl = function (element) {
      var docDomEl = $document[0];
      var offsetParent = element.offsetParent || docDomEl;
      while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
        offsetParent = offsetParent.offsetParent;
      }
      return offsetParent || docDomEl;
    };

    return {
      /**
       * Provides read-only equivalent of jQuery's position function:
       * http://api.jquery.com/position/
       */
      position: function (element) {
        var elBCR = this.offset(element);
        var offsetParentBCR = { top: 0, left: 0 };
        var offsetParentEl = parentOffsetEl(element[0]);
        if (offsetParentEl != $document[0]) {
          offsetParentBCR = this.offset(angular.element(offsetParentEl));
          offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
          offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
        }

        var boundingClientRect = element[0].getBoundingClientRect();
        return {
          width: boundingClientRect.width || element.prop('offsetWidth'),
          height: boundingClientRect.height || element.prop('offsetHeight'),
          top: elBCR.top - offsetParentBCR.top,
          left: elBCR.left - offsetParentBCR.left
        };
      },

      /**
       * Provides read-only equivalent of jQuery's offset function:
       * http://api.jquery.com/offset/
       */
      offset: function (element) {
        var boundingClientRect = element[0].getBoundingClientRect();
        return {
          width: boundingClientRect.width || element.prop('offsetWidth'),
          height: boundingClientRect.height || element.prop('offsetHeight'),
          top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
          left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
        };
      },

      /**
       * Provides coordinates for the targetEl in relation to hostEl
       */
      positionElements: function (hostEl, targetEl, positionStr, appendToBody) {

        var positionStrParts = positionStr.split('-');
        var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';

        var hostElPos,
          targetElWidth,
          targetElHeight,
          targetElPos;

        hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);

        targetElWidth = targetEl.prop('offsetWidth');
        targetElHeight = targetEl.prop('offsetHeight');

        var shiftWidth = {
          center: function () {
            return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
          },
          left: function () {
            return hostElPos.left;
          },
          right: function () {
            return hostElPos.left + hostElPos.width;
          }
        };

        var shiftHeight = {
          center: function () {
            return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
          },
          top: function () {
            return hostElPos.top;
          },
          bottom: function () {
            return hostElPos.top + hostElPos.height;
          }
        };

        switch (pos0) {
          case 'right':
            targetElPos = {
              top: shiftHeight[pos1](),
              left: shiftWidth[pos0]()
            };
            break;
          case 'left':
            targetElPos = {
              top: shiftHeight[pos1](),
              left: hostElPos.left - targetElWidth
            };
            break;
          case 'bottom':
            targetElPos = {
              top: shiftHeight[pos0](),
              left: shiftWidth[pos1]()
            };
            break;
          default:
            targetElPos = {
              top: hostElPos.top - targetElHeight,
              left: shiftWidth[pos1]()
            };
            break;
        }

        return targetElPos;
      }
    };
  }]);
<ul class="dropdown-menu" ng-style="{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}" ng-keydown="keydown($event)">
	<li ng-transclude></li>
	<li ng-if="showButtonBar" style="padding:10px 9px 2px">
		<span class="btn-group">
			<button type="button" class="btn btn-sm btn-info" ng-click="select('today')">{{ getText('current') }}</button>
			<button type="button" class="btn btn-sm btn-danger" ng-click="select(null)">{{ getText('clear') }}</button>
		</span>
		<button type="button" class="btn btn-sm btn-success pull-right" ng-click="close()">{{ getText('close') }}</button>
	</li>
</ul>
<div ng-switch="datepickerMode" role="application" ng-keydown="keydown($event)">
  <daypicker ng-switch-when="day" tabindex="0"></daypicker>
  <monthpicker ng-switch-when="month" tabindex="0"></monthpicker>
  <yearpicker ng-switch-when="year" tabindex="0"></yearpicker>
</div>
<table role="grid" aria-labelledby="{{uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
  <thead>
    <tr>
      <th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>
      <th colspan="{{5 + showWeeks}}"><button id="{{uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
      <th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>
    </tr>
    <tr>
      <th ng-show="showWeeks" class="text-center"></th>
      <th ng-repeat="label in labels track by $index" class="text-center"><small aria-label="{{label.full}}">{{label.abbr}}</small></th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="row in rows track by $index">
      <td ng-show="showWeeks" class="text-center h6"><em>{{ weekNumbers[$index] }}</em></td>
      <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{dt.uid}}" aria-disabled="{{!!dt.disabled}}">
          <button type="button" style="width:100%;" class="btn btn-sm" ng-class="{'btn-info': dt.selected, 'btn-success':!dt.disabled, 'btn-default': dt.disabled,  active: isActive(dt)}" 
              ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1">
              <span ng-class="{'is-muted': dt.secondary, 'text-info': dt.current}">{{dt.label}}</span>
          </button>
      </td>
    </tr>
  </tbody>
</table>
<table role="grid" aria-labelledby="{{uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
  <thead>
    <tr>
      <th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>
      <th><button id="{{uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
      <th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="row in rows track by $index">
      <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{dt.uid}}" aria-disabled="{{!!dt.disabled}}">
        <button type="button" style="width:100%;" class="btn btn-default" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="{'text-info': dt.current}">{{dt.label}}</span></button>
      </td>
    </tr>
  </tbody>
</table>
<table role="grid" aria-labelledby="{{uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
  <thead>
    <tr>
      <th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>
      <th colspan="3"><button id="{{uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
      <th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="row in rows track by $index">
      <td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{dt.uid}}" aria-disabled="{{!!dt.disabled}}">
        <button type="button" style="width:100%;" class="btn btn-default" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="{'text-info': dt.current}">{{dt.label}}</span></button>
      </td>
    </tr>
  </tbody>
</table>
[{
  "timeZone": "Africa/Abidjan",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Accra",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Addis_Ababa",
  "offset": "3:00:00"
}, {
  "timeZone": "Africa/Algiers",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Asmera",
  "offset": "3:00:00"
}, {
  "timeZone": "Africa/Bamako",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Bangui",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Banjul",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Bissau",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Blantyre",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Brazzaville",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Bujumbura",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Cairo",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Casablanca",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Ceuta",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Conakry",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Dakar",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Dar_es_Salaam",
  "offset": "3:00:00"
}, {
  "timeZone": "Africa/Djibouti",
  "offset": "3:00:00"
}, {
  "timeZone": "Africa/Douala",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/El_Aaiun",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Freetown",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Gaborone",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Harare",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Johannesburg",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Juba",
  "offset": "3:00:00"
}, {
  "timeZone": "Africa/Kampala",
  "offset": "3:00:00"
}, {
  "timeZone": "Africa/Khartoum",
  "offset": "3:00:00"
}, {
  "timeZone": "Africa/Kigali",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Kinshasa",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Lagos",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Libreville",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Lome",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Luanda",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Lubumbashi",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Lusaka",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Malabo",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Maputo",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Maseru",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Mbabane",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Mogadishu",
  "offset": "3:00:00"
}, {
  "timeZone": "Africa/Monrovia",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Nairobi",
  "offset": "3:00:00"
}, {
  "timeZone": "Africa/Ndjamena",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Niamey",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Nouakchott",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Ouagadougou",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Porto-Novo",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Sao_Tome",
  "offset": "0:00:00"
}, {
  "timeZone": "Africa/Tripoli",
  "offset": "2:00:00"
}, {
  "timeZone": "Africa/Tunis",
  "offset": "1:00:00"
}, {
  "timeZone": "Africa/Windhoek",
  "offset": "1:00:00"
}, {
  "timeZone": "America/Anchorage",
  "offset": "-9:00:00"
}, {
  "timeZone": "America/Anguilla",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Antigua",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Araguaina",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Argentina/La_Rioja",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Argentina/Rio_Gallegos",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Argentina/Salta",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Argentina/San_Juan",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Argentina/San_Luis",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Argentina/Tucuman",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Argentina/Ushuaia",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Aruba",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Asuncion",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Bahia",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Bahia_Banderas",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Barbados",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Belem",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Belize",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Blanc-Sablon",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Boa_Vista",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Bogota",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Boise",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Buenos_Aires",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Cambridge_Bay",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Campo_Grande",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Cancun",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Caracas",
  "offset": "-4:30:00"
}, {
  "timeZone": "America/Catamarca",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Cayenne",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Cayman",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Chicago",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Chihuahua",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Coral_Harbour",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Cordoba",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Costa_Rica",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Creston",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Cuiaba",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Curacao",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Danmarkshavn",
  "offset": "0:00:00"
}, {
  "timeZone": "America/Dawson",
  "offset": "-8:00:00"
}, {
  "timeZone": "America/Dawson_Creek",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Denver",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Detroit",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Dominica",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Edmonton",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Eirunepe",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/El_Salvador",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Fort_Nelson",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Fortaleza",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Glace_Bay",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Godthab",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Goose_Bay",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Grand_Turk",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Grenada",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Guadeloupe",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Guatemala",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Guayaquil",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Guyana",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Halifax",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Havana",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Hermosillo",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Indiana/Knox",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Indiana/Marengo",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Indiana/Petersburg",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Indiana/Tell_City",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Indiana/Vevay",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Indiana/Vincennes",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Indiana/Winamac",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Indianapolis",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Inuvik",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Iqaluit",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Jamaica",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Jujuy",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Juneau",
  "offset": "-9:00:00"
}, {
  "timeZone": "America/Kentucky/Monticello",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Kralendijk",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/La_Paz",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Lima",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Los_Angeles",
  "offset": "-8:00:00"
}, {
  "timeZone": "America/Louisville",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Lower_Princes",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Maceio",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Managua",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Manaus",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Marigot",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Martinique",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Matamoros",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Mazatlan",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Mendoza",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Menominee",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Merida",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Metlakatla",
  "offset": "-9:00:00"
}, {
  "timeZone": "America/Mexico_City",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Moncton",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Monterrey",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Montevideo",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Montreal",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Montserrat",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Nassau",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/New_York",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Nipigon",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Nome",
  "offset": "-9:00:00"
}, {
  "timeZone": "America/Noronha",
  "offset": "-2:00:00"
}, {
  "timeZone": "America/North_Dakota/Beulah",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/North_Dakota/Center",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/North_Dakota/New_Salem",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Ojinaga",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Panama",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Pangnirtung",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Paramaribo",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Phoenix",
  "offset": "-7:00:00"
}, {
  "timeZone": "America/Port_of_Spain",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Port-au-Prince",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Porto_Velho",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Puerto_Rico",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Rainy_River",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Rankin_Inlet",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Recife",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Regina",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Resolute",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Rio_Branco",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Santa_Isabel",
  "offset": "-8:00:00"
}, {
  "timeZone": "America/Santarem",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Santiago",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Santo_Domingo",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Sao_Paulo",
  "offset": "-3:00:00"
}, {
  "timeZone": "America/Scoresbysund",
  "offset": "-1:00:00"
}, {
  "timeZone": "America/Sitka",
  "offset": "-9:00:00"
}, {
  "timeZone": "America/St_Barthelemy",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/St_Johns",
  "offset": "-3:30:00"
}, {
  "timeZone": "America/St_Kitts",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/St_Lucia",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/St_Thomas",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/St_Vincent",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Swift_Current",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Tegucigalpa",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Thule",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Thunder_Bay",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Tijuana",
  "offset": "-8:00:00"
}, {
  "timeZone": "America/Toronto",
  "offset": "-5:00:00"
}, {
  "timeZone": "America/Tortola",
  "offset": "-4:00:00"
}, {
  "timeZone": "America/Vancouver",
  "offset": "-8:00:00"
}, {
  "timeZone": "America/Whitehorse",
  "offset": "-8:00:00"
}, {
  "timeZone": "America/Winnipeg",
  "offset": "-6:00:00"
}, {
  "timeZone": "America/Yakutat",
  "offset": "-9:00:00"
}, {
  "timeZone": "America/Yellowknife",
  "offset": "-7:00:00"
}, {
  "timeZone": "Antarctica/Casey",
  "offset": "8:00:00"
}, {
  "timeZone": "Antarctica/Davis",
  "offset": "7:00:00"
}, {
  "timeZone": "Antarctica/DumontDUrville",
  "offset": "10:00:00"
}, {
  "timeZone": "Antarctica/Macquarie",
  "offset": "11:00:00"
}, {
  "timeZone": "Antarctica/Mawson",
  "offset": "5:00:00"
}, {
  "timeZone": "Antarctica/McMurdo",
  "offset": "12:00:00"
}, {
  "timeZone": "Antarctica/Palmer",
  "offset": "-4:00:00"
}, {
  "timeZone": "Antarctica/Rothera",
  "offset": "-3:00:00"
}, {
  "timeZone": "Antarctica/Syowa",
  "offset": "3:00:00"
}, {
  "timeZone": "Antarctica/Vostok",
  "offset": "6:00:00"
}, {
  "timeZone": "Arctic/Longyearbyen",
  "offset": "1:00:00"
}, {
  "timeZone": "Asia/Aden",
  "offset": "3:00:00"
}, {
  "timeZone": "Asia/Almaty",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Amman",
  "offset": "2:00:00"
}, {
  "timeZone": "Asia/Anadyr",
  "offset": "12:00:00"
}, {
  "timeZone": "Asia/Aqtau",
  "offset": "5:00:00"
}, {
  "timeZone": "Asia/Aqtobe",
  "offset": "5:00:00"
}, {
  "timeZone": "Asia/Ashgabat",
  "offset": "5:00:00"
}, {
  "timeZone": "Asia/Baghdad",
  "offset": "3:00:00"
}, {
  "timeZone": "Asia/Bahrain",
  "offset": "3:00:00"
}, {
  "timeZone": "Asia/Baku",
  "offset": "4:00:00"
}, {
  "timeZone": "Asia/Bangkok",
  "offset": "7:00:00"
}, {
  "timeZone": "Asia/Barnaul",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Beirut",
  "offset": "2:00:00"
}, {
  "timeZone": "Asia/Bishkek",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Brunei",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Calcutta",
  "offset": "5:30:00"
}, {
  "timeZone": "Asia/Chita",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Choibalsan",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Colombo",
  "offset": "5:30:00"
}, {
  "timeZone": "Asia/Damascus",
  "offset": "2:00:00"
}, {
  "timeZone": "Asia/Dhaka",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Dili",
  "offset": "9:00:00"
}, {
  "timeZone": "Asia/Dubai",
  "offset": "4:00:00"
}, {
  "timeZone": "Asia/Dushanbe",
  "offset": "5:00:00"
}, {
  "timeZone": "Asia/Hong_Kong",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Irkutsk",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Jakarta",
  "offset": "7:00:00"
}, {
  "timeZone": "Asia/Jayapura",
  "offset": "9:00:00"
}, {
  "timeZone": "Asia/Jerusalem",
  "offset": "2:00:00"
}, {
  "timeZone": "Asia/Kabul",
  "offset": "4:30:00"
}, {
  "timeZone": "Asia/Kamchatka",
  "offset": "12:00:00"
}, {
  "timeZone": "Asia/Karachi",
  "offset": "5:00:00"
}, {
  "timeZone": "Asia/Katmandu",
  "offset": "5:45:00"
}, {
  "timeZone": "Asia/Khandyga",
  "offset": "9:00:00"
}, {
  "timeZone": "Asia/Krasnoyarsk",
  "offset": "7:00:00"
}, {
  "timeZone": "Asia/Kuala_Lumpur",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Kuching",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Kuwait",
  "offset": "3:00:00"
}, {
  "timeZone": "Asia/Macau",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Magadan",
  "offset": "10:00:00"
}, {
  "timeZone": "Asia/Makassar",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Manila",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Muscat",
  "offset": "4:00:00"
}, {
  "timeZone": "Asia/Nicosia",
  "offset": "2:00:00"
}, {
  "timeZone": "Asia/Novokuznetsk",
  "offset": "7:00:00"
}, {
  "timeZone": "Asia/Novosibirsk",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Omsk",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Oral",
  "offset": "5:00:00"
}, {
  "timeZone": "Asia/Phnom_Penh",
  "offset": "7:00:00"
}, {
  "timeZone": "Asia/Pontianak",
  "offset": "7:00:00"
}, {
  "timeZone": "Asia/Pyongyang",
  "offset": "8:30:00"
}, {
  "timeZone": "Asia/Qatar",
  "offset": "3:00:00"
}, {
  "timeZone": "Asia/Qyzylorda",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Rangoon",
  "offset": "6:30:00"
}, {
  "timeZone": "Asia/Riyadh",
  "offset": "3:00:00"
}, {
  "timeZone": "Asia/Saigon",
  "offset": "7:00:00"
}, {
  "timeZone": "Asia/Sakhalin",
  "offset": "10:00:00"
}, {
  "timeZone": "Asia/Samarkand",
  "offset": "5:00:00"
}, {
  "timeZone": "Asia/Seoul",
  "offset": "9:00:00"
}, {
  "timeZone": "Asia/Shanghai",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Singapore",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Srednekolymsk",
  "offset": "11:00:00"
}, {
  "timeZone": "Asia/Taipei",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Tashkent",
  "offset": "5:00:00"
}, {
  "timeZone": "Asia/Tbilisi",
  "offset": "4:00:00"
}, {
  "timeZone": "Asia/Tehran",
  "offset": "3:30:00"
}, {
  "timeZone": "Asia/Thimphu",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Tokyo",
  "offset": "9:00:00"
}, {
  "timeZone": "Asia/Tomsk",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Ulaanbaatar",
  "offset": "8:00:00"
}, {
  "timeZone": "Asia/Urumqi",
  "offset": "6:00:00"
}, {
  "timeZone": "Asia/Ust-Nera",
  "offset": "10:00:00"
}, {
  "timeZone": "Asia/Vientiane",
  "offset": "7:00:00"
}, {
  "timeZone": "Asia/Vladivostok",
  "offset": "10:00:00"
}, {
  "timeZone": "Asia/Yakutsk",
  "offset": "9:00:00"
}, {
  "timeZone": "Asia/Yekaterinburg",
  "offset": "5:00:00"
}, {
  "timeZone": "Asia/Yerevan",
  "offset": "4:00:00"
}, {
  "timeZone": "Atlantic/Azores",
  "offset": "-1:00:00"
}, {
  "timeZone": "Atlantic/Bermuda",
  "offset": "-4:00:00"
}, {
  "timeZone": "Atlantic/Canary",
  "offset": "0:00:00"
}, {
  "timeZone": "Atlantic/Cape_Verde",
  "offset": "-1:00:00"
}, {
  "timeZone": "Atlantic/Faeroe",
  "offset": "0:00:00"
}, {
  "timeZone": "Atlantic/Madeira",
  "offset": "0:00:00"
}, {
  "timeZone": "Atlantic/Reykjavik",
  "offset": "0:00:00"
}, {
  "timeZone": "Atlantic/South_Georgia",
  "offset": "-2:00:00"
}, {
  "timeZone": "Atlantic/St_Helena",
  "offset": "0:00:00"
}, {
  "timeZone": "Atlantic/Stanley",
  "offset": "-3:00:00"
}, {
  "timeZone": "Australia/Adelaide",
  "offset": "9:30:00"
}, {
  "timeZone": "Australia/Brisbane",
  "offset": "10:00:00"
}, {
  "timeZone": "Australia/Broken_Hill",
  "offset": "9:30:00"
}, {
  "timeZone": "Australia/Currie",
  "offset": "10:00:00"
}, {
  "timeZone": "Australia/Darwin",
  "offset": "9:30:00"
}, {
  "timeZone": "Australia/Hobart",
  "offset": "10:00:00"
}, {
  "timeZone": "Australia/Lindeman",
  "offset": "10:00:00"
}, {
  "timeZone": "Australia/Melbourne",
  "offset": "10:00:00"
}, {
  "timeZone": "Australia/Perth",
  "offset": "8:00:00"
}, {
  "timeZone": "Australia/Sydney",
  "offset": "10:00:00"
}, {
  "timeZone": "CST6CDT",
  "offset": "-6:00:00"
}, {
  "timeZone": "EST5EDT",
  "offset": "-5:00:00"
}, {
  "timeZone": "Etc/GMT",
  "offset": "0:00:00"
}, {
  "timeZone": "Etc/GMT+1",
  "offset": "-1:00:00"
}, {
  "timeZone": "Etc/GMT+10",
  "offset": "-10:00:00"
}, {
  "timeZone": "Etc/GMT+11",
  "offset": "-11:00:00"
}, {
  "timeZone": "Etc/GMT+12",
  "offset": "-12:00:00"
}, {
  "timeZone": "Etc/GMT+2",
  "offset": "-2:00:00"
}, {
  "timeZone": "Etc/GMT+3",
  "offset": "-3:00:00"
}, {
  "timeZone": "Etc/GMT+4",
  "offset": "-4:00:00"
}, {
  "timeZone": "Etc/GMT+5",
  "offset": "-5:00:00"
}, {
  "timeZone": "Etc/GMT+6",
  "offset": "-6:00:00"
}, {
  "timeZone": "Etc/GMT+7",
  "offset": "-7:00:00"
}, {
  "timeZone": "Etc/GMT-1",
  "offset": "1:00:00"
}, {
  "timeZone": "Etc/GMT-10",
  "offset": "10:00:00"
}, {
  "timeZone": "Etc/GMT-11",
  "offset": "11:00:00"
}, {
  "timeZone": "Etc/GMT-12",
  "offset": "12:00:00"
}, {
  "timeZone": "Etc/GMT-13",
  "offset": "13:00:00"
}, {
  "timeZone": "Etc/GMT-14",
  "offset": "14:00:00"
}, {
  "timeZone": "Etc/GMT-2",
  "offset": "2:00:00"
}, {
  "timeZone": "Etc/GMT-3",
  "offset": "3:00:00"
}, {
  "timeZone": "Etc/GMT-4",
  "offset": "4:00:00"
}, {
  "timeZone": "Etc/GMT-5",
  "offset": "5:00:00"
}, {
  "timeZone": "Etc/GMT-6",
  "offset": "6:00:00"
}, {
  "timeZone": "Etc/GMT-7",
  "offset": "7:00:00"
}, {
  "timeZone": "Etc/GMT-8",
  "offset": "8:00:00"
}, {
  "timeZone": "Etc/GMT-9",
  "offset": "9:00:00"
}, {
  "timeZone": "Europe/Amsterdam",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Andorra",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Astrakhan",
  "offset": "3:00:00"
}, {
  "timeZone": "Europe/Athens",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Belgrade",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Berlin",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Bratislava",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Brussels",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Bucharest",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Budapest",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Busingen",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Chisinau",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Copenhagen",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Dublin",
  "offset": "0:00:00"
}, {
  "timeZone": "Europe/Gibraltar",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Guernsey",
  "offset": "0:00:00"
}, {
  "timeZone": "Europe/Helsinki",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Isle_of_Man",
  "offset": "0:00:00"
}, {
  "timeZone": "Europe/Istanbul",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Jersey",
  "offset": "0:00:00"
}, {
  "timeZone": "Europe/Kaliningrad",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Kiev",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Kirov",
  "offset": "3:00:00"
}, {
  "timeZone": "Europe/Lisbon",
  "offset": "0:00:00"
}, {
  "timeZone": "Europe/Ljubljana",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/London",
  "offset": "0:00:00"
}, {
  "timeZone": "Europe/Luxembourg",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Madrid",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Malta",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Mariehamn",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Minsk",
  "offset": "3:00:00"
}, {
  "timeZone": "Europe/Monaco",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Moscow",
  "offset": "3:00:00"
}, {
  "timeZone": "Europe/Oslo",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Paris",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Podgorica",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Prague",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Riga",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Rome",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Samara",
  "offset": "4:00:00"
}, {
  "timeZone": "Europe/San_Marino",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Sarajevo",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Simferopol",
  "offset": "3:00:00"
}, {
  "timeZone": "Europe/Skopje",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Sofia",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Stockholm",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Tallinn",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Tirane",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Ulyanovsk",
  "offset": "3:00:00"
}, {
  "timeZone": "Europe/Uzhgorod",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Vaduz",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Vatican",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Vienna",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Vilnius",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Volgograd",
  "offset": "3:00:00"
}, {
  "timeZone": "Europe/Warsaw",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Zagreb",
  "offset": "1:00:00"
}, {
  "timeZone": "Europe/Zaporozhye",
  "offset": "2:00:00"
}, {
  "timeZone": "Europe/Zurich",
  "offset": "1:00:00"
}, {
  "timeZone": "Indian/Antananarivo",
  "offset": "3:00:00"
}, {
  "timeZone": "Indian/Chagos",
  "offset": "6:00:00"
}, {
  "timeZone": "Indian/Christmas",
  "offset": "7:00:00"
}, {
  "timeZone": "Indian/Cocos",
  "offset": "6:30:00"
}, {
  "timeZone": "Indian/Comoro",
  "offset": "3:00:00"
}, {
  "timeZone": "Indian/Kerguelen",
  "offset": "5:00:00"
}, {
  "timeZone": "Indian/Mahe",
  "offset": "4:00:00"
}, {
  "timeZone": "Indian/Maldives",
  "offset": "5:00:00"
}, {
  "timeZone": "Indian/Mauritius",
  "offset": "4:00:00"
}, {
  "timeZone": "Indian/Mayotte",
  "offset": "3:00:00"
}, {
  "timeZone": "Indian/Reunion",
  "offset": "4:00:00"
}, {
  "timeZone": "MST7MDT",
  "offset": "-7:00:00"
}, {
  "timeZone": "Pacific/Apia",
  "offset": "13:00:00"
}, {
  "timeZone": "Pacific/Auckland",
  "offset": "12:00:00"
}, {
  "timeZone": "Pacific/Bougainville",
  "offset": "11:00:00"
}, {
  "timeZone": "Pacific/Easter",
  "offset": "-5:00:00"
}, {
  "timeZone": "Pacific/Efate",
  "offset": "11:00:00"
}, {
  "timeZone": "Pacific/Enderbury",
  "offset": "13:00:00"
}, {
  "timeZone": "Pacific/Fakaofo",
  "offset": "13:00:00"
}, {
  "timeZone": "Pacific/Fiji",
  "offset": "12:00:00"
}, {
  "timeZone": "Pacific/Funafuti",
  "offset": "12:00:00"
}, {
  "timeZone": "Pacific/Galapagos",
  "offset": "-6:00:00"
}, {
  "timeZone": "Pacific/Guadalcanal",
  "offset": "11:00:00"
}, {
  "timeZone": "Pacific/Guam",
  "offset": "10:00:00"
}, {
  "timeZone": "Pacific/Honolulu",
  "offset": "-10:00:00"
}, {
  "timeZone": "Pacific/Johnston",
  "offset": "-10:00:00"
}, {
  "timeZone": "Pacific/Kiritimati",
  "offset": "14:00:00"
}, {
  "timeZone": "Pacific/Kosrae",
  "offset": "11:00:00"
}, {
  "timeZone": "Pacific/Kwajalein",
  "offset": "12:00:00"
}, {
  "timeZone": "Pacific/Majuro",
  "offset": "12:00:00"
}, {
  "timeZone": "Pacific/Midway",
  "offset": "-11:00:00"
}, {
  "timeZone": "Pacific/Nauru",
  "offset": "12:00:00"
}, {
  "timeZone": "Pacific/Niue",
  "offset": "-11:00:00"
}, {
  "timeZone": "Pacific/Norfolk",
  "offset": "11:00:00"
}, {
  "timeZone": "Pacific/Noumea",
  "offset": "11:00:00"
}, {
  "timeZone": "Pacific/Pago_Pago",
  "offset": "-11:00:00"
}, {
  "timeZone": "Pacific/Palau",
  "offset": "9:00:00"
}, {
  "timeZone": "Pacific/Ponape",
  "offset": "11:00:00"
}, {
  "timeZone": "Pacific/Port_Moresby",
  "offset": "10:00:00"
}, {
  "timeZone": "Pacific/Rarotonga",
  "offset": "-10:00:00"
}, {
  "timeZone": "Pacific/Saipan",
  "offset": "10:00:00"
}, {
  "timeZone": "Pacific/Tahiti",
  "offset": "-10:00:00"
}, {
  "timeZone": "Pacific/Tarawa",
  "offset": "12:00:00"
}, {
  "timeZone": "Pacific/Tongatapu",
  "offset": "13:00:00"
}, {
  "timeZone": "Pacific/Truk",
  "offset": "10:00:00"
}, {
  "timeZone": "Pacific/Wake",
  "offset": "12:00:00"
}, {
  "timeZone": "Pacific/Wallis",
  "offset": "12:00:00"
}, {
  "timeZone": "PST8PDT",
  "offset": "-8:00:00"
}]
const apiName = 'test.timezone.svc';
const tzCtrlName = 'TimeZoneCtrl';

if (!def) def = {};
if (!def.tz) def.tz = {};


def.tz.service = {
  name: apiName,
  dependencies: ['$http'],
  definition: function($http) {
    var success = function(response) {
      if (response && response.data) {
        return response.data;
      }
      return null;
    };
    var failure = function(error) {
      console.error(error);
    };
    var api = {
      getSupportedTimezones: function() {
        return $http.get('api/timezones.json')
          .then(success, failure);
      }
    };

    return api;
  }
};

def.tz.controller = {
  name: tzCtrlName,
  dependencies: [apiName],
  definition: function(timeZoneSvc) {
    var self = this;
    self.tzData = {
      all: [],
      forCountry:undefined,
      selected:undefined
    };
    
    Object.defineProperty(self, 'selectedTimeZone', {
      get: function () { 
        if (self.tzData.selected) {
          if (self.tzData.selected.region != 'Other') {
            return self.tzData.selected.city + ', ' + self.tzData.selected.region;
          } else {
            return self.tzData.selected.city;
          }
        }
        return 'Somewhere';
      }
    });

    timeZoneSvc.getSupportedTimezones().then(function(data) {
      if (data) {
        var mapped = {};
        for (var i = 0; i < data.length; i++) {
          var tz = data[i];
          var region = tz.timeZone.split('/');
          if (region.length == 1 || region[0] == 'Etc') {
            tz.region = 'Other';
          } else {
            tz.region = region[0];
          }
          if (region.length > 1) {
            tz.city = region[1].replace(/_/g, ' ');
          } else {
            tz.city = region[0].replace(/_/g, ' ');
          }
          if (!mapped[tz.region]) {
            mapped[tz.region] = {};
          }
          if (!mapped[tz.region][tz.city]) {
            mapped[tz.region][tz.city] = tz;
          }
        }
        self.tzData.all = mapped;
      }
    });
  }
};

app.service(def.tz.service.name, def.tz.service.dependencies.concat(def.tz.service.definition));
app.controller(def.tz.controller.name, def.tz.controller.dependencies.concat(def.tz.controller.definition));