├── .rspec ├── spec ├── dummy │ ├── public │ │ └── favicon.ico │ ├── app │ │ ├── assets │ │ │ └── javascripts │ │ │ │ └── application.js │ │ ├── controllers │ │ │ └── application_controller.rb │ │ └── views │ │ │ └── layouts │ │ │ └── application.html.erb │ ├── config │ │ ├── initializers │ │ │ └── teabag.rb │ │ ├── environment.rb │ │ ├── boot.rb │ │ ├── environments │ │ │ ├── development.rb │ │ │ └── test.rb │ │ ├── routes.rb │ │ └── application.rb │ ├── config.ru │ ├── Rakefile │ └── script │ │ └── rails ├── javascripts │ ├── spec_helper.coffee │ └── temporal_spec.coffee ├── engine │ └── temporal_spec.rb ├── controllers │ └── application_controller_spec.rb └── spec_helper.rb ├── .rvmrc.example ├── lib ├── temporal-rails.rb └── temporal │ ├── version.rb │ ├── rails.rb │ ├── engine.rb │ └── controller_additions.rb ├── .travis.yml ├── .gitignore ├── Gemfile ├── temporal-rails.gemspec ├── MIT.LICENSE ├── Rakefile ├── README.md ├── distro ├── temporal.min.js └── temporal.js └── vendor └── assets └── javascripts └── temporal.js.coffee /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /spec/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/javascripts/spec_helper.coffee: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rvmrc.example: -------------------------------------------------------------------------------- 1 | rvm use --create 1.9.3@temporal 2 | -------------------------------------------------------------------------------- /lib/temporal-rails.rb: -------------------------------------------------------------------------------- 1 | require 'temporal/rails' 2 | -------------------------------------------------------------------------------- /lib/temporal/version.rb: -------------------------------------------------------------------------------- 1 | module Temporal 2 | VERSION = '0.2.4' 3 | end 4 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | //= require temporal 2 | 3 | Temporal.detect('jejacks0n'); 4 | -------------------------------------------------------------------------------- /lib/temporal/rails.rb: -------------------------------------------------------------------------------- 1 | module Temporal 2 | require 'temporal/version' 3 | require 'temporal/engine' 4 | end 5 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/teabag.rb: -------------------------------------------------------------------------------- 1 | Teabag.setup do |config| 2 | 3 | config.root = Temporal::Engine.root 4 | 5 | end 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 1.9.3 3 | before_script: 4 | - 'sh -e /etc/init.d/xvfb start' 5 | -env: 6 | - DISPLAY=':99.0' 7 | script: bundle exec rake --trace 8 | -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Dummy::Application 5 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /spec/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | 3 | def welcome 4 | render text: "Welcome, your time zone is: #{Time.zone}", layout: 'application' 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Temporal: Regression Test 5 | <%= javascript_include_tag :application %> 6 | 7 | 8 | <%= yield %> 9 | 10 | 11 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | gemfile = File.expand_path('../../../../Gemfile', __FILE__) 2 | 3 | if File.exist?(gemfile) 4 | ENV['BUNDLE_GEMFILE'] = gemfile 5 | require 'bundler' 6 | Bundler.setup 7 | end 8 | 9 | $:.unshift File.expand_path('../../../../lib', __FILE__) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .bundle 3 | .rvmrc 4 | .powenv 5 | 6 | # Local config files 7 | Gemfile.lock 8 | .rvmrc 9 | 10 | # Build files 11 | distro/build 12 | pkg/ 13 | 14 | # Tempfiles 15 | tmp/ 16 | coverage 17 | capybara* 18 | 19 | # Dummy app 20 | spec/dummy/log 21 | spec/dummy/db/*.sqlite3 22 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | Dummy::Application.load_tasks 8 | -------------------------------------------------------------------------------- /spec/dummy/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /lib/temporal/engine.rb: -------------------------------------------------------------------------------- 1 | require 'temporal/controller_additions' 2 | 3 | module Temporal 4 | class Engine < ::Rails::Engine 5 | initializer "temporal.controller_additions" do 6 | ActiveSupport.on_load(:action_controller) do 7 | include Temporal::ControllerAdditions 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gemspec 4 | 5 | # used by the dummy application 6 | gem 'rails', '>= 3.2.8' 7 | 8 | group :development, :test do 9 | gem 'rspec-rails' 10 | gem 'uglifier' 11 | gem 'teabag' 12 | 13 | # required for travis-ci and linux environments 14 | gem "phantomjs-linux" if RUBY_PLATFORM =~ /linux/ 15 | end 16 | -------------------------------------------------------------------------------- /lib/temporal/controller_additions.rb: -------------------------------------------------------------------------------- 1 | module Temporal 2 | module ControllerAdditions 3 | def self.included(base) 4 | if Rails::VERSION::MAJOR >= 5 5 | base.send(:before_action, :set_time_zone) 6 | else 7 | base.send(:before_filter, :set_time_zone) 8 | end 9 | end 10 | 11 | def set_time_zone 12 | Time.zone = cookies[:timezone] ? ActiveSupport::TimeZone.new(cookies[:timezone]) : Rails.application.config.time_zone 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/engine/temporal_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Temporal do 4 | 5 | it "is a module" do 6 | Temporal.should be_a(Module) 7 | end 8 | 9 | it "has a version" do 10 | Temporal::VERSION.should be_a(String) 11 | end 12 | 13 | it "defines ControllerAdditions" do 14 | Temporal::ControllerAdditions.should be_a(Module) 15 | end 16 | 17 | it "includes ControllerAdditions in ActionController::Base" do 18 | ActionController::Base.new.methods.should include(:set_time_zone) 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /spec/controllers/application_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe ApplicationController, type: :controller do 4 | 5 | before do 6 | Rails.application.config.time_zone = 'Central Time (US & Canada)' 7 | end 8 | 9 | it "set the timezone to the configured default if the cookie isn't set" do 10 | get :welcome 11 | Time.zone.to_s.should =~ /Central Time/ 12 | response.code.should == "200" 13 | end 14 | 15 | it "set the timezone based on the cookie" do 16 | @request.cookies[:timezone] = 'America/Denver' 17 | get :welcome 18 | Time.zone.to_s.should =~ /Denver/ 19 | response.code.should == "200" 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /temporal-rails.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path("../lib", __FILE__) 3 | require 'temporal/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'temporal-rails' 7 | s.version = Temporal::VERSION 8 | s.authors = ['Jeremy Jackson'] 9 | s.email = ['jejacks0n@gmail.com'] 10 | s.homepage = 'http://github.com/jejacks0n/temporal' 11 | s.summary = 'Temporal: Javascript timezone detection for Rails' 12 | s.description = 'Javascript timezone detection that also sets Time.zone for Rails to use' 13 | s.licenses = ['MIT'] 14 | 15 | s.files = Dir["{lib,vendor}/**/*"] + ["MIT.LICENSE", "README.md"] 16 | s.test_files = Dir["{spec}/**/*"] 17 | 18 | # Runtime Dependencies 19 | s.add_dependency 'railties', ['>= 3.2.5'] 20 | s.add_dependency 'coffee-rails' 21 | 22 | end 23 | -------------------------------------------------------------------------------- /MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Temporal uses Javascript timezone detection and sets Time.zone for 2 | Rails to use. 3 | 4 | Documentation and other useful information can be found at 5 | https://github.com/jejacks0n/temporal 6 | 7 | Copyright (c) 2012 Jeremy Jackson 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining 10 | a copy of this software and associated documentation files (the 11 | "Software"), to deal in the Software without restriction, including 12 | without limitation the rights to use, copy, modify, merge, publish, 13 | distribute, sublicense, and/or sell copies of the Software, and to 14 | permit persons to whom the Software is furnished to do so, subject to 15 | the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | #config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | 25 | # Raise exception on mass assignment protection for Active Record models 26 | #config.active_record.mass_assignment_sanitizer = :strict 27 | 28 | # Log the query plan for queries taking more than this (works 29 | # with SQLite, MySQL, and PostgreSQL) 30 | #config.active_record.auto_explain_threshold_in_seconds = 0.5 31 | 32 | # Do not compress assets 33 | config.assets.compress = false 34 | 35 | # Expands the lines which load the assets 36 | config.assets.debug = true 37 | end 38 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | ENV['RAILS_ROOT'] = File.expand_path('../dummy', __FILE__) 3 | require File.expand_path('../dummy/config/environment', __FILE__) 4 | 5 | require 'rspec/rails' 6 | require 'rspec/autorun' 7 | 8 | # Requires supporting ruby files with custom matchers and macros, etc, 9 | # in spec/support/ and its subdirectories. 10 | Dir[Temporal::Engine.root.join('spec/support/**/*.rb')].each { |f| require f } 11 | 12 | RSpec.configure do |config| 13 | # ## Mock Framework 14 | # 15 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: 16 | # 17 | # config.mock_with :mocha 18 | # config.mock_with :flexmock 19 | # config.mock_with :rr 20 | 21 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 22 | #config.fixture_path = "#{::Temporal::Engine.root}/spec/fixtures" 23 | 24 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 25 | # examples within a transaction, remove the following line or assign false 26 | # instead of true. 27 | #config.use_transactional_fixtures = true 28 | 29 | # If true, the base class of anonymous controllers will be inferred 30 | # automatically. This will be the default behavior in future versions of 31 | # rspec-rails. 32 | config.infer_base_class_for_anonymous_controllers = false 33 | 34 | # Run specs in random order to surface order dependencies. If you find an 35 | # order dependency and want to debug it, you can fix the order by providing 36 | # the seed, which is printed after each run. 37 | # --seed 1234 38 | config.order = "random" 39 | end 40 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_assets = true 12 | config.static_cache_control = "public, max-age=3600" 13 | 14 | # Log error messages when you accidentally call methods on nil 15 | config.whiny_nils = true 16 | 17 | # Show full error reports and disable caching 18 | config.consider_all_requests_local = true 19 | config.action_controller.perform_caching = false 20 | 21 | # Raise exceptions instead of rendering exception templates 22 | config.action_dispatch.show_exceptions = false 23 | 24 | # Disable request forgery protection in test environment 25 | config.action_controller.allow_forgery_protection = false 26 | 27 | # Tell Action Mailer not to deliver emails to the real world. 28 | # The :test delivery method accumulates sent emails in the 29 | # ActionMailer::Base.deliveries array. 30 | #config.action_mailer.delivery_method = :test 31 | 32 | # Raise exception on mass assignment protection for Active Record models 33 | #config.active_record.mass_assignment_sanitizer = :strict 34 | 35 | # Print deprecation notices to the stderr 36 | config.active_support.deprecation = :stderr 37 | end 38 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | begin 3 | require 'bundler/setup' 4 | rescue LoadError 5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 6 | end 7 | 8 | # Dummy App 9 | # ----------------------------------------------------------------------------- 10 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__) 11 | load 'rails/tasks/engine.rake' 12 | Bundler::GemHelper.install_tasks 13 | 14 | # RSpec 15 | # ----------------------------------------------------------------------------- 16 | task 'db:test:prepare' => 'app:db:test:prepare' 17 | load 'rspec/rails/tasks/rspec.rake' 18 | 19 | namespace :spec do 20 | 21 | [:engine].each do |sub| 22 | desc "Run the code examples in spec/#{sub}" 23 | RSpec::Core::RakeTask.new(sub => 'db:test:prepare') do |t| 24 | t.pattern = "./spec/#{sub}/**/*_spec.rb" 25 | end 26 | end 27 | 28 | end 29 | 30 | # Teabag 31 | # ----------------------------------------------------------------------------- 32 | desc "Run javascript specs" 33 | task :teabag => 'app:teabag' 34 | 35 | # Temporal 36 | # ----------------------------------------------------------------------------- 37 | namespace :temporal do 38 | desc "Builds Temporal into the distribution ready bundle" 39 | task :build => "build:javascripts" 40 | 41 | namespace :build do 42 | 43 | desc "Compile coffeescripts into javacripts" 44 | task :javascripts => :environment do 45 | env = Rails.application.assets 46 | 47 | %w(temporal.js).each do |path| 48 | asset = env.find_asset(path) 49 | asset.write_to(Temporal::Engine.root.join("distro/#{path}")) 50 | File.open(Temporal::Engine.root.join("distro/#{path.gsub(/\.js/, '.min.js')}"), 'w') do |file| 51 | file.write(Uglifier.compile(asset.source)) 52 | end 53 | end 54 | end 55 | 56 | end 57 | 58 | end 59 | 60 | # Default 61 | # ----------------------------------------------------------------------------- 62 | Rake::Task['default'].prerequisites.clear 63 | Rake::Task['default'].clear 64 | 65 | task :default => [:spec, :teabag] 66 | -------------------------------------------------------------------------------- /spec/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.routes.draw do 2 | 3 | root to: 'application#welcome' 4 | 5 | # The priority is based upon order of creation: 6 | # first created -> highest priority. 7 | 8 | # Sample of regular route: 9 | # match 'products/:id' => 'catalog#view' 10 | # Keep in mind you can assign values other than :controller and :action 11 | 12 | # Sample of named route: 13 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 14 | # This route can be invoked with purchase_url(:id => product.id) 15 | 16 | # Sample resource route (maps HTTP verbs to controller actions automatically): 17 | # resources :products 18 | 19 | # Sample resource route with options: 20 | # resources :products do 21 | # member do 22 | # get 'short' 23 | # post 'toggle' 24 | # end 25 | # 26 | # collection do 27 | # get 'sold' 28 | # end 29 | # end 30 | 31 | # Sample resource route with sub-resources: 32 | # resources :products do 33 | # resources :comments, :sales 34 | # resource :seller 35 | # end 36 | 37 | # Sample resource route with more complex sub-resources 38 | # resources :products do 39 | # resources :comments 40 | # resources :sales do 41 | # get 'recent', :on => :collection 42 | # end 43 | # end 44 | 45 | # Sample resource route within a namespace: 46 | # namespace :admin do 47 | # # Directs /admin/products/* to Admin::ProductsController 48 | # # (app/controllers/admin/products_controller.rb) 49 | # resources :products 50 | # end 51 | 52 | # You can have the root of your site routed with "root" 53 | # just remember to delete public/index.html. 54 | # root :to => 'welcome#index' 55 | 56 | # See how all your routes lay out with "rake routes" 57 | 58 | # This is a legacy wild controller route that's not recommended for RESTful applications. 59 | # Note: This route will make all actions in every controller accessible via GET requests. 60 | # match ':controller(/:action(/:id))(.:format)' 61 | 62 | end 63 | -------------------------------------------------------------------------------- /spec/javascripts/temporal_spec.coffee: -------------------------------------------------------------------------------- 1 | #= require temporal 2 | 3 | describe "Temporal", -> 4 | 5 | beforeEach -> 6 | document.cookie = 'timezone=' 7 | document.cookie = 'timezone_offset=' 8 | @timezoneStub = {name: 'foo', offset: 0} # 0 here ensure the jsonp request isn't made 9 | 10 | describe "signature", -> 11 | 12 | it "has a detect and reference method", -> 13 | expect(Object.keys(Temporal).length).toBe 2 14 | expect(Object.keys(Temporal)).toEqual ['detect', 'reference'] 15 | expect(typeof(Temporal.detect)).toBe 'function' 16 | expect(typeof(Temporal.reference)).toBe 'function' 17 | 18 | 19 | describe ".detect", -> 20 | 21 | beforeEach -> 22 | window.Temporal = Temporal.reference() if Temporal.reference 23 | @spy = spyOn(Temporal.prototype, 'detectLocally').andReturn @timezoneStub 24 | @callback = -> 25 | 26 | it "instantiates an instance and passes arguments", -> 27 | instance = Temporal.detect('username', @callback) 28 | expect(@spy).toHaveBeenCalled() 29 | expect(instance.username).toBe 'username' 30 | expect(instance.callback).toBe @callback 31 | 32 | 33 | describe "constructor", -> 34 | 35 | it "calls #detectLocally", -> 36 | spy = spyOn(Temporal.prototype, 'detectLocally').andReturn @timezoneStub 37 | new Temporal() 38 | expect(spy.callCount).toBe 1 39 | 40 | it "calls #geoLocate if there's a username for the GeoName API", -> 41 | spyOn(Temporal.prototype, 'detectLocally').andReturn name: 'foo', offset: 1 42 | spy = spyOn(Temporal.prototype, 'geoLocate') 43 | new Temporal('username') 44 | if navigator.geolocation 45 | expect(spy.callCount).toBe 1 46 | 47 | it "doesn't call #geoLocate if there isn't a username", -> 48 | spyOn(Temporal.prototype, 'detectLocally').andReturn name: 'foo', offset: 1 49 | spy = spyOn(Temporal.prototype, 'geoLocate') 50 | new Temporal() 51 | expect(spy.callCount).toBe 0 52 | 53 | it "calls #set", -> 54 | spyOn(Temporal.prototype, 'detectLocally').andReturn @timezoneStub 55 | spy = spyOn(Temporal.prototype, 'set') 56 | new Temporal() 57 | expect(spy.callCount).toBe 1 58 | expect(spy).toHaveBeenCalledWith name: 'foo', offset: 0 59 | 60 | 61 | describe "#detectLocally", -> 62 | 63 | beforeEach -> 64 | spyOn(Temporal.prototype, 'detect') 65 | @temporal = new Temporal() 66 | 67 | it "returns a quickly determined time zone", -> 68 | spyOn(Temporal.prototype, 'januaryOffset').andReturn -420 69 | spyOn(Temporal.prototype, 'juneOffset').andReturn -360 70 | timezone = @temporal.detectLocally() 71 | expect(timezone).toEqual name: 'America/Denver', offset: -7 72 | 73 | it "handles other locations than denver", -> 74 | spyOn(Temporal.prototype, 'januaryOffset').andReturn 120 75 | spyOn(Temporal.prototype, 'juneOffset').andReturn 120 76 | timezone = @temporal.detectLocally() 77 | expect(timezone).toEqual name: 'Africa/Johannesburg', offset: 2 78 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'action_controller/railtie' 4 | require 'sprockets/railtie' 5 | 6 | if defined?(Bundler) 7 | # If you precompile assets before deploying to production, use this line 8 | Bundler.require(*Rails.groups(:assets => %w(development test))) 9 | # If you want your assets lazily compiled in production, use this line 10 | # Bundler.require(:default, :assets, Rails.env) 11 | end 12 | 13 | module Dummy 14 | class Application < Rails::Application 15 | # Settings in config/environments/* take precedence over those specified here. 16 | # Application configuration should go into files in config/initializers 17 | # -- all .rb files in that directory are automatically loaded. 18 | 19 | # Custom directories with classes and modules you want to be autoloadable. 20 | # config.autoload_paths += %W(#{config.root}/extras) 21 | 22 | # Only load the plugins named here, in the order given (default is alphabetical). 23 | # :all can be used as a placeholder for all plugins not explicitly named. 24 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 25 | 26 | # Activate observers that should always be running. 27 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 28 | 29 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 30 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 31 | # config.time_zone = 'Central Time (US & Canada)' 32 | 33 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 34 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 35 | # config.i18n.default_locale = :de 36 | 37 | # Configure the default encoding used in templates for Ruby 1.9. 38 | config.encoding = "utf-8" 39 | 40 | # Configure sensitive parameters which will be filtered from the log file. 41 | config.filter_parameters += [:password] 42 | 43 | # Enable escaping HTML in JSON. 44 | config.active_support.escape_html_entities_in_json = true 45 | 46 | # Use SQL instead of Active Record's schema dumper when creating the database. 47 | # This is necessary if your schema can't be completely dumped by the schema dumper, 48 | # like if you have constraints or database-specific column types 49 | #config.active_record.schema_format = :sql 50 | 51 | # Enforce whitelist mode for mass assignment. 52 | # This will create an empty whitelist of attributes available for mass-assignment for all models 53 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible 54 | # parameters by using an attr_accessible or attr_protected declaration. 55 | #config.active_record.whitelist_attributes = true 56 | 57 | Dummy::Application.config.session_store :cookie_store, :key => '_dummy_session' 58 | Dummy::Application.config.secret_token = '990bac5b6d4f068883259503539cad4e9ec00f137986fcbc718a1e0b645af7a3eb0340468f8fae09958a6281190396d65789b6316af61e47a8cbda3c8c071a0e' 59 | 60 | # Enable the asset pipeline 61 | config.assets.enabled = true 62 | 63 | # Version of your assets, change this if you want to expire all your assets 64 | config.assets.version = '1.0' 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Temporal 2 | 3 | [![Build Status](https://secure.travis-ci.org/jejacks0n/temporal.png)](http://travis-ci.org/jejacks0n/temporal) 4 | [![License](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT) 5 | 6 | Temporal is a small (~7.5k) Javascript library that uses a collection of techniques to determine a clients time zone. 7 | Once a time zone has been determined, a cookie is set which can be used on the server. Now you can display local times 8 | throughout the rest of the response cycle. 9 | 10 | The first method is based on checking various times on different dates to resolve to a short list of time zones. The 11 | short list is by no means extensive, and is meant to provide the time zone offset -- not specifically the location of 12 | the client. The data is comprised of the most useful aspects of the [Time Zone Database](http://www.iana.org/time-zones) which keeps the data loaded 13 | on the client small. 14 | 15 | The second method is to use the HTML5 Geolocation API combined with the [GeoNames API](http://www.geonames.org/export/web-services.html). Latitude and longitude is 16 | provided by the client (when approved), and the name/offset of the time zone is fetched using JSONP via the GeoNames 17 | API. This method provides much more accurate location information -- though I haven't been able to get the two to 18 | methods to disagree about the actual offset, so if you don't need this level of location accuracy you can get by 19 | without it. 20 | 21 | 22 | ## The Story 23 | 24 | If you've ever done time zone specific logic in your applications you know it's a bit of a beast. First, you'll need 25 | to ask each user where they're located to know what time zone you should use when displaying any times converted to 26 | their local time. Wouldn't it be nice not to have to ask users for their time zone? That's one step removed from your 27 | sign up / configuration process, and maybe you don't even have a sign up process, in which case it's even harder to 28 | display local times. 29 | 30 | I haven't found a really good solution for detecting a users time zone, and I'm not happy asking a user for it, so I 31 | wrote Temporal to determine it for me before ever having to ask. I'm putting it out there in case anyone else finds it 32 | useful as a tool, or a learning opportunity. 33 | 34 | 35 | ## Installation 36 | 37 | ### Rails 38 | 39 | gem 'temporal-rails' 40 | 41 | Then require temporal in your application.js: 42 | 43 | //= require temporal 44 | 45 | ### Just the Javascript? 46 | 47 | Download [temporal.js](https://raw.github.com/jejacks0n/temporal/master/distro/temporal.js) or [temporal.min.js](https://raw.github.com/jejacks0n/temporal/master/distro/temporal.min.js) 48 | and add them to your project. The same API applies, but you would need to consume the cookie that's set on the back 49 | end -- using data similar to [TZInfo](http://tzinfo.rubyforge.org/). 50 | 51 | 52 | ## Usage 53 | 54 | There's really not much to it. Call `Temporal.detect()` to trigger the detection. This method sets a cookie on the 55 | client, and the next request to the server will have that cookie, letting us use it to set the Rails Time.zone (which 56 | is done for you in the Gem). It's important to note that the exact location of the user isn't guaranteed, so it should 57 | be treated as the time zone, and not the location (eg. don't display it unless you have a way to change it). 58 | 59 | The `Temporal.detect` method takes two arguments: 60 | 61 | - your GeoNames username -- can be created [here](http://www.geonames.org/login) and also turn on the web service [here](http://www.geonames.org/manageaccount) (it's near the bottom) 62 | - a callback function 63 | 64 | If you don't provide the first argument the HTML5 geolocation and GeoNames APIs will not be used. 65 | 66 | The callback is called whenever the time zone is set or changed -- it can be called twice if you're using the GeoName 67 | API because Temporal first does the quick detection and sets the cookie based on that, and then calls through to the 68 | GeoNames API. Since the GoeNames API uses JSONP, the reponse may take a moment to update the cookie further and with 69 | more accuracy. 70 | 71 | More reading about time zone handling in rails: [What time is it? Or, handling timezones in Rails](http://databasically.com/2010/10/22/what-time-is-it-or-handling-timezones-in-rails/) 72 | 73 | Temporal is as efficient as possible when it comes to refreshing the users time zone and using the GeoNames API. It 74 | does this by caching the determined time zone for a month, and does a quick check on each page load to see if it looks 75 | like the users time zone has changed (if they're traveling, or have moved, etc.). If it looks like the time zone may 76 | have changed it will trigger the GoeNames API hit again for clarification. 77 | 78 | 79 | ## License 80 | 81 | Licensed under the [MIT License](http://opensource.org/licenses/mit-license.php) 82 | 83 | Copyright 2012 [Jeremy Jackson](https://github.com/jejacks0n) 84 | 85 | 86 | ## Enjoy =) 87 | -------------------------------------------------------------------------------- /distro/temporal.min.js: -------------------------------------------------------------------------------- 1 | (function(){var e,t,n,r,i,s,o,u,a=function(e,t){return function(){return e.apply(t,arguments)}},f={}.hasOwnProperty;o=function(){function t(e,t){this.username=e!=null?e:null,this.callback=t!=null?t:null,this.parseGeoResponse=a(this.parseGeoResponse,this),this.geoSuccess=a(this.geoSuccess,this),this.detect()}var e;return e="geoSuccessCallback"+parseInt(Math.random()*1e4),t.detect=function(e,n){return e==null&&(e=null),n==null&&(n=null),new t(e,n)},t.prototype.detect=function(){var e;return e=this.detectLocally(),this.username&&navigator.geolocation&&e.offset!==this.get().offset&&this.geoLocate(),this.set(e)},t.prototype.detectLocally=function(){var e,t,s;return e=this.januaryOffset(),t=this.juneOffset(),s={offset:e,dst:0,hemisphere:i},e-t<0?s={offset:e,dst:1,hemisphere:n}:e-t>0&&(s={offset:t,dst:1,hemisphere:r}),new u(""+[s.offset,s.dst].join(",")+(s.hemisphere===r?",s":""))},t.prototype.geoLocate=function(){return navigator.geolocation.getCurrentPosition(this.geoSuccess,function(){})},t.prototype.geoSuccess=function(t){var n;return window[e]=this.parseGeoResponse,n=document.createElement("script"),n.setAttribute("src","http://api.geonames.org/timezoneJSON?lat="+t.coords.latitude+"&lng="+t.coords.longitude+"&username="+this.username+"&callback="+e),document.getElementsByTagName("head")[0].appendChild(n)},t.prototype.parseGeoResponse=function(t){delete window[e];if(t.timezoneId)return this.set(new u({name:t.timezoneId,offset:t.rawOffset}))},t.prototype.set=function(e){var t;return this.timezone=e,window.timezone=this.timezone,t=new Date,t.setMonth(t.getMonth()+1),document.cookie="timezone="+this.timezone.name+"; expires="+t.toGMTString(),document.cookie="timezone_offset="+this.timezone.offset+"; expires="+t.toGMTString(),typeof this.callback=="function"?this.callback(this.timezone):void 0},t.prototype.get=function(){return{name:this.getCookie("timezone"),offset:parseFloat(this.getCookie("timezone_offset"))||0}},t.prototype.getCookie=function(e){var t;return t=document.cookie.match(new RegExp("(?:^|;)\\s?"+e+"=(.*?)(?:;|$)","i")),t&&unescape(t[1])},t.prototype.januaryOffset=function(){return this.dateOffset(new Date(2011,0,1,0,0,0,0))},t.prototype.juneOffset=function(){return this.dateOffset(new Date(2011,5,1,0,0,0,0))},t.prototype.dateOffset=function(e){return-e.getTimezoneOffset()},t}.call(this),u=function(){function i(e){var t,n,i;if(typeof e=="string"){i=s[e];for(t in i){if(!f.call(i,t))continue;n=i[t],this[t]=n}r()}else for(t in e){if(!f.call(e,t))continue;n=e[t],this[t]=n}}var n,r;return n=function(e){return(e.getMonth()>5?this.juneOffset():this.januaryOffset())-this.dateOffset(e)!==0},r=function(){var r,i,s;r=e[this.name];if(typeof r=="undefined")return;for(i in r){s=r[i];if(n(t[s])){this.name=s;return}}},i}(),this.Temporal={detect:o.detect,reference:function(){return o}},r="SOUTH",n="NORTH",i="N/A",e={"America/Denver":["America/Denver","America/Mazatlan"],"America/Chicago":["America/Chicago","America/Mexico_City"],"America/Asuncion":["Atlantic/Stanley","America/Asuncion","America/Santiago","America/Campo_Grande"],"America/Montevideo":["America/Montevideo","America/Sao_Paolo"],"Asia/Beirut":["Asia/Gaza","Asia/Beirut","Europe/Minsk","Europe/Istanbul","Asia/Damascus","Asia/Jerusalem","Africa/Cairo"],"Asia/Yerevan":["Asia/Yerevan","Asia/Baku"],"Pacific/Auckland":["Pacific/Auckland","Pacific/Fiji"],"America/Los_Angeles":["America/Los_Angeles","America/Santa_Isabel"],"America/New_York":["America/Havana","America/New_York"],"America/Halifax":["America/Goose_Bay","America/Halifax"],"America/Godthab":["America/Miquelon","America/Godthab"]},t={"America/Denver":new Date(2011,2,13,3,0,0,0),"America/Mazatlan":new Date(2011,3,3,3,0,0,0),"America/Chicago":new Date(2011,2,13,3,0,0,0),"America/Mexico_City":new Date(2011,3,3,3,0,0,0),"Atlantic/Stanley":new Date(2011,8,4,7,0,0,0),"America/Asuncion":new Date(2011,9,2,3,0,0,0),"America/Santiago":new Date(2011,9,9,3,0,0,0),"America/Campo_Grande":new Date(2011,9,16,5,0,0,0),"America/Montevideo":new Date(2011,9,2,3,0,0,0),"America/Sao_Paolo":new Date(2011,9,16,5,0,0,0),"America/Los_Angeles":new Date(2011,2,13,8,0,0,0),"America/Santa_Isabel":new Date(2011,3,5,8,0,0,0),"America/Havana":new Date(2011,2,13,2,0,0,0),"America/New_York":new Date(2011,2,13,7,0,0,0),"Asia/Gaza":new Date(2011,2,26,23,0,0,0),"Asia/Beirut":new Date(2011,2,27,1,0,0,0),"Europe/Minsk":new Date(2011,2,27,3,0,0,0),"Europe/Istanbul":new Date(2011,2,27,7,0,0,0),"Asia/Damascus":new Date(2011,3,1,2,0,0,0),"Asia/Jerusalem":new Date(2011,3,1,6,0,0,0),"Africa/Cairo":new Date(2011,3,29,4,0,0,0),"Asia/Yerevan":new Date(2011,2,27,4,0,0,0),"Asia/Baku":new Date(2011,2,27,8,0,0,0),"Pacific/Auckland":new Date(2011,8,26,7,0,0,0),"Pacific/Fiji":new Date(2010,11,29,23,0,0,0),"America/Halifax":new Date(2011,2,13,6,0,0,0),"America/Goose_Bay":new Date(2011,2,13,2,1,0,0),"America/Miquelon":new Date(2011,2,13,5,0,0,0),"America/Godthab":new Date(2011,2,27,1,0,0,0)},s={"-720,0":{offset:-12,name:"Etc/GMT+12"},"-660,0":{offset:-11,name:"Pacific/Pago_Pago"},"-600,1":{offset:-11,name:"America/Adak"},"-660,1,s":{offset:-11,name:"Pacific/Apia"},"-600,0":{offset:-10,name:"Pacific/Honolulu"},"-570,0":{offset:-10.5,name:"Pacific/Marquesas"},"-540,0":{offset:-9,name:"Pacific/Gambier"},"-540,1":{offset:-9,name:"America/Anchorage"},"-480,1":{offset:-8,name:"America/Los_Angeles"},"-480,0":{offset:-8,name:"Pacific/Pitcairn"},"-420,0":{offset:-7,name:"America/Phoenix"},"-420,1":{offset:-7,name:"America/Denver"},"-360,0":{offset:-6,name:"America/Guatemala"},"-360,1":{offset:-6,name:"America/Chicago"},"-360,1,s":{offset:-6,name:"Pacific/Easter"},"-300,0":{offset:-5,name:"America/Bogota"},"-300,1":{offset:-5,name:"America/New_York"},"-270,0":{offset:-4.5,name:"America/Caracas"},"-240,1":{offset:-4,name:"America/Halifax"},"-240,0":{offset:-4,name:"America/Santo_Domingo"},"-240,1,s":{offset:-4,name:"America/Asuncion"},"-210,1":{offset:-3.5,name:"America/St_Johns"},"-180,1":{offset:-3,name:"America/Godthab"},"-180,0":{offset:-3,name:"America/Argentina/Buenos_Aires"},"-180,1,s":{offset:-3,name:"America/Montevideo"},"-120,0":{offset:-2,name:"America/Noronha"},"-120,1":{offset:-2,name:"Etc/GMT+2"},"-60,1":{offset:-1,name:"Atlantic/Azores"},"-60,0":{offset:-1,name:"Atlantic/Cape_Verde"},"0,0":{offset:0,name:"Africa/Casablanca"},"0,1":{offset:0,name:"Europe/London"},"60,1":{offset:1,name:"Europe/Berlin"},"60,0":{offset:1,name:"Africa/Lagos"},"60,1,s":{offset:1,name:"Africa/Windhoek"},"120,1":{offset:2,name:"Asia/Beirut"},"120,0":{offset:2,name:"Africa/Johannesburg"},"180,1":{offset:3,name:"Europe/Moscow"},"180,0":{offset:3,name:"Asia/Baghdad"},"210,1":{offset:3.5,name:"Asia/Tehran"},"240,0":{offset:4,name:"Asia/Dubai"},"240,1":{offset:4,name:"Asia/Yerevan"},"270,0":{offset:4.5,name:"Asia/Kabul"},"300,1":{offset:5,name:"Asia/Yekaterinburg"},"300,0":{offset:5,name:"Asia/Karachi"},"330,0":{offset:5,name:"Asia/Kolkata"},"345,0":{offset:5.75,name:"Asia/Kathmandu"},"360,0":{offset:6,name:"Asia/Dhaka"},"360,1":{offset:6,name:"Asia/Omsk"},"390,0":{offset:6,name:"Asia/Rangoon"},"420,1":{offset:7,name:"Asia/Krasnoyarsk"},"420,0":{offset:7,name:"Asia/Jakarta"},"480,0":{offset:8,name:"Asia/Shanghai"},"480,1":{offset:8,name:"Asia/Irkutsk"},"525,0":{offset:8.75,name:"Australia/Eucla"},"525,1,s":{offset:8.75,name:"Australia/Eucla"},"540,1":{offset:9,name:"Asia/Yakutsk"},"540,0":{offset:9,name:"Asia/Tokyo"},"570,0":{offset:9.5,name:"Australia/Darwin"},"570,1,s":{offset:9.5,name:"Australia/Adelaide"},"600,0":{offset:10,name:"Australia/Brisbane"},"600,1":{offset:10,name:"Asia/Vladivostok"},"600,1,s":{offset:10,name:"Australia/Sydney"},"630,1,s":{offset:10.5,name:"Australia/Lord_Howe"},"660,1":{offset:11,name:"Asia/Kamchatka"},"660,0":{offset:11,name:"Pacific/Noumea"},"690,0":{offset:11.5,name:"Pacific/Norfolk"},"720,1,s":{offset:12,name:"Pacific/Auckland"},"720,0":{offset:12,name:"Pacific/Tarawa"},"765,1,s":{offset:12.75,name:"Pacific/Chatham"},"780,0":{offset:13,name:"Pacific/Tongatapu"},"840,0":{offset:14,name:"Pacific/Kiritimati"}}}).call(this); 2 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/temporal.js.coffee: -------------------------------------------------------------------------------- 1 | # Temporal -- use detect() to detect, and reference() if you need a reference 2 | # to the class so you can use it. 3 | # ----------------------------------------------------------------------------- 4 | class Temporal 5 | 6 | jsonpCallback = "geoSuccessCallback#{parseInt(Math.random() * 10000)}" 7 | 8 | @detect: (username = null, callback = null) => 9 | new Temporal(username, callback) 10 | 11 | constructor: (@username = null, @callback = null) -> 12 | @detect() 13 | 14 | detect: -> 15 | timezone = @detectLocally() 16 | @geoLocate() if @username and navigator.geolocation and timezone.offset != @get().offset 17 | @set(timezone) 18 | 19 | detectLocally: -> 20 | januaryOffset = @januaryOffset() 21 | juneOffset = @juneOffset() 22 | key = {offset: januaryOffset, dst: 0, hemisphere: HEMISPHERE_UNKNOWN} 23 | if januaryOffset - juneOffset < 0 24 | key = {offset: januaryOffset, dst: 1, hemisphere: HEMISPHERE_NORTH} 25 | else if januaryOffset - juneOffset > 0 26 | key = {offset: juneOffset, dst: 1, hemisphere: HEMISPHERE_SOUTH} 27 | new TimeZone("#{([key.offset, key.dst].join(','))}#{if key.hemisphere is HEMISPHERE_SOUTH then ',s' else ''}") 28 | 29 | geoLocate: -> 30 | navigator.geolocation.getCurrentPosition(@geoSuccess, ->) 31 | 32 | geoSuccess: (position) => 33 | window[jsonpCallback] = @parseGeoResponse 34 | script = document.createElement('script') 35 | script.setAttribute('src', "http://api.geonames.org/timezoneJSON?lat=#{position.coords.latitude}&lng=#{position.coords.longitude}&username=#{@username}&callback=#{jsonpCallback}") 36 | document.getElementsByTagName('head')[0].appendChild(script) 37 | 38 | parseGeoResponse: (response) => 39 | delete(window[jsonpCallback]) 40 | @set(new TimeZone(name: response.timezoneId, offset: response.rawOffset)) if response.timezoneId 41 | 42 | set: (@timezone) -> 43 | window.timezone = @timezone 44 | expiration = new Date() 45 | expiration.setMonth(expiration.getMonth() + 1) 46 | document.cookie = "timezone=#{@timezone.name}; expires=#{expiration.toGMTString()}" 47 | document.cookie = "timezone_offset=#{@timezone.offset}; expires=#{expiration.toGMTString()}" 48 | @callback?(@timezone) 49 | 50 | get: -> 51 | name: @getCookie('timezone') 52 | offset: parseFloat(@getCookie('timezone_offset')) || 0 53 | 54 | getCookie: (name) -> 55 | match = document.cookie.match(new RegExp("(?:^|;)\\s?#{name}=(.*?)(?:;|$)", 'i')) 56 | match && unescape(match[1]) 57 | 58 | januaryOffset: -> 59 | @dateOffset(new Date(2011, 0, 1, 0, 0, 0, 0)) 60 | 61 | juneOffset: -> 62 | @dateOffset(new Date(2011, 5, 1, 0, 0, 0, 0)) 63 | 64 | dateOffset: (date) -> 65 | -date.getTimezoneOffset() 66 | 67 | 68 | # Timezone -- contains offset and timezone name 69 | # ----------------------------------------------------------------------------- 70 | class TimeZone 71 | 72 | dateIsDst = (date) -> 73 | (((if date.getMonth() > 5 then @juneOffset() else @januaryOffset())) - @dateOffset(date)) isnt 0 74 | 75 | resolveAmbiguity = -> 76 | ambiguous = AMBIGIOUS_ZONES[@name] 77 | return if typeof(ambiguous) is 'undefined' 78 | for key, value of ambiguous 79 | if dateIsDst(DST_START_DATES[value]) 80 | @name = value 81 | return 82 | 83 | constructor: (keyOrProperties) -> 84 | if typeof(keyOrProperties) == 'string' 85 | zone = TIMEZONES[keyOrProperties] 86 | @[property] = value for own property, value of zone 87 | resolveAmbiguity() 88 | else 89 | @[property] = value for own property, value of keyOrProperties 90 | 91 | 92 | # Expose Temporal to the global scope 93 | # ----------------------------------------------------------------------------- 94 | @Temporal = {detect: Temporal.detect, reference: -> Temporal} 95 | 96 | 97 | # Data 98 | # ----------------------------------------------------------------------------- 99 | HEMISPHERE_SOUTH = 'SOUTH' 100 | HEMISPHERE_NORTH = 'NORTH' 101 | HEMISPHERE_UNKNOWN = 'N/A' 102 | AMBIGIOUS_ZONES = 103 | 'America/Denver': ['America/Denver', 'America/Mazatlan'] 104 | 'America/Chicago': ['America/Chicago', 'America/Mexico_City'] 105 | 'America/Asuncion': ['Atlantic/Stanley', 'America/Asuncion', 'America/Santiago', 'America/Campo_Grande'] 106 | 'America/Montevideo': ['America/Montevideo', 'America/Sao_Paolo'] 107 | 'Asia/Beirut': ['Asia/Gaza', 'Asia/Beirut', 'Europe/Minsk', 'Europe/Istanbul', 'Asia/Damascus', 'Asia/Jerusalem', 'Africa/Cairo'] 108 | 'Asia/Yerevan': ['Asia/Yerevan', 'Asia/Baku'] 109 | 'Pacific/Auckland': ['Pacific/Auckland', 'Pacific/Fiji'] 110 | 'America/Los_Angeles': ['America/Los_Angeles', 'America/Santa_Isabel'] 111 | 'America/New_York': ['America/Havana', 'America/New_York'] 112 | 'America/Halifax': ['America/Goose_Bay', 'America/Halifax'] 113 | 'America/Godthab': ['America/Miquelon', 'America/Godthab'] 114 | DST_START_DATES = 115 | 'America/Denver': new Date(2011, 2, 13, 3, 0, 0, 0) 116 | 'America/Mazatlan': new Date(2011, 3, 3, 3, 0, 0, 0) 117 | 'America/Chicago': new Date(2011, 2, 13, 3, 0, 0, 0) 118 | 'America/Mexico_City': new Date(2011, 3, 3, 3, 0, 0, 0) 119 | 'Atlantic/Stanley': new Date(2011, 8, 4, 7, 0, 0, 0) 120 | 'America/Asuncion': new Date(2011, 9, 2, 3, 0, 0, 0) 121 | 'America/Santiago': new Date(2011, 9, 9, 3, 0, 0, 0) 122 | 'America/Campo_Grande': new Date(2011, 9, 16, 5, 0, 0, 0) 123 | 'America/Montevideo': new Date(2011, 9, 2, 3, 0, 0, 0) 124 | 'America/Sao_Paolo': new Date(2011, 9, 16, 5, 0, 0, 0) 125 | 'America/Los_Angeles': new Date(2011, 2, 13, 8, 0, 0, 0) 126 | 'America/Santa_Isabel': new Date(2011, 3, 5, 8, 0, 0, 0) 127 | 'America/Havana': new Date(2011, 2, 13, 2, 0, 0, 0) 128 | 'America/New_York': new Date(2011, 2, 13, 7, 0, 0, 0) 129 | 'Asia/Gaza': new Date(2011, 2, 26, 23, 0, 0, 0) 130 | 'Asia/Beirut': new Date(2011, 2, 27, 1, 0, 0, 0) 131 | 'Europe/Minsk': new Date(2011, 2, 27, 3, 0, 0, 0) 132 | 'Europe/Istanbul': new Date(2011, 2, 27, 7, 0, 0, 0) 133 | 'Asia/Damascus': new Date(2011, 3, 1, 2, 0, 0, 0) 134 | 'Asia/Jerusalem': new Date(2011, 3, 1, 6, 0, 0, 0) 135 | 'Africa/Cairo': new Date(2011, 3, 29, 4, 0, 0, 0) 136 | 'Asia/Yerevan': new Date(2011, 2, 27, 4, 0, 0, 0) 137 | 'Asia/Baku': new Date(2011, 2, 27, 8, 0, 0, 0) 138 | 'Pacific/Auckland': new Date(2011, 8, 26, 7, 0, 0, 0) 139 | 'Pacific/Fiji': new Date(2010, 11, 29, 23, 0, 0, 0) 140 | 'America/Halifax': new Date(2011, 2, 13, 6, 0, 0, 0) 141 | 'America/Goose_Bay': new Date(2011, 2, 13, 2, 1, 0, 0) 142 | 'America/Miquelon': new Date(2011, 2, 13, 5, 0, 0, 0) 143 | 'America/Godthab': new Date(2011, 2, 27, 1, 0, 0, 0) 144 | TIMEZONES = 145 | '-720,0': {offset: -12, name: 'Etc/GMT+12'} 146 | '-660,0': {offset: -11, name: 'Pacific/Pago_Pago'} 147 | '-600,1': {offset: -11, name: 'America/Adak'} 148 | '-660,1,s': {offset: -11, name: 'Pacific/Apia'} 149 | '-600,0': {offset: -10, name: 'Pacific/Honolulu'} 150 | '-570,0': {offset: -10.5, name: 'Pacific/Marquesas'} 151 | '-540,0': {offset: -9, name: 'Pacific/Gambier'} 152 | '-540,1': {offset: -9, name: 'America/Anchorage'} 153 | '-480,1': {offset: -8, name: 'America/Los_Angeles'} 154 | '-480,0': {offset: -8, name: 'Pacific/Pitcairn'} 155 | '-420,0': {offset: -7, name: 'America/Phoenix'} 156 | '-420,1': {offset: -7, name: 'America/Denver'} 157 | '-360,0': {offset: -6, name: 'America/Guatemala'} 158 | '-360,1': {offset: -6, name: 'America/Chicago'} 159 | '-360,1,s': {offset: -6, name: 'Pacific/Easter'} 160 | '-300,0': {offset: -5, name: 'America/Bogota'} 161 | '-300,1': {offset: -5, name: 'America/New_York'} 162 | '-270,0': {offset: -4.5, name: 'America/Caracas'} 163 | '-240,1': {offset: -4, name: 'America/Halifax'} 164 | '-240,0': {offset: -4, name: 'America/Santo_Domingo'} 165 | '-240,1,s': {offset: -4, name: 'America/Asuncion'} 166 | '-210,1': {offset: -3.5, name: 'America/St_Johns'} 167 | '-180,1': {offset: -3, name: 'America/Godthab'} 168 | '-180,0': {offset: -3, name: 'America/Argentina/Buenos_Aires,'} 169 | '-180,1,s': {offset: -3, name: 'America/Montevideo'} 170 | '-120,0': {offset: -2, name: 'America/Noronha'} 171 | '-120,1': {offset: -2, name: 'Etc/GMT+2'} 172 | '-60,1': {offset: -1, name: 'Atlantic/Azores'} 173 | '-60,0': {offset: -1, name: 'Atlantic/Cape_Verde'} 174 | '0,0': {offset: 0, name: 'Africa/Casablanca'} 175 | '0,1': {offset: 0, name: 'Europe/London'} 176 | '60,1': {offset: 1, name: 'Europe/Berlin'} 177 | '60,0': {offset: 1, name: 'Africa/Lagos'} 178 | '60,1,s': {offset: 1, name: 'Africa/Windhoek'} 179 | '120,1': {offset: 2, name: 'Asia/Beirut'} 180 | '120,0': {offset: 2, name: 'Africa/Johannesburg'} 181 | '180,1': {offset: 3, name: 'Europe/Moscow'} 182 | '180,0': {offset: 3, name: 'Asia/Baghdad'} 183 | '210,1': {offset: 3.5, name: 'Asia/Tehran'} 184 | '240,0': {offset: 4, name: 'Asia/Dubai'} 185 | '240,1': {offset: 4, name: 'Asia/Yerevan'} 186 | '270,0': {offset: 4.5, name: 'Asia/Kabul'} 187 | '300,1': {offset: 5, name: 'Asia/Yekaterinburg'} 188 | '300,0': {offset: 5, name: 'Asia/Karachi'} 189 | '330,0': {offset: 5, name: 'Asia/Kolkata'} 190 | '345,0': {offset: 5.75, name: 'Asia/Kathmandu'} 191 | '360,0': {offset: 6, name: 'Asia/Dhaka'} 192 | '360,1': {offset: 6, name: 'Asia/Omsk'} 193 | '390,0': {offset: 6, name: 'Asia/Rangoon'} 194 | '420,1': {offset: 7, name: 'Asia/Krasnoyarsk'} 195 | '420,0': {offset: 7, name: 'Asia/Jakarta'} 196 | '480,0': {offset: 8, name: 'Asia/Shanghai'} 197 | '480,1': {offset: 8, name: 'Asia/Irkutsk'} 198 | '525,0': {offset: 8.75, name: 'Australia/Eucla'} 199 | '525,1,s': {offset: 8.75, name: 'Australia/Eucla'} 200 | '540,1': {offset: 9, name: 'Asia/Yakutsk'} 201 | '540,0': {offset: 9, name: 'Asia/Tokyo'} 202 | '570,0': {offset: 9.5, name: 'Australia/Darwin'} 203 | '570,1,s': {offset: 9.5, name: 'Australia/Adelaide'} 204 | '600,0': {offset: 10, name: 'Australia/Brisbane'} 205 | '600,1': {offset: 10, name: 'Asia/Vladivostok'} 206 | '600,1,s': {offset: 10, name: 'Australia/Sydney'} 207 | '630,1,s': {offset: 10.5, name: 'Australia/Lord_Howe'} 208 | '660,1': {offset: 11, name: 'Asia/Kamchatka'} 209 | '660,0': {offset: 11, name: 'Pacific/Noumea'} 210 | '690,0': {offset: 11.5, name: 'Pacific/Norfolk'} 211 | '720,1,s': {offset: 12, name: 'Pacific/Auckland'} 212 | '720,0': {offset: 12, name: 'Pacific/Tarawa'} 213 | '765,1,s': {offset: 12.75, name: 'Pacific/Chatham'} 214 | '780,0': {offset: 13, name: 'Pacific/Tongatapu'} 215 | '840,0': {offset: 14, name: 'Pacific/Kiritimati'} 216 | -------------------------------------------------------------------------------- /distro/temporal.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var AMBIGIOUS_ZONES, DST_START_DATES, HEMISPHERE_NORTH, HEMISPHERE_SOUTH, HEMISPHERE_UNKNOWN, TIMEZONES, Temporal, TimeZone, 3 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 4 | __hasProp = {}.hasOwnProperty; 5 | 6 | Temporal = (function() { 7 | var jsonpCallback; 8 | 9 | jsonpCallback = "geoSuccessCallback" + (parseInt(Math.random() * 10000)); 10 | 11 | Temporal.detect = function(username, callback) { 12 | if (username == null) { 13 | username = null; 14 | } 15 | if (callback == null) { 16 | callback = null; 17 | } 18 | return new Temporal(username, callback); 19 | }; 20 | 21 | function Temporal(username, callback) { 22 | this.username = username != null ? username : null; 23 | this.callback = callback != null ? callback : null; 24 | this.parseGeoResponse = __bind(this.parseGeoResponse, this); 25 | 26 | this.geoSuccess = __bind(this.geoSuccess, this); 27 | 28 | this.detect(); 29 | } 30 | 31 | Temporal.prototype.detect = function() { 32 | var timezone; 33 | timezone = this.detectLocally(); 34 | if (this.username && navigator.geolocation && timezone.offset !== this.get().offset) { 35 | this.geoLocate(); 36 | } 37 | return this.set(timezone); 38 | }; 39 | 40 | Temporal.prototype.detectLocally = function() { 41 | var januaryOffset, juneOffset, key; 42 | januaryOffset = this.januaryOffset(); 43 | juneOffset = this.juneOffset(); 44 | key = { 45 | offset: januaryOffset, 46 | dst: 0, 47 | hemisphere: HEMISPHERE_UNKNOWN 48 | }; 49 | if (januaryOffset - juneOffset < 0) { 50 | key = { 51 | offset: januaryOffset, 52 | dst: 1, 53 | hemisphere: HEMISPHERE_NORTH 54 | }; 55 | } else if (januaryOffset - juneOffset > 0) { 56 | key = { 57 | offset: juneOffset, 58 | dst: 1, 59 | hemisphere: HEMISPHERE_SOUTH 60 | }; 61 | } 62 | return new TimeZone("" + ([key.offset, key.dst].join(',')) + (key.hemisphere === HEMISPHERE_SOUTH ? ',s' : '')); 63 | }; 64 | 65 | Temporal.prototype.geoLocate = function() { 66 | return navigator.geolocation.getCurrentPosition(this.geoSuccess, function() {}); 67 | }; 68 | 69 | Temporal.prototype.geoSuccess = function(position) { 70 | var script; 71 | window[jsonpCallback] = this.parseGeoResponse; 72 | script = document.createElement('script'); 73 | script.setAttribute('src', "http://api.geonames.org/timezoneJSON?lat=" + position.coords.latitude + "&lng=" + position.coords.longitude + "&username=" + this.username + "&callback=" + jsonpCallback); 74 | return document.getElementsByTagName('head')[0].appendChild(script); 75 | }; 76 | 77 | Temporal.prototype.parseGeoResponse = function(response) { 78 | delete window[jsonpCallback]; 79 | if (response.timezoneId) { 80 | return this.set(new TimeZone({ 81 | name: response.timezoneId, 82 | offset: response.rawOffset 83 | })); 84 | } 85 | }; 86 | 87 | Temporal.prototype.set = function(timezone) { 88 | var expiration; 89 | this.timezone = timezone; 90 | window.timezone = this.timezone; 91 | expiration = new Date(); 92 | expiration.setMonth(expiration.getMonth() + 1); 93 | document.cookie = "timezone=" + this.timezone.name + "; expires=" + (expiration.toGMTString()); 94 | document.cookie = "timezone_offset=" + this.timezone.offset + "; expires=" + (expiration.toGMTString()); 95 | return typeof this.callback === "function" ? this.callback(this.timezone) : void 0; 96 | }; 97 | 98 | Temporal.prototype.get = function() { 99 | return { 100 | name: this.getCookie('timezone'), 101 | offset: parseFloat(this.getCookie('timezone_offset')) || 0 102 | }; 103 | }; 104 | 105 | Temporal.prototype.getCookie = function(name) { 106 | var match; 107 | match = document.cookie.match(new RegExp("(?:^|;)\\s?" + name + "=(.*?)(?:;|$)", 'i')); 108 | return match && unescape(match[1]); 109 | }; 110 | 111 | Temporal.prototype.januaryOffset = function() { 112 | return this.dateOffset(new Date(2011, 0, 1, 0, 0, 0, 0)); 113 | }; 114 | 115 | Temporal.prototype.juneOffset = function() { 116 | return this.dateOffset(new Date(2011, 5, 1, 0, 0, 0, 0)); 117 | }; 118 | 119 | Temporal.prototype.dateOffset = function(date) { 120 | return -date.getTimezoneOffset(); 121 | }; 122 | 123 | return Temporal; 124 | 125 | }).call(this); 126 | 127 | TimeZone = (function() { 128 | var dateIsDst, resolveAmbiguity; 129 | 130 | dateIsDst = function(date) { 131 | return ((date.getMonth() > 5 ? this.juneOffset() : this.januaryOffset()) - this.dateOffset(date)) !== 0; 132 | }; 133 | 134 | resolveAmbiguity = function() { 135 | var ambiguous, key, value; 136 | ambiguous = AMBIGIOUS_ZONES[this.name]; 137 | if (typeof ambiguous === 'undefined') { 138 | return; 139 | } 140 | for (key in ambiguous) { 141 | value = ambiguous[key]; 142 | if (dateIsDst(DST_START_DATES[value])) { 143 | this.name = value; 144 | return; 145 | } 146 | } 147 | }; 148 | 149 | function TimeZone(keyOrProperties) { 150 | var property, value, zone; 151 | if (typeof keyOrProperties === 'string') { 152 | zone = TIMEZONES[keyOrProperties]; 153 | for (property in zone) { 154 | if (!__hasProp.call(zone, property)) continue; 155 | value = zone[property]; 156 | this[property] = value; 157 | } 158 | resolveAmbiguity(); 159 | } else { 160 | for (property in keyOrProperties) { 161 | if (!__hasProp.call(keyOrProperties, property)) continue; 162 | value = keyOrProperties[property]; 163 | this[property] = value; 164 | } 165 | } 166 | } 167 | 168 | return TimeZone; 169 | 170 | })(); 171 | 172 | this.Temporal = { 173 | detect: Temporal.detect, 174 | reference: function() { 175 | return Temporal; 176 | } 177 | }; 178 | 179 | HEMISPHERE_SOUTH = 'SOUTH'; 180 | 181 | HEMISPHERE_NORTH = 'NORTH'; 182 | 183 | HEMISPHERE_UNKNOWN = 'N/A'; 184 | 185 | AMBIGIOUS_ZONES = { 186 | 'America/Denver': ['America/Denver', 'America/Mazatlan'], 187 | 'America/Chicago': ['America/Chicago', 'America/Mexico_City'], 188 | 'America/Asuncion': ['Atlantic/Stanley', 'America/Asuncion', 'America/Santiago', 'America/Campo_Grande'], 189 | 'America/Montevideo': ['America/Montevideo', 'America/Sao_Paolo'], 190 | 'Asia/Beirut': ['Asia/Gaza', 'Asia/Beirut', 'Europe/Minsk', 'Europe/Istanbul', 'Asia/Damascus', 'Asia/Jerusalem', 'Africa/Cairo'], 191 | 'Asia/Yerevan': ['Asia/Yerevan', 'Asia/Baku'], 192 | 'Pacific/Auckland': ['Pacific/Auckland', 'Pacific/Fiji'], 193 | 'America/Los_Angeles': ['America/Los_Angeles', 'America/Santa_Isabel'], 194 | 'America/New_York': ['America/Havana', 'America/New_York'], 195 | 'America/Halifax': ['America/Goose_Bay', 'America/Halifax'], 196 | 'America/Godthab': ['America/Miquelon', 'America/Godthab'] 197 | }; 198 | 199 | DST_START_DATES = { 200 | 'America/Denver': new Date(2011, 2, 13, 3, 0, 0, 0), 201 | 'America/Mazatlan': new Date(2011, 3, 3, 3, 0, 0, 0), 202 | 'America/Chicago': new Date(2011, 2, 13, 3, 0, 0, 0), 203 | 'America/Mexico_City': new Date(2011, 3, 3, 3, 0, 0, 0), 204 | 'Atlantic/Stanley': new Date(2011, 8, 4, 7, 0, 0, 0), 205 | 'America/Asuncion': new Date(2011, 9, 2, 3, 0, 0, 0), 206 | 'America/Santiago': new Date(2011, 9, 9, 3, 0, 0, 0), 207 | 'America/Campo_Grande': new Date(2011, 9, 16, 5, 0, 0, 0), 208 | 'America/Montevideo': new Date(2011, 9, 2, 3, 0, 0, 0), 209 | 'America/Sao_Paolo': new Date(2011, 9, 16, 5, 0, 0, 0), 210 | 'America/Los_Angeles': new Date(2011, 2, 13, 8, 0, 0, 0), 211 | 'America/Santa_Isabel': new Date(2011, 3, 5, 8, 0, 0, 0), 212 | 'America/Havana': new Date(2011, 2, 13, 2, 0, 0, 0), 213 | 'America/New_York': new Date(2011, 2, 13, 7, 0, 0, 0), 214 | 'Asia/Gaza': new Date(2011, 2, 26, 23, 0, 0, 0), 215 | 'Asia/Beirut': new Date(2011, 2, 27, 1, 0, 0, 0), 216 | 'Europe/Minsk': new Date(2011, 2, 27, 3, 0, 0, 0), 217 | 'Europe/Istanbul': new Date(2011, 2, 27, 7, 0, 0, 0), 218 | 'Asia/Damascus': new Date(2011, 3, 1, 2, 0, 0, 0), 219 | 'Asia/Jerusalem': new Date(2011, 3, 1, 6, 0, 0, 0), 220 | 'Africa/Cairo': new Date(2011, 3, 29, 4, 0, 0, 0), 221 | 'Asia/Yerevan': new Date(2011, 2, 27, 4, 0, 0, 0), 222 | 'Asia/Baku': new Date(2011, 2, 27, 8, 0, 0, 0), 223 | 'Pacific/Auckland': new Date(2011, 8, 26, 7, 0, 0, 0), 224 | 'Pacific/Fiji': new Date(2010, 11, 29, 23, 0, 0, 0), 225 | 'America/Halifax': new Date(2011, 2, 13, 6, 0, 0, 0), 226 | 'America/Goose_Bay': new Date(2011, 2, 13, 2, 1, 0, 0), 227 | 'America/Miquelon': new Date(2011, 2, 13, 5, 0, 0, 0), 228 | 'America/Godthab': new Date(2011, 2, 27, 1, 0, 0, 0) 229 | }; 230 | 231 | TIMEZONES = { 232 | '-720,0': { 233 | offset: -12, 234 | name: 'Etc/GMT+12' 235 | }, 236 | '-660,0': { 237 | offset: -11, 238 | name: 'Pacific/Pago_Pago' 239 | }, 240 | '-600,1': { 241 | offset: -11, 242 | name: 'America/Adak' 243 | }, 244 | '-660,1,s': { 245 | offset: -11, 246 | name: 'Pacific/Apia' 247 | }, 248 | '-600,0': { 249 | offset: -10, 250 | name: 'Pacific/Honolulu' 251 | }, 252 | '-570,0': { 253 | offset: -10.5, 254 | name: 'Pacific/Marquesas' 255 | }, 256 | '-540,0': { 257 | offset: -9, 258 | name: 'Pacific/Gambier' 259 | }, 260 | '-540,1': { 261 | offset: -9, 262 | name: 'America/Anchorage' 263 | }, 264 | '-480,1': { 265 | offset: -8, 266 | name: 'America/Los_Angeles' 267 | }, 268 | '-480,0': { 269 | offset: -8, 270 | name: 'Pacific/Pitcairn' 271 | }, 272 | '-420,0': { 273 | offset: -7, 274 | name: 'America/Phoenix' 275 | }, 276 | '-420,1': { 277 | offset: -7, 278 | name: 'America/Denver' 279 | }, 280 | '-360,0': { 281 | offset: -6, 282 | name: 'America/Guatemala' 283 | }, 284 | '-360,1': { 285 | offset: -6, 286 | name: 'America/Chicago' 287 | }, 288 | '-360,1,s': { 289 | offset: -6, 290 | name: 'Pacific/Easter' 291 | }, 292 | '-300,0': { 293 | offset: -5, 294 | name: 'America/Bogota' 295 | }, 296 | '-300,1': { 297 | offset: -5, 298 | name: 'America/New_York' 299 | }, 300 | '-270,0': { 301 | offset: -4.5, 302 | name: 'America/Caracas' 303 | }, 304 | '-240,1': { 305 | offset: -4, 306 | name: 'America/Halifax' 307 | }, 308 | '-240,0': { 309 | offset: -4, 310 | name: 'America/Santo_Domingo' 311 | }, 312 | '-240,1,s': { 313 | offset: -4, 314 | name: 'America/Asuncion' 315 | }, 316 | '-210,1': { 317 | offset: -3.5, 318 | name: 'America/St_Johns' 319 | }, 320 | '-180,1': { 321 | offset: -3, 322 | name: 'America/Godthab' 323 | }, 324 | '-180,0': { 325 | offset: -3, 326 | name: 'America/Argentina/Buenos_Aires' 327 | }, 328 | '-180,1,s': { 329 | offset: -3, 330 | name: 'America/Montevideo' 331 | }, 332 | '-120,0': { 333 | offset: -2, 334 | name: 'America/Noronha' 335 | }, 336 | '-120,1': { 337 | offset: -2, 338 | name: 'Etc/GMT+2' 339 | }, 340 | '-60,1': { 341 | offset: -1, 342 | name: 'Atlantic/Azores' 343 | }, 344 | '-60,0': { 345 | offset: -1, 346 | name: 'Atlantic/Cape_Verde' 347 | }, 348 | '0,0': { 349 | offset: 0, 350 | name: 'Africa/Casablanca' 351 | }, 352 | '0,1': { 353 | offset: 0, 354 | name: 'Europe/London' 355 | }, 356 | '60,1': { 357 | offset: 1, 358 | name: 'Europe/Berlin' 359 | }, 360 | '60,0': { 361 | offset: 1, 362 | name: 'Africa/Lagos' 363 | }, 364 | '60,1,s': { 365 | offset: 1, 366 | name: 'Africa/Windhoek' 367 | }, 368 | '120,1': { 369 | offset: 2, 370 | name: 'Asia/Beirut' 371 | }, 372 | '120,0': { 373 | offset: 2, 374 | name: 'Africa/Johannesburg' 375 | }, 376 | '180,1': { 377 | offset: 3, 378 | name: 'Europe/Moscow' 379 | }, 380 | '180,0': { 381 | offset: 3, 382 | name: 'Asia/Baghdad' 383 | }, 384 | '210,1': { 385 | offset: 3.5, 386 | name: 'Asia/Tehran' 387 | }, 388 | '240,0': { 389 | offset: 4, 390 | name: 'Asia/Dubai' 391 | }, 392 | '240,1': { 393 | offset: 4, 394 | name: 'Asia/Yerevan' 395 | }, 396 | '270,0': { 397 | offset: 4.5, 398 | name: 'Asia/Kabul' 399 | }, 400 | '300,1': { 401 | offset: 5, 402 | name: 'Asia/Yekaterinburg' 403 | }, 404 | '300,0': { 405 | offset: 5, 406 | name: 'Asia/Karachi' 407 | }, 408 | '330,0': { 409 | offset: 5, 410 | name: 'Asia/Kolkata' 411 | }, 412 | '345,0': { 413 | offset: 5.75, 414 | name: 'Asia/Kathmandu' 415 | }, 416 | '360,0': { 417 | offset: 6, 418 | name: 'Asia/Dhaka' 419 | }, 420 | '360,1': { 421 | offset: 6, 422 | name: 'Asia/Omsk' 423 | }, 424 | '390,0': { 425 | offset: 6, 426 | name: 'Asia/Rangoon' 427 | }, 428 | '420,1': { 429 | offset: 7, 430 | name: 'Asia/Krasnoyarsk' 431 | }, 432 | '420,0': { 433 | offset: 7, 434 | name: 'Asia/Jakarta' 435 | }, 436 | '480,0': { 437 | offset: 8, 438 | name: 'Asia/Shanghai' 439 | }, 440 | '480,1': { 441 | offset: 8, 442 | name: 'Asia/Irkutsk' 443 | }, 444 | '525,0': { 445 | offset: 8.75, 446 | name: 'Australia/Eucla' 447 | }, 448 | '525,1,s': { 449 | offset: 8.75, 450 | name: 'Australia/Eucla' 451 | }, 452 | '540,1': { 453 | offset: 9, 454 | name: 'Asia/Yakutsk' 455 | }, 456 | '540,0': { 457 | offset: 9, 458 | name: 'Asia/Tokyo' 459 | }, 460 | '570,0': { 461 | offset: 9.5, 462 | name: 'Australia/Darwin' 463 | }, 464 | '570,1,s': { 465 | offset: 9.5, 466 | name: 'Australia/Adelaide' 467 | }, 468 | '600,0': { 469 | offset: 10, 470 | name: 'Australia/Brisbane' 471 | }, 472 | '600,1': { 473 | offset: 10, 474 | name: 'Asia/Vladivostok' 475 | }, 476 | '600,1,s': { 477 | offset: 10, 478 | name: 'Australia/Sydney' 479 | }, 480 | '630,1,s': { 481 | offset: 10.5, 482 | name: 'Australia/Lord_Howe' 483 | }, 484 | '660,1': { 485 | offset: 11, 486 | name: 'Asia/Kamchatka' 487 | }, 488 | '660,0': { 489 | offset: 11, 490 | name: 'Pacific/Noumea' 491 | }, 492 | '690,0': { 493 | offset: 11.5, 494 | name: 'Pacific/Norfolk' 495 | }, 496 | '720,1,s': { 497 | offset: 12, 498 | name: 'Pacific/Auckland' 499 | }, 500 | '720,0': { 501 | offset: 12, 502 | name: 'Pacific/Tarawa' 503 | }, 504 | '765,1,s': { 505 | offset: 12.75, 506 | name: 'Pacific/Chatham' 507 | }, 508 | '780,0': { 509 | offset: 13, 510 | name: 'Pacific/Tongatapu' 511 | }, 512 | '840,0': { 513 | offset: 14, 514 | name: 'Pacific/Kiritimati' 515 | } 516 | }; 517 | 518 | }).call(this); 519 | --------------------------------------------------------------------------------