harvested2 5.0.3

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.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.gitignore +35 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +34 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +12 -0
  8. data/Gemfile +20 -0
  9. data/HISTORY.md +118 -0
  10. data/MIT-LICENSE +21 -0
  11. data/README.md +66 -0
  12. data/Rakefile +24 -0
  13. data/harvested2.gemspec +30 -0
  14. data/lib/ext/array.rb +52 -0
  15. data/lib/ext/date.rb +9 -0
  16. data/lib/ext/hash.rb +17 -0
  17. data/lib/ext/time.rb +5 -0
  18. data/lib/harvest/account.rb +13 -0
  19. data/lib/harvest/api/account.rb +25 -0
  20. data/lib/harvest/api/base.rb +72 -0
  21. data/lib/harvest/api/clients.rb +10 -0
  22. data/lib/harvest/api/company.rb +12 -0
  23. data/lib/harvest/api/contacts.rb +9 -0
  24. data/lib/harvest/api/expense_categories.rb +9 -0
  25. data/lib/harvest/api/expenses.rb +26 -0
  26. data/lib/harvest/api/invoice_categories.rb +9 -0
  27. data/lib/harvest/api/invoice_messages.rb +86 -0
  28. data/lib/harvest/api/invoice_payments.rb +41 -0
  29. data/lib/harvest/api/invoices.rb +9 -0
  30. data/lib/harvest/api/projects.rb +9 -0
  31. data/lib/harvest/api/task_assignments.rb +75 -0
  32. data/lib/harvest/api/tasks.rb +9 -0
  33. data/lib/harvest/api/time_entry.rb +19 -0
  34. data/lib/harvest/api/user_assignments.rb +75 -0
  35. data/lib/harvest/api/users.rb +10 -0
  36. data/lib/harvest/base.rb +333 -0
  37. data/lib/harvest/behavior/activatable.rb +31 -0
  38. data/lib/harvest/behavior/crud.rb +80 -0
  39. data/lib/harvest/client.rb +23 -0
  40. data/lib/harvest/company.rb +8 -0
  41. data/lib/harvest/contact.rb +20 -0
  42. data/lib/harvest/credentials.rb +34 -0
  43. data/lib/harvest/errors.rb +27 -0
  44. data/lib/harvest/expense.rb +54 -0
  45. data/lib/harvest/expense_category.rb +10 -0
  46. data/lib/harvest/hardy_client.rb +80 -0
  47. data/lib/harvest/invoice.rb +75 -0
  48. data/lib/harvest/invoice_category.rb +8 -0
  49. data/lib/harvest/invoice_message.rb +8 -0
  50. data/lib/harvest/invoice_payment.rb +8 -0
  51. data/lib/harvest/line_item.rb +21 -0
  52. data/lib/harvest/model.rb +133 -0
  53. data/lib/harvest/project.rb +41 -0
  54. data/lib/harvest/receipt.rb +12 -0
  55. data/lib/harvest/task.rb +21 -0
  56. data/lib/harvest/task_assignment.rb +27 -0
  57. data/lib/harvest/time_entry.rb +57 -0
  58. data/lib/harvest/timezones.rb +130 -0
  59. data/lib/harvest/user.rb +58 -0
  60. data/lib/harvest/user_assignment.rb +27 -0
  61. data/lib/harvest/version.rb +3 -0
  62. data/lib/harvested2.rb +96 -0
  63. data/spec/factories/client.rb +14 -0
  64. data/spec/factories/contact.rb +8 -0
  65. data/spec/factories/expense.rb +10 -0
  66. data/spec/factories/expenses_category.rb +7 -0
  67. data/spec/factories/invoice.rb +25 -0
  68. data/spec/factories/invoice_category.rb +5 -0
  69. data/spec/factories/invoice_message.rb +9 -0
  70. data/spec/factories/invoice_payment.rb +7 -0
  71. data/spec/factories/line_item.rb +9 -0
  72. data/spec/factories/project.rb +15 -0
  73. data/spec/factories/task.rb +8 -0
  74. data/spec/factories/task_assignment.rb +8 -0
  75. data/spec/factories/time_entry.rb +13 -0
  76. data/spec/factories/user.rb +19 -0
  77. data/spec/factories/user_assigment.rb +7 -0
  78. data/spec/functional/clients_spec.rb +105 -0
  79. data/spec/functional/errors_spec.rb +42 -0
  80. data/spec/functional/expenses_spec.rb +97 -0
  81. data/spec/functional/invoice_messages_spec.rb +48 -0
  82. data/spec/functional/invoice_payments_spec.rb +51 -0
  83. data/spec/functional/invoice_spec.rb +138 -0
  84. data/spec/functional/project_spec.rb +76 -0
  85. data/spec/functional/tasks_spec.rb +119 -0
  86. data/spec/functional/time_entries_spec.rb +87 -0
  87. data/spec/functional/users_spec.rb +72 -0
  88. data/spec/harvest/base_spec.rb +10 -0
  89. data/spec/harvest/basic_auth_credentials_spec.rb +12 -0
  90. data/spec/harvest/expense_category_spec.rb +5 -0
  91. data/spec/harvest/expense_spec.rb +18 -0
  92. data/spec/harvest/invoice_message_spec.rb +5 -0
  93. data/spec/harvest/invoice_payment_spec.rb +5 -0
  94. data/spec/harvest/invoice_spec.rb +5 -0
  95. data/spec/harvest/oauth_credentials_spec.rb +11 -0
  96. data/spec/harvest/project_spec.rb +5 -0
  97. data/spec/harvest/task_assignment_spec.rb +5 -0
  98. data/spec/harvest/task_spec.rb +5 -0
  99. data/spec/harvest/time_entry_spec.rb +23 -0
  100. data/spec/harvest/user_assignment_spec.rb +5 -0
  101. data/spec/harvest/user_spec.rb +34 -0
  102. data/spec/spec_helper.rb +22 -0
  103. data/spec/support/factory_bot.rb +5 -0
  104. data/spec/support/harvested_helpers.rb +28 -0
  105. data/spec/support/json_examples.rb +9 -0
  106. metadata +238 -0
@@ -0,0 +1,8 @@
1
+ module Harvest
2
+ class InvoiceCategory < Hashie::Mash
3
+ include Harvest::Model
4
+
5
+ skip_json_root true
6
+ api_path '/invoice_item_categories'
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Harvest
2
+ class InvoiceMessage < Hashie::Mash
3
+ include Harvest::Model
4
+
5
+ skip_json_root true
6
+ api_path '/messages'
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Harvest
2
+ class InvoicePayment < Hashie::Mash
3
+ include Harvest::Model
4
+
5
+ skip_json_root true
6
+ api_path '/payments'
7
+ end
8
+ end
@@ -0,0 +1,21 @@
1
+ module Harvest
2
+ class LineItem < Hashie::Mash
3
+ def initialize(args = {}, _ = nil)
4
+ args = args.stringify_keys
5
+ self.project = args.delete('project') if args['project']
6
+ super
7
+ end
8
+
9
+ def project=(project)
10
+ self['project_id'] = project['id']
11
+ end
12
+
13
+ def active?
14
+ !deactivated
15
+ end
16
+
17
+ def line_item_as_json
18
+ { 'line_item' => { 'id' => line_item_id } }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,133 @@
1
+ module Harvest
2
+ module Model
3
+ def self.included(base)
4
+ base.send :include, InstanceMethods
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+ def to_json(*args)
10
+ as_json(*args).to_json(*args)
11
+ end
12
+
13
+ def as_json(args = {})
14
+ inner_json = self.to_hash.stringify_keys
15
+ inner_json.delete('cache_version')
16
+ if self.class.skip_json_root?
17
+ inner_json
18
+ else
19
+ { self.class.json_root => inner_json }
20
+ end
21
+ end
22
+
23
+ def ==(other)
24
+ other.kind_of?(self.class) && id == other.id
25
+ end
26
+
27
+ def impersonated_user_id
28
+ if respond_to?(:of_user) && respond_to?(:user_id)
29
+ of_user || user_id
30
+ elsif !respond_to?(:of_user) && respond_to?(:user_id)
31
+ user_id
32
+ elsif respond_to?(:of_user)
33
+ of_user
34
+ end
35
+ end
36
+
37
+ def json_root
38
+ self.class.json_root
39
+ end
40
+ end
41
+
42
+ module ClassMethods
43
+ # This sets the API path so the API collections can use them in an agnostic way
44
+ # @return [void]
45
+ def api_path(path = nil)
46
+ @_api_path ||= path
47
+ end
48
+
49
+ def skip_json_root(skip = nil)
50
+ @_skip_json_root ||= skip
51
+ end
52
+
53
+ def skip_json_root?
54
+ @_skip_json_root == true
55
+ end
56
+
57
+ def parse(json)
58
+ parsed = String === json ? JSON.parse(json) : json
59
+ objects = skip_json_root? && parsed[json_root.pluralize] || parsed
60
+ Array.wrap(objects).map { |attrs| new(attrs) }
61
+ end
62
+
63
+ def json_root
64
+ Harvest::Model::Utility.underscore(
65
+ Harvest::Model::Utility.demodulize(to_s))
66
+ end
67
+
68
+ def to_json(json)
69
+ parsed = String === json ? JSON.parse(json) : json
70
+ end
71
+
72
+ def wrap(model_or_attrs)
73
+ case model_or_attrs
74
+ when Hashie::Mash
75
+ model_or_attrs
76
+ when Hash
77
+ new(model_or_attrs)
78
+ else
79
+ model_or_attrs
80
+ end
81
+ end
82
+
83
+ def delegate_methods(options)
84
+ raise "no methods given" if options.empty?
85
+ options.each do |source, dest|
86
+ class_eval <<-EOV
87
+ def #{source}
88
+ #{dest}
89
+ end
90
+ EOV
91
+ end
92
+ end
93
+ end
94
+
95
+ module Utility
96
+ class << self
97
+
98
+ # Both methods are shamelessly ripped from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/inflections.rb
99
+
100
+ # Removes the module part from the expression in the string.
101
+ #
102
+ # Examples:
103
+ # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
104
+ # "Inflections".demodulize
105
+ def demodulize(class_name_in_module)
106
+ class_name_in_module.to_s.gsub(/^.*::/, '')
107
+ end
108
+
109
+ # Makes an underscored, lowercase form from the expression in the string.
110
+ #
111
+ # Changes '::' to '/' to convert namespaces to paths.
112
+ #
113
+ # Examples:
114
+ # "ActiveRecord".underscore # => "active_record"
115
+ # "ActiveRecord::Errors".underscore # => active_record/errors
116
+ #
117
+ # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
118
+ # though there are cases where that does not hold:
119
+ #
120
+ # "SSLError".underscore.camelize # => "SslError"
121
+ def underscore(camel_cased_word)
122
+ word = camel_cased_word.to_s.dup
123
+ word.gsub!(/::/, '/')
124
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
125
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
126
+ word.tr!("-", "_")
127
+ word.downcase!
128
+ word
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,41 @@
1
+ module Harvest
2
+
3
+ # The model that contains information about a project
4
+ #
5
+ # == Fields
6
+ # [+id+] (READONLY) the id of the project
7
+ # [+name+] (REQUIRED) the name of the project
8
+ # [+client_id+] (REQUIRED) the client id of the project
9
+ # [+code+] the project code
10
+ # [+notes+] the project notes
11
+ # [+active?+] true|false whether the project is active
12
+ # [+billable?+] true|false where the project is billable
13
+ # [+budget_by+] how the budget is calculated for the project +project|project_cost|task|person|nil+
14
+ # [+budget+] what the budget is for the project (based on budget_by)
15
+ # [+bill_by+] how to bill the project +Tasks|People|Project|nil+
16
+ # [+hourly_rate+] what the hourly rate for the project is based on +bill_by+
17
+ # [+notify_when_over_budget?+] whether the project will send notifications when it goes over budget
18
+ # [+over_budget_notification_percentage+] what percentage of the budget the project has to be before it sends a notification. Based on +notify_when_over_budget?+
19
+ # [+show_budget_to_all?+] whether the project's budget is shown to employees and contractors
20
+ # [+basecamp_id+] (READONLY) the id of the basecamp project associated to the project
21
+ # [+highrise_deal_id+] (READONLY) the id of the highrise deal associated to the project
22
+ # [+active_task_assignments_count+] (READONLY) the number of active task assignments
23
+ # [+created_at+] (READONLY) when the project was created
24
+ # [+updated_at+] (READONLY) when the project was updated
25
+ class Project < Hashie::Mash
26
+ include Harvest::Model
27
+
28
+ skip_json_root true
29
+ api_path '/projects'
30
+
31
+ def initialize(args = {}, _ = nil)
32
+ args = args.stringify_keys
33
+ self.client = args.delete('client') if args['client']
34
+ super
35
+ end
36
+
37
+ def client=(client)
38
+ self['client_id'] = client['id']
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,12 @@
1
+ module Harvest
2
+ # The model that contains information about a task
3
+ #
4
+ # == Fields
5
+ # [+url+] URL file
6
+ # [+file_name+] file name
7
+ # [+file_size+] size of the image
8
+ # [+content_type+] image/gif
9
+ class Receipt < Hashie::Mash
10
+ include Harvest::Model
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ module Harvest
2
+ # The model that contains information about a task
3
+ #
4
+ # == Fields
5
+ # [+id+] (READONLY) the id of the task
6
+ # [+name+] (REQUIRED) the name of the task
7
+ # [+billable+] whether the task is billable by default
8
+ # [+deactivated+] whether the task is deactivated
9
+ # [+hourly_rate+] what the default hourly rate for the task is
10
+ # [+default?+] whether to add this task to new projects by default
11
+ class Task < Hashie::Mash
12
+ include Harvest::Model
13
+
14
+ skip_json_root true
15
+ api_path '/tasks'
16
+
17
+ def active?
18
+ !deactivated
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Harvest
2
+ class TaskAssignment < Hashie::Mash
3
+ include Harvest::Model
4
+
5
+ skip_json_root true
6
+
7
+ def initialize(args = {}, _ = nil)
8
+ args = args.stringify_keys
9
+ self.task = args.delete('task') if args['task']
10
+ self.project = args.delete('project') if args['project']
11
+
12
+ super
13
+ end
14
+
15
+ def task=(task)
16
+ self['task_id'] = task['id']
17
+ end
18
+
19
+ def project=(project)
20
+ self['project_id'] = project['id']
21
+ end
22
+
23
+ def active?
24
+ !deactivated
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,57 @@
1
+ module Harvest
2
+ class TimeEntry < Hashie::Mash
3
+ include Harvest::Model
4
+
5
+ skip_json_root true
6
+ api_path '/time_entries'
7
+
8
+ delegate_methods(closed?: :is_closed,
9
+ billed?: :is_billed)
10
+
11
+ def initialize(args = {}, _ = nil)
12
+ args = args.stringify_keys
13
+ self.user = args.delete('user') if args['user']
14
+ self.task = args.delete('task') if args['task']
15
+ self.client = args.delete('client') if args['client']
16
+ self.project = args.delete('project') if args['project']
17
+ self.spent_date = args.delete('spent_date') if args['spent_date']
18
+ self.user_assignment = args.delete('user_assignment') if args['user_assignment']
19
+ self.task_assignment = args.delete('task_assignment') if args['task_assignment']
20
+ super
21
+ end
22
+
23
+ def user=(user)
24
+ self['user_id'] = user['id']
25
+ end
26
+
27
+ def client=(client)
28
+ self['client_id'] = client['id']
29
+ end
30
+
31
+ def project=(project)
32
+ self['project_id'] = project['id']
33
+ end
34
+
35
+ def task=(task)
36
+ self['task_id'] = task['id']
37
+ end
38
+
39
+ def user_assignment=(user_assignment)
40
+ self['user_assignment_id'] = user_assignment['id']
41
+ end
42
+
43
+ def task_assignment=(task_assignment)
44
+ self['task_assignment_id'] = task_assignment['id']
45
+ end
46
+
47
+ def spent_date=(date)
48
+ self['spent_date'] = Date.parse(date.to_s)
49
+ end
50
+
51
+ def as_json(args = {})
52
+ super(args).to_hash.stringify_keys.tap do |hash|
53
+ hash.update('spent_date' => (spent_date.nil? ? nil : spent_date.xmlschema))
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,130 @@
1
+ module Harvest
2
+ # shamelessly ripped from Rails: http://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb
3
+ module Timezones
4
+ MAPPING = {
5
+ 'pacific/midway' => 'Midway Island',
6
+ 'pacific/pago_pago' => 'Samoa',
7
+ 'pacific/honolulu' => 'Hawaii',
8
+ 'america/juneau' => 'Alaska',
9
+ 'america/los_angeles' => 'Pacific Time (US & Canada)',
10
+ 'america/tijuana' => 'Tijuana',
11
+ 'america/denver' => 'Mountain Time (US & Canada)',
12
+ 'america/phoenix' => 'Arizona',
13
+ 'america/chihuahua' => 'Chihuahua',
14
+ 'america/mazatlan' => 'Mazatlan',
15
+ 'america/chicago' => 'Central Time (US & Canada)',
16
+ 'america/regina' => 'Saskatchewan',
17
+ 'america/mexico_city' => 'Mexico City',
18
+ 'america/monterrey' => 'Monterrey',
19
+ 'america/guatemala' => 'Central America',
20
+ 'america/new_york' => 'Eastern Time (US & Canada)',
21
+ 'america/indiana/indianapolis' => 'Indiana (East)',
22
+ 'america/bogota' => 'Bogota',
23
+ 'america/lima' => 'Lima',
24
+ 'america/halifax' => 'Atlantic Time (Canada)',
25
+ 'america/caracas' => 'Caracas',
26
+ 'america/la_paz' => 'La Paz',
27
+ 'america/santiago' => 'Santiago',
28
+ 'america/st_johns' => 'Newfoundland',
29
+ 'america/sao_paulo' => 'Brasilia',
30
+ 'america/argentina/buenos_aires' => 'Buenos Aires',
31
+ 'america/argentina/san_juan' => 'Georgetown',
32
+ 'america/godthab' => 'Greenland',
33
+ 'atlantic/south_georgia' => 'Mid-Atlantic',
34
+ 'atlantic/azores' => 'Azores',
35
+ 'atlantic/cape_verde' => 'Cape Verde Is.',
36
+ 'europe/dublin' => 'Dublin',
37
+ 'europe/lisbon' => 'Lisbon',
38
+ 'europe/london' => 'London',
39
+ 'africa/casablanca' => 'Casablanca',
40
+ 'africa/monrovia' => 'Monrovia',
41
+ 'etc/utc' => 'UTC',
42
+ 'europe/belgrade' => 'Belgrade',
43
+ 'europe/bratislava' => 'Bratislava',
44
+ 'europe/budapest' => 'Budapest',
45
+ 'europe/ljubljana' => 'Ljubljana',
46
+ 'europe/prague' => 'Prague',
47
+ 'europe/sarajevo' => 'Sarajevo',
48
+ 'europe/skopje' => 'Skopje',
49
+ 'europe/warsaw' => 'Warsaw',
50
+ 'europe/zagreb' => 'Zagreb',
51
+ 'europe/brussels' => 'Brussels',
52
+ 'europe/copenhagen' => 'Copenhagen',
53
+ 'europe/madrid' => 'Madrid',
54
+ 'europe/paris' => 'Paris',
55
+ 'europe/amsterdam' => 'Amsterdam',
56
+ 'europe/berlin' => 'Berlin',
57
+ 'europe/rome' => 'Rome',
58
+ 'europe/stockholm' => 'Stockholm',
59
+ 'europe/vienna' => 'Vienna',
60
+ 'africa/algiers' => 'West Central Africa',
61
+ 'europe/bucharest' => 'Bucharest',
62
+ 'africa/cairo' => 'Cairo',
63
+ 'europe/helsinki' => 'Helsinki',
64
+ 'europe/kiev' => 'Kyev',
65
+ 'europe/riga' => 'Riga',
66
+ 'europe/sofia' => 'Sofia',
67
+ 'europe/tallinn' => 'Tallinn',
68
+ 'europe/vilnius' => 'Vilnius',
69
+ 'europe/athens' => 'Athens',
70
+ 'europe/istanbul' => 'Istanbul',
71
+ 'europe/minsk' => 'Minsk',
72
+ 'asia/jerusalem' => 'Jerusalem',
73
+ 'africa/harare' => 'Harare',
74
+ 'africa/johannesburg' => 'Pretoria',
75
+ 'europe/moscow' => 'Moscow',
76
+ 'asia/kuwait' => 'Kuwait',
77
+ 'asia/riyadh' => 'Riyadh',
78
+ 'africa/nairobi' => 'Nairobi',
79
+ 'asia/baghdad' => 'Baghdad',
80
+ 'asia/tehran' => 'Tehran',
81
+ 'asia/muscat' => 'Muscat',
82
+ 'asia/baku' => 'Baku',
83
+ 'asia/tbilisi' => 'Tbilisi',
84
+ 'asia/yerevan' => 'Yerevan',
85
+ 'asia/kabul' => 'Kabul',
86
+ 'asia/yekaterinburg' => 'Ekaterinburg',
87
+ 'asia/karachi' => 'Karachi',
88
+ 'asia/tashkent' => 'Tashkent',
89
+ 'asia/kolkata' => 'Kolkata',
90
+ 'asia/katmandu' => 'Kathmandu',
91
+ 'asia/dhaka' => 'Dhaka',
92
+ 'asia/colombo' => 'Sri Jayawardenepura',
93
+ 'asia/almaty' => 'Almaty',
94
+ 'asia/novosibirsk' => 'Novosibirsk',
95
+ 'asia/rangoon' => 'Rangoon',
96
+ 'asia/bangkok' => 'Bangkok',
97
+ 'asia/jakarta' => 'Jakarta',
98
+ 'asia/krasnoyarsk' => 'Krasnoyarsk',
99
+ 'asia/shanghai' => 'Beijing',
100
+ 'asia/chongqing' => 'Chongqing',
101
+ 'asia/hong_kong' => 'Hong Kong',
102
+ 'asia/urumqi' => 'Urumqi',
103
+ 'asia/kuala_lumpur' => 'Kuala Lumpur',
104
+ 'asia/singapore' => 'Singapore',
105
+ 'asia/taipei' => 'Taipei',
106
+ 'australia/perth' => 'Perth',
107
+ 'asia/irkutsk' => 'Irkutsk',
108
+ 'asia/ulaanbaatar' => 'Ulaan Bataar',
109
+ 'asia/seoul' => 'Seoul',
110
+ 'asia/tokyo' => 'Tokyo',
111
+ 'asia/yakutsk' => 'Yakutsk',
112
+ 'australia/darwin' => 'Darwin',
113
+ 'australia/adelaide' => 'Adelaide',
114
+ 'australia/melbourne' => 'Melbourne',
115
+ 'australia/sydney' => 'Sydney',
116
+ 'australia/brisbane' => 'Brisbane',
117
+ 'australia/hobart' => 'Hobart',
118
+ 'asia/vladivostok' => 'Vladivostok',
119
+ 'pacific/guam' => 'Guam',
120
+ 'pacific/port_moresby' => 'Port Moresby',
121
+ 'asia/magadan' => 'Magadan',
122
+ 'pacific/noumea' => 'New Caledonia',
123
+ 'pacific/fiji' => 'Fiji',
124
+ 'asia/kamchatka' => 'Kamchatka',
125
+ 'pacific/majuro' => 'Marshall Is.',
126
+ 'pacific/auckland' => 'Auckland',
127
+ 'pacific/tongatapu' => "Nuku'alofa"
128
+ }
129
+ end
130
+ end