paulsm-icalendar 1.1.0.4
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.
- data/COPYING +56 -0
- data/GPL +340 -0
- data/README +266 -0
- data/Rakefile +109 -0
- data/docs/rfcs/itip_notes.txt +69 -0
- data/docs/rfcs/rfc2425.pdf +0 -0
- data/docs/rfcs/rfc2426.pdf +0 -0
- data/docs/rfcs/rfc2445.pdf +0 -0
- data/docs/rfcs/rfc2446.pdf +0 -0
- data/docs/rfcs/rfc2447.pdf +0 -0
- data/docs/rfcs/rfc3283.txt +738 -0
- data/examples/create_cal.rb +45 -0
- data/examples/parse_cal.rb +20 -0
- data/examples/single_event.ics +18 -0
- data/lib/hash_attrs.rb +34 -0
- data/lib/icalendar.rb +39 -0
- data/lib/icalendar/base.rb +43 -0
- data/lib/icalendar/calendar.rb +113 -0
- data/lib/icalendar/component.rb +442 -0
- data/lib/icalendar/component/alarm.rb +44 -0
- data/lib/icalendar/component/event.rb +129 -0
- data/lib/icalendar/component/freebusy.rb +38 -0
- data/lib/icalendar/component/journal.rb +61 -0
- data/lib/icalendar/component/timezone.rb +105 -0
- data/lib/icalendar/component/todo.rb +64 -0
- data/lib/icalendar/conversions.rb +150 -0
- data/lib/icalendar/helpers.rb +109 -0
- data/lib/icalendar/parameter.rb +33 -0
- data/lib/icalendar/parser.rb +396 -0
- data/lib/icalendar/rrule.rb +126 -0
- data/lib/icalendar/tzinfo.rb +121 -0
- data/lib/meta.rb +32 -0
- data/test/calendar_test.rb +71 -0
- data/test/component/event_test.rb +256 -0
- data/test/component/timezone_test.rb +67 -0
- data/test/component/todo_test.rb +13 -0
- data/test/component_test.rb +76 -0
- data/test/conversions_test.rb +97 -0
- data/test/coverage/STUB +0 -0
- data/test/fixtures/folding.ics +23 -0
- data/test/fixtures/life.ics +46 -0
- data/test/fixtures/simplecal.ics +119 -0
- data/test/fixtures/single_event.ics +23 -0
- data/test/interactive.rb +17 -0
- data/test/parameter_test.rb +29 -0
- data/test/parser_test.rb +84 -0
- data/test/read_write.rb +23 -0
- metadata +108 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 Rick (http://github.com/rubyredrick)
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
require 'date'
|
10
|
+
require 'uri'
|
11
|
+
require 'stringio'
|
12
|
+
|
13
|
+
module Icalendar
|
14
|
+
|
15
|
+
# This class is not yet fully functional..
|
16
|
+
#
|
17
|
+
# Gem versions < 1.1.0.0 used to return a string for the recurrence_rule component,
|
18
|
+
# but now it returns this Icalendar::RRule class. ie It's not backwards compatible!
|
19
|
+
#
|
20
|
+
# To get the original RRULE value from a parsed feed, use the 'orig_value' property.
|
21
|
+
#
|
22
|
+
# Example:
|
23
|
+
# rules = event.recurrence_rules.map{ |rule| rule.orig_value }
|
24
|
+
|
25
|
+
class RRule < Icalendar::Base
|
26
|
+
|
27
|
+
class Weekday
|
28
|
+
def initialize(day, position)
|
29
|
+
@day, @position = day, position
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"#{@position}#{@day}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(name, params, value, parser)
|
38
|
+
@value = value
|
39
|
+
frequency_match = value.match(/FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY)/)
|
40
|
+
raise Icalendar::InvalidPropertyValue.new("FREQ must be specified for RRULE values") unless frequency_match
|
41
|
+
@frequency = frequency_match[1]
|
42
|
+
@until = parse_date_val("UNTIL", value)
|
43
|
+
@count = parse_int_val("COUNT", value)
|
44
|
+
raise Icalendar::InvalidPropertyValue.new("UNTIL and COUNT must not both be specified for RRULE values") if [@until, @count].compact.length > 1
|
45
|
+
@interval = parse_int_val("INTERVAL", value)
|
46
|
+
@by_list = {:bysecond => parse_int_list("BYSECOND", value)}
|
47
|
+
@by_list[:byminute] = parse_int_list("BYMINUTE",value)
|
48
|
+
@by_list[:byhour] = parse_int_list("BYHOUR", value)
|
49
|
+
@by_list[:byday] = parse_weekday_list("BYDAY", value)
|
50
|
+
@by_list[:bymonthday] = parse_int_list("BYMONTHDAY", value)
|
51
|
+
@by_list[:byyearday] = parse_int_list("BYYEARDAY", value)
|
52
|
+
@by_list[:byweekno] = parse_int_list("BYWEEKNO", value)
|
53
|
+
@by_list[:bymonth] = parse_int_list("BYMONTH", value)
|
54
|
+
@by_list[:bysetpos] = parse_int_list("BYSETPOS", value)
|
55
|
+
@wkst = parse_wkstart(value)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the original pre-parsed RRULE value.
|
59
|
+
def orig_value
|
60
|
+
@value
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_ical
|
64
|
+
result = ["FREQ=#{@frequency}"]
|
65
|
+
result << ";UNTIL=#{@until.to_ical}" if @until
|
66
|
+
result << ";COUNT=#{@count}" if @count
|
67
|
+
result << ";INTERVAL=#{@interval}" if @interval
|
68
|
+
@by_list.each do |key, value|
|
69
|
+
result << ";#{key.to_s.upcase}=#{value}" if value
|
70
|
+
end
|
71
|
+
result << ";WKST=#{@wkst}" if @wkst
|
72
|
+
result.join
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_date_val(name, string)
|
76
|
+
match = string.match(/;#{name}=(.*?)(;|$)/)
|
77
|
+
match ? DateTime.parse(match[1]) : nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def parse_int_val(name, string)
|
81
|
+
match = string.match(/;#{name}=(\d+)(;|$)/)
|
82
|
+
match ? match[1].to_i : nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_int_list(name, string)
|
86
|
+
match = string.match(/;#{name}=([+-]?.*?)(;|$)/)
|
87
|
+
if match
|
88
|
+
match[1].split(",").map {|int| int.to_i}
|
89
|
+
else
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def parse_weekday_list(name, string)
|
95
|
+
match = string.match(/;#{name}=(.*?)(;|$)/)
|
96
|
+
if match
|
97
|
+
match[1].split(",").map {|weekday|
|
98
|
+
wd_match = weekday.match(/([+-]?\d*)(SU|MO|TU|WE|TH|FR|SA)/)
|
99
|
+
Weekday.new(wd_match[2], wd_match[1])
|
100
|
+
}
|
101
|
+
else
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_wkstart(string)
|
107
|
+
match = string.match(/;WKSTART=(SU|MO|TU|WE|TH|FR|SA)(;|$)/)
|
108
|
+
if match
|
109
|
+
%w{SU MO TU WE TH FR SA}.index(match[1])
|
110
|
+
else
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# TODO: Incomplete
|
116
|
+
def occurrences_of_event_starting(event, datetime)
|
117
|
+
initial_start = event.dtstart
|
118
|
+
(0...@count).map {|day_offset|
|
119
|
+
occurrence = event.clone
|
120
|
+
occurrence.dtstart = initial_start + day_offset
|
121
|
+
occurrence.clone
|
122
|
+
}
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
=begin
|
2
|
+
Copyright (C) 2008 Sean Dague
|
3
|
+
|
4
|
+
This library is free software; you can redistribute it and/or modify it
|
5
|
+
under the same terms as the ruby language itself, see the file COPYING for
|
6
|
+
details.
|
7
|
+
=end
|
8
|
+
|
9
|
+
# The following adds a bunch of mixins to the tzinfo class, with the
|
10
|
+
# intent on making it very easy to load in tzinfo data for generating
|
11
|
+
# ical events. With this you can do the following:
|
12
|
+
#
|
13
|
+
# require "icalendar/tzinfo"
|
14
|
+
#
|
15
|
+
# estart = DateTime.new(2008, 12, 29, 8, 0, 0)
|
16
|
+
# eend = DateTime.new(2008, 12, 29, 11, 0, 0)
|
17
|
+
# tstring = "America/Chicago"
|
18
|
+
#
|
19
|
+
# tz = TZInfo::Timezone.get(tstring)
|
20
|
+
# cal = Calendar.new
|
21
|
+
# # the mixins now generate all the timezone info for the date in question
|
22
|
+
# timezone = tz.ical_timezone(estart)
|
23
|
+
# cal.add(timezone)
|
24
|
+
#
|
25
|
+
# cal.event do
|
26
|
+
# dtstart estart
|
27
|
+
# dtend eend
|
28
|
+
# summary "Meeting with the man."
|
29
|
+
# description "Have a long lunch meeting and decide nothing..."
|
30
|
+
# klass "PRIVATE"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# puts cal.to_ical
|
34
|
+
#
|
35
|
+
# The recurance rule calculations are hacky, and only start at the
|
36
|
+
# beginning of the current dst transition. I doubt this works for non
|
37
|
+
# dst areas yet. However, for a standard dst flipping zone, this
|
38
|
+
# seems to work fine (tested in Mozilla Thunderbird + Lightning).
|
39
|
+
# Future goal would be making this better.
|
40
|
+
|
41
|
+
# require "rubygems"
|
42
|
+
# require "tzinfo"
|
43
|
+
|
44
|
+
module TZInfo
|
45
|
+
class Timezone
|
46
|
+
def ical_timezone(date)
|
47
|
+
period = period_for_local(date)
|
48
|
+
timezone = Icalendar::Timezone.new
|
49
|
+
timezone.timezone_id = identifier
|
50
|
+
timezone.add(period.daylight)
|
51
|
+
timezone.add(period.standard)
|
52
|
+
return timezone
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class TimezoneTransitionInfo
|
57
|
+
def offset_from
|
58
|
+
a = previous_offset.utc_total_offset
|
59
|
+
sprintf("%2.2d%2.2d", (a / 3600).to_i, ((a / 60) % 60).to_i)
|
60
|
+
end
|
61
|
+
|
62
|
+
def offset_to
|
63
|
+
a = offset.utc_total_offset
|
64
|
+
sprintf("%2.2d%2.2d", (a / 3600).to_i, ((a / 60) % 60).to_i)
|
65
|
+
end
|
66
|
+
|
67
|
+
def rrule
|
68
|
+
start = local_start.to_datetime
|
69
|
+
# this is somewhat of a hack, but seems to work ok
|
70
|
+
[sprintf(
|
71
|
+
"FREQ=YEARLY;BYMONTH=%d;BYDAY=%d%s",
|
72
|
+
start.month,
|
73
|
+
((start.day - 1)/ 7).to_i + 1,
|
74
|
+
start.strftime("%a").upcase[0,2]
|
75
|
+
)]
|
76
|
+
end
|
77
|
+
|
78
|
+
def dtstart
|
79
|
+
local_start.to_datetime.strftime("%Y%m%dT%H%M%S")
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
class TimezonePeriod
|
85
|
+
def daylight
|
86
|
+
day = Icalendar::Daylight.new
|
87
|
+
if dst?
|
88
|
+
day.timezone_name = abbreviation.to_s
|
89
|
+
day.timezone_offset_from = start_transition.offset_from
|
90
|
+
day.timezone_offset_to = start_transition.offset_to
|
91
|
+
day.dtstart = start_transition.dtstart
|
92
|
+
day.recurrence_rules = start_transition.rrule
|
93
|
+
else
|
94
|
+
day.timezone_name = abbreviation.to_s.sub("ST","DT")
|
95
|
+
day.timezone_offset_from = end_transition.offset_from
|
96
|
+
day.timezone_offset_to = end_transition.offset_to
|
97
|
+
day.dtstart = end_transition.dtstart
|
98
|
+
day.recurrence_rules = end_transition.rrule
|
99
|
+
end
|
100
|
+
return day
|
101
|
+
end
|
102
|
+
|
103
|
+
def standard
|
104
|
+
std = Icalendar::Standard.new
|
105
|
+
if dst?
|
106
|
+
std.timezone_name = abbreviation.to_s.sub("DT","ST")
|
107
|
+
std.timezone_offset_from = end_transition.offset_from
|
108
|
+
std.timezone_offset_to = end_transition.offset_to
|
109
|
+
std.dtstart = end_transition.dtstart
|
110
|
+
std.recurrence_rules = end_transition.rrule
|
111
|
+
else
|
112
|
+
std.timezone_name = abbreviation.to_s
|
113
|
+
std.timezone_offset_from = start_transition.offset_from
|
114
|
+
std.timezone_offset_to = start_transition.offset_to
|
115
|
+
std.dtstart = start_transition.dtstart
|
116
|
+
std.recurrence_rules = start_transition.rrule
|
117
|
+
end
|
118
|
+
return std
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
data/lib/meta.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# A set of methods to help create meta-programming gizmos.
|
2
|
+
class Object
|
3
|
+
# The metaclass is the singleton behind every object.
|
4
|
+
def metaclass
|
5
|
+
class << self
|
6
|
+
self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Evaluates the block in the context of the metaclass
|
11
|
+
def meta_eval &blk
|
12
|
+
metaclass.instance_eval &blk
|
13
|
+
end
|
14
|
+
|
15
|
+
# Acts like an include except it adds the module's methods
|
16
|
+
# to the metaclass so they act like class methods.
|
17
|
+
def meta_include mod
|
18
|
+
meta_eval do
|
19
|
+
include mod
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Adds methods to a metaclass
|
24
|
+
def meta_def name, &blk
|
25
|
+
meta_eval { define_method name, &blk }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Defines an instance method within a class
|
29
|
+
def class_def name, &blk
|
30
|
+
class_eval { define_method name, &blk }
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'icalendar'
|
5
|
+
|
6
|
+
require 'date'
|
7
|
+
|
8
|
+
class TestCalendar < Test::Unit::TestCase
|
9
|
+
include Icalendar
|
10
|
+
# Generate a calendar using the raw api, and then spit it out
|
11
|
+
# as a string. Parse the string and make sure everything matches up.
|
12
|
+
def test_raw_generation
|
13
|
+
# Create a fresh calendar
|
14
|
+
cal = Calendar.new
|
15
|
+
|
16
|
+
cal.calscale = "GREGORIAN"
|
17
|
+
cal.version = "3.2"
|
18
|
+
cal.prodid = "test-prodid"
|
19
|
+
|
20
|
+
# Now generate the string and then parse it so we can verify
|
21
|
+
# that everything was set, generated and parsed correctly.
|
22
|
+
calString = cal.to_ical
|
23
|
+
|
24
|
+
cals = Parser.new(calString).parse
|
25
|
+
|
26
|
+
cal2 = cals.first
|
27
|
+
assert_equal("GREGORIAN", cal2.calscale)
|
28
|
+
assert_equal("3.2", cal2.version)
|
29
|
+
assert_equal("test-prodid", cal2.prodid)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_block_creation
|
33
|
+
cal = Calendar.new
|
34
|
+
cal.event do
|
35
|
+
self.dtend = "19970903T190000Z"
|
36
|
+
self.summary = "This is my summary"
|
37
|
+
end
|
38
|
+
|
39
|
+
event = cal.event
|
40
|
+
event.dtend "19970903T190000Z", {:TZID => "Europe/Copenhagen"}
|
41
|
+
event.summary "This is my summary"
|
42
|
+
|
43
|
+
ev = cal.events.each do |ev|
|
44
|
+
assert_equal("19970903T190000Z", ev.dtend)
|
45
|
+
assert_equal("This is my summary", ev.summary)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_find
|
50
|
+
cal = Calendar.new
|
51
|
+
|
52
|
+
# add some events so we actually have to search
|
53
|
+
10.times do
|
54
|
+
cal.event
|
55
|
+
cal.todo
|
56
|
+
cal.journal
|
57
|
+
cal.freebusy
|
58
|
+
end
|
59
|
+
event = cal.events[5]
|
60
|
+
assert_equal(event, cal.find_event(event.uid))
|
61
|
+
|
62
|
+
todo = cal.todos[5]
|
63
|
+
assert_equal(todo, cal.find_todo(todo.uid))
|
64
|
+
|
65
|
+
journal = cal.journals[5]
|
66
|
+
assert_equal(journal, cal.find_journal(journal.uid))
|
67
|
+
|
68
|
+
freebusy = cal.freebusys[5]
|
69
|
+
assert_equal(freebusy, cal.find_freebusy(freebusy.uid))
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'icalendar'
|
5
|
+
|
6
|
+
unless defined? 1.days
|
7
|
+
class Integer
|
8
|
+
def days
|
9
|
+
self * 60 * 60 * 24
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Define a test event
|
15
|
+
testEvent = <<EOS
|
16
|
+
BEGIN:VEVENT
|
17
|
+
UID:19970901T130000Z-123401@host.com
|
18
|
+
DTSTAMP:19970901T1300Z
|
19
|
+
DTSTART:19970903T163000Z
|
20
|
+
DTEND:19970903T190000Z
|
21
|
+
SUMMARY:Annual Employee Review
|
22
|
+
CLASS:PRIVATE
|
23
|
+
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
24
|
+
END:VEVENT
|
25
|
+
EOS
|
26
|
+
|
27
|
+
class TestEvent < Test::Unit::TestCase
|
28
|
+
|
29
|
+
# Create a calendar with an event for each test.
|
30
|
+
def setup
|
31
|
+
@cal = Icalendar::Calendar.new
|
32
|
+
@event = Icalendar::Event.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_new
|
36
|
+
assert(@event)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Properties that can only occur once per event
|
40
|
+
def test_single_properties
|
41
|
+
@event.ip_class = "PRIVATE"
|
42
|
+
|
43
|
+
@cal.add_event(@event)
|
44
|
+
|
45
|
+
cals = Icalendar::Parser.new(@cal.to_ical).parse
|
46
|
+
cal2 = cals.first
|
47
|
+
event2 = cal2.events.first
|
48
|
+
|
49
|
+
assert_equal("PRIVATE", event2.ip_class)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class TestEventWithSpecifiedTimezone < Test::Unit::TestCase
|
54
|
+
|
55
|
+
def setup
|
56
|
+
src = <<EOS
|
57
|
+
BEGIN:VCALENDAR
|
58
|
+
METHOD:PUBLISH
|
59
|
+
CALSCALE:GREGORIAN
|
60
|
+
VERSION:2.0
|
61
|
+
BEGIN:VEVENT
|
62
|
+
UID:19970901T130000Z-123401@host.com
|
63
|
+
DTSTAMP:19970901T1300Z
|
64
|
+
DTSTART;TZID=America/Chicago:19970903T163000
|
65
|
+
DTEND;TZID=America/Chicago:19970903T190000
|
66
|
+
SUMMARY:Annual Employee Review
|
67
|
+
CLASS:PRIVATE
|
68
|
+
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
69
|
+
END:VEVENT
|
70
|
+
END:VCALENDAR
|
71
|
+
EOS
|
72
|
+
@calendar = Icalendar.parse(src).first
|
73
|
+
@event = @calendar.events.first
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_event_is_parsed
|
77
|
+
assert_not_nil(@event)
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_dtstart_should_understand_icalendar_tzid
|
81
|
+
assert_respond_to(@event.dtstart, :icalendar_tzid)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_dtstart_tzid_should_be_correct
|
85
|
+
puts "#{@event.dtstart.icalendar_tzid} #{@event.dtstart}"
|
86
|
+
assert_equal("America/Chicago",@event.dtstart.icalendar_tzid)
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_dtend_tzid_should_be_correct
|
90
|
+
assert_equal("America/Chicago",@event.dtend.icalendar_tzid)
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
class TestEventWithZuluTimezone < Test::Unit::TestCase
|
96
|
+
|
97
|
+
def setup
|
98
|
+
src = <<EOS
|
99
|
+
BEGIN:VCALENDAR
|
100
|
+
METHOD:PUBLISH
|
101
|
+
CALSCALE:GREGORIAN
|
102
|
+
VERSION:2.0
|
103
|
+
BEGIN:VEVENT
|
104
|
+
UID:19970901T130000Z-123401@host.com
|
105
|
+
DTSTAMP:19970901T1300Z
|
106
|
+
DTSTART:19970903T163000Z
|
107
|
+
DTEND:19970903T190000Z
|
108
|
+
SUMMARY:Annual Employee Review
|
109
|
+
CLASS:PRIVATE
|
110
|
+
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
111
|
+
END:VEVENT
|
112
|
+
END:VCALENDAR
|
113
|
+
EOS
|
114
|
+
@calendar = Icalendar.parse(src).first
|
115
|
+
@event = @calendar.events.first
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_event_is_parsed
|
119
|
+
assert_not_nil(@event)
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_dtstart_tzid_should_be_correct
|
123
|
+
puts "#{@event.dtstart.icalendar_tzid} #{@event.dtstart}"
|
124
|
+
assert_equal("UTC",@event.dtstart.icalendar_tzid)
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_dtend_tzid_should_be_correct
|
128
|
+
assert_equal("UTC",@event.dtend.icalendar_tzid)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
class TestEventWithFloatingTimezone < Test::Unit::TestCase
|
134
|
+
|
135
|
+
def setup
|
136
|
+
src = <<EOS
|
137
|
+
BEGIN:VCALENDAR
|
138
|
+
METHOD:PUBLISH
|
139
|
+
CALSCALE:GREGORIAN
|
140
|
+
VERSION:2.0
|
141
|
+
BEGIN:VEVENT
|
142
|
+
UID:19970901T130000Z-123401@host.com
|
143
|
+
DTSTAMP:19970901T1300Z
|
144
|
+
DTSTART:19970903T163000
|
145
|
+
DTEND:19970903T190000
|
146
|
+
SUMMARY:Annual Employee Review
|
147
|
+
CLASS:PRIVATE
|
148
|
+
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
149
|
+
END:VEVENT
|
150
|
+
END:VCALENDAR
|
151
|
+
EOS
|
152
|
+
@calendar = Icalendar.parse(src).first
|
153
|
+
@event = @calendar.events.first
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_event_is_parsed
|
157
|
+
assert_not_nil(@event)
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_dtstart_tzid_should_be_nil
|
161
|
+
puts "#{@event.dtstart.icalendar_tzid.inspect} #{@event.dtstart}"
|
162
|
+
assert_nil(@event.dtstart.icalendar_tzid)
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_dtend_tzid_should_be_nil
|
166
|
+
assert_nil(@event.dtend.icalendar_tzid)
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
class TestAllDayEventWithoutTime < Test::Unit::TestCase
|
172
|
+
|
173
|
+
def setup
|
174
|
+
src = <<EOS
|
175
|
+
BEGIN:VCALENDAR
|
176
|
+
VERSION:2.0
|
177
|
+
X-WR-CALNAME:New Event
|
178
|
+
PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
|
179
|
+
X-WR-RELCALID:3A016BE7-8932-4456-8ABD-C8F7EEC5963A
|
180
|
+
X-WR-TIMEZONE:Europe/London
|
181
|
+
CALSCALE:GREGORIAN
|
182
|
+
METHOD:PUBLISH
|
183
|
+
BEGIN:VEVENT
|
184
|
+
DTSTART;VALUE=DATE:20090110
|
185
|
+
DTEND;VALUE=DATE:20090111
|
186
|
+
SUMMARY:New Event
|
187
|
+
UID:3829F33C-F601-49AC-A3A5-C3AC4A6A3483
|
188
|
+
SEQUENCE:4
|
189
|
+
DTSTAMP:20090109T184719Z
|
190
|
+
END:VEVENT
|
191
|
+
END:VCALENDAR
|
192
|
+
EOS
|
193
|
+
@calendar = Icalendar.parse(src).first
|
194
|
+
@event = @calendar.events.first
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_event_is_parsed
|
198
|
+
assert_not_nil(@event)
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_dtstart_set_correctly
|
202
|
+
assert_equal("20090110", @event.dtstart.to_ical)
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
class TestRecurringEventWithCount < Test::Unit::TestCase
|
208
|
+
# DTSTART;TZID=US-Eastern:19970902T090000
|
209
|
+
# RRULE:FREQ=DAILY;COUNT=10
|
210
|
+
# ==> (1997 9:00 AM EDT)September 2-11
|
211
|
+
|
212
|
+
def setup
|
213
|
+
src = <<EOS
|
214
|
+
BEGIN:VCALENDAR
|
215
|
+
METHOD:PUBLISH
|
216
|
+
CALSCALE:GREGORIAN
|
217
|
+
VERSION:2.0
|
218
|
+
BEGIN:VEVENT
|
219
|
+
UID:19970901T130000Z-123401@host.com
|
220
|
+
DTSTAMP:19970901T1300Z
|
221
|
+
DTSTART:19970902T090000Z
|
222
|
+
DTEND:19970902T100000Z
|
223
|
+
RRULE:FREQ=DAILY;COUNT=10
|
224
|
+
SUMMARY:Annual Employee Review
|
225
|
+
CLASS:PRIVATE
|
226
|
+
CATEGORIES:BUSINESS,HUMAN RESOURCES
|
227
|
+
END:VEVENT
|
228
|
+
END:VCALENDAR
|
229
|
+
EOS
|
230
|
+
@calendar = Icalendar.parse(src).first
|
231
|
+
@event = @calendar.events.first
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_event_is_parsed
|
235
|
+
assert_not_nil(@event)
|
236
|
+
end
|
237
|
+
|
238
|
+
def test_recurrence_rules_should_return_a_recurrence_rule_array
|
239
|
+
assert_equal 1, @event.recurrence_rules.length
|
240
|
+
assert_kind_of(Icalendar::RRule, @event.recurrence_rules.first)
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_occurrences_after_with_start_before_start_at_should_return_count_occurrences
|
244
|
+
assert_equal 10, @event.occurrences_starting(Time.utc(1997, 9, 2, 8, 30, 0, 0)).length
|
245
|
+
end
|
246
|
+
|
247
|
+
# def test_occurrences_after_with_start_before_start_at_should_return_an_event_with_the_dtstart_as_the_first_event
|
248
|
+
# assert_equal @event.dtstart.to_s, @event.occurrences_starting(Time.utc(1997, 9, 2, 8, 30, 0, 0)).first.dtstart.to_s
|
249
|
+
# end
|
250
|
+
#
|
251
|
+
# def test_occurrences_after_with_start_before_start_at_should_return_events_with_the_correct_dtstart_values
|
252
|
+
# expected = (0..9).map {|delta| (@event.dtstart + delta).to_s}
|
253
|
+
# assert_equal expected, @event.occurrences_starting(Time.utc(1997, 9, 2, 8, 30, 0, 0)).map {|occurence| occurence.dtstart.to_s}
|
254
|
+
# end
|
255
|
+
end
|
256
|
+
|