flycal-cli 0.7.7 → 1.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -181
  3. data/bin/flycal +2 -2
  4. data/config/defaults.yml +2 -28
  5. data/lib/flycal/cache_annotator.rb +83 -0
  6. data/lib/flycal/cache_key.rb +82 -0
  7. data/lib/flycal/calendar_query.rb +60 -0
  8. data/lib/{flycal_cli → flycal}/calendar_service.rb +48 -4
  9. data/lib/{flycal_cli/cli.rb → flycal/cli/app.rb} +246 -171
  10. data/lib/{flycal_cli → flycal/cli}/auth.rb +5 -3
  11. data/lib/{flycal_cli → flycal/cli}/clipboard.rb +3 -1
  12. data/lib/{flycal_cli → flycal/cli}/config.rb +6 -3
  13. data/lib/flycal/cli/file_cache.rb +44 -0
  14. data/lib/flycal/cli/locale.rb +58 -0
  15. data/lib/flycal/cli.rb +14 -0
  16. data/lib/flycal/client.rb +141 -0
  17. data/lib/flycal/credentials.rb +34 -0
  18. data/lib/{flycal_cli → flycal}/date_time_parser.rb +4 -4
  19. data/lib/flycal/default_slots.rb +77 -0
  20. data/lib/{flycal_cli → flycal}/description_query.rb +3 -3
  21. data/lib/{flycal_cli → flycal}/duration_parser.rb +3 -3
  22. data/lib/flycal/error.rb +5 -0
  23. data/lib/flycal/event_mapper.rb +58 -0
  24. data/lib/{flycal_cli → flycal}/locale.rb +35 -3
  25. data/lib/{flycal_cli → flycal}/mock/calendar_service.rb +1 -1
  26. data/lib/{flycal_cli → flycal}/mock/config.rb +14 -14
  27. data/lib/{flycal_cli → flycal}/mock/event_generator.rb +6 -4
  28. data/lib/flycal/mock/slot_scenario.rb +75 -0
  29. data/lib/flycal/mock.rb +6 -0
  30. data/lib/{flycal_cli → flycal}/pipeline/aggregator.rb +21 -24
  31. data/lib/{flycal_cli → flycal}/pipeline/json_renderer.rb +16 -17
  32. data/lib/{flycal_cli → flycal}/pipeline/params.rb +1 -1
  33. data/lib/{flycal_cli → flycal}/pipeline/renderer.rb +4 -4
  34. data/lib/{flycal_cli → flycal}/pipeline/retriever.rb +1 -1
  35. data/lib/{flycal_cli → flycal}/pipeline/search_pipeline.rb +1 -1
  36. data/lib/{flycal_cli → flycal}/pipeline/text_renderer.rb +14 -12
  37. data/lib/flycal/pipeline.rb +6 -0
  38. data/lib/{flycal_cli → flycal}/slot_finder.rb +12 -12
  39. data/lib/flycal/slot_formatter.rb +196 -0
  40. data/lib/flycal/slot_query.rb +74 -0
  41. data/lib/flycal/slot_templates.rb +129 -0
  42. data/lib/flycal/time_format.rb +36 -0
  43. data/lib/flycal/version.rb +11 -0
  44. data/lib/flycal.rb +12 -0
  45. data/lib/flycal_cli.rb +42 -17
  46. data/mcp/tools.json +21 -8
  47. metadata +41 -25
  48. data/lib/flycal_cli/mock.rb +0 -10
  49. data/lib/flycal_cli/pipeline.rb +0 -14
  50. data/lib/flycal_cli/slot_formatter.rb +0 -68
  51. data/lib/flycal_cli/version.rb +0 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db8d0ab49b0872b92c469825aff6a0a944f9012eed76c8bc0768bc82156de1f9
4
- data.tar.gz: 16e758564b7adee8f7ea53b79c856865105afc26aa8489abe73313906f35ceb5
3
+ metadata.gz: fcbd8c2cfb6f90272cf8cc8cfa489bc99a7487efca11f68560b2d7f96ac92f34
4
+ data.tar.gz: 21e92239db0dc5b3f587abdd1e859b5e67e67293706f6ec1f50ca34ca4fe0e1d
5
5
  SHA512:
6
- metadata.gz: 9485b33d254189f6f96d4c96f9e8867b047b35354fc60f63da86b8bd84612686afb559b6d5d937b98f41ac091777fd32a25ef1d0827a0cc6f4eb7f11be2287e5
7
- data.tar.gz: c9b338282c8fa1ecc7a6b979d6c7c8632fcfe03ae598affdb27b98e603545dfb85e688d9d1180569bea2106094322fb9b664f48db89a731dade13d6c80ed2fe9
6
+ metadata.gz: 2524fb58d1faecdf6ac6643761315f2586f6fe10b4535f2f50b87418f871665f13c84110c3d627ad2131bb9b2ef95aa171c0e6e1e41b52f87d7b76fd7261493c
7
+ data.tar.gz: 906c85b9dd3711aa16874a6f41be9851be1accbd07d131b09fb9f6173ed26d0e081f6607b457d606f538ea8861ab98dcff89b6cdab4f482b79ffe0c8af41eb7b
data/README.md CHANGED
@@ -1,188 +1,13 @@
1
1
  # flycal-cli
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/flycal-cli.svg)](https://badge.fury.io/rb/flycal-cli)
3
+ CLI wrapper around **Flycal** core (`lib/flycal` in the monorepo).
4
4
 
5
- Read your Google Calendar from the terminal: search events, find free slots, copy them into an email. **Read-only** — nothing is modified on the calendar.
6
-
7
- ## Quick start
8
-
9
- ```bash
10
- gem install flycal-cli
11
- flycal login
12
- ```
13
-
14
- During the test phase the project already provides the Google API credentials.
15
- Open the browser, approve access, done.
16
-
17
- Optional but useful once:
18
-
19
- ```bash
20
- flycal config # set default calendar + calendars that block free slots
21
- flycal calendars # list calendars (name + id)
22
- ```
23
-
24
- ## Common examples
25
-
26
- **Find free slots (most useful day-to-day)**
27
-
28
- ```bash
29
- flycal slots
30
- flycal slots --in "5 days"
31
- flycal slots --from monday --in "5 days"
32
- flycal slots --from "next monday" --in "2 weeks" --duration 1h
33
- flycal slots --in "12 days" --template dinner
34
- flycal slots --locale it --from lunedi --in "5 days"
35
- ```
36
-
37
- Output is a short header plus a clean list ready to paste (and copied to the clipboard when `pbcopy` / `clip` / `wl-copy` / `xclip` is available):
38
-
39
- ```
40
- found 6 slots
41
- from sat 1 August 2026 to sat 8 August 2026
42
- with duration 45min, template dinner
43
- considering calendars
44
- - Work
45
- - Personal
46
- link: https://calendar.google.com/calendar/r/day/2026/8/1
47
-
48
- saturday 1/8
49
- 19 - 23
50
-
51
- monday 3/8
52
- 19 - 23
53
- ```
54
-
55
- **Search events**
56
-
57
- ```bash
58
- flycal search
59
- flycal search --in 30days
60
- flycal search --in 30days --description meeting
61
- flycal search --from 2026-08-01 --to 2026-08-31 -c Work
62
- flycal search --from monday --in "2 weeks" --locale it
63
- ```
64
-
65
- **Other commands**
66
-
67
- ```bash
68
- flycal version
69
- flycal update
70
- flycal logout
71
- ```
72
-
73
- All commands accept `--locale en|it`.
74
-
75
- ---
76
-
77
- ## Configuration (`~/.flycal/config.yml`)
78
-
79
- Defaults are created automatically on first run. Edit with `flycal config` → `edit config`, or by hand.
80
-
81
- ```yaml
82
- calendar_default: ~
83
- locale: en
84
- slots:
85
- templates:
86
- work:
87
- days: [1, 2, 3, 4, 5] # 1=Mon … 7=Sun
88
- hours:
89
- - 9:30-13:00
90
- - 14:00-18:30
91
- dinner:
92
- days: [1, 2, 3, 4, 5, 6, 7]
93
- hours:
94
- - 19-23
95
- exclude_calendars: [] # calendars whose events block free slots
96
- defaults:
97
- from: now
98
- default_duration: 45min
99
- free_before: 0m
100
- free_after: 15m
101
- ```
102
-
103
- | Key | Meaning |
104
- |---|---|
105
- | `calendar_default` | Default calendar (`~` = unset) |
106
- | `locale` | `en` or `it` |
107
- | `slots.templates` | Named schedules; `flycal slots` uses the first one unless `--template` is set |
108
- | `slots.exclude_calendars` | Events here block free slots (fallback: `calendar_default`) |
109
- | `slots.defaults.*` | Defaults for `--from` / `--duration` |
110
- | `slots.free_before` / `free_after` | Buffers around busy events |
111
-
112
- Interactive helpers:
5
+ - Core namespace: `Flycal`
6
+ - CLI namespace: `Flycal::Cli`
7
+ - Gem name: `flycal-cli`
113
8
 
114
9
  ```bash
115
- flycal config
10
+ ruby -Ilib -Iflycal_cli/lib flycal_cli/bin/flycal --help
116
11
  ```
117
12
 
118
- - `calendar_default` pick the default calendar
119
- - `exclude_calendars` — multi-select calendars that block slots
120
- - `edit config` — open the YAML in `$EDITOR`
121
-
122
- ---
123
-
124
- ## Parameters (reference)
125
-
126
- ### `flycal slots`
127
-
128
- | Option | Default | Notes |
129
- |---|---|---|
130
- | `--duration` | `45min` | Min free range length (`1h`, `90min`, …) |
131
- | `--from` / `-f` | `now` | Absolute date or relative: `monday`, `next monday`, `tomorrow`, `lunedi`, … |
132
- | `--in` / `-i` | `1 week` | Window from `--from` (`"5 days"`, `2weeks`, …) |
133
- | `--template` / `-T` | first template (`work`) | e.g. `dinner` |
134
- | `--calendar` / `-c` | — | Fallback if `exclude_calendars` is empty |
135
- | `--locale` | config / `en` | `en` or `it` |
136
-
137
- ### `flycal search`
138
-
139
- | Option | Default | Notes |
140
- |---|---|---|
141
- | `--from` / `-f` | today midnight | Absolute or relative date |
142
- | `--to` / `-t` | +30 days | Ignored if `--in` is set |
143
- | `--in` / `-i` | — | Duration from `--from` |
144
- | `--description` / `-d` | — | Contains filter; OR terms with `\|` (e.g. `rui\|solver`) |
145
- | `--groupBy` | auto | `day`, `week`, `month`, or `description` (split `--description` terms) |
146
- | `--calendar` / `-c` | `calendar_default` | Name or ID |
147
- | `--locale` | config / `en` | `en` or `it` |
148
- | `--format` | `text` | Output format: `text` or `json` |
149
- | `--mockTemplate` | — | Load mock defaults from `mocks/` or `mockTemplates/<name>.json` (no Google API) |
150
- | `--mockCalendar` | from template | Generated mock calendar name/id (required in template or CLI) |
151
- | `--mockSeed` | random | Reproducible mock distribution (printed in summary) |
152
-
153
- Mock templates live in `mocks/` or `mockTemplates/` (example: `mock1.json` with `mockCalendar`). CLI mock flags override template values. Search `--from` / `--to` keep the normal defaults (today → +30 days), independent of the mock generation window.
154
-
155
- Relative dates work in English and Italian: `today`/`oggi`, `tomorrow`/`domani`, `monday`/`lunedi`, `next monday`/`prossimo lunedi`, `last friday`/`scorso venerdi`.
156
-
157
- ---
158
-
159
- ## Optional: your own Google Cloud credentials
160
-
161
- **Not needed** for the default test API — `flycal login` is enough.
162
-
163
- Use this only if you want flycal to talk to **your own** Google Cloud OAuth client instead of the project-provided test credentials.
164
-
165
- 1. [Google Cloud Console → Credentials](https://console.cloud.google.com/apis/credentials)
166
- 2. Enable **Google Calendar API**
167
- 3. Create **Desktop app** OAuth credentials
168
- 4. Authorized redirect URI: `http://127.0.0.1:9292/oauth2callback`
169
- 5. Save the JSON as `~/.flycal/credentials.json`
170
- 6. Run `flycal login`
171
-
172
- You can also ask a teammate who manages the Cloud project to share that JSON with you.
173
-
174
- ---
175
-
176
- ## Files
177
-
178
- | Path | Purpose |
179
- |---|---|
180
- | `~/.flycal/config.yml` | Settings (calendars, templates, locale, …) |
181
- | `~/.flycal/credentials.json` | Own OAuth client (optional) |
182
- | `~/.flycal/tokens.yml` | Login tokens (managed automatically) |
183
-
184
- MCP catalog for agents: [`mcp/tools.json`](mcp/tools.json)
185
-
186
- ## License
187
-
188
- MIT
13
+ Release from the Rails app root: `bin/release-cli`
data/bin/flycal CHANGED
@@ -4,8 +4,8 @@
4
4
  require "flycal_cli"
5
5
 
6
6
  if ARGV.include?("--version") || ARGV.include?("-v")
7
- puts "flycal #{FlycalCli::VERSION}"
7
+ puts "flycal #{Flycal::VERSION}"
8
8
  exit 0
9
9
  end
10
10
 
11
- FlycalCli::Cli.start(ARGV)
11
+ Flycal::Cli::App.start(ARGV)
data/config/defaults.yml CHANGED
@@ -1,30 +1,4 @@
1
1
  calendar_default: ~
2
2
  locale: en
3
- slots:
4
- templates:
5
- work:
6
- days:
7
- - 1
8
- - 2
9
- - 3
10
- - 4
11
- - 5
12
- hours:
13
- - 9:30-13:00
14
- - 14:00-18:30
15
- dinner:
16
- days:
17
- - 1
18
- - 2
19
- - 3
20
- - 4
21
- - 5
22
- - 6
23
- hours:
24
- - 19-23
25
- exclude_calendars: []
26
- defaults:
27
- from: now
28
- default_duration: 45min
29
- free_before: 0m
30
- free_after: 15m
3
+ # `slots` is injected from Flycal::DefaultSlots at load/setup time.
4
+ # After first write, the CLI reads templates from this file on disk.
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flycal
4
+ # Injects cache HIT/MISS metadata into text / JSON payloads.
5
+ module CacheAnnotator
6
+ module_function
7
+
8
+ def line(status, _cache_key = nil)
9
+ "Cache: #{status}"
10
+ end
11
+
12
+ def annotate_text(text, status, cache_key = nil)
13
+ lines = text.to_s.sub(/\n\z/, "").split("\n")
14
+ insert_cache_line!(lines, status, cache_key, fallback: :append)
15
+ "#{lines.join("\n")}\n"
16
+ end
17
+
18
+ def annotate_payload!(payload, status, cache_key)
19
+ payload = stringify_keys(payload)
20
+ info = payload["info"] ||= {}
21
+ info["cache"] = status
22
+ info.delete("cache_key")
23
+
24
+ if payload["text"].is_a?(Array)
25
+ lines = payload["text"].map(&:to_s)
26
+ insert_cache_line!(lines, status, cache_key, fallback: :unshift)
27
+ payload["text"] = lines
28
+ end
29
+
30
+ payload
31
+ end
32
+
33
+ def strip_for_storage(payload)
34
+ copy = Marshal.load(Marshal.dump(stringify_keys(payload)))
35
+ if copy["info"].is_a?(Hash)
36
+ copy["info"] = copy["info"].dup
37
+ copy["info"].delete("cache")
38
+ copy["info"].delete("cache_key")
39
+ end
40
+ if copy["text"].is_a?(Array)
41
+ copy["text"] = copy["text"].reject { |l| l.to_s.start_with?("Cache:") }
42
+ end
43
+ copy
44
+ end
45
+
46
+ def stringify_keys(value)
47
+ case value
48
+ when Hash
49
+ value.each_with_object({}) { |(k, v), out| out[k.to_s] = stringify_keys(v) }
50
+ when Array
51
+ value.map { |v| stringify_keys(v) }
52
+ else
53
+ value
54
+ end
55
+ end
56
+
57
+ def insert_cache_line!(lines, status, cache_key = nil, fallback: :append)
58
+ lines.reject! { |l| l.to_s.start_with?("Cache:") }
59
+ marker = line(status, cache_key)
60
+
61
+ occupied_idx = lines.index { |l| l.to_s.start_with?("Total time occupied:") }
62
+ if occupied_idx
63
+ lines.insert(occupied_idx + 1, marker)
64
+ return lines
65
+ end
66
+
67
+ idx = lines.index("---")
68
+ if idx
69
+ lines.insert(idx + 1, marker)
70
+ else
71
+ blank_idx = lines.index { |l| l == "" }
72
+ if blank_idx
73
+ lines.insert(blank_idx, marker)
74
+ elsif fallback == :unshift
75
+ lines.unshift(marker)
76
+ else
77
+ lines << "---" << marker
78
+ end
79
+ end
80
+ lines
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "json"
5
+
6
+ module Flycal
7
+ # Builds stable cache keys for search/slots from fully resolved params
8
+ # (defaults applied, just before Google API calls).
9
+ class CacheKey
10
+ DEFAULT_TTL_MINUTES = 5
11
+
12
+ # Commands / CLI verbs / API paths that use response caching.
13
+ # Everything else (version, calendars, login, config, …) is uncached.
14
+ CACHED_COMMANDS = %w[search slots].freeze
15
+
16
+ SKIP_KEYS = %w[
17
+ nocache cache_status cache_key events totals groups calendar_names raw
18
+ ].freeze
19
+
20
+ class << self
21
+ def cached_commands
22
+ CACHED_COMMANDS
23
+ end
24
+
25
+ def cached?(command)
26
+ CACHED_COMMANDS.include?(command.to_s)
27
+ end
28
+
29
+ def generate(command, params)
30
+ digest = Digest::SHA256.hexdigest(canonical_json(params))
31
+ "flycal-#{command}-#{digest}"
32
+ end
33
+
34
+ def canonical_json(params)
35
+ JSON.generate(deep_sort(normalize(params)))
36
+ end
37
+
38
+ def normalize(value)
39
+ case value
40
+ when Hash
41
+ value.each_with_object({}) do |(k, v), out|
42
+ key = k.to_s
43
+ next if SKIP_KEYS.include?(key)
44
+
45
+ out[key] = normalize(v)
46
+ end
47
+ when Array
48
+ value.map { |v| normalize(v) }
49
+ when Time, DateTime
50
+ value.respond_to?(:iso8601) ? value.iso8601 : value.to_s
51
+ when Date
52
+ value.iso8601
53
+ when nil, TrueClass, FalseClass, Numeric, String
54
+ value
55
+ else
56
+ if value.respond_to?(:iso8601)
57
+ value.iso8601
58
+ elsif value.respond_to?(:to_h) && !value.is_a?(String)
59
+ normalize(value.to_h)
60
+ else
61
+ value.to_s
62
+ end
63
+ end
64
+ end
65
+
66
+ def deep_sort(value)
67
+ case value
68
+ when Hash
69
+ value.keys.map(&:to_s).sort.each_with_object({}) do |k, out|
70
+ # Hash may have symbol keys after normalize we use strings
71
+ raw_key = value.key?(k) ? k : value.keys.find { |x| x.to_s == k }
72
+ out[k] = deep_sort(value[raw_key])
73
+ end
74
+ when Array
75
+ value.map { |v| deep_sort(v) }
76
+ else
77
+ value
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Flycal
4
+ # Parses and resolves calendar selection options for CLI and HTTP API.
5
+ #
6
+ # Accepts:
7
+ # - a single string: "Work"
8
+ # - pipe/comma OR list: "Work|Personal" or "Work,Personal"
9
+ # - an array (e.g. repeated --calendar flags): ["Work", "Personal"]
10
+ # - mixed: ["Work|Gym", "Personal"]
11
+ module CalendarQuery
12
+ module_function
13
+
14
+ # Expand raw option value(s) into calendar reference strings.
15
+ def refs(value)
16
+ Array(value).flat_map { |entry| entry.to_s.split(/[|,]/) }
17
+ .map(&:strip)
18
+ .reject(&:empty?)
19
+ .uniq
20
+ end
21
+
22
+ # Resolve refs to calendar IDs against a list of calendar objects
23
+ # (must respond to #id / #summary, or hash with those keys).
24
+ def resolve_ids(calendars, value)
25
+ refs(value).filter_map { |ref| resolve_ref(calendars, ref) }.uniq
26
+ end
27
+
28
+ def resolve_ref(calendars, ref)
29
+ ref = ref.to_s.strip
30
+ return nil if ref.empty?
31
+
32
+ exact = calendars.find { |cal| calendar_id(cal) == ref }
33
+ return calendar_id(exact) if exact
34
+
35
+ exact_name = calendars.find { |cal| calendar_summary(cal).to_s.casecmp?(ref) }
36
+ return calendar_id(exact_name) if exact_name
37
+
38
+ needle = ref.downcase
39
+ partial = calendars.find do |cal|
40
+ summary = calendar_summary(cal).to_s
41
+ !summary.empty? && summary.downcase.include?(needle)
42
+ end
43
+ calendar_id(partial)
44
+ end
45
+
46
+ def calendar_id(cal)
47
+ return nil if cal.nil?
48
+ return cal.id if cal.respond_to?(:id)
49
+
50
+ cal[:id] || cal["id"]
51
+ end
52
+
53
+ def calendar_summary(cal)
54
+ return nil if cal.nil?
55
+ return cal.summary if cal.respond_to?(:summary)
56
+
57
+ cal[:summary] || cal["summary"] || cal[:name] || cal["name"]
58
+ end
59
+ end
60
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "google/apis/calendar_v3"
4
4
 
5
- module FlycalCli
5
+ module Flycal
6
6
  class CalendarService
7
7
  def initialize(credentials)
8
8
  @service = Google::Apis::CalendarV3::CalendarService.new
@@ -18,6 +18,10 @@ module FlycalCli
18
18
  @service.get_calendar(calendar_id)
19
19
  end
20
20
 
21
+ def get_event(calendar_id, event_id)
22
+ @service.get_event(calendar_id, event_id)
23
+ end
24
+
21
25
  def list_events(calendar_id, time_min:, time_max:, query: nil)
22
26
  events = []
23
27
  page_token = nil
@@ -25,8 +29,8 @@ module FlycalCli
25
29
  loop do
26
30
  result = @service.list_events(
27
31
  calendar_id,
28
- time_min: time_min.iso8601,
29
- time_max: time_max.iso8601,
32
+ time_min: iso8601(time_min),
33
+ time_max: iso8601(time_max),
30
34
  q: query,
31
35
  single_events: true,
32
36
  order_by: "startTime",
@@ -52,7 +56,7 @@ module FlycalCli
52
56
  begin
53
57
  events = list_events(cal_id, time_min: time_min, time_max: time_max, query: nil)
54
58
  events.each { |e| all_events << { calendar_id: cal_id, event: e } }
55
- rescue Google::Apis::Errors::Error => e
59
+ rescue Google::Apis::Error => e
56
60
  warn "Error in calendar #{cal_id}: #{e.message}"
57
61
  end
58
62
  end
@@ -67,5 +71,45 @@ module FlycalCli
67
71
 
68
72
  all_events
69
73
  end
74
+
75
+ def create_event(calendar_id: "primary", summary:, start_time:, end_time:, all_day: false)
76
+ event = Google::Apis::CalendarV3::Event.new(summary: summary)
77
+ apply_event_times!(event, start_time: start_time, end_time: end_time, all_day: all_day)
78
+ @service.insert_event(calendar_id, event)
79
+ end
80
+
81
+ def update_event(calendar_id: "primary", event_id:, summary:, start_time:, end_time:, all_day: false)
82
+ event = get_event(calendar_id, event_id)
83
+ event.summary = summary if summary && !summary.to_s.empty?
84
+ apply_event_times!(event, start_time: start_time, end_time: end_time, all_day: all_day)
85
+ @service.update_event(calendar_id, event_id, event)
86
+ end
87
+
88
+ private
89
+
90
+ def iso8601(value)
91
+ return value.iso8601 if value.respond_to?(:iso8601)
92
+ return value.to_time.iso8601 if value.respond_to?(:to_time)
93
+
94
+ value.to_s
95
+ end
96
+
97
+ def apply_event_times!(event, start_time:, end_time:, all_day:)
98
+ if all_day
99
+ event.start = Google::Apis::CalendarV3::EventDateTime.new(
100
+ date: Date.parse(start_time.to_s).to_s
101
+ )
102
+ event.end = Google::Apis::CalendarV3::EventDateTime.new(
103
+ date: Date.parse((end_time || start_time).to_s).to_s
104
+ )
105
+ else
106
+ event.start = Google::Apis::CalendarV3::EventDateTime.new(
107
+ date_time: DateTime.parse(start_time.to_s).rfc3339
108
+ )
109
+ event.end = Google::Apis::CalendarV3::EventDateTime.new(
110
+ date_time: DateTime.parse((end_time || start_time).to_s).rfc3339
111
+ )
112
+ end
113
+ end
70
114
  end
71
115
  end