harvested 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +23 -0
- data/HISTORY +16 -0
- data/MIT-LICENSE +20 -0
- data/README.md +67 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/examples/basics.rb +35 -0
- data/examples/clear_account.rb +28 -0
- data/examples/task_assignments.rb +27 -0
- data/examples/user_assignments.rb +24 -0
- data/features/account.feature +7 -0
- data/features/client_contacts.feature +23 -0
- data/features/clients.feature +29 -0
- data/features/errors.feature +25 -0
- data/features/expense_categories.feature +21 -0
- data/features/expenses.feature +55 -0
- data/features/hardy_client.feature +40 -0
- data/features/projects.feature +39 -0
- data/features/reporting.feature +72 -0
- data/features/step_definitions/account_steps.rb +7 -0
- data/features/step_definitions/assignment_steps.rb +100 -0
- data/features/step_definitions/contact_steps.rb +11 -0
- data/features/step_definitions/debug_steps.rb +3 -0
- data/features/step_definitions/error_steps.rb +113 -0
- data/features/step_definitions/expenses_steps.rb +46 -0
- data/features/step_definitions/harvest_steps.rb +8 -0
- data/features/step_definitions/model_steps.rb +90 -0
- data/features/step_definitions/people_steps.rb +4 -0
- data/features/step_definitions/report_steps.rb +91 -0
- data/features/step_definitions/time_entry_steps.rb +40 -0
- data/features/support/env.rb +37 -0
- data/features/support/error_helpers.rb +18 -0
- data/features/support/fixtures/empty_clients.xml +2 -0
- data/features/support/fixtures/over_limit.xml +8 -0
- data/features/support/fixtures/receipt.png +0 -0
- data/features/support/fixtures/under_limit.xml +8 -0
- data/features/support/harvest_credentials.example.yml +4 -0
- data/features/support/harvest_helpers.rb +11 -0
- data/features/support/inflections.rb +9 -0
- data/features/task_assignment.feature +69 -0
- data/features/tasks.feature +25 -0
- data/features/time_tracking.feature +29 -0
- data/features/user_assignments.feature +33 -0
- data/features/users.feature +55 -0
- data/lib/harvest/api/account.rb +10 -0
- data/lib/harvest/api/base.rb +42 -0
- data/lib/harvest/api/clients.rb +10 -0
- data/lib/harvest/api/contacts.rb +19 -0
- data/lib/harvest/api/expense_categories.rb +9 -0
- data/lib/harvest/api/expenses.rb +28 -0
- data/lib/harvest/api/projects.rb +39 -0
- data/lib/harvest/api/reports.rb +39 -0
- data/lib/harvest/api/task_assignments.rb +32 -0
- data/lib/harvest/api/tasks.rb +9 -0
- data/lib/harvest/api/time.rb +32 -0
- data/lib/harvest/api/user_assignments.rb +32 -0
- data/lib/harvest/api/users.rb +15 -0
- data/lib/harvest/base.rb +59 -0
- data/lib/harvest/base_model.rb +34 -0
- data/lib/harvest/behavior/activatable.rb +21 -0
- data/lib/harvest/behavior/crud.rb +31 -0
- data/lib/harvest/client.rb +18 -0
- data/lib/harvest/contact.rb +16 -0
- data/lib/harvest/credentials.rb +21 -0
- data/lib/harvest/errors.rb +23 -0
- data/lib/harvest/expense.rb +19 -0
- data/lib/harvest/expense_category.rb +18 -0
- data/lib/harvest/hardy_client.rb +80 -0
- data/lib/harvest/project.rb +22 -0
- data/lib/harvest/rate_limit_status.rb +16 -0
- data/lib/harvest/task.rb +20 -0
- data/lib/harvest/task_assignment.rb +34 -0
- data/lib/harvest/time_entry.rb +41 -0
- data/lib/harvest/timezones.rb +150 -0
- data/lib/harvest/user.rb +40 -0
- data/lib/harvest/user_assignment.rb +34 -0
- data/lib/harvested.rb +31 -0
- data/spec/harvest/base_spec.rb +9 -0
- data/spec/harvest/credentials_spec.rb +22 -0
- data/spec/harvest/expense_spec.rb +15 -0
- data/spec/harvest/task_assignment_spec.rb +10 -0
- data/spec/harvest/time_entry_spec.rb +22 -0
- data/spec/harvest/user_assignment_spec.rb +10 -0
- data/spec/harvest/user_spec.rb +32 -0
- data/spec/spec.default.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +243 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
module Harvest
|
2
|
+
class BaseModel
|
3
|
+
def initialize(attributes = {})
|
4
|
+
self.attributes = attributes
|
5
|
+
end
|
6
|
+
|
7
|
+
def attributes=(attributes)
|
8
|
+
attributes.each {|k,v| send("#{k}=", v)}
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
id == other.id
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_i
|
16
|
+
id
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_xml
|
20
|
+
builder = Builder::XmlMarkup.new
|
21
|
+
builder.tag!(self.class.tag_name) do |c|
|
22
|
+
self.class.elements.each do |f|
|
23
|
+
c.tag!(f.tag, send(f.name)) if send(f.name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def api_path(path = nil)
|
30
|
+
@path ||= path
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Harvest
|
2
|
+
module Behavior
|
3
|
+
module Activatable
|
4
|
+
def deactivate(model)
|
5
|
+
if model.active?
|
6
|
+
request(:post, credentials, "#{api_model.api_path}/#{model.to_i}/toggle")
|
7
|
+
model.active = false
|
8
|
+
end
|
9
|
+
model
|
10
|
+
end
|
11
|
+
|
12
|
+
def activate(model)
|
13
|
+
if !model.active?
|
14
|
+
request(:post, credentials, "#{api_model.api_path}/#{model.to_i}/toggle")
|
15
|
+
model.active = true
|
16
|
+
end
|
17
|
+
model
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Harvest
|
2
|
+
module Behavior
|
3
|
+
module Crud
|
4
|
+
def all
|
5
|
+
response = request(:get, credentials, api_model.api_path)
|
6
|
+
api_model.parse(response.body)
|
7
|
+
end
|
8
|
+
|
9
|
+
def find(id)
|
10
|
+
response = request(:get, credentials, "#{api_model.api_path}/#{id}")
|
11
|
+
api_model.parse(response.body, :single => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def create(model)
|
15
|
+
response = request(:post, credentials, "#{api_model.api_path}", :body => model.to_xml)
|
16
|
+
id = response.headers["location"].first.match(/\/.*\/(\d+)/)[1]
|
17
|
+
find(id)
|
18
|
+
end
|
19
|
+
|
20
|
+
def update(model)
|
21
|
+
request(:put, credentials, "#{api_model.api_path}/#{model.to_i}", :body => model.to_xml)
|
22
|
+
find(model.id)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(model)
|
26
|
+
request(:delete, credentials, "#{api_model.api_path}/#{model.to_i}")
|
27
|
+
model.id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Harvest
|
2
|
+
class Client < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
api_path '/clients'
|
6
|
+
|
7
|
+
element :id, Integer
|
8
|
+
element :active, Boolean
|
9
|
+
element :name, String
|
10
|
+
element :details, String
|
11
|
+
element :currency, String
|
12
|
+
element :currency_symbol, String, :tag => "currency-symbol"
|
13
|
+
element :cache_version, Integer, :tag => "cache-version"
|
14
|
+
element :updated_at, Time, :tag => "updated-at"
|
15
|
+
|
16
|
+
alias_method :active?, :active
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Harvest
|
2
|
+
class Contact < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
api_path '/contacts'
|
6
|
+
|
7
|
+
element :id, Integer
|
8
|
+
element :client_id, Integer, :tag => "client-id"
|
9
|
+
element :email, String
|
10
|
+
element :first_name, String, :tag => "first-name"
|
11
|
+
element :last_name, String, :tag => "last-name"
|
12
|
+
element :phone_office, String, :tag => "phone-office"
|
13
|
+
element :phone_mobile, String, :tag => "phone-mobile"
|
14
|
+
element :fax, String
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Harvest
|
2
|
+
class Credentials
|
3
|
+
attr_accessor :subdomain, :username, :password, :ssl
|
4
|
+
|
5
|
+
def initialize(subdomain, username, password, ssl = true)
|
6
|
+
@subdomain, @username, @password, @ssl = subdomain, username, password, ssl
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
!subdomain.nil? && !username.nil? && !password.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def basic_auth
|
14
|
+
Base64.encode64("#{username}:#{password}").delete("\r\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
def host
|
18
|
+
"#{ssl ? "https" : "http"}://#{subdomain}.harvestapp.com"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Harvest
|
2
|
+
class InvalidCredentials < StandardError; end
|
3
|
+
|
4
|
+
class HTTPError < StandardError
|
5
|
+
attr_reader :response
|
6
|
+
def initialize(response)
|
7
|
+
@response = response
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
hint = response.headers["hint"].nil? ? nil : response.headers["hint"].first
|
13
|
+
"#{self.class.to_s} : #{response.code}#{" - #{hint}" if hint}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class RateLimited < HTTPError; end
|
18
|
+
class NotFound < HTTPError; end
|
19
|
+
class Unavailable < HTTPError; end
|
20
|
+
class InformHarvest < HTTPError; end
|
21
|
+
class BadRequest < HTTPError; end
|
22
|
+
class ServerError < HTTPError; end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Harvest
|
2
|
+
class Expense < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
api_path '/expenses'
|
6
|
+
|
7
|
+
element :id, Integer
|
8
|
+
element :notes, String
|
9
|
+
element :units, Integer
|
10
|
+
element :total_cost, Float, :tag => 'total-cost'
|
11
|
+
element :project_id, Integer, :tag => 'project-id'
|
12
|
+
element :expense_category_id, Integer, :tag => 'expense-category-id'
|
13
|
+
element :spent_at, Time, :tag => 'spent-at'
|
14
|
+
|
15
|
+
def spent_at=(date)
|
16
|
+
@spent_at = (String === date ? Time.parse(date) : date)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Harvest
|
2
|
+
class ExpenseCategory < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
tag 'expense-category'
|
6
|
+
api_path '/expense_categories'
|
7
|
+
|
8
|
+
element :id, Integer
|
9
|
+
element :name, String
|
10
|
+
element :unit_name, String, :tag => 'unit-name'
|
11
|
+
element :unit_price, Float, :tag => 'unit-price'
|
12
|
+
element :deactivated, Boolean
|
13
|
+
|
14
|
+
def active?
|
15
|
+
!deactivated
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Harvest
|
2
|
+
class HardyClient < Delegator
|
3
|
+
def initialize(client, max_retries)
|
4
|
+
super(client)
|
5
|
+
@_sd_obj = @client = client
|
6
|
+
@max_retries = max_retries
|
7
|
+
(@client.public_methods - Object.public_instance_methods).each do |name|
|
8
|
+
instance_eval <<-END
|
9
|
+
def #{name}(*args)
|
10
|
+
wrap_collection do
|
11
|
+
@client.send('#{name}', *args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
END
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def __getobj__; @_sd_obj; end
|
19
|
+
def __setobj__(obj); @_sd_obj = obj; end
|
20
|
+
|
21
|
+
def wrap_collection
|
22
|
+
collection = yield
|
23
|
+
HardyCollection.new(collection, self, @max_retries)
|
24
|
+
end
|
25
|
+
|
26
|
+
class HardyCollection < Delegator
|
27
|
+
def initialize(collection, client, max_retries)
|
28
|
+
super(collection)
|
29
|
+
@_sd_obj = @collection = collection
|
30
|
+
@client = client
|
31
|
+
@max_retries = max_retries
|
32
|
+
(@collection.public_methods - Object.public_instance_methods).each do |name|
|
33
|
+
instance_eval <<-END
|
34
|
+
def #{name}(*args)
|
35
|
+
retry_rate_limits do
|
36
|
+
@collection.send('#{name}', *args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
END
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def __getobj__; @_sd_obj; end
|
44
|
+
def __setobj__(obj); @_sd_obj = obj; end
|
45
|
+
|
46
|
+
def retry_rate_limits
|
47
|
+
retries = 0
|
48
|
+
|
49
|
+
retry_func = lambda do |e|
|
50
|
+
if retries < @max_retries
|
51
|
+
retries += 1
|
52
|
+
true
|
53
|
+
else
|
54
|
+
raise e
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
yield
|
60
|
+
rescue Harvest::RateLimited => e
|
61
|
+
seconds = if e.response.headers["retry-after"]
|
62
|
+
e.response.headers["retry-after"].first.to_i
|
63
|
+
else
|
64
|
+
16
|
65
|
+
end
|
66
|
+
sleep(seconds)
|
67
|
+
retry
|
68
|
+
rescue Harvest::Unavailable, Harvest::InformHarvest => e
|
69
|
+
would_retry = retry_func.call(e)
|
70
|
+
sleep(16) if @client.account.rate_limit_status.over_limit?
|
71
|
+
retry if would_retry
|
72
|
+
rescue Net::HTTPError, Net::HTTPFatalError => e
|
73
|
+
retry if retry_func.call(e)
|
74
|
+
rescue SystemCallError => e
|
75
|
+
retry if e.is_a?(Errno::ECONNRESET) && retry_func.call(e)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Harvest
|
2
|
+
class Project < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
api_path '/projects'
|
6
|
+
|
7
|
+
element :id, Integer
|
8
|
+
element :client_id, Integer, :tag => 'client-id'
|
9
|
+
element :name, String
|
10
|
+
element :code, String
|
11
|
+
element :notes, String
|
12
|
+
element :fees, String
|
13
|
+
element :active, Boolean
|
14
|
+
element :billable, Boolean
|
15
|
+
element :budget, String
|
16
|
+
element :budget_by, Float, :tag => 'budget-by'
|
17
|
+
element :hourly_rate, Float, :tag => 'hourly-rate'
|
18
|
+
element :bill_by, String, :tag => 'bill-by'
|
19
|
+
|
20
|
+
alias_method :active?, :active
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Harvest
|
2
|
+
class RateLimitStatus < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
tag 'hash'
|
6
|
+
element :last_access_at, Time, :tag => 'last-access-at'
|
7
|
+
element :count, Integer
|
8
|
+
element :timeframe_limit, Integer, :tag => 'timeframe-limit'
|
9
|
+
element :max_calls, Integer, :tag => 'max-calls'
|
10
|
+
element :lockout_seconds, Integer, :tag => 'lockout-seconds'
|
11
|
+
|
12
|
+
def over_limit?
|
13
|
+
count > max_calls
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/harvest/task.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Harvest
|
2
|
+
class Task < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
api_path '/tasks'
|
6
|
+
|
7
|
+
element :id, Integer
|
8
|
+
element :name, String
|
9
|
+
element :billable, Boolean, :tag => 'billable-by-default'
|
10
|
+
element :deactivated, Boolean, :tag => 'deactivated'
|
11
|
+
element :hourly_rate, Float, :tag => 'default-hourly-rate'
|
12
|
+
element :default, Boolean, :tag => 'is-default'
|
13
|
+
|
14
|
+
def active?
|
15
|
+
!deactivated
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :default?, :default
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Harvest
|
2
|
+
class TaskAssignment < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
tag 'task-assignment'
|
6
|
+
element :id, Integer
|
7
|
+
element :task_id, Integer, :tag => 'task-id'
|
8
|
+
element :project_id, Integer, :tag => 'project-id'
|
9
|
+
element :billable, Boolean
|
10
|
+
element :deactivated, Boolean
|
11
|
+
element :hourly_rate, Float, :tag => 'hourly-rate'
|
12
|
+
|
13
|
+
def task=(task)
|
14
|
+
@task_id = task.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
def project=(project)
|
18
|
+
@project_id = project.to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
def active?
|
22
|
+
!deactivated
|
23
|
+
end
|
24
|
+
|
25
|
+
def task_xml
|
26
|
+
builder = Builder::XmlMarkup.new
|
27
|
+
builder.task do |t|
|
28
|
+
t.id(task_id)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
alias_method :billable?, :billable
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Harvest
|
2
|
+
class TimeEntry < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
tag 'day_entry'
|
6
|
+
|
7
|
+
element :id, Integer
|
8
|
+
element :client, String
|
9
|
+
element :project, String
|
10
|
+
element :task, String
|
11
|
+
element :hours, Float
|
12
|
+
element :notes, String
|
13
|
+
|
14
|
+
element :project_id, Integer
|
15
|
+
element :task_id, Integer
|
16
|
+
element :spent_at, Time
|
17
|
+
element :created_at, Time
|
18
|
+
element :updated_at, Time
|
19
|
+
element :user_id, Integer
|
20
|
+
element :closed, Boolean, :tag => 'is-closed'
|
21
|
+
element :billed, Boolean, :tag => 'is-billed'
|
22
|
+
|
23
|
+
def spent_at=(date)
|
24
|
+
@spent_at = (String === date ? Time.parse(date) : date)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_xml
|
28
|
+
builder = Builder::XmlMarkup.new
|
29
|
+
builder.request do |r|
|
30
|
+
r.tag!('notes', notes) if notes
|
31
|
+
r.tag!('hours', hours) if hours
|
32
|
+
r.tag!('project_id', project_id) if project_id
|
33
|
+
r.tag!('task_id', task_id) if task_id
|
34
|
+
r.tag!('spent_at', spent_at) if spent_at
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
alias_method :closed?, :closed
|
39
|
+
alias_method :billed?, :billed
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
# shamelessly ripped from Rails: http://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb
|
2
|
+
|
3
|
+
module Harvest
|
4
|
+
module Timezones
|
5
|
+
MAPPING = {
|
6
|
+
"pacific/midway" => "International Date Line West",
|
7
|
+
"pacific/midway" => "Midway Island",
|
8
|
+
"pacific/pago_pago" => "Samoa",
|
9
|
+
"pacific/honolulu" => "Hawaii",
|
10
|
+
"america/juneau" => "Alaska",
|
11
|
+
"america/los_angeles" => "Pacific Time (US & Canada)",
|
12
|
+
"america/tijuana" => "Tijuana",
|
13
|
+
"america/denver" => "Mountain Time (US & Canada)",
|
14
|
+
"america/phoenix" => "Arizona",
|
15
|
+
"america/chihuahua" => "Chihuahua",
|
16
|
+
"america/mazatlan" => "Mazatlan",
|
17
|
+
"america/chicago" => "Central Time (US & Canada)",
|
18
|
+
"america/regina" => "Saskatchewan",
|
19
|
+
"america/mexico_city" => "Guadalajara",
|
20
|
+
"america/mexico_city" => "Mexico City",
|
21
|
+
"america/monterrey" => "Monterrey",
|
22
|
+
"america/guatemala" => "Central America",
|
23
|
+
"america/new_york" => "Eastern Time (US & Canada)",
|
24
|
+
"america/indiana/indianapolis" => "Indiana (East)",
|
25
|
+
"america/bogota" => "Bogota",
|
26
|
+
"america/lima" => "Lima",
|
27
|
+
"america/lima" => "Quito",
|
28
|
+
"america/halifax" => "Atlantic Time (Canada)",
|
29
|
+
"america/caracas" => "Caracas",
|
30
|
+
"america/la_paz" => "La Paz",
|
31
|
+
"america/santiago" => "Santiago",
|
32
|
+
"america/st_johns" => "Newfoundland",
|
33
|
+
"america/sao_paulo" => "Brasilia",
|
34
|
+
"america/argentina/buenos_aires" => "Buenos Aires",
|
35
|
+
"america/argentina/san_juan" => "Georgetown",
|
36
|
+
"america/godthab" => "Greenland",
|
37
|
+
"atlantic/south_georgia" => "Mid-Atlantic",
|
38
|
+
"atlantic/azores" => "Azores",
|
39
|
+
"atlantic/cape_verde" => "Cape Verde Is.",
|
40
|
+
"europe/dublin" => "Dublin",
|
41
|
+
"europe/dublin" => "Edinburgh",
|
42
|
+
"europe/lisbon" => "Lisbon",
|
43
|
+
"europe/london" => "London",
|
44
|
+
"africa/casablanca" => "Casablanca",
|
45
|
+
"africa/monrovia" => "Monrovia",
|
46
|
+
"etc/utc" => "UTC",
|
47
|
+
"europe/belgrade" => "Belgrade",
|
48
|
+
"europe/bratislava" => "Bratislava",
|
49
|
+
"europe/budapest" => "Budapest",
|
50
|
+
"europe/ljubljana" => "Ljubljana",
|
51
|
+
"europe/prague" => "Prague",
|
52
|
+
"europe/sarajevo" => "Sarajevo",
|
53
|
+
"europe/skopje" => "Skopje",
|
54
|
+
"europe/warsaw" => "Warsaw",
|
55
|
+
"europe/zagreb" => "Zagreb",
|
56
|
+
"europe/brussels" => "Brussels",
|
57
|
+
"europe/copenhagen" => "Copenhagen",
|
58
|
+
"europe/madrid" => "Madrid",
|
59
|
+
"europe/paris" => "Paris",
|
60
|
+
"europe/amsterdam" => "Amsterdam",
|
61
|
+
"europe/berlin" => "Berlin",
|
62
|
+
"europe/berlin" => "Bern",
|
63
|
+
"europe/rome" => "Rome",
|
64
|
+
"europe/stockholm" => "Stockholm",
|
65
|
+
"europe/vienna" => "Vienna",
|
66
|
+
"africa/algiers" => "West Central Africa",
|
67
|
+
"europe/bucharest" => "Bucharest",
|
68
|
+
"africa/cairo" => "Cairo",
|
69
|
+
"europe/helsinki" => "Helsinki",
|
70
|
+
"europe/kiev" => "Kyev",
|
71
|
+
"europe/riga" => "Riga",
|
72
|
+
"europe/sofia" => "Sofia",
|
73
|
+
"europe/tallinn" => "Tallinn",
|
74
|
+
"europe/vilnius" => "Vilnius",
|
75
|
+
"europe/athens" => "Athens",
|
76
|
+
"europe/istanbul" => "Istanbul",
|
77
|
+
"europe/minsk" => "Minsk",
|
78
|
+
"asia/jerusalem" => "Jerusalem",
|
79
|
+
"africa/harare" => "Harare",
|
80
|
+
"africa/johannesburg" => "Pretoria",
|
81
|
+
"europe/moscow" => "Moscow",
|
82
|
+
"europe/moscow" => "St. Petersburg",
|
83
|
+
"europe/moscow" => "Volgograd",
|
84
|
+
"asia/kuwait" => "Kuwait",
|
85
|
+
"asia/riyadh" => "Riyadh",
|
86
|
+
"africa/nairobi" => "Nairobi",
|
87
|
+
"asia/baghdad" => "Baghdad",
|
88
|
+
"asia/tehran" => "Tehran",
|
89
|
+
"asia/muscat" => "Abu Dhabi",
|
90
|
+
"asia/muscat" => "Muscat",
|
91
|
+
"asia/baku" => "Baku",
|
92
|
+
"asia/tbilisi" => "Tbilisi",
|
93
|
+
"asia/yerevan" => "Yerevan",
|
94
|
+
"asia/kabul" => "Kabul",
|
95
|
+
"asia/yekaterinburg" => "Ekaterinburg",
|
96
|
+
"asia/karachi" => "Islamabad",
|
97
|
+
"asia/karachi" => "Karachi",
|
98
|
+
"asia/tashkent" => "Tashkent",
|
99
|
+
"asia/kolkata" => "Chennai",
|
100
|
+
"asia/kolkata" => "Kolkata",
|
101
|
+
"asia/kolkata" => "Mumbai",
|
102
|
+
"asia/kolkata" => "New Delhi",
|
103
|
+
"asia/katmandu" => "Kathmandu",
|
104
|
+
"asia/dhaka" => "Astana",
|
105
|
+
"asia/dhaka" => "Dhaka",
|
106
|
+
"asia/colombo" => "Sri Jayawardenepura",
|
107
|
+
"asia/almaty" => "Almaty",
|
108
|
+
"asia/novosibirsk" => "Novosibirsk",
|
109
|
+
"asia/rangoon" => "Rangoon",
|
110
|
+
"asia/bangkok" => "Bangkok",
|
111
|
+
"asia/bangkok" => "Hanoi",
|
112
|
+
"asia/jakarta" => "Jakarta",
|
113
|
+
"asia/krasnoyarsk" => "Krasnoyarsk",
|
114
|
+
"asia/shanghai" => "Beijing",
|
115
|
+
"asia/chongqing" => "Chongqing",
|
116
|
+
"asia/hong_kong" => "Hong Kong",
|
117
|
+
"asia/urumqi" => "Urumqi",
|
118
|
+
"asia/kuala_lumpur" => "Kuala Lumpur",
|
119
|
+
"asia/singapore" => "Singapore",
|
120
|
+
"asia/taipei" => "Taipei",
|
121
|
+
"australia/perth" => "Perth",
|
122
|
+
"asia/irkutsk" => "Irkutsk",
|
123
|
+
"asia/ulaanbaatar" => "Ulaan Bataar",
|
124
|
+
"asia/seoul" => "Seoul",
|
125
|
+
"asia/tokyo" => "Osaka",
|
126
|
+
"asia/tokyo" => "Sapporo",
|
127
|
+
"asia/tokyo" => "Tokyo",
|
128
|
+
"asia/yakutsk" => "Yakutsk",
|
129
|
+
"australia/darwin" => "Darwin",
|
130
|
+
"australia/adelaide" => "Adelaide",
|
131
|
+
"australia/melbourne" => "Canberra",
|
132
|
+
"australia/melbourne" => "Melbourne",
|
133
|
+
"australia/sydney" => "Sydney",
|
134
|
+
"australia/brisbane" => "Brisbane",
|
135
|
+
"australia/hobart" => "Hobart",
|
136
|
+
"asia/vladivostok" => "Vladivostok",
|
137
|
+
"pacific/guam" => "Guam",
|
138
|
+
"pacific/port_moresby" => "Port Moresby",
|
139
|
+
"asia/magadan" => "Magadan",
|
140
|
+
"asia/magadan" => "Solomon Is.",
|
141
|
+
"pacific/noumea" => "New Caledonia",
|
142
|
+
"pacific/fiji" => "Fiji",
|
143
|
+
"asia/kamchatka" => "Kamchatka",
|
144
|
+
"pacific/majuro" => "Marshall Is.",
|
145
|
+
"pacific/auckland" => "Auckland",
|
146
|
+
"pacific/auckland" => "Wellington",
|
147
|
+
"pacific/tongatapu" => "Nuku'alofa"
|
148
|
+
}
|
149
|
+
end
|
150
|
+
end
|