icalendar 2.5.2 → 2.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +3 -5
- data/History.txt +21 -0
- data/README.md +4 -3
- data/icalendar.gemspec +10 -9
- data/lib/icalendar.rb +2 -0
- data/lib/icalendar/component.rb +14 -3
- data/lib/icalendar/has_components.rb +17 -5
- data/lib/icalendar/has_properties.rb +4 -4
- data/lib/icalendar/marshable.rb +34 -0
- data/lib/icalendar/parser.rb +36 -12
- data/lib/icalendar/timezone.rb +33 -18
- data/lib/icalendar/tzinfo.rb +2 -2
- data/lib/icalendar/values/utc_offset.rb +1 -2
- data/lib/icalendar/version.rb +1 -1
- data/spec/calendar_spec.rb +15 -0
- data/spec/event_spec.rb +7 -0
- data/spec/fixtures/bad_wrapping.ics +14 -0
- data/spec/fixtures/custom_component.ics +158 -0
- data/spec/parser_spec.rb +10 -0
- data/spec/roundtrip_spec.rb +9 -0
- data/spec/timezone_spec.rb +48 -0
- metadata +22 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fbeadc87a1316894e02896fd63e7f7644a1ab5bf3a542b0d5a02c59aabf2d603
|
4
|
+
data.tar.gz: 3aaf09d7984fed57644d091fa76285084d9fbf3568e6078939e7887d6f44f50a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6134ff7d12c820d7ee10a316108c1d7a9db274cccd366b93a49fa491931038edd78eb37789a24962cc6370c83fd34d67508dd24b574e8d1fa235e815c1fa22d
|
7
|
+
data.tar.gz: 0bad8ab7548493a133686dab3a6d36a58aeecddb8642da83cef5a2184d0d08e97dc59307fc6fdd9574935319044ccb5ffa50f9c936aa60e5637a2430e78eb446
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -3,17 +3,15 @@ before_install:
|
|
3
3
|
- gem install bundler
|
4
4
|
language: ruby
|
5
5
|
rvm:
|
6
|
+
- 2.7
|
7
|
+
- 2.6
|
6
8
|
- 2.5
|
7
|
-
- 2.4
|
8
|
-
- 2.3
|
9
|
-
- 2.2
|
10
9
|
- jruby-19mode
|
11
|
-
- rbx-3
|
12
10
|
- ruby-head
|
13
11
|
- jruby-head
|
12
|
+
- truffleruby-head
|
14
13
|
matrix:
|
15
14
|
allow_failures:
|
16
|
-
- rvm: 2.2
|
17
15
|
- rvm: ruby-head
|
18
16
|
- rvm: jruby-head
|
19
17
|
script: bundle exec rake spec
|
data/History.txt
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
=== 2.7.1 2021-03-14
|
2
|
+
* Recover from bad line-wrapping code that splits in the middle of Unicode code points
|
3
|
+
* Add a verbose option to the Parser to quiet some of the chattier log entries
|
4
|
+
|
5
|
+
=== 2.7.0 2020-09-12
|
6
|
+
* Handle custom component names, with and without X- prefix
|
7
|
+
* Fix Component lookup to avoid namespace collisions
|
8
|
+
|
9
|
+
=== 2.6.1 2019-12-07
|
10
|
+
* Improve performance when generating large ICS files - Alex Balhatchet
|
11
|
+
|
12
|
+
=== 2.6.0 2019-11-26
|
13
|
+
* Improve performance for calculating timezone offsets - Justin Howard
|
14
|
+
* Make it possible to de/serialize with Marshal - Pawel Niewiadomski
|
15
|
+
* Avoid FrozenError when running with frozen_string_literal
|
16
|
+
* Update minimum Ruby version to supported versions
|
17
|
+
|
18
|
+
=== 2.5.3 2019-03-04
|
19
|
+
* Improve parsing performance - nehresma
|
20
|
+
* Support tzinfo 2.0 - Misty De Meo
|
21
|
+
|
1
22
|
=== 2.5.2 2018-12-08
|
2
23
|
* Remove usage of the global TimezoneStore instance, in favor of a local variable in the parser
|
3
24
|
* Deprecate TimezoneStore class methods
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
iCalendar -- Internet calendaring, Ruby style
|
2
2
|
===
|
3
3
|
|
4
|
-
[![Build Status](https://travis-ci.
|
4
|
+
[![Build Status](https://travis-ci.com/icalendar/icalendar.svg?branch=master)](https://travis-ci.com/icalendar/icalendar)
|
5
5
|
[![Code Climate](https://codeclimate.com/github/icalendar/icalendar.png)](https://codeclimate.com/github/icalendar/icalendar)
|
6
6
|
|
7
7
|
<http://github.com/icalendar/icalendar>
|
@@ -115,7 +115,7 @@ cal.event do |e|
|
|
115
115
|
a.attendee = %w(mailto:me@my-domain.com mailto:me-too@my-domain.com) # one or more email recipients (required)
|
116
116
|
a.append_attendee "mailto:me-three@my-domain.com"
|
117
117
|
a.trigger = "-PT15M" # 15 minutes before
|
118
|
-
a.append_attach Icalendar::Values::Uri.new
|
118
|
+
a.append_attach Icalendar::Values::Uri.new("ftp://host.com/novo-procs/felizano.exe", "fmttype" => "application/binary") # email attachments (optional)
|
119
119
|
end
|
120
120
|
|
121
121
|
e.alarm do |a|
|
@@ -220,7 +220,8 @@ end
|
|
220
220
|
iCalendar has some basic support for creating VTIMEZONE blocks from timezone information pulled from `tzinfo`.
|
221
221
|
You must require `tzinfo` support manually to take advantage.
|
222
222
|
|
223
|
-
iCalendar has been tested and works with `tzinfo` versions 0.3 and
|
223
|
+
iCalendar has been tested and works with `tzinfo` versions 0.3, 1.x, and 2.x. The `tzinfo-data` gem may also
|
224
|
+
be required depending on your version of `tzinfo` and potentially your operating system.
|
224
225
|
|
225
226
|
#### Example ####
|
226
227
|
|
data/icalendar.gemspec
CHANGED
@@ -24,22 +24,23 @@ ActiveSupport is required for TimeWithZone support, but not required for general
|
|
24
24
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename f }
|
25
25
|
s.require_paths = ['lib']
|
26
26
|
|
27
|
-
s.required_ruby_version = '>= 2.
|
27
|
+
s.required_ruby_version = '>= 2.4.0'
|
28
28
|
|
29
29
|
s.add_dependency 'ice_cube', '~> 0.16'
|
30
30
|
|
31
|
-
s.add_development_dependency 'rake', '~>
|
32
|
-
s.add_development_dependency 'bundler', '~>
|
31
|
+
s.add_development_dependency 'rake', '~> 13.0'
|
32
|
+
s.add_development_dependency 'bundler', '~> 2.0'
|
33
33
|
|
34
|
-
# test with
|
34
|
+
# test with all groups of tzinfo dependencies
|
35
|
+
# tzinfo 2.x
|
36
|
+
# s.add_development_dependency 'tzinfo', '~> 2.0'
|
37
|
+
# s.add_development_dependency 'tzinfo-data', '~> 1.2020'
|
35
38
|
# tzinfo 1.x
|
36
|
-
s.add_development_dependency 'activesupport', '~>
|
37
|
-
s.add_development_dependency 'i18n', '~> 1.
|
39
|
+
s.add_development_dependency 'activesupport', '~> 6.0'
|
40
|
+
s.add_development_dependency 'i18n', '~> 1.8'
|
38
41
|
s.add_development_dependency 'tzinfo', '~> 1.2'
|
39
|
-
s.add_development_dependency 'tzinfo-data', '~> 1.
|
42
|
+
s.add_development_dependency 'tzinfo-data', '~> 1.2020'
|
40
43
|
# tzinfo 0.x
|
41
|
-
# s.add_development_dependency 'activesupport', '~> 4.0'
|
42
|
-
# s.add_development_dependency 'i18n', '~> 0.7'
|
43
44
|
# s.add_development_dependency 'tzinfo', '~> 0.3'
|
44
45
|
# end tzinfo
|
45
46
|
|
data/lib/icalendar.rb
CHANGED
@@ -22,6 +22,7 @@ end
|
|
22
22
|
|
23
23
|
require 'icalendar/has_properties'
|
24
24
|
require 'icalendar/has_components'
|
25
|
+
require 'icalendar/marshable'
|
25
26
|
require 'icalendar/component'
|
26
27
|
require 'icalendar/value'
|
27
28
|
require 'icalendar/alarm'
|
@@ -32,3 +33,4 @@ require 'icalendar/freebusy'
|
|
32
33
|
require 'icalendar/timezone'
|
33
34
|
require 'icalendar/calendar'
|
34
35
|
require 'icalendar/parser'
|
36
|
+
require 'icalendar/version'
|
data/lib/icalendar/component.rb
CHANGED
@@ -11,9 +11,10 @@ module Icalendar
|
|
11
11
|
attr_accessor :parent
|
12
12
|
|
13
13
|
def self.parse(source)
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
_parse source
|
15
|
+
rescue ArgumentError
|
16
|
+
source.rewind if source.respond_to?(:rewind)
|
17
|
+
_parse Parser.clean_bad_wrapping(source)
|
17
18
|
end
|
18
19
|
|
19
20
|
def initialize(name, ical_name = nil)
|
@@ -71,6 +72,8 @@ module Icalendar
|
|
71
72
|
# than 75 octets, but you need to split between characters, not bytes.
|
72
73
|
# This is challanging with Unicode composing accents, for example.
|
73
74
|
|
75
|
+
return long_line if long_line.bytesize <= Icalendar::MAX_LINE_LENGTH
|
76
|
+
|
74
77
|
chars = long_line.scan(/\P{M}\p{M}*/u) # split in graphenes
|
75
78
|
folded = ['']
|
76
79
|
bytes = 0
|
@@ -99,6 +102,14 @@ module Icalendar
|
|
99
102
|
end
|
100
103
|
collection.empty? ? nil : collection.join.chomp("\r\n")
|
101
104
|
end
|
105
|
+
|
106
|
+
class << self
|
107
|
+
private def _parse(source)
|
108
|
+
parser = Parser.new(source)
|
109
|
+
parser.component_class = self
|
110
|
+
parser.parse
|
111
|
+
end
|
112
|
+
end
|
102
113
|
end
|
103
114
|
|
104
115
|
end
|
@@ -10,7 +10,7 @@ module Icalendar
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def initialize(*args)
|
13
|
-
@custom_components = Hash.new
|
13
|
+
@custom_components = Hash.new
|
14
14
|
super
|
15
15
|
end
|
16
16
|
|
@@ -21,21 +21,33 @@ module Icalendar
|
|
21
21
|
c
|
22
22
|
end
|
23
23
|
|
24
|
+
def add_custom_component(component_name, c)
|
25
|
+
c.parent = self
|
26
|
+
yield c if block_given?
|
27
|
+
(custom_components[component_name.downcase.gsub("-", "_")] ||= []) << c
|
28
|
+
c
|
29
|
+
end
|
30
|
+
|
31
|
+
def custom_component(component_name)
|
32
|
+
custom_components[component_name.downcase.gsub("-", "_")] || []
|
33
|
+
end
|
34
|
+
|
24
35
|
def method_missing(method, *args, &block)
|
25
36
|
method_name = method.to_s
|
26
37
|
if method_name =~ /^add_(x_\w+)$/
|
27
38
|
component_name = $1
|
28
39
|
custom = args.first || Component.new(component_name, component_name.upcase)
|
29
|
-
|
30
|
-
|
31
|
-
|
40
|
+
add_custom_component(component_name, custom, &block)
|
41
|
+
elsif method_name =~ /^x_/ && custom_component(method_name).size > 0
|
42
|
+
custom_component method_name
|
32
43
|
else
|
33
44
|
super
|
34
45
|
end
|
35
46
|
end
|
36
47
|
|
37
48
|
def respond_to_missing?(method_name, include_private = false)
|
38
|
-
method_name.to_s
|
49
|
+
string_method = method_name.to_s
|
50
|
+
string_method.start_with?('add_x_') || custom_component(string_method).size > 0 || super
|
39
51
|
end
|
40
52
|
|
41
53
|
module ClassMethods
|
@@ -10,7 +10,7 @@ module Icalendar
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def initialize(*args)
|
13
|
-
@custom_properties = Hash.new
|
13
|
+
@custom_properties = Hash.new
|
14
14
|
super
|
15
15
|
end
|
16
16
|
|
@@ -39,7 +39,7 @@ module Icalendar
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def custom_property(property_name)
|
42
|
-
custom_properties[property_name.downcase]
|
42
|
+
custom_properties[property_name.downcase] || []
|
43
43
|
end
|
44
44
|
|
45
45
|
def append_custom_property(property_name, value)
|
@@ -49,9 +49,9 @@ module Icalendar
|
|
49
49
|
elsif self.class.multiple_properties.include? property_name
|
50
50
|
send "append_#{property_name}", value
|
51
51
|
elsif value.is_a? Icalendar::Value
|
52
|
-
custom_properties[property_name] << value
|
52
|
+
(custom_properties[property_name] ||= []) << value
|
53
53
|
else
|
54
|
-
custom_properties[property_name] << Icalendar::Values::Text.new(value)
|
54
|
+
(custom_properties[property_name] ||= []) << Icalendar::Values::Text.new(value)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Icalendar
|
2
|
+
module Marshable
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
def marshal_dump
|
8
|
+
instance_variables
|
9
|
+
.reject { |ivar| self.class.transient_variables.include?(ivar) }
|
10
|
+
.each_with_object({}) do |ivar, serialized|
|
11
|
+
|
12
|
+
serialized[ivar] = instance_variable_get(ivar)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def marshal_load(serialized)
|
17
|
+
serialized.each do |ivar, value|
|
18
|
+
unless self.class.transient_variables.include?(ivar)
|
19
|
+
instance_variable_set(ivar, value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def transient_variables
|
26
|
+
@transient_variables ||= [:@transient_variables]
|
27
|
+
end
|
28
|
+
|
29
|
+
def transient_variable(name)
|
30
|
+
transient_variables.push(name.to_sym)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/icalendar/parser.rb
CHANGED
@@ -4,9 +4,24 @@ module Icalendar
|
|
4
4
|
|
5
5
|
class Parser
|
6
6
|
attr_writer :component_class
|
7
|
-
attr_reader :source, :strict, :timezone_store
|
7
|
+
attr_reader :source, :strict, :timezone_store, :verbose
|
8
8
|
|
9
|
-
def
|
9
|
+
def self.clean_bad_wrapping(source)
|
10
|
+
content = if source.respond_to? :read
|
11
|
+
source.read
|
12
|
+
elsif source.respond_to? :to_s
|
13
|
+
source.to_s
|
14
|
+
else
|
15
|
+
msg = 'Icalendar::Parser.clean_bad_wrapping must be called with a String or IO object'
|
16
|
+
Icalendar.fatal msg
|
17
|
+
fail ArgumentError, msg
|
18
|
+
end
|
19
|
+
encoding = content.encoding
|
20
|
+
content.force_encoding(Encoding::ASCII_8BIT)
|
21
|
+
content.gsub(/\r?\n[ \t]/, "").force_encoding(encoding)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(source, strict = false, verbose = false)
|
10
25
|
if source.respond_to? :gets
|
11
26
|
@source = source
|
12
27
|
elsif source.respond_to? :to_s
|
@@ -18,6 +33,7 @@ module Icalendar
|
|
18
33
|
end
|
19
34
|
read_in_data
|
20
35
|
@strict = strict
|
36
|
+
@verbose = verbose
|
21
37
|
@timezone_store = TimezoneStore.new
|
22
38
|
end
|
23
39
|
|
@@ -49,7 +65,7 @@ module Icalendar
|
|
49
65
|
Icalendar.logger.error "No method \"#{method_name}\" for component #{component}"
|
50
66
|
raise nme
|
51
67
|
else
|
52
|
-
Icalendar.logger.warn "No method \"#{method_name}\" for component #{component}. Appending to custom."
|
68
|
+
Icalendar.logger.warn "No method \"#{method_name}\" for component #{component}. Appending to custom." if verbose?
|
53
69
|
component.append_custom_property prop_name, prop_value
|
54
70
|
end
|
55
71
|
end
|
@@ -82,8 +98,8 @@ module Icalendar
|
|
82
98
|
if !fields[:params]['value'].nil?
|
83
99
|
klass_name = fields[:params].delete('value').first
|
84
100
|
unless klass_name.upcase == klass.value_type
|
85
|
-
klass_name = klass_name.downcase.gsub(/(?:\A|-)(.)/) { |m| m[-1].upcase }
|
86
|
-
klass =
|
101
|
+
klass_name = "Icalendar::Values::#{klass_name.downcase.gsub(/(?:\A|-)(.)/) { |m| m[-1].upcase }}"
|
102
|
+
klass = Object.const_get klass_name if Object.const_defined?(klass_name)
|
87
103
|
end
|
88
104
|
end
|
89
105
|
klass
|
@@ -93,6 +109,10 @@ module Icalendar
|
|
93
109
|
!!@strict
|
94
110
|
end
|
95
111
|
|
112
|
+
def verbose?
|
113
|
+
@verbose
|
114
|
+
end
|
115
|
+
|
96
116
|
private
|
97
117
|
|
98
118
|
def component_class
|
@@ -106,14 +126,14 @@ module Icalendar
|
|
106
126
|
timezone_store.store(component) if klass_name == 'Timezone'
|
107
127
|
break
|
108
128
|
elsif fields[:name] == 'begin'
|
109
|
-
klass_name = fields[:value].gsub(/\AV/, '').downcase.capitalize
|
129
|
+
klass_name = fields[:value].gsub(/\AV/, '').gsub("-", "_").downcase.capitalize
|
110
130
|
Icalendar.logger.debug "Adding component #{klass_name}"
|
111
|
-
if
|
112
|
-
component.add_component parse_component(
|
113
|
-
elsif
|
114
|
-
component.add_component parse_component(Icalendar::Timezone
|
131
|
+
if Object.const_defined? "Icalendar::#{klass_name}"
|
132
|
+
component.add_component parse_component(Object.const_get("Icalendar::#{klass_name}").new)
|
133
|
+
elsif Object.const_defined? "Icalendar::Timezone::#{klass_name}"
|
134
|
+
component.add_component parse_component(Object.const_get("Icalendar::Timezone::#{klass_name}").new)
|
115
135
|
else
|
116
|
-
component.
|
136
|
+
component.add_custom_component klass_name, parse_component(Component.new klass_name.downcase, fields[:value])
|
117
137
|
end
|
118
138
|
else
|
119
139
|
parse_property component, fields
|
@@ -172,7 +192,11 @@ module Icalendar
|
|
172
192
|
end
|
173
193
|
end
|
174
194
|
end
|
175
|
-
|
195
|
+
# Building the string to send to the logger is expensive.
|
196
|
+
# Only do it if the logger is at the right log level.
|
197
|
+
if ::Logger::DEBUG >= Icalendar.logger.level
|
198
|
+
Icalendar.logger.debug "Found fields: #{parts.inspect} with params: #{params.inspect}"
|
199
|
+
end
|
176
200
|
{
|
177
201
|
name: parts[:name].downcase.gsub('-', '_'),
|
178
202
|
params: params,
|
data/lib/icalendar/timezone.rb
CHANGED
@@ -14,10 +14,40 @@ module Icalendar
|
|
14
14
|
optional_property :comment
|
15
15
|
optional_property :rdate, Icalendar::Values::DateTime
|
16
16
|
optional_property :tzname
|
17
|
+
|
18
|
+
transient_variable :@cached_occurrences
|
19
|
+
transient_variable :@occurrences
|
17
20
|
end
|
18
21
|
end
|
22
|
+
|
23
|
+
def occurrences
|
24
|
+
@occurrences ||= IceCube::Schedule.new(dtstart.to_time) do |s|
|
25
|
+
rrule.each do |rule|
|
26
|
+
s.add_recurrence_rule IceCube::Rule.from_ical(rule.value_ical)
|
27
|
+
end
|
28
|
+
rdate.each do |date|
|
29
|
+
s.add_recurrence_time date.to_time
|
30
|
+
end
|
31
|
+
end.all_occurrences_enumerator
|
32
|
+
end
|
33
|
+
|
34
|
+
def previous_occurrence(from)
|
35
|
+
from = IceCube::TimeUtil.match_zone(from, dtstart.to_time)
|
36
|
+
|
37
|
+
@cached_occurrences ||= []
|
38
|
+
while @cached_occurrences.empty? || @cached_occurrences.last <= from
|
39
|
+
begin
|
40
|
+
@cached_occurrences << occurrences.next
|
41
|
+
rescue StopIteration
|
42
|
+
break
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
@cached_occurrences.reverse_each.find { |occurrence| occurrence < from }
|
47
|
+
end
|
19
48
|
end
|
20
49
|
class Daylight < Component
|
50
|
+
include Marshable
|
21
51
|
include TzProperties
|
22
52
|
|
23
53
|
def initialize
|
@@ -25,6 +55,7 @@ module Icalendar
|
|
25
55
|
end
|
26
56
|
end
|
27
57
|
class Standard < Component
|
58
|
+
include Marshable
|
28
59
|
include TzProperties
|
29
60
|
|
30
61
|
def initialize
|
@@ -75,30 +106,14 @@ module Icalendar
|
|
75
106
|
|
76
107
|
def standard_for(local)
|
77
108
|
possible = standards.map do |std|
|
78
|
-
|
79
|
-
std.rrule.each do |rule|
|
80
|
-
s.add_recurrence_rule IceCube::Rule.from_ical(rule.value_ical)
|
81
|
-
end
|
82
|
-
std.rdate.each do |date|
|
83
|
-
s.add_recurrence_time date.to_time
|
84
|
-
end
|
85
|
-
end
|
86
|
-
[schedule.previous_occurrence(local.to_time), std]
|
109
|
+
[std.previous_occurrence(local.to_time), std]
|
87
110
|
end
|
88
111
|
possible.sort_by(&:first).last
|
89
112
|
end
|
90
113
|
|
91
114
|
def daylight_for(local)
|
92
115
|
possible = daylights.map do |day|
|
93
|
-
|
94
|
-
day.rrule.each do |rule|
|
95
|
-
s.add_recurrence_rule IceCube::Rule.from_ical(rule.value_ical)
|
96
|
-
end
|
97
|
-
day.rdate.each do |date|
|
98
|
-
s.add_recurrence_time date.to_time
|
99
|
-
end
|
100
|
-
end
|
101
|
-
[schedule.previous_occurrence(local.to_time), day]
|
116
|
+
[day.previous_occurrence(local.to_time), day]
|
102
117
|
end
|
103
118
|
possible.sort_by(&:first).last
|
104
119
|
end
|
data/lib/icalendar/tzinfo.rb
CHANGED
@@ -61,7 +61,7 @@ module Icalendar
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def rrule
|
64
|
-
start = local_start.to_datetime
|
64
|
+
start = (respond_to?(:local_start_at) ? local_start_at : local_start).to_datetime
|
65
65
|
# this is somewhat of a hack, but seems to work ok
|
66
66
|
# assumes that no timezone transition is in law as "4th X of the month"
|
67
67
|
# but only as 1st X, 2nd X, 3rd X, or Last X
|
@@ -76,7 +76,7 @@ module Icalendar
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def dtstart
|
79
|
-
local_start.to_datetime.strftime '%Y%m%dT%H%M%S'
|
79
|
+
(respond_to?(:local_start_at) ? local_start_at : local_start).to_datetime.strftime '%Y%m%dT%H%M%S'
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
@@ -37,8 +37,7 @@ module Icalendar
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def parse_fields(value)
|
40
|
-
value.gsub
|
41
|
-
md = /\A(?<behind>[+-])(?<hours>\d{2})(?<minutes>\d{2})(?<seconds>\d{2})?\z/.match value
|
40
|
+
md = /\A(?<behind>[+-])(?<hours>\d{2})(?<minutes>\d{2})(?<seconds>\d{2})?\z/.match value.gsub(/\s+/, '')
|
42
41
|
{
|
43
42
|
behind: (md[:behind] == '-'),
|
44
43
|
hours: md[:hours].to_i,
|
data/lib/icalendar/version.rb
CHANGED
data/spec/calendar_spec.rb
CHANGED
@@ -2,6 +2,12 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Icalendar::Calendar do
|
4
4
|
|
5
|
+
context 'marshalling' do
|
6
|
+
it 'can be de/serialized' do
|
7
|
+
Marshal.load(Marshal.dump(subject))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
5
11
|
context 'values' do
|
6
12
|
let(:property) { 'my-value' }
|
7
13
|
|
@@ -164,4 +170,13 @@ END:VCALENDAR
|
|
164
170
|
expect(subject.ip_method).to eq 'PUBLISH'
|
165
171
|
end
|
166
172
|
end
|
173
|
+
|
174
|
+
describe '.parse' do
|
175
|
+
let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'bad_wrapping.ics') }
|
176
|
+
|
177
|
+
it 'correctly parses a bad file' do
|
178
|
+
actual = described_class.parse(source)
|
179
|
+
expect(actual[0]).to be_a(described_class)
|
180
|
+
end
|
181
|
+
end
|
167
182
|
end
|
data/spec/event_spec.rb
CHANGED
@@ -113,6 +113,13 @@ describe Icalendar::Event do
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
+
describe "#custom_property" do
|
117
|
+
it "returns a default for missing properties" do
|
118
|
+
expect(subject.x_doesnt_exist).to eq([])
|
119
|
+
expect(subject.custom_property "x_doesnt_exist").to eq([])
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
116
123
|
describe '.parse' do
|
117
124
|
let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', fn) }
|
118
125
|
let(:fn) { 'event.ics' }
|
@@ -0,0 +1,14 @@
|
|
1
|
+
BEGIN:VCALENDAR
|
2
|
+
VERSION:2.0
|
3
|
+
PRODID:manual
|
4
|
+
CALSCALE:GREGORIAN
|
5
|
+
BEGIN:VEVENT
|
6
|
+
DTSTAMP:20200902T223352Z
|
7
|
+
UID:6e7d7fe5-6735-4cdd-bfe4-761dfcecd7a7
|
8
|
+
DTSTART;VALUE=DATE:20200902
|
9
|
+
DTEND;VALUE=DATE:20200903
|
10
|
+
DESCRIPTION:Event description that puts a UTF-8 multi-octet sequence right�
|
11
|
+
�here.
|
12
|
+
SUMMARY:UTF-8 multi-octet sequence test
|
13
|
+
END:VEVENT
|
14
|
+
END:VCALENDAR
|
@@ -0,0 +1,158 @@
|
|
1
|
+
BEGIN:VCALENDAR
|
2
|
+
PRODID:-//Atlassian Confluence//Calendar Plugin 1.0//EN
|
3
|
+
VERSION:2.0
|
4
|
+
CALSCALE:GREGORIAN
|
5
|
+
X-WR-CALNAME:Grimsell testkalender
|
6
|
+
X-WR-CALDESC:
|
7
|
+
X-WR-TIMEZONE:Europe/Stockholm
|
8
|
+
X-MIGRATED-FOR-USER-KEY:true
|
9
|
+
METHOD:PUBLISH
|
10
|
+
BEGIN:VTIMEZONE
|
11
|
+
TZID:Europe/Stockholm
|
12
|
+
TZURL:http://tzurl.org/zoneinfo/Europe/Stockholm
|
13
|
+
SEQUENCE:9206
|
14
|
+
BEGIN:DAYLIGHT
|
15
|
+
TZOFFSETFROM:+0100
|
16
|
+
TZOFFSETTO:+0200
|
17
|
+
TZNAME:CEST
|
18
|
+
DTSTART:19810329T020000
|
19
|
+
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
20
|
+
END:DAYLIGHT
|
21
|
+
BEGIN:STANDARD
|
22
|
+
TZOFFSETFROM:+0200
|
23
|
+
TZOFFSETTO:+0100
|
24
|
+
TZNAME:CET
|
25
|
+
DTSTART:19961027T030000
|
26
|
+
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
27
|
+
END:STANDARD
|
28
|
+
BEGIN:STANDARD
|
29
|
+
TZOFFSETFROM:+011212
|
30
|
+
TZOFFSETTO:+010014
|
31
|
+
TZNAME:SET
|
32
|
+
DTSTART:18790101T000000
|
33
|
+
RDATE:18790101T000000
|
34
|
+
END:STANDARD
|
35
|
+
BEGIN:STANDARD
|
36
|
+
TZOFFSETFROM:+010014
|
37
|
+
TZOFFSETTO:+0100
|
38
|
+
TZNAME:CET
|
39
|
+
DTSTART:19000101T000000
|
40
|
+
RDATE:19000101T000000
|
41
|
+
END:STANDARD
|
42
|
+
BEGIN:DAYLIGHT
|
43
|
+
TZOFFSETFROM:+0100
|
44
|
+
TZOFFSETTO:+0200
|
45
|
+
TZNAME:CEST
|
46
|
+
DTSTART:19160514T230000
|
47
|
+
RDATE:19160514T230000
|
48
|
+
RDATE:19800406T020000
|
49
|
+
END:DAYLIGHT
|
50
|
+
BEGIN:STANDARD
|
51
|
+
TZOFFSETFROM:+0200
|
52
|
+
TZOFFSETTO:+0100
|
53
|
+
TZNAME:CET
|
54
|
+
DTSTART:19161001T010000
|
55
|
+
RDATE:19161001T010000
|
56
|
+
RDATE:19800928T030000
|
57
|
+
RDATE:19810927T030000
|
58
|
+
RDATE:19820926T030000
|
59
|
+
RDATE:19830925T030000
|
60
|
+
RDATE:19840930T030000
|
61
|
+
RDATE:19850929T030000
|
62
|
+
RDATE:19860928T030000
|
63
|
+
RDATE:19870927T030000
|
64
|
+
RDATE:19880925T030000
|
65
|
+
RDATE:19890924T030000
|
66
|
+
RDATE:19900930T030000
|
67
|
+
RDATE:19910929T030000
|
68
|
+
RDATE:19920927T030000
|
69
|
+
RDATE:19930926T030000
|
70
|
+
RDATE:19940925T030000
|
71
|
+
RDATE:19950924T030000
|
72
|
+
END:STANDARD
|
73
|
+
BEGIN:STANDARD
|
74
|
+
TZOFFSETFROM:+0100
|
75
|
+
TZOFFSETTO:+0100
|
76
|
+
TZNAME:CET
|
77
|
+
DTSTART:19800101T000000
|
78
|
+
RDATE:19800101T000000
|
79
|
+
END:STANDARD
|
80
|
+
END:VTIMEZONE
|
81
|
+
BEGIN:VEVENT
|
82
|
+
DTSTAMP:20151203T104347Z
|
83
|
+
SUMMARY:Kaffepaus
|
84
|
+
UID:20151203T101856Z-1543254179@intranet.idainfront.se
|
85
|
+
DESCRIPTION:
|
86
|
+
ORGANIZER;X-CONFLUENCE-USER-KEY=ff80818141e4d3b60141e4d4b75700a2;CN=Magnu
|
87
|
+
s Grimsell;CUTYPE=INDIVIDUAL:mailto:magnus.grimsell@idainfront.se
|
88
|
+
CREATED:20151203T101856Z
|
89
|
+
LAST-MODIFIED:20151203T101856Z
|
90
|
+
SEQUENCE:1
|
91
|
+
X-CONFLUENCE-SUBCALENDAR-TYPE:other
|
92
|
+
TRANSP:OPAQUE
|
93
|
+
STATUS:CONFIRMED
|
94
|
+
DTSTART:20151203T090000Z
|
95
|
+
DTEND:20151203T091500Z
|
96
|
+
END:VEVENT
|
97
|
+
BEGIN:X-EVENT-SERIES
|
98
|
+
SUMMARY:iipax - Sprints
|
99
|
+
DESCRIPTION:
|
100
|
+
X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint
|
101
|
+
URL:jira://8112ef9a-343e-30e6-b469-4f993bf0371d?projectKey=II&dateFieldNa
|
102
|
+
me=sprint
|
103
|
+
END:X-EVENT-SERIES
|
104
|
+
BEGIN:VEVENT
|
105
|
+
DTSTAMP:20151203T104348Z
|
106
|
+
DTSTART;TZID=Europe/Stockholm:20130115T170800
|
107
|
+
DTEND;TZID=Europe/Stockholm:20130204T170800
|
108
|
+
UID:3cb4df4b-eb18-43fd-934a-16c15acfb4b1-3@jira.idainfront.se
|
109
|
+
X-GREENHOPPER-SPRINT-CLOSED:false
|
110
|
+
X-GREENHOPPER-SPRINT-EDITABLE:true
|
111
|
+
X-JIRA-PROJECT;X-JIRA-PROJECT-KEY=II:iipax
|
112
|
+
X-GREENHOPPER-SPRINT-VIEWBOARDS-URL:https://jira.idainfront.se/secure/GHG
|
113
|
+
oToBoard.jspa?sprintId=3
|
114
|
+
SEQUENCE:0
|
115
|
+
X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint
|
116
|
+
TRANSP:OPAQUE
|
117
|
+
STATUS:CONFIRMED
|
118
|
+
DESCRIPTION:https://jira.idainfront.se/secure/GHGoToBoard.jspa?sprintId=3
|
119
|
+
|
120
|
+
SUMMARY:iipax - Callisto-6
|
121
|
+
END:VEVENT
|
122
|
+
BEGIN:VEVENT
|
123
|
+
DTSTAMP:20151203T104348Z
|
124
|
+
DTSTART;TZID=Europe/Stockholm:20130219T080000
|
125
|
+
DTEND;TZID=Europe/Stockholm:20130319T095900
|
126
|
+
UID:3cb4df4b-eb18-43fd-934a-16c15acfb4b1-5@jira.idainfront.se
|
127
|
+
X-GREENHOPPER-SPRINT-CLOSED:false
|
128
|
+
X-GREENHOPPER-SPRINT-EDITABLE:true
|
129
|
+
X-JIRA-PROJECT;X-JIRA-PROJECT-KEY=II:iipax
|
130
|
+
X-GREENHOPPER-SPRINT-VIEWBOARDS-URL:https://jira.idainfront.se/secure/GHG
|
131
|
+
oToBoard.jspa?sprintId=5
|
132
|
+
SEQUENCE:0
|
133
|
+
X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint
|
134
|
+
TRANSP:OPAQUE
|
135
|
+
STATUS:CONFIRMED
|
136
|
+
DESCRIPTION:https://jira.idainfront.se/secure/GHGoToBoard.jspa?sprintId=5
|
137
|
+
|
138
|
+
SUMMARY:iipax - Bulbasaur
|
139
|
+
END:VEVENT
|
140
|
+
BEGIN:VEVENT
|
141
|
+
DTSTAMP:20151203T104348Z
|
142
|
+
DTSTART;TZID=Europe/Stockholm:20130326T105900
|
143
|
+
DTEND;TZID=Europe/Stockholm:20130419T105900
|
144
|
+
UID:3cb4df4b-eb18-43fd-934a-16c15acfb4b1-7@jira.idainfront.se
|
145
|
+
X-GREENHOPPER-SPRINT-CLOSED:true
|
146
|
+
X-GREENHOPPER-SPRINT-EDITABLE:true
|
147
|
+
X-JIRA-PROJECT;X-JIRA-PROJECT-KEY=II:iipax
|
148
|
+
X-GREENHOPPER-SPRINT-VIEWBOARDS-URL:https://jira.idainfront.se/secure/GHG
|
149
|
+
oToBoard.jspa?sprintId=7
|
150
|
+
SEQUENCE:0
|
151
|
+
X-CONFLUENCE-SUBCALENDAR-TYPE:jira-agile-sprint
|
152
|
+
TRANSP:OPAQUE
|
153
|
+
STATUS:CONFIRMED
|
154
|
+
DESCRIPTION:https://jira.idainfront.se/secure/GHGoToBoard.jspa?sprintId=7
|
155
|
+
|
156
|
+
SUMMARY:iipax - Ivysaur
|
157
|
+
END:VEVENT
|
158
|
+
END:VCALENDAR
|
data/spec/parser_spec.rb
CHANGED
@@ -66,6 +66,16 @@ describe Icalendar::Parser do
|
|
66
66
|
expect(event.dtstart.utc).to eq Time.parse("20180104T150000Z")
|
67
67
|
end
|
68
68
|
end
|
69
|
+
context 'custom_component.ics' do
|
70
|
+
let(:fn) { 'custom_component.ics' }
|
71
|
+
|
72
|
+
it 'correctly handles custom named components' do
|
73
|
+
parsed = subject.parse
|
74
|
+
calendar = parsed.first
|
75
|
+
expect(calendar.custom_component('x_event_series').size).to eq 1
|
76
|
+
expect(calendar.custom_component('X-EVENT-SERIES').size).to eq 1
|
77
|
+
end
|
78
|
+
end
|
69
79
|
end
|
70
80
|
|
71
81
|
describe '#parse with bad line' do
|
data/spec/roundtrip_spec.rb
CHANGED
@@ -135,6 +135,15 @@ describe Icalendar do
|
|
135
135
|
ical = subject.parse.first.to_ical
|
136
136
|
expect(ical).to include 'CUSTOMFIELD:Not properly noted as custom with X- prefix.'
|
137
137
|
end
|
138
|
+
|
139
|
+
context 'custom components' do
|
140
|
+
let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'custom_component.ics') }
|
141
|
+
|
142
|
+
it 'can output the custom component' do
|
143
|
+
ical = subject.parse.first.to_ical
|
144
|
+
expect(ical).to include 'BEGIN:X-EVENT-SERIES'
|
145
|
+
end
|
146
|
+
end
|
138
147
|
end
|
139
148
|
end
|
140
149
|
end
|
data/spec/timezone_spec.rb
CHANGED
@@ -28,4 +28,52 @@ describe Icalendar::Timezone do
|
|
28
28
|
it { should_not be_valid }
|
29
29
|
end
|
30
30
|
end
|
31
|
+
|
32
|
+
context 'marshalling' do
|
33
|
+
context 'with standard/daylight components' do
|
34
|
+
before do
|
35
|
+
subject.standard do |standard|
|
36
|
+
standard.rrule = Icalendar::Values::Recur.new("FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=10")
|
37
|
+
standard.dtstart = Icalendar::Values::DateTime.new("16010101T030000")
|
38
|
+
standard.tzoffsetfrom = Icalendar::Values::UtcOffset.new("+0200")
|
39
|
+
standard.tzoffsetto = Icalendar::Values::UtcOffset.new("+0100")
|
40
|
+
end
|
41
|
+
|
42
|
+
subject.daylight do |daylight|
|
43
|
+
daylight.rrule = Icalendar::Values::Recur.new("FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3")
|
44
|
+
daylight.dtstart = Icalendar::Values::DateTime.new("16010101T020000")
|
45
|
+
daylight.tzoffsetfrom = Icalendar::Values::UtcOffset.new("+0100")
|
46
|
+
daylight.tzoffsetto = Icalendar::Values::UtcOffset.new("+0200")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'can be de/serialized' do
|
51
|
+
first_standard = subject.standards.first
|
52
|
+
first_daylight = subject.daylights.first
|
53
|
+
|
54
|
+
expect(first_standard.valid?).to be_truthy
|
55
|
+
expect(first_daylight.valid?).to be_truthy
|
56
|
+
|
57
|
+
# calling previous_occurrence intializes @cached_occurrences with a time that's not handled by ruby marshaller
|
58
|
+
first_occurence_for = Time.new(1601, 10, 31)
|
59
|
+
|
60
|
+
standard_previous_occurrence = first_standard.previous_occurrence(first_occurence_for)
|
61
|
+
expect(standard_previous_occurrence).not_to be_nil
|
62
|
+
|
63
|
+
daylight_previous_occurrence = first_daylight.previous_occurrence(first_occurence_for)
|
64
|
+
expect(daylight_previous_occurrence).not_to be_nil
|
65
|
+
|
66
|
+
deserialized = nil
|
67
|
+
|
68
|
+
expect { deserialized = Marshal.load(Marshal.dump(subject)) }.not_to raise_exception
|
69
|
+
|
70
|
+
expect(deserialized.standards.first.previous_occurrence(first_occurence_for)).to eq(standard_previous_occurrence)
|
71
|
+
expect(deserialized.daylights.first.previous_occurrence(first_occurence_for)).to eq(daylight_previous_occurrence)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'can be de/serialized' do
|
76
|
+
expect { Marshal.load(Marshal.dump(subject)) }.not_to raise_exception
|
77
|
+
end
|
78
|
+
end
|
31
79
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: icalendar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Ahearn
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ice_cube
|
@@ -30,56 +30,56 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '2.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '2.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: activesupport
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '6.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '6.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: i18n
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '1.
|
75
|
+
version: '1.8'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '1.
|
82
|
+
version: '1.8'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: tzinfo
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: '1.
|
103
|
+
version: '1.2020'
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: '1.
|
110
|
+
version: '1.2020'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: timecop
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -180,6 +180,7 @@ files:
|
|
180
180
|
- lib/icalendar/has_properties.rb
|
181
181
|
- lib/icalendar/journal.rb
|
182
182
|
- lib/icalendar/logger.rb
|
183
|
+
- lib/icalendar/marshable.rb
|
183
184
|
- lib/icalendar/parser.rb
|
184
185
|
- lib/icalendar/timezone.rb
|
185
186
|
- lib/icalendar/timezone_store.rb
|
@@ -209,6 +210,8 @@ files:
|
|
209
210
|
- spec/calendar_spec.rb
|
210
211
|
- spec/downcased_hash_spec.rb
|
211
212
|
- spec/event_spec.rb
|
213
|
+
- spec/fixtures/bad_wrapping.ics
|
214
|
+
- spec/fixtures/custom_component.ics
|
212
215
|
- spec/fixtures/event.ics
|
213
216
|
- spec/fixtures/nondefault_values.ics
|
214
217
|
- spec/fixtures/nonstandard.ics
|
@@ -245,7 +248,7 @@ metadata: {}
|
|
245
248
|
post_install_message: 'ActiveSupport is required for TimeWithZone support, but not
|
246
249
|
required for general use.
|
247
250
|
|
248
|
-
'
|
251
|
+
'
|
249
252
|
rdoc_options: []
|
250
253
|
require_paths:
|
251
254
|
- lib
|
@@ -253,16 +256,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
253
256
|
requirements:
|
254
257
|
- - ">="
|
255
258
|
- !ruby/object:Gem::Version
|
256
|
-
version: 2.
|
259
|
+
version: 2.4.0
|
257
260
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
258
261
|
requirements:
|
259
262
|
- - ">="
|
260
263
|
- !ruby/object:Gem::Version
|
261
264
|
version: '0'
|
262
265
|
requirements: []
|
263
|
-
|
264
|
-
|
265
|
-
signing_key:
|
266
|
+
rubygems_version: 3.2.11
|
267
|
+
signing_key:
|
266
268
|
specification_version: 4
|
267
269
|
summary: A ruby implementation of the iCalendar specification (RFC-5545).
|
268
270
|
test_files:
|
@@ -270,6 +272,8 @@ test_files:
|
|
270
272
|
- spec/calendar_spec.rb
|
271
273
|
- spec/downcased_hash_spec.rb
|
272
274
|
- spec/event_spec.rb
|
275
|
+
- spec/fixtures/bad_wrapping.ics
|
276
|
+
- spec/fixtures/custom_component.ics
|
273
277
|
- spec/fixtures/event.ics
|
274
278
|
- spec/fixtures/nondefault_values.ics
|
275
279
|
- spec/fixtures/nonstandard.ics
|