timelogic-api 1.0.3 → 2.0.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +314 -133
  3. data/docs/BulkError.md +1 -1
  4. data/docs/CalendarResponse.md +2 -2
  5. data/docs/DstResponse.md +2 -2
  6. data/docs/ElapsedResponse.md +1 -1
  7. data/docs/ErrorResponse.md +1 -1
  8. data/docs/JwkKey.md +5 -1
  9. data/docs/TimeApi.md +157 -155
  10. data/docs/TimePayload.md +1 -1
  11. data/docs/TimezoneMatch.md +3 -23
  12. data/docs/TimezoneResponse.md +46 -0
  13. data/lib/timelogic-api/api/time_api.rb +175 -180
  14. data/lib/timelogic-api/api/utility_api.rb +1 -1
  15. data/lib/timelogic-api/api_client.rb +3 -1
  16. data/lib/timelogic-api/api_error.rb +1 -1
  17. data/lib/timelogic-api/configuration.rb +1 -1
  18. data/lib/timelogic-api/models/api_error.rb +1 -1
  19. data/lib/timelogic-api/models/bulk_error.rb +2 -2
  20. data/lib/timelogic-api/models/calendar_response.rb +3 -1
  21. data/lib/timelogic-api/models/diff_endpoint_ref.rb +1 -1
  22. data/lib/timelogic-api/models/diff_response.rb +1 -1
  23. data/lib/timelogic-api/models/dst_response.rb +3 -1
  24. data/lib/timelogic-api/models/elapsed_response.rb +2 -2
  25. data/lib/timelogic-api/models/error_response.rb +2 -2
  26. data/lib/timelogic-api/models/get_current_time200_response.rb +1 -1
  27. data/lib/timelogic-api/models/jwk_key.rb +27 -5
  28. data/lib/timelogic-api/models/jwks_response.rb +1 -1
  29. data/lib/timelogic-api/models/time_payload.rb +2 -1
  30. data/lib/timelogic-api/models/time_payload_bulk_item.rb +1 -1
  31. data/lib/timelogic-api/models/time_payload_bulk_response.rb +29 -183
  32. data/lib/timelogic-api/models/timezone_match.rb +6 -186
  33. data/lib/timelogic-api/models/{timezone_offset_response.rb → timezone_response.rb} +21 -14
  34. data/lib/timelogic-api/version.rb +1 -1
  35. data/lib/timelogic-api/world_time_api.rb +192 -0
  36. data/lib/timelogic-api.rb +3 -4
  37. metadata +5 -8
  38. data/docs/GetTimezone200Response.md +0 -49
  39. data/docs/TimezoneOffsetResponse.md +0 -44
  40. data/docs/TimezoneResolvedResponse.md +0 -42
  41. data/lib/timelogic-api/models/get_timezone200_response.rb +0 -105
  42. data/lib/timelogic-api/models/timezone_resolved_response.rb +0 -434
@@ -1,7 +1,7 @@
1
1
  =begin
2
2
  #TimeLogic API | A World Time API
3
3
 
4
- # Official public API contract for TimeLogic API.
4
+ #Direct API contract for client integrations. It documents authentication, endpoint parameters, response shapes, public signing keys, and signed JSON controls. Authentication: - the API supports `Authorization: Bearer <token>`, `X-API-Key`, and `api_key` query credentials - RapidAPI access uses `X-RapidAPI-Key` and `X-RapidAPI-Host`; the SDKs expose this as a `rapidApi` transport option that accepts only the RapidAPI key Behavioral notes: - all documented operations are `GET` - only one selector family may be used at a time - current and convert support bulk only through one comma-separated `tz`, `ip`, or `offset` selector - bulk selectors accept at most 10 values, and every request URL is limited to 8192 characters - add, diff, calendar, dst, elapsed, timezone, and clock are single-target routes - credentials are extracted in Authorization, X-API-Key, then api_key query order; conflicting values are rejected - the first server is the default API host. The second server is the RapidAPI gateway and can be selected or overridden by SDK configuration - `sign` is available on supported JSON routes and is not supported on `/v1/time/clock`
5
5
 
6
6
  The version of the OpenAPI document: 1.0.0
7
7
 
@@ -14,47 +14,21 @@ require 'date'
14
14
  require 'time'
15
15
 
16
16
  module TimeLogic::Api
17
+ # One geographic zone whose current offset equals the requested offset.
17
18
  class TimezoneMatch
18
- attr_accessor :unix
19
-
20
- attr_accessor :unix_ms
21
-
22
- attr_accessor :utc
23
-
24
- attr_accessor :iso_local
25
-
26
- attr_accessor :rfc2822
27
-
28
- attr_accessor :human
29
-
30
- attr_accessor :day_number
31
-
32
- attr_accessor :day_short
33
-
34
- attr_accessor :day_full
35
-
19
+ # IANA timezone name.
36
20
  attr_accessor :timezone
37
21
 
38
- attr_accessor :formatted
39
-
22
+ # Current UTC offset of the zone in minutes.
40
23
  attr_accessor :offset
41
24
 
25
+ # Whether daylight saving time is currently in effect in the zone.
42
26
  attr_accessor :dst
43
27
 
44
28
  # Attribute mapping from ruby-style variable name to JSON key.
45
29
  def self.attribute_map
46
30
  {
47
- :'unix' => :'unix',
48
- :'unix_ms' => :'unix_ms',
49
- :'utc' => :'utc',
50
- :'iso_local' => :'iso_local',
51
- :'rfc2822' => :'rfc2822',
52
- :'human' => :'human',
53
- :'day_number' => :'day_number',
54
- :'day_short' => :'day_short',
55
- :'day_full' => :'day_full',
56
31
  :'timezone' => :'timezone',
57
- :'formatted' => :'formatted',
58
32
  :'offset' => :'offset',
59
33
  :'dst' => :'dst'
60
34
  }
@@ -68,17 +42,7 @@ module TimeLogic::Api
68
42
  # Attribute type mapping.
69
43
  def self.openapi_types
70
44
  {
71
- :'unix' => :'Integer',
72
- :'unix_ms' => :'Integer',
73
- :'utc' => :'Time',
74
- :'iso_local' => :'String',
75
- :'rfc2822' => :'String',
76
- :'human' => :'String',
77
- :'day_number' => :'Integer',
78
- :'day_short' => :'String',
79
- :'day_full' => :'String',
80
45
  :'timezone' => :'String',
81
- :'formatted' => :'String',
82
46
  :'offset' => :'Integer',
83
47
  :'dst' => :'Boolean'
84
48
  }
@@ -87,17 +51,9 @@ module TimeLogic::Api
87
51
  # List of attributes with nullable: true
88
52
  def self.openapi_nullable
89
53
  Set.new([
90
- :'iso_local',
91
54
  ])
92
55
  end
93
56
 
94
- # List of class defined in allOf (OpenAPI v3)
95
- def self.openapi_all_of
96
- [
97
- :'TimePayload'
98
- ]
99
- end
100
-
101
57
  # Initializes the object
102
58
  # @param [Hash] attributes Model attributes in the form of hash
103
59
  def initialize(attributes = {})
@@ -113,70 +69,12 @@ module TimeLogic::Api
113
69
  h[k.to_sym] = v
114
70
  }
115
71
 
116
- if attributes.key?(:'unix')
117
- self.unix = attributes[:'unix']
118
- else
119
- self.unix = nil
120
- end
121
-
122
- if attributes.key?(:'unix_ms')
123
- self.unix_ms = attributes[:'unix_ms']
124
- else
125
- self.unix_ms = nil
126
- end
127
-
128
- if attributes.key?(:'utc')
129
- self.utc = attributes[:'utc']
130
- else
131
- self.utc = nil
132
- end
133
-
134
- if attributes.key?(:'iso_local')
135
- self.iso_local = attributes[:'iso_local']
136
- else
137
- self.iso_local = nil
138
- end
139
-
140
- if attributes.key?(:'rfc2822')
141
- self.rfc2822 = attributes[:'rfc2822']
142
- else
143
- self.rfc2822 = nil
144
- end
145
-
146
- if attributes.key?(:'human')
147
- self.human = attributes[:'human']
148
- else
149
- self.human = nil
150
- end
151
-
152
- if attributes.key?(:'day_number')
153
- self.day_number = attributes[:'day_number']
154
- else
155
- self.day_number = nil
156
- end
157
-
158
- if attributes.key?(:'day_short')
159
- self.day_short = attributes[:'day_short']
160
- else
161
- self.day_short = nil
162
- end
163
-
164
- if attributes.key?(:'day_full')
165
- self.day_full = attributes[:'day_full']
166
- else
167
- self.day_full = nil
168
- end
169
-
170
72
  if attributes.key?(:'timezone')
171
73
  self.timezone = attributes[:'timezone']
172
74
  else
173
75
  self.timezone = nil
174
76
  end
175
77
 
176
- if attributes.key?(:'formatted')
177
- self.formatted = attributes[:'formatted']
178
- end
179
-
180
78
  if attributes.key?(:'offset')
181
79
  self.offset = attributes[:'offset']
182
80
  else
@@ -195,46 +93,6 @@ module TimeLogic::Api
195
93
  def list_invalid_properties
196
94
  warn '[DEPRECATED] the `list_invalid_properties` method is obsolete'
197
95
  invalid_properties = Array.new
198
- if @unix.nil?
199
- invalid_properties.push('invalid value for "unix", unix cannot be nil.')
200
- end
201
-
202
- if @unix_ms.nil?
203
- invalid_properties.push('invalid value for "unix_ms", unix_ms cannot be nil.')
204
- end
205
-
206
- if @utc.nil?
207
- invalid_properties.push('invalid value for "utc", utc cannot be nil.')
208
- end
209
-
210
- if @rfc2822.nil?
211
- invalid_properties.push('invalid value for "rfc2822", rfc2822 cannot be nil.')
212
- end
213
-
214
- if @human.nil?
215
- invalid_properties.push('invalid value for "human", human cannot be nil.')
216
- end
217
-
218
- if @day_number.nil?
219
- invalid_properties.push('invalid value for "day_number", day_number cannot be nil.')
220
- end
221
-
222
- if @day_number > 7
223
- invalid_properties.push('invalid value for "day_number", must be smaller than or equal to 7.')
224
- end
225
-
226
- if @day_number < 1
227
- invalid_properties.push('invalid value for "day_number", must be greater than or equal to 1.')
228
- end
229
-
230
- if @day_short.nil?
231
- invalid_properties.push('invalid value for "day_short", day_short cannot be nil.')
232
- end
233
-
234
- if @day_full.nil?
235
- invalid_properties.push('invalid value for "day_full", day_full cannot be nil.')
236
- end
237
-
238
96
  if @timezone.nil?
239
97
  invalid_properties.push('invalid value for "timezone", timezone cannot be nil.')
240
98
  end
@@ -254,56 +112,18 @@ module TimeLogic::Api
254
112
  # @return true if the model is valid
255
113
  def valid?
256
114
  warn '[DEPRECATED] the `valid?` method is obsolete'
257
- return false if @unix.nil?
258
- return false if @unix_ms.nil?
259
- return false if @utc.nil?
260
- return false if @rfc2822.nil?
261
- return false if @human.nil?
262
- return false if @day_number.nil?
263
- return false if @day_number > 7
264
- return false if @day_number < 1
265
- return false if @day_short.nil?
266
- return false if @day_full.nil?
267
115
  return false if @timezone.nil?
268
116
  return false if @offset.nil?
269
117
  return false if @dst.nil?
270
118
  true
271
119
  end
272
120
 
273
- # Custom attribute writer method with validation
274
- # @param [Object] day_number Value to be assigned
275
- def day_number=(day_number)
276
- if day_number.nil?
277
- fail ArgumentError, 'day_number cannot be nil'
278
- end
279
-
280
- if day_number > 7
281
- fail ArgumentError, 'invalid value for "day_number", must be smaller than or equal to 7.'
282
- end
283
-
284
- if day_number < 1
285
- fail ArgumentError, 'invalid value for "day_number", must be greater than or equal to 1.'
286
- end
287
-
288
- @day_number = day_number
289
- end
290
-
291
121
  # Checks equality by comparing each attribute.
292
122
  # @param [Object] Object to be compared
293
123
  def ==(o)
294
124
  return true if self.equal?(o)
295
125
  self.class == o.class &&
296
- unix == o.unix &&
297
- unix_ms == o.unix_ms &&
298
- utc == o.utc &&
299
- iso_local == o.iso_local &&
300
- rfc2822 == o.rfc2822 &&
301
- human == o.human &&
302
- day_number == o.day_number &&
303
- day_short == o.day_short &&
304
- day_full == o.day_full &&
305
126
  timezone == o.timezone &&
306
- formatted == o.formatted &&
307
127
  offset == o.offset &&
308
128
  dst == o.dst
309
129
  end
@@ -317,7 +137,7 @@ module TimeLogic::Api
317
137
  # Calculates hash code according to all attributes.
318
138
  # @return [Integer] Hash code
319
139
  def hash
320
- [unix, unix_ms, utc, iso_local, rfc2822, human, day_number, day_short, day_full, timezone, formatted, offset, dst].hash
140
+ [timezone, offset, dst].hash
321
141
  end
322
142
 
323
143
  # Builds the object from hash
@@ -1,7 +1,7 @@
1
1
  =begin
2
2
  #TimeLogic API | A World Time API
3
3
 
4
- # Official public API contract for TimeLogic API.
4
+ #Direct API contract for client integrations. It documents authentication, endpoint parameters, response shapes, public signing keys, and signed JSON controls. Authentication: - the API supports `Authorization: Bearer <token>`, `X-API-Key`, and `api_key` query credentials - RapidAPI access uses `X-RapidAPI-Key` and `X-RapidAPI-Host`; the SDKs expose this as a `rapidApi` transport option that accepts only the RapidAPI key Behavioral notes: - all documented operations are `GET` - only one selector family may be used at a time - current and convert support bulk only through one comma-separated `tz`, `ip`, or `offset` selector - bulk selectors accept at most 10 values, and every request URL is limited to 8192 characters - add, diff, calendar, dst, elapsed, timezone, and clock are single-target routes - credentials are extracted in Authorization, X-API-Key, then api_key query order; conflicting values are rejected - the first server is the default API host. The second server is the RapidAPI gateway and can be selected or overridden by SDK configuration - `sign` is available on supported JSON routes and is not supported on `/v1/time/clock`
5
5
 
6
6
  The version of the OpenAPI document: 1.0.0
7
7
 
@@ -14,8 +14,8 @@ require 'date'
14
14
  require 'time'
15
15
 
16
16
  module TimeLogic::Api
17
- # Offset-based timezone lookup result with `matching_zones` for the current request-time offset match.
18
- class TimezoneOffsetResponse
17
+ # Timezone lookup result. It is the time payload for the resolved target plus `offset` (minutes east of UTC) and `dst`. Every selector returns this one shape. `tz`, `ip`, `lat` + `lon` and `auto_tz` lookups resolve a named zone, so `timezone` is set and `matching_zones` is absent. `offset` lookups return `timezone=null` and add `matching_zones`, the geographic zones whose current offset equals the requested one.
18
+ class TimezoneResponse
19
19
  attr_accessor :unix
20
20
 
21
21
  attr_accessor :unix_ms
@@ -28,6 +28,7 @@ module TimeLogic::Api
28
28
 
29
29
  attr_accessor :human
30
30
 
31
+ # Day of the week counted from Sunday (1 = Sunday, 2 = Monday ... 7 = Saturday).
31
32
  attr_accessor :day_number
32
33
 
33
34
  attr_accessor :day_short
@@ -38,10 +39,16 @@ module TimeLogic::Api
38
39
 
39
40
  attr_accessor :formatted
40
41
 
42
+ # UTC offset of the resolved target in minutes, for example `-240` for UTC-04:00.
41
43
  attr_accessor :offset
42
44
 
45
+ # Whether daylight saving time is in effect for the resolved target. Always `false` for `offset` lookups.
43
46
  attr_accessor :dst
44
47
 
48
+ # Exact UTC offset in seconds. Present only when the resolved offset has a seconds part (historical local mean time); no zone has one at the current instant, so lookups today do not include it.
49
+ attr_accessor :offset_seconds
50
+
51
+ # Present only for `offset=` lookups. The geographic zones whose current offset equals the requested one. `UTC` and `Etc/*` zones are intentionally excluded; use `tz=UTC` for UTC.
45
52
  attr_accessor :matching_zones
46
53
 
47
54
  # Attribute mapping from ruby-style variable name to JSON key.
@@ -60,6 +67,7 @@ module TimeLogic::Api
60
67
  :'formatted' => :'formatted',
61
68
  :'offset' => :'offset',
62
69
  :'dst' => :'dst',
70
+ :'offset_seconds' => :'offset_seconds',
63
71
  :'matching_zones' => :'matching_zones'
64
72
  }
65
73
  end
@@ -85,6 +93,7 @@ module TimeLogic::Api
85
93
  :'formatted' => :'String',
86
94
  :'offset' => :'Integer',
87
95
  :'dst' => :'Boolean',
96
+ :'offset_seconds' => :'Integer',
88
97
  :'matching_zones' => :'Array<TimezoneMatch>'
89
98
  }
90
99
  end
@@ -100,7 +109,7 @@ module TimeLogic::Api
100
109
  # List of class defined in allOf (OpenAPI v3)
101
110
  def self.openapi_all_of
102
111
  [
103
- :'TimezoneResolvedResponse'
112
+ :'TimePayload'
104
113
  ]
105
114
  end
106
115
 
@@ -108,13 +117,13 @@ module TimeLogic::Api
108
117
  # @param [Hash] attributes Model attributes in the form of hash
109
118
  def initialize(attributes = {})
110
119
  if (!attributes.is_a?(Hash))
111
- fail ArgumentError, "The input argument (attributes) must be a hash in `TimeLogic::Api::TimezoneOffsetResponse` initialize method"
120
+ fail ArgumentError, "The input argument (attributes) must be a hash in `TimeLogic::Api::TimezoneResponse` initialize method"
112
121
  end
113
122
 
114
123
  # check to see if the attribute exists and convert string to symbol for hash key
115
124
  attributes = attributes.each_with_object({}) { |(k, v), h|
116
125
  if (!self.class.attribute_map.key?(k.to_sym))
117
- fail ArgumentError, "`#{k}` is not a valid attribute in `TimeLogic::Api::TimezoneOffsetResponse`. Please check the name to make sure it's valid. List of attributes: " + self.class.attribute_map.keys.inspect
126
+ fail ArgumentError, "`#{k}` is not a valid attribute in `TimeLogic::Api::TimezoneResponse`. Please check the name to make sure it's valid. List of attributes: " + self.class.attribute_map.keys.inspect
118
127
  end
119
128
  h[k.to_sym] = v
120
129
  }
@@ -195,12 +204,14 @@ module TimeLogic::Api
195
204
  self.dst = nil
196
205
  end
197
206
 
207
+ if attributes.key?(:'offset_seconds')
208
+ self.offset_seconds = attributes[:'offset_seconds']
209
+ end
210
+
198
211
  if attributes.key?(:'matching_zones')
199
212
  if (value = attributes[:'matching_zones']).is_a?(Array)
200
213
  self.matching_zones = value
201
214
  end
202
- else
203
- self.matching_zones = nil
204
215
  end
205
216
  end
206
217
 
@@ -257,10 +268,6 @@ module TimeLogic::Api
257
268
  invalid_properties.push('invalid value for "dst", dst cannot be nil.')
258
269
  end
259
270
 
260
- if @matching_zones.nil?
261
- invalid_properties.push('invalid value for "matching_zones", matching_zones cannot be nil.')
262
- end
263
-
264
271
  invalid_properties
265
272
  end
266
273
 
@@ -280,7 +287,6 @@ module TimeLogic::Api
280
287
  return false if @day_full.nil?
281
288
  return false if @offset.nil?
282
289
  return false if @dst.nil?
283
- return false if @matching_zones.nil?
284
290
  true
285
291
  end
286
292
 
@@ -320,6 +326,7 @@ module TimeLogic::Api
320
326
  formatted == o.formatted &&
321
327
  offset == o.offset &&
322
328
  dst == o.dst &&
329
+ offset_seconds == o.offset_seconds &&
323
330
  matching_zones == o.matching_zones
324
331
  end
325
332
 
@@ -332,7 +339,7 @@ module TimeLogic::Api
332
339
  # Calculates hash code according to all attributes.
333
340
  # @return [Integer] Hash code
334
341
  def hash
335
- [unix, unix_ms, utc, iso_local, rfc2822, human, day_number, day_short, day_full, timezone, formatted, offset, dst, matching_zones].hash
342
+ [unix, unix_ms, utc, iso_local, rfc2822, human, day_number, day_short, day_full, timezone, formatted, offset, dst, offset_seconds, matching_zones].hash
336
343
  end
337
344
 
338
345
  # Builds the object from hash
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TimeLogic
4
4
  module Api
5
- VERSION = '1.0.3'
5
+ VERSION = '2.0.0'
6
6
  end
7
7
  end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ # WorldTimeAPI-compatible client for the TimeLogic API /api/* routes.
4
+ #
5
+ # Contract: openapi/openapi-worldtimeapi.yaml. The routes answer the same paths, field
6
+ # names and plain-text variants as worldtimeapi.org; a TimeLogic API key is required and
7
+ # is sent as the X-API-Key header. This module uses Ruby's standard library only and does
8
+ # not use or change the generated /v1 ApiClient, Configuration or TransportConfiguration.
9
+ #
10
+ # client = TimeLogic::Api::WorldTimeApi::Client.new(api_key: ENV.fetch('TIMELOGIC_API_KEY'))
11
+ # now = client.get_timezone('Europe/London')
12
+ # puts now.datetime
13
+
14
+ require 'json'
15
+ require 'net/http'
16
+ require 'uri'
17
+
18
+ module TimeLogic
19
+ module Api
20
+ module WorldTimeApi
21
+ DEFAULT_BASE_URL = 'https://api.timelogicapi.com'
22
+
23
+ FIELDS = %i[
24
+ abbreviation client_ip datetime day_of_week day_of_year dst dst_from dst_offset
25
+ dst_until raw_offset timezone unixtime utc_datetime utc_offset week_number
26
+ ].freeze
27
+
28
+ # Time object with the worldtimeapi.org field names and types.
29
+ DateTime = Struct.new(*FIELDS, keyword_init: true) do
30
+ def self.from_hash(hash)
31
+ new(**FIELDS.to_h { |field| [field, hash[field.to_s]] })
32
+ end
33
+ end
34
+
35
+ # HTTP answer, including the X-TimeLogic-Signature* headers when signing is enabled.
36
+ RawResponse = Struct.new(:status, :headers, :body, keyword_init: true)
37
+
38
+ # Raised for any non-2xx answer. +error+ is the worldtimeapi.org-style message (for
39
+ # example "unknown location") or the TimeLogic message for authentication and quota errors.
40
+ class Error < StandardError
41
+ attr_reader :status, :error, :code, :body
42
+
43
+ def initialize(status, error, code, body)
44
+ super("WorldTimeAPI-compatible request failed (#{status}): #{error}")
45
+ @status = status
46
+ @error = error
47
+ @code = code
48
+ @body = body
49
+ end
50
+
51
+ def unknown_location?
52
+ status == 404 && error == 'unknown location'
53
+ end
54
+
55
+ def malformed_ip?
56
+ status == 404 && error == 'malformed ip'
57
+ end
58
+ end
59
+
60
+ # The 404 {"error":"unknown location"} answer.
61
+ class UnknownLocationError < Error; end
62
+
63
+ def self.error_from_response(status, body)
64
+ message = body.to_s.strip
65
+ code = nil
66
+ if message.start_with?('Error:')
67
+ message = message.delete_prefix('Error:').strip
68
+ else
69
+ begin
70
+ parsed = JSON.parse(body.to_s)
71
+ if parsed.is_a?(Hash) && parsed.key?('error')
72
+ if parsed['error'].is_a?(String)
73
+ message = parsed['error']
74
+ elsif parsed['error'].is_a?(Hash)
75
+ message = parsed['error']['message'].to_s
76
+ code = parsed['error']['code']&.to_s
77
+ end
78
+ end
79
+ rescue JSON::ParserError
80
+ # keep the raw body as the message
81
+ end
82
+ end
83
+ klass = status == 404 && message == 'unknown location' ? UnknownLocationError : Error
84
+ klass.new(status, message, code, body.to_s)
85
+ end
86
+
87
+ # Path for "Europe/London", "America/Argentina/Salta" or a one-word zone such as "UTC".
88
+ def self.zone_path(timezone, text = false)
89
+ segments = timezone.to_s.split('/').reject(&:empty?)
90
+ unless (1..3).cover?(segments.length)
91
+ raise ArgumentError, 'timezone must have one to three segments, for example "Europe/London"'
92
+ end
93
+
94
+ "/api/timezone/#{segments.map { |s| URI.encode_www_form_component(s).gsub('+', '%20') }.join('/')}#{text ? '.txt' : ''}"
95
+ end
96
+
97
+ def self.ip_path(ip = nil, text = false)
98
+ base = ip.nil? || ip.to_s.empty? ? '/api/ip' : "/api/ip/#{URI.encode_www_form_component(ip.to_s)}"
99
+ "#{base}#{text ? '.txt' : ''}"
100
+ end
101
+
102
+ # Client for the WorldTimeAPI-compatible routes.
103
+ class Client
104
+ # transport: optional callable (url, headers) -> RawResponse, used by tests.
105
+ def initialize(api_key:, base_url: DEFAULT_BASE_URL, sign: false, timeout: 30, transport: nil)
106
+ raise ArgumentError, 'api_key is required' if api_key.nil? || api_key.to_s.empty?
107
+
108
+ @api_key = api_key.to_s
109
+ @base_url = base_url.to_s.sub(%r{/+\z}, '')
110
+ @sign = sign
111
+ @timeout = timeout
112
+ @transport = transport
113
+ end
114
+
115
+ # GET /api/timezone
116
+ def list_timezones
117
+ get_json('/api/timezone')
118
+ end
119
+
120
+ # GET /api/timezone.txt
121
+ def list_timezones_text
122
+ get_text('/api/timezone.txt')
123
+ end
124
+
125
+ # GET /api/timezone/{area}
126
+ def list_timezones_in_area(area)
127
+ value = get_json(WorldTimeApi.zone_path(area))
128
+ raise Error.new(200, "#{area} is a timezone, not an area; use get_timezone", nil, JSON.generate(value)) unless value.is_a?(Array)
129
+
130
+ value
131
+ end
132
+
133
+ # GET /api/timezone/{area}.txt
134
+ def list_timezones_in_area_text(area)
135
+ get_text(WorldTimeApi.zone_path(area, true))
136
+ end
137
+
138
+ # GET /api/timezone/{area}/{location}[/{region}], or a one-word zone such as UTC.
139
+ def get_timezone(timezone)
140
+ DateTime.from_hash(get_json(WorldTimeApi.zone_path(timezone)))
141
+ end
142
+
143
+ def get_timezone_text(timezone)
144
+ get_text(WorldTimeApi.zone_path(timezone, true))
145
+ end
146
+
147
+ # GET /api/ip (the caller's address) or /api/ip/{ip}.
148
+ def get_ip(ip = nil)
149
+ DateTime.from_hash(get_json(WorldTimeApi.ip_path(ip)))
150
+ end
151
+
152
+ def get_ip_text(ip = nil)
153
+ get_text(WorldTimeApi.ip_path(ip, true))
154
+ end
155
+
156
+ # GET any /api/* path and return status, headers and body without raising.
157
+ def request_raw(path)
158
+ url = "#{@base_url}#{path}#{@sign ? '?sign=true' : ''}"
159
+ headers = { 'X-API-Key' => @api_key, 'Accept' => path.end_with?('.txt') ? 'text/plain' : 'application/json' }
160
+ return @transport.call(url, headers) if @transport
161
+
162
+ uri = URI.parse(url)
163
+ http = Net::HTTP.new(uri.host, uri.port)
164
+ http.use_ssl = uri.scheme == 'https'
165
+ http.open_timeout = @timeout
166
+ http.read_timeout = @timeout
167
+ request = Net::HTTP::Get.new(uri.request_uri)
168
+ headers.each { |name, value| request[name] = value }
169
+ response = http.request(request)
170
+ RawResponse.new(
171
+ status: response.code.to_i,
172
+ headers: response.each_header.to_h { |name, value| [name.downcase, value] },
173
+ body: response.body.to_s.dup.force_encoding('UTF-8')
174
+ )
175
+ end
176
+
177
+ private
178
+
179
+ def get_text(path)
180
+ raw = request_raw(path)
181
+ raise WorldTimeApi.error_from_response(raw.status, raw.body) unless (200..299).cover?(raw.status)
182
+
183
+ raw.body
184
+ end
185
+
186
+ def get_json(path)
187
+ JSON.parse(get_text(path))
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
data/lib/timelogic-api.rb CHANGED
@@ -4,7 +4,7 @@ end
4
4
  =begin
5
5
  #TimeLogic API | A World Time API
6
6
 
7
- # Official public API contract for TimeLogic API.
7
+ #Direct API contract for client integrations. It documents authentication, endpoint parameters, response shapes, public signing keys, and signed JSON controls. Authentication: - the API supports `Authorization: Bearer <token>`, `X-API-Key`, and `api_key` query credentials - RapidAPI access uses `X-RapidAPI-Key` and `X-RapidAPI-Host`; the SDKs expose this as a `rapidApi` transport option that accepts only the RapidAPI key Behavioral notes: - all documented operations are `GET` - only one selector family may be used at a time - current and convert support bulk only through one comma-separated `tz`, `ip`, or `offset` selector - bulk selectors accept at most 10 values, and every request URL is limited to 8192 characters - add, diff, calendar, dst, elapsed, timezone, and clock are single-target routes - credentials are extracted in Authorization, X-API-Key, then api_key query order; conflicting values are rejected - the first server is the default API host. The second server is the RapidAPI gateway and can be selected or overridden by SDK configuration - `sign` is available on supported JSON routes and is not supported on `/v1/time/clock`
8
8
 
9
9
  The version of the OpenAPI document: 1.0.0
10
10
 
@@ -30,14 +30,12 @@ require 'timelogic-api/models/dst_response'
30
30
  require 'timelogic-api/models/elapsed_response'
31
31
  require 'timelogic-api/models/error_response'
32
32
  require 'timelogic-api/models/get_current_time200_response'
33
- require 'timelogic-api/models/get_timezone200_response'
34
33
  require 'timelogic-api/models/jwk_key'
35
34
  require 'timelogic-api/models/jwks_response'
36
35
  require 'timelogic-api/models/time_payload'
37
36
  require 'timelogic-api/models/time_payload_bulk_item'
38
37
  require 'timelogic-api/models/timezone_match'
39
- require 'timelogic-api/models/timezone_offset_response'
40
- require 'timelogic-api/models/timezone_resolved_response'
38
+ require 'timelogic-api/models/timezone_response'
41
39
  require 'timelogic-api/models/time_payload_bulk_response'
42
40
 
43
41
  # APIs
@@ -61,3 +59,4 @@ module TimeLogic::Api
61
59
  end
62
60
  end
63
61
  end
62
+ require 'timelogic-api/world_time_api'