icalendar-recurrence 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9a2ae1e548eb6c46fa2693f5951f3528e99b8abb
4
- data.tar.gz: 0aaecc9ba2d1c7aa887f820d9dfe94348c472f28
2
+ SHA256:
3
+ metadata.gz: 0ae33082e4e2cfe166c0dfa58e2ff01b2833fd40dafa6272d4add3cc48d76381
4
+ data.tar.gz: e2052acd29e364ddad718774c56d17b43cb7e75e27fd09e73f26a16e0da50e8d
5
5
  SHA512:
6
- metadata.gz: f28af3cd81e7afde47cee2c16032ad1c6eee436064081fb3280355007ab0e063c60dceb2a7383e4c11df26ca347b6487bde78670ac1c34f508fef7a538178fdd
7
- data.tar.gz: ff86db8785368cd0d38f84f06dc5c371298eeb6b642ce8331d3b5bd43645151eaba17475d1b25e92aedb16b8604842c10689ba571e0f40bed7a770ea28c74ccf
6
+ metadata.gz: 82818e3411bff13fdb16786d99b875270ad3066ed993ac5cdd31f270c2bb743392dc828c0ec436f5fb0cdb17c1613717498743ed7984e17d4d7a6958c7ec7e4d
7
+ data.tar.gz: 10be0f14a28212d383643bf9b2ddd65e851079032426dfc5f7ae3f4c2081ec0e08211d1be58d5b57b3e604d7fcc7d98528792cf8b9321ea8f58c8c321ceb45c1
@@ -0,0 +1,32 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+ pull_request:
9
+ branches:
10
+ - main
11
+ - master
12
+
13
+ jobs:
14
+ build:
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ ruby:
20
+ - 3.0.6
21
+ - 3.1.4
22
+ - 3.2.2
23
+
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - name: Set up Ruby
27
+ uses: ruby/setup-ruby@v1
28
+ with:
29
+ ruby-version: ${{ matrix.ruby }}
30
+ bundler-cache: true
31
+ - name: Run rspec tests
32
+ run: bundle exec rake spec
data/.gitignore CHANGED
@@ -1,5 +1,7 @@
1
1
  *.gem
2
2
  *.rbc
3
+ .ruby-version
4
+ .DS_Store
3
5
  .bundle
4
6
  .config
5
7
  .yardoc
@@ -14,4 +16,4 @@ rdoc
14
16
  spec/reports
15
17
  test/tmp
16
18
  test/version_tmp
17
- tmp
19
+ tmp
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # icalendar-recurrence CHANGELOG
2
2
 
3
+ ## 1.2.0 (November 1, 2023)
4
+
5
+ - Fix exception date handling across DST boundaries (PR #25)
6
+ - Add `:parent` to the Occurrence class, to match constructor for Icalendar::Component (PR #23)
7
+ - Add `Event#all_occurrences` method (PR #28)
8
+ - Move TZInfo to a runtime dependency (PR #31)
9
+ - Handle `RDATE` recurrence rules (PR #33)
10
+ - Use built-in `.utc` method for occurrence time in all cases (PR #35 & #37)
11
+
3
12
  ## 1.1.2 (September 23, 2016)
4
13
 
5
14
  - Loosen dependency on ice_cube gem to allow minor upgrades (issue #16)
data/README.md CHANGED
@@ -20,11 +20,15 @@ and run `bundle install` from your shell.
20
20
  require 'date' # for parse method
21
21
  require 'icalendar/recurrence'
22
22
 
23
- calendars = Icalendar.parse(File.read(path_to_ics)) # parse an ICS file
23
+ calendars = Icalendar::Calendar.parse(File.read(path_to_ics)) # parse an ICS file
24
24
  event = Array(calendars).first.events.first # retrieve the first event
25
25
  event.occurrences_between(Date.parse("2014-01-01"), Date.parse("2014-02-01")) # get all occurrence for one month
26
26
  ```
27
27
 
28
+ ### Get all occurrences
29
+
30
+ To get all occurrences you can use `all_occurrences`. This only works when you have specified an ending using `until` or `count` in your RRULE.
31
+
28
32
  ### Working with occurrences
29
33
 
30
34
  An event occurrence is a simple struct object with `start_time` and `end_time` methods.
@@ -67,7 +71,7 @@ EOF
67
71
 
68
72
  # An event that occurs every day, starting January 1, 2014 with one excluded
69
73
  # date. January 28, 2014 will not appear in the occurrences.
70
- calendars = Icalendar.parse(ics_string)
74
+ calendars = Icalendar::Calendar.parse(ics_string)
71
75
  every_day_except_jan_28 = Array(calendars).first.events.first
72
76
  puts "Every day except January 28, 2014, occurrences from 2014-01-01 to 2014-02-01:"
73
77
  puts every_day_except_jan_28.occurrences_between(Date.parse("2014-01-01"), Date.parse("2014-02-01"))
@@ -18,14 +18,14 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ["lib"]
19
19
 
20
20
  spec.add_runtime_dependency 'icalendar', '~> 2.0'
21
- spec.add_runtime_dependency 'ice_cube', '~> 0.13'
21
+ spec.add_runtime_dependency 'ice_cube', '~> 0.16'
22
+ spec.add_runtime_dependency 'tzinfo', '~> 2.0'
22
23
 
23
- spec.add_development_dependency 'activesupport', '~> 4.0'
24
+ spec.add_development_dependency 'activesupport', '~> 7.1'
24
25
  spec.add_development_dependency 'awesome_print'
25
26
  spec.add_development_dependency 'pry'
26
- spec.add_development_dependency 'bundler', '~> 1.3'
27
- spec.add_development_dependency 'rake', '~> 10.2'
28
- spec.add_development_dependency 'rspec', '~> 2.14'
29
- spec.add_development_dependency 'timecop', '~> 0.6.3'
30
- spec.add_development_dependency 'tzinfo', '~> 0.3'
27
+ spec.add_development_dependency 'bundler', '~> 2.0'
28
+ spec.add_development_dependency 'rake', '>= 12.3.3'
29
+ spec.add_development_dependency 'rspec', '~> 3.12'
30
+ spec.add_development_dependency 'timecop', '~> 0.9'
31
31
  end
@@ -13,8 +13,12 @@ module Icalendar
13
13
  dtend
14
14
  end
15
15
 
16
- def occurrences_between(begin_time, closing_time)
17
- schedule.occurrences_between(begin_time, closing_time)
16
+ def occurrences_between(begin_time, closing_time, spans: false)
17
+ schedule.occurrences_between(begin_time, closing_time, spans: spans)
18
+ end
19
+
20
+ def all_occurrences
21
+ schedule.all_occurrences
18
22
  end
19
23
 
20
24
  def schedule
@@ -33,4 +37,4 @@ module Icalendar
33
37
  class Event
34
38
  include Icalendar::Recurrence::EventExtensions
35
39
  end
36
- end
40
+ end
@@ -2,7 +2,7 @@ require 'ice_cube'
2
2
 
3
3
  module Icalendar
4
4
  module Recurrence
5
- class Occurrence < Struct.new(:start_time, :end_time)
5
+ class Occurrence < Struct.new(:start_time, :end_time, :parent)
6
6
  end
7
7
 
8
8
  class Schedule
@@ -32,8 +32,8 @@ module Icalendar
32
32
  end
33
33
  end
34
34
 
35
- def occurrences_between(begin_time, closing_time)
36
- ice_cube_occurrences = ice_cube_schedule.occurrences_between(TimeUtil.to_time(begin_time), TimeUtil.to_time(closing_time))
35
+ def occurrences_between(begin_time, closing_time, spans: false)
36
+ ice_cube_occurrences = ice_cube_schedule.occurrences_between(TimeUtil.to_time(begin_time), TimeUtil.to_time(closing_time), spans: spans)
37
37
 
38
38
  ice_cube_occurrences.map do |occurrence|
39
39
  convert_ice_cube_occurrence(occurrence)
@@ -49,20 +49,10 @@ module Icalendar
49
49
  end
50
50
 
51
51
  def convert_ice_cube_occurrence(ice_cube_occurrence)
52
- if timezone
53
- begin
54
- tz = TZInfo::Timezone.get(timezone)
55
- start_time = tz.local_to_utc(ice_cube_occurrence.start_time)
56
- end_time = tz.local_to_utc(ice_cube_occurrence.end_time)
57
- rescue TZInfo::InvalidTimezoneIdentifier => e
58
- warn "Unknown TZID specified in ical event (#{timezone.inspect}), ignoring (will likely cause event to be at wrong time!)"
59
- end
60
- end
52
+ start_time = ice_cube_occurrence.start_time.utc
53
+ end_time = ice_cube_occurrence.end_time.utc
61
54
 
62
- start_time ||= ice_cube_occurrence.start_time
63
- end_time ||= ice_cube_occurrence.end_time
64
-
65
- Icalendar::Recurrence::Occurrence.new(start_time, end_time)
55
+ Icalendar::Recurrence::Occurrence.new(start_time, end_time, @event)
66
56
  end
67
57
 
68
58
  def ice_cube_schedule
@@ -75,14 +65,25 @@ module Icalendar
75
65
  schedule.add_recurrence_rule(ice_cube_recurrence_rule)
76
66
  end
77
67
 
78
- event.exdate.each do |exception_date|
79
- exception_date = Time.parse(exception_date) if exception_date.is_a?(String)
80
- schedule.add_exception_time(TimeUtil.to_time(exception_date))
68
+ event.exdate.each do |exception_date_or_dates|
69
+ Array(exception_date_or_dates).each do |exception_date|
70
+ # exception times should have the same tz offset as the event start or they'll be ignored as different times
71
+ # ignored if ActiveSupport::TimeWithZone is available
72
+ schedule.add_exception_time(TimeUtil.to_time(exception_date, moment: start_time))
73
+ end
74
+ end
75
+
76
+ event.rdate.each do |extra_date_or_dates|
77
+ Array(extra_date_or_dates).each do |extra_date|
78
+ # exception times should have the same tz offset as the event start or they'll be ignored as different times
79
+ # ignored if ActiveSupport::TimeWithZone is available
80
+ schedule.add_recurrence_time(TimeUtil.to_time(extra_date, moment: start_time))
81
+ end
81
82
  end
82
83
 
83
84
  schedule
84
85
  end
85
-
86
+
86
87
  def convert_duration_to_seconds(ical_duration)
87
88
  return 0 unless ical_duration
88
89
 
@@ -1,11 +1,17 @@
1
1
  require 'tzinfo'
2
+ begin
3
+ require 'active_support/time'
4
+ rescue LoadError
5
+ # that's ok, will just fall back to Time
6
+ end
2
7
 
3
8
  module Icalendar
4
9
  module Recurrence
5
10
  module TimeUtil
6
- def datetime_to_time(datetime)
11
+ def datetime_to_time(datetime, options = {})
7
12
  raise ArgumentError, "Unsupported DateTime object passed (must be Icalendar::Values::DateTime#{datetime.class} passed instead)" unless supported_datetime_object?(datetime)
8
- offset = timezone_offset(datetime.ical_params["tzid"], moment: datetime.to_date)
13
+ options[:moment] ||= datetime.to_date
14
+ offset = timezone_offset(datetime.ical_params["tzid"], options)
9
15
  offset ||= datetime.strftime("%:z")
10
16
 
11
17
  Time.new(datetime.year, datetime.month, datetime.mday, datetime.hour, datetime.min, datetime.sec, offset)
@@ -16,11 +22,13 @@ module Icalendar
16
22
  Time.new(date.year, date.month, date.mday)
17
23
  end
18
24
 
19
- def to_time(time_object)
25
+ def to_time(time_object, options = {})
20
26
  if supported_time_object?(time_object)
21
27
  time_object
28
+ elsif supported_icalendar_object?(time_object)
29
+ time_object.value
22
30
  elsif supported_datetime_object?(time_object)
23
- datetime_to_time(time_object)
31
+ datetime_to_time(time_object, options)
24
32
  elsif supported_date_object?(time_object)
25
33
  date_to_time(time_object)
26
34
  elsif time_object.is_a?(String)
@@ -41,8 +49,7 @@ module Icalendar
41
49
  #
42
50
  def timezone_offset(tzid, options = {})
43
51
  tzid = Array(tzid).first
44
- options = {moment: Time.now}.merge(options)
45
- moment = options.fetch(:moment)
52
+ moment = options.fetch(:moment, Time.now)
46
53
  utc_moment = to_time(moment.clone).utc
47
54
  tzid = tzid.to_s.gsub(/^(["'])|(["'])$/, "")
48
55
  utc_offset = TZInfo::Timezone.get(tzid).period_for_utc(utc_moment).utc_total_offset # this seems to work, but I feel like there is a lurking bug
@@ -50,7 +57,7 @@ module Icalendar
50
57
  hour_offset = "+#{hour_offset}" if hour_offset >= 0
51
58
  match = hour_offset.to_s.match(/(\+|-)(\d+)/)
52
59
  "#{match[1]}#{match[2].rjust(2, "0")}:00"
53
- rescue TZInfo::InvalidTimezoneIdentifier => e
60
+ rescue TZInfo::InvalidTimezoneIdentifier
54
61
  nil
55
62
  end
56
63
 
@@ -67,8 +74,16 @@ module Icalendar
67
74
  time_object.is_a?(Icalendar::Values::DateTime)
68
75
  end
69
76
 
77
+ def supported_icalendar_object?(time_object)
78
+ time_object.is_a?(Icalendar::Values::DateTime) && supported_activesupport_object?(time_object.value)
79
+ end
80
+
70
81
  def supported_time_object?(time_object)
71
- time_object.is_a?(Time)
82
+ time_object.is_a?(Time) || supported_activesupport_object?(time_object)
83
+ end
84
+
85
+ def supported_activesupport_object?(time_object)
86
+ defined?(ActiveSupport::TimeWithZone) && time_object.is_a?(ActiveSupport::TimeWithZone)
72
87
  end
73
88
 
74
89
  # Replaces the existing offset with one associated with given TZID. Does
@@ -1,5 +1,5 @@
1
1
  module Icalendar
2
2
  module Recurrence
3
- VERSION = "1.1.2"
3
+ VERSION = "1.2.0"
4
4
  end
5
5
  end
@@ -52,9 +52,31 @@ describe "Event#occurrences_between" do
52
52
  end
53
53
  end
54
54
 
55
+ context "event repeating every thursday with multiple exceptions" do
56
+ context "exclusions are on a single line" do
57
+ let(:event) { example_event :multiple_exception } # has exclusions in array form
58
+ it "properly calculates recurrence, including exclusion dates" do
59
+ occurrences = event.occurrences_between(start_time, start_time + 1.months)
60
+
61
+ expect(occurrences.length).to eq(1)
62
+ expect(occurrences.first.start_time).to eq(Time.parse("2017-11-16 13:40:00 UTC"))
63
+ end
64
+ end
65
+
66
+ context "exclusions are on multiple lines" do
67
+ let(:event) { example_event :multiple_exception_multiple_line } # has multiple single exclusions
68
+ it "properly calculates recurrence, including exclusion dates" do
69
+ occurrences = event.occurrences_between(start_time, start_time + 1.months)
70
+
71
+ expect(occurrences.length).to eq(1)
72
+ expect(occurrences.first.start_time).to eq(Time.parse("2017-11-16 13:40:00 UTC"))
73
+ end
74
+ end
75
+ end
76
+
55
77
  context "event repeating bimonthly (DST example)" do
56
78
  let(:event) { example_event :on_third_every_two_months }
57
-
79
+
58
80
  it "occurs twice over 60 days" do
59
81
  occurrences = event.occurrences_between(start_time, start_time + 60.days)
60
82
 
@@ -64,6 +86,36 @@ describe "Event#occurrences_between" do
64
86
  end
65
87
  end
66
88
 
89
+ context "event repeating weekly with exdates" do
90
+ let(:event) { example_event :dst_exdate }
91
+
92
+ it "properly skips christmas and new years" do
93
+ occurrences = event.occurrences_between(start_time, start_time + 4.months)
94
+
95
+ expect(occurrences.length).to eq(14)
96
+ christmas_day = Date.new 2019, 12, 25
97
+ expect(occurrences.all? { |o| o.start_time.to_date != christmas_day }).to eq(true)
98
+ new_years_day = Date.new 2020, 1, 1
99
+ expect(occurrences.all? { |o| o.start_time.to_date != new_years_day }).to eq(true)
100
+ end
101
+ end
102
+
103
+ context "event repeating weekly with rdates" do
104
+ let(:event) { example_event :dst_rdate }
105
+
106
+ it "properly includes specified rdates" do
107
+ occurrences = event.occurrences_between(start_time, start_time + 4.months)
108
+ extra_day_after_period = Date.new 2020, 02, 01
109
+ occurrence = occurrences.find { |o| o.start_time.to_date == extra_day_after_period }
110
+
111
+ expect(occurrences.length).to eq(17)
112
+ expect(occurrence).to_not be_nil
113
+
114
+ expect(occurrence.start_time).to eq(Time.new 2020, 02, 01, 19, 0, 0, "+00:00")
115
+ expect(occurrence.end_time).to eq(Time.new 2020, 02, 01, 21, 0, 0, "+00:00")
116
+ end
117
+ end
118
+
67
119
  context "event repeating yearly" do
68
120
  let(:event) { example_event :first_of_every_year }
69
121
 
@@ -81,7 +133,7 @@ describe "Event#occurrences_between" do
81
133
 
82
134
  it "occurrs 10 times over two weeks" do
83
135
  occurrences = event.occurrences_between(start_time, start_time + 13.days)
84
-
136
+
85
137
  expect(occurrences.length).to eq(10)
86
138
  expect(occurrences.map(&:start_time)).to include(Time.parse("2014-01-10"))
87
139
  expect(occurrences.map(&:start_time)).to_not include(Time.parse("2014-01-11"))
@@ -118,13 +170,13 @@ describe "Event#occurrences_between" do
118
170
 
119
171
  context "event repeating on first saturday of month event" do
120
172
  let(:event) { example_event :first_saturday_of_month }
121
-
173
+
122
174
  it "occurs twice over two months" do
123
175
  occurrences = event.occurrences_between(start_time, start_time + 55.days)
124
176
 
125
177
  expected_start_times = [
126
- Time.parse("2014-01-04 at 12am").force_zone("America/Los_Angeles"),
127
- Time.parse("2014-02-01 at 12am").force_zone("America/Los_Angeles"),
178
+ Time.parse("2014-01-04 at 10am").force_zone("America/Los_Angeles"),
179
+ Time.parse("2014-02-01 at 10am").force_zone("America/Los_Angeles"),
128
180
  ]
129
181
 
130
182
  expect(occurrences.length).to eq(2)
@@ -151,4 +203,29 @@ describe "Event#occurrences_between" do
151
203
  expect(occurrences.first.start_time).to eq(Time.parse("20140114T180000Z"))
152
204
  end
153
205
  end
206
+ end
207
+
208
+ describe "Event#all_occurrences" do
209
+ let(:start_time) { event.start_time }
210
+
211
+ context "event repeating once a month for three months" do
212
+ let(:event) { example_event :one_day_a_month_for_three_months }
213
+
214
+ it "get all 3 occurences" do
215
+ occurrences = event.all_occurrences
216
+
217
+ expect(occurrences.length).to eq(3)
218
+ end
219
+ end
220
+
221
+ context "event repeating yearly" do
222
+ let(:event) { example_event :first_of_every_year }
223
+
224
+ it "can not get all occurences when not using count or until" do
225
+ expect {
226
+ event.all_occurrences
227
+ }.to raise_error(ArgumentError)
228
+ end
229
+ end
230
+
154
231
  end
@@ -13,6 +13,20 @@ describe Icalendar::Recurrence::Schedule do
13
13
  expect(example_occurrence).to respond_to :end_time
14
14
  end
15
15
 
16
+ it "returns occurrences within range, including duration spanning #start_time " do
17
+ schedule = Schedule.new(example_event(:week_long))
18
+ occurrences = schedule.occurrences_between(Time.parse("2014-01-13T09:00:00-08:00"), Date.parse("2014-01-20"), spans: true)
19
+
20
+ expect(schedule.start_time).to eq(Time.parse("2014-01-13T08:00:00-08:00"))
21
+ expect(occurrences.count).to eq(7)
22
+ end
23
+
24
+ it 'returns object whose event method matches the origin event' do
25
+ # Simple test to make sure the event carried over; different __id__
26
+ expect(example_occurrence.parent.custom_properties).to eq example_event(:daily).custom_properties
27
+ expect(example_occurrence.parent.name).to eq example_event(:daily).name
28
+ end
29
+
16
30
  context "timezoned event" do
17
31
  let(:example_occurrence) do
18
32
  timezoned_event = example_event :first_saturday_of_month
@@ -20,7 +34,7 @@ describe Icalendar::Recurrence::Schedule do
20
34
  example_occurrence = schedule.occurrences_between(Date.parse("2014-02-01"), Date.parse("2014-03-01")).first
21
35
  end
22
36
 
23
- it "#occurrences_between return object that responds to #start_time and #end_time (timezoned example)" do
37
+ it "returns object that responds to #start_time and #end_time (timezoned example)" do
24
38
  expect(example_occurrence).to respond_to :start_time
25
39
  expect(example_occurrence).to respond_to :end_time
26
40
  end
data/spec/spec_helper.rb CHANGED
@@ -12,7 +12,6 @@ include Icalendar::Recurrence
12
12
  include Helpers
13
13
 
14
14
  RSpec.configure do |config|
15
- config.treat_symbols_as_metadata_keys_with_true_values = true
16
15
  config.run_all_when_everything_filtered = true
17
16
  config.filter_run :focus
18
17
  config.order = 'random'
@@ -0,0 +1,44 @@
1
+ BEGIN:VCALENDAR
2
+ PRODID:-//Google Inc//Google Calendar 70.9054//EN
3
+ VERSION:2.0
4
+ CALSCALE:GREGORIAN
5
+ METHOD:PUBLISH
6
+ X-WR-CALNAME:Minimal example
7
+ X-WR-TIMEZONE:Europe/Berlin
8
+ X-WR-CALDESC:Example for #221
9
+ BEGIN:VTIMEZONE
10
+ TZID:Europe/Berlin
11
+ X-LIC-LOCATION:Europe/Berlin
12
+ BEGIN:DAYLIGHT
13
+ TZOFFSETFROM:+0100
14
+ TZOFFSETTO:+0200
15
+ TZNAME:CEST
16
+ DTSTART:19700329T020000
17
+ RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
18
+ END:DAYLIGHT
19
+ BEGIN:STANDARD
20
+ TZOFFSETFROM:+0200
21
+ TZOFFSETTO:+0100
22
+ TZNAME:CET
23
+ DTSTART:19701025T030000
24
+ RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
25
+ END:STANDARD
26
+ END:VTIMEZONE
27
+ BEGIN:VEVENT
28
+ DTSTART;TZID=Europe/Berlin:20191016T100000
29
+ DTEND;TZID=Europe/Berlin:20191016T120000
30
+ RRULE:FREQ=WEEKLY;WKST=MO;UNTIL=20200129T225959Z;BYDAY=WE
31
+ EXDATE;TZID=Europe/Berlin:20200101T100000
32
+ EXDATE;TZID=Europe/Berlin:20191225T100000
33
+ DTSTAMP:20191121T083508Z
34
+ UID:01234567890123456789@google.com
35
+ CREATED:20191001T090000Z
36
+ DESCRIPTION:This repeating event should not occur on the specified EXDATEs
37
+ LAST-MODIFIED:20191001T090000Z
38
+ LOCATION:Room 1
39
+ SEQUENCE:1
40
+ STATUS:CONFIRMED
41
+ SUMMARY:A repeating event
42
+ TRANSP:OPAQUE
43
+ END:VEVENT
44
+ END:VCALENDAR
@@ -0,0 +1,43 @@
1
+ BEGIN:VCALENDAR
2
+ PRODID:-//Google Inc//Google Calendar 70.9054//EN
3
+ VERSION:2.0
4
+ CALSCALE:GREGORIAN
5
+ METHOD:PUBLISH
6
+ X-WR-CALNAME:Minimal example
7
+ X-WR-TIMEZONE:Europe/Berlin
8
+ X-WR-CALDESC:Example for #221
9
+ BEGIN:VTIMEZONE
10
+ TZID:Europe/Berlin
11
+ X-LIC-LOCATION:Europe/Berlin
12
+ BEGIN:DAYLIGHT
13
+ TZOFFSETFROM:+0100
14
+ TZOFFSETTO:+0200
15
+ TZNAME:CEST
16
+ DTSTART:19700329T020000
17
+ RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
18
+ END:DAYLIGHT
19
+ BEGIN:STANDARD
20
+ TZOFFSETFROM:+0200
21
+ TZOFFSETTO:+0100
22
+ TZNAME:CET
23
+ DTSTART:19701025T030000
24
+ RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
25
+ END:STANDARD
26
+ END:VTIMEZONE
27
+ BEGIN:VEVENT
28
+ DTSTART;TZID=Europe/Berlin:20191016T100000
29
+ DTEND;TZID=Europe/Berlin:20191016T120000
30
+ RRULE:FREQ=WEEKLY;WKST=MO;UNTIL=20200129T225959Z;BYDAY=WE
31
+ RDATE;TZID=Europe/Berlin:20200201T200000
32
+ DTSTAMP:20191121T083508Z
33
+ UID:01234567890123456789@google.com
34
+ CREATED:20191001T090000Z
35
+ DESCRIPTION:This repeating event should additionally occur on the specified RDATESs
36
+ LAST-MODIFIED:20191001T090000Z
37
+ LOCATION:Room 1
38
+ SEQUENCE:1
39
+ STATUS:CONFIRMED
40
+ SUMMARY:A repeating event
41
+ TRANSP:OPAQUE
42
+ END:VEVENT
43
+ END:VCALENDAR
@@ -29,8 +29,8 @@ DESCRIPTION:\nScheduled maintenance \n
29
29
  X-MS-OLK-SENDER:mailto:foo@example.ca
30
30
  ORGANIZER;SENT-BY="mailto:foo@example.ca":mailto:itadmin@example.ca
31
31
  X-MS-OLK-SENDER:mailto:foo@example.ca
32
- DTSTART;TZID="America/Los_Angeles":20140104T000000
33
- DTEND;TZID="America/Los_Angeles":20140104T040000
32
+ DTSTART;TZID="America/Los_Angeles":20140104T100000
33
+ DTEND;TZID="America/Los_Angeles":20140104T110000
34
34
  STATUS:CONFIRMED
35
35
  CLASS:PUBLIC
36
36
  X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
@@ -0,0 +1,20 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ CALSCALE:GREGORIAN
4
+ PRODID:-//Apple Inc.//Mac OS X 10.12.6//EN
5
+ BEGIN:VEVENT
6
+ UID:AA000000-AAAA-CCCC-DDDD-CC7176000000
7
+ DTSTART;TZID=Asia/Dubai:20171019T174000
8
+ DTEND;TZID=Asia/Dubai:20171019T181000
9
+ CREATED:20170830T130728Z
10
+ DTSTAMP:20171016T185512Z
11
+ EXDATE;TZID=Asia/Dubai:20171102T174000,20171026T174000,20171019T174000,20
12
+ 171109T174000
13
+ LAST-MODIFIED:20171016T185511Z
14
+ RRULE:FREQ=WEEKLY;UNTIL=20171215T195959Z;BYDAY=TH
15
+ SEQUENCE:0
16
+ SUMMARY:Exception with timezone
17
+ TRANSP:OPAQUE
18
+ X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC
19
+ END:VEVENT
20
+ END:VCALENDAR
@@ -0,0 +1,22 @@
1
+ BEGIN:VCALENDAR
2
+ VERSION:2.0
3
+ CALSCALE:GREGORIAN
4
+ PRODID:-//Apple Inc.//Mac OS X 10.12.6//EN
5
+ BEGIN:VEVENT
6
+ UID:AA000000-AAAA-CCCC-DDDD-CC7176000000
7
+ DTSTART;TZID=Asia/Dubai:20171019T174000
8
+ DTEND;TZID=Asia/Dubai:20171019T181000
9
+ CREATED:20170830T130728Z
10
+ DTSTAMP:20171016T185512Z
11
+ EXDATE;TZID=Asia/Dubai:20171102T174000
12
+ EXDATE;TZID=Asia/Dubai:20171026T174000
13
+ EXDATE;TZID=Asia/Dubai:20171019T174000
14
+ EXDATE;TZID=Asia/Dubai:20171109T174000
15
+ LAST-MODIFIED:20171016T185511Z
16
+ RRULE:FREQ=WEEKLY;UNTIL=20171215T195959Z;BYDAY=TH
17
+ SEQUENCE:0
18
+ SUMMARY:Exception with timezone
19
+ TRANSP:OPAQUE
20
+ X-APPLE-TRAVEL-ADVISORY-BEHAVIOR:AUTOMATIC
21
+ END:VEVENT
22
+ END:VCALENDAR
@@ -0,0 +1,23 @@
1
+ BEGIN:VCALENDAR
2
+ X-WR-CALNAME:Example Calendar
3
+ X-WR-CALID:f512e378-050c-4366-809a-ef471ce45b09:297138
4
+ PRODID:Zimbra-Calendar-Provider
5
+ VERSION:2.0
6
+ METHOD:PUBLISH
7
+ BEGIN:VEVENT
8
+ UID:28aad2ac-af09-44d0-b07d-6f49177c086f
9
+ RRULE:FREQ=DAILY;UNTIL=20140120T075959Z;INTERVAL=1
10
+ SUMMARY:All week\, then done
11
+ X-ALT-DESC;FMTTYPE=text/html:<html><body></body></html>
12
+ ORGANIZER;CN=Jordan Raine:mailto:jraine@sfu.ca
13
+ DTSTART;TZID="America/Los_Angeles":20140113T080000
14
+ DTEND;TZID="America/Los_Angeles":20140113T120000
15
+ STATUS:CONFIRMED
16
+ CLASS:PUBLIC
17
+ X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
18
+ TRANSP:OPAQUE
19
+ LAST-MODIFIED:20140117T200033Z
20
+ DTSTAMP:20140117T200033Z
21
+ SEQUENCE:0
22
+ END:VEVENT
23
+ END:VCALENDAR
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icalendar-recurrence
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Raine
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-20 00:00:00.000000000 Z
11
+ date: 2023-11-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: icalendar
@@ -30,28 +30,42 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.13'
33
+ version: '0.16'
34
34
  type: :runtime
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: '0.13'
40
+ version: '0.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tzinfo
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: activesupport
43
57
  requirement: !ruby/object:Gem::Requirement
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '4.0'
61
+ version: '7.1'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '4.0'
68
+ version: '7.1'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: awesome_print
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -86,80 +100,66 @@ dependencies:
86
100
  requirements:
87
101
  - - "~>"
88
102
  - !ruby/object:Gem::Version
89
- version: '1.3'
103
+ version: '2.0'
90
104
  type: :development
91
105
  prerelease: false
92
106
  version_requirements: !ruby/object:Gem::Requirement
93
107
  requirements:
94
108
  - - "~>"
95
109
  - !ruby/object:Gem::Version
96
- version: '1.3'
110
+ version: '2.0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rake
99
113
  requirement: !ruby/object:Gem::Requirement
100
114
  requirements:
101
- - - "~>"
115
+ - - ">="
102
116
  - !ruby/object:Gem::Version
103
- version: '10.2'
117
+ version: 12.3.3
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
107
121
  requirements:
108
- - - "~>"
122
+ - - ">="
109
123
  - !ruby/object:Gem::Version
110
- version: '10.2'
124
+ version: 12.3.3
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: rspec
113
127
  requirement: !ruby/object:Gem::Requirement
114
128
  requirements:
115
129
  - - "~>"
116
130
  - !ruby/object:Gem::Version
117
- version: '2.14'
131
+ version: '3.12'
118
132
  type: :development
119
133
  prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
136
  - - "~>"
123
137
  - !ruby/object:Gem::Version
124
- version: '2.14'
138
+ version: '3.12'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: timecop
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
143
  - - "~>"
130
144
  - !ruby/object:Gem::Version
131
- version: 0.6.3
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: 0.6.3
139
- - !ruby/object:Gem::Dependency
140
- name: tzinfo
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '0.3'
145
+ version: '0.9'
146
146
  type: :development
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - "~>"
151
151
  - !ruby/object:Gem::Version
152
- version: '0.3'
153
- description:
152
+ version: '0.9'
153
+ description:
154
154
  email:
155
155
  - jnraine@gmail.com
156
156
  executables: []
157
157
  extensions: []
158
158
  extra_rdoc_files: []
159
159
  files:
160
+ - ".github/workflows/main.yml"
160
161
  - ".gitignore"
161
162
  - ".rspec"
162
- - ".travis.yml"
163
163
  - CHANGELOG.md
164
164
  - Gemfile
165
165
  - Guardfile
@@ -179,6 +179,8 @@ files:
179
179
  - spec/lib/time_util_spec.rb
180
180
  - spec/spec_helper.rb
181
181
  - spec/support/fixtures/daily_event.ics
182
+ - spec/support/fixtures/dst_exdate_event.ics
183
+ - spec/support/fixtures/dst_rdate_event.ics
182
184
  - spec/support/fixtures/embedded_timezone_event.ics
183
185
  - spec/support/fixtures/every_monday_event.ics
184
186
  - spec/support/fixtures/every_other_day_event.ics
@@ -189,16 +191,19 @@ files:
189
191
  - spec/support/fixtures/first_sunday_of_january_yearly_event.ics
190
192
  - spec/support/fixtures/monday_until_friday_event.ics
191
193
  - spec/support/fixtures/multi_day_weekly_event.ics
194
+ - spec/support/fixtures/multiple_exception_event.ics
195
+ - spec/support/fixtures/multiple_exception_multiple_line_event.ics
192
196
  - spec/support/fixtures/on_third_every_two_months_event.ics
193
197
  - spec/support/fixtures/one_day_a_month_for_three_months_event.ics
194
198
  - spec/support/fixtures/utc_event.ics
199
+ - spec/support/fixtures/week_long_event.ics
195
200
  - spec/support/fixtures/weekly_with_count_event.ics
196
201
  - spec/support/helpers.rb
197
202
  homepage: https://github.com/icalendar/icalendar-recurrence
198
203
  licenses:
199
204
  - MIT
200
205
  metadata: {}
201
- post_install_message:
206
+ post_install_message:
202
207
  rdoc_options: []
203
208
  require_paths:
204
209
  - lib
@@ -213,9 +218,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
213
218
  - !ruby/object:Gem::Version
214
219
  version: '0'
215
220
  requirements: []
216
- rubyforge_project:
217
- rubygems_version: 2.5.2
218
- signing_key:
221
+ rubygems_version: 3.4.10
222
+ signing_key:
219
223
  specification_version: 4
220
224
  summary: Provides recurrence to icalendar gem.
221
225
  test_files:
@@ -224,6 +228,8 @@ test_files:
224
228
  - spec/lib/time_util_spec.rb
225
229
  - spec/spec_helper.rb
226
230
  - spec/support/fixtures/daily_event.ics
231
+ - spec/support/fixtures/dst_exdate_event.ics
232
+ - spec/support/fixtures/dst_rdate_event.ics
227
233
  - spec/support/fixtures/embedded_timezone_event.ics
228
234
  - spec/support/fixtures/every_monday_event.ics
229
235
  - spec/support/fixtures/every_other_day_event.ics
@@ -234,8 +240,11 @@ test_files:
234
240
  - spec/support/fixtures/first_sunday_of_january_yearly_event.ics
235
241
  - spec/support/fixtures/monday_until_friday_event.ics
236
242
  - spec/support/fixtures/multi_day_weekly_event.ics
243
+ - spec/support/fixtures/multiple_exception_event.ics
244
+ - spec/support/fixtures/multiple_exception_multiple_line_event.ics
237
245
  - spec/support/fixtures/on_third_every_two_months_event.ics
238
246
  - spec/support/fixtures/one_day_a_month_for_three_months_event.ics
239
247
  - spec/support/fixtures/utc_event.ics
248
+ - spec/support/fixtures/week_long_event.ics
240
249
  - spec/support/fixtures/weekly_with_count_event.ics
241
250
  - spec/support/helpers.rb
data/.travis.yml DELETED
@@ -1,9 +0,0 @@
1
- language: ruby
2
- sudo: false
3
- rvm:
4
- - 2.4.1
5
- - 2.3.4
6
- - 2.1.7
7
- - 2.0.0
8
- - 1.9.3
9
- script: bundle exec rspec