timesteps 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,251 @@
1
+ #
2
+ # format.rb is modified version of date/format.rb distributed in Ruby 1.8.7.
3
+ #
4
+ # format.rb: Written by Tadayoshi Funaba 1999-2008
5
+ # $Id: format.rb,v 2.43 2008-01-17 20:16:31+09 tadf Exp $
6
+
7
+
8
+ #
9
+ #
10
+ #
11
+ class DateTimeLike
12
+
13
+ # @private
14
+ MILLISECONDS_IN_DAY = Rational(1, 86400*10**3)
15
+
16
+ # @private
17
+ MILLISECONDS_IN_SECOND = Rational(1, 10**3)
18
+
19
+ # @private
20
+ NANOSECONDS_IN_DAY = Rational(1, 86400*10**9)
21
+
22
+ # @private
23
+ NANOSECONDS_IN_SECOND = Rational(1, 10**9)
24
+
25
+ # @private
26
+ SECONDS_IN_DAY = Rational(1, 86400)
27
+
28
+ # @private
29
+ # Full month names, in English. Months count from 1 to 12; a
30
+ # month's numerical representation indexed into this array
31
+ # gives the name of that month (hence the first element is nil).
32
+ MONTHNAMES = [nil] + %w(January February March April May June July
33
+ August September October November December)
34
+
35
+ # @private
36
+ # Full names of days of the week, in English. Days of the week
37
+ # count from 0 to 6 (except in the commercial week); a day's numerical
38
+ # representation indexed into this array gives the name of that day.
39
+ DAYNAMES = %w(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
40
+
41
+ # @private
42
+ # Abbreviated month names, in English.
43
+ ABBR_MONTHNAMES = [nil] + %w(Jan Feb Mar Apr May Jun
44
+ Jul Aug Sep Oct Nov Dec)
45
+
46
+ # @private
47
+ # Abbreviated day names, in English.
48
+ ABBR_DAYNAMES = %w(Sun Mon Tue Wed Thu Fri Sat)
49
+
50
+ [MONTHNAMES, DAYNAMES, ABBR_MONTHNAMES, ABBR_DAYNAMES].each do |xs|
51
+ xs.each{|x| x.freeze unless x.nil?}.freeze
52
+ end
53
+
54
+ def emit(e, f) # :nodoc:
55
+ case e
56
+ when Numeric
57
+ sign = %w(+ + -)[e <=> 0]
58
+ e = e.abs
59
+ end
60
+
61
+ s = e.to_s
62
+
63
+ if f[:s] && f[:p] == '0'
64
+ f[:w] -= 1
65
+ end
66
+
67
+ if f[:s] && f[:p] == "\s"
68
+ s[0,0] = sign
69
+ end
70
+
71
+ if f[:p] != '-'
72
+ s = s.rjust(f[:w], f[:p])
73
+ end
74
+
75
+ if f[:s] && f[:p] != "\s"
76
+ s[0,0] = sign
77
+ end
78
+
79
+ s = s.upcase if f[:u]
80
+ s = s.downcase if f[:d]
81
+ s
82
+ end
83
+
84
+ def emit_w(e, w, f) # :nodoc:
85
+ f[:w] = [f[:w], w].compact.max
86
+ emit(e, f)
87
+ end
88
+
89
+ def emit_n(e, w, f) # :nodoc:
90
+ f[:p] ||= '0'
91
+ emit_w(e, w, f)
92
+ end
93
+
94
+ def emit_sn(e, w, f) # :nodoc:
95
+ if e < 0
96
+ w += 1
97
+ f[:s] = true
98
+ end
99
+ emit_n(e, w, f)
100
+ end
101
+
102
+ def emit_z(e, w, f) # :nodoc:
103
+ w += 1
104
+ f[:s] = true
105
+ emit_n(e, w, f)
106
+ end
107
+
108
+ def emit_a(e, w, f) # :nodoc:
109
+ f[:p] ||= "\s"
110
+ emit_w(e, w, f)
111
+ end
112
+
113
+ def emit_ad(e, w, f) # :nodoc:
114
+ if f[:x]
115
+ f[:u] = true
116
+ f[:d] = false
117
+ end
118
+ emit_a(e, w, f)
119
+ end
120
+
121
+ def emit_au(e, w, f) # :nodoc:
122
+ if f[:x]
123
+ f[:u] = false
124
+ f[:d] = true
125
+ end
126
+ emit_a(e, w, f)
127
+ end
128
+
129
+ private :emit, :emit_w, :emit_n, :emit_sn, :emit_z,
130
+ :emit_a, :emit_ad, :emit_au
131
+
132
+ def strftime(fmt='%F')
133
+
134
+ fmt.gsub(/%([-_0^#]+)?(\d+)?([EO]?(?::{1,3}z|.))/m) do |m|
135
+ f = {}
136
+ a = $&
137
+ s, w, c = $1, $2, $3
138
+ if s
139
+ s.scan(/./) do |k|
140
+ case k
141
+ when '-'; f[:p] = '-'
142
+ when '_'; f[:p] = "\s"
143
+ when '0'; f[:p] = '0'
144
+ when '^'; f[:u] = true
145
+ when '#'; f[:x] = true
146
+ end
147
+ end
148
+ end
149
+ if w
150
+ f[:w] = w.to_i
151
+ end
152
+ case c
153
+ when 'A'; emit_ad(DAYNAMES[wday], 0, f)
154
+ when 'a'; emit_ad(ABBR_DAYNAMES[wday], 0, f)
155
+ when 'B'; emit_ad(MONTHNAMES[month], 0, f)
156
+ when 'b'; emit_ad(ABBR_MONTHNAMES[month], 0, f)
157
+ when 'C', 'EC'; emit_sn((year / 100).floor, 2, f)
158
+ when 'c', 'Ec'; emit_a(strftime('%a %b %e %H:%M:%S %Y'), 0, f)
159
+ when 'D'; emit_a(strftime('%m/%d/%y'), 0, f)
160
+ when 'd', 'Od'; emit_n(day, 2, f)
161
+ when 'e', 'Oe'; emit_a(day, 2, f)
162
+ when 'F'
163
+ if m == '%F'
164
+ format('%.4d-%02d-%02d', year, month, day) # 4p
165
+ else
166
+ emit_a(strftime('%Y-%m-%d'), 0, f)
167
+ end
168
+ when 'G'; emit_sn(cwyear, 4, f)
169
+ when 'g'; emit_n(cwyear % 100, 2, f)
170
+ when 'H', 'OH'; emit_n(hour, 2, f)
171
+ when 'h'; emit_ad(strftime('%b'), 0, f)
172
+ when 'I', 'OI'; emit_n((hour % 12).nonzero? || 12, 2, f)
173
+ when 'j'; emit_n(yday, 3, f)
174
+ when 'k'; emit_a(hour, 2, f)
175
+ when 'L'
176
+ emit_n((sec_fraction / MILLISECONDS_IN_SECOND).round, 3, f)
177
+ when 'l'; emit_a((hour % 12).nonzero? || 12, 2, f)
178
+ when 'M', 'OM'; emit_n(minute, 2, f)
179
+ when 'm', 'Om'; emit_n(month, 2, f)
180
+ when 'N'
181
+ emit_n((sec_fraction / NANOSECONDS_IN_SECOND).round, 9, f)
182
+ when 'n'; "\n"
183
+ when 'P'; emit_ad(strftime('%p').downcase, 0, f)
184
+ when 'p'; emit_au(if hour < 12 then 'AM' else 'PM' end, 0, f)
185
+ when 'Q'
186
+ s = ((ajd - self::class::UNIX_EPOCH_IN_AJD) / MILLISECONDS_IN_DAY).round
187
+ emit_sn(s, 1, f)
188
+ when 'R'; emit_a(strftime('%H:%M'), 0, f)
189
+ when 'r'; emit_a(strftime('%I:%M:%S %p'), 0, f)
190
+ when 'S', 'OS'; emit_n(second.floor, 2, f)
191
+ when 's'
192
+ s = ((ajd - self::class::UNIX_EPOCH_IN_AJD) / SECONDS_IN_DAY).round
193
+ emit_sn(s, 1, f)
194
+ when 'T'
195
+ if m == '%T'
196
+ format('%02d:%02d:%02d', hour, minute, second) # 4p
197
+ else
198
+ emit_a(strftime('%H:%M:%S'), 0, f)
199
+ end
200
+ when 't'; "\t"
201
+ when 'U', 'W', 'OU', 'OW'
202
+ emit_n(if c[-1,1] == 'U' then wnum0 else wnum1 end, 2, f)
203
+ when 'u', 'Ou'; emit_n(cwday, 1, f)
204
+ when 'V', 'OV'; emit_n(cweek, 2, f)
205
+ when 'v'; emit_a(strftime('%e-%b-%Y'), 0, f)
206
+ when 'w', 'Ow'; emit_n(wday, 1, f)
207
+ when 'X', 'EX'; emit_a(strftime('%H:%M:%S'), 0, f)
208
+ when 'x', 'Ex'; emit_a(strftime('%m/%d/%y'), 0, f)
209
+ when 'Y', 'EY'; emit_sn(year, 4, f)
210
+ when 'y', 'Ey', 'Oy'; emit_n(year % 100, 2, f)
211
+ when 'Z'; emit_au(strftime('%:z'), 0, f)
212
+ when /\A(:{0,3})z/
213
+ t = $1.size
214
+ sign = if offset < 0 then -1 else +1 end
215
+ fr = offset.abs
216
+ ss = fr.div(SECONDS_IN_DAY) # 4p
217
+ hh, ss = ss.divmod(3600)
218
+ mm, ss = ss.divmod(60)
219
+ if t == 3
220
+ if ss.nonzero? then t = 2
221
+ elsif mm.nonzero? then t = 1
222
+ else t = -1
223
+ end
224
+ end
225
+ case t
226
+ when -1
227
+ tail = []
228
+ sep = ''
229
+ when 0
230
+ f[:w] -= 2 if f[:w]
231
+ tail = ['%02d' % mm]
232
+ sep = ''
233
+ when 1
234
+ f[:w] -= 3 if f[:w]
235
+ tail = ['%02d' % mm]
236
+ sep = ':'
237
+ when 2
238
+ f[:w] -= 6 if f[:w]
239
+ tail = ['%02d' % mm, '%02d' % ss]
240
+ sep = ':'
241
+ end
242
+ ([emit_z(sign * hh, 2, f)] + tail).join(sep)
243
+ when '%'; emit_a('%', 0, f)
244
+ when '+'; emit_a(strftime('%a %b %e %H:%M:%S %Z %Y'), 0, f)
245
+ else
246
+ a
247
+ end
248
+ end
249
+ end
250
+
251
+ end
@@ -0,0 +1,43 @@
1
+ require_relative "../timesteps"
2
+
3
+ class TimeStep
4
+
5
+ # @private
6
+ REGEXP_GRADS_TDEF = /\A(?:TDEF\s+)?(\d+)\s+LINEAR\s+([^\s]*?)\s+([^\s]*?)\s*\z/i
7
+
8
+ # @private
9
+ GRADS_INCREMENT = {
10
+ "mn" => "minutes",
11
+ "hr" => "hours",
12
+ "dy" => "days",
13
+ "mo" => "months",
14
+ "yr" => "years",
15
+ }
16
+
17
+ def self.parse_grads_tdef (tdef_string)
18
+ if tdef_string.strip =~ REGEXP_GRADS_TDEF
19
+ count = $1.to_i
20
+ time = $2
21
+ increment = $3
22
+ else
23
+ raise "invalid grads tdef string"
24
+ end
25
+ if time =~ /(((\d{2})?(:(\d{2}))?Z)?(\d{2}))?(\w{3})(\d{4})/i
26
+ hour = $3 || "00"
27
+ min = $5 || "00"
28
+ day = $6 || "01"
29
+ mon = $7
30
+ year = $8
31
+ origin = DateTime.parse("#{year}#{mon}#{day} #{hour}:#{min}:00")
32
+ else
33
+ raise "invalid time format"
34
+ end
35
+
36
+ increment = increment.sub(/(mn|hr|dy|mo|yr)/i) {|s| GRADS_INCREMENT[s.downcase]}
37
+
38
+ spec = format("%s since %s", increment, origin.strftime("%F %T"))
39
+
40
+ return TimeStep.new(spec, count: count)
41
+ end
42
+
43
+ end
@@ -0,0 +1,48 @@
1
+ class DateTime
2
+
3
+ module ParseTimeStampExtension
4
+
5
+ # Parses the given datetime expression and creates an instance.
6
+ # `DateTime._parse()` is called internally.
7
+ #
8
+ # @param spec [String]
9
+ # @option bc [Boolean]
10
+ #
11
+ # @return [DateTimeFixedDPY]
12
+
13
+ def parse_timestamp (spec, start=Date::ITALY, bc: false)
14
+ hash = DateTime._parse(spec)
15
+ year, month, day, hour, minute, second, sec_fraction, offset =
16
+ hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)
17
+ if bc and year < 0
18
+ year = year + 1
19
+ end
20
+ hour ||= 0
21
+ minute ||= 0
22
+ second ||= 0.0
23
+ sec_fraction ||= 0.0
24
+ offset ||= 0
25
+ if hour == 24 && minute == 0 && second == 0.0
26
+ self.new(year, month, day, 23, minute, second + sec_fraction, offset.quo(86400), start) + 1.quo(24)
27
+ else
28
+ self.new(year, month, day, hour, minute, second + sec_fraction, offset.quo(86400), start)
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ extend ParseTimeStampExtension
35
+
36
+ class NoLeap < DateTimeLike
37
+ extend ParseTimeStampExtension
38
+ end
39
+
40
+ class AllLeap < DateTimeLike
41
+ extend ParseTimeStampExtension
42
+ end
43
+
44
+ class Fixed360Day < DateTimeLike
45
+ extend ParseTimeStampExtension
46
+ end
47
+
48
+ end
@@ -0,0 +1,362 @@
1
+
2
+ class TimeStep
3
+
4
+ using DateTimeExt
5
+
6
+ # Constructs the object.
7
+ #
8
+ # The argument `spec` specifies the time step definition, which has the form,
9
+ # "<INTERVAL> since <TIME>"
10
+ # For example,
11
+ # * "second since 1970-01-01 00:00:00 +00:00"
12
+ # * "hour since 2001-01-01 00:00:00 JST"
13
+ # * "3 days since 2001-01-01 00:00:00 +00:00"
14
+ # * "10 years since 1901-01-01 00:00:00 +00:00"
15
+ # The symbol for time unit symbols should be one of
16
+ # * years, year
17
+ # * months, month
18
+ # * days, day, d
19
+ # * hours, hr, h
20
+ # * minutes, minute, min
21
+ # * seconds, second, sec, s
22
+ # The option `calendar` specifies the calendar for datetime calculation,
23
+ # * standard, gregorian -> DateTime with Date::ITALY as start
24
+ # * proleptic_gregorian -> DateTime with Date::GREGORIAN as start
25
+ # * proleptic_julian, julian -> DateTime with Date::JULIAN as start
26
+ # * noleap, 365_day -> DateTimeNoLeap
27
+ # * allleap, 366_day -> DateTimeAllLeap
28
+ # * 360_day -> DateTimeFixed360Day
29
+ # The option `bc` is a flag whether AD/BC or EC when parsing timestamp for
30
+ # negative year.
31
+ # The option count specifies the number of time steps, which is hint for
32
+ # some methods (#each etc).
33
+ #
34
+ # @param spec [String]
35
+ # @option calendar [String, TimeStep::Calendar]
36
+ # @option bc [Boolean]
37
+ # @option count [Integer] number of time steps (as hint for some methods)
38
+ #
39
+ def initialize (spec, calendar: "standard", bc: false, count: nil)
40
+ case calendar
41
+ when String
42
+ @calendar = Calendar.new(calendar, bc: bc)
43
+ else
44
+ @calendar = calendar
45
+ end
46
+ intervalspec, timespec = spec.split(/\s+since\s+/)
47
+ parse_interval(intervalspec)
48
+ @origin = @calendar.parse(timespec)
49
+ @intervalspec = format("%g %s", @numeric, symbol)
50
+ @timespec = @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
51
+ @definition = format("%s since %s", @intervalspec, @timespec)
52
+ @count = count
53
+ end
54
+
55
+ # @private
56
+ PATTERN_NUMERIC = '[\+\-]?\d*(?:\.\d+)?(?:[eE][\+\-]?\d+)?'
57
+ # @private
58
+ PATTERN_UNITS = 'years?|months?|days?|hours?|minutes?|seconds?|d|hr|h|min|sec|s'
59
+
60
+ def parse_interval (spec)
61
+ if spec.strip =~ /(#{PATTERN_NUMERIC}|)\s*(#{PATTERN_UNITS})/
62
+ if $1 == ""
63
+ @numeric = 1.to_r
64
+ else
65
+ @numeric = Float($1).to_r
66
+ end
67
+ symbol = $2
68
+ else
69
+ raise "invalid interval specification"
70
+ end
71
+ @interval = @numeric
72
+ case symbol
73
+ when "years", "year"
74
+ unless numeric.denominator == 1
75
+ raise "numeric factor for year should be an integer"
76
+ end
77
+ @symbol = :years
78
+ @interval *= 356.242*86400
79
+ when "months", "month"
80
+ unless numeric.denominator == 1
81
+ raise "numeric factor for month should be an integer"
82
+ end
83
+ @symbol = :months
84
+ @interval *= 30.4368*86400
85
+ when "days", "day", "d"
86
+ @symbol = :days
87
+ @interval *= 86400
88
+ when "hours", "hour", "hr", "h"
89
+ @symbol = :hours
90
+ @interval *= 3600
91
+ when "minutes", "minute", "min"
92
+ @symbol = :minutes
93
+ @interval *= 60
94
+ when "seconds", "second", "sec", "s"
95
+ @symbol = :seconds
96
+ end
97
+ end
98
+
99
+ private :parse_interval
100
+
101
+ attr_reader :definition,
102
+ :intervalspec,
103
+ :timespec,
104
+ :numeric,
105
+ :symbol,
106
+ :interval,
107
+ :origin,
108
+ :calendar
109
+
110
+ attr_accessor :count
111
+
112
+ # Returns limit time if the object has `count`, otherwise returns nil.
113
+ #
114
+ # @return [DateTime, nil]
115
+ def limit
116
+ if @count
117
+ return time_at(@count - 1)
118
+ else
119
+ return nil
120
+ end
121
+ end
122
+
123
+ # Sets count by giving maximum time.
124
+ #
125
+ # @return [DateTime, nil]
126
+ def limit= (time)
127
+ if time
128
+ @count = (prev_index_of(time) + 1).to_i
129
+ else
130
+ @count = nil
131
+ end
132
+ return limit
133
+ end
134
+
135
+ def valid? (*indices)
136
+ if @count
137
+ if indices.size == 1
138
+ index = indices.first
139
+ if index >= 0 and index < @count
140
+ return true
141
+ else
142
+ return false
143
+ end
144
+ else
145
+ return indices.map{|index| valid?(index) }
146
+ end
147
+ else
148
+ if indices.size == 1
149
+ return true
150
+ else
151
+ return indices.map{|index| true }
152
+ end
153
+ end
154
+ end
155
+
156
+ def info
157
+ return {
158
+ "definition" => @definition.clone,
159
+ "intervalspec" => @intervalspec.clone,
160
+ "timespec" => @timespec.clone,
161
+ "calendar" => @calendar.name,
162
+ "numeric" => @numeric,
163
+ "symbol" => @symbol.to_s,
164
+ "interval" => @interval,
165
+ "origin" => @origin.clone,
166
+ "count" => @count,
167
+ "bc" => @calendar.bc?,
168
+ }
169
+ end
170
+
171
+ def inspect
172
+ "#<TimeStep definition='#{definition}' calendar='#{calendar.name}'>"
173
+ end
174
+
175
+ # Returns true if other has same contents of `definition` and `calendar`
176
+ # as self has.
177
+ #
178
+ # @return [Boolean]
179
+ def == (other)
180
+ return @definition == other.definition && @calendar == other.calendar
181
+ end
182
+
183
+ def user_to_days (index)
184
+ return ( @interval * index.to_r ).quo(86400)
185
+ end
186
+
187
+ def days_to_user (index)
188
+ return ( 86400 * index.to_r ).quo(@interval)
189
+ end
190
+
191
+ # Returns the time represented by the given amount as DateTime object
192
+ #
193
+ # @param indices [Numeric,Array<Numeric>]
194
+ #
195
+ # @return [DateTime]
196
+ def time_at (*indices)
197
+ if indices.size == 1
198
+ index = indices.first
199
+ raise ArgumentError, "index should be numeric" unless index.is_a?(Numeric)
200
+ index = index.to_r
201
+ case @symbol
202
+ when :years
203
+ unless index.denominator == 1
204
+ raise "index for years should be an integer"
205
+ end
206
+ return @origin.next_year(index*@numeric)
207
+ when :months
208
+ unless index.denominator == 1
209
+ raise "index for years should be an integer"
210
+ end
211
+ return @origin.next_month(index*@numeric)
212
+ else
213
+ days = user_to_days(index) + @origin.jd + @origin.fraction - @origin.offset
214
+ jday = days.floor
215
+ fday = days - days.floor
216
+ return (@calendar.jday2date(jday) + fday).new_offset(@origin.offset)
217
+ end
218
+ else
219
+ return indices.map{|index| time_at(index) }
220
+ end
221
+ end
222
+
223
+ # Returns the index for the given time in the unit represented by the object
224
+ #
225
+ # @param times [DateTime] time object
226
+ #
227
+ # @return [Numeric]
228
+ def index_at (*times)
229
+ if times.size == 1
230
+ time = times.first
231
+ time = @calendar.parse(time) if time.is_a?(String)
232
+ case @symbol
233
+ when :years
234
+ return time.difference_in_years(@origin).quo(@numeric.to_i)
235
+ when :months
236
+ return time.difference_in_months(@origin).quo(@numeric.to_i)
237
+ else
238
+ jday = @calendar.date2jday(time.year, time.month, time.day)
239
+ fday = time.fraction
240
+ udays = days_to_user(jday - @origin.jd)
241
+ utime = days_to_user(fday - @origin.fraction)
242
+ return udays + utime
243
+ end
244
+ else
245
+ return times.map{|time| index_at(time) }
246
+ end
247
+ end
248
+
249
+ # Calculate the time difference in days from origin at the index.
250
+ #
251
+ # @param indices [Array]
252
+ # @return [DateTime, Array<DateTime>]
253
+ def days_at (*indices)
254
+ if indices.size == 1
255
+ index = indices.first
256
+ case @symbol
257
+ when :years
258
+ unless index.denominator == 1
259
+ raise "index for years should be an integer"
260
+ end
261
+ return @origin.next_year(@numeric*index) - @origin
262
+ when :months
263
+ unless index.denominator == 1
264
+ raise "index for years should be an integer"
265
+ end
266
+ return @origin.next_month(@numeric*index) - @origin
267
+ else
268
+ return user_to_days(index)
269
+ end
270
+ else
271
+ return indices.map{ |index| days_at(index) }
272
+ end
273
+ end
274
+
275
+ # Returns TimeStep object which has origin time determined
276
+ # by the given `index`.
277
+ #
278
+ # @param index [Numeric]
279
+ #
280
+ # @return [TimeStep]
281
+ def shift_origin (index)
282
+ time = time_at(index)
283
+ return TimeStep.new(@intervalspec + " since " + time.strftime("%Y-%m-%d %H:%M:%S.%N %:z"), calendar: @calendar)
284
+ end
285
+
286
+ # Returns TimeStep object which has origin time specified
287
+ # by the given `time`.
288
+ #
289
+ # @param time [DateTime]
290
+ #
291
+ # @return [TimeStep]
292
+ def new_origin (time)
293
+ case time
294
+ when String
295
+ time = @calendar.parse(time)
296
+ end
297
+ return TimeStep.new(@intervalspec + " since " + time.strftime("%Y-%m-%d %H:%M:%S.%N %:z"), calendar: @calendar)
298
+ end
299
+
300
+ include Enumerable
301
+
302
+ def each (limit = nil, incr = 1, &block)
303
+ if limit.nil?
304
+ raise "step method require count" unless @count
305
+ limit = @count - 1
306
+ end
307
+ if block
308
+ 0.step(limit, incr) do |k|
309
+ block.call(time_at(k))
310
+ end
311
+ else
312
+ return Enumerator.new {|y|
313
+ 0.step(limit, incr) do |k|
314
+ y << time_at(k)
315
+ end
316
+ }
317
+ end
318
+ end
319
+
320
+ def times (&block)
321
+ raise "step method require count" unless @count
322
+ (0...@count).each(&block)
323
+ end
324
+
325
+ # Creates TimeStep::Pair
326
+ #
327
+ #
328
+ def in (unit)
329
+ return TimeStep::Pair.new(self, format("%s since %s", unit, @timespec), calendar: @calendar)
330
+ end
331
+
332
+ def next_index_of (time)
333
+ case time
334
+ when String
335
+ time = @calendar.parse(time)
336
+ end
337
+ index = index_at(time).to_r
338
+ if index.denominator == 1
339
+ return index + 1
340
+ else
341
+ return index.ceil
342
+ end
343
+ end
344
+
345
+ def prev_index_of (time)
346
+ return next_index_of(time) - 1
347
+ end
348
+
349
+ def next_time_of (time)
350
+ return time_at(next_index_of(time))
351
+ end
352
+
353
+ def prev_time_of (time)
354
+ return time_at(prev_index_of(time))
355
+ end
356
+
357
+ def parse (time)
358
+ return @calendar.parse(time)
359
+ end
360
+
361
+ end
362
+