ess 0.9.3 → 1.0.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.
- data/README.md +78 -5
- data/lib/ess/dtd.rb +11 -1
- data/lib/ess/element.rb +86 -7
- data/lib/ess/ess.rb +298 -0
- data/lib/ess/examples.rb +6 -3
- data/lib/ess/helpers.rb +3 -0
- data/lib/ess/maker.rb +40 -6
- data/lib/ess/parser.rb +31 -0
- data/lib/ess/postprocessing.rb +4 -0
- data/lib/ess/pusher.rb +38 -0
- data/lib/ess/validation.rb +36 -1
- data/lib/ess/version.rb +1 -1
- data/spec/ess/ess_spec.rb +350 -0
- data/spec/ess/full_spec.rb +184 -5
- data/spec/ess/parser_spec.rb +58 -0
- data/spec/spec_helper.rb +1 -0
- metadata +7 -2
data/README.md
CHANGED
@@ -3,13 +3,13 @@ ruby-ess [](https://travis-ci.org/essfeed/ruby-ess)
|
5
5
|
|
6
|
-
Generate ESS XML feeds with Ruby
|
6
|
+
Generate and parse ESS XML feeds with Ruby
|
7
7
|
|
8
8
|
## Installation
|
9
9
|
|
10
10
|
Add this line to your application's Gemfile:
|
11
11
|
|
12
|
-
gem 'ess', '~> 0.
|
12
|
+
gem 'ess', '~> 1.0.0'
|
13
13
|
|
14
14
|
And then execute:
|
15
15
|
|
@@ -17,12 +17,18 @@ And then execute:
|
|
17
17
|
|
18
18
|
Or install it yourself as:
|
19
19
|
|
20
|
-
$ gem install ess -v 0.
|
20
|
+
$ gem install ess -v 1.0.0
|
21
|
+
|
22
|
+
## Information
|
23
|
+
|
24
|
+
* RDoc documentation [available on RubyDoc.info](http://rubydoc.info/gems/ess/frames)
|
25
|
+
* Source code [available on GitHub](http://github.com/essfeed/ruby-ess)
|
21
26
|
|
22
27
|
## Usage
|
23
28
|
|
24
|
-
Producing your own ESS feeds is easy. Here is
|
25
|
-
it's done, with most of the available tags
|
29
|
+
Producing your own ESS feeds is easy. Here is a rather extensive
|
30
|
+
example about how it's done, with most of the available tags
|
31
|
+
available in ESS:
|
26
32
|
|
27
33
|
```ruby
|
28
34
|
|
@@ -411,6 +417,73 @@ can be assigned to the ID tag of a channel or feed, and if it doesn't
|
|
411
417
|
start with "ESSID:" or "EVENTID:", it will be regenerated using that
|
412
418
|
string as the key.
|
413
419
|
|
420
|
+
### Parsing ESS files
|
421
|
+
|
422
|
+
There is also an ESS::Parser class, which has one method "#parse"
|
423
|
+
which accepts a string containing the ESS/XML document, or a File
|
424
|
+
object. It parses the document and returns an ESS::ESS object,
|
425
|
+
which represents the root elements of an ESS document.
|
426
|
+
|
427
|
+
This object has methods which correspond to child tags, and each
|
428
|
+
method retrieves another object which represents a child tag with
|
429
|
+
that name, and which again has methods corresponding to its child
|
430
|
+
elements. I believe it's all easier to understand from an example:
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
|
434
|
+
ess = ESS::Parser.parse(xml_doc)
|
435
|
+
|
436
|
+
# To retrieve a child tag, just use a method with the same name
|
437
|
+
title_tag = ess.channel.title
|
438
|
+
|
439
|
+
# To retrieve a tags text, use the #text! method
|
440
|
+
title_text = title_tag.text!
|
441
|
+
|
442
|
+
# Some tags car be repeated more then once. For that case the tag
|
443
|
+
# objects accept tag_name_list methods, which return a list of child
|
444
|
+
# tags named "tag_name", like this:
|
445
|
+
feeds = ess.channel.feed_list
|
446
|
+
feeds.each do |feed|
|
447
|
+
puts feed.title.text!
|
448
|
+
end
|
449
|
+
|
450
|
+
# For reading attribute values, the objects accept attr_name_attr
|
451
|
+
# methods, for example, to retrieve the value of attribute "type" of
|
452
|
+
# a "item" tag:
|
453
|
+
item = feeds[0].dates.item_list[0]
|
454
|
+
date_item_type = item.type_attr
|
455
|
+
|
456
|
+
```
|
457
|
+
|
458
|
+
#### find_coming and find_between
|
459
|
+
|
460
|
+
To aid web developers who want to display events in a list or a
|
461
|
+
calendar, the ESS::ESS objects returned by the parser have two
|
462
|
+
additional methods: find_coming and find_between.
|
463
|
+
|
464
|
+
find_coming tries to find the next N events for your event list.
|
465
|
+
It returns a list of hashes, each hash representing one item for
|
466
|
+
the list. The hash contains the :time and :feed keys, the former
|
467
|
+
being the start time of the event, and the later the complete
|
468
|
+
feed describing the whole event.
|
469
|
+
|
470
|
+
find_coming accepts two parameters. The first parameter is the
|
471
|
+
number of events it should return and the default is 10. The second
|
472
|
+
parameter defaults to Time.now, and the method with return N events
|
473
|
+
starting from the moment specified.
|
474
|
+
|
475
|
+
find_between returns a list of all events between the two moments
|
476
|
+
in time, which as specified as parameters with Time objects. For
|
477
|
+
example, to retrieve all events in June 2013, run this:
|
478
|
+
|
479
|
+
```ruby
|
480
|
+
|
481
|
+
start_time = "2013-06-01 00:00"
|
482
|
+
end_time = "2013-06-30 24:00"
|
483
|
+
events = ess.find_between(start_time, end_time)
|
484
|
+
|
485
|
+
```
|
486
|
+
|
414
487
|
## Contributing
|
415
488
|
|
416
489
|
1. Fork it
|
data/lib/ess/dtd.rb
CHANGED
@@ -3,6 +3,10 @@ require 'ess/validation'
|
|
3
3
|
|
4
4
|
module ESS
|
5
5
|
module DTD
|
6
|
+
##
|
7
|
+
# The information in this module should not be used directly, it is
|
8
|
+
# intended for the document builder and parser.
|
9
|
+
#
|
6
10
|
include ESS::Postprocessing
|
7
11
|
include ESS::Validation
|
8
12
|
|
@@ -327,7 +331,7 @@ module ESS
|
|
327
331
|
:country_code => { :dtd => COUNTRY_CODE,
|
328
332
|
:mandatory => false,
|
329
333
|
:max_occurs => 1 },
|
330
|
-
:email => { :dtd =>
|
334
|
+
:email => { :dtd => EMAIL,
|
331
335
|
:mandatory => false,
|
332
336
|
:max_occurs => 1 },
|
333
337
|
:phone => { :dtd => BASIC_ELEMENT,
|
@@ -480,6 +484,12 @@ module ESS
|
|
480
484
|
}
|
481
485
|
|
482
486
|
class InvalidValueError < RuntimeError
|
487
|
+
##
|
488
|
+
# This exception is generated when the builder or parser
|
489
|
+
# receive a value which is not valid for a tag or attribute.
|
490
|
+
# It should contain a message describing what was the value which
|
491
|
+
# caused it to be raised.
|
492
|
+
#
|
483
493
|
end
|
484
494
|
end
|
485
495
|
end
|
data/lib/ess/element.rb
CHANGED
@@ -4,27 +4,61 @@ require 'ess/pusher'
|
|
4
4
|
|
5
5
|
module ESS
|
6
6
|
class Element
|
7
|
+
##
|
8
|
+
# Objects of this class represent the various tags available in ESS
|
9
|
+
#
|
10
|
+
|
11
|
+
|
7
12
|
include ESS::Helpers
|
8
13
|
|
9
|
-
|
14
|
+
##
|
15
|
+
# Returns the dictionary describing this tag.
|
16
|
+
#
|
17
|
+
def dtd
|
18
|
+
return @dtd
|
19
|
+
end
|
10
20
|
|
21
|
+
##
|
22
|
+
# There should never be a need to create an Element object yourself,
|
23
|
+
# except if you're the developer of this library. Use ESS::Maker.make
|
24
|
+
# if you need to create a new ESS document.
|
25
|
+
#
|
26
|
+
# === Parameters
|
27
|
+
#
|
28
|
+
# [name] a symbol, the name of the tag being created
|
29
|
+
# [dtd] a hash describing the tag
|
30
|
+
#
|
11
31
|
def initialize name, dtd
|
12
32
|
@name, @dtd = name, dtd
|
13
33
|
end
|
14
34
|
|
35
|
+
##
|
36
|
+
# Return the name of the tag as a symbol.
|
37
|
+
#
|
15
38
|
def name!
|
16
39
|
@name
|
17
40
|
end
|
18
41
|
|
42
|
+
##
|
43
|
+
# Returns or sets the text contained in this tag
|
44
|
+
#
|
19
45
|
def text! text=nil
|
20
46
|
return @text ||= "" if text.nil?
|
21
47
|
@text = do_text_postprocessing_of text
|
22
48
|
end
|
23
49
|
|
50
|
+
##
|
51
|
+
# Returns a short description of this object.
|
52
|
+
#
|
24
53
|
def inspect
|
25
54
|
"#<#{self.class}:#{object_id} text=\"#{@text}\">"
|
26
55
|
end
|
27
56
|
|
57
|
+
##
|
58
|
+
# Validates the tag according to its DTD and all child tags. Throws an
|
59
|
+
# ESS::Validation::ValidationError exception in case the document is
|
60
|
+
# incomplete or an invalid value if found.
|
61
|
+
#
|
28
62
|
def validate
|
29
63
|
run_tag_validators
|
30
64
|
check_attributes
|
@@ -32,6 +66,10 @@ module ESS
|
|
32
66
|
return nil # if no errors found, i.e. no exceptions have been raised
|
33
67
|
end
|
34
68
|
|
69
|
+
##
|
70
|
+
# Same as #validate, but returns false if an error is found, instead of
|
71
|
+
# throwing exceptions.
|
72
|
+
#
|
35
73
|
def valid?
|
36
74
|
begin
|
37
75
|
validate
|
@@ -41,6 +79,11 @@ module ESS
|
|
41
79
|
return true
|
42
80
|
end
|
43
81
|
|
82
|
+
##
|
83
|
+
# Returns the feed as an XML document in a string. An Builder::XmlMarkup
|
84
|
+
# object can be passed as an argument and used as output, instead of
|
85
|
+
# generating a string object.
|
86
|
+
#
|
44
87
|
def to_xml! xml=nil
|
45
88
|
convert_to_string = true if xml.nil?
|
46
89
|
xml = Builder::XmlMarkup.new if xml.nil?
|
@@ -61,16 +104,48 @@ module ESS
|
|
61
104
|
xml.target! if convert_to_string
|
62
105
|
end
|
63
106
|
|
107
|
+
##
|
108
|
+
# A convenience method for pushing the current document to aggregators.
|
109
|
+
# It calls the ESS::Pusher.push_to_aggregators method and passes all
|
110
|
+
# options to it.
|
111
|
+
#
|
64
112
|
def push_to_aggregators options={}
|
65
113
|
raise RuntimeError, "only ESS root element can be pushed to aggregators" if @name != :ess
|
66
114
|
options[:data] = self.to_xml!
|
67
115
|
Pusher::push_to_aggregators options
|
68
116
|
end
|
69
117
|
|
118
|
+
##
|
119
|
+
# Same as #to_xml!, but accepts no arguments.
|
120
|
+
#
|
70
121
|
def to_s
|
71
122
|
to_xml!
|
72
123
|
end
|
73
124
|
|
125
|
+
##
|
126
|
+
# Disables postprocessing of tag values.
|
127
|
+
#
|
128
|
+
def disable_postprocessing
|
129
|
+
@@postprocessing_disabled = true
|
130
|
+
end
|
131
|
+
|
132
|
+
##
|
133
|
+
# Enables postprocessing of tag values.
|
134
|
+
def enable_postprocessing
|
135
|
+
@@postprocessing_disabled = false
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Returns true if postprocessing has been disabled.
|
140
|
+
#
|
141
|
+
def postprocessing_disabled?
|
142
|
+
@@postprocessing_disabled ||= false
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# Handles methods corresponding to a tag name, ending with either
|
147
|
+
# _list or _attr, or starting with add_ .
|
148
|
+
#
|
74
149
|
def method_missing m, *args, &block
|
75
150
|
if method_name_is_tag_name? m
|
76
151
|
return assign_tag(m, args, &block)
|
@@ -156,8 +231,10 @@ module ESS
|
|
156
231
|
end
|
157
232
|
|
158
233
|
def run_post_processing tag
|
159
|
-
|
160
|
-
@dtd[:tags][tag.name!]
|
234
|
+
unless postprocessing_disabled?
|
235
|
+
if @dtd[:tags][tag.name!].keys.include? :postprocessing
|
236
|
+
@dtd[:tags][tag.name!][:postprocessing].each { |processor| processor.process(self, tag) }
|
237
|
+
end
|
161
238
|
end
|
162
239
|
end
|
163
240
|
|
@@ -170,10 +247,12 @@ module ESS
|
|
170
247
|
end
|
171
248
|
|
172
249
|
def do_text_postprocessing_of text
|
173
|
-
|
174
|
-
|
175
|
-
@dtd
|
176
|
-
|
250
|
+
unless postprocessing_disabled?
|
251
|
+
text = text.to_s if text.class != String
|
252
|
+
if @dtd.include? :postprocessing_text
|
253
|
+
@dtd[:postprocessing_text].each do |processor|
|
254
|
+
text = processor.process text
|
255
|
+
end
|
177
256
|
end
|
178
257
|
end
|
179
258
|
text
|
data/lib/ess/ess.rb
CHANGED
@@ -1,8 +1,306 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
1
3
|
module ESS
|
2
4
|
class ESS < Element
|
3
5
|
def initialize
|
4
6
|
super :ess, DTD::ESS
|
5
7
|
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# Returns the next n events from the moment specified in the second
|
11
|
+
# optional argument.
|
12
|
+
#
|
13
|
+
# === Parameters
|
14
|
+
#
|
15
|
+
# [n = 10] how many coming events should be returned
|
16
|
+
# [start_time] only events hapenning after this time will be considered
|
17
|
+
#
|
18
|
+
# === Returns
|
19
|
+
#
|
20
|
+
# A list of hashes, sorted by event start time, each hash having
|
21
|
+
# two keys:
|
22
|
+
#
|
23
|
+
# [:time] start time of the event
|
24
|
+
# [:feed] feed describing the event
|
25
|
+
#
|
26
|
+
def find_coming n=10, start_time=nil
|
27
|
+
start_time = Time.now if start_time.nil?
|
28
|
+
feeds = []
|
29
|
+
channel.feed_list.each do |feed|
|
30
|
+
feed.dates.item_list.each do |item|
|
31
|
+
if item.type_attr == "standalone"
|
32
|
+
feeds << { :time => Time.parse(item.start.text!), :feed => feed }
|
33
|
+
elsif item.type_attr == "recurrent"
|
34
|
+
moments = parse_recurrent_date_item(item, n, start_time)
|
35
|
+
moments.each do |moment|
|
36
|
+
feeds << { :time => moment, :feed => feed }
|
37
|
+
end
|
38
|
+
elsif item.type_attr == "permanent"
|
39
|
+
start = Time.parse(item.start.text!)
|
40
|
+
if start > start_time
|
41
|
+
feeds << { :time => start, :feed => feed }
|
42
|
+
else
|
43
|
+
feeds << { :time => start_time, :feed => feed }
|
44
|
+
end
|
45
|
+
else
|
46
|
+
raise DTD::InvalidValueError, "the \"#{item.type_attr}\" is not valid for a date item type attribute"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
feeds = feeds.delete_if { |x| x[:time] < start_time }
|
51
|
+
feeds.sort! { |x, y| x[:time] <=> y[:time] }
|
52
|
+
return feeds[0..n-1]
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Returns all events starting after the time specified by the first
|
57
|
+
# parameter and before the time specified by the second parameter,
|
58
|
+
# which accept regular Time objects.
|
59
|
+
#
|
60
|
+
# === Parameters
|
61
|
+
#
|
62
|
+
# [start_time] will return only events starting after this moment
|
63
|
+
# [end_time] will return only events starting before this moment
|
64
|
+
#
|
65
|
+
# === Returns
|
66
|
+
#
|
67
|
+
# A list of hashes, sorted by event start time, each hash having
|
68
|
+
# two keys:
|
69
|
+
#
|
70
|
+
# [:time] start time of the event
|
71
|
+
# [:feed] feed describing the event
|
72
|
+
#
|
73
|
+
def find_between start_time, end_time
|
74
|
+
feeds = []
|
75
|
+
channel.feed_list.each do |feed|
|
76
|
+
feed.dates.item_list.each do |item|
|
77
|
+
if item.type_attr == "standalone"
|
78
|
+
feed_start_time = Time.parse(item.start.text!)
|
79
|
+
if feed_start_time.between?(start_time, end_time)
|
80
|
+
feeds << { :time => feed_start_time, :feed => feed }
|
81
|
+
end
|
82
|
+
elsif item.type_attr == "recurrent"
|
83
|
+
moments = parse_recurrent_date_item(item, end_time, start_time)
|
84
|
+
moments.each do |moment|
|
85
|
+
if moment.between?(start_time, end_time)
|
86
|
+
feeds << { :time => moment, :feed => feed }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
elsif item.type_attr == "permanent"
|
90
|
+
start = Time.parse(item.start.text!)
|
91
|
+
unless start > end_time
|
92
|
+
if start > start_time
|
93
|
+
feeds << { :time => start, :feed => feed }
|
94
|
+
else
|
95
|
+
feeds << { :time => start_time, :feed => feed }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
else
|
99
|
+
raise DTD::InvalidValueError, "the \"#{item.type_attr}\" is not valid for a date item type attribute"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
feeds.sort! { |x, y| x[:time] <=> y[:time] }
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
WEEK_DAYS = {
|
109
|
+
'monday' => 1,
|
110
|
+
'tuesday' => 2,
|
111
|
+
'wednesday' => 3,
|
112
|
+
'thursday' => 4,
|
113
|
+
'friday' => 5,
|
114
|
+
'saturday' => 6,
|
115
|
+
'sunday' => 7
|
116
|
+
}
|
117
|
+
|
118
|
+
INC_FUNCS = {
|
119
|
+
"year" => lambda { |time| inc_year(time) },
|
120
|
+
"month" => lambda { |time| inc_month(time) },
|
121
|
+
"week" => lambda { |time| inc_week(time) },
|
122
|
+
"day" => lambda { |time| inc_day(time) },
|
123
|
+
"hour" => lambda { |time| inc_hour(time) }
|
124
|
+
}
|
125
|
+
|
126
|
+
def parse_recurrent_date_item item, n_or_end_date, start_time
|
127
|
+
current = first = Time.parse(item.start.text!)
|
128
|
+
inc_period_func = INC_FUNCS[item.unit_attr || "hour"]
|
129
|
+
interval = (item.interval_attr == "") ? 1 : item.interval_attr.to_i
|
130
|
+
all = []
|
131
|
+
if item.limit_attr.length == 0
|
132
|
+
break_func = (n.class == FixNum) ? lambda { all.length >= n_or_end_date } : lambda { current > n_or_end_date }
|
133
|
+
while true
|
134
|
+
parse_unit(item, current, all)
|
135
|
+
interval.times do current = inc_period_func.call(current) end
|
136
|
+
all = all.delete_if { |x| x[:time] < start_time }
|
137
|
+
break if break_func.call
|
138
|
+
end
|
139
|
+
else
|
140
|
+
item.limit_attr.to_i.times do
|
141
|
+
parse_unit(item, current, all)
|
142
|
+
interval.times do current = inc_period_func.call(current) end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
all = all.delete_if { |time| time < start_time || time < first }
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse_unit item, current, all
|
149
|
+
case item.unit_attr.downcase
|
150
|
+
when "year"
|
151
|
+
all << current
|
152
|
+
when "month"
|
153
|
+
weeks = item.selected_week_attr.downcase.split(",")
|
154
|
+
days = item.selected_day_attr.downcase.split(",")
|
155
|
+
if weeks.any?
|
156
|
+
if days.none?
|
157
|
+
days = WEEK_DAYS.keys
|
158
|
+
end
|
159
|
+
end
|
160
|
+
if weeks.none?
|
161
|
+
if days.any?
|
162
|
+
weeks = ["first", "second", "third", "fourth", "last"]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
if days.any?
|
166
|
+
days.each do |day|
|
167
|
+
if WEEK_DAYS.include? day
|
168
|
+
parse_month_week_day(day, current, weeks, all)
|
169
|
+
elsif day.to_i.to_s == day
|
170
|
+
parse_month_day(day, current, all)
|
171
|
+
else
|
172
|
+
raise InvalidValueError, "the \"#{day}\" value is not valid for a date item selected_day attribute"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
else
|
176
|
+
all << current
|
177
|
+
end
|
178
|
+
when "week"
|
179
|
+
days = item.selected_day_attr.split(",")
|
180
|
+
if days.none?
|
181
|
+
all << current
|
182
|
+
else
|
183
|
+
days.each { |day| parse_week_day(day, current, all) }
|
184
|
+
end
|
185
|
+
when "day"
|
186
|
+
all << current
|
187
|
+
when "hour"
|
188
|
+
all << current
|
189
|
+
else
|
190
|
+
raise InvalidValueError, "the \"#{item.unit_attr}\" is not valid for a date item unit attribute"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def parse_month_week_day day, current, weeks, all
|
195
|
+
weeks.each do |week|
|
196
|
+
case week
|
197
|
+
when "first"
|
198
|
+
current = change_time(current, :day => 1)
|
199
|
+
when "second"
|
200
|
+
current = change_time(current, :day => 8)
|
201
|
+
when "third"
|
202
|
+
current = change_time(current, :day => 15)
|
203
|
+
when "fourth"
|
204
|
+
current = change_time(current, :day => 22)
|
205
|
+
when "last"
|
206
|
+
current = change_time(current, :day => (days_in_month(current) - 6))
|
207
|
+
else
|
208
|
+
raise InvalidValueError, "the \"#{item.unit_attr}\" is not valid for a date item unit attribute"
|
209
|
+
end
|
210
|
+
all << change_time(current, :day => ((7+ WEEK_DAYS[day] - current.wday) % 7 + current.day))
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def change_time time, options
|
215
|
+
sec = options[:sec] || time.sec
|
216
|
+
min = options[:min] || time.min
|
217
|
+
hour = options[:hour] || time.hour
|
218
|
+
day = options[:day] || time.day
|
219
|
+
month = options[:month] || time.month
|
220
|
+
year = options[:year] || time.year
|
221
|
+
time.class.new(year, month, day, hour, min, sec, time.utc_offset)
|
222
|
+
end
|
223
|
+
|
224
|
+
def days_in_month time
|
225
|
+
self.class.days_in_month time
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.days_in_month time
|
229
|
+
case time.month
|
230
|
+
when 1, 3, 5, 7, 8, 10, 12
|
231
|
+
31
|
232
|
+
when 2
|
233
|
+
if time.year % 4 == 0
|
234
|
+
29
|
235
|
+
else
|
236
|
+
28
|
237
|
+
end
|
238
|
+
when 4, 6, 9, 11
|
239
|
+
30
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def parse_month_day day, current, all
|
244
|
+
all << change_time(current, :day => day)
|
245
|
+
end
|
246
|
+
|
247
|
+
def parse_week_day day, current, all
|
248
|
+
day = day.downcase
|
249
|
+
current_wday = current.wday
|
250
|
+
current_wday = 7 if current_wday == 0
|
251
|
+
if WEEK_DAYS.keys.include? day
|
252
|
+
next_day = current.day + WEEK_DAYS[day] - current_wday
|
253
|
+
elsif day.to_i.to_s == day
|
254
|
+
next_day = current.day + day.to_i - current_wday
|
255
|
+
else
|
256
|
+
raise InvalidValueError, "the \"#{day}\" value is not valid for a date item selected_day attribute"
|
257
|
+
end
|
258
|
+
month = current.month
|
259
|
+
if next_day > days_in_month(current)
|
260
|
+
next_day -= days_in_month(current)
|
261
|
+
month += 1
|
262
|
+
end
|
263
|
+
year = current.year
|
264
|
+
if month > 12
|
265
|
+
month -= 12
|
266
|
+
year += 1
|
267
|
+
end
|
268
|
+
all << change_time(current, :year => year, :month => month, :day => next_day)
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.inc_year time
|
272
|
+
if time.year % 4 == 0
|
273
|
+
time + 60*60*24*366
|
274
|
+
else
|
275
|
+
time + 60*60*24*365
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def self.inc_month time
|
280
|
+
increment = nil
|
281
|
+
if time.month == 2
|
282
|
+
if time.year % 4 == 0
|
283
|
+
increment = 60*60*24*29
|
284
|
+
else
|
285
|
+
increment = 60*60*24*28
|
286
|
+
end
|
287
|
+
else
|
288
|
+
increment = 60*60*24*days_in_month(time)
|
289
|
+
end
|
290
|
+
time + increment
|
291
|
+
end
|
292
|
+
|
293
|
+
def self.inc_week time
|
294
|
+
time + 60*60*24*7
|
295
|
+
end
|
296
|
+
|
297
|
+
def self.inc_day
|
298
|
+
time + 60*60*24
|
299
|
+
end
|
300
|
+
|
301
|
+
def self.inc_hour
|
302
|
+
kime + 3600
|
303
|
+
end
|
6
304
|
end
|
7
305
|
end
|
8
306
|
|