harvested2 5.0.3

Sign up to get free protection for your applications and to get access to all the features.
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