ess 0.9.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![ESS Feed Standard](http://essfeed.org/images/8/87/ESS_logo_32x32.png
|
|
3
3
|
|
4
4
|
[![Build Status](https://travis-ci.org/essfeed/ruby-ess.png)](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
|
|