icalPal 3.3.0 → 3.5.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.
- checksums.yaml +4 -4
- data/README.md +45 -25
- data/bin/icalPal +30 -89
- data/bin/icalpal +30 -89
- data/icalPal.gemspec +11 -9
- data/lib/ToICalPal.rb +24 -27
- data/lib/defaults.rb +14 -6
- data/lib/event.rb +99 -78
- data/lib/icalPal.rb +18 -11
- data/lib/options.rb +58 -13
- data/lib/rdt.rb +22 -4
- data/lib/utils.rb +0 -7
- data/lib/version.rb +1 -1
- metadata +23 -2
data/lib/ToICalPal.rb
CHANGED
@@ -6,25 +6,25 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
|
|
6
6
|
# ANSI[https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-T.416-199303-I!!PDF-E&type=items]
|
7
7
|
# colors
|
8
8
|
ANSI = {
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
black: 30, '#000000': '38;5;0',
|
10
|
+
red: 31, '#ff0000': '38;5;1',
|
11
|
+
green: 32, '#00ff00': '38;5;2',
|
12
|
+
yellow: 33, '#ffff00': '38;5;3',
|
13
|
+
blue: 34, '#0000ff': '38;5;4',
|
14
|
+
magenta: 35, '#ff00ff': '38;5;5',
|
15
|
+
cyan: 36, '#00ffff': '38;5;6',
|
16
|
+
white: 37, '#ffffff': '38;5;255',
|
17
|
+
default: 39, custom: nil,
|
18
18
|
|
19
19
|
# Reminders custom colors
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
20
|
+
brown: '38;2;162;132;94',
|
21
|
+
gray: '38;2;91;98;106',
|
22
|
+
indigo: '38;2;88;86;214',
|
23
|
+
lightblue: '38;2;90;200;250',
|
24
|
+
orange: '38;2;255;149;0',
|
25
|
+
pink: '38;2;255;45;85',
|
26
|
+
purple: '38;2;204;115;225',
|
27
|
+
rose: '38;2;217;166;159',
|
28
28
|
}.freeze
|
29
29
|
|
30
30
|
# Increased intensity
|
@@ -55,7 +55,7 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
|
|
55
55
|
# @option opts [Array<String>] :ps List of property separators
|
56
56
|
# @option opts [String] :ss Section separator
|
57
57
|
def initialize(opts)
|
58
|
-
super
|
58
|
+
super
|
59
59
|
@opts = opts
|
60
60
|
end
|
61
61
|
|
@@ -76,10 +76,7 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
|
|
76
76
|
def accept_list_start(_arg)
|
77
77
|
begin
|
78
78
|
return if @item['placeholder']
|
79
|
-
rescue
|
80
|
-
end
|
81
79
|
|
82
|
-
begin
|
83
80
|
if (@item['due_date'] + ICalPal::ITIME).between?(ICalPal::ITIME + 1, $now.to_i)
|
84
81
|
@res << "#{@opts[:ab]} " unless @opts[:nb]
|
85
82
|
return
|
@@ -95,7 +92,7 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
|
|
95
92
|
# @param arg [RDoc::Markup::ListItem]
|
96
93
|
# @option arg [String] .label Contains the property name
|
97
94
|
def accept_list_item_start(arg)
|
98
|
-
@res << @opts[:ps][@ps] || ' ' unless @item['placeholder']
|
95
|
+
@res << (@opts[:ps][@ps] || ' ') unless @item['placeholder']
|
99
96
|
@res << colorize(*LABEL_COLOR, arg.label) << ': ' unless @opts[:npn] || NO_LABEL.any?(arg.label)
|
100
97
|
|
101
98
|
@ps += 1 unless @ps == @opts[:ps].count - 1
|
@@ -133,7 +130,7 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
|
|
133
130
|
# @param p [RDoc::Markup::Paragraph]
|
134
131
|
# @option p [Array<String>] :parts The property's text
|
135
132
|
def accept_paragraph(p)
|
136
|
-
t = p.parts.join('; ').gsub(
|
133
|
+
t = p.parts.join('; ').gsub("\n", "\n ")
|
137
134
|
t = colorize(*DATE_COLOR, t) if @prop == 'datetime'
|
138
135
|
@res << t
|
139
136
|
end
|
@@ -196,10 +193,10 @@ class RDoc::Markup::ToICalPal < RDoc::Markup::Formatter
|
|
196
193
|
|
197
194
|
# @!visibility private
|
198
195
|
|
199
|
-
# @param
|
200
|
-
def accept_list_end(
|
196
|
+
# @param _a [Array] Ignored
|
197
|
+
def accept_list_end(_a) end
|
201
198
|
|
202
|
-
# @param
|
203
|
-
def accept_list_item_end(
|
199
|
+
# @param _a [Array] Ignored
|
200
|
+
def accept_list_item_end(_a) end
|
204
201
|
|
205
202
|
end
|
data/lib/defaults.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Does anybody really know what time it is?
|
2
|
-
|
3
|
-
$
|
2
|
+
now = Time.now
|
3
|
+
$now = ICalPal::RDT.from_time(now)
|
4
|
+
$today = ICalPal::RDT.new(*$now.to_a[0..2] + [ 0, 0, 0 ])
|
4
5
|
|
5
6
|
# Defaults
|
6
7
|
$defaults = {
|
@@ -8,11 +9,11 @@ $defaults = {
|
|
8
9
|
ab: '!',
|
9
10
|
aep: [],
|
10
11
|
bullet: '•',
|
11
|
-
cf: "#{
|
12
|
+
cf: "#{Dir.home}/.icalpal",
|
12
13
|
color: false,
|
13
14
|
db: [
|
14
|
-
"#{
|
15
|
-
"#{
|
15
|
+
"#{Dir.home}/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb",
|
16
|
+
"#{Dir.home}/Library/Calendars/Calendar.sqlitedb",
|
16
17
|
],
|
17
18
|
debug: Logger::WARN,
|
18
19
|
df: '%b %-d, %Y',
|
@@ -26,6 +27,7 @@ $defaults = {
|
|
26
27
|
is: [],
|
27
28
|
it: [],
|
28
29
|
li: 0,
|
30
|
+
norc: false,
|
29
31
|
output: 'default',
|
30
32
|
ps: [ "\n " ],
|
31
33
|
r: false,
|
@@ -37,32 +39,38 @@ $defaults = {
|
|
37
39
|
sp: false,
|
38
40
|
tf: '%-I:%M %p',
|
39
41
|
},
|
42
|
+
|
40
43
|
tasks: {
|
41
44
|
dated: 0,
|
42
45
|
db: [ ICalPal::Reminder::DB_PATH ],
|
43
46
|
iep: %w[ title notes due priority ],
|
44
47
|
sort: 'prio',
|
45
48
|
},
|
49
|
+
|
46
50
|
undatedTasks: {
|
47
51
|
dated: 1,
|
48
52
|
db: [ ICalPal::Reminder::DB_PATH ],
|
49
53
|
iep: %w[ title notes due priority ],
|
50
54
|
sort: 'prio',
|
51
55
|
},
|
56
|
+
|
52
57
|
datedTasks: {
|
53
58
|
dated: 2,
|
54
59
|
db: [ ICalPal::Reminder::DB_PATH ],
|
55
60
|
iep: %w[ title notes due priority ],
|
56
61
|
sort: 'prio',
|
57
62
|
},
|
63
|
+
|
58
64
|
stores: {
|
59
65
|
iep: %w[ account type ],
|
60
66
|
sort: 'account',
|
61
67
|
},
|
68
|
+
|
62
69
|
calendars: {
|
63
70
|
iep: %w[ calendar type UUID ],
|
64
71
|
sort: 'calendar',
|
65
72
|
},
|
73
|
+
|
66
74
|
events: {
|
67
75
|
days: nil,
|
68
76
|
ea: false,
|
@@ -77,7 +85,7 @@ $defaults = {
|
|
77
85
|
ps: [ "\n " ],
|
78
86
|
sa: false,
|
79
87
|
sed: false,
|
80
|
-
sort: '
|
88
|
+
sort: 'sctime',
|
81
89
|
ss: "\n------------------------",
|
82
90
|
to: nil,
|
83
91
|
uid: false,
|
data/lib/event.rb
CHANGED
@@ -1,18 +1,20 @@
|
|
1
|
-
require '
|
1
|
+
require 'timezone'
|
2
2
|
|
3
3
|
module ICalPal
|
4
4
|
# Class representing items from the <tt>CalendarItem</tt> table
|
5
5
|
class Event
|
6
6
|
include ICalPal
|
7
7
|
|
8
|
-
# Standard accessor with special handling for +sdate+. Setting
|
9
|
-
#
|
8
|
+
# Standard accessor with special handling for +sdate+ and +edate+. Setting
|
9
|
+
# those will also set +sctime+ and +ectime+ respectively.
|
10
10
|
#
|
11
11
|
# @param k [String] Key/property name
|
12
12
|
# @param v [Object] Key/property value
|
13
13
|
def []=(k, v)
|
14
14
|
@self[k] = v
|
15
|
-
|
15
|
+
|
16
|
+
@self['sctime'] = Time.at(@self['sdate'].to_i, in: 'UTC') if k == 'sdate'
|
17
|
+
@self['ectime'] = Time.at(@self['edate'].to_i, in: 'UTC') if k == 'edate'
|
16
18
|
end
|
17
19
|
|
18
20
|
# Standard accessor with special handling for +age+,
|
@@ -34,10 +36,10 @@ module ICalPal
|
|
34
36
|
t += ' at ' unless @self['all_day'].positive?
|
35
37
|
end
|
36
38
|
|
37
|
-
unless @self['all_day'] && @self['all_day'].positive? || @self['placeholder']
|
39
|
+
unless (@self['all_day'] && @self['all_day'].positive?) || @self['placeholder']
|
38
40
|
t ||= ''
|
39
|
-
t += "#{@self['
|
40
|
-
t += " - #{@self['
|
41
|
+
t += "#{@self['sctime'].strftime($opts[:tf])}" if @self['sctime']
|
42
|
+
t += " - #{@self['ectime'].strftime($opts[:tf])}" unless $opts[:eed] || !@self['ectime'] || @self['duration'].zero?
|
41
43
|
end
|
42
44
|
t
|
43
45
|
|
@@ -45,10 +47,10 @@ module ICalPal
|
|
45
47
|
(@self['location'])? [ @self['location'], @self['address'] ].join(' ').chop : nil
|
46
48
|
|
47
49
|
when 'notes' # \n -> :nnr
|
48
|
-
(@self['notes'])? @self['notes'].strip.gsub(
|
50
|
+
(@self['notes'])? @self['notes'].strip.gsub("\n", $opts[:nnr]) : nil
|
49
51
|
|
50
52
|
when 'sday' # pseudo-property
|
51
|
-
|
53
|
+
RDT.new(*@self['sdate'].to_a[0..2])
|
52
54
|
|
53
55
|
when 'status' # Integer -> String
|
54
56
|
EventKit::EKEventStatus.select { |_k, v| v == @self['status'] }.keys[0]
|
@@ -83,23 +85,24 @@ module ICalPal
|
|
83
85
|
|
84
86
|
# Convert JSON arrays to Arrays
|
85
87
|
@self['attendees'] = JSON.parse(obj['attendees'])
|
86
|
-
# rubocop: disable Lint/UselessAssignment
|
87
88
|
@self['xdate'] = JSON.parse(obj['xdate']).map do |k|
|
88
|
-
|
89
|
+
RDT.from_itime(k) if k
|
89
90
|
end
|
90
|
-
# rubocop: enable Lint/UselessAssignment
|
91
91
|
|
92
92
|
# Convert iCal dates to normal dates
|
93
93
|
obj.keys.select { |i| i.end_with? '_date' }.each do |k|
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
next unless obj[k]
|
95
|
+
|
96
|
+
begin
|
97
|
+
zone = Timezone.fetch(obj['start_tz'])
|
98
|
+
rescue Timezone::Error::InvalidZone
|
99
|
+
zone = 'UTC'
|
100
|
+
end
|
97
101
|
|
98
|
-
|
99
|
-
tzoffset = Time.zone_offset($now.zone)
|
102
|
+
ctime = obj[k] + ITIME
|
100
103
|
|
101
|
-
@self[
|
102
|
-
@self[
|
104
|
+
@self["#{k[0]}ctime"] = Time.at(ctime)
|
105
|
+
@self["#{k[0]}date"] = RDT.from_time(Time.at(ctime, in: zone))
|
103
106
|
end
|
104
107
|
|
105
108
|
# Type of calendar event is from
|
@@ -123,14 +126,17 @@ module ICalPal
|
|
123
126
|
# Sanity checks
|
124
127
|
return events if nDays > 100_000
|
125
128
|
|
129
|
+
# If multi-day, each (unique) day needs to end at 23:59:59
|
130
|
+
self['edate'] = RDT.new(*@self['sdate'].to_a[0..2] + [ 23, 59, 59 ]) if nDays.positive?
|
131
|
+
|
126
132
|
# Repeat for multi-day events
|
127
133
|
(nDays + 1).times do |i|
|
128
134
|
break if self['sdate'] > $opts[:to]
|
129
135
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
136
|
+
if in_window?(self['sdate'], self['edate'])
|
137
|
+
self['daynum'] = i + 1 if nDays.positive?
|
138
|
+
events.push(clone)
|
139
|
+
end
|
134
140
|
|
135
141
|
self['sdate'] += 1
|
136
142
|
self['edate'] += 1
|
@@ -145,78 +151,76 @@ module ICalPal
|
|
145
151
|
# All occurrences of a recurring event that are within our window
|
146
152
|
def recurring
|
147
153
|
stop = [ $opts[:to], (self['rdate'] || $opts[:to]) ].min
|
154
|
+
events = []
|
155
|
+
count = 1
|
148
156
|
|
149
157
|
# See if event ends before we start
|
150
|
-
|
151
|
-
$log.debug("#{stop} < #{$opts[:from]}")
|
152
|
-
return([])
|
153
|
-
end
|
158
|
+
return events if $opts[:from] > stop
|
154
159
|
|
155
160
|
# Get changes to series
|
156
|
-
changes = [ {
|
161
|
+
changes = [ { orig_date: -1 } ]
|
157
162
|
changes += $rows.select { |r| r['orig_item_id'] == self['ROWID'] }
|
158
163
|
|
159
|
-
events = []
|
160
|
-
count = 1
|
161
|
-
|
162
164
|
while self['sdate'] <= stop
|
163
165
|
# count
|
164
166
|
break if self['count'].positive? && count > self['count']
|
165
167
|
|
166
168
|
count += 1
|
167
169
|
|
168
|
-
# Handle specifier
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
occurrences = [ clone ]
|
173
|
-
end
|
170
|
+
# Handle specifier
|
171
|
+
o = []
|
172
|
+
o.push(self) unless self['specifier'] && self['specifier'].length.positive?
|
173
|
+
o += occurrences if self['specifier'] && self['specifier'].length.positive?
|
174
174
|
|
175
175
|
# Check for changes
|
176
|
-
|
177
|
-
|
178
|
-
|
176
|
+
o.each do |occurrence|
|
177
|
+
skip = false
|
178
|
+
|
179
|
+
changes[1..].each do |change|
|
180
|
+
codate = Time.at(change['orig_date'] + ITIME).to_a[3..5].reverse
|
181
|
+
odate = occurrence['sdate'].ymd
|
179
182
|
|
180
|
-
|
183
|
+
skip = true if codate == odate
|
181
184
|
end
|
182
|
-
end
|
183
185
|
|
184
|
-
|
186
|
+
events.push(clone(occurrence)) if in_window?(occurrence['sdate'], occurrence['edate']) && !skip
|
187
|
+
end
|
185
188
|
|
186
|
-
|
189
|
+
# Handle frequency and interval
|
190
|
+
apply_frequency! if self['frequency'] && self['interval']
|
187
191
|
end
|
188
192
|
|
189
193
|
# Remove exceptions
|
190
194
|
events.delete_if { |event| event['xdate'].any?(event['sdate']) }
|
191
195
|
|
192
|
-
events
|
196
|
+
events.uniq { |e| e['sdate'] }
|
193
197
|
end
|
194
198
|
|
195
199
|
private
|
196
200
|
|
197
201
|
# @!visibility public
|
198
202
|
|
199
|
-
#
|
200
|
-
|
201
|
-
|
203
|
+
# Deep clone an object
|
204
|
+
#
|
205
|
+
# @param obj [Object]
|
206
|
+
# @return [Object] a deep clone of obj
|
207
|
+
def clone(obj = self)
|
208
|
+
Marshal.load(Marshal.dump(obj))
|
202
209
|
end
|
203
210
|
|
204
|
-
# Get next
|
211
|
+
# Get next occurrences of a recurring event given a specifier
|
205
212
|
#
|
206
|
-
# @
|
207
|
-
|
208
|
-
|
209
|
-
occurrences = []
|
213
|
+
# @return [Array<ICalPal::Event>]
|
214
|
+
def occurrences
|
215
|
+
o = []
|
210
216
|
|
211
217
|
dow = DOW.keys
|
212
|
-
dom = [
|
218
|
+
dom = []
|
213
219
|
moy = 1..12
|
214
220
|
nth = nil
|
215
221
|
|
216
|
-
specifier = (self['specifier'])? self['specifier'] : []
|
217
|
-
|
218
222
|
# Deconstruct specifier
|
219
|
-
specifier.split(';').each do |k|
|
223
|
+
self['specifier'].split(';').each do |k|
|
220
224
|
j = k.split('=')
|
221
225
|
|
222
226
|
# D=Day of the week, M=Day of the month, O=Month of the year, S=Nth
|
@@ -230,38 +234,55 @@ module ICalPal
|
|
230
234
|
end
|
231
235
|
|
232
236
|
# Build array of DOWs
|
233
|
-
dows = [
|
234
|
-
dow.each
|
237
|
+
dows = []
|
238
|
+
dow.each do |d|
|
239
|
+
dows.push(DOW[d[-2..].to_sym])
|
240
|
+
nth = d[0..-3].to_i if [ '+', '-' ].include? d[0]
|
241
|
+
end
|
235
242
|
|
236
243
|
# Months of the year (O)
|
237
|
-
moy.each do |
|
238
|
-
|
244
|
+
moy.each do |mo|
|
245
|
+
m = mo.to_i
|
246
|
+
|
247
|
+
# Set dates to the first of <m>
|
248
|
+
nsdate = RDT.new(self['sdate'].year, m, 1, self['sdate'].hour, self['sdate'].minute, self['sdate'].second)
|
249
|
+
nedate = RDT.new(self['edate'].year, m, 1, self['edate'].hour, self['edate'].minute, self['edate'].second)
|
250
|
+
|
251
|
+
# ...but not in the past
|
252
|
+
nsdate >>= 12 if nsdate.month < m
|
253
|
+
nedate >>= 12 if nedate.month < m
|
254
|
+
|
255
|
+
next if nsdate > $opts[:to]
|
256
|
+
next if ((nedate >> 1) - 1) < $opts[:from]
|
239
257
|
|
240
|
-
|
241
|
-
nedate = RDT.new(self['edate'].year, m.to_i, 1)
|
258
|
+
c = clone
|
242
259
|
|
243
260
|
# Days of the month (M)
|
244
|
-
dom.each do |
|
245
|
-
|
261
|
+
dom.each do |day|
|
262
|
+
c['sdate'] = RDT.new(nsdate.year, nsdate.month, day.to_i)
|
263
|
+
c['edate'] = RDT.new(nedate.year, nedate.month, day.to_i)
|
246
264
|
|
247
|
-
|
248
|
-
self['edate'] = RDT.new(nedate.year, nedate.month, x.to_i)
|
249
|
-
occurrences.push(clone)
|
265
|
+
o.push(clone(c)) if in_window?(c['sdate'], c['edate'])
|
250
266
|
end
|
251
267
|
|
252
268
|
# Days of the week (D)
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
269
|
+
dows.each do |day|
|
270
|
+
if nth
|
271
|
+
c['sdate'] = ICalPal.nth(nth, day, nsdate)
|
272
|
+
c['edate'] = ICalPal.nth(nth, day, nedate)
|
273
|
+
else
|
274
|
+
diff = day - c['sdate'].wday
|
275
|
+
diff += 7 if diff.negative?
|
276
|
+
|
277
|
+
c['sdate'] += diff
|
278
|
+
c['edate'] += diff
|
279
|
+
end
|
280
|
+
|
281
|
+
o.push(clone(c)) if in_window?(c['sdate'], c['edate'])
|
261
282
|
end
|
262
283
|
end
|
263
284
|
|
264
|
-
|
285
|
+
o
|
265
286
|
end
|
266
287
|
|
267
288
|
# Apply frequency and interval
|
@@ -278,7 +299,7 @@ module ICalPal
|
|
278
299
|
when 'yearly' then self[d] >>= self['interval'] * 12
|
279
300
|
else $log.error("Unknown frequency: #{self['frequency']}")
|
280
301
|
end
|
281
|
-
end
|
302
|
+
end
|
282
303
|
end
|
283
304
|
|
284
305
|
# Check if an event starts or ends between from and to, or if it's
|
data/lib/icalPal.rb
CHANGED
@@ -19,8 +19,7 @@ module ICalPal
|
|
19
19
|
# @return [Class] The subclass of ICalPal
|
20
20
|
def self.call(klass)
|
21
21
|
case klass
|
22
|
-
when 'accounts' then Store
|
23
|
-
when 'stores' then Store
|
22
|
+
when 'accounts', 'stores' then Store
|
24
23
|
when 'calendars' then Calendar
|
25
24
|
when 'events' then Event
|
26
25
|
when 'tasks' then Reminder
|
@@ -32,7 +31,7 @@ module ICalPal
|
|
32
31
|
|
33
32
|
# Load data
|
34
33
|
def self.load_data(db_file, q)
|
35
|
-
$log.debug(q.gsub(
|
34
|
+
$log.debug(q.gsub("\n", ' '))
|
36
35
|
|
37
36
|
rows = []
|
38
37
|
|
@@ -93,8 +92,7 @@ module ICalPal
|
|
93
92
|
# @param headers [Array] Key names used as the header row in a CSV::Table
|
94
93
|
# @return [CSV::Row] The +Store+, +Calendar+, or +CalendarItem+ as a CSV::Row
|
95
94
|
def to_csv(headers)
|
96
|
-
values = []
|
97
|
-
headers.each { |h| values.push((@self[h].respond_to?(:gsub))? @self[h].gsub(/\n/, '\n') : @self[h]) }
|
95
|
+
values = headers.map { |h| (@self[h].respond_to?(:gsub))? @self[h].gsub("\n", '\n') : @self[h] }
|
98
96
|
|
99
97
|
CSV::Row.new(headers, values)
|
100
98
|
end
|
@@ -113,13 +111,14 @@ module ICalPal
|
|
113
111
|
# Get the +n+'th +dow+ in month +m+
|
114
112
|
#
|
115
113
|
# @param n [Integer] Integer between -4 and +4
|
116
|
-
# @param dow [
|
114
|
+
# @param dow [Integer] Day of the week
|
117
115
|
# @param m [RDT] The RDT with the year and month we're searching
|
118
116
|
# @return [RDT] The resulting day
|
119
117
|
def self.nth(n, dow, m)
|
120
|
-
# Get the number of days in the month
|
121
|
-
|
122
|
-
a
|
118
|
+
# Get the number of days in the month by advancing to the first of
|
119
|
+
# the next month, then going back one day
|
120
|
+
a = [ RDT.new(m.year, m.month, 1, m.hour, m.minute, m.second) ]
|
121
|
+
a[1] = (a[0] >> 1) - 1
|
123
122
|
|
124
123
|
# Reverse it if going backwards
|
125
124
|
a.reverse! if n.negative?
|
@@ -127,7 +126,7 @@ module ICalPal
|
|
127
126
|
|
128
127
|
j = 0
|
129
128
|
a[0].step(a[1], step) do |i|
|
130
|
-
j += step if dow
|
129
|
+
j += step if dow == i.wday
|
131
130
|
return i if j == n
|
132
131
|
end
|
133
132
|
end
|
@@ -138,7 +137,7 @@ module ICalPal
|
|
138
137
|
# Days of the week abbreviations used in recurrence rules
|
139
138
|
#
|
140
139
|
# <tt><i>SU, MO, TU, WE, TH, FR, SA</i></tt>
|
141
|
-
DOW = {
|
140
|
+
DOW = { SU: 0, MO: 1, TU: 2, WE: 3, TH: 4, FR: 5, SA: 6 }.freeze
|
142
141
|
|
143
142
|
# @!group Accessors
|
144
143
|
def [](k)
|
@@ -156,5 +155,13 @@ module ICalPal
|
|
156
155
|
def values
|
157
156
|
@self.values
|
158
157
|
end
|
158
|
+
|
159
|
+
# Like inspect, but easier for humans to read
|
160
|
+
#
|
161
|
+
# @return [Array<String>] @self as a key=value array, sorted by key
|
162
|
+
def dump
|
163
|
+
@self.keys.sort.map { |k| "#{k}: #{@self[k]}" }
|
164
|
+
end
|
165
|
+
|
159
166
|
# @!endgroup
|
160
167
|
end
|