sheets_v4 0.6.0 → 0.8.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +99 -1
- data/Rakefile +3 -0
- data/examples/README.md +1 -0
- data/lib/sheets_v4/{validate_api_objects → api_object_validation}/load_schemas.rb +7 -7
- data/lib/sheets_v4/{validate_api_objects → api_object_validation}/resolve_schema_ref.rb +6 -6
- data/lib/sheets_v4/{validate_api_objects → api_object_validation}/traverse_object_tree.rb +2 -2
- data/lib/sheets_v4/api_object_validation/validate_api_object.rb +87 -0
- data/lib/sheets_v4/api_object_validation.rb +20 -0
- data/lib/sheets_v4/color.rb +0 -1
- data/lib/sheets_v4/convert_dates_and_times.rb +243 -0
- data/lib/sheets_v4/{credential_creator.rb → create_credential.rb} +2 -2
- data/lib/sheets_v4/google_extensions/sheet.rb +32 -0
- data/lib/sheets_v4/google_extensions/sheets_service.rb +99 -0
- data/lib/sheets_v4/google_extensions/spreadsheet.rb +24 -0
- data/lib/sheets_v4/google_extensions.rb +42 -0
- data/lib/sheets_v4/validate_api_objects/{validate.rb → validate_api_object.rb} +7 -6
- data/lib/sheets_v4/validate_api_objects.rb +6 -6
- data/lib/sheets_v4/version.rb +1 -1
- data/lib/sheets_v4.rb +225 -95
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aaee6f486ac124cdefd974acfe3f18357ade8e9f90655a46e746ebfad53a391b
|
4
|
+
data.tar.gz: 0f3c1e513b45a841052be82817e5bcf157aba492282999f401bb069e048e2263
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3762e42b88065e6bc76e5e749d629dc02073c2584053a9c979fd8b46196e70e155c2f06d87a768b511a93b30d38d09c5da6592ef875a9fb70cb7bd97b67ed7a
|
7
|
+
data.tar.gz: 64bed7345bcc3ee05d204b47770b297e8a53d05cc1deb6611bd114197cb46690df2683549e772b7b9453fbf158eba492efc05faf93e966047f57055e9d203d87
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,27 @@ Changes for each release are listed in this file.
|
|
4
4
|
|
5
5
|
This project adheres to [Semantic Versioning](https://semver.org/) for its releases.
|
6
6
|
|
7
|
+
## v0.8.0 (2023-10-15)
|
8
|
+
|
9
|
+
[Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.7.0..v0.8.0)
|
10
|
+
|
11
|
+
Changes since v0.7.0:
|
12
|
+
|
13
|
+
* d8f695c Add extensions to Google::Apis::SheetsV4 classes (#26)
|
14
|
+
* ed2dc0e Show the cop names on Rubocop offenses when Rubocop is run from Rake (#25)
|
15
|
+
* c5bfcc1 Group the SheetsV4 methods in the Yard Docs to make them easier to find (#24)
|
16
|
+
|
17
|
+
## v0.7.0 (2023-10-08)
|
18
|
+
|
19
|
+
[Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.6.0..v0.7.0)
|
20
|
+
|
21
|
+
Changes since v0.6.0:
|
22
|
+
|
23
|
+
* 616fe1f Add conversions bewteen Date/DateTime and spreadsheet values (#22)
|
24
|
+
* 6f37337 Rename SheetsV4::ValidateApiObjects to SheetsV4::ApiObjectValidation (#21)
|
25
|
+
* 0f76992 Rename SheetsV4::ValidateApiObjects::Validate to SheetsV4::ValidateApiObjects::ValidateApiObject (#20)
|
26
|
+
* e80040c Rename SheetsV4::CredentialCreator to SheetsV4::CreateCredential (#19)
|
27
|
+
|
7
28
|
## v0.6.0 (2023-10-03)
|
8
29
|
|
9
30
|
[Full Changelog](https://github.com/main-branch/sheets_v4/compare/v0.5.0..v0.6.0)
|
data/README.md
CHANGED
@@ -25,6 +25,11 @@ Unofficial helpers for the Google Sheets V4 API
|
|
25
25
|
* [Method 2: constructing requests using hashes](#method-2-constructing-requests-using-hashes)
|
26
26
|
* [Which method should be used?](#which-method-should-be-used)
|
27
27
|
* [Validating requests](#validating-requests)
|
28
|
+
* [Google Extensions](#google-extensions)
|
29
|
+
* [SheetsService Extensions](#sheetsservice-extensions)
|
30
|
+
* [Spreadsheet Extensions](#spreadsheet-extensions)
|
31
|
+
* [Sheet Extensions](#sheet-extensions)
|
32
|
+
* [Working with dates and times](#working-with-dates-and-times)
|
28
33
|
* [Colors](#colors)
|
29
34
|
* [Development](#development)
|
30
35
|
* [Contributing](#contributing)
|
@@ -125,7 +130,7 @@ sheets_service = File.open('credential.json') do |credential_source|
|
|
125
130
|
end
|
126
131
|
```
|
127
132
|
|
128
|
-
or an already constructed `Google::Auth
|
133
|
+
or an already constructed `Google::Auth::*` object.
|
129
134
|
|
130
135
|
### Building a request
|
131
136
|
|
@@ -273,6 +278,99 @@ request:
|
|
273
278
|
SheetsV4.validate_api_object(schema: 'batch_update_spreadsheet_request', object: requests)
|
274
279
|
```
|
275
280
|
|
281
|
+
### Google Extensions
|
282
|
+
|
283
|
+
The `SheetsV4::GoogleExtensions` module provides extensions to the `Google::Apis::SheetsV4`
|
284
|
+
classes to simplify use of the SheetsV4 API.
|
285
|
+
|
286
|
+
These extensions are not loaded by default and are not required to use other parts
|
287
|
+
of this Gem. To enable these extension, you must:
|
288
|
+
|
289
|
+
```Ruby
|
290
|
+
require 'sheets_v4/google_extensions'
|
291
|
+
```
|
292
|
+
|
293
|
+
#### SheetsService Extensions
|
294
|
+
|
295
|
+
Functionality is added to `get_spreadsheet` to set the `sheets_service` attribute on
|
296
|
+
the returned spreadsheet and set the `sheets_service` and `spreadsheet` attributes
|
297
|
+
on the sheets contained in the spreadsheet.
|
298
|
+
|
299
|
+
This can simplify complex spreadsheet updates because you won't have to pass a
|
300
|
+
sheets_service, spreadsheet, and sheet objects separately.
|
301
|
+
|
302
|
+
#### Spreadsheet Extensions
|
303
|
+
|
304
|
+
The `sheets_service` attribute is added and is set by `SheetsService#get_spreadsheet`.
|
305
|
+
|
306
|
+
#### Sheet Extensions
|
307
|
+
|
308
|
+
The `sheets_service` and `spreadsheet` attributes are added. Both are set when the
|
309
|
+
sheet's spreadsheet is loaded by `SheetsService#get_spreadsheet`.
|
310
|
+
|
311
|
+
### Working with dates and times
|
312
|
+
|
313
|
+
Google Sheets, similar to other spreadsheet programs, stores dates and date-time
|
314
|
+
values as numbers. This system makes it easier to perform calculations with
|
315
|
+
dates and times.
|
316
|
+
|
317
|
+
This gem provides two sets of equavalent conversion methods. The first set is defined
|
318
|
+
as class methods on the `SheetsV4` class.
|
319
|
+
|
320
|
+
* `SheetsV4.date_to_gs(date)` returns a numeric cell value
|
321
|
+
* `SheetsV4.gs_to_date(cell_value)` returns a Date object
|
322
|
+
* `SheetsV4.datetime_to_gs(datetime)` returns a numeric cell value
|
323
|
+
* `SheetsV4.gs_to_datetime(cell_value)` returns a DateTime object
|
324
|
+
|
325
|
+
In order to convert to and from spreadsheet values, the spreadsheet timezone must
|
326
|
+
be known. A spreadsheet's timezone is found in the Google Sheets spreadsheet object's
|
327
|
+
properties:
|
328
|
+
|
329
|
+
```Ruby
|
330
|
+
SheetsV4.default_spreadsheet_tz = spreadsheet.properties.time_zone
|
331
|
+
```
|
332
|
+
|
333
|
+
If a time zone is not set using `SheetsV4.default_spreadsheet_tz`, a RuntimeError
|
334
|
+
will be raised when any of the above methods are used.
|
335
|
+
|
336
|
+
Here is an example of how the timezone can change the values fetched from the
|
337
|
+
spreadsheet:
|
338
|
+
|
339
|
+
```Ruby
|
340
|
+
cell_value = 44333.191666666666
|
341
|
+
|
342
|
+
SheetsV4.default_spreadsheet_tz = 'America/New_York'
|
343
|
+
datetime = SheetsV4.gs_to_datetime(cell_value) #=> Mon, 17 May 2021 04:36:00 -0400
|
344
|
+
datetime.utc #=> 2021-05-17 08:36:00 UTC
|
345
|
+
|
346
|
+
SheetsV4.default_spreadsheet_tz = 'America/Los_Angeles'
|
347
|
+
datetime = SheetsV4.gs_to_datetime(cell_value) #=> Mon, 17 May 2021 04:36:00 -0700
|
348
|
+
datetime.utc #=> 2021-05-17 11:36:00 UTC
|
349
|
+
```
|
350
|
+
|
351
|
+
Valid time zone names are those listed in one of these two sources:
|
352
|
+
|
353
|
+
* `ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name }`
|
354
|
+
* `ActiveSupport::TimeZone.all.map(&:name)`
|
355
|
+
|
356
|
+
The `SheetsV4` methods works well if the spreadsheet timezone is constant through
|
357
|
+
the run of the program. If this is not the case -- for instance when working with
|
358
|
+
multiple spreadsheets whose timezones may be different -- then use
|
359
|
+
`SheetsV4::ConvertDatesAndTimes`.
|
360
|
+
|
361
|
+
Each instance of `SheetsV4::ConvertDatesAndTimes` has it's own spreadsheet timezone
|
362
|
+
used in the conversions. Instance methods for this class are the same as the
|
363
|
+
date conversion methods on the SheetsV4 class.
|
364
|
+
|
365
|
+
Example:
|
366
|
+
|
367
|
+
```Ruby
|
368
|
+
cell_value = 44333.191666666666
|
369
|
+
converter = SheetsV4::ConvertDatesAndTimes.new('America/Los_Angeles')
|
370
|
+
datetime = SheetsV4.gs_to_datetime(cell_value) #=> Mon, 17 May 2021 04:36:00 -0700
|
371
|
+
datetime.utc #=> 2021-05-17 11:36:00 UTC
|
372
|
+
```
|
373
|
+
|
276
374
|
### Colors
|
277
375
|
|
278
376
|
Color objects (with appropriate :red, :green, :blue values) can be retrieved by name
|
data/Rakefile
CHANGED
data/examples/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SheetsV4
|
4
|
-
module
|
4
|
+
module ApiObjectValidation
|
5
5
|
# Load the Google Discovery API description for the Sheets V4 API
|
6
6
|
#
|
7
7
|
# @example
|
8
8
|
# logger = Logger.new(STDOUT, :level => Logger::ERROR)
|
9
|
-
# schemas = SheetsV4::
|
9
|
+
# schemas = SheetsV4::ApiObjectValidation::LoadSchemas.new(logger:).call
|
10
10
|
#
|
11
11
|
# @api private
|
12
12
|
#
|
@@ -18,7 +18,7 @@ module SheetsV4
|
|
18
18
|
# The schemas are only loaded once and cached.
|
19
19
|
#
|
20
20
|
# @example
|
21
|
-
# schema_loader = SheetsV4::
|
21
|
+
# schema_loader = SheetsV4::ApiObjectValidation::LoadSchemas.new
|
22
22
|
#
|
23
23
|
# @param logger [Logger] the logger to use
|
24
24
|
#
|
@@ -30,8 +30,8 @@ module SheetsV4
|
|
30
30
|
#
|
31
31
|
# @example
|
32
32
|
# logger = Logger.new(STDOUT, :level => Logger::INFO)
|
33
|
-
#
|
34
|
-
#
|
33
|
+
# schema_loader = SheetsV4::ApiObjectValidation::LoadSchemas.new(logger)
|
34
|
+
# schema_loader.logger == logger # => true
|
35
35
|
#
|
36
36
|
# @return [Logger]
|
37
37
|
#
|
@@ -84,7 +84,7 @@ module SheetsV4
|
|
84
84
|
# Log an error and raise a RuntimeError based on the HTTP response code
|
85
85
|
# @param http_response [Net::HTTPResponse] the HTTP response
|
86
86
|
# @return [void]
|
87
|
-
# @
|
87
|
+
# @raise [RuntimeError]
|
88
88
|
# @api private
|
89
89
|
def raise_error(http_response)
|
90
90
|
message = "HTTP Error '#{http_response.code}' loading schemas from '#{http_response.uri}'"
|
@@ -146,7 +146,7 @@ module SheetsV4
|
|
146
146
|
# @return [void]
|
147
147
|
# @api private
|
148
148
|
def post_process_schemas(schemas)
|
149
|
-
SheetsV4::
|
149
|
+
SheetsV4::ApiObjectValidation::TraverseObjectTree.call(
|
150
150
|
object: schemas, visitor: ->(path:, object:) { schema_visitor(path:, object:) }
|
151
151
|
)
|
152
152
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SheetsV4
|
4
|
-
module
|
4
|
+
module ApiObjectValidation
|
5
5
|
# Resolve a JSON schema reference to a Google Sheets API schema
|
6
6
|
#
|
7
7
|
# This class uses the Google Discovery API to get the schemas. Any schema reference
|
@@ -9,14 +9,14 @@ module SheetsV4
|
|
9
9
|
# name in the Google Discovery API and returning the schema object (as a Hash).
|
10
10
|
#
|
11
11
|
# This means that `{ "$ref": "cell_data" }` is resolved by returning
|
12
|
-
# `SheetsV4::
|
12
|
+
# `SheetsV4::ApiObjectValidation::LoadSchemas.new(logger:).call['cell_data']`.
|
13
13
|
#
|
14
|
-
# An RuntimeError is raised if `SheetsV4::
|
14
|
+
# An RuntimeError is raised if `SheetsV4::ApiObjectValidation::LoadSchemas.new.call`
|
15
15
|
# does not have a key matching the schema name.
|
16
16
|
#
|
17
17
|
# @example
|
18
18
|
# logger = Logger.new(STDOUT, level: Logger::INFO)
|
19
|
-
# ref_resolver = SheetsV4::
|
19
|
+
# ref_resolver = SheetsV4::ApiObjectValidation::ResolveSchemaRef.new(logger:)
|
20
20
|
# people_schema = { 'type' => 'array', 'items' => { '$ref' => 'person' } }
|
21
21
|
# json_validator = JSONSchemer.schema(people_schema, ref_resolver:)
|
22
22
|
# people_json = [{ 'name' => { 'first' => 'John', 'last' => 'Doe' } }]
|
@@ -53,7 +53,7 @@ module SheetsV4
|
|
53
53
|
|
54
54
|
# Resolve a JSON schema reference
|
55
55
|
#
|
56
|
-
# @param ref [URI] the reference to resolve usually in the form "json-schemer://schema/
|
56
|
+
# @param ref [URI] the reference to resolve usually in the form "json-schemer://schema/[name]"
|
57
57
|
#
|
58
58
|
# @return [Hash] the schema object as a hash
|
59
59
|
#
|
@@ -62,7 +62,7 @@ module SheetsV4
|
|
62
62
|
def call(ref)
|
63
63
|
schema_name = ref.path[1..]
|
64
64
|
logger.debug { "Reading schema #{schema_name}" }
|
65
|
-
schemas = SheetsV4::
|
65
|
+
schemas = SheetsV4::ApiObjectValidation::LoadSchemas.new(logger:).call
|
66
66
|
schemas[schema_name].tap do |schema_object|
|
67
67
|
raise "Schema for #{ref} not found" unless schema_object
|
68
68
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SheetsV4
|
4
|
-
module
|
4
|
+
module ApiObjectValidation
|
5
5
|
# Visit all objects in arbitrarily nested object tree of hashes and/or arrays
|
6
6
|
#
|
7
7
|
# @api public
|
@@ -16,7 +16,7 @@ module SheetsV4
|
|
16
16
|
#
|
17
17
|
# ```Ruby
|
18
18
|
# visitor = -> (path:, object:) { puts "path: #{path}, object: #{obj}" }
|
19
|
-
# SheetsV4::
|
19
|
+
# SheetsV4::ApiObjectValidation::TraverseObjectTree.call(object:, visitor:)
|
20
20
|
# ```
|
21
21
|
#
|
22
22
|
# @example Given a simple object (not very exciting)
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
require 'json_schemer'
|
6
|
+
|
7
|
+
module SheetsV4
|
8
|
+
module ApiObjectValidation
|
9
|
+
# Validate objects against a Google Sheets API request object schema
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
#
|
13
|
+
class ValidateApiObject
|
14
|
+
# Create a new api object validator
|
15
|
+
#
|
16
|
+
# By default, a nil logger is used. This means that no messages are logged.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# validator = SheetsV4::ApiObjectValidation::ValidateApiObject.new
|
20
|
+
#
|
21
|
+
# @param logger [Logger] the logger to use
|
22
|
+
#
|
23
|
+
def initialize(logger: Logger.new(nil))
|
24
|
+
@logger = logger
|
25
|
+
end
|
26
|
+
|
27
|
+
# The logger to use internally
|
28
|
+
#
|
29
|
+
# Validation errors are logged at the error level. Other messages are logged
|
30
|
+
# at the debug level.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# logger = Logger.new(STDOUT, :level => Logger::INFO)
|
34
|
+
# validator = SheetsV4::ApiObjectValidation::ValidateApiObject.new(logger)
|
35
|
+
# validator.logger == logger # => true
|
36
|
+
# validator.logger.debug { "Debug message" }
|
37
|
+
#
|
38
|
+
# @return [Logger]
|
39
|
+
#
|
40
|
+
attr_reader :logger
|
41
|
+
|
42
|
+
# Validate the object using the JSON schema named schema_name
|
43
|
+
#
|
44
|
+
# @example
|
45
|
+
# schema_name = 'batch_update_spreadsheet_request'
|
46
|
+
# object = { 'requests' => [] }
|
47
|
+
# validator = SheetsV4::ApiObjectValidation::ValidateApiObject.new
|
48
|
+
# validator.call(schema_name:, object:)
|
49
|
+
#
|
50
|
+
# @param schema_name [String] the name of the schema to validate against
|
51
|
+
# @param object [Object] the object to validate
|
52
|
+
#
|
53
|
+
# @raise [RuntimeError] if the object does not conform to the schema
|
54
|
+
#
|
55
|
+
# @return [void]
|
56
|
+
#
|
57
|
+
def call(schema_name:, object:)
|
58
|
+
logger.debug { "Validating #{object} against #{schema_name}" }
|
59
|
+
|
60
|
+
schema = { '$ref' => schema_name }
|
61
|
+
schemer = JSONSchemer.schema(schema, ref_resolver:)
|
62
|
+
errors = schemer.validate(object)
|
63
|
+
raise_error!(schema_name, object, errors) if errors.any?
|
64
|
+
|
65
|
+
logger.debug { "Object #{object} conforms to #{schema_name}" }
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# The resolver to use to resolve JSON schema references
|
71
|
+
# @return [ResolveSchemaRef]
|
72
|
+
# @api private
|
73
|
+
def ref_resolver = @ref_resolver ||= SheetsV4::ApiObjectValidation::ResolveSchemaRef.new(logger:)
|
74
|
+
|
75
|
+
# Raise an error when the object does not conform to the schema
|
76
|
+
# @return [void]
|
77
|
+
# @raise [RuntimeError]
|
78
|
+
# @api private
|
79
|
+
def raise_error!(schema_name, object, errors)
|
80
|
+
error = errors.first['error']
|
81
|
+
error_message = "Object #{object} does not conform to #{schema_name}: #{error}"
|
82
|
+
logger.error(error_message)
|
83
|
+
raise error_message
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SheetsV4
|
4
|
+
# Validate API Objects against the Google Discovery API
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# logger = Logger.new(STDOUT, :level => Logger::ERROR)
|
8
|
+
# schema_name = 'batch_update_spreadsheet_request'
|
9
|
+
# object = { 'requests' => [] }
|
10
|
+
# SheetsV4::ApiObjectValidation::ValidateApiObject.new(logger:).call(schema_name:, object:)
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
#
|
14
|
+
module ApiObjectValidation; end
|
15
|
+
end
|
16
|
+
|
17
|
+
require_relative 'api_object_validation/load_schemas'
|
18
|
+
require_relative 'api_object_validation/resolve_schema_ref'
|
19
|
+
require_relative 'api_object_validation/traverse_object_tree'
|
20
|
+
require_relative 'api_object_validation/validate_api_object'
|
data/lib/sheets_v4/color.rb
CHANGED
@@ -0,0 +1,243 @@
|
|
1
|
+
# Copyright (c) 2023 Yahoo
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require 'active_support'
|
6
|
+
require 'active_support/values/time_zone'
|
7
|
+
require 'active_support/core_ext/numeric/time'
|
8
|
+
require 'active_support/core_ext/string/zones'
|
9
|
+
|
10
|
+
module SheetsV4
|
11
|
+
# Convert between Ruby Date and DateTime objects and Google Sheets values
|
12
|
+
#
|
13
|
+
# Google Sheets uses decimal values to represent dates and times. These
|
14
|
+
# conversion routines allows converting from Ruby Date and DateTime objects
|
15
|
+
# and values that Google Sheets recognize.
|
16
|
+
#
|
17
|
+
# DateTime objects passed to `datetime_to_gs` or `date_to_gs` are converted to
|
18
|
+
# the time zone of the spreadsheet given in the initializer. DateTime objects
|
19
|
+
# returned by `gs_to_datetime` are always in the spreadsheet's time zone.
|
20
|
+
#
|
21
|
+
# Valid time zone names are those listed in one of these two sources:
|
22
|
+
# * `ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name }`
|
23
|
+
# * `ActiveSupport::TimeZone.all.map(&:name)`
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# tz = spreadsheet.properties.time_zone #=> e.g. 'America/Los_Angeles'
|
27
|
+
# converter = SheetsV4::ConvertDatesAndTimes.new(tz)
|
28
|
+
# date = Date.parse('1967-03-15')
|
29
|
+
# converter.date_to_gs(date) #=> 24546
|
30
|
+
# converter.gs_to_date(24546) #=> #<Date: 1967-03-15 ((2439565j,0s,0n),+0s,2299161j)>
|
31
|
+
#
|
32
|
+
# date_time = DateTime.parse('2021-05-17 11:36:00 UTC')
|
33
|
+
# converter.datetime_to_gs(date_time) #=> 44333.191666666666
|
34
|
+
# converter.gs_to_datetime(44333.191666666666)
|
35
|
+
# #=> #<DateTime: 2021-05-17T11:36:00+00:00 ((2459352j,41760s,0n),+0s,2299161j)>
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
#
|
39
|
+
class ConvertDatesAndTimes
|
40
|
+
# The time zone passed into the initializer
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# time_zone = 'UTC'
|
44
|
+
# converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
|
45
|
+
# converter.spreadsheet_tz
|
46
|
+
# #=> #<ActiveSupport::TimeZone:0x00007fe39000f908 @name="America/Los_Angeles", ...>
|
47
|
+
#
|
48
|
+
# @return [ActiveSupport::TimeZone] the time zone
|
49
|
+
#
|
50
|
+
attr_reader :spreadsheet_tz
|
51
|
+
|
52
|
+
# Initialize the conversion routines for a spreadsheet
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# time_zone = 'America/Los_Angeles'
|
56
|
+
# converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
|
57
|
+
#
|
58
|
+
# @param spreadsheet_tz [String] the time zone set in the spreadsheet properties
|
59
|
+
#
|
60
|
+
# @raise [RuntimeError] if the time zone is not valid
|
61
|
+
#
|
62
|
+
def initialize(spreadsheet_tz)
|
63
|
+
@spreadsheet_tz = ActiveSupport::TimeZone.new(spreadsheet_tz)
|
64
|
+
raise "Invalid time zone '#{spreadsheet_tz}'" unless @spreadsheet_tz
|
65
|
+
end
|
66
|
+
|
67
|
+
# Convert a Ruby DateTime object to a Google Sheets date time value
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# time_zone = 'America/Los_Angeles'
|
71
|
+
# converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
|
72
|
+
# date_time = DateTime.parse('2021-05-17 11:36:00 UTC')
|
73
|
+
# converter.datetime_to_gs(date_time) #=> 44333.191666666666
|
74
|
+
#
|
75
|
+
# @param datetime [DateTime, nil] the date and time to convert
|
76
|
+
#
|
77
|
+
# @return [Float, String] the value to store in a Google Sheets cell
|
78
|
+
# Returns a Float if datetime is not nil; otherwise, returns an empty string.
|
79
|
+
#
|
80
|
+
def datetime_to_gs(datetime)
|
81
|
+
return '' unless datetime
|
82
|
+
|
83
|
+
time = datetime.to_time.in_time_zone(spreadsheet_tz)
|
84
|
+
unix_to_gs_epoch(replace_time_zone(time, 'UTC').to_i)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Convert a Google Sheets date time value to a DateTime object
|
88
|
+
#
|
89
|
+
# Time is rounded to the nearest second. The DateTime object returned is in
|
90
|
+
# the spreadsheet's time zone given in the initiaizer.
|
91
|
+
#
|
92
|
+
# @example
|
93
|
+
# time_zone = 'America/Los_Angeles'
|
94
|
+
# converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
|
95
|
+
# gs_value = 44333.191666666666
|
96
|
+
# converter.gs_to_datetime(gs_value) #=> #<DateTime: 2021-05-17T04:35:59-07:00 ...>
|
97
|
+
#
|
98
|
+
# @param gs_datetime [Float, "", nil] the value from the Google Sheets cell
|
99
|
+
#
|
100
|
+
# @return [DateTime, nil] the value represented by gs_datetime
|
101
|
+
# Returns a DateTime object if a Float was given; otherwise, returns nil if an
|
102
|
+
# empty string or nil was given.
|
103
|
+
#
|
104
|
+
def gs_to_datetime(gs_datetime)
|
105
|
+
return nil if gs_datetime.nil? || gs_datetime == ''
|
106
|
+
|
107
|
+
raise 'gs_datetime is a string' if gs_datetime.is_a?(String)
|
108
|
+
|
109
|
+
unix_epoch_datetime = gs_to_unix_epoch(gs_datetime.to_f)
|
110
|
+
time = Time.at_without_coercion(unix_epoch_datetime, in: 'UTC')
|
111
|
+
replace_time_zone(time, spreadsheet_tz).to_datetime
|
112
|
+
end
|
113
|
+
|
114
|
+
# Convert a Ruby Date object to a Google Sheets date value
|
115
|
+
#
|
116
|
+
# The Google Sheets date value is a float.
|
117
|
+
#
|
118
|
+
# @example with a Date object
|
119
|
+
# time_zone = 'America/Los_Angeles'
|
120
|
+
# converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
|
121
|
+
# date = Date.parse('2021-05-17')
|
122
|
+
# converter.date_to_gs(date) #=> 44333
|
123
|
+
#
|
124
|
+
# @example with a DateTime object
|
125
|
+
# time_zone = 'America/Los_Angeles'
|
126
|
+
# converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
|
127
|
+
# date_time = DateTime.parse('2021-05-17 11:36:00 UTC')
|
128
|
+
# converter.date_to_gs(date_time) #=> 44333
|
129
|
+
#
|
130
|
+
# @param date [DateTime, Date, nil] the date to convert
|
131
|
+
#
|
132
|
+
# @return [Float, String] the value to sstore in a Google Sheets cell
|
133
|
+
# Returns a Float if date is not nil; otherwise, returns an empty string
|
134
|
+
#
|
135
|
+
def date_to_gs(date)
|
136
|
+
return datetime_to_gs(date).to_i if date.is_a?(DateTime)
|
137
|
+
|
138
|
+
return (date - gs_epoch_start_date).to_i if date.is_a?(Date)
|
139
|
+
|
140
|
+
''
|
141
|
+
end
|
142
|
+
|
143
|
+
# Convert a Google Sheets date value to a Ruby Date object
|
144
|
+
#
|
145
|
+
# @example with a Date value
|
146
|
+
# time_zone = 'America/Los_Angeles'
|
147
|
+
# converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
|
148
|
+
# gs_value = 44333
|
149
|
+
# converter.gs_to_date(gs_value) #=> #<Date: 2021-05-17 ...>
|
150
|
+
#
|
151
|
+
# @example with a Date and Time value
|
152
|
+
# time_zone = 'America/Los_Angeles'
|
153
|
+
# converter = SheetsV4::ConvertDatesAndTimes.new(time_zone)
|
154
|
+
# gs_value = 44333.191666666666
|
155
|
+
# converter.gs_to_date(gs_value) #=> #<Date: 2021-05-17 ...>
|
156
|
+
#
|
157
|
+
# @param gs_date [Float, "", nil] the value from the Google Sheets cell
|
158
|
+
#
|
159
|
+
# @return [Date, nil] the value represented by gs_date
|
160
|
+
# Returns a Date object if a Float was given; otherwise, returns nil if an
|
161
|
+
# empty string or nil was given.
|
162
|
+
#
|
163
|
+
def gs_to_date(gs_date)
|
164
|
+
return nil if gs_date.nil? || gs_date == ''
|
165
|
+
|
166
|
+
raise 'gs_date is a string' if gs_date.is_a?(String)
|
167
|
+
|
168
|
+
(gs_epoch_start_date + gs_date.to_i)
|
169
|
+
end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
# The Google Sheets epoch start Date to use when calculating dates
|
174
|
+
# @return [Date] the 'zero' Date
|
175
|
+
# @api private
|
176
|
+
def gs_epoch_start_date
|
177
|
+
@gs_epoch_start_date ||= Date.parse('1899-12-30')
|
178
|
+
end
|
179
|
+
|
180
|
+
# The number of seconds in a day
|
181
|
+
#
|
182
|
+
# @return [Integer] number of seconds in a day
|
183
|
+
#
|
184
|
+
SECONDS_PER_DAY = 86_400
|
185
|
+
|
186
|
+
# The number of seconds between the Google Sheets Epoch and Unix Epoch
|
187
|
+
#
|
188
|
+
# Effectively the number of seconds between 1899-12-30 00:00:00 UTC and
|
189
|
+
# 1970-01-01 00:00:00 UTC.
|
190
|
+
#
|
191
|
+
# @return [Integer] the number of seconds
|
192
|
+
#
|
193
|
+
SECONDS_BETWEEN_GS_AND_UNIX_EPOCHS = 25_569 * 86_400
|
194
|
+
|
195
|
+
# Convert a gs_datetime to unix time
|
196
|
+
#
|
197
|
+
# @param gs_datetime [Float] time relative to the Google Sheets Epoch
|
198
|
+
#
|
199
|
+
# gs_datetime is a float representing the number of days since the start of
|
200
|
+
# the Google Sheets epoch (1899-12-30 00:00:00 UTC).
|
201
|
+
#
|
202
|
+
# @return [Integer] the same datetime in Unix Time rounded to the nearest second
|
203
|
+
#
|
204
|
+
# Unix time is an integer representing the number of seconds since the start of
|
205
|
+
# the Unix Epoch (1970-01-01 00:00:00 UTC). The number returned is rounded
|
206
|
+
# to the nearest second.
|
207
|
+
#
|
208
|
+
# @api private
|
209
|
+
#
|
210
|
+
def gs_to_unix_epoch(gs_datetime)
|
211
|
+
(gs_datetime * SECONDS_PER_DAY).round - SECONDS_BETWEEN_GS_AND_UNIX_EPOCHS
|
212
|
+
end
|
213
|
+
|
214
|
+
# Convert a unix time to gs_datetime
|
215
|
+
#
|
216
|
+
# @param unix_time [Integer] seconds since the Unix epoch 1970-01-01 00:00:00 UTC
|
217
|
+
#
|
218
|
+
# @return [Float] days since the Google Sheets epoch 1899-12-30 00:00:00 UTC
|
219
|
+
#
|
220
|
+
# @api private
|
221
|
+
#
|
222
|
+
def unix_to_gs_epoch(unix_time)
|
223
|
+
(unix_time + SECONDS_BETWEEN_GS_AND_UNIX_EPOCHS).to_f / SECONDS_PER_DAY
|
224
|
+
end
|
225
|
+
|
226
|
+
# Given a time, change the time zone without impacting the displayed date/time
|
227
|
+
#
|
228
|
+
# @example
|
229
|
+
# replace_time_zone(Time.parse('2021-05-21 11:40 UTC'), 'America/Los_Angeles')
|
230
|
+
# #=> '2021-05-21 11:40 -0700'
|
231
|
+
#
|
232
|
+
# @param time [Time] the time object to adjust
|
233
|
+
# @param time_zone_name [String] the desired time zone
|
234
|
+
#
|
235
|
+
# @return [Time] the resulting time object
|
236
|
+
#
|
237
|
+
# @api private
|
238
|
+
#
|
239
|
+
def replace_time_zone(time, time_zone_name)
|
240
|
+
time.asctime.in_time_zone(time_zone_name)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
@@ -5,7 +5,7 @@ require 'json_schemer'
|
|
5
5
|
module SheetsV4
|
6
6
|
# Creates a Google API credential with an access token
|
7
7
|
#
|
8
|
-
class
|
8
|
+
class CreateCredential
|
9
9
|
# Creates a Google API credential with an access token
|
10
10
|
#
|
11
11
|
# This wraps the boiler plate code into one function to make constructing a
|
@@ -14,7 +14,7 @@ module SheetsV4
|
|
14
14
|
# @example Constructing a credential from the contents of ~/.credential
|
15
15
|
# credential_source = File.read(File.join(Dir.home, '.credential'))
|
16
16
|
# scope = Google::Apis::SheetsV4::AUTH_SPREADSHEETS
|
17
|
-
# credential =
|
17
|
+
# credential = SheetsV4::CreateCredential.call(credential_source, scope)
|
18
18
|
#
|
19
19
|
# @param [Google::Auth::*, String, IO, nil] credential_source may be one of four things:
|
20
20
|
# (1) a previously created credential that you want to reuse, (2) a credential read
|