icalendar 2.9.0 → 2.10.1
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/.github/workflows/main.yml +3 -4
- data/History.txt +8 -0
- data/icalendar.gemspec +11 -6
- data/lib/icalendar/has_properties.rb +2 -2
- data/lib/icalendar/parser.rb +2 -2
- data/lib/icalendar/timezone.rb +6 -4
- data/lib/icalendar/value.rb +1 -1
- data/lib/icalendar/values/date.rb +1 -1
- data/lib/icalendar/values/date_time.rb +2 -2
- data/lib/icalendar/values/helpers/active_support_time_with_zone_adapter.rb +16 -0
- data/lib/icalendar/values/helpers/array.rb +62 -0
- data/lib/icalendar/values/helpers/time_with_zone.rb +86 -0
- data/lib/icalendar/values/time.rb +2 -2
- data/lib/icalendar/version.rb +1 -1
- data/spec/fixtures/reversed_timezone.ics +143 -0
- data/spec/parser_spec.rb +9 -0
- data/spec/roundtrip_spec.rb +1 -1
- data/spec/values/date_time_spec.rb +16 -0
- metadata +28 -25
- data/lib/icalendar/values/active_support_time_with_zone_adapter.rb +0 -14
- data/lib/icalendar/values/array.rb +0 -60
- data/lib/icalendar/values/time_with_zone.rb +0 -55
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b9f7e30ef6614ad4175a9d282d1b03b57f140f98a2962b4e0268aa015f8f942
|
4
|
+
data.tar.gz: 7ebabfddd0f3d8e8e05e1ef82d2389007c1419c727c5404d2ec2c1c09565a8bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d3221e4e5bd2120014ae11fe80a8e0a3b36af37b10aadd18f3bc6022e296efa47ea27d09572900bb6d6419fff64054caa6709e6b14e5182d32a188d0130b7f0
|
7
|
+
data.tar.gz: 5d7d996d53a12543de0830fdb8214e2ae39df4c19c649e81cd95ceb8d30d61f1f178b7a9a4f4e37adb591609eeac7e89d930cf5ffc24951c9cbfcb0b815c2a03
|
data/.github/workflows/main.yml
CHANGED
@@ -17,13 +17,12 @@ jobs:
|
|
17
17
|
strategy:
|
18
18
|
matrix:
|
19
19
|
ruby:
|
20
|
-
-
|
21
|
-
- 3.
|
22
|
-
- 3.1.2
|
20
|
+
- 3.0.6
|
21
|
+
- 3.1.4
|
23
22
|
- 3.2.2
|
24
23
|
|
25
24
|
steps:
|
26
|
-
- uses: actions/checkout@
|
25
|
+
- uses: actions/checkout@v4
|
27
26
|
- name: Set up Ruby
|
28
27
|
uses: ruby/setup-ruby@v1
|
29
28
|
with:
|
data/History.txt
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
=== Unreleased
|
2
2
|
|
3
|
+
=== 2.10.1 2023-12-01
|
4
|
+
* Parsing now handles VTIMEZONE blocks defined after their TZID is used in events and other components
|
5
|
+
|
6
|
+
=== 2.10.0 2023-11-01
|
7
|
+
* Add changelog metadata to gemspec - Juri Hahn
|
8
|
+
* Attempt to rescue timezone info when given a nonstandard tzid with no VTIMEZONE
|
9
|
+
* Move Values classes that shouldn't be directly used into Helpers module
|
10
|
+
|
3
11
|
=== 2.9.0 2023-08-11
|
4
12
|
* Always include the VALUE of Event URLs for improved compatibility - Sean Kelley
|
5
13
|
* Improved parse performance - Thomas Cannon
|
data/icalendar.gemspec
CHANGED
@@ -20,6 +20,10 @@ variety of calendaring applications.
|
|
20
20
|
ActiveSupport is required for TimeWithZone support, but not required for general use.
|
21
21
|
EOM
|
22
22
|
|
23
|
+
s.metadata = {
|
24
|
+
'changelog_uri' => 'https://github.com/icalendar/icalendar/blob/main/History.txt'
|
25
|
+
}
|
26
|
+
|
23
27
|
s.files = `git ls-files`.split "\n"
|
24
28
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split "\n"
|
25
29
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename f }
|
@@ -34,13 +38,13 @@ ActiveSupport is required for TimeWithZone support, but not required for general
|
|
34
38
|
|
35
39
|
# test with all groups of tzinfo dependencies
|
36
40
|
# tzinfo 2.x
|
37
|
-
|
38
|
-
|
39
|
-
# tzinfo 1.x
|
40
|
-
s.add_development_dependency 'activesupport', '~> 6.0'
|
41
|
-
s.add_development_dependency 'i18n', '~> 1.8'
|
42
|
-
s.add_development_dependency 'tzinfo', '~> 1.2'
|
41
|
+
s.add_development_dependency 'activesupport', '~> 7.1'
|
42
|
+
s.add_development_dependency 'tzinfo', '~> 2.0'
|
43
43
|
s.add_development_dependency 'tzinfo-data', '~> 1.2020'
|
44
|
+
# tzinfo 1.x
|
45
|
+
# s.add_development_dependency 'activesupport', '~> 6.0'
|
46
|
+
# s.add_development_dependency 'tzinfo', '~> 1.2'
|
47
|
+
# s.add_development_dependency 'tzinfo-data', '~> 1.2020'
|
44
48
|
# tzinfo 0.x
|
45
49
|
# s.add_development_dependency 'tzinfo', '~> 0.3'
|
46
50
|
# end tzinfo
|
@@ -48,4 +52,5 @@ ActiveSupport is required for TimeWithZone support, but not required for general
|
|
48
52
|
s.add_development_dependency 'timecop', '~> 0.9'
|
49
53
|
s.add_development_dependency 'rspec', '~> 3.8'
|
50
54
|
s.add_development_dependency 'simplecov', '~> 0.16'
|
55
|
+
s.add_development_dependency 'byebug'
|
51
56
|
end
|
@@ -148,7 +148,7 @@ module Icalendar
|
|
148
148
|
|
149
149
|
define_method "#{prop}=" do |value|
|
150
150
|
mapped = map_property_value value, klass, true, new_property
|
151
|
-
if mapped.is_a? Icalendar::Values::Array
|
151
|
+
if mapped.is_a? Icalendar::Values::Helpers::Array
|
152
152
|
instance_variable_set property_var, mapped.to_a.compact
|
153
153
|
else
|
154
154
|
instance_variable_set property_var, [mapped].compact
|
@@ -179,7 +179,7 @@ module Icalendar
|
|
179
179
|
if value.nil? || value.is_a?(Icalendar::Value)
|
180
180
|
value
|
181
181
|
elsif value.is_a? ::Array
|
182
|
-
Icalendar::Values::Array.new value, klass, params, {delimiter: (multi_valued ? ',' : ';')}
|
182
|
+
Icalendar::Values::Helpers::Array.new value, klass, params, {delimiter: (multi_valued ? ',' : ';')}
|
183
183
|
else
|
184
184
|
klass.new value, params
|
185
185
|
end
|
data/lib/icalendar/parser.rb
CHANGED
@@ -84,7 +84,7 @@ module Icalendar
|
|
84
84
|
klass = get_wrapper_class component, fields
|
85
85
|
if wrap_in_array? klass, fields[:value], multi_property
|
86
86
|
delimiter = fields[:value].match(WRAP_PROPERTY_VALUE_DELIMETER_REGEX)[1]
|
87
|
-
Icalendar::Values::Array.new fields[:value].split(WRAP_PROPERTY_VALUE_SPLIT_REGEX),
|
87
|
+
Icalendar::Values::Helpers::Array.new fields[:value].split(WRAP_PROPERTY_VALUE_SPLIT_REGEX),
|
88
88
|
klass,
|
89
89
|
fields[:params],
|
90
90
|
delimiter: delimiter
|
@@ -211,7 +211,7 @@ module Icalendar
|
|
211
211
|
param_value = param_value.gsub(PVALUE_GSUB_REGEX, '')
|
212
212
|
params[param_name] << param_value
|
213
213
|
if param_name == 'tzid'
|
214
|
-
params['x-tz-
|
214
|
+
params['x-tz-store'] = timezone_store
|
215
215
|
end
|
216
216
|
end
|
217
217
|
end
|
data/lib/icalendar/timezone.rb
CHANGED
@@ -108,15 +108,17 @@ module Icalendar
|
|
108
108
|
|
109
109
|
def standard_for(local)
|
110
110
|
possible = standards.map do |std|
|
111
|
-
|
112
|
-
|
111
|
+
prev = std.previous_occurrence(local.to_time)
|
112
|
+
[prev, std] unless prev.nil?
|
113
|
+
end.compact
|
113
114
|
possible.sort_by(&:first).last
|
114
115
|
end
|
115
116
|
|
116
117
|
def daylight_for(local)
|
117
118
|
possible = daylights.map do |day|
|
118
|
-
|
119
|
-
|
119
|
+
prev = day.previous_occurrence(local.to_time)
|
120
|
+
[prev, day] unless prev.nil?
|
121
|
+
end.compact
|
120
122
|
possible.sort_by(&:first).last
|
121
123
|
end
|
122
124
|
end
|
data/lib/icalendar/value.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'date'
|
4
|
-
require_relative 'time_with_zone'
|
4
|
+
require_relative 'helpers/time_with_zone'
|
5
5
|
|
6
6
|
module Icalendar
|
7
7
|
module Values
|
8
8
|
|
9
9
|
class DateTime < Value
|
10
|
-
include TimeWithZone
|
10
|
+
include Helpers::TimeWithZone
|
11
11
|
|
12
12
|
FORMAT = '%Y%m%dT%H%M%S'
|
13
13
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Icalendar
|
4
|
+
module Values
|
5
|
+
module Helpers
|
6
|
+
class ActiveSupportTimeWithZoneAdapter < ActiveSupport::TimeWithZone
|
7
|
+
# ActiveSupport::TimeWithZone implements a #to_a method that will cause
|
8
|
+
# unexpected behavior in components with multi_property DateTime
|
9
|
+
# properties when the setters for those properties are invoked with an
|
10
|
+
# Icalendar::Values::DateTime that is delegating for an
|
11
|
+
# ActiveSupport::TimeWithZone. To avoid this behavior, undefine #to_a.
|
12
|
+
undef_method :to_a
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Icalendar
|
4
|
+
module Values
|
5
|
+
module Helpers
|
6
|
+
|
7
|
+
class Array < Value
|
8
|
+
|
9
|
+
attr_reader :value_delimiter
|
10
|
+
|
11
|
+
def initialize(value, klass, params = {}, options = {})
|
12
|
+
@value_delimiter = options[:delimiter] || ','
|
13
|
+
mapped = if value.is_a? ::Array
|
14
|
+
value.map do |v|
|
15
|
+
if v.is_a? Icalendar::Values::Helpers::Array
|
16
|
+
Icalendar::Values::Helpers::Array.new v.value, klass, v.ical_params, delimiter: v.value_delimiter
|
17
|
+
elsif v.is_a? ::Array
|
18
|
+
Icalendar::Values::Helpers::Array.new v, klass, params, delimiter: value_delimiter
|
19
|
+
elsif v.is_a? Icalendar::Value
|
20
|
+
v
|
21
|
+
else
|
22
|
+
klass.new v, params
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
[klass.new(value, params)]
|
27
|
+
end
|
28
|
+
super mapped
|
29
|
+
end
|
30
|
+
|
31
|
+
def params_ical
|
32
|
+
value.each do |v|
|
33
|
+
ical_params.merge! v.ical_params
|
34
|
+
end
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
def value_ical
|
39
|
+
value.map do |v|
|
40
|
+
v.value_ical
|
41
|
+
end.join value_delimiter
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid?
|
45
|
+
klass = value.first.class
|
46
|
+
!value.all? { |v| v.class == klass }
|
47
|
+
end
|
48
|
+
|
49
|
+
def value_type
|
50
|
+
value.first.value_type
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def needs_value_type?(default_type)
|
56
|
+
value.first.class != default_type
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'active_support/time'
|
5
|
+
|
6
|
+
if defined?(ActiveSupport::TimeWithZone)
|
7
|
+
require_relative 'active_support_time_with_zone_adapter'
|
8
|
+
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
|
+
rescue LoadError
|
16
|
+
# tis ok, just a bit less fancy
|
17
|
+
end
|
18
|
+
|
19
|
+
module Icalendar
|
20
|
+
module Values
|
21
|
+
module Helpers
|
22
|
+
module TimeWithZone
|
23
|
+
attr_reader :tz_utc, :timezone_store
|
24
|
+
|
25
|
+
def initialize(value, params = {})
|
26
|
+
params = Icalendar::DowncasedHash(params)
|
27
|
+
@tz_utc = params['tzid'] == 'UTC'
|
28
|
+
@timezone_store = params.delete 'x-tz-store'
|
29
|
+
super (offset_value(value, params) || value), params
|
30
|
+
end
|
31
|
+
|
32
|
+
def __getobj__
|
33
|
+
orig_value = super
|
34
|
+
if set_offset?
|
35
|
+
orig_value
|
36
|
+
else
|
37
|
+
offset = offset_value(orig_value, ical_params)
|
38
|
+
__setobj__(offset) unless offset.nil?
|
39
|
+
offset || orig_value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def params_ical
|
44
|
+
ical_params.delete 'tzid' if tz_utc
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
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
|
+
def set_offset?
|
81
|
+
!!@offset_value
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'date'
|
4
|
-
require_relative 'time_with_zone'
|
4
|
+
require_relative 'helpers/time_with_zone'
|
5
5
|
|
6
6
|
module Icalendar
|
7
7
|
module Values
|
8
8
|
|
9
9
|
class Time < Value
|
10
|
-
include TimeWithZone
|
10
|
+
include Helpers::TimeWithZone
|
11
11
|
|
12
12
|
FORMAT = '%H%M%S'
|
13
13
|
|
data/lib/icalendar/version.rb
CHANGED
@@ -0,0 +1,143 @@
|
|
1
|
+
BEGIN:VCALENDAR
|
2
|
+
VERSION:2.0
|
3
|
+
X-WR-CALNAME:My Calendar
|
4
|
+
X-APPLE-CALENDAR-COLOR:#44A703
|
5
|
+
BEGIN:VEVENT
|
6
|
+
CREATED:20231026T211915Z
|
7
|
+
DTEND;TZID=NONSTANDARDTZ:20231025T180000
|
8
|
+
DTSTAMP:20231026T211916Z
|
9
|
+
DTSTART;TZID=NONSTANDARDTZ:20231025T163000
|
10
|
+
LAST-MODIFIED:20231026T211915Z
|
11
|
+
LOCATION:Somewhere
|
12
|
+
SEQUENCE:0
|
13
|
+
SUMMARY:Summary summary summary
|
14
|
+
UID:some-unique-uid
|
15
|
+
END:VEVENT
|
16
|
+
BEGIN:VTIMEZONE
|
17
|
+
TZID:NONSTANDARDTZ
|
18
|
+
X-LIC-LOCATION:NONSTANDARDTZ
|
19
|
+
BEGIN:STANDARD
|
20
|
+
DTSTART:18831118T120702
|
21
|
+
RDATE:18831118T120702
|
22
|
+
TZNAME:PST
|
23
|
+
TZOFFSETFROM:-075258
|
24
|
+
TZOFFSETTO:-0800
|
25
|
+
END:STANDARD
|
26
|
+
BEGIN:DAYLIGHT
|
27
|
+
DTSTART:19180331T020000
|
28
|
+
RRULE:FREQ=YEARLY;UNTIL=19190330T100000Z;BYMONTH=3;BYDAY=-1SU
|
29
|
+
TZNAME:PDT
|
30
|
+
TZOFFSETFROM:-0800
|
31
|
+
TZOFFSETTO:-0700
|
32
|
+
END:DAYLIGHT
|
33
|
+
BEGIN:STANDARD
|
34
|
+
DTSTART:19181027T020000
|
35
|
+
RRULE:FREQ=YEARLY;UNTIL=19191026T090000Z;BYMONTH=10;BYDAY=-1SU
|
36
|
+
TZNAME:PST
|
37
|
+
TZOFFSETFROM:-0700
|
38
|
+
TZOFFSETTO:-0800
|
39
|
+
END:STANDARD
|
40
|
+
BEGIN:DAYLIGHT
|
41
|
+
DTSTART:19420209T020000
|
42
|
+
RDATE:19420209T020000
|
43
|
+
TZNAME:PWT
|
44
|
+
TZOFFSETFROM:-0800
|
45
|
+
TZOFFSETTO:-0700
|
46
|
+
END:DAYLIGHT
|
47
|
+
BEGIN:DAYLIGHT
|
48
|
+
DTSTART:19450814T160000
|
49
|
+
RDATE:19450814T160000
|
50
|
+
TZNAME:PPT
|
51
|
+
TZOFFSETFROM:-0700
|
52
|
+
TZOFFSETTO:-0700
|
53
|
+
END:DAYLIGHT
|
54
|
+
BEGIN:STANDARD
|
55
|
+
DTSTART:19450930T020000
|
56
|
+
RDATE:19450930T020000
|
57
|
+
RDATE:19490101T020000
|
58
|
+
TZNAME:PST
|
59
|
+
TZOFFSETFROM:-0700
|
60
|
+
TZOFFSETTO:-0800
|
61
|
+
END:STANDARD
|
62
|
+
BEGIN:STANDARD
|
63
|
+
DTSTART:19460101T000000
|
64
|
+
RDATE:19460101T000000
|
65
|
+
RDATE:19670101T000000
|
66
|
+
TZNAME:PST
|
67
|
+
TZOFFSETFROM:-0800
|
68
|
+
TZOFFSETTO:-0800
|
69
|
+
END:STANDARD
|
70
|
+
BEGIN:DAYLIGHT
|
71
|
+
DTSTART:19480314T020100
|
72
|
+
RDATE:19480314T020100
|
73
|
+
RDATE:19740106T020000
|
74
|
+
RDATE:19750223T020000
|
75
|
+
TZNAME:PDT
|
76
|
+
TZOFFSETFROM:-0800
|
77
|
+
TZOFFSETTO:-0700
|
78
|
+
END:DAYLIGHT
|
79
|
+
BEGIN:DAYLIGHT
|
80
|
+
DTSTART:19500430T010000
|
81
|
+
RRULE:FREQ=YEARLY;UNTIL=19660424T090000Z;BYMONTH=4;BYDAY=-1SU
|
82
|
+
TZNAME:PDT
|
83
|
+
TZOFFSETFROM:-0800
|
84
|
+
TZOFFSETTO:-0700
|
85
|
+
END:DAYLIGHT
|
86
|
+
BEGIN:STANDARD
|
87
|
+
DTSTART:19500924T020000
|
88
|
+
RRULE:FREQ=YEARLY;UNTIL=19610924T090000Z;BYMONTH=9;BYDAY=-1SU
|
89
|
+
TZNAME:PST
|
90
|
+
TZOFFSETFROM:-0700
|
91
|
+
TZOFFSETTO:-0800
|
92
|
+
END:STANDARD
|
93
|
+
BEGIN:STANDARD
|
94
|
+
DTSTART:19621028T020000
|
95
|
+
RRULE:FREQ=YEARLY;UNTIL=19661030T090000Z;BYMONTH=10;BYDAY=-1SU
|
96
|
+
TZNAME:PST
|
97
|
+
TZOFFSETFROM:-0700
|
98
|
+
TZOFFSETTO:-0800
|
99
|
+
END:STANDARD
|
100
|
+
BEGIN:DAYLIGHT
|
101
|
+
DTSTART:19670430T020000
|
102
|
+
RRULE:FREQ=YEARLY;UNTIL=19730429T100000Z;BYMONTH=4;BYDAY=-1SU
|
103
|
+
TZNAME:PDT
|
104
|
+
TZOFFSETFROM:-0800
|
105
|
+
TZOFFSETTO:-0700
|
106
|
+
END:DAYLIGHT
|
107
|
+
BEGIN:STANDARD
|
108
|
+
DTSTART:19671029T020000
|
109
|
+
RRULE:FREQ=YEARLY;UNTIL=20061029T090000Z;BYMONTH=10;BYDAY=-1SU
|
110
|
+
TZNAME:PST
|
111
|
+
TZOFFSETFROM:-0700
|
112
|
+
TZOFFSETTO:-0800
|
113
|
+
END:STANDARD
|
114
|
+
BEGIN:DAYLIGHT
|
115
|
+
DTSTART:19760425T020000
|
116
|
+
RRULE:FREQ=YEARLY;UNTIL=19860427T100000Z;BYMONTH=4;BYDAY=-1SU
|
117
|
+
TZNAME:PDT
|
118
|
+
TZOFFSETFROM:-0800
|
119
|
+
TZOFFSETTO:-0700
|
120
|
+
END:DAYLIGHT
|
121
|
+
BEGIN:DAYLIGHT
|
122
|
+
DTSTART:19870405T020000
|
123
|
+
RRULE:FREQ=YEARLY;UNTIL=20060402T100000Z;BYMONTH=4;BYDAY=1SU
|
124
|
+
TZNAME:PDT
|
125
|
+
TZOFFSETFROM:-0800
|
126
|
+
TZOFFSETTO:-0700
|
127
|
+
END:DAYLIGHT
|
128
|
+
BEGIN:DAYLIGHT
|
129
|
+
DTSTART:20070311T020000
|
130
|
+
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
|
131
|
+
TZNAME:PDT
|
132
|
+
TZOFFSETFROM:-0800
|
133
|
+
TZOFFSETTO:-0700
|
134
|
+
END:DAYLIGHT
|
135
|
+
BEGIN:STANDARD
|
136
|
+
DTSTART:20071104T020000
|
137
|
+
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
|
138
|
+
TZNAME:PST
|
139
|
+
TZOFFSETFROM:-0700
|
140
|
+
TZOFFSETTO:-0800
|
141
|
+
END:STANDARD
|
142
|
+
END:VTIMEZONE
|
143
|
+
END:VCALENDAR
|
data/spec/parser_spec.rb
CHANGED
@@ -5,6 +5,15 @@ describe Icalendar::Parser do
|
|
5
5
|
let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', fn) }
|
6
6
|
|
7
7
|
describe '#parse' do
|
8
|
+
context 'reversed_timezone.ics' do
|
9
|
+
let(:fn) { 'reversed_timezone.ics' }
|
10
|
+
|
11
|
+
it 'correctly parses the event timezone' do
|
12
|
+
event = subject.parse.first.events.first
|
13
|
+
expect(event.dtstart.utc_offset).to eq -25200
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
8
17
|
context 'single_event.ics' do
|
9
18
|
let(:fn) { 'single_event.ics' }
|
10
19
|
|
data/spec/roundtrip_spec.rb
CHANGED
@@ -14,7 +14,7 @@ describe Icalendar do
|
|
14
14
|
event = Icalendar::Event.new
|
15
15
|
parsed = Icalendar::Calendar.parse(source).first
|
16
16
|
event.rdate = parsed.events.first.rdate
|
17
|
-
expect(event.rdate.first).to be_kind_of Icalendar::Values::Array
|
17
|
+
expect(event.rdate.first).to be_kind_of Icalendar::Values::Helpers::Array
|
18
18
|
expect(event.rdate.first.params_ical).to eq ";TZID=US-Mountain"
|
19
19
|
end
|
20
20
|
end
|
@@ -27,6 +27,22 @@ describe Icalendar::Values::DateTime do
|
|
27
27
|
expect(subject.value.utc.hour).to eq 23
|
28
28
|
end
|
29
29
|
end
|
30
|
+
|
31
|
+
context 'nonstandard format tzid local time' do
|
32
|
+
let(:value) { '20230901T230404' }
|
33
|
+
let(:params) { {'tzid' => 'Singapore Standard Time'} }
|
34
|
+
|
35
|
+
it 'parses the value as local time' do
|
36
|
+
expect(subject.value.hour).to eq 23
|
37
|
+
expect(subject.value.utc_offset).to eq 28800
|
38
|
+
expect(subject.value.utc.hour).to eq 15
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'updates the tzid' do
|
42
|
+
# use an array because that's how output from the parser will end up
|
43
|
+
expect(subject.ical_params['tzid']).to eq ['Asia/Singapore']
|
44
|
+
end
|
45
|
+
end
|
30
46
|
end
|
31
47
|
|
32
48
|
else
|
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.10.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Ahearn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ice_cube
|
@@ -58,42 +58,28 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '7.1'
|
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: '
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: i18n
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '1.8'
|
76
|
-
type: :development
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '1.8'
|
68
|
+
version: '7.1'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: tzinfo
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
86
72
|
requirements:
|
87
73
|
- - "~>"
|
88
74
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
75
|
+
version: '2.0'
|
90
76
|
type: :development
|
91
77
|
prerelease: false
|
92
78
|
version_requirements: !ruby/object:Gem::Requirement
|
93
79
|
requirements:
|
94
80
|
- - "~>"
|
95
81
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
82
|
+
version: '2.0'
|
97
83
|
- !ruby/object:Gem::Dependency
|
98
84
|
name: tzinfo-data
|
99
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,6 +136,20 @@ dependencies:
|
|
150
136
|
- - "~>"
|
151
137
|
- !ruby/object:Gem::Version
|
152
138
|
version: '0.16'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: byebug
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
153
|
description: |
|
154
154
|
Implements the iCalendar specification (RFC-5545) in Ruby. This allows
|
155
155
|
for the generation and parsing of .ics files, which are used by a
|
@@ -187,8 +187,6 @@ files:
|
|
187
187
|
- lib/icalendar/todo.rb
|
188
188
|
- lib/icalendar/tzinfo.rb
|
189
189
|
- lib/icalendar/value.rb
|
190
|
-
- lib/icalendar/values/active_support_time_with_zone_adapter.rb
|
191
|
-
- lib/icalendar/values/array.rb
|
192
190
|
- lib/icalendar/values/binary.rb
|
193
191
|
- lib/icalendar/values/boolean.rb
|
194
192
|
- lib/icalendar/values/cal_address.rb
|
@@ -197,12 +195,14 @@ files:
|
|
197
195
|
- lib/icalendar/values/date_time.rb
|
198
196
|
- lib/icalendar/values/duration.rb
|
199
197
|
- lib/icalendar/values/float.rb
|
198
|
+
- lib/icalendar/values/helpers/active_support_time_with_zone_adapter.rb
|
199
|
+
- lib/icalendar/values/helpers/array.rb
|
200
|
+
- lib/icalendar/values/helpers/time_with_zone.rb
|
200
201
|
- lib/icalendar/values/integer.rb
|
201
202
|
- lib/icalendar/values/period.rb
|
202
203
|
- lib/icalendar/values/recur.rb
|
203
204
|
- lib/icalendar/values/text.rb
|
204
205
|
- lib/icalendar/values/time.rb
|
205
|
-
- lib/icalendar/values/time_with_zone.rb
|
206
206
|
- lib/icalendar/values/uri.rb
|
207
207
|
- lib/icalendar/values/utc_offset.rb
|
208
208
|
- lib/icalendar/version.rb
|
@@ -216,6 +216,7 @@ files:
|
|
216
216
|
- spec/fixtures/nondefault_values.ics
|
217
217
|
- spec/fixtures/nonstandard.ics
|
218
218
|
- spec/fixtures/recurrence.ics
|
219
|
+
- spec/fixtures/reversed_timezone.ics
|
219
220
|
- spec/fixtures/single_event.ics
|
220
221
|
- spec/fixtures/single_event_bad_dtstart.ics
|
221
222
|
- spec/fixtures/single_event_bad_line.ics
|
@@ -247,7 +248,8 @@ licenses:
|
|
247
248
|
- BSD-2-Clause
|
248
249
|
- GPL-3.0-only
|
249
250
|
- icalendar
|
250
|
-
metadata:
|
251
|
+
metadata:
|
252
|
+
changelog_uri: https://github.com/icalendar/icalendar/blob/main/History.txt
|
251
253
|
post_install_message: 'ActiveSupport is required for TimeWithZone support, but not
|
252
254
|
required for general use.
|
253
255
|
|
@@ -266,7 +268,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
266
268
|
- !ruby/object:Gem::Version
|
267
269
|
version: '0'
|
268
270
|
requirements: []
|
269
|
-
rubygems_version: 3.3.
|
271
|
+
rubygems_version: 3.3.26
|
270
272
|
signing_key:
|
271
273
|
specification_version: 4
|
272
274
|
summary: A ruby implementation of the iCalendar specification (RFC-5545).
|
@@ -281,6 +283,7 @@ test_files:
|
|
281
283
|
- spec/fixtures/nondefault_values.ics
|
282
284
|
- spec/fixtures/nonstandard.ics
|
283
285
|
- spec/fixtures/recurrence.ics
|
286
|
+
- spec/fixtures/reversed_timezone.ics
|
284
287
|
- spec/fixtures/single_event.ics
|
285
288
|
- spec/fixtures/single_event_bad_dtstart.ics
|
286
289
|
- spec/fixtures/single_event_bad_line.ics
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Icalendar
|
4
|
-
module Values
|
5
|
-
class ActiveSupportTimeWithZoneAdapter < ActiveSupport::TimeWithZone
|
6
|
-
# ActiveSupport::TimeWithZone implements a #to_a method that will cause
|
7
|
-
# unexpected behavior in components with multi_property DateTime
|
8
|
-
# properties when the setters for those properties are invoked with an
|
9
|
-
# Icalendar::Values::DateTime that is delegating for an
|
10
|
-
# ActiveSupport::TimeWithZone. To avoid this behavior, undefine #to_a.
|
11
|
-
undef_method :to_a
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,60 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Icalendar
|
4
|
-
module Values
|
5
|
-
|
6
|
-
class Array < Value
|
7
|
-
|
8
|
-
attr_reader :value_delimiter
|
9
|
-
|
10
|
-
def initialize(value, klass, params = {}, options = {})
|
11
|
-
@value_delimiter = options[:delimiter] || ','
|
12
|
-
mapped = if value.is_a? ::Array
|
13
|
-
value.map do |v|
|
14
|
-
if v.is_a? Icalendar::Values::Array
|
15
|
-
Icalendar::Values::Array.new v.value, klass, v.ical_params, delimiter: v.value_delimiter
|
16
|
-
elsif v.is_a? ::Array
|
17
|
-
Icalendar::Values::Array.new v, klass, params, delimiter: value_delimiter
|
18
|
-
elsif v.is_a? Icalendar::Value
|
19
|
-
v
|
20
|
-
else
|
21
|
-
klass.new v, params
|
22
|
-
end
|
23
|
-
end
|
24
|
-
else
|
25
|
-
[klass.new(value, params)]
|
26
|
-
end
|
27
|
-
super mapped
|
28
|
-
end
|
29
|
-
|
30
|
-
def params_ical
|
31
|
-
value.each do |v|
|
32
|
-
ical_params.merge! v.ical_params
|
33
|
-
end
|
34
|
-
super
|
35
|
-
end
|
36
|
-
|
37
|
-
def value_ical
|
38
|
-
value.map do |v|
|
39
|
-
v.value_ical
|
40
|
-
end.join value_delimiter
|
41
|
-
end
|
42
|
-
|
43
|
-
def valid?
|
44
|
-
klass = value.first.class
|
45
|
-
!value.all? { |v| v.class == klass }
|
46
|
-
end
|
47
|
-
|
48
|
-
def value_type
|
49
|
-
value.first.value_type
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def needs_value_type?(default_type)
|
55
|
-
value.first.class != default_type
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'icalendar/timezone_store'
|
4
|
-
|
5
|
-
begin
|
6
|
-
require 'active_support/time'
|
7
|
-
|
8
|
-
if defined?(ActiveSupport::TimeWithZone)
|
9
|
-
require 'icalendar/values/active_support_time_with_zone_adapter'
|
10
|
-
end
|
11
|
-
rescue NameError
|
12
|
-
# ActiveSupport v7+ needs the base require to be run first before loading
|
13
|
-
# specific parts of it.
|
14
|
-
# https://guides.rubyonrails.org/active_support_core_extensions.html#stand-alone-active-support
|
15
|
-
require 'active_support'
|
16
|
-
retry
|
17
|
-
rescue LoadError
|
18
|
-
# tis ok, just a bit less fancy
|
19
|
-
end
|
20
|
-
|
21
|
-
module Icalendar
|
22
|
-
module Values
|
23
|
-
module TimeWithZone
|
24
|
-
attr_reader :tz_utc
|
25
|
-
|
26
|
-
def initialize(value, params = {})
|
27
|
-
params = Icalendar::DowncasedHash(params)
|
28
|
-
@tz_utc = params['tzid'] == 'UTC'
|
29
|
-
x_tz_info = params.delete 'x-tz-info'
|
30
|
-
|
31
|
-
offset_value = unless params['tzid'].nil?
|
32
|
-
tzid = params['tzid'].is_a?(::Array) ? params['tzid'].first : params['tzid']
|
33
|
-
if defined?(ActiveSupport::TimeZone) &&
|
34
|
-
defined?(ActiveSupportTimeWithZoneAdapter) &&
|
35
|
-
(tz = ActiveSupport::TimeZone[tzid])
|
36
|
-
ActiveSupportTimeWithZoneAdapter.new(nil, tz, value)
|
37
|
-
elsif !x_tz_info.nil?
|
38
|
-
offset = x_tz_info.offset_for_local(value).to_s
|
39
|
-
if value.respond_to?(:change)
|
40
|
-
value.change offset: offset
|
41
|
-
else
|
42
|
-
::Time.new value.year, value.month, value.day, value.hour, value.min, value.sec, offset
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
super((offset_value || value), params)
|
47
|
-
end
|
48
|
-
|
49
|
-
def params_ical
|
50
|
-
ical_params.delete 'tzid' if tz_utc
|
51
|
-
super
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|