icalendar 2.4.1 → 2.8.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.
Files changed (45) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/main.yml +32 -0
  3. data/.gitignore +1 -0
  4. data/History.txt +39 -0
  5. data/{COPYING → LICENSE} +0 -0
  6. data/README.md +6 -22
  7. data/icalendar.gemspec +17 -18
  8. data/lib/icalendar/alarm.rb +1 -0
  9. data/lib/icalendar/calendar.rb +10 -0
  10. data/lib/icalendar/component.rb +14 -3
  11. data/lib/icalendar/event.rb +3 -0
  12. data/lib/icalendar/has_components.rb +17 -5
  13. data/lib/icalendar/has_properties.rb +27 -19
  14. data/lib/icalendar/journal.rb +2 -0
  15. data/lib/icalendar/marshable.rb +34 -0
  16. data/lib/icalendar/parser.rb +49 -16
  17. data/lib/icalendar/timezone.rb +68 -0
  18. data/lib/icalendar/timezone_store.rb +36 -0
  19. data/lib/icalendar/todo.rb +4 -1
  20. data/lib/icalendar/tzinfo.rb +2 -2
  21. data/lib/icalendar/values/array.rb +1 -2
  22. data/lib/icalendar/values/date.rb +2 -0
  23. data/lib/icalendar/values/date_or_date_time.rb +23 -11
  24. data/lib/icalendar/values/date_time.rb +4 -0
  25. data/lib/icalendar/values/time_with_zone.rb +23 -6
  26. data/lib/icalendar/values/uri.rb +2 -2
  27. data/lib/icalendar/values/utc_offset.rb +10 -2
  28. data/lib/icalendar/version.rb +1 -1
  29. data/lib/icalendar.rb +3 -1
  30. data/spec/alarm_spec.rb +7 -2
  31. data/spec/calendar_spec.rb +34 -0
  32. data/spec/event_spec.rb +24 -0
  33. data/spec/fixtures/bad_wrapping.ics +14 -0
  34. data/spec/fixtures/custom_component.ics +158 -0
  35. data/spec/fixtures/single_event.ics +1 -1
  36. data/spec/fixtures/single_event_bad_organizer.ics +22 -0
  37. data/spec/fixtures/single_event_organizer_parsed.ics +22 -0
  38. data/spec/fixtures/tzid_search.ics +31 -0
  39. data/spec/parser_spec.rb +31 -9
  40. data/spec/roundtrip_spec.rb +32 -9
  41. data/spec/timezone_spec.rb +48 -0
  42. data/spec/tzinfo_spec.rb +1 -1
  43. data/spec/values/date_or_date_time_spec.rb +10 -0
  44. metadata +65 -40
  45. data/.travis.yml +0 -18
@@ -1,3 +1,5 @@
1
+ require 'ice_cube'
2
+
1
3
  module Icalendar
2
4
 
3
5
  class Timezone < Component
@@ -12,10 +14,40 @@ module Icalendar
12
14
  optional_property :comment
13
15
  optional_property :rdate, Icalendar::Values::DateTime
14
16
  optional_property :tzname
17
+
18
+ transient_variable :@cached_occurrences
19
+ transient_variable :@occurrences
20
+ end
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
15
44
  end
45
+
46
+ @cached_occurrences.reverse_each.find { |occurrence| occurrence < from }
16
47
  end
17
48
  end
18
49
  class Daylight < Component
50
+ include Marshable
19
51
  include TzProperties
20
52
 
21
53
  def initialize
@@ -23,6 +55,7 @@ module Icalendar
23
55
  end
24
56
  end
25
57
  class Standard < Component
58
+ include Marshable
26
59
  include TzProperties
27
60
 
28
61
  def initialize
@@ -49,5 +82,40 @@ module Icalendar
49
82
  standards.all? { |s| s.valid? strict } or return false
50
83
  super
51
84
  end
85
+
86
+ def offset_for_local(local)
87
+ standard = standard_for local
88
+ daylight = daylight_for local
89
+
90
+ if standard.nil? && daylight.nil?
91
+ "+00:00"
92
+ elsif daylight.nil?
93
+ standard.last.tzoffsetto
94
+ elsif standard.nil?
95
+ daylight.last.tzoffsetto
96
+ else
97
+ sdst = standard.first
98
+ ddst = daylight.first
99
+ if sdst > ddst
100
+ standard.last.tzoffsetto
101
+ else
102
+ daylight.last.tzoffsetto
103
+ end
104
+ end
105
+ end
106
+
107
+ def standard_for(local)
108
+ possible = standards.map do |std|
109
+ [std.previous_occurrence(local.to_time), std]
110
+ end
111
+ possible.sort_by(&:first).last
112
+ end
113
+
114
+ def daylight_for(local)
115
+ possible = daylights.map do |day|
116
+ [day.previous_occurrence(local.to_time), day]
117
+ end
118
+ possible.sort_by(&:first).last
119
+ end
52
120
  end
53
121
  end
@@ -0,0 +1,36 @@
1
+ require 'delegate'
2
+ require 'icalendar/downcased_hash'
3
+
4
+ module Icalendar
5
+ class TimezoneStore < ::SimpleDelegator
6
+
7
+ def initialize
8
+ super DowncasedHash.new({})
9
+ end
10
+
11
+ def self.instance
12
+ warn "**** DEPRECATION WARNING ****\nTimezoneStore.instance will be removed in 3.0. Please instantiate a TimezoneStore object."
13
+ @instance ||= new
14
+ end
15
+
16
+ def self.store(timezone)
17
+ warn "**** DEPRECATION WARNING ****\nTimezoneStore.store will be removed in 3.0. Please use instance methods."
18
+ instance.store timezone
19
+ end
20
+
21
+ def self.retrieve(tzid)
22
+ warn "**** DEPRECATION WARNING ****\nTimezoneStore.retrieve will be removed in 3.0. Please use instance methods."
23
+ instance.retrieve tzid
24
+ end
25
+
26
+ def store(timezone)
27
+ self[timezone.tzid] = timezone
28
+ end
29
+
30
+ def retrieve(tzid)
31
+ self[tzid]
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -12,6 +12,7 @@ module Icalendar
12
12
  mutually_exclusive_properties :due, :duration
13
13
 
14
14
  optional_single_property :ip_class
15
+ optional_single_property :color
15
16
  optional_single_property :completed, Icalendar::Values::DateTime
16
17
  optional_single_property :created, Icalendar::Values::DateTime
17
18
  optional_single_property :description
@@ -38,6 +39,8 @@ module Icalendar
38
39
  optional_property :related_to
39
40
  optional_property :resources
40
41
  optional_property :rdate, Icalendar::Values::DateTime
42
+ optional_property :conference, Icalendar::Values::Uri, false, true
43
+ optional_property :image, Icalendar::Values::Uri, false, true
41
44
 
42
45
  component :alarm, false
43
46
 
@@ -49,4 +52,4 @@ module Icalendar
49
52
 
50
53
  end
51
54
 
52
- end
55
+ end
@@ -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
 
@@ -22,7 +22,7 @@ module Icalendar
22
22
  else
23
23
  [klass.new(value, params)]
24
24
  end
25
- super mapped, params
25
+ super mapped
26
26
  end
27
27
 
28
28
  def params_ical
@@ -52,7 +52,6 @@ module Icalendar
52
52
  def needs_value_type?(default_type)
53
53
  value.first.class != default_type
54
54
  end
55
-
56
55
  end
57
56
 
58
57
  end
@@ -7,6 +7,8 @@ module Icalendar
7
7
  FORMAT = '%Y%m%d'
8
8
 
9
9
  def initialize(value, params = {})
10
+ params.delete 'tzid'
11
+ params.delete 'x-tz-info'
10
12
  if value.is_a? String
11
13
  begin
12
14
  parsed_date = ::Date.strptime(value, FORMAT)
@@ -2,25 +2,37 @@ module Icalendar
2
2
  module Values
3
3
 
4
4
  # DateOrDateTime can be used to set an attribute to either a Date or a DateTime value.
5
- # It should not be used wihtout also invoking the `call` method.
6
- class DateOrDateTime
5
+ # It should not be used without also invoking the `call` method.
6
+ class DateOrDateTime < Value
7
7
 
8
- attr_reader :value, :params, :parsed
9
- def initialize(value, params = {})
10
- @value = value
11
- @params = params
8
+ def call
9
+ parsed
12
10
  end
13
11
 
14
- def call
12
+ def value_ical
13
+ parsed.value_ical
14
+ end
15
+
16
+ def params_ical
17
+ parsed.params_ical
18
+ end
19
+
20
+ private
21
+
22
+ def parsed
15
23
  @parsed ||= begin
16
- Icalendar::Values::DateTime.new value, params
24
+ Icalendar::Values::DateTime.new value, ical_params
17
25
  rescue Icalendar::Values::DateTime::FormatError
18
- Icalendar::Values::Date.new value, params
26
+ Icalendar::Values::Date.new value, ical_params
19
27
  end
20
28
  end
21
29
 
22
- def to_ical
23
- fail NoMethodError, 'You cannot use DateOrDateTime directly. Invoke `call` before `to_ical`'
30
+ def needs_value_type?(default_type)
31
+ parsed.class != default_type
32
+ end
33
+
34
+ def value_type
35
+ parsed.class.value_type
24
36
  end
25
37
 
26
38
  end
@@ -43,6 +43,10 @@ module Icalendar
43
43
  end
44
44
  end
45
45
 
46
+ def utc?
47
+ value.respond_to?(:utc?) ? value.utc? : value.to_time.utc?
48
+ end
49
+
46
50
  class FormatError < ArgumentError
47
51
  end
48
52
  end
@@ -1,9 +1,17 @@
1
+ require 'icalendar/timezone_store'
2
+
1
3
  begin
2
4
  require 'active_support/time'
3
5
 
4
6
  if defined?(ActiveSupport::TimeWithZone)
5
7
  require 'icalendar/values/active_support_time_with_zone_adapter'
6
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
7
15
  rescue LoadError
8
16
  # tis ok, just a bit less fancy
9
17
  end
@@ -16,15 +24,24 @@ module Icalendar
16
24
  def initialize(value, params = {})
17
25
  params = Icalendar::DowncasedHash(params)
18
26
  @tz_utc = params['tzid'] == 'UTC'
27
+ x_tz_info = params.delete 'x-tz-info'
19
28
 
20
- if defined?(ActiveSupport::TimeZone) && defined?(ActiveSupportTimeWithZoneAdapter) && !params['tzid'].nil?
29
+ offset_value = unless params['tzid'].nil?
21
30
  tzid = params['tzid'].is_a?(::Array) ? params['tzid'].first : params['tzid']
22
- zone = ActiveSupport::TimeZone[tzid]
23
- value = ActiveSupportTimeWithZoneAdapter.new nil, zone, value unless zone.nil?
24
- super value, params
25
- else
26
- super value, params
31
+ if defined?(ActiveSupport::TimeZone) &&
32
+ defined?(ActiveSupportTimeWithZoneAdapter) &&
33
+ (tz = ActiveSupport::TimeZone[tzid])
34
+ ActiveSupportTimeWithZoneAdapter.new(nil, tz, value)
35
+ elsif !x_tz_info.nil?
36
+ offset = x_tz_info.offset_for_local(value).to_s
37
+ if value.respond_to?(:change)
38
+ value.change offset: offset
39
+ else
40
+ ::Time.new value.year, value.month, value.day, value.hour, value.min, value.sec, offset
41
+ end
42
+ end
27
43
  end
44
+ super((offset_value || value), params)
28
45
  end
29
46
 
30
47
  def params_ical
@@ -6,7 +6,7 @@ module Icalendar
6
6
  class Uri < Value
7
7
 
8
8
  def initialize(value, params = {})
9
- parsed = URI.parse value rescue value
9
+ parsed = URI.parse(value) rescue value
10
10
  super parsed, params
11
11
  end
12
12
 
@@ -16,4 +16,4 @@ module Icalendar
16
16
  end
17
17
 
18
18
  end
19
- end
19
+ end
@@ -21,6 +21,15 @@ module Icalendar
21
21
  "#{behind? ? '-' : '+'}#{'%02d' % hours}#{'%02d' % minutes}#{'%02d' % seconds if seconds > 0}"
22
22
  end
23
23
 
24
+ def to_s
25
+ str = "#{behind? ? '-' : '+'}#{'%02d' % hours}:#{'%02d' % minutes}"
26
+ if seconds > 0
27
+ "#{str}:#{'%02d' % seconds}"
28
+ else
29
+ str
30
+ end
31
+ end
32
+
24
33
  private
25
34
 
26
35
  def zero_offset?
@@ -28,8 +37,7 @@ module Icalendar
28
37
  end
29
38
 
30
39
  def parse_fields(value)
31
- value.gsub!(/\s+/, '')
32
- 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+/, '')
33
41
  {
34
42
  behind: (md[:behind] == '-'),
35
43
  hours: md[:hours].to_i,
@@ -1,5 +1,5 @@
1
1
  module Icalendar
2
2
 
3
- VERSION = '2.4.1'
3
+ VERSION = '2.8.0'
4
4
 
5
5
  end
data/lib/icalendar.rb CHANGED
@@ -13,7 +13,7 @@ module Icalendar
13
13
  end
14
14
 
15
15
  def self.parse(source, single = false)
16
- warn "**** DEPRECATION WARNING ****\nIcalendar.parse will be removed. Please switch to Icalendar::Calendar.parse."
16
+ warn "**** DEPRECATION WARNING ****\nIcalendar.parse will be removed in 3.0. Please switch to Icalendar::Calendar.parse."
17
17
  calendars = Parser.new(source).parse
18
18
  single ? calendars.first : calendars
19
19
  end
@@ -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/spec/alarm_spec.rb CHANGED
@@ -6,14 +6,15 @@ describe Icalendar::Alarm do
6
6
  describe '#valid?' do
7
7
  subject do
8
8
  described_class.new.tap do |a|
9
- a.action = 'AUDIO'
10
9
  a.trigger = Icalendar::Values::DateTime.new(Time.now.utc)
11
10
  end
12
11
  end
13
12
  context 'neither duration or repeat is set' do
13
+ before(:each) { subject.action = 'AUDIO' }
14
14
  it { should be_valid }
15
15
  end
16
16
  context 'both duration and repeat are set' do
17
+ before(:each) { subject.action = 'AUDIO' }
17
18
  before(:each) do
18
19
  subject.duration = 'PT15M'
19
20
  subject.repeat = 4
@@ -29,8 +30,12 @@ describe Icalendar::Alarm do
29
30
  it { should_not be_valid }
30
31
  end
31
32
 
33
+ context 'DISPLAY is set as default action' do
34
+ it 'must be DISPLAY' do
35
+ expect(subject.action).to eq 'DISPLAY'
36
+ end
37
+ end
32
38
  context 'display action' do
33
- before(:each) { subject.action = 'DISPLAY' }
34
39
  it 'requires description' do
35
40
  expect(subject).to_not be_valid
36
41
  subject.description = 'Display Text'
@@ -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
 
@@ -117,6 +123,16 @@ describe Icalendar::Calendar do
117
123
  describe '#to_ical' do
118
124
  before(:each) do
119
125
  Timecop.freeze DateTime.new(2013, 12, 26, 5, 0, 0, '+0000')
126
+ subject.ip_name = 'Company Vacation Days'
127
+ subject.description = 'The description'
128
+ subject.last_modified = "20140101T000000Z"
129
+ subject.url = 'https://example.com'
130
+ subject.color = 'red'
131
+ subject.image = 'https://example.com/image.png'
132
+ subject.uid = '5FC53010-1267-4F8E-BC28-1D7AE55A7C99'
133
+ subject.categories = 'MEETING'
134
+ subject.refresh_interval = 'P1W'
135
+ subject.source = 'https://example.com/holidays.ics'
120
136
  subject.event do |e|
121
137
  e.summary = 'An event'
122
138
  e.dtstart = "20140101T000000Z"
@@ -139,6 +155,15 @@ BEGIN:VCALENDAR
139
155
  VERSION:2.0
140
156
  PRODID:icalendar-ruby
141
157
  CALSCALE:GREGORIAN
158
+ LAST-MODIFIED;VALUE=DATE-TIME:20140101T000000Z
159
+ URL;VALUE=URI:https://example.com
160
+ REFRESH-INTERVAL;VALUE=DURATION:P1W
161
+ SOURCE;VALUE=URI:https://example.com/holidays.ics
162
+ COLOR:red
163
+ NAME:Company Vacation Days
164
+ DESCRIPTION:The description
165
+ CATEGORIES:MEETING
166
+ IMAGE;VALUE=URI:https://example.com/image.png
142
167
  BEGIN:VEVENT
143
168
  DTSTAMP:20131226T050000Z
144
169
  DTSTART:20140101T000000Z
@@ -164,4 +189,13 @@ END:VCALENDAR
164
189
  expect(subject.ip_method).to eq 'PUBLISH'
165
190
  end
166
191
  end
192
+
193
+ describe '.parse' do
194
+ let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', 'bad_wrapping.ics') }
195
+
196
+ it 'correctly parses a bad file' do
197
+ actual = described_class.parse(source)
198
+ expect(actual[0]).to be_a(described_class)
199
+ end
200
+ end
167
201
  end
data/spec/event_spec.rb CHANGED
@@ -96,6 +96,30 @@ describe Icalendar::Event do
96
96
  end
97
97
  end
98
98
 
99
+ describe "#append_custom_property" do
100
+ context "with custom property" do
101
+ it "appends to the custom properties hash" do
102
+ subject.append_custom_property "x_my_property", "test value"
103
+ expect(subject.custom_properties).to eq({"x_my_property" => ["test value"]})
104
+ end
105
+ end
106
+
107
+ context "with a defined property" do
108
+ it "sets the proper setter" do
109
+ subject.append_custom_property "summary", "event"
110
+ expect(subject.summary).to eq "event"
111
+ expect(subject.custom_properties).to eq({})
112
+ end
113
+ end
114
+ end
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
+
99
123
  describe '.parse' do
100
124
  let(:source) { File.read File.join(File.dirname(__FILE__), 'fixtures', fn) }
101
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
@@ -9,7 +9,7 @@ DTSTART;TZID=US-Mountain:20050120T170000
9
9
  DTEND;TZID=US-Mountain:20050120T184500
10
10
  CLASS:PRIVATE
11
11
  GEO:37.386013;-122.0829322
12
- ORGANIZER:mailto:joebob@random.net
12
+ ORGANIZER;CN="Joe Bob: Magician":mailto:joebob@random.net
13
13
  PRIORITY:2
14
14
  SUMMARY:This is a really long summary to test the method of unfolding lines
15
15
  \, so I'm just going to make it a whole bunch of lines. With a twist: a "