cal-invite 0.1.3 → 0.1.4
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/.DS_Store +0 -0
- data/.rdoc_options +85 -0
- data/CHANGELOG.md +8 -0
- data/README.md +6 -0
- data/Rakefile +26 -0
- data/lib/.DS_Store +0 -0
- data/lib/cal_invite/.DS_Store +0 -0
- data/lib/cal_invite/configuration.rb +28 -0
- data/lib/cal_invite/event.rb +83 -1
- data/lib/cal_invite/providers/base_provider.rb +19 -0
- data/lib/cal_invite/providers/google.rb +56 -4
- data/lib/cal_invite/providers/ical.rb +64 -6
- data/lib/cal_invite/providers/ics.rb +70 -4
- data/lib/cal_invite/providers/ics_content.rb +80 -7
- data/lib/cal_invite/providers/office365.rb +57 -3
- data/lib/cal_invite/providers/outlook.rb +64 -3
- data/lib/cal_invite/providers/yahoo.rb +57 -0
- data/lib/cal_invite/providers.rb +6 -1
- data/lib/cal_invite/version.rb +1 -1
- data/lib/cal_invite.rb +33 -0
- data/lib/tasks/rdoc.rake +13 -0
- metadata +35 -2
@@ -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
|
-
#
|
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
|
-
#
|
105
|
-
|
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}"
|