paulsm-icalendar 1.1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|