icalendar 2.10.3 → 2.11.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '091658a5c785f876d990794ef88444d4ea39e047aaf533ca3e93bfc8549c2495'
4
- data.tar.gz: 1292b5e139aae74264694245d9a722bc42e501abe6620b341b12d4922ac3cac8
3
+ metadata.gz: 0d1b88e3b63c6a6c5865093d45c296bfc4f0d807922b79bbd071355f5bdf72a2
4
+ data.tar.gz: 2b0e5e61d9dbab1ec76aab2a97bf3055da0ee42bfcb294867e1cba5d60841d17
5
5
  SHA512:
6
- metadata.gz: 698656b8f89e982eb58c80cce76c4a8512df6ebdbcc37779eeeb2c57daaf3f180fe0226c3a8bdb66155438e2f71afa6df76b537d8a4cb528646211c850a1e70d
7
- data.tar.gz: acd49ceb561f8f5a61be057c3ea00b938a6907a4af9826afd27fe2f484e256d1ff97869d5777ac58e79e55834ed97cc7c56136fb38d6b54f500959dc054c8d22
6
+ metadata.gz: 070602bed48d0b45c04dc6801944b0f2d8916a67ecc39194ff93a5bc6619aca5eafad06843cb39df72910f9babbe010310dcada5bce1a27d046297586a0d1042
7
+ data.tar.gz: f4e43c98d659fa97b4cffadb0c79ba34ef0ba077d181272747eaf0065301d1b540a3fa5169dcd8d1701a60076f1f748ec8d28a44d1aefaf179197437ecab2958
@@ -21,6 +21,7 @@ jobs:
21
21
  - 3.1
22
22
  - 3.2
23
23
  - 3.3
24
+ - 3.4
24
25
 
25
26
  steps:
26
27
  - uses: actions/checkout@v4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 2.11.2 - 2025-06-21
4
+ - Deal with more bad value parameter values by falling back to the property default type
5
+
6
+ ## 2.11.1 - 2025-06-06
7
+ - Gracefully deal with malformed ics files that use spaces in the value parameter instead of hyphens
8
+
9
+ ## 2.11.0 - 2025-04-12
10
+ - Require gems for ruby 3.4 that used to be in stdlib - Go Sueyoshi
11
+ - Refactor how timezone offsets are calculated - Pat Allan
12
+ - 'tzid' param is always returned as an array when accessing `ical_params` directly, rather than matching whatever the input parsed. ICS output remains unchanged
13
+
3
14
  ## 2.10.3 - 2024-09-21
4
15
  - Override Icalendar::Value.== so that value objects can be compared to each other.
5
16
  - Correctly load activesupport before activesupport/time
data/icalendar.gemspec CHANGED
@@ -31,7 +31,9 @@ ActiveSupport is required for TimeWithZone support, but not required for general
31
31
 
32
32
  s.required_ruby_version = '>= 2.4.0'
33
33
 
34
+ s.add_dependency 'base64'
34
35
  s.add_dependency 'ice_cube', '~> 0.16'
36
+ s.add_dependency 'logger'
35
37
  s.add_dependency 'ostruct'
36
38
 
37
39
  s.add_development_dependency 'rake', '~> 13.0'
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icalendar
4
+ class Offset
5
+ class ActiveSupportExact < Icalendar::Offset
6
+ def valid?
7
+ support_classes_defined? && tz
8
+ end
9
+
10
+ def normalized_value
11
+ Icalendar.logger.debug("Plan a - parsing #{value}/#{tzid} as ActiveSupport::TimeWithZone")
12
+ # plan a - use ActiveSupport::TimeWithZone
13
+ Icalendar::Values::Helpers::ActiveSupportTimeWithZoneAdapter.new(nil, tz, value)
14
+ end
15
+
16
+ private
17
+
18
+ def tz
19
+ @tz ||= ActiveSupport::TimeZone[tzid]
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icalendar
4
+ class Offset
5
+ class ActiveSupportPartial < Offset
6
+ def valid?
7
+ support_classes_defined? && tz
8
+ end
9
+
10
+ def normalized_value
11
+ # plan c - try to find an ActiveSupport::TimeWithZone based on the first word of the tzid
12
+ Icalendar.logger.debug("Plan c - parsing #{value}/#{tz.tzinfo.name} as ActiveSupport::TimeWithZone")
13
+ Icalendar::Values::Helpers::ActiveSupportTimeWithZoneAdapter.new(nil, tz, value)
14
+ end
15
+
16
+ def normalized_tzid
17
+ [tz.tzinfo.name]
18
+ end
19
+
20
+ private
21
+
22
+ def tz
23
+ @tz ||= ActiveSupport::TimeZone[tzid.split.first]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icalendar
4
+ class Offset
5
+ class Null < Offset
6
+ def valid?
7
+ true
8
+ end
9
+
10
+ def normalized_value
11
+ # plan d - just ignore the tzid
12
+ Icalendar.logger.info("Ignoring timezone #{tzid} for time #{value}")
13
+ nil
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icalendar
4
+ class Offset
5
+ class TimeZoneStore < Offset
6
+ def valid?
7
+ timezone_store && tz_info
8
+ end
9
+
10
+ def normalized_value
11
+ # plan b - use definition from provided `VTIMEZONE`
12
+ offset = tz_info.offset_for_local(value).to_s
13
+
14
+ Icalendar.logger.debug("Plan b - parsing #{value} with offset: #{offset}")
15
+ if value.respond_to?(:change)
16
+ value.change offset: offset
17
+ else
18
+ ::Time.new value.year, value.month, value.day, value.hour, value.min, value.sec, offset
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def tz_info
25
+ @tz_info ||= timezone_store.retrieve(tzid)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Icalendar
4
+ class Offset
5
+ def self.build(value, params, timezone_store)
6
+ return nil if params.nil? || params['tzid'].nil?
7
+
8
+ tzid = Array(params['tzid']).first
9
+
10
+ [
11
+ Icalendar::Offset::ActiveSupportExact,
12
+ Icalendar::Offset::TimeZoneStore,
13
+ Icalendar::Offset::ActiveSupportPartial,
14
+ Icalendar::Offset::Null
15
+ ].lazy.map { |klass| klass.new(tzid, value, timezone_store) }.detect(&:valid?)
16
+ end
17
+
18
+ def initialize(tzid, value, timezone_store)
19
+ @tzid = tzid
20
+ @value = value
21
+ @timezone_store = timezone_store
22
+ end
23
+
24
+ def normalized_tzid
25
+ Array(tzid)
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :tzid, :value, :timezone_store
31
+
32
+ def support_classes_defined?
33
+ defined?(ActiveSupport::TimeZone) &&
34
+ defined?(Icalendar::Values::Helpers::ActiveSupportTimeWithZoneAdapter)
35
+ end
36
+ end
37
+ end
38
+
39
+ require_relative "offset/active_support_exact"
40
+ require_relative "offset/active_support_partial"
41
+ require_relative "offset/null"
42
+ require_relative "offset/time_zone_store"
@@ -105,7 +105,7 @@ module Icalendar
105
105
  ((multi_property && value =~ WRAP_IN_ARRAY_REGEX_1) || value =~ WRAP_IN_ARRAY_REGEX_2)
106
106
  end
107
107
 
108
- GET_WRAPPER_CLASS_GSUB_REGEX = /(?:\A|-)(.)/.freeze
108
+ GET_WRAPPER_CLASS_GSUB_REGEX = /(?:\A|-|\s+)(.)/.freeze
109
109
 
110
110
  def get_wrapper_class(component, fields)
111
111
  klass = component.class.default_property_types[fields[:name]]
@@ -113,7 +113,12 @@ module Icalendar
113
113
  klass_name = fields[:params].delete('value').first
114
114
  unless klass_name.upcase == klass.value_type
115
115
  klass_name = "Icalendar::Values::#{klass_name.downcase.gsub(GET_WRAPPER_CLASS_GSUB_REGEX) { |m| m[-1].upcase }}"
116
- klass = Object.const_get klass_name if Object.const_defined?(klass_name)
116
+ begin
117
+ klass = Object.const_get klass_name if Object.const_defined?(klass_name)
118
+ rescue NameError => e
119
+ Icalendar.logger.error "NameError trying to find value type for #{component.name} | #{fields[:name]}: #{e.message}"
120
+ raise e if strict?
121
+ end
117
122
  end
118
123
  end
119
124
  klass
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "icalendar/offset"
4
+
3
5
  begin
4
6
  require 'active_support'
5
7
  require 'active_support/time'
@@ -21,7 +23,13 @@ module Icalendar
21
23
  params = Icalendar::DowncasedHash(params)
22
24
  @tz_utc = params['tzid'] == 'UTC'
23
25
  @timezone_store = params.delete 'x-tz-store'
24
- super (offset_value(value, params) || value), params
26
+
27
+ offset = Icalendar::Offset.build(value, params, timezone_store)
28
+
29
+ @offset_value = offset&.normalized_value
30
+ params['tzid'] = offset.normalized_tzid if offset
31
+
32
+ super (@offset_value || value), params
25
33
  end
26
34
 
27
35
  def __getobj__
@@ -29,9 +37,9 @@ module Icalendar
29
37
  if set_offset?
30
38
  orig_value
31
39
  else
32
- offset = offset_value(orig_value, ical_params)
33
- __setobj__(offset) unless offset.nil?
34
- offset || orig_value
40
+ new_value = Icalendar::Offset.build(orig_value, ical_params, timezone_store)&.normalized_value
41
+ __setobj__(new_value) unless new_value.nil?
42
+ new_value || orig_value
35
43
  end
36
44
  end
37
45
 
@@ -42,36 +50,6 @@ module Icalendar
42
50
 
43
51
  private
44
52
 
45
- def offset_value(value, params)
46
- @offset_value = unless params.nil? || params['tzid'].nil?
47
- tzid = params['tzid'].is_a?(::Array) ? params['tzid'].first : params['tzid']
48
- support_classes_defined = defined?(ActiveSupport::TimeZone) && defined?(ActiveSupportTimeWithZoneAdapter)
49
- if support_classes_defined && (tz = ActiveSupport::TimeZone[tzid])
50
- Icalendar.logger.debug("Plan a - parsing #{value}/#{tzid} as ActiveSupport::TimeWithZone")
51
- # plan a - use ActiveSupport::TimeWithZone
52
- ActiveSupportTimeWithZoneAdapter.new(nil, tz, value)
53
- elsif !timezone_store.nil? && !(x_tz_info = timezone_store.retrieve(tzid)).nil?
54
- # plan b - use definition from provided `VTIMEZONE`
55
- offset = x_tz_info.offset_for_local(value).to_s
56
- Icalendar.logger.debug("Plan b - parsing #{value} with offset: #{offset}")
57
- if value.respond_to?(:change)
58
- value.change offset: offset
59
- else
60
- ::Time.new value.year, value.month, value.day, value.hour, value.min, value.sec, offset
61
- end
62
- elsif support_classes_defined && (tz = ActiveSupport::TimeZone[tzid.split.first])
63
- # plan c - try to find an ActiveSupport::TimeWithZone based on the first word of the tzid
64
- Icalendar.logger.debug("Plan c - parsing #{value}/#{tz.tzinfo.name} as ActiveSupport::TimeWithZone")
65
- params['tzid'] = [tz.tzinfo.name]
66
- ActiveSupportTimeWithZoneAdapter.new(nil, tz, value)
67
- else
68
- # plan d - just ignore the tzid
69
- Icalendar.logger.info("Ignoring timezone #{tzid} for time #{value}")
70
- nil
71
- end
72
- end
73
- end
74
-
75
53
  def set_offset?
76
54
  !!@offset_value
77
55
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Icalendar
4
4
 
5
- VERSION = '2.10.3'
5
+ VERSION = '2.11.2'
6
6
 
7
7
  end
@@ -0,0 +1,23 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ PRODID:bsprodidfortestabc123
4
+ CALSCALE:GREGORIAN
5
+ BEGIN:VEVENT
6
+ DTSTAMP:20050118T211523Z
7
+ UID:bsuidfortestabc123
8
+ DTSTART;TZID=US-Mountain:20050120T170000
9
+ DTEND;TZID=US-Mountain:20050120T184500
10
+ CLASS:PRIVATE
11
+ GEO:37.386013;-122.0829322
12
+ ORGANIZER;CN="Joe Bob: Magician":mailto:joebob@random.net
13
+ LOCATION;VALUE=1000 MAIN ST EXAMPLE, STATE 12345:1000 Main St Example\, State 12345
14
+ PRIORITY:2
15
+ SUMMARY:This is a really long summary to test the method of unfolding lines
16
+ \, so I'm just going to make it a whole bunch of lines. With a twist: a "
17
+ ö" takes up multiple bytes\, and should be wrapped to the next line.
18
+ ATTACH:http://bush.sucks.org/impeach/him.rhtml
19
+ ATTACH:http://corporations-dominate.existence.net/why.rhtml
20
+ RDATE;TZID=US-Mountain:20050121T170000,20050122T170000
21
+ X-TEST-COMPONENT;QTEST="Hello, World":Shouldn't double double quotes
22
+ END:VEVENT
23
+ END:VCALENDAR
@@ -38,7 +38,7 @@ SEQUENCE:13
38
38
  BEGIN:VALARM
39
39
  X-WR-ALARMUID:6F54CD66-F2A9-491D-8892-7E3209F6A2E4
40
40
  UID:6F54CD66-F2A9-491D-8892-7E3209F6A2E4
41
- TRIGGER;VALUE=DATE-TIME:19760401T005545Z
41
+ TRIGGER;VALUE=DATE TIME:19760401T005545Z
42
42
  ACTION:NONE
43
43
  END:VALARM
44
44
  END:VEVENT
data/spec/parser_spec.rb CHANGED
@@ -116,4 +116,14 @@ describe Icalendar::Parser do
116
116
  expect(event.dtstart).to be_kind_of Icalendar::Values::Date
117
117
  end
118
118
  end
119
+
120
+ describe 'completely bad location value' do
121
+ let(:fn) { 'single_event_bad_location.ics' }
122
+
123
+ it 'falls back to string type for location' do
124
+ event = subject.parse.first.events.first
125
+ expect(event.location).to be_kind_of Icalendar::Values::Text
126
+ expect(event.location.value).to eq "1000 Main St Example, State 12345"
127
+ end
128
+ end
119
129
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icalendar
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.10.3
4
+ version: 2.11.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Ahearn
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-21 00:00:00.000000000 Z
11
+ date: 2025-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base64
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: ice_cube
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: logger
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: ostruct
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -195,6 +223,11 @@ files:
195
223
  - lib/icalendar/journal.rb
196
224
  - lib/icalendar/logger.rb
197
225
  - lib/icalendar/marshable.rb
226
+ - lib/icalendar/offset.rb
227
+ - lib/icalendar/offset/active_support_exact.rb
228
+ - lib/icalendar/offset/active_support_partial.rb
229
+ - lib/icalendar/offset/null.rb
230
+ - lib/icalendar/offset/time_zone_store.rb
198
231
  - lib/icalendar/parser.rb
199
232
  - lib/icalendar/timezone.rb
200
233
  - lib/icalendar/timezone_store.rb
@@ -234,6 +267,7 @@ files:
234
267
  - spec/fixtures/single_event.ics
235
268
  - spec/fixtures/single_event_bad_dtstart.ics
236
269
  - spec/fixtures/single_event_bad_line.ics
270
+ - spec/fixtures/single_event_bad_location.ics
237
271
  - spec/fixtures/single_event_bad_organizer.ics
238
272
  - spec/fixtures/single_event_organizer_parsed.ics
239
273
  - spec/fixtures/timezone.ics
@@ -302,6 +336,7 @@ test_files:
302
336
  - spec/fixtures/single_event.ics
303
337
  - spec/fixtures/single_event_bad_dtstart.ics
304
338
  - spec/fixtures/single_event_bad_line.ics
339
+ - spec/fixtures/single_event_bad_location.ics
305
340
  - spec/fixtures/single_event_bad_organizer.ics
306
341
  - spec/fixtures/single_event_organizer_parsed.ics
307
342
  - spec/fixtures/timezone.ics