icalendar 2.10.2 → 2.11.0

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: 25be453368a5f3878e1914626da23ed5116c8e7835f60bf67c2cf0d87f6766dc
4
- data.tar.gz: 00163247da24fc8057dcd4e1955f72a709ad3fd668526e35af17004232d4b173
3
+ metadata.gz: 8a3ae780c56015cbd4ff657ce042c086038f2f922069fa40eaaeaeedd3b7144c
4
+ data.tar.gz: 88d669f06da7a9b91c3c8a92dd08e11e70b8712a2c4b05a752427abc1ed56435
5
5
  SHA512:
6
- metadata.gz: d812eaf1f48875019ce982e7c6fc9ce900a1dccd315ae072867d62407ff8a13fbb0b2dae9e7c0c9b83870e1630fc5c63c09a4d0812eb539f7c8ffb799d5656b1
7
- data.tar.gz: dcadb2f20833a329b0e4b3845ec2cf41c5b415f739cb34489f8987837892280b888f328dc3e07c6c2aeff6bc5cf5b1db24a9e061993b2868a51cf89444e037e2
6
+ metadata.gz: 250a5156dbb7f7ec7cd7f9fddd51982676d81b5a41ae2815c85b41f3f114e3b3c812c07794463d3563f85a93d8097075a19bb7d37b2ca4694682cc8804ff9be7
7
+ data.tar.gz: 11c71867043fd1fd94cc6199e470b9ff4ce55c7c5e2455d0435b46676b4017743cec130de8f5383faf66eee4e28a400b6b807bf76eb1c781fd47604b7276a4c5
@@ -18,9 +18,10 @@ jobs:
18
18
  fail-fast: false
19
19
  matrix:
20
20
  ruby:
21
- - 3.1.6
22
- - 3.2.4
23
- - 3.3.4
21
+ - 3.1
22
+ - 3.2
23
+ - 3.3
24
+ - 3.4
24
25
 
25
26
  steps:
26
27
  - uses: actions/checkout@v4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 2.11.0 - 2025-04-12
4
+ - Require gems for ruby 3.4 that used to be in stdlib - Go Sueyoshi
5
+ - Refactor how timezone offsets are calculated - Pat Allan
6
+ - 'tzid' param is always returned as an array when accessing `ical_params` directly, rather than matching whatever the input parsed. ICS output remains unchanged
7
+
8
+ ## 2.10.3 - 2024-09-21
9
+ - Override Icalendar::Value.== so that value objects can be compared to each other.
10
+ - Correctly load activesupport before activesupport/time
11
+ - Load ostruct to address deprecation warning - aki77
12
+
3
13
  ## 2.10.2 - 2024-07-21
4
14
  - Raise Icalendar::Parser::ParseError on bad .ics file input instead of RuntimeError - Micah Geisel
5
15
  - Remove Ruby 3.0 from the test matrix - still should work for now with ice_cube < 0.17.0
data/icalendar.gemspec CHANGED
@@ -31,7 +31,10 @@ 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'
37
+ s.add_dependency 'ostruct'
35
38
 
36
39
  s.add_development_dependency 'rake', '~> 13.0'
37
40
  s.add_development_dependency 'bundler', '~> 2.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"
@@ -45,6 +45,14 @@ module Icalendar
45
45
  self.class.value_type
46
46
  end
47
47
 
48
+ def ==(other)
49
+ if other.is_a?(Icalendar::Value)
50
+ super(other.value) && ical_params == other.ical_params
51
+ else
52
+ super
53
+ end
54
+ end
55
+
48
56
  private
49
57
 
50
58
  def needs_value_type?(default_type)
@@ -1,17 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "icalendar/offset"
4
+
3
5
  begin
6
+ require 'active_support'
4
7
  require 'active_support/time'
5
8
 
6
9
  if defined?(ActiveSupport::TimeWithZone)
7
10
  require_relative 'active_support_time_with_zone_adapter'
8
11
  end
9
- rescue NameError
10
- # ActiveSupport v7+ needs the base require to be run first before loading
11
- # specific parts of it.
12
- # https://guides.rubyonrails.org/active_support_core_extensions.html#stand-alone-active-support
13
- require 'active_support'
14
- retry
15
12
  rescue LoadError
16
13
  # tis ok, just a bit less fancy
17
14
  end
@@ -26,7 +23,13 @@ module Icalendar
26
23
  params = Icalendar::DowncasedHash(params)
27
24
  @tz_utc = params['tzid'] == 'UTC'
28
25
  @timezone_store = params.delete 'x-tz-store'
29
- 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
30
33
  end
31
34
 
32
35
  def __getobj__
@@ -34,9 +37,9 @@ module Icalendar
34
37
  if set_offset?
35
38
  orig_value
36
39
  else
37
- offset = offset_value(orig_value, ical_params)
38
- __setobj__(offset) unless offset.nil?
39
- 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
40
43
  end
41
44
  end
42
45
 
@@ -47,36 +50,6 @@ module Icalendar
47
50
 
48
51
  private
49
52
 
50
- def offset_value(value, params)
51
- @offset_value = unless params.nil? || params['tzid'].nil?
52
- tzid = params['tzid'].is_a?(::Array) ? params['tzid'].first : params['tzid']
53
- support_classes_defined = defined?(ActiveSupport::TimeZone) && defined?(ActiveSupportTimeWithZoneAdapter)
54
- if support_classes_defined && (tz = ActiveSupport::TimeZone[tzid])
55
- Icalendar.logger.debug("Plan a - parsing #{value}/#{tzid} as ActiveSupport::TimeWithZone")
56
- # plan a - use ActiveSupport::TimeWithZone
57
- ActiveSupportTimeWithZoneAdapter.new(nil, tz, value)
58
- elsif !timezone_store.nil? && !(x_tz_info = timezone_store.retrieve(tzid)).nil?
59
- # plan b - use definition from provided `VTIMEZONE`
60
- offset = x_tz_info.offset_for_local(value).to_s
61
- Icalendar.logger.debug("Plan b - parsing #{value} with offset: #{offset}")
62
- if value.respond_to?(:change)
63
- value.change offset: offset
64
- else
65
- ::Time.new value.year, value.month, value.day, value.hour, value.min, value.sec, offset
66
- end
67
- elsif support_classes_defined && (tz = ActiveSupport::TimeZone[tzid.split.first])
68
- # plan c - try to find an ActiveSupport::TimeWithZone based on the first word of the tzid
69
- Icalendar.logger.debug("Plan c - parsing #{value}/#{tz.tzinfo.name} as ActiveSupport::TimeWithZone")
70
- params['tzid'] = [tz.tzinfo.name]
71
- ActiveSupportTimeWithZoneAdapter.new(nil, tz, value)
72
- else
73
- # plan d - just ignore the tzid
74
- Icalendar.logger.info("Ignoring timezone #{tzid} for time #{value}")
75
- nil
76
- end
77
- end
78
- end
79
-
80
53
  def set_offset?
81
54
  !!@offset_value
82
55
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Icalendar
4
4
 
5
- VERSION = '2.10.2'
5
+ VERSION = '2.11.0'
6
6
 
7
7
  end
data/spec/spec_helper.rb CHANGED
@@ -12,6 +12,12 @@ end
12
12
  require 'timecop'
13
13
  require 'icalendar'
14
14
 
15
+ if defined?(ActiveSupport)
16
+ # this has been default behavior for new Rails apps for a long time, and the
17
+ # only option once ActiveSupport goes to 8.x
18
+ ActiveSupport.to_time_preserves_timezone = true if ActiveSupport.respond_to?(:to_time_preserves_timezone=)
19
+ end
20
+
15
21
  RSpec.configure do |config|
16
22
  config.run_all_when_everything_filtered = true
17
23
  config.filter_run :focus
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Icalendar::Values::Date do
4
+
5
+ subject { described_class.new value, params }
6
+ let(:value) { '20140209' }
7
+ let(:params) { {} }
8
+
9
+ describe "#==" do
10
+ let(:other) { described_class.new value }
11
+
12
+ it "is equivalent to another Icalendar::Values::Date" do
13
+ expect(subject).to eq other
14
+ end
15
+
16
+ context "differing params" do
17
+ let(:params) { {"x-custom-param": "param-value"} }
18
+
19
+ it "is no longer equivalent" do
20
+ expect(subject).not_to eq other
21
+ end
22
+ end
23
+ end
24
+ 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.2
4
+ version: 2.11.0
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-07-21 00:00:00.000000000 Z
11
+ date: 2025-04-12 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,34 @@ 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'
55
+ - !ruby/object:Gem::Dependency
56
+ name: ostruct
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
27
69
  - !ruby/object:Gem::Dependency
28
70
  name: rake
29
71
  requirement: !ruby/object:Gem::Requirement
@@ -181,6 +223,11 @@ files:
181
223
  - lib/icalendar/journal.rb
182
224
  - lib/icalendar/logger.rb
183
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
184
231
  - lib/icalendar/parser.rb
185
232
  - lib/icalendar/timezone.rb
186
233
  - lib/icalendar/timezone_store.rb
@@ -237,6 +284,7 @@ files:
237
284
  - spec/todo_spec.rb
238
285
  - spec/tzinfo_spec.rb
239
286
  - spec/values/date_or_date_time_spec.rb
287
+ - spec/values/date_spec.rb
240
288
  - spec/values/date_time_spec.rb
241
289
  - spec/values/duration_spec.rb
242
290
  - spec/values/period_spec.rb
@@ -304,6 +352,7 @@ test_files:
304
352
  - spec/todo_spec.rb
305
353
  - spec/tzinfo_spec.rb
306
354
  - spec/values/date_or_date_time_spec.rb
355
+ - spec/values/date_spec.rb
307
356
  - spec/values/date_time_spec.rb
308
357
  - spec/values/duration_spec.rb
309
358
  - spec/values/period_spec.rb