timelogic-api 1.0.2 → 1.1.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +293 -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/TimeApi.md +151 -151
  9. data/docs/TimePayload.md +1 -1
  10. data/docs/TimezoneMatch.md +1 -1
  11. data/docs/TimezoneOffsetResponse.md +2 -2
  12. data/docs/TimezoneResolvedResponse.md +1 -1
  13. data/lib/timelogic-api/api/time_api.rb +169 -159
  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/get_timezone200_response.rb +1 -1
  28. data/lib/timelogic-api/models/jwk_key.rb +1 -1
  29. data/lib/timelogic-api/models/jwks_response.rb +1 -1
  30. data/lib/timelogic-api/models/time_payload.rb +2 -1
  31. data/lib/timelogic-api/models/time_payload_bulk_item.rb +1 -1
  32. data/lib/timelogic-api/models/time_payload_bulk_response.rb +1 -1
  33. data/lib/timelogic-api/models/timezone_match.rb +2 -1
  34. data/lib/timelogic-api/models/timezone_offset_response.rb +3 -1
  35. data/lib/timelogic-api/models/timezone_resolved_response.rb +2 -1
  36. data/lib/timelogic-api/version.rb +1 -1
  37. data/lib/timelogic-api/world_time_api.rb +192 -0
  38. data/lib/timelogic-api.rb +2 -1
  39. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99f78307654b0f19808cfdcfa5c80a130330b13ff1b270efb41ac88a7be659d2
4
- data.tar.gz: 6c118b0e59adf92aab76beaa359fbc3a3289267e62d7105676fdea7420800d18
3
+ metadata.gz: 5c2aaabeb34292d10a3fa9a740b47b680a83c55d8b804b18e8845e494fa9febb
4
+ data.tar.gz: 85714922fc04495aae65793e4cd412211e2a6e7284bdb96db7c180a9e44221ae
5
5
  SHA512:
6
- metadata.gz: ca7a990caf505935e66a5f094ae6000ab7a37e206f535f72a456db9319f16527b9f8db602cdd422c527656a5ba9e5b7037a9f2aa99498546f4ee0c47c9444715
7
- data.tar.gz: 29e6b7c3d24283a1d5950cc666499d783f48ff7440c433ccc9d9c24eeed247a00d22f63a03becedaf272631a2541bf5bb4125780e3dd5ec779814661726f715b
6
+ metadata.gz: d831cf4f316848f67d326996d74e191c6a5c50ee76e2f59ce758c15605af66a830b2c93d20ecf6142f91872731a37657206b253926405c3f7d46252b0ad38666
7
+ data.tar.gz: 81edd337490edf0d16218de6c8bc4ccea5fda264431b642aff380c8b4f5ff1020bf446a6a38a82d1bd2e4ab3476aeeeab823b1f876c4424cf516b7265c3d6d68
data/README.md CHANGED
@@ -1,188 +1,348 @@
1
- # TimeLogic API | A World Time API
1
+ # TimeLogic API SDK for Ruby
2
2
 
3
- Official Ruby SDK for TimeLogic API, providing world time, timezone, calendar, and signed response operations.
3
+ Official Ruby client for [TimeLogic API](https://api.timelogicapi.com): current time, time zone lookups, conversions, date arithmetic, business-day counts, DST status, calendars, embeddable clocks and signed responses. The package contains two clients:
4
4
 
5
- ## Installation
5
+ - the `/v1` client, generated from the [TimeLogic API contract](https://github.com/TimeLogic-API-LLC/timelogic-api-sdks/blob/main/openapi/openapi.yaml), with direct and RapidAPI transports;
6
+ - a [WorldTimeAPI-compatible client](#worldtimeapi-compatible-client) for the `/api/timezone` and `/api/ip` routes, which use worldtimeapi.org's paths, fields and plain-text format.
6
7
 
7
- ### Build a gem
8
+ Package: [`timelogic-api`](https://rubygems.org/gems/timelogic-api), version 1.1.0. Changes are listed in the [changelog](https://github.com/TimeLogic-API-LLC/timelogic-api-sdks/blob/main/CHANGELOG.md).
8
9
 
9
- To build the Ruby code into a gem:
10
+ ## Install
10
11
 
11
- ```shell
12
- gem build timelogic-api.gemspec
12
+ ```bash
13
+ gem install timelogic-api -v 1.1.0
13
14
  ```
14
15
 
15
- Then either install the gem locally:
16
+ Or in a Gemfile: `gem 'timelogic-api', '1.1.0'`. Requires Ruby 2.7+.
16
17
 
17
- ```shell
18
- gem install ./timelogic-api-1.0.2.gem
19
- ```
18
+ ## Quick start
20
19
 
21
- (for development, run `gem install --dev ./timelogic-api-1.0.2.gem` to install the development dependencies)
20
+ Get an API key, export it as `TIMELOGIC_API_KEY`, and run:
22
21
 
23
- or publish the gem to a gem hosting service, e.g. [RubyGems](https://rubygems.org/).
22
+ ```ruby
23
+ require 'timelogic-api'
24
24
 
25
- Finally add this to the Gemfile:
25
+ config = TimeLogic::Api::TransportConfiguration.build(api_key: ENV.fetch('TIMELOGIC_API_KEY'))
26
+ time = TimeLogic::Api::TimeApi.new(TimeLogic::Api::ApiClient.new(config))
26
27
 
27
- gem 'timelogic-api', '~> 1.0.2'
28
+ now = time.get_current_time(tz: 'America/New_York')
29
+ puts "#{now.iso_local} #{now.timezone}"
30
+ ```
28
31
 
29
- ### Install from Git
32
+ ## Configuration
30
33
 
31
- If the Ruby gem is hosted at a git repository: https://github.com/TimeLogic-API-LLC/timelogic-api-sdks.git, then add the following in the Gemfile:
34
+ The transport helper sets the host and credentials for the `/v1` client. Pass a TimeLogic key for the direct API, or a RapidAPI key with the RapidAPI option; the SDK then sends `X-RapidAPI-Key` and `X-RapidAPI-Host` (default host `timelogic-api-world-time-timezones-time-calculations.p.rapidapi.com`, overridable). Keep keys in environment variables or a secret store, not in source control.
32
35
 
33
- gem 'timelogic-api', :git => 'https://github.com/TimeLogic-API-LLC/timelogic-api-sdks.git'
36
+ ```ruby
37
+ require 'timelogic-api'
38
+
39
+ # Direct API (default host https://api.timelogicapi.com, sends Authorization: Bearer <key>)
40
+ direct = TimeLogic::Api::TransportConfiguration.build(api_key: ENV.fetch('TIMELOGIC_API_KEY'))
34
41
 
35
- ### Include the Ruby code directly
42
+ # RapidAPI: pass only your RapidAPI key; the SDK sets X-RapidAPI-Key and X-RapidAPI-Host
43
+ rapid = TimeLogic::Api::TransportConfiguration.build(api_key: ENV.fetch('RAPIDAPI_KEY'), rapid_api: true)
36
44
 
37
- Include the Ruby code directly using `-I` as follows:
45
+ # A different host (for example a compatible gateway or a test server)
46
+ custom = TimeLogic::Api::TransportConfiguration.build(api_key: ENV.fetch('TIMELOGIC_API_KEY'), base_url: 'https://your-compatible-gateway.example')
38
47
 
39
- ```shell
40
- ruby -Ilib script.rb
48
+ # Request timeout in seconds (default 0, meaning no timeout). The SDK does not retry.
49
+ direct.timeout = 10
41
50
  ```
42
51
 
43
- ## Getting Started
52
+ ## Operations
53
+
54
+ | Operation | Endpoint | Method |
55
+ | --- | --- | --- |
56
+ | Current time | `GET /v1/time/current` | `TimeApi#get_current_time` |
57
+ | Convert | `GET /v1/time/convert` | `TimeApi#convert_time` |
58
+ | Add | `GET /v1/time/add` | `TimeApi#add_time` |
59
+ | Difference | `GET /v1/time/diff` | `TimeApi#diff_time` |
60
+ | Elapsed | `GET /v1/time/elapsed` | `TimeApi#get_elapsed` |
61
+ | Timezone | `GET /v1/timezone` | `TimeApi#get_timezone` |
62
+ | Daylight saving | `GET /v1/time/dst` | `TimeApi#get_dst` |
63
+ | Calendar | `GET /v1/time/calendar` | `TimeApi#get_calendar` |
64
+ | Clock | `GET /v1/time/clock` | `TimeApi#get_clock` |
65
+ | Signing keys | `GET /.well-known/time-api-public-key` | `UtilityApi#get_public_signing_key` |
66
+
67
+ Operations take an options hash named after the query parameters in snake_case; `next` is a Ruby keyword, so `get_dst` takes `_next:`. Each operation has a `…_with_http_info` variant that returns `[data, status_code, headers]`. API errors raise `TimeLogic::Api::ApiException` with `code`, `response_headers` and `response_body`. Parameters, response fields and errors for every operation: [How the API works](#how-the-api-works). Generated reference: [TimeApi](https://github.com/TimeLogic-API-LLC/timelogic-api-sdks/blob/main/packages/ruby/docs/TimeApi.md), [UtilityApi](https://github.com/TimeLogic-API-LLC/timelogic-api-sdks/blob/main/packages/ruby/docs/UtilityApi.md) and the models in the same folder.
44
68
 
45
- Please follow the [installation](#installation) procedure and then run the following code:
69
+ ### Examples
46
70
 
47
71
  ```ruby
48
- # Load the gem
49
72
  require 'timelogic-api'
50
73
 
51
- # Setup authorization
52
- TimeLogic::Api.configure do |config|
53
- # Configure API key authorization: apiKeyHeader
54
- config.api_key['X-API-Key'] = 'YOUR API KEY'
55
- # Uncomment the following line to set a prefix for the API key, e.g. 'Bearer' (defaults to nil)
56
- # config.api_key_prefix['X-API-Key'] = 'Bearer'
57
-
58
- # Configure Bearer authorization (TimeLogic API key): bearerAuth
59
- config.access_token = 'YOUR_BEARER_TOKEN'
60
- # Configure a proc to get access tokens in lieu of the static access_token configuration
61
- config.access_token_getter = -> { 'YOUR TOKEN GETTER PROC' }
62
-
63
- # Configure API key authorization: apiKeyQuery
64
- config.api_key['api_key'] = 'YOUR API KEY'
65
- # Uncomment the following line to set a prefix for the API key, e.g. 'Bearer' (defaults to nil)
66
- # config.api_key_prefix['api_key'] = 'Bearer'
67
-
68
- # Configure API key authorization: rapidApiKey
69
- config.api_key['X-RapidAPI-Key'] = 'YOUR API KEY'
70
- # Uncomment the following line to set a prefix for the API key, e.g. 'Bearer' (defaults to nil)
71
- # config.api_key_prefix['X-RapidAPI-Key'] = 'Bearer'
72
-
73
- # Configure API key authorization: rapidApiHost
74
- config.api_key['X-RapidAPI-Host'] = 'YOUR API KEY'
75
- # Uncomment the following line to set a prefix for the API key, e.g. 'Bearer' (defaults to nil)
76
- # config.api_key_prefix['X-RapidAPI-Host'] = 'Bearer'
74
+ client = TimeLogic::Api::ApiClient.new(TimeLogic::Api::TransportConfiguration.build(api_key: ENV.fetch('TIMELOGIC_API_KEY')))
75
+ time = TimeLogic::Api::TimeApi.new(client)
76
+
77
+ # Convert a New York wall-clock time to Tokyo
78
+ tokyo = time.convert_time(iso: '2026-03-01T09:00:00', source_tz: 'America/New_York', tz: 'Asia/Tokyo')
79
+
80
+ # Add 1 day and 2 hours, shown in New York time
81
+ later = time.add_time(iso: '2026-03-07T12:00:00Z', days: 1, hours: 2, tz: 'America/New_York')
82
+
83
+ # Duration and business days between two instants (Zurich holiday calendar)
84
+ diff = time.diff_time('iso=2026-01-02T00:00:00Z', 'iso=2026-04-30T00:00:00Z', business_days: true, holiday_country: 'CH-ZH')
85
+ puts "#{diff.days} #{diff.direction} #{diff.business_days}" # 118 forward 82
86
+
87
+ # Time left until a deadline
88
+ left = time.get_elapsed(iso: '2027-01-01T00:00:00Z')
89
+
90
+ # Zone details, DST status with the next transition, ISO week number
91
+ berlin = time.get_timezone(tz: 'Europe/Berlin')
92
+ dst = time.get_dst(tz: 'America/New_York', _next: true) # `next` is a Ruby keyword, so the option is _next
93
+ calendar = time.get_calendar(tz: 'Europe/London', week: true)
94
+
95
+ # Embeddable HTML clock
96
+ html = time.get_clock('digital-dashboard', tz: 'Europe/London')
97
+
98
+ # Public signing keys (no API key needed)
99
+ keys = TimeLogic::Api::UtilityApi.new(client).get_public_signing_key
100
+ ```
101
+
102
+ ## Errors, headers and signed responses
103
+
104
+ ```ruby
105
+ require 'timelogic-api'
106
+
107
+ time = TimeLogic::Api::TimeApi.new(TimeLogic::Api::ApiClient.new(TimeLogic::Api::TransportConfiguration.build(api_key: ENV.fetch('TIMELOGIC_API_KEY'))))
108
+
109
+ begin
110
+ time.get_current_time(tz: 'Bad/Zone')
111
+ rescue TimeLogic::Api::ApiException => e
112
+ puts "#{e.code} #{e.response_body}"
113
+ # 400 {"error":{"code":"INVALID_PARAMETER","message":"Invalid timezone.",...},"request_id":"..."}
77
114
  end
78
115
 
79
- api_instance = TimeLogic::Api::TimeApi.new
80
- opts = {
81
- unix: 1711300000, # Integer |
82
- unix_ms: 1711300000000, # Integer |
83
- iso: '2024-03-24T15:00:00', # String | ISO-8601 timestamp. To supply a local wall-clock time without an explicit offset, pair it with one of `source_tz=Area/City`, `source_ip=...`, `source_lat=...&source_lon=...`, or `source_offset=±HH:MM`.
84
- source_tz: 'America/New_York', # String | Used only with `iso=...` when the ISO value has no explicit offset.
85
- source_ip: '8.8.8.8', # String | Used only with `iso=...` when the ISO value has no explicit offset. Resolves that local wall-clock time through the timezone mapped from this IP address.
86
- source_lat: 40.7128, # Float | Used only with `iso=...` when the ISO value has no explicit offset. Provide together with `source_lon` to resolve that local wall-clock time through the timezone mapped from these coordinates.
87
- source_lon: -74.006, # Float | Used only with `iso=...` when the ISO value has no explicit offset. Provide together with `source_lat`.
88
- source_offset: '-05:00', # String | Used only with `iso=...` when the ISO value has no explicit offset. Interprets that local wall-clock time at this fixed UTC offset.
89
- seconds: 30, # Integer |
90
- minutes: 15, # Integer |
91
- hours: 2, # Integer |
92
- days: 7, # Integer |
93
- tz: 'America/New_York', # String | IANA timezone name. On bulk-capable routes, a comma-separated list enables bulk mode.
94
- ip: '8.8.8.8', # String | IP address. On bulk-capable routes, a comma-separated list enables bulk mode.
95
- lat: 40.7128, # Float | Latitude. Must be provided together with `lon`.
96
- lon: -74.006, # Float | Longitude. Must be provided together with `lat`.
97
- offset: '-04:00', # String | Fixed UTC offset in `+HH:MM` or `-HH:MM` format. On bulk-capable routes, a comma-separated list enables bulk mode.
98
- auto_tz: true, # Boolean | Set to `true` to resolve using the caller IP from Cloudflare headers.
99
- utc: true, # Boolean | Set to `true` to force UTC on routes that support it.
100
- format: '%Y-%m-%d %H:%M:%S', # String | Custom date/time format template using supported `strftime`-style directives such as `%Y-%m-%d %H:%M:%S`. Reference: [strftime](https://strftime.net/).
101
- sign: true # Boolean | Set to exactly `true` to ask the gateway to sign the final JSON response. Every authenticated JSON response, including errors, then includes signing headers. Not supported on `/v1/time/clock`.
102
- }
116
+ # *_with_http_info returns [data, status code, headers]
117
+ dst, status, headers = time.get_dst_with_http_info(tz: 'Asia/Kolkata', sign: true)
118
+ puts "#{status} #{headers['x-timelogic-quota-limit']} #{headers['x-timelogic-signature']}"
119
+ ```
120
+
121
+ The SDK does not retry. A `429 QUOTA_EXCEEDED` carries no `Retry-After` header; quotas reset at 00:00 UTC on the 1st of each month. Status codes and error codes: [Errors](#errors).
122
+
123
+ ## WorldTimeAPI-compatible client
124
+
125
+ A separate client for the WorldTimeAPI-compatible routes. Code written for worldtimeapi.org needs only the new host and an API key: paths, field names, types and the `.txt` format are the same. The client sends the key as `X-API-Key`, has a 30-second timeout, does not retry, and does not use or change the `/v1` configuration.
126
+
127
+ ```ruby
128
+ require 'timelogic-api'
129
+
130
+ wta = TimeLogic::Api::WorldTimeApi::Client.new(api_key: ENV.fetch('TIMELOGIC_API_KEY')) # timeout: 30 seconds
131
+
132
+ zones = wta.list_timezones # ["Africa/Abidjan", ...]
133
+ europe = wta.list_timezones_in_area('Europe') # ["Europe/Amsterdam", ...]
134
+ london = wta.get_timezone('Europe/London')
135
+ puts "#{london.datetime} #{london.utc_offset} #{london.dst}"
136
+ text = wta.get_timezone_text('Europe/London') # "abbreviation: GMT\nclient_ip: ..."
137
+ mine = wta.get_ip # the caller's address
138
+ google = wta.get_ip('8.8.8.8')
103
139
 
104
140
  begin
105
- #Add modifiers to a timestamp
106
- result = api_instance.add_time(opts)
107
- p result
108
- rescue TimeLogic::Api::ApiError => e
109
- puts "Exception when calling TimeApi->add_time: #{e}"
141
+ wta.get_timezone('Nope/Zone')
142
+ rescue TimeLogic::Api::WorldTimeApi::UnknownLocationError
143
+ puts 'unknown location' # 404 {"error":"unknown location"}
144
+ rescue TimeLogic::Api::WorldTimeApi::Error => e
145
+ puts "#{e.status} #{e.error}"
110
146
  end
111
147
 
148
+ # Signed responses: read the headers (lower-case names) through request_raw
149
+ signed = TimeLogic::Api::WorldTimeApi::Client.new(api_key: ENV.fetch('TIMELOGIC_API_KEY'), sign: true)
150
+ raw = signed.request_raw('/api/timezone/Europe/London')
151
+ puts "#{raw.status} #{raw.headers['x-timelogic-signature']} #{raw.body}"
152
+ ```
153
+
154
+ Routes, fields and errors: [WorldTimeAPI-compatible routes](#worldtimeapi-compatible-routes).
155
+
156
+ ## Known issues
157
+
158
+ Checked against the live API for this release. All other operations, including single-target `current`, `convert` and `timezone` calls, decode normally.
159
+
160
+ - Bulk `get_current_time` / `convert_time` calls (comma-separated selectors) and `offset` lookups with `get_timezone` return `nil`. Ask for the parsed JSON instead of a model:
161
+
162
+ ```ruby
163
+ require 'timelogic-api'
164
+
165
+ time = TimeLogic::Api::TimeApi.new(TimeLogic::Api::ApiClient.new(TimeLogic::Api::TransportConfiguration.build(api_key: ENV.fetch('TIMELOGIC_API_KEY'))))
166
+
167
+ # Bulk current/convert and offset timezone lookups: ask for the parsed JSON instead of a model
168
+ items = time.get_current_time(tz: 'Europe/London,Asia/Tokyo', debug_return_type: 'Object')
169
+ puts items[1][:timezone] # Asia/Tokyo
170
+
171
+ offset = time.get_timezone(offset: '+05:30', debug_return_type: 'Object')
172
+ puts offset[:matching_zones].map { |zone| zone[:timezone] }.inspect # ["Asia/Colombo", "Asia/Kolkata"]
173
+ ```
174
+
175
+ ## How the API works
176
+
177
+ ### Base URLs
178
+
179
+ | Transport | Base URL | Credentials the SDK sends |
180
+ | --- | --- | --- |
181
+ | Direct API (default) | `https://api.timelogicapi.com` | `Authorization: Bearer <your TimeLogic API key>` |
182
+ | RapidAPI | `https://timelogic-api-world-time-timezones-time-calculations.p.rapidapi.com` | `X-RapidAPI-Key: <your RapidAPI key>` and `X-RapidAPI-Host: <RapidAPI host>` |
183
+
184
+ The gateway accepts a direct key in three places, checked in this order: `Authorization: Bearer <key>`, the `X-API-Key` header, and the `api_key` query parameter. The `/v1` clients in this package send the bearer header; the WorldTimeAPI-compatible client sends `X-API-Key`. Sending two different keys in one request is rejected with `401 UNAUTHORIZED` ("Conflicting API key credentials."). All operations are `GET` requests (`HEAD` is answered like `GET` without a body).
185
+
186
+ ### Choosing a target (selector rules)
187
+
188
+ Most time operations take one *target*, chosen with exactly one selector family:
189
+
190
+ | Selector | Example | Notes |
191
+ | --- | --- | --- |
192
+ | `tz` | `America/New_York` | IANA zone, matched case-insensitively. Links and aliases resolve to their canonical zone (`Europe/Stockholm` returns `Europe/Berlin`, `UTC` returns `Etc/UTC`). `Etc/GMT+5` is UTC-05:00 (POSIX sign). |
193
+ | `ip` | `8.8.8.8` | IPv4 or IPv6. Private, reserved and loopback addresses have no zone and return `404 NOT_FOUND`. |
194
+ | `lat` + `lon` | `40.7128`, `-74.006` | Both are required together. |
195
+ | `offset` | `-04:00` | Fixed offset, `+HH:MM` or `-HH:MM` only (`+0530` and `+5` are rejected). The response has `timezone: null`. |
196
+ | `auto_tz` | `true` | Uses the caller's IP address as seen by the gateway. Not available on the clock route. |
197
+ | `utc` | `true` | Forces UTC (convert and add only). The response has `iso_local: null` and `timezone: null`. |
198
+
199
+ Combining two selector families returns `400 AMBIGUOUS_TARGET`. Where a target is optional and none is given, the response is in UTC with `timezone: null` and `iso_local: null`; `/v1/timezone` requires a target and returns `400 MISSING_TARGET` without one.
200
+
201
+ **Bulk requests.** `current` and `convert` accept a comma-separated list in one `tz`, `ip` or `offset` selector (at most 10 values; any comma, even a trailing one, switches to bulk). The response is then an array in request order; each item is either a normal time payload or an item error `{ "item", "error": { "code", "message", "details" }, "request_id" }`, so one bad value does not fail the whole request. Every request URL is limited to 8192 characters.
202
+
203
+ **Input timestamps.** `convert`, `add`, `calendar`, `elapsed` and `clock` take one input instant: `unix` (whole seconds, -62135596800 to 253402300799), `unix_ms` (whole milliseconds) or `iso` (ISO-8601). An `iso` value without an offset is a local wall-clock time; pair it with one hint (`source_tz`, `source_ip`, `source_lat` + `source_lon`, or `source_offset`), otherwise it is read as UTC. Dates must exist (`2027-02-29` returns `400 INVALID_TIMESTAMP`), a local time inside a spring-forward gap returns `400 INVALID_TIMESTAMP`, and an ambiguous fall-back time resolves to its first occurrence.
204
+
205
+ **Custom formatting.** Every JSON time route accepts `format`, a `strftime`-style template that adds a `formatted` field. Supported directives: `%a %A %b %B %C %d %D %e %F %h %H %I %j %k %l %m %M %n %p %P %r %R %s %S %t %T %u %v %w %x %X %y %Y %z %:z %Z %%`, with the `-`, `_` and `0` padding flags on numeric directives. `%Z` prints the IANA zone name (or `UTC+HH:MM` for an offset target), not an abbreviation. Any other directive returns `400 INVALID_PARAMETER`. On `diff` and `elapsed`, `format` is a duration template instead, using `%days`, `%hours`, `%minutes` and `%seconds` (each a total in that unit, so 118 days renders `%days days %hours hours` as `118 days 2832 hours`).
206
+
207
+ ### Operations
208
+
209
+ | Operation | Endpoint | What it returns |
210
+ | --- | --- | --- |
211
+ | Current time | `GET /v1/time/current` | The current time for one target, or an array for a bulk selector. |
212
+ | Convert | `GET /v1/time/convert` | One input instant (`unix`, `unix_ms` or `iso`) expressed in a target zone or offset; bulk targets supported. |
213
+ | Add | `GET /v1/time/add` | A base instant (default: now) plus `seconds`, `minutes`, `hours` and `days`, in a target zone. `days` are exact 24-hour days, so the wall-clock time shifts across a DST change. |
214
+ | Difference | `GET /v1/time/diff` | The duration between `from` and `to`, with `direction` and optional business days. |
215
+ | Elapsed | `GET /v1/time/elapsed` | Time elapsed since, or remaining until, a reference instant (compared with now or a `compare_*` instant), with optional business days. |
216
+ | Timezone | `GET /v1/timezone` | Zone metadata for a target: current time plus `offset` (minutes) and `dst`. An `offset` lookup adds `matching_zones`. |
217
+ | Daylight saving | `GET /v1/time/dst` | Whether DST is active for a target; `next=true` adds `next_transition`. |
218
+ | Calendar | `GET /v1/time/calendar` | Calendar fields (`year`, `month`, `month_name`, `day`) for an instant and target; `week=true` adds the ISO `week_number`. |
219
+ | Clock | `GET /v1/time/clock` | An embeddable HTML clock fragment (`text/html`). `style` is required (30 styles, for example `digital-dashboard` or `analog-station`); `auto_tz` and `sign` are not supported here. |
220
+ | Signing keys | `GET /.well-known/time-api-public-key` | The public Ed25519 keys (JWKS) used to verify signed responses. No API key needed; `kid` filters to one key. |
221
+
222
+ #### Time payload fields
223
+
224
+ `current`, `convert`, `add`, `timezone`, `dst` and `calendar` all return these fields:
225
+
226
+ | Field | Type | Meaning |
227
+ | --- | --- | --- |
228
+ | `unix` | integer | Unix seconds. |
229
+ | `unix_ms` | integer | Unix milliseconds. |
230
+ | `utc` | string | UTC timestamp, for example `2026-11-01T13:16:03Z`. |
231
+ | `iso_local` | string or null | Local ISO-8601 time with offset; `null` for UTC-only responses. |
232
+ | `rfc2822` | string | RFC 2822 local time. |
233
+ | `human` | string | Readable local time, for example `November 1, 2026, 8:16 AM America/New_York`. |
234
+ | `day_number` | integer | Day of the week counted from Sunday (1 = Sunday ... 7 = Saturday). |
235
+ | `day_short`, `day_full` | string | `Sun` / `Sunday`. |
236
+ | `timezone` | string or null | Canonical IANA zone; `null` for offset and UTC targets. |
237
+ | `formatted` | string | Present only when `format` was sent. |
238
+
239
+ Extra fields per operation:
240
+
241
+ - `/v1/timezone`: `offset` (minutes east of UTC) and `dst` (boolean); with an `offset` selector also `matching_zones`, the geographic zones currently at that offset (`UTC` and `Etc/*` excluded).
242
+ - `/v1/time/dst`: `dst_active` and, with `next=true`, `next_transition` (the next UTC-offset change, or `null` if the zone has none).
243
+ - `/v1/time/calendar`: `year`, `month`, `month_name`, `day`, and with `week=true` `week_number` (ISO 8601 week).
244
+ - `/v1/time/diff`: `seconds`, `minutes`, `hours`, `days` (each a total, not a remainder), `direction` (`forward`, `backward` or `same`), `human` (always in hours, minutes and seconds), `from.unix_ms`, `to.unix_ms`, and `business_days` when requested.
245
+ - `/v1/time/elapsed`: `seconds`, `minutes`, `hours`, `days`, `human`, `direction` (`left` while the reference is still ahead, `passed` once it is behind), and `business_days` when requested.
246
+
247
+ #### Difference and elapsed
248
+
249
+ `diff` takes two *time specifiers*, `from` and `to`: `now`, `unix=<seconds>`, `unix_ms=<milliseconds>`, `iso=<ISO-8601>`, `offset=±HH:MM`, `tz=<IANA zone>` or `ip=<address>`. The `tz=`, `ip=` and `offset=` forms mean "now" (they only choose the calendar date used for business days). A local `iso` can be placed with `from_tz`/`from_ip`/`from_lat`+`from_lon`/`from_offset` and the matching `to_*` hints.
250
+
251
+ `elapsed` takes one reference instant (`unix`, `unix_ms` or `iso` with `source_*` hints) and compares it with now, with a `compare_unix`/`compare_unix_ms`/`compare_iso` instant (with `compare_source_*` hints), or with the current time at a target selector.
252
+
253
+ **Business days.** Set `business_days=true` to add `business_days`. Days are counted by calendar date, start included and end excluded. Add `holiday_country` (ISO 3166-1 alpha-2, any letter case, optionally with a subdivision such as `US-CA` or `CH-ZH`) to skip that calendar's public holidays; `holiday_subdivision` (for example `ZH`, `ENG`, `01`) selects a region and requires `holiday_country`. Weekends follow the calendar's country (Friday/Saturday for `SA`, Saturday/Sunday when no calendar is given). Holiday coverage is 1900 to 2100. Example: from `2026-01-02T00:00:00Z` to `2026-04-30T00:00:00Z` gives `business_days: 83` with `holiday_country=FR`, `82` with `CH-ZH`, and `79` with `SA`.
254
+
255
+ ### Errors
256
+
257
+ Every error has the same JSON body:
258
+
259
+ ```json
260
+ { "error": { "code": "INVALID_PARAMETER", "message": "Invalid timezone.", "details": { "tz": "Bad/Zone" } }, "request_id": "547f27d2-1acc-46e6-a9a6-686ec2ca99d9" }
112
261
  ```
113
262
 
114
- ## Documentation for API Endpoints
263
+ | Status | Codes | When |
264
+ | --- | --- | --- |
265
+ | 400 | `INVALID_PARAMETER`, `INVALID_TIMESTAMP`, `AMBIGUOUS_TARGET`, `MISSING_TARGET` | A parameter is malformed or out of range, two selector families were combined, or a required target is missing. |
266
+ | 401 | `UNAUTHORIZED` | Missing, invalid or conflicting API key credentials. |
267
+ | 403 | `SUBSCRIPTION_INACTIVE` | The key or the customer subscription is not active. |
268
+ | 404 | `NOT_FOUND` | Unknown route (including a trailing slash), or an `ip` with no timezone mapping. |
269
+ | 405 | `METHOD_NOT_ALLOWED` | A method other than `GET`/`HEAD`; the response carries `Allow: GET, HEAD`. |
270
+ | 429 | `QUOTA_EXCEEDED` | The plan's monthly quota is used up. |
271
+ | 500 | `INTERNAL_ERROR` | Internal failure, including signing failures. |
272
+ | 501 | `DEPENDENCY_NOT_READY` | A required resolver or dependency is not ready. |
273
+ | 502 | `INTERNAL_ERROR` | The gateway could not reach the core service. |
115
274
 
116
- All URIs are relative to *https://api.timelogicapi.com*
275
+ Quote `request_id` (also sent as the `X-Request-Id` header) when you contact support.
117
276
 
118
- Class | Method | HTTP request | Description
119
- ------------ | ------------- | ------------- | -------------
120
- *TimeLogic::Api::TimeApi* | [**add_time**](docs/TimeApi.md#add_time) | **GET** /v1/time/add | Add modifiers to a timestamp
121
- *TimeLogic::Api::TimeApi* | [**convert_time**](docs/TimeApi.md#convert_time) | **GET** /v1/time/convert | Convert a timestamp into a target timezone or offset
122
- *TimeLogic::Api::TimeApi* | [**diff_time**](docs/TimeApi.md#diff_time) | **GET** /v1/time/diff | Difference between two instants
123
- *TimeLogic::Api::TimeApi* | [**get_calendar**](docs/TimeApi.md#get_calendar) | **GET** /v1/time/calendar | Calendar projection for a target instant
124
- *TimeLogic::Api::TimeApi* | [**get_clock**](docs/TimeApi.md#get_clock) | **GET** /v1/time/clock | Render a live HTML clock
125
- *TimeLogic::Api::TimeApi* | [**get_current_time**](docs/TimeApi.md#get_current_time) | **GET** /v1/time/current | Get the current time for a target
126
- *TimeLogic::Api::TimeApi* | [**get_dst**](docs/TimeApi.md#get_dst) | **GET** /v1/time/dst | Daylight-saving status for a target
127
- *TimeLogic::Api::TimeApi* | [**get_elapsed**](docs/TimeApi.md#get_elapsed) | **GET** /v1/time/elapsed | Time elapsed since or remaining until a reference instant
128
- *TimeLogic::Api::TimeApi* | [**get_timezone**](docs/TimeApi.md#get_timezone) | **GET** /v1/timezone | Resolve timezone information for a target
129
- *TimeLogic::Api::UtilityApi* | [**get_public_signing_key**](docs/UtilityApi.md#get_public_signing_key) | **GET** /.well-known/time-api-public-key | Public signing key set
277
+ ### Response headers, quotas and limits
130
278
 
279
+ | Header | Meaning |
280
+ | --- | --- |
281
+ | `X-Request-Id` | Request identifier, on every response. |
282
+ | `X-Auth-Mode` | How the request was authenticated (`api_key`). |
283
+ | `X-TimeLogic-Customer-ID` | Your customer identifier. |
284
+ | `X-TimeLogic-Plan` | Your plan, for example `free`. |
285
+ | `X-TimeLogic-Quota-Limit` | Requests included per month, or `unlimited`. |
286
+ | `X-TimeLogic-Quota-State` | Cached quota state, for example `active`. |
287
+ | `X-TimeLogic-Key-Id` | On unsigned responses, the ID of the API key that made the request; on signed responses, the signing key `kid`. |
131
288
 
132
- ## Documentation for Models
289
+ Header names are case-insensitive in HTTP. Some SDKs return headers as a plain map keyed by the name as received (most arrive in lower case, the signature headers as `X-TimeLogic-Signature…`), so compare names case-insensitively.
133
290
 
134
- - [TimeLogic::Api::ApiError](docs/ApiError.md)
135
- - [TimeLogic::Api::BulkError](docs/BulkError.md)
136
- - [TimeLogic::Api::CalendarResponse](docs/CalendarResponse.md)
137
- - [TimeLogic::Api::DiffEndpointRef](docs/DiffEndpointRef.md)
138
- - [TimeLogic::Api::DiffResponse](docs/DiffResponse.md)
139
- - [TimeLogic::Api::DstResponse](docs/DstResponse.md)
140
- - [TimeLogic::Api::ElapsedResponse](docs/ElapsedResponse.md)
141
- - [TimeLogic::Api::ErrorResponse](docs/ErrorResponse.md)
142
- - [TimeLogic::Api::GetCurrentTime200Response](docs/GetCurrentTime200Response.md)
143
- - [TimeLogic::Api::GetTimezone200Response](docs/GetTimezone200Response.md)
144
- - [TimeLogic::Api::JwkKey](docs/JwkKey.md)
145
- - [TimeLogic::Api::JwksResponse](docs/JwksResponse.md)
146
- - [TimeLogic::Api::TimePayload](docs/TimePayload.md)
147
- - [TimeLogic::Api::TimePayloadBulkItem](docs/TimePayloadBulkItem.md)
148
- - [TimeLogic::Api::TimePayloadBulkResponse](docs/TimePayloadBulkResponse.md)
149
- - [TimeLogic::Api::TimezoneMatch](docs/TimezoneMatch.md)
150
- - [TimeLogic::Api::TimezoneOffsetResponse](docs/TimezoneOffsetResponse.md)
151
- - [TimeLogic::Api::TimezoneResolvedResponse](docs/TimezoneResolvedResponse.md)
291
+ Quotas are counted per UTC calendar month and reset at 00:00 UTC on the 1st. There is no remaining-requests or reset header, no `Retry-After` on `429`, and no published per-second limit. Bulk selectors take at most 10 values and every request URL is limited to 8192 characters.
152
292
 
293
+ ### Signed responses
153
294
 
154
- ## Documentation for Authorization
295
+ Pass `sign` (`true`) on any JSON route except the clock to receive a detached Ed25519 signature over the exact response body, in these headers: `X-TimeLogic-Signature` (Base64), `X-TimeLogic-Key-Id`, `X-TimeLogic-Signature-Alg` (`Ed25519`), `X-TimeLogic-Signature-Timestamp`, `X-TimeLogic-Content-SHA256` and `X-TimeLogic-Signature-Input`. To verify: decode `X-TimeLogic-Signature-Input` (Base64URL), check that its `content-sha256` line matches the SHA-256 of the body bytes, then verify the signature over the decoded input with the key whose `kid` matches, from `GET /.well-known/time-api-public-key`. The body is unchanged by signing. The headers are exposed to browsers through CORS.
155
296
 
297
+ ## WorldTimeAPI-compatible routes
156
298
 
157
- Authentication schemes defined for the API:
158
- ### bearerAuth
299
+ ### Routes
159
300
 
160
- - **Type**: Bearer authentication (TimeLogic API key)
301
+ The client covers all 12 routes of the WorldTimeAPI-compatible contract ([`openapi/openapi-worldtimeapi.yaml`](https://github.com/TimeLogic-API-LLC/timelogic-api-sdks/blob/main/openapi/openapi-worldtimeapi.yaml)). Each JSON route has a plain-text twin ending in `.txt`.
161
302
 
162
- ### apiKeyHeader
303
+ | Route | Returns |
304
+ | --- | --- |
305
+ | `GET /api/timezone`, `/api/timezone.txt` | Every zone name. |
306
+ | `GET /api/timezone/{area}`, `.txt` | The zone names in an area such as `Europe`, or the time object for a one-word zone such as `UTC`. |
307
+ | `GET /api/timezone/{area}/{location}`, `.txt` | The time object for a zone such as `Europe/London`. |
308
+ | `GET /api/timezone/{area}/{location}/{region}`, `.txt` | The time object for a three-part zone such as `America/Argentina/Salta`. |
309
+ | `GET /api/ip`, `/api/ip.txt` | The time object for the caller's IP address. |
310
+ | `GET /api/ip/{ip}`, `.txt` | The time object for an IPv4 or IPv6 address. |
163
311
 
312
+ Zone names match in any letter case. Query parameters other than `api_key` and `sign` are ignored.
164
313
 
165
- - **Type**: API key
166
- - **API key parameter name**: X-API-Key
167
- - **Location**: HTTP header
314
+ ### Time object fields
168
315
 
169
- ### apiKeyQuery
316
+ | Field | Example | Meaning |
317
+ | --- | --- | --- |
318
+ | `abbreviation` | `GMT` | Zone abbreviation. |
319
+ | `client_ip` | `203.0.113.7` | The caller's IP address. |
320
+ | `datetime` | `2026-11-01T13:18:00.721000+00:00` | Local time with six fractional digits (millisecond resolution, so the last three are `000`). |
321
+ | `day_of_week` | `0` | 0 = Sunday ... 6 = Saturday. |
322
+ | `day_of_year` | `305` | 1-366. |
323
+ | `dst` | `false` | Whether DST is in effect. |
324
+ | `dst_from` | `null` | Start of the current DST period, or `null`. |
325
+ | `dst_offset` | `0` | DST offset in seconds. |
326
+ | `dst_until` | `null` | End of the current DST period, or `null`. |
327
+ | `raw_offset` | `0` | Standard offset in seconds. |
328
+ | `timezone` | `Europe/London` | Zone name. |
329
+ | `unixtime` | `1793539080` | Unix seconds. |
330
+ | `utc_datetime` | `2026-11-01T13:18:00.721000+00:00` | UTC time. |
331
+ | `utc_offset` | `+00:00` | Current total offset. |
332
+ | `week_number` | `44` | ISO 8601 week number. |
170
333
 
334
+ The `.txt` variants return the same fields as `key: value` lines in alphabetical order.
171
335
 
172
- - **Type**: API key
173
- - **API key parameter name**: api_key
174
- - **Location**: URL query string
336
+ ### Errors and authentication
175
337
 
176
- ### rapidApiKey
338
+ Unknown zones and malformed addresses return `404` with worldtimeapi.org's bodies: `{"error":"unknown location"}`, `{"error":"malformed ip"}` or `{"error":"not found"}` (on `.txt` routes, a line starting with `Error:`). Authentication and quota errors (`401`, `403`, `429`) use the TimeLogic error body shown in [Errors](#errors). Every route requires a TimeLogic API key, which the client sends as the `X-API-Key` header, and successful calls count toward the monthly quota like `/v1` calls. The client connects to the direct API (`https://api.timelogicapi.com` by default); it does not use the RapidAPI transport.
177
339
 
340
+ With signing enabled, the client adds `sign=true` and responses carry the same detached signature headers as `/v1` ([Signed responses](#signed-responses)); the body is byte-identical to the unsigned body. Use the raw request method to read those headers.
178
341
 
179
- - **Type**: API key
180
- - **API key parameter name**: X-RapidAPI-Key
181
- - **Location**: HTTP header
342
+ ## Support
182
343
 
183
- ### rapidApiHost
344
+ Questions and problem reports: [issue tracker](https://github.com/TimeLogic-API-LLC/timelogic-api-sdks/issues). Security issues: see [SECURITY.md](https://github.com/TimeLogic-API-LLC/timelogic-api-sdks/blob/main/SECURITY.md). Service status: [status.timelogicapi.com](https://status.timelogicapi.com).
184
345
 
346
+ ## License
185
347
 
186
- - **Type**: API key
187
- - **API key parameter name**: X-RapidAPI-Host
188
- - **Location**: HTTP header
348
+ [Unlicense](https://github.com/TimeLogic-API-LLC/timelogic-api-sdks/blob/main/LICENSE).
data/docs/BulkError.md CHANGED
@@ -6,7 +6,7 @@
6
6
  | ---- | ---- | ----------- | ----- |
7
7
  | **error** | [**ApiError**](ApiError.md) | | |
8
8
  | **request_id** | **String** | | |
9
- | **timestamp** | **Time** | Intentionally omitted from public API error responses to avoid exposing request timing; use request_id for support correlation. | [optional] |
9
+ | **timestamp** | **Time** | Intentionally omitted from public API error responses, including per-item errors inside bulk arrays, to avoid exposing request timing; use request_id for support correlation. | [optional] |
10
10
  | **item** | **String** | | |
11
11
 
12
12
  ## Example
@@ -10,7 +10,7 @@
10
10
  | **iso_local** | **String** | | |
11
11
  | **rfc2822** | **String** | | |
12
12
  | **human** | **String** | | |
13
- | **day_number** | **Integer** | | |
13
+ | **day_number** | **Integer** | Day of the week counted from Sunday (1 &#x3D; Sunday, 2 &#x3D; Monday ... 7 &#x3D; Saturday). | |
14
14
  | **day_short** | **String** | | |
15
15
  | **day_full** | **String** | | |
16
16
  | **timezone** | **String** | | |
@@ -19,7 +19,7 @@
19
19
  | **month** | **Integer** | | |
20
20
  | **month_name** | **String** | | |
21
21
  | **day** | **Integer** | | |
22
- | **week_number** | **Integer** | | [optional] |
22
+ | **week_number** | **Integer** | ISO 8601 week number (present with &#x60;week&#x3D;true&#x60;). It belongs to the ISO week-year, which is not returned and can differ from &#x60;year&#x60; near January 1 (2027-01-01 is week 53 of 2026). | [optional] |
23
23
 
24
24
  ## Example
25
25
 
data/docs/DstResponse.md CHANGED
@@ -10,13 +10,13 @@
10
10
  | **iso_local** | **String** | | |
11
11
  | **rfc2822** | **String** | | |
12
12
  | **human** | **String** | | |
13
- | **day_number** | **Integer** | | |
13
+ | **day_number** | **Integer** | Day of the week counted from Sunday (1 &#x3D; Sunday, 2 &#x3D; Monday ... 7 &#x3D; Saturday). | |
14
14
  | **day_short** | **String** | | |
15
15
  | **day_full** | **String** | | |
16
16
  | **timezone** | **String** | | |
17
17
  | **formatted** | **String** | | [optional] |
18
18
  | **dst_active** | **Boolean** | | |
19
- | **next_transition** | **Time** | | [optional] |
19
+ | **next_transition** | **Time** | Present only with &#x60;next&#x3D;true&#x60;. The instant of the next UTC offset change, written in the offset in force before it (e.g. &#x60;2026-11-01T02:00:00-04:00&#x60;); the new offset is not returned. &#x60;null&#x60; when no change is scheduled, for offset targets and with no selector. Tracks offset changes only, so &#x60;dst_active&#x60; can change without one. | [optional] |
20
20
 
21
21
  ## Example
22
22
 
@@ -9,7 +9,7 @@
9
9
  | **hours** | **Integer** | | |
10
10
  | **days** | **Integer** | | |
11
11
  | **human** | **String** | | |
12
- | **direction** | **String** | &#x60;left&#x60; means the target instant is still in the future. &#x60;passed&#x60; means the target instant is already in the past. | |
12
+ | **direction** | **String** | &#x60;left&#x60; means the reference instant is after the compared instant (now, or &#x60;compare_*&#x60;). &#x60;passed&#x60; means it is before it, or equal to it. | |
13
13
  | **formatted** | **String** | | [optional] |
14
14
  | **business_days** | **Integer** | | [optional] |
15
15
 
@@ -6,7 +6,7 @@
6
6
  | ---- | ---- | ----------- | ----- |
7
7
  | **error** | [**ApiError**](ApiError.md) | | |
8
8
  | **request_id** | **String** | | |
9
- | **timestamp** | **Time** | Intentionally omitted from public API error responses to avoid exposing request timing; use request_id for support correlation. | [optional] |
9
+ | **timestamp** | **Time** | Intentionally omitted from public API error responses, including per-item errors inside bulk arrays, to avoid exposing request timing; use request_id for support correlation. | [optional] |
10
10
 
11
11
  ## Example
12
12