icalendar 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -75,6 +75,131 @@ Now for some examples:
75
75
  # network port, database etc.
76
76
  cal_string = cal.to_ical
77
77
  puts cal_string
78
+
79
+ ## ALARMS
80
+
81
+ ## Within an event, you can create e-mail notification alarms like this...
82
+
83
+ cal.event.do
84
+ # ...other event properties
85
+ alarm do
86
+ action "EMAIL"
87
+ description "This is an event reminder" # email body (required)
88
+ summary "Alarm notification" # email subject (required)
89
+ attendees %w(mailto:me@my-domain.com mailto:me-too@my-domain.com) # one or more email recipients (required)
90
+ add_attendee "mailto:me-three@my-domain.com"
91
+ remove_attendee "mailto:me@my-domain.com"
92
+ trigger "-PT15M" # 15 minutes before
93
+ add_attach "ftp://host.com/novo-procs/felizano.exe", {"FMTTYPE" => "application/binary"} # email attachments (optional)
94
+ end
95
+
96
+ alarm do
97
+ action "DISPLAY" # This line isn't necessary, it's the default
98
+ summary "Alarm notification"
99
+ trigger "-P1DT0H0M0S" # 1 day before
100
+ end
101
+
102
+ alarm do
103
+ action "AUDIO"
104
+ trigger "-PT15M"
105
+ add_attach "Basso", {"VALUE" => ["URI"]} # only one attach allowed (optional)
106
+ end
107
+ end
108
+
109
+ # Output
110
+
111
+ # BEGIN:VALARM
112
+ # ACTION:EMAIL
113
+ # ATTACH;FMTTYPE=application/binary:ftp://host.com/novo-procs/felizano.exe
114
+ # TRIGGER:-PT15M
115
+ # SUMMARY:Alarm notification
116
+ # DESCRIPTION:This is an event reminder
117
+ # ATTENDEE:mailto:me-too@my-domain.com
118
+ # ATTENDEE:mailto:me-three@my-domain.com
119
+ # END:VALARM
120
+ #
121
+ # BEGIN:VALARM
122
+ # ACTION:DISPLAY
123
+ # TRIGGER:-P1DT0H0M0S
124
+ # SUMMARY:Alarm notification
125
+ # END:VALARM
126
+ #
127
+ # BEGIN:VALARM
128
+ # ACTION:AUDIO
129
+ # ATTACH;VALUE=URI:Basso
130
+ # TRIGGER:-PT15M
131
+ # END:VALARM
132
+
133
+ ## Timezones
134
+
135
+ # Create a timezone definition (previous convention)
136
+ cal = Calendar.new
137
+ timezone = Icalendar::Timezone.new
138
+ daylight = Icalendar::Daylight.new
139
+ standard = Icalendar::Standard.new
140
+
141
+ timezone.timezone_id = "America/Chicago"
142
+
143
+ daylight.timezone_offset_from = "-0600"
144
+ daylight.timezone_offset_to = "-0500"
145
+ daylight.timezone_name = "CDT"
146
+ daylight.dtstart = "19700308TO20000"
147
+ daylight.recurrence_rules = ["FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"]
148
+
149
+ standard.timezone_offset_from = "-0500"
150
+ standard.timezone_offset_to = "-0600"
151
+ standard.timezone_name = "CST"
152
+ standard.dtstart = "19701101T020000"
153
+ standard.recurrence_rules = ["YEARLY;BYMONTH=11;BYDAY=1SU"]
154
+
155
+ timezone.add(daylight)
156
+ timezone.add(standard)
157
+ cal.add(timezone)
158
+
159
+ # Now, you can make timezones like this
160
+ cal = Calendar.new
161
+ cal.timezone do
162
+ timezone_id "America/Chicago"
163
+
164
+ daylight do
165
+ timezone_offset_from "-0600"
166
+ timezone_offset_to "-0500"
167
+ timezone_name "CDT"
168
+ dtstart "19700308TO20000"
169
+ add_recurrence_rule "FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"
170
+ end
171
+
172
+ standard do
173
+ timezone_offset_from "-0500"
174
+ timezone_offset_to "-0600"
175
+ timezone_name "CST"
176
+ dtstart "19701101T020000"
177
+ add_recurrence_rule "YEARLY;BYMONTH=11;BYDAY=1SU"
178
+ end
179
+ end
180
+
181
+ # Both conventions output
182
+
183
+ # BEGIN:VTIMEZONE
184
+ # TZID:America/Chicago
185
+ # BEGIN:DAYLIGHT
186
+ # TZOFFSETFROM:-0600
187
+ # TZOFFSETTO:-0500
188
+ # TZNAME:CDT
189
+ # DTSTART:19700308T020000
190
+ # RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
191
+ # END:DAYLIGHT
192
+ # BEGIN:STANDARD
193
+ # TZOFFSETFROM:-0500
194
+ # TZOFFSETTO:-0600
195
+ # TZNAME:CST
196
+ # DTSTART:19701101T020000
197
+ # RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
198
+ # END:STANDARD
199
+ # END:VTIMEZONE
200
+
201
+ == Unicode
202
+ Add `$KCODE = 'u'` to make icalender work correctly with Utf8 texts
78
203
 
79
204
  == Parsing iCalendars:
80
205
 
data/Rakefile CHANGED
@@ -5,7 +5,7 @@ require 'rake/rdoctask'
5
5
  require 'rake/clean'
6
6
  require 'rake/contrib/sshpublisher'
7
7
 
8
- PKG_VERSION = "1.0.2"
8
+ PKG_VERSION = "1.1.0"
9
9
 
10
10
  $VERBOSE = nil
11
11
  TEST_CHANGES_SINCE = Time.now - 600 # Recent tests = changed in last 10 minutes
@@ -74,8 +74,8 @@ spec = Gem::Specification.new do |s|
74
74
  s.extra_rdoc_files = ["README", "COPYING", "GPL"]
75
75
  s.rdoc_options.concat ['--main', 'README']
76
76
 
77
- s.author = "Jeff Rose"
78
- s.email = "rosejn@gmail.com"
77
+ s.author = "Sean Dague"
78
+ s.email = "sean@dague.net"
79
79
  end
80
80
 
81
81
  Rake::GemPackageTask.new(spec) do |pkg|
data/docs/api/STUB ADDED
File without changes
data/lib/icalendar.rb CHANGED
@@ -34,3 +34,5 @@ require 'icalendar/component/alarm'
34
34
  # Calendar parser
35
35
  require 'icalendar/parser'
36
36
 
37
+ # TZINFO support
38
+ # require 'icalendar/tzinfo'
@@ -27,9 +27,18 @@ module Icalendar
27
27
 
28
28
  def event(&block)
29
29
  e = Event.new
30
+ # Note: I'm not sure this is the best way to pass this down, but it works
31
+ e.tzid = self.timezones[0].tzid if self.timezones.length > 0
32
+
30
33
  self.add_component e
31
34
 
32
- e.instance_eval &block if block
35
+ if block
36
+ e.instance_eval &block
37
+ if e.tzid
38
+ e.dtstart.ical_params = { "TZID" => e.tzid }
39
+ e.dtend.ical_params = { "TZID" => e.tzid }
40
+ end
41
+ end
33
42
 
34
43
  e
35
44
  end
@@ -145,25 +145,29 @@ module Icalendar
145
145
 
146
146
  # Property value
147
147
  value = ":#{val.to_ical}"
148
- escaped = prelude + value.gsub("\\", "\\\\").gsub("\n", "\\n").gsub(",", "\\,").gsub(";", "\\;")
149
- s << escaped.slice!(0, MAX_LINE_LENGTH) << "\r\n " while escaped.size > MAX_LINE_LENGTH
150
- s << escaped << "\r\n"
151
- s.gsub!(/ *$/, '')
148
+ add_sliced_text(s,prelude+escape_chars(value))
152
149
  else
153
150
  prelude = "#{key.gsub(/_/, '-').upcase}"
154
151
  val.each do |v|
155
152
  params = print_parameters(v)
156
153
  value = ":#{v.to_ical}"
157
- escaped = prelude + params + value.gsub("\\", "\\\\").gsub("\n", "\\n").gsub(",", "\\,").gsub(";", "\\;")
158
- s << escaped.slice!(0, MAX_LINE_LENGTH) << "\r\n " while escaped.size > MAX_LINE_LENGTH
159
- s << escaped << "\r\n"
160
- s.gsub!(/ *$/, '')
154
+ add_sliced_text(s,prelude + params + escape_chars(value))
161
155
  end
162
156
  end
163
157
  end
164
158
  s
165
159
  end
166
160
 
161
+ def escape_chars(value)
162
+ value.gsub("\\", "\\\\").gsub("\n", "\\n").gsub(",", "\\,").gsub(";", "\\;")
163
+ end
164
+
165
+ def add_sliced_text(add_to,escaped)
166
+ escaped = escaped.split('') # split is unicdoe-aware when `$KCODE = 'u'`
167
+ add_to << escaped.slice!(0,MAX_LINE_LENGTH).to_s << "\r\n " while escaped.length != 0 # shift(MAX_LINE_LENGTH) does not work with ruby 1.8.6
168
+ add_to.gsub!(/ *$/, '')
169
+ end
170
+
167
171
  # Print the parameters for a specific property
168
172
  def print_parameters(val)
169
173
  s = ""
@@ -31,7 +31,7 @@ module Icalendar
31
31
 
32
32
  # Multi properties
33
33
  ical_multiline_property :attendee, :attendee, :attendees
34
- ical_multi_property :attach, :attachment, :attachments
34
+ ical_multiline_property :attach, :attachment, :attachments
35
35
 
36
36
  def initialize()
37
37
  super("VALARM")
@@ -27,6 +27,9 @@ module Icalendar
27
27
  # Complete description of the calendar component
28
28
  ical_property :description
29
29
 
30
+ # Specifies the timezone for the event
31
+ attr_accessor :tzid
32
+
30
33
  # Specifies date-time when calendar component begins
31
34
  ical_property :dtstart, :start
32
35
 
@@ -117,7 +120,10 @@ module Icalendar
117
120
 
118
121
  a
119
122
  end
120
-
123
+
124
+ def occurrences_starting(time)
125
+ recurrence_rules.first.occurrences_of_event_starting(self, time)
126
+ end
121
127
 
122
128
  end
123
129
  end
@@ -50,19 +50,37 @@ module Icalendar
50
50
  # of components.
51
51
  def to_ical
52
52
  print_component do
53
- s = ""
53
+ s = ""
54
54
  @components.each_value do |comp|
55
55
  s << comp.to_ical
56
56
  end
57
- s
57
+ s
58
58
  end
59
59
  end
60
-
60
+
61
61
 
62
62
  def initialize(name = "VTIMEZONE")
63
63
  super(name)
64
64
  end
65
+
66
+ # Allow block syntax for declaration of standard and daylight components of timezone
67
+ def standard(&block)
68
+ e = Standard.new
69
+ self.add_component e
65
70
 
71
+ e.instance_eval &block if block
72
+
73
+ e
74
+ end
75
+
76
+ def daylight(&block)
77
+ e = Daylight.new
78
+ self.add_component e
79
+
80
+ e.instance_eval &block if block
81
+
82
+ e
83
+ end
66
84
  end
67
85
 
68
86
  # A Standard component is a sub-component of the Timezone component which
@@ -16,6 +16,12 @@ require 'date'
16
16
  # end
17
17
  # end
18
18
 
19
+ module Icalendar
20
+ module TzidSupport
21
+ attr_accessor :icalendar_tzid
22
+ end
23
+ end
24
+
19
25
  require 'uri/generic'
20
26
 
21
27
  class String
@@ -53,7 +59,10 @@ module URI
53
59
  end
54
60
 
55
61
  class DateTime < Date
56
- def to_ical(utc = false)
62
+ attr_accessor :ical_params
63
+ include Icalendar::TzidSupport
64
+
65
+ def to_ical
57
66
  s = ""
58
67
 
59
68
  # 4 digit year
@@ -82,7 +91,7 @@ class DateTime < Date
82
91
  s << self.sec.to_s
83
92
 
84
93
  # UTC time gets a Z suffix
85
- if utc
94
+ if icalendar_tzid == "UTC"
86
95
  s << "Z"
87
96
  end
88
97
 
@@ -91,6 +100,7 @@ class DateTime < Date
91
100
  end
92
101
 
93
102
  class Date
103
+ attr_accessor :ical_params
94
104
  def to_ical(utc = false)
95
105
  s = ""
96
106
 
@@ -108,6 +118,7 @@ class Date
108
118
  end
109
119
 
110
120
  class Time
121
+ attr_accessor :ical_params
111
122
  def to_ical(utc = false)
112
123
  s = ""
113
124
 
@@ -12,6 +12,101 @@ require 'uri'
12
12
  require 'stringio'
13
13
 
14
14
  module Icalendar
15
+ class RRule
16
+
17
+ class Weekday
18
+ def initialize(day, position)
19
+ @day, @position = day, position
20
+ end
21
+
22
+ def to_s
23
+ "#{@position}#{@day}"
24
+ end
25
+ end
26
+
27
+ def initialize(name, params, value, parser)
28
+ frequency_match = value.match(/FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY)/)
29
+ raise Icalendar::InvalidPropertyValue.new("FREQ must be specified for RRULE values") unless frequency_match
30
+ @frequency = frequency_match[1]
31
+ @until = parse_date_val("UNTIL", value)
32
+ @count = parse_int_val("COUNT", value)
33
+ raise Icalendar::InvalidPropertyValue.new("UNTIL and COUNT must not both be specified for RRULE values") if [@until, @count].compact.length > 1
34
+ @interval = parse_int_val("INTERVAL", value)
35
+ @by_list = {:bysecond => parse_int_list("BYSECOND", value)}
36
+ @by_list[:byminute] = parse_int_list("BYMINUTE",value)
37
+ @by_list[:byhour] = parse_int_list("BYHOUR", value)
38
+ @by_list[:byday] = parse_weekday_list("BYDAY", value)
39
+ @by_list[:bymonthday] = parse_int_list("BYMONTHDAY", value)
40
+ @by_list[:byyearday] = parse_int_list("BYYEARDAY", value)
41
+ @by_list[:byweekno] = parse_int_list("BYWEEKNO", value)
42
+ @by_list[:bymonth] = parse_int_list("BYMONTH", value)
43
+ @by_list[:bysetpos] = parse_int_list("BYSETPOS", value)
44
+ @wkst = parse_wkstart(value)
45
+ end
46
+
47
+ def to_ical
48
+ result = ["FREQ=#{@frequency}"]
49
+ result << ";UNTIL=#{@until.to_ical}" if @until
50
+ result << ";COUNT=#{@count}" if @count
51
+ result << ";INTERVAL=#{@interval}" if @interval
52
+ @by_list.each do |key, value|
53
+ result << ";#{key.to_s.upcase}=#{value}" if value
54
+ end
55
+ result << ";WKST=#{@wkst}" if @wkst
56
+ result.join
57
+ end
58
+
59
+ def parse_date_val(name, string)
60
+ match = string.match(/;#{name}=(.*?)(;|$)/)
61
+ match ? DateTime.parse(match[1]) : nil
62
+ end
63
+
64
+ def parse_int_val(name, string)
65
+ match = string.match(/;#{name}=(\d+)(;|$)/)
66
+ match ? match[1].to_i : nil
67
+ end
68
+
69
+ def parse_int_list(name, string)
70
+ match = string.match(/;#{name}=([+-]?.*?)(;|$)/)
71
+ if match
72
+ match[1].split(",").map {|int| int.to_i}
73
+ else
74
+ nil
75
+ end
76
+ end
77
+
78
+ def parse_weekday_list(name, string)
79
+ match = string.match(/;#{name}=(.*?)(;|$)/)
80
+ if match
81
+ match[1].split(",").map {|weekday|
82
+ wd_match = weekday.match(/([+-]?\d*)(SU|MO|TU|WE|TH|FR|SA)/)
83
+ Weekday.new(wd_match[2], wd_match[1])
84
+ }
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ def parse_wkstart(string)
91
+ match = string.match(/;WKSTART=(SU|MO|TU|WE|TH|FR|SA)(;|$)/)
92
+ if match
93
+ %w{SU MO TU WE TH FR SA}.index(match[1])
94
+ else
95
+ nil
96
+ end
97
+ end
98
+
99
+ # TODO: Incomplete
100
+ def occurrences_of_event_starting(event, datetime)
101
+ initial_start = event.dtstart
102
+ (0...@count).map {|day_offset|
103
+ occurrence = event.clone
104
+ occurrence.dtstart = initial_start + day_offset
105
+ occurrence.clone
106
+ }
107
+ end
108
+ end
109
+
15
110
  def Icalendar.parse(src, single = false)
16
111
  cals = Icalendar::Parser.new(src).parse
17
112
 
@@ -153,6 +248,7 @@ module Icalendar
153
248
 
154
249
  # Lookup the property name to see if we have a string to
155
250
  # object parser for this property type.
251
+ orig_value = value
156
252
  if @parsers.has_key?(name)
157
253
  value = @parsers[name].call(name, params, value)
158
254
  end
@@ -223,7 +319,7 @@ module Icalendar
223
319
  pname = $1
224
320
  pvals = $3
225
321
 
226
- # If their isn't an '=' sign then we need to do some custom
322
+ # If there isn't an '=' sign then we need to do some custom
227
323
  # business. Defaults to 'type'
228
324
  if $2 == ""
229
325
  pvals = $1
@@ -298,6 +394,11 @@ module Icalendar
298
394
  # GEO
299
395
  m = self.method(:parse_geo)
300
396
  @parsers["GEO"] = m
397
+
398
+ #RECUR
399
+ m = self.method(:parse_recur)
400
+ @parsers["RRULE"] = m
401
+ @parsers["EXRULE"] = m
301
402
 
302
403
  end
303
404
 
@@ -317,11 +418,25 @@ module Icalendar
317
418
  # NOTE: invalid dates & times will be returned as strings...
318
419
  def parse_datetime(name, params, value)
319
420
  begin
320
- DateTime.parse(value)
421
+ if params["VALUE"] && params["VALUE"].first == "DATE"
422
+ result = Date.parse(value)
423
+ else
424
+ result = DateTime.parse(value)
425
+ if /Z$/ =~ value
426
+ timezone = "UTC"
427
+ else
428
+ timezone = params["TZID"].first if params["TZID"]
429
+ end
430
+ result.icalendar_tzid = timezone
431
+ end
432
+ result
321
433
  rescue Exception
322
434
  value
323
435
  end
324
-
436
+ end
437
+
438
+ def parse_recur(name, params, value)
439
+ ::Icalendar::RRule.new(name, params, value, self)
325
440
  end
326
441
 
327
442
  # Durations
@@ -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
@@ -3,6 +3,14 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
3
  require 'test/unit'
4
4
  require 'icalendar'
5
5
 
6
+ unless defined? 1.days
7
+ class Integer
8
+ def days
9
+ self * 60 * 60 * 24
10
+ end
11
+ end
12
+ end
13
+
6
14
  # Define a test event
7
15
  testEvent = <<EOS
8
16
  BEGIN:VEVENT
@@ -40,5 +48,209 @@ class TestEvent < Test::Unit::TestCase
40
48
 
41
49
  assert_equal("PRIVATE", event2.ip_class)
42
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
43
130
 
44
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
+
@@ -0,0 +1,67 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
2
+
3
+ require 'test/unit'
4
+ require 'icalendar'
5
+
6
+ class TestTimezone < Test::Unit::TestCase
7
+
8
+ # Create a calendar with an event for each test.
9
+ def setup
10
+ @cal = Icalendar::Calendar.new
11
+ # Define a test timezone
12
+ @testTimezone = %Q(BEGIN:VTIMEZONE\r\nTZID:America/Chicago\r\nBEGIN:STANDARD\r\nTZOFFSETTO:-0600\r\nRRULE:FREQ=YEARLY\\;BYMONTH=11\\;BYDAY=1SU\r\nTZOFFSETFROM:-0500\r\nDTSTART:19701101T020000\r\nTZNAME:CST\r\nEND:STANDARD\r\nBEGIN:DAYLIGHT\r\nTZOFFSETTO:-0500\r\nRRULE:FREQ=YEARLY\\;BYMONTH=3\\;BYDAY=2SU\r\nTZOFFSETFROM:-0600\r\nDTSTART:19700308TO20000\r\nTZNAME:CDT\r\nEND:DAYLIGHT\r\nEND:VTIMEZONE\r\n)
13
+ end
14
+
15
+ def test_new
16
+ @tz = Icalendar::Timezone.new
17
+ assert(@tz)
18
+ end
19
+
20
+ def test_raw_generation
21
+ timezone = Icalendar::Timezone.new
22
+ daylight = Icalendar::Daylight.new
23
+ standard = Icalendar::Standard.new
24
+
25
+ timezone.timezone_id = "America/Chicago"
26
+
27
+ daylight.timezone_offset_from = "-0600"
28
+ daylight.timezone_offset_to = "-0500"
29
+ daylight.timezone_name = "CDT"
30
+ daylight.dtstart = "19700308TO20000"
31
+ daylight.recurrence_rules = ["FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"]
32
+
33
+ standard.timezone_offset_from = "-0500"
34
+ standard.timezone_offset_to = "-0600"
35
+ standard.timezone_name = "CST"
36
+ standard.dtstart = "19701101T020000"
37
+ standard.recurrence_rules = ["FREQ=YEARLY;BYMONTH=11;BYDAY=1SU"]
38
+
39
+ timezone.add(standard)
40
+ timezone.add(daylight)
41
+ @cal.add(timezone)
42
+ assert_equal(@testTimezone, @cal.timezones.first.to_ical)
43
+ end
44
+
45
+ def test_block_creation
46
+ @cal.timezone do
47
+ timezone_id "America/Chicago"
48
+
49
+ daylight do
50
+ timezone_offset_from "-0600"
51
+ timezone_offset_to "-0500"
52
+ timezone_name "CDT"
53
+ dtstart "19700308TO20000"
54
+ add_recurrence_rule "FREQ=YEARLY;BYMONTH=3;BYDAY=2SU"
55
+ end
56
+
57
+ standard do
58
+ timezone_offset_from "-0500"
59
+ timezone_offset_to "-0600"
60
+ timezone_name "CST"
61
+ dtstart "19701101T020000"
62
+ add_recurrence_rule "FREQ=YEARLY;BYMONTH=11;BYDAY=1SU"
63
+ end
64
+ end
65
+ assert_equal(@testTimezone, @cal.timezones.first.to_ical)
66
+ end
67
+ end
@@ -18,7 +18,7 @@ LAST-MODIFIED:19960817T133000
18
18
  SEQUENCE:2
19
19
  ORGANIZER:mailto:joe@example.com?subject=Ruby
20
20
  UID:foobar
21
- X-TIME-OF-DAY:111736
21
+ X-TIME-OF-DAY:101736
22
22
  CATEGORIES:foo\\,bar\\,baz
23
23
  GEO:46.01\\;8.57
24
24
  DESCRIPTION:desc
@@ -60,7 +60,7 @@ EOS
60
60
  timestamp DateTime.parse("2006-07-20T17:40:52+0200")
61
61
 
62
62
  # Time
63
- x_time_of_day Time.at(123456)
63
+ x_time_of_day Time.at(123456).utc
64
64
 
65
65
  uid "foobar"
66
66
  end
File without changes
metadata CHANGED
@@ -1,98 +1,109 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
3
- specification_version: 1
4
2
  name: icalendar
5
3
  version: !ruby/object:Gem::Version
6
- version: 1.0.2
7
- date: 2007-11-25 00:00:00 +01:00
8
- summary: A ruby implementation of the iCalendar specification (RFC-2445).
9
- require_paths:
10
- - lib
11
- email: rosejn@gmail.com
12
- homepage: http://icalendar.rubyforge.org/
13
- rubyforge_project:
14
- description: Implements the iCalendar specification (RFC-2445) in Ruby. This allows for the generation and parsing of .ics files, which are used by a variety of calendaring applications.
15
- autorequire: icalendar
16
- default_executable:
17
- bindir: bin
18
- has_rdoc: true
19
- required_ruby_version: !ruby/object:Gem::Version::Requirement
20
- requirements:
21
- - - ">"
22
- - !ruby/object:Gem::Version
23
- version: 0.0.0
24
- version:
4
+ version: 1.1.0
25
5
  platform: ruby
26
- signing_key:
27
- cert_chain:
28
- post_install_message:
29
6
  authors:
30
- - Jeff Rose
7
+ - Sean Dague
8
+ autorequire: icalendar
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-29 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Implements the iCalendar specification (RFC-2445) in Ruby. This allows for the generation and parsing of .ics files, which are used by a variety of calendaring applications.
17
+ email: sean@dague.net
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - COPYING
25
+ - GPL
31
26
  files:
32
- - test/component
33
- - test/component/event_test.rb
34
- - test/component/todo_test.rb
27
+ - test/calendar_test.rb
35
28
  - test/parameter_test.rb
36
29
  - test/interactive.rb
37
- - test/coverage
30
+ - test/conversions_test.rb
38
31
  - test/component_test.rb
32
+ - test/parser_test.rb
33
+ - test/read_write.rb
39
34
  - test/fixtures
40
- - test/fixtures/simplecal.ics
41
- - test/fixtures/folding.ics
42
35
  - test/fixtures/single_event.ics
36
+ - test/fixtures/folding.ics
37
+ - test/fixtures/simplecal.ics
43
38
  - test/fixtures/life.ics
44
- - test/read_write.rb
45
- - test/calendar_test.rb
46
- - test/parser_test.rb
47
- - test/conversions_test.rb
39
+ - test/component
40
+ - test/component/timezone_test.rb
41
+ - test/component/todo_test.rb
42
+ - test/component/event_test.rb
43
+ - test/coverage
44
+ - test/coverage/STUB
48
45
  - lib/icalendar
46
+ - lib/icalendar/parameter.rb
47
+ - lib/icalendar/component.rb
48
+ - lib/icalendar/base.rb
49
+ - lib/icalendar/parser.rb
50
+ - lib/icalendar/tzinfo.rb
51
+ - lib/icalendar/calendar.rb
49
52
  - lib/icalendar/component
50
53
  - lib/icalendar/component/alarm.rb
54
+ - lib/icalendar/component/todo.rb
51
55
  - lib/icalendar/component/event.rb
52
- - lib/icalendar/component/freebusy.rb
53
56
  - lib/icalendar/component/journal.rb
54
57
  - lib/icalendar/component/timezone.rb
55
- - lib/icalendar/component/todo.rb
58
+ - lib/icalendar/component/freebusy.rb
56
59
  - lib/icalendar/conversions.rb
57
- - lib/icalendar/parameter.rb
58
- - lib/icalendar/component.rb
59
60
  - lib/icalendar/helpers.rb
60
- - lib/icalendar/parser.rb
61
- - lib/icalendar/calendar.rb
62
- - lib/icalendar/base.rb
63
- - lib/hash_attrs.rb
64
- - lib/icalendar.rb
65
61
  - lib/meta.rb
66
- - docs/api
62
+ - lib/icalendar.rb
63
+ - lib/hash_attrs.rb
67
64
  - docs/rfcs
65
+ - docs/rfcs/rfc2446.pdf
66
+ - docs/rfcs/rfc2426.pdf
68
67
  - docs/rfcs/itip_notes.txt
68
+ - docs/rfcs/rfc2447.pdf
69
69
  - docs/rfcs/rfc2425.pdf
70
- - docs/rfcs/rfc2426.pdf
71
70
  - docs/rfcs/rfc2445.pdf
72
- - docs/rfcs/rfc2446.pdf
73
- - docs/rfcs/rfc2447.pdf
74
71
  - docs/rfcs/rfc3283.txt
72
+ - docs/api
73
+ - docs/api/STUB
75
74
  - examples/single_event.ics
76
- - examples/create_cal.rb
77
75
  - examples/parse_cal.rb
76
+ - examples/create_cal.rb
78
77
  - Rakefile
79
78
  - README
80
79
  - COPYING
81
80
  - GPL
82
- test_files: []
83
-
81
+ has_rdoc: true
82
+ homepage: http://icalendar.rubyforge.org/
83
+ post_install_message:
84
84
  rdoc_options:
85
85
  - --main
86
86
  - README
87
- extra_rdoc_files:
88
- - README
89
- - COPYING
90
- - GPL
91
- executables: []
92
-
93
- extensions: []
94
-
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: "0"
94
+ version:
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "0"
100
+ version:
95
101
  requirements: []
96
102
 
97
- dependencies: []
103
+ rubyforge_project:
104
+ rubygems_version: 1.2.0
105
+ signing_key:
106
+ specification_version: 2
107
+ summary: A ruby implementation of the iCalendar specification (RFC-2445).
108
+ test_files: []
98
109