cal-invite 0.1.3 → 0.1.5

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.
@@ -3,7 +3,35 @@
3
3
  # lib/cal_invite/providers/ical.rb
4
4
  module CalInvite
5
5
  module Providers
6
+ # iCalendar format provider for generating ICS (iCalendar) content.
7
+ # This provider generates calendar content following the iCalendar specification (RFC 5545).
8
+ # It supports single events, all-day events, and multi-day sessions with proper timezone handling.
9
+ #
10
+ # @example Creating a regular event
11
+ # event = CalInvite::Event.new(
12
+ # title: "Team Meeting",
13
+ # start_time: Time.now,
14
+ # end_time: Time.now + 3600,
15
+ # timezone: 'America/New_York'
16
+ # )
17
+ # ical = CalInvite::Providers::Ical.new(event)
18
+ # ics_content = ical.generate
19
+ #
20
+ # @example Creating a multi-day event
21
+ # event = CalInvite::Event.new(
22
+ # title: "Conference",
23
+ # multi_day_sessions: [
24
+ # { start_time: Time.now, end_time: Time.now + 3600 },
25
+ # { start_time: Time.now + 86400, end_time: Time.now + 90000 }
26
+ # ]
27
+ # )
28
+ # ics_content = CalInvite::Providers::Ical.new(event).generate
6
29
  class Ical < BaseProvider
30
+ # Generates an iCalendar format string containing the event details.
31
+ # Includes proper calendar headers, timezone information (if needed),
32
+ # and one or more event blocks.
33
+ #
34
+ # @return [String] The complete iCalendar format content
7
35
  def generate
8
36
  [
9
37
  "BEGIN:VCALENDAR",
@@ -19,6 +47,10 @@ module CalInvite
19
47
 
20
48
  private
21
49
 
50
+ # Generates the event blocks (VEVENT) for the calendar.
51
+ # Handles both single events and multi-day sessions.
52
+ #
53
+ # @return [String] The formatted event block(s)
22
54
  def generate_events
23
55
  if event.multi_day_sessions.any?
24
56
  event.multi_day_sessions.map { |session| generate_vevent(session) }.join("\r\n")
@@ -27,22 +59,25 @@ module CalInvite
27
59
  end
28
60
  end
29
61
 
62
+ # Generates a single VEVENT block with all event details.
63
+ # Handles both all-day events and time-specific events.
64
+ #
65
+ # @param session [Hash, nil] Optional session details for multi-day events
66
+ # @return [String] The formatted VEVENT block
67
+ # @raise [ArgumentError] If required time fields are missing for non-all-day events
30
68
  def generate_vevent(session = nil)
31
69
  lines = ["BEGIN:VEVENT"]
32
70
 
33
71
  if event.all_day
34
72
  start_date = event.start_time || Time.now
35
73
  end_date = event.end_time || (start_date + 86400)
36
-
37
74
  lines << "DTSTART;VALUE=DATE:#{format_date(start_date)}"
38
75
  lines << "DTEND;VALUE=DATE:#{format_date(end_date)}"
39
76
  else
40
77
  start_time = session ? session[:start_time] : event.start_time
41
78
  end_time = session ? session[:end_time] : event.end_time
42
-
43
79
  raise ArgumentError, "Start time is required for non-all-day events" unless start_time
44
80
  raise ArgumentError, "End time is required for non-all-day events" unless end_time
45
-
46
81
  lines << "DTSTART;TZID=#{event.timezone}:#{format_local_time(start_time)}"
47
82
  lines << "DTEND;TZID=#{event.timezone}:#{format_local_time(end_time)}"
48
83
  end
@@ -70,9 +105,12 @@ module CalInvite
70
105
  lines.join("\r\n")
71
106
  end
72
107
 
108
+ # Generates the timezone block (VTIMEZONE) for the calendar.
109
+ # Only included for non-all-day events.
110
+ #
111
+ # @return [String, nil] The formatted timezone block, or nil for all-day events
73
112
  def generate_timezone
74
113
  return nil if event.all_day # No timezone needed for all-day events
75
-
76
114
  [
77
115
  "BEGIN:VTIMEZONE",
78
116
  "TZID:#{event.timezone}",
@@ -80,26 +118,46 @@ module CalInvite
80
118
  ].join("\r\n")
81
119
  end
82
120
 
121
+ # Generates a unique identifier for the calendar event.
122
+ # Format: timestamp-randomhex@cal-invite
123
+ #
124
+ # @return [String] The generated UID
83
125
  def generate_uid
84
126
  "#{Time.now.to_i}-#{SecureRandom.hex(8)}@cal-invite"
85
127
  end
86
128
 
129
+ # Formats a time object as a date string in iCalendar format.
130
+ #
131
+ # @param time [Time] The time to format
132
+ # @return [String] The formatted date (YYYYMMDD)
87
133
  def format_date(time)
88
134
  time.strftime("%Y%m%d")
89
135
  end
90
136
 
137
+ # Formats a time object as a local time string in iCalendar format.
138
+ # Times are assumed to be in the correct timezone already.
139
+ #
140
+ # @param time [Time] The time to format
141
+ # @return [String] The formatted local time (YYYYMMDDTHHmmSS)
91
142
  def format_local_time(time)
92
- # All times are already in UTC, just format them
93
143
  time.strftime("%Y%m%dT%H%M%S")
94
144
  end
95
145
 
146
+ # Formats a time object as an UTC timestamp in iCalendar format.
147
+ #
148
+ # @param time [Time] The time to format
149
+ # @return [String] The formatted UTC timestamp (YYYYMMDDTHHmmSSZ)
96
150
  def format_timestamp(time)
97
151
  time.strftime("%Y%m%dT%H%M%SZ")
98
152
  end
99
153
 
154
+ # Escapes special characters in text according to iCalendar spec.
155
+ # Handles backslashes, newlines, commas, and semicolons.
156
+ #
157
+ # @param text [String, nil] The text to escape
158
+ # @return [String] The escaped text
100
159
  def escape_text(text)
101
160
  return '' if text.nil?
102
-
103
161
  text.to_s
104
162
  .gsub('\\', '\\\\')
105
163
  .gsub("\n", '\\n')
@@ -3,7 +3,43 @@
3
3
  # lib/cal_invite/providers/ics.rb
4
4
  module CalInvite
5
5
  module Providers
6
+ # Generic ICS provider for generating standard iCalendar (.ics) files.
7
+ # This provider generates ICS files that are compatible with most calendar applications.
8
+ # Supports all-day events, regular events, and multi-day sessions with proper timezone handling.
9
+ #
10
+ # @example Creating a regular event ICS file
11
+ # event = CalInvite::Event.new(
12
+ # title: "Team Meeting",
13
+ # start_time: Time.now,
14
+ # end_time: Time.now + 3600,
15
+ # timezone: 'America/New_York'
16
+ # )
17
+ # ics = CalInvite::Providers::Ics.new(event)
18
+ # ics_content = ics.generate
19
+ #
20
+ # @example Creating an all-day event ICS file
21
+ # event = CalInvite::Event.new(
22
+ # title: "Company Holiday",
23
+ # all_day: true,
24
+ # start_time: Date.today,
25
+ # end_time: Date.today + 1
26
+ # )
27
+ # ics_content = CalInvite::Providers::Ics.new(event).generate
28
+ #
29
+ # @example Creating a multi-day event ICS file
30
+ # event = CalInvite::Event.new(
31
+ # title: "Conference",
32
+ # multi_day_sessions: [
33
+ # { start_time: Time.parse("2024-04-01 09:00"), end_time: Time.parse("2024-04-01 17:00") },
34
+ # { start_time: Time.parse("2024-04-02 09:00"), end_time: Time.parse("2024-04-02 17:00") }
35
+ # ]
36
+ # )
37
+ # ics_content = CalInvite::Providers::Ics.new(event).generate
6
38
  class Ics < BaseProvider
39
+ # Generates the complete ICS calendar content with proper calendar properties.
40
+ # Handles all event types: all-day, regular, and multi-day sessions.
41
+ #
42
+ # @return [String] The complete ICS calendar content in iCalendar format
7
43
  def generate
8
44
  calendar_lines = [
9
45
  "BEGIN:VCALENDAR",
@@ -29,6 +65,9 @@ module CalInvite
29
65
 
30
66
  private
31
67
 
68
+ # Generates an all-day event component (VEVENT) with appropriate formatting.
69
+ #
70
+ # @return [Array<String>] Array of iCalendar format lines for the all-day event
32
71
  def generate_all_day_event
33
72
  vevent = [
34
73
  "BEGIN:VEVENT",
@@ -38,12 +77,16 @@ module CalInvite
38
77
  "DTEND;VALUE=DATE:#{format_date(event.end_time)}",
39
78
  "SUMMARY:#{escape_text(event.title)}"
40
79
  ]
41
-
42
80
  add_optional_fields(vevent)
43
81
  vevent << "END:VEVENT"
44
82
  vevent
45
83
  end
46
84
 
85
+ # Generates a regular or multi-day session event component (VEVENT).
86
+ #
87
+ # @param start_time [Time] The event start time
88
+ # @param end_time [Time] The event end time
89
+ # @return [Array<String>] Array of iCalendar format lines for the event
47
90
  def generate_vevent(start_time, end_time)
48
91
  vevent = [
49
92
  "BEGIN:VEVENT",
@@ -53,16 +96,19 @@ module CalInvite
53
96
  "DTEND;TZID=#{event.timezone}:#{format_local_timestamp(end_time)}",
54
97
  "SUMMARY:#{escape_text(event.title)}"
55
98
  ]
56
-
57
99
  add_optional_fields(vevent)
58
100
  vevent << "END:VEVENT"
59
101
  vevent
60
102
  end
61
103
 
104
+ # Adds optional fields to the event component if they exist.
105
+ # Handles description, location, URL, and attendees.
106
+ #
107
+ # @param vevent [Array<String>] The current event lines array
108
+ # @return [void]
62
109
  def add_optional_fields(vevent)
63
110
  description_parts = []
64
111
  description_parts << format_description if format_description
65
-
66
112
  if description_parts.any?
67
113
  vevent << "DESCRIPTION:#{escape_text(description_parts.join('\n\n'))}"
68
114
  end
@@ -82,23 +128,43 @@ module CalInvite
82
128
  end
83
129
  end
84
130
 
131
+ # Formats a time object as an UTC timestamp in iCalendar format.
132
+ #
133
+ # @param time [Time] The time to format
134
+ # @return [String] The formatted UTC timestamp (YYYYMMDDTHHmmSSZ)
85
135
  def format_timestamp(time)
86
136
  time.utc.strftime("%Y%m%dT%H%M%SZ")
87
137
  end
88
138
 
139
+ # Formats a time object as a date string in iCalendar format.
140
+ #
141
+ # @param time [Time] The time to format
142
+ # @return [String] The formatted date (YYYYMMDD)
89
143
  def format_date(time)
90
144
  time.strftime("%Y%m%d")
91
145
  end
92
146
 
147
+ # Formats a time object as a local timestamp in iCalendar format.
148
+ # Note: Times are expected to be in UTC already.
149
+ #
150
+ # @param time [Time] The time to format
151
+ # @return [String] The formatted local time (YYYYMMDDTHHmmSS)
93
152
  def format_local_timestamp(time)
94
- # All times are already in UTC, just format them
95
153
  time.strftime("%Y%m%dT%H%M%S")
96
154
  end
97
155
 
156
+ # Generates a unique identifier for the calendar event.
157
+ # Format: timestamp-randomhex@cal-invite
158
+ #
159
+ # @return [String] The generated UID
98
160
  def generate_uid
99
161
  "#{Time.now.to_i}-#{SecureRandom.hex(8)}@cal-invite"
100
162
  end
101
163
 
164
+ # Escapes special characters in text according to iCalendar spec.
165
+ #
166
+ # @param text [String, nil] The text to escape
167
+ # @return [String] The escaped text, or empty string if input was nil
102
168
  def escape_text(text)
103
169
  return '' if text.nil?
104
170
  text.to_s
@@ -1,7 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # lib/cal_invite/providers/ics_content.rb
2
4
  module CalInvite
3
5
  module Providers
6
+ # ICS content provider for generating calendar files in iCalendar format.
7
+ # This provider focuses on generating standards-compliant ICS content
8
+ # that can be used directly or wrapped for file download.
9
+ #
10
+ # @example Generate ICS content for a single event
11
+ # event = CalInvite::Event.new(
12
+ # title: "Team Meeting",
13
+ # start_time: Time.now,
14
+ # end_time: Time.now + 3600
15
+ # )
16
+ # generator = CalInvite::Providers::IcsContent.new(event)
17
+ # ics_content = generator.generate
18
+ #
19
+ # @example Generate ICS content for a multi-day event
20
+ # event = CalInvite::Event.new(
21
+ # title: "Conference",
22
+ # multi_day_sessions: [
23
+ # { start_time: Time.parse("2024-04-01 09:00"), end_time: Time.parse("2024-04-01 17:00") },
24
+ # { start_time: Time.parse("2024-04-02 09:00"), end_time: Time.parse("2024-04-02 17:00") }
25
+ # ]
26
+ # )
27
+ # ics_content = CalInvite::Providers::IcsContent.new(event).generate
4
28
  class IcsContent < BaseProvider
29
+ # Generates the complete ICS calendar content with all event details.
30
+ # Handles both single events and multi-day sessions.
31
+ #
32
+ # @return [String] The complete ICS calendar content in iCalendar format
5
33
  def generate
6
34
  calendar_lines = [
7
35
  "BEGIN:VCALENDAR",
@@ -25,6 +53,11 @@ module CalInvite
25
53
 
26
54
  private
27
55
 
56
+ # Generates a single VEVENT component with proper event properties.
57
+ #
58
+ # @param start_time [Time] The event start time
59
+ # @param end_time [Time] The event end time
60
+ # @return [Array<String>] Array of iCalendar format lines for the event
28
61
  def generate_vevent(start_time, end_time)
29
62
  [
30
63
  "BEGIN:VEVENT",
@@ -41,14 +74,26 @@ module CalInvite
41
74
  ].compact
42
75
  end
43
76
 
77
+ # Formats a time object as an UTC timestamp in iCalendar format.
78
+ #
79
+ # @param time [Time] The time to format
80
+ # @return [String] The formatted UTC timestamp (YYYYMMDDTHHmmSSZ)
44
81
  def format_timestamp(time)
45
82
  time.utc.strftime("%Y%m%dT%H%M%SZ")
46
83
  end
47
84
 
85
+ # Generates a unique identifier for the calendar event.
86
+ # Format: timestamp-randomhex@cal-invite
87
+ #
88
+ # @return [String] The generated UID
48
89
  def generate_uid
49
90
  "#{Time.now.to_i}-#{SecureRandom.hex(8)}@cal-invite"
50
91
  end
51
92
 
93
+ # Escapes special characters in text according to iCalendar spec.
94
+ #
95
+ # @param text [String, nil] The text to escape
96
+ # @return [String] The escaped text, or empty string if input was nil
52
97
  def escape_text(text)
53
98
  return '' if text.nil?
54
99
  text.to_s
@@ -58,29 +103,46 @@ module CalInvite
58
103
  .gsub(';', '\\;')
59
104
  end
60
105
 
106
+ # Generates the DESCRIPTION line if a description exists.
107
+ #
108
+ # @return [String, nil] The formatted DESCRIPTION line, or nil if no description
61
109
  def description_line
62
110
  return nil unless event.description
63
111
  "DESCRIPTION:#{escape_text(event.description)}"
64
112
  end
65
113
 
114
+ # Generates the LOCATION line if a location exists.
115
+ #
116
+ # @return [String, nil] The formatted LOCATION line, or nil if no location
66
117
  def location_line
67
118
  return nil unless event.location
68
119
  "LOCATION:#{escape_text(event.location)}"
69
120
  end
70
121
 
122
+ # Generates the URL line if a URL exists.
123
+ #
124
+ # @return [String, nil] The formatted URL line, or nil if no URL
71
125
  def url_line
72
126
  return nil unless event.url
73
127
  "URL:#{escape_text(event.url)}"
74
128
  end
75
129
 
130
+ # Generates ATTENDEE lines for all attendees if showing attendees is enabled.
131
+ #
132
+ # @return [Array<String>, nil] Array of ATTENDEE lines, or nil if no attendees or not showing
76
133
  def attendee_lines
77
134
  return nil unless event.show_attendees && event.attendees&.any?
78
135
  event.attendees.map { |attendee| "ATTENDEE;RSVP=TRUE:mailto:#{attendee}" }
79
136
  end
80
137
  end
81
138
 
82
- # Optional download wrapper
139
+ # Module for handling ICS file downloads with proper headers and file naming.
140
+ # Provides utility methods for preparing ICS content for HTTP download.
83
141
  module IcsDownload
142
+ # Generates appropriate HTTP headers for ICS file download.
143
+ #
144
+ # @param filename [String] The desired filename for the download
145
+ # @return [Hash] HTTP headers for the ICS file download
84
146
  def self.headers(filename)
85
147
  {
86
148
  'Content-Type' => 'text/calendar; charset=UTF-8',
@@ -88,10 +150,22 @@ module CalInvite
88
150
  }
89
151
  end
90
152
 
153
+ # Sanitizes a filename by removing potentially problematic characters.
154
+ #
155
+ # @param filename [String] The filename to sanitize
156
+ # @return [String] The sanitized filename
91
157
  def self.sanitize_filename(filename)
92
158
  filename.gsub(/[^0-9A-Za-z.\-]/, '_')
93
159
  end
94
160
 
161
+ # Wraps ICS content with appropriate download information.
162
+ #
163
+ # @param content [String] The ICS calendar content
164
+ # @param title [String] The event title to use in the filename
165
+ # @return [Hash] Hash containing content and headers for download
166
+ # @example
167
+ # result = IcsDownload.wrap_for_download(ics_content, "team-meeting")
168
+ # # => { content: "BEGIN:VCALENDAR...", headers: { 'Content-Type' => '...' } }
95
169
  def self.wrap_for_download(content, title)
96
170
  filename = sanitize_filename("#{title.downcase}_#{Time.now.strftime('%Y%m%d')}.ics")
97
171
  {
@@ -101,11 +175,10 @@ module CalInvite
101
175
  end
102
176
  end
103
177
 
104
- # For compatibility, keep the original classes but inherit from IcsContent
105
- class Ics < IcsContent
106
- end
107
-
108
- class Ical < IcsContent
109
- end
178
+ # Compatibility class aliases
179
+ # @api private
180
+ class Ics < IcsContent; end
181
+ # @api private
182
+ class Ical < IcsContent; end
110
183
  end
111
184
  end
@@ -3,7 +3,37 @@
3
3
  # lib/cal_invite/providers/office365.rb
4
4
  module CalInvite
5
5
  module Providers
6
+ # Microsoft Office 365 provider for generating calendar event URLs.
7
+ # This provider generates URLs that open the Office 365 web calendar
8
+ # with a pre-filled event creation form. Supports both all-day and
9
+ # time-specific events with proper timezone handling.
10
+ #
11
+ # @example Creating a regular event URL
12
+ # event = CalInvite::Event.new(
13
+ # title: "Team Meeting",
14
+ # start_time: Time.now,
15
+ # end_time: Time.now + 3600,
16
+ # description: "Weekly team sync"
17
+ # )
18
+ # office365 = CalInvite::Providers::Office365.new(event)
19
+ # url = office365.generate
20
+ #
21
+ # @example Creating an all-day event URL with attendees
22
+ # event = CalInvite::Event.new(
23
+ # title: "Company Off-Site",
24
+ # all_day: true,
25
+ # start_time: Date.today,
26
+ # end_time: Date.today + 1,
27
+ # attendees: ["john@example.com", "jane@example.com"],
28
+ # show_attendees: true
29
+ # )
30
+ # url = CalInvite::Providers::Office365.new(event).generate
6
31
  class Office365 < BaseProvider
32
+ # Generates an Office 365 calendar URL for the event.
33
+ # Automatically handles both all-day and time-specific events.
34
+ #
35
+ # @return [String] The Office 365 calendar URL
36
+ # @raise [ArgumentError] If required time fields are missing for non-all-day events
7
37
  def generate
8
38
  if event.all_day
9
39
  generate_all_day_event
@@ -14,6 +44,10 @@ module CalInvite
14
44
 
15
45
  private
16
46
 
47
+ # Generates a URL for an all-day event.
48
+ # Uses simplified date format and sets the allday flag.
49
+ #
50
+ # @return [String] The Office 365 calendar URL for an all-day event
17
51
  def generate_all_day_event
18
52
  params = {
19
53
  subject: url_encode(event.title),
@@ -27,11 +61,15 @@ module CalInvite
27
61
 
28
62
  params[:startdt] = url_encode(format_date(start_date))
29
63
  params[:enddt] = url_encode(format_date(end_date))
30
-
31
64
  add_optional_params(params)
32
65
  build_url(params)
33
66
  end
34
67
 
68
+ # Generates a URL for a regular (time-specific) event.
69
+ # Includes specific start and end times in UTC format.
70
+ #
71
+ # @return [String] The Office 365 calendar URL for a regular event
72
+ # @raise [ArgumentError] If start_time or end_time is missing
35
73
  def generate_single_event
36
74
  params = {
37
75
  subject: url_encode(event.title),
@@ -43,26 +81,38 @@ module CalInvite
43
81
 
44
82
  params[:startdt] = url_encode(format_time(event.start_time))
45
83
  params[:enddt] = url_encode(format_time(event.end_time))
46
-
47
84
  add_optional_params(params)
48
85
  build_url(params)
49
86
  end
50
87
 
88
+ # Formats a time object as a date string for Office 365.
89
+ #
90
+ # @param time [Time] The time to format
91
+ # @return [String] The formatted date (YYYY-MM-DD)
51
92
  def format_date(time)
52
93
  time.strftime('%Y-%m-%d')
53
94
  end
54
95
 
96
+ # Formats a time object as a UTC timestamp for Office 365.
97
+ # Office 365 handles timezone conversion on their end.
98
+ #
99
+ # @param time [Time] The time to format
100
+ # @return [String] The formatted UTC time (YYYY-MM-DDThh:mm:ssZ)
55
101
  def format_time(time)
56
102
  # Always use UTC format, timezone is handled by the calendar
57
103
  time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
58
104
  end
59
105
 
106
+ # Adds optional parameters to the URL parameters hash.
107
+ # Handles description, virtual meeting URL, location, and attendees.
108
+ #
109
+ # @param params [Hash] The parameters hash to update
110
+ # @return [Hash] The updated parameters hash
60
111
  def add_optional_params(params)
61
112
  description_parts = []
62
113
  description_parts << format_description if format_description
63
114
  description_parts << "Virtual Meeting URL: #{format_url}" if format_url
64
115
  params[:body] = url_encode(description_parts.join("\n\n")) if description_parts.any?
65
-
66
116
  params[:location] = url_encode(format_location) if format_location
67
117
 
68
118
  if attendees = attendees_list
@@ -72,6 +122,10 @@ module CalInvite
72
122
  params
73
123
  end
74
124
 
125
+ # Builds the final Office 365 calendar URL.
126
+ #
127
+ # @param params [Hash] The parameters to include in the URL
128
+ # @return [String] The complete Office 365 calendar URL
75
129
  def build_url(params)
76
130
  query = params.map { |k, v| "#{k}=#{v}" }.join('&')
77
131
  "https://outlook.office.com/owa/?#{query}"
@@ -1,8 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # lib/cal_invite/providers/outlook.rb
3
4
  module CalInvite
4
5
  module Providers
6
+ # Microsoft Outlook Live (outlook.live.com) provider for generating calendar event URLs.
7
+ # This provider generates URLs that open the Outlook.com web calendar
8
+ # with a pre-filled event creation form. Supports both all-day and
9
+ # time-specific events.
10
+ #
11
+ # Note: This is for personal Outlook.com accounts. For corporate Office 365,
12
+ # use the {Office365} provider instead.
13
+ #
14
+ # @example Creating a regular event URL
15
+ # event = CalInvite::Event.new(
16
+ # title: "Team Meeting",
17
+ # start_time: Time.now,
18
+ # end_time: Time.now + 3600,
19
+ # description: "Weekly team sync"
20
+ # )
21
+ # outlook = CalInvite::Providers::Outlook.new(event)
22
+ # url = outlook.generate
23
+ #
24
+ # @example Creating an all-day event URL with attendees
25
+ # event = CalInvite::Event.new(
26
+ # title: "Company Holiday",
27
+ # all_day: true,
28
+ # start_time: Date.today,
29
+ # end_time: Date.today + 1,
30
+ # attendees: ["john@example.com", "jane@example.com"],
31
+ # show_attendees: true
32
+ # )
33
+ # url = CalInvite::Providers::Outlook.new(event).generate
34
+ #
35
+ # @see Office365 For corporate Office 365 calendar URLs
5
36
  class Outlook < BaseProvider
37
+ # Generates an Outlook.com calendar URL for the event.
38
+ # Automatically handles both all-day and time-specific events.
39
+ #
40
+ # @return [String] The Outlook.com calendar URL
41
+ # @raise [ArgumentError] If required time fields are missing for non-all-day events
6
42
  def generate
7
43
  if event.all_day
8
44
  generate_all_day_event
@@ -13,6 +49,10 @@ module CalInvite
13
49
 
14
50
  private
15
51
 
52
+ # Generates a URL for an all-day event.
53
+ # Uses simplified date format and sets the allday flag.
54
+ #
55
+ # @return [String] The Outlook.com calendar URL for an all-day event
16
56
  def generate_all_day_event
17
57
  params = {
18
58
  path: '/calendar/0/action/compose',
@@ -20,9 +60,9 @@ module CalInvite
20
60
  allday: 'true'
21
61
  }
22
62
 
63
+ # Set start and end dates
23
64
  start_date = event.start_time || Time.now
24
65
  end_date = event.end_time || (start_date + 86400)
25
-
26
66
  params[:startdt] = format_date(start_date)
27
67
  params[:enddt] = format_date(end_date)
28
68
 
@@ -30,6 +70,11 @@ module CalInvite
30
70
  build_url(params)
31
71
  end
32
72
 
73
+ # Generates a URL for a regular (time-specific) event.
74
+ # Includes specific start and end times in UTC format.
75
+ #
76
+ # @return [String] The Outlook.com calendar URL for a regular event
77
+ # @raise [ArgumentError] If start_time or end_time is missing
33
78
  def generate_single_event
34
79
  params = {
35
80
  path: '/calendar/0/action/compose',
@@ -41,26 +86,38 @@ module CalInvite
41
86
 
42
87
  params[:startdt] = format_time(event.start_time)
43
88
  params[:enddt] = format_time(event.end_time)
44
-
45
89
  add_optional_params(params)
46
90
  build_url(params)
47
91
  end
48
92
 
93
+ # Formats a time object as a date string for Outlook.com.
94
+ #
95
+ # @param time [Time] The time to format
96
+ # @return [String] The formatted date (YYYY-MM-DD)
49
97
  def format_date(time)
50
98
  time.strftime('%Y-%m-%d')
51
99
  end
52
100
 
101
+ # Formats a time object as a UTC timestamp for Outlook.com.
102
+ # Outlook.com handles timezone conversion on their end.
103
+ #
104
+ # @param time [Time] The time to format
105
+ # @return [String] The formatted UTC time (YYYY-MM-DDThh:mm:ssZ)
53
106
  def format_time(time)
54
107
  # Always use UTC format, timezone is handled by the calendar
55
108
  time.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
56
109
  end
57
110
 
111
+ # Adds optional parameters to the URL parameters hash.
112
+ # Handles description, virtual meeting URL, location, and attendees.
113
+ #
114
+ # @param params [Hash] The parameters hash to update
115
+ # @return [Hash] The updated parameters hash
58
116
  def add_optional_params(params)
59
117
  description_parts = []
60
118
  description_parts << format_description if format_description
61
119
  description_parts << "Virtual Meeting URL: #{format_url}" if format_url
62
120
  params[:body] = url_encode(description_parts.join("\n\n")) if description_parts.any?
63
-
64
121
  params[:location] = url_encode(format_location) if format_location
65
122
 
66
123
  if attendees = attendees_list
@@ -70,6 +127,10 @@ module CalInvite
70
127
  params
71
128
  end
72
129
 
130
+ # Builds the final Outlook.com calendar URL.
131
+ #
132
+ # @param params [Hash] The parameters to include in the URL
133
+ # @return [String] The complete Outlook.com calendar URL
73
134
  def build_url(params)
74
135
  query = params.map { |k, v| "#{k}=#{v}" }.join('&')
75
136
  "https://outlook.live.com/calendar/0/action/compose?#{query}"