timesteps 0.9.6 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,104 @@
1
1
 
2
+ # TimeStep class
3
+ #
4
+ #
2
5
  class TimeStep
3
6
 
4
7
  using DateTimeExt
5
8
 
9
+ # @private
10
+ CALENDARS = {}
11
+
12
+ [
13
+ "standard",
14
+ "gregorian",
15
+ "proleptic_gregorian",
16
+ "proleptic_julian",
17
+ "julian",
18
+ "noleap",
19
+ "365_day",
20
+ "allleap",
21
+ "366_day",
22
+ "360_day",
23
+ ].each do |calendar|
24
+ CALENDARS[calendar] = Calendar.new(calendar)
25
+ end
26
+
27
+ # @private
28
+ PATTERN_NUMERIC = '[\+\-]?\d*(?:\.\d+)?(?:[eE][\+\-]?\d+)?'
29
+
30
+ # @private
31
+ PATTERN_UNITS = [
32
+ 'years?',
33
+ 'months?',
34
+ 'days?',
35
+ 'hours?',
36
+ 'minutes?',
37
+ 'seconds?',
38
+ 'd',
39
+ 'hrs?',
40
+ 'h',
41
+ 'mins?',
42
+ 'secs?',
43
+ 's',
44
+ 'milliseconds?',
45
+ 'msecs?',
46
+ 'ms',
47
+ 'microseconds?',
48
+ '(?:weeks?|w)(?:\-(?:sun|mon|tue|wed|thu|fri|sat))?',
49
+ 'sundays?',
50
+ 'mondays?',
51
+ 'tuesdays?',
52
+ 'wednesdays?',
53
+ 'thursdays?',
54
+ 'fridays?',
55
+ 'saturdays?',
56
+ 'ayears?',
57
+ ].join("|")
58
+
59
+ # @private
60
+ WDAY = {
61
+ sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6,
62
+ }
63
+
64
+ # @private
65
+ WDAY_NAME = {
66
+ sun: "sundays",
67
+ mon: "mondays",
68
+ tue: "tuesdays",
69
+ wed: "wednesdays",
70
+ thu: "thursdays",
71
+ fri: "fridays",
72
+ sat: "saturdays",
73
+ }
74
+
75
+ # Extracts numeric part and symbol part from the given interval specification.
76
+ #
77
+ # @example
78
+ # TimeStep.split_interval_spec("12 months")
79
+ # # => [12, "months"]
80
+ #
81
+ # TimeStep.split_interval_spec("month-end")
82
+ # # => [1, "month-end"]
83
+ #
84
+ # @param [String] interval specification (ex. "12 months", "3 hours", "year")
85
+ # @return [Array(Numeric, String)] A pair of `numeric` and `symbol`
86
+ #
87
+ def self.split_interval_spec (spec)
88
+ if spec.strip =~ /\A(#{PATTERN_NUMERIC}|)\s*((?:#{PATTERN_UNITS}).*)\z/i
89
+ numeric = if $1 == ""
90
+ 1
91
+ else
92
+ Float($1)
93
+ end
94
+ numeric = numeric.to_i if numeric.denominator == 1
95
+ symbol = $2
96
+ else
97
+ raise "the interval specification '#{spec}' is invalid."
98
+ end
99
+ return numeric, symbol
100
+ end
101
+
6
102
  # Constructs the object.
7
103
  #
8
104
  # The argument `spec` specifies the time step definition, which has the form,
@@ -12,6 +108,7 @@ class TimeStep
12
108
  # * "hour since 2001-01-01 00:00:00 JST"
13
109
  # * "3 days since 2001-01-01 00:00:00 +00:00"
14
110
  # * "10 years since 1901-01-01 00:00:00 +00:00"
111
+ #
15
112
  # The symbol for time unit symbols should be one of
16
113
  # * ayears, ayear (astronomical year: 365.242198781 day)
17
114
  # * years, year
@@ -22,12 +119,16 @@ class TimeStep
22
119
  # * seconds, second, secs, sec, s
23
120
  # * milliseconds, millisecond, msecs, msec, ms
24
121
  # * microseconds, microsecond
122
+ #
25
123
  # If you have already origin time object or general date string,
26
124
  # you can use `since` option,
27
125
  # TimeStep.new("3 hours", since: time)
28
126
  # TimeStep.new("3 hours", since: "2001010121", format: '%Y%m%d%H')
29
- # The option count specifies the number of time steps, which is hint for
30
- # some methods (#each etc).
127
+ # When origin time is specified in both 'spec' and 'since' option,
128
+ # the origin time in 'spec' has priority.
129
+ # If origin time is not specified in neither 'spec' and 'since' option,
130
+ # the default value is set to the origin time ("0000-01-01 00:00:00" for date and "1970-01-01 00:00:00" for time).
131
+ # The time offset from UTC can be set by 'offset' option.
31
132
  # The option `calendar` specifies the name of calendar for datetime calculation,
32
133
  # * "standard", "gregorian" -> DateTime with Date::ITALY as start
33
134
  # * "proleptic_gregorian" -> DateTime with Date::GREGORIAN as start
@@ -35,100 +136,125 @@ class TimeStep
35
136
  # * "noleap", "365_day" -> DateTimeNoLeap
36
137
  # * "allleap", "366_day" -> DateTimeAllLeap
37
138
  # * "360_day" -> DateTimeFixed360Day
38
- # The option `bc` is a flag whether AD/BC or EC when parsing timestamp for
39
- # negative year.
40
- #
41
- # @param spec [String]
42
- # @option since [DateTime, DateTimeLike, String]
43
- # @option format [String]
44
- # @option calendar [String, TimeStep::Calendar]
45
- # @option bc [Boolean]
46
- # @option count [Integer] number of time steps (as hint for some methods)
139
+ #
140
+ # @param spec [String] timestep specification
141
+ # @param since [DateTime, String]
142
+ # @param offset [Numeric, String] offset in origin time
143
+ # @param format [String] template string for strptime for parsing time
144
+ # @param calendar [String, TimeStep::Calendar]
47
145
  #
48
- def initialize (spec, since: nil, format: nil, count: nil, calendar: "standard", bc: false)
49
- @calendar = Calendar.new(calendar, bc: bc)
50
- if since
51
- parse_interval(spec)
52
- case since
53
- when String
54
- @origin = @calendar.parse(since, format: format)
146
+ def initialize (spec, since: nil, offset: nil, format: nil, calendar: "standard", tz: nil)
147
+
148
+ case calendar
149
+ when String
150
+ if tz
151
+ raise "tz option can be used only with 'standard' calendar type" if calendar != "standard"
152
+ @calendar = Calendar.new(calendar, tz: tz)
55
153
  else
56
- unless @calendar.valid_datetime_type?(since)
57
- raise "calendar of the given date-time object doesn't match with the given calendar in option"
58
- end
59
- @origin = since
154
+ @calendar = CALENDARS[calendar]
155
+ raise "specified calendar type '#{calendar}' is invalid" unless @calendar
60
156
  end
157
+ when TimeStep::Calendar
158
+ @calendar = calendar
61
159
  else
62
- spec1, spec2 = spec.split(/\s+since\s+/)
63
- parse_interval(spec1)
64
- @origin = @calendar.parse(spec2)
160
+ raise "invalid object for option 'calendar'"
65
161
  end
66
- @count = count
67
- end
68
-
69
- def interval_spec
70
- return format("%g %s", @numeric, @symbol)
71
- end
72
-
73
- def origin_spec
74
- return @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
75
- end
76
162
 
77
- def definition
78
- format("%s since %s", interval_spec, origin_spec)
163
+ if spec =~ /\s+since\s+/
164
+ interval_spec, time_spec = $~.pre_match, $~.post_match
165
+ parse_interval(interval_spec)
166
+ @origin = @calendar.parse(time_spec, offset: offset)
167
+ else
168
+ parse_interval(spec)
169
+ @origin = case since
170
+ when nil
171
+ case @symbol
172
+ when :hours, :minutes, :seconds
173
+ @calendar.parse("1970-1-1", offset: offset)
174
+ else
175
+ @calendar.parse("0000-1-1", offset: offset)
176
+ end
177
+ when String
178
+ @calendar.parse(since, format: format, offset: offset)
179
+ when Time
180
+ since.to_datetime
181
+ else
182
+ raise "datetime mismatched with calendar type" unless @calendar.valid_datetime_type?(since)
183
+ since
184
+ end
185
+ end
186
+
187
+ if @wday
188
+ origin = @origin - @origin.wday + WDAY[@wday]
189
+ origin -= 7 unless @origin >= origin
190
+ @origin = origin
191
+ end
192
+
79
193
  end
80
194
 
81
195
  # @private
82
- PATTERN_NUMERIC = '[\+\-]?\d*(?:\.\d+)?(?:[eE][\+\-]?\d+)?'
83
- # @private
84
- PATTERN_UNITS = 'ayears?|years?|months?|days?|hours?|minutes?|seconds?|d|hrs?|hrs?|h|mins?|secs?|s|milliseconds?|msecs?|ms|microseconds?'
85
-
86
196
  def parse_interval (spec)
87
- if spec.strip =~ /(#{PATTERN_NUMERIC}|)\s*(#{PATTERN_UNITS})/
88
- if $1 == ""
89
- @numeric = 1.to_r
90
- else
91
- @numeric = Float($1).to_r
92
- end
197
+ if spec.strip =~ /\A(#{PATTERN_NUMERIC}|)\s*(#{PATTERN_UNITS})\z/i
198
+ @numeric = if $1 == ""
199
+ 1.to_r
200
+ else
201
+ Float($1).to_r
202
+ end
93
203
  symbol = $2
94
204
  else
95
205
  raise "the interval specification '#{spec}' is invalid."
96
206
  end
97
207
  @interval = @numeric
98
208
  case symbol
99
- when /\Aayears?/
100
- @symbol = :days
101
- @numeric *= 365.242198781.to_r
102
- @interval = @numeric * 86400
103
- when /\Ayears?/
209
+ when /\Ayears?\z/i
104
210
  unless numeric.denominator == 1
105
211
  raise "numeric factor for year should be an integer"
106
212
  end
107
213
  @symbol = :years
108
214
  @interval *= 356.242*86400
109
- when /\Amonths?/
215
+ when /\Amonths?\z/i
110
216
  unless numeric.denominator == 1
111
217
  raise "numeric factor for month should be an integer"
112
218
  end
113
219
  @symbol = :months
114
220
  @interval *= 30.4368*86400
115
- when /\A(days?|d)/
221
+ when /\A(days?|d)\z/i
116
222
  @symbol = :days
117
223
  @interval *= 86400
118
- when /\A(hours?|hrs?|h)/
224
+ when /\A(hours?|hrs?|h)\z/i
119
225
  @symbol = :hours
120
226
  @interval *= 3600
121
- when /\A(minutes?|mins?)/
227
+ when /\A(minutes?|mins?)\z/i
122
228
  @symbol = :minutes
123
229
  @interval *= 60
124
- when /\A(seconds?|secs?|s)/
230
+ when /\A(seconds?|secs?|s)\z/i
125
231
  @symbol = :seconds
126
- when /\A(milliseconds?|msecs?|ms)/
232
+ when /\A(milliseconds?|msecs?|ms)\z/i
127
233
  @symbol = :seconds
128
234
  @interval *= 1.quo(1000)
129
- when /\Amicroseconds?/
235
+ when /\Amicroseconds?\z/i
130
236
  @symbol = :seconds
131
237
  @interval *= 1.quo(1000000)
238
+ when /\A(weeks?|w)\-(sun|mon|tue|wed|thu|fri|sat)\z/i
239
+ symbol =~ /\A(weeks?|w)\-(\w{3})/i
240
+ @wday = $2.downcase.intern
241
+ @symbol = WDAY_NAME[@wday].intern ### :sundays, :mondays, ...
242
+ @interval *= 7*86400
243
+ when /\A(weeks?|w)\z/i
244
+ @symbol = :weeks
245
+ @interval *= 7*86400
246
+ when /\Asundays?|mondays?|tuesdays?|wednesdays?|thursday?|fridays?|saturdays?\z/i
247
+ symbol =~ /\A(\w{3})/i
248
+ @wday = $1.downcase.intern
249
+ @symbol = WDAY_NAME[@wday].intern ### :sundays, :mondays, ...
250
+ @interval *= 7*86400
251
+ when /\Aayears?\z/i
252
+ @symbol = :days
253
+ @numeric *= 365.242198781.to_r
254
+ @interval = @numeric * 86400
255
+ end
256
+ if @numeric.denominator == 1
257
+ @numeric = @numeric.to_i
132
258
  end
133
259
  end
134
260
 
@@ -140,120 +266,119 @@ class TimeStep
140
266
  :origin,
141
267
  :calendar
142
268
 
143
- attr_accessor :count
144
-
145
- # Returns limit time if the object has `count`, otherwise returns nil.
146
- #
147
- # @return [DateTime, nil]
148
- def limit
149
- if @count
150
- return time_at(@count - 1)
269
+ # Returns a string expression for interval section in timestem spec.
270
+ #
271
+ # @return [String]
272
+ def interval_spec
273
+ if @wday
274
+ return format("%g %s", @numeric, WDAY_NAME[@wday])
151
275
  else
152
- return nil
276
+ return format("%g %s", @numeric, @symbol)
153
277
  end
154
278
  end
155
279
 
156
- # Sets count by giving maximum time.
157
- #
158
- # @return [DateTime, nil]
159
- def limit= (time)
160
- if time
161
- @count = next_index_of(time).to_i
280
+ # Returns a string expression for origin time section in timestep spec.
281
+ #
282
+ # @return [String]
283
+ def origin_spec
284
+ if @calendar.tz
285
+ return @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z %Z")
162
286
  else
163
- @count = nil
287
+ return @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
164
288
  end
165
- return limit
166
- end
167
-
168
- # Sets count by giving maximum time.
169
- #
170
- def set_limit (time, format: nil)
171
- case time
172
- when String
173
- time = @calendar.parse(time, format: format)
174
- end
175
- self.limit = time
176
289
  end
177
290
 
178
- def valid? (*indices)
179
- if @count
180
- if indices.size == 1
181
- index = indices.first
182
- if index >= 0 and index < @count
183
- return true
184
- else
185
- return false
186
- end
187
- else
188
- return indices.map{|index| valid?(index) }
189
- end
190
- else
191
- if indices.size == 1
192
- return true
193
- else
194
- return indices.map{|index| true }
195
- end
196
- end
291
+ # Returns a string expression of definition of timestep.
292
+ # The return value can be used for constructs other TimeStep object.
293
+ #
294
+ # @return [String]
295
+ def definition
296
+ format("%s since %s", interval_spec, origin_spec)
197
297
  end
198
298
 
199
- def debug_info
200
- return {
201
- "definition" => definition,
202
- "interval_spec" => interval_spec,
203
- "origin_spec" => origin_spec,
204
- "calendar" => @calendar.name,
205
- "numeric" => @numeric,
206
- "symbol" => @symbol.to_s,
207
- "interval" => @interval,
208
- "origin" => @origin.clone,
209
- "count" => @count,
210
- "bc" => @calendar.bc?,
211
- }
299
+ # Returns the time offset of origin time.
300
+ #
301
+ # @return [Rational]
302
+ def offset
303
+ return @origin.offset
212
304
  end
213
305
 
214
- private :debug_info
306
+ # Parses datetime string and return datetime object.
307
+ # In the parsing, the calendar of the object is used.
308
+ # If `format` option is given, `strptime` method is
309
+ # used for the parsing. Otherwise, the `parse` is used.
310
+ #
311
+ # @param time [String] string to be parsed
312
+ # @param format [String] template string for strptime for parsing time
313
+ #
314
+ # @return [DateTime]
315
+ def parse (time, format: nil)
316
+ return @calendar.parse(time, format: format, offset: @origin.offset)
317
+ end
215
318
 
216
- # Returns the value as a string for inspection.
319
+ # Returns a string for inspection.
217
320
  #
321
+ # @return [String]
218
322
  def inspect
219
- "#<TimeStep definition='#{definition}' calendar='#{calendar.name}'>"
323
+ options = ""
324
+ case @calendar.name
325
+ when "standard", "gregorian"
326
+ else
327
+ options << " calendar='#{calendar.name}'"
328
+ end
329
+ "#<TimeStep definition='#{definition}'#{options}>"
220
330
  end
221
331
 
222
332
  # Returns true if other has same contents of `definition` and `calendar`
223
333
  # as self has.
224
334
  #
335
+ # @param other [TimeStep]
336
+ #
225
337
  # @return [Boolean]
226
338
  def == (other)
227
339
  return definition == other.definition && @calendar == other.calendar
228
340
  end
229
341
 
342
+ # @private
230
343
  def user_to_days (index)
231
344
  return ( @interval * index.to_r ).quo(86400)
232
345
  end
233
346
 
347
+ # @private
234
348
  def days_to_user (index)
235
- return ( 86400 * index.to_r ).quo(@interval)
349
+ if @interval == 0
350
+ return 0
351
+ else
352
+ return ( 86400 * index.to_r ).quo(@interval)
353
+ end
236
354
  end
237
355
 
238
356
  private :user_to_days, :days_to_user
239
357
 
240
- # Returns the time represented by the given amount as DateTime object
358
+ # Returns the datetime object (array) for the given index (indices).
241
359
  #
242
- # @param indices [Numeric,Array<Numeric>]
360
+ # @example
361
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
362
+ # ts.time_at(0)
363
+ # # => #<DateTime: 2001-01-01T00:00:00+00:00 ...>
364
+ # ts.time_at(14)
365
+ # # => #<DateTime: 2001-01-15T00:00:00+00:00 ...>
243
366
  #
244
- # @return [DateTime]
367
+ # @param indices [Array<Numeric>]
368
+ #
369
+ # @return [DateTime, Array<DateTime>]
245
370
  def time_at (*indices)
246
371
  if indices.size == 1
247
372
  index = indices.first
248
373
  raise ArgumentError, "index argument should be a numeric" unless index.is_a?(Numeric)
249
374
  case @symbol
250
375
  when :years
251
- unless index.denominator == 1
376
+ unless (index*@numeric).denominator == 1
252
377
  raise ArgumentError, "index argument should be an integer for years"
253
378
  end
254
379
  return @origin.next_year(index*@numeric)
255
380
  when :months
256
- unless index.denominator == 1
381
+ unless (index*@numeric).denominator == 1
257
382
  raise ArgumentError, "index argument should be an integer for months"
258
383
  end
259
384
  return @origin.next_month(index*@numeric)
@@ -268,27 +393,39 @@ class TimeStep
268
393
  end
269
394
  end
270
395
 
271
- # Calculate the duration in days since origin time at the given index.
396
+ alias [] time_at
397
+
398
+ # Calculate the duration (array) in day unit since origin time at the given index (indices).
399
+ #
400
+ # @example
401
+ # ts = TimeStep.new("hours since 2001-01-01 00:00:00")
402
+ # ts.duration_at(0)
403
+ # # => 0 ### 0 days
404
+ # ts.duration_at(12)
405
+ # # => (1/2) ### half of a day
406
+ # ts.duration_at(14*24)
407
+ # # => 14 ### 14 days
408
+ #
409
+ # @param indices [Array<Numeric>]
272
410
  #
273
- # @param indices [Array]
274
411
  # @return [DateTime, Array<DateTime>]
275
412
  def duration_at (*indices)
276
413
  if indices.size == 1
277
414
  index = indices.first
278
- case @symbol
279
- when :years
280
- unless index.denominator == 1
281
- raise ArgumentError, "index argument should be an integer for years"
282
- end
283
- days = @origin.next_year(@numeric*index) - @origin
284
- when :months
285
- unless index.denominator == 1
286
- raise ArgumentError, "index argument should be an integer for months"
287
- end
288
- days = @origin.next_month(@numeric*index) - @origin
289
- else
290
- days = user_to_days(index)
291
- end
415
+ days = case @symbol
416
+ when :years
417
+ unless (index*@numeric).denominator == 1
418
+ raise ArgumentError, "index argument should be an integer for years"
419
+ end
420
+ @origin.next_year(@numeric*index) - @origin
421
+ when :months
422
+ unless (index*@numeric).denominator == 1
423
+ raise ArgumentError, "index argument should be an integer for months"
424
+ end
425
+ @origin.next_month(@numeric*index) - @origin
426
+ else
427
+ user_to_days(index)
428
+ end
292
429
  days = days.to_i if days.denominator == 1
293
430
  return days
294
431
  else
@@ -296,20 +433,35 @@ class TimeStep
296
433
  end
297
434
  end
298
435
 
299
- # Returns the index for the given time in the unit represented by the object
436
+ # Returns the index (indices) for the given time (array).
300
437
  #
301
- # @param times [DateTime] time object
438
+ # @example
439
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
440
+ # ts.index_at(ts.parse("2001-01-01 00:00:00"))
441
+ # # => 0
442
+ # ts.index_at("2001-01-15 00:00:00")
443
+ # # => 14
444
+ # ts.index_at("2002")
445
+ # # => 365
302
446
  #
303
- # @return [Numeric]
447
+ # @param times [Array<DateTime>]
448
+ # @param format [String] template string for strptime for parsing time
449
+ #
450
+ # @return [Numeric, Array<Numeric>]
304
451
  def index_at (*times, format: nil)
305
452
  if times.size == 1
306
453
  time = times.first
307
- time = @calendar.parse(time, format: format) if time.is_a?(String)
454
+ time = time.to_datetime if time.is_a?(Time)
455
+ time = @calendar.parse(time, format: format, offset: @origin.offset) if time.is_a?(String)
308
456
  case @symbol
309
457
  when :years
310
- index = time.difference_in_years(@origin).quo(@numeric.to_i)
458
+ diff = time.difference_in_years(@origin)
459
+ frac = diff - diff.floor
460
+ index = diff.floor.quo(@numeric.to_i) + frac
311
461
  when :months
312
- index = time.difference_in_months(@origin).quo(@numeric.to_i)
462
+ diff = time.difference_in_months(@origin)
463
+ frac = diff - diff.floor
464
+ index = diff.floor.quo(@numeric.to_i) + frac
313
465
  else
314
466
  jday = @calendar.date2jday(time.year, time.month, time.day)
315
467
  fday = time.fraction
@@ -324,146 +476,226 @@ class TimeStep
324
476
  end
325
477
  end
326
478
 
327
- # Returns TimeStep object which has origin time determined
328
- # by the given `index`.
479
+ # Returns new timestep object which has new origin time specified by `index`.
480
+ #
481
+ # @example
482
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
483
+ # # => #<TimeStep definition='1 days since 2001-01-01 00:00:00.000000000 +00:00'>
484
+ # ts.shift_origin(14)
485
+ # # => #<TimeStep definition='1 days since 2001-01-15 00:00:00.000000000 +00:00'>
486
+ # ts.shift_origin(365)
487
+ # # => #<TimeStep definition='1 days since 2002-01-01 00:00:00.000000000 +00:00'>
329
488
  #
330
489
  # @param index [Numeric]
490
+ # @param with [String, Symbol] "index" : shift by index , "duration" : shift by duration
331
491
  #
332
492
  # @return [TimeStep]
333
- def shift_origin (index)
334
- time = time_at(index)
335
- return TimeStep.new(interval_spec, since: time, calendar: @calendar.name, bc: @calendar.bc?)
336
- end
337
-
338
- def shift_origin_with_duration (duration)
339
- time = @origin + duration
340
- return TimeStep.new(interval_spec, since: time, calendar: @calendar.name, bc: @calendar.bc?)
493
+ def shift_origin (index, with: "index")
494
+ case with
495
+ when :index, "index"
496
+ time = time_at(index)
497
+ return TimeStep.new(interval_spec, since: time, calendar: @calendar)
498
+ when :duration, "duration", :days, "days"
499
+ time = @origin + index
500
+ return TimeStep.new(interval_spec, since: time, calendar: @calendar)
501
+ end
341
502
  end
342
503
 
343
- # Returns TimeStep object which has origin time specified
344
- # by the given `time`.
504
+ # Returns new timestep object which holds the given time as origin.
345
505
  #
346
- # @param time [DateTime]
506
+ # @example
507
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
508
+ # # => #<TimeStep definition='1 days since 2001-01-01 00:00:00.000000000 +00:00'>
509
+ # ts.new_origin(ts.parse("2001-01-15"))
510
+ # # => #<TimeStep definition='1 days since 2001-01-15 00:00:00.000000000 +00:00'>
511
+ # ts.new_origin("2001-01-15")
512
+ # # => #<TimeStep definition='1 days since 2001-01-15 00:00:00.000000000 +00:00'>
513
+ # ts.new_origin("2002")
514
+ # # => #<TimeStep definition='1 days since 2002-01-01 00:00:00.000000000 +00:00'>
515
+ #
516
+ # @param time [DateTime, String]
347
517
  #
348
518
  # @return [TimeStep]
349
- def new_origin (time)
350
- case time
351
- when String
352
- time = @calendar.parse(time)
519
+ def new_origin (time, truncate: false)
520
+ time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
521
+ time = self.truncate(time) if truncate
522
+ if @wday
523
+ origin = time - time.wday + WDAY[@wday]
524
+ origin -= 7 unless time >= origin
525
+ time = origin
353
526
  end
354
- return TimeStep.new(interval_spec, since: time, calendar: @calendar.name, bc: @calendar.bc?)
527
+ return TimeStep.new(interval_spec, since: time, calendar: @calendar)
355
528
  end
356
529
 
357
- include Enumerable
358
-
359
- def each (limit = nil, incr = 1, &block)
360
- if limit.nil?
361
- raise "count (or limit) is required by #step" unless @count
362
- limit = @count - 1
363
- end
364
- if block
365
- 0.step(limit, incr) do |k|
366
- block.call(time_at(k))
367
- end
368
- else
369
- return Enumerator.new {|y|
370
- 0.step(limit, incr) do |k|
371
- y << time_at(k)
372
- end
373
- }
374
- end
375
- end
376
-
377
- def times (&block)
378
- raise "count (or limit) is required by #step" unless @count
379
- (0...@count).each(&block)
380
- end
381
-
382
- # Creates TimeStep::Pair
530
+ # Truncate the given datetime to the unit of the object.
383
531
  #
532
+ # @example
533
+ # hours = TimeStep.new("hours since 2001-01-01 00:00:00")
534
+ # hours.truncate("2001-01-15 12:35:00")
535
+ # # => #<DateTime: 2001-01-15T12:00:00+00:00 ...>
384
536
  #
385
- def in (unit)
386
- return TimeStep::Pair.new(self, format("%s since %s", unit, origin_spec), calendar: @calendar.name, bc: @calendar.bc?)
537
+ # days = TimeStep.new("days since 2001-01-01 00:00:00")
538
+ # days.truncate("2001-01-15 12:00:00")
539
+ # # => #<DateTime: 2001-01-15T00:00:00+00:00 ...>
540
+ #
541
+ # months = TimeStep.new("months since 2001-01-01 00:00:00")
542
+ # months.truncate("2001-05-15 12:00:00")
543
+ # # => #<DateTime: 2001-05-01T00:00:00+00:00 ...>
544
+ #
545
+ # @param time [DateTime, String]
546
+ #
547
+ # @return [DateTime]
548
+ def truncate (time)
549
+ return time_at(index_at(time).floor)
387
550
  end
388
-
551
+
389
552
  # Returns next integer index of the given time
553
+ #
554
+ # @example
555
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
556
+ # ts.next_index_of("2001-01-14 12:00:00")
557
+ # #=> 14
558
+ # ts.next_index_of("2001-01-15 00:00:00")
559
+ # #=> 15
560
+ #
561
+ # @param time [DateTime, String]
562
+ #
563
+ # @return [Numeric]
390
564
  def next_index_of (time)
391
- case time
392
- when String
393
- time = @calendar.parse(time)
394
- end
395
- index = index_at(time)
396
- if index.denominator == 1
397
- return index.to_i + 1
398
- else
399
- return index.ceil
400
- end
565
+ time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
566
+ return index_at(time).floor + 1
401
567
  end
402
568
 
403
569
  # Returns previous integer index of the given time
570
+ #
571
+ # @example
572
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
573
+ # ts.prev_index_of("2001-01-14 12:00:00")
574
+ # #=> 13
575
+ # ts.prev_index_of("2001-01-15 00:00:00")
576
+ # #=> 13
577
+ #
578
+ # @param time [DateTime, String]
579
+ #
580
+ # @return [Numeric]
404
581
  def prev_index_of (time)
405
- case time
406
- when String
407
- time = @calendar.parse(time)
408
- end
409
- index = index_at(time)
410
- if index.denominator == 1
411
- return index.to_i - 1
412
- else
413
- return index.floor
414
- end
582
+ time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
583
+ return index_at(time).ceil - 1
415
584
  end
416
585
 
417
586
  # Returns next time of the given time
587
+ #
588
+ # @example
589
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
590
+ # ts.next_time_of("2001-01-14 12:00:00")
591
+ # #=> #<DateTime: 2001-01-15T00:00:00+00:00 ...>
592
+ # ts.next_time_of("2001-01-15 00:00:00")
593
+ # #=> #<DateTime: 2001-01-16T00:00:00+00:00 ...>
594
+ #
595
+ # @param time [DateTime, String]
596
+ #
597
+ # @return [DateTime]
418
598
  def next_time_of (time)
419
599
  return time_at(next_index_of(time))
420
600
  end
421
601
 
422
602
  # Returns previous time of the given time
603
+ #
604
+ # @example
605
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
606
+ # ts.prev_time_of("2001-01-14 12:00:00")
607
+ # #=> #<DateTime: 2001-01-14T00:00:00+00:00 ...>
608
+ # ts.prev_time_of("2001-01-15 00:00:00")
609
+ # #=> #<DateTime: 2001-01-14T00:00:00+00:00 ...>
610
+ #
611
+ # @param time [String, DateTime]
612
+ #
613
+ # @return [DateTime]
423
614
  def prev_time_of (time)
424
615
  return time_at(prev_index_of(time))
425
616
  end
426
617
 
427
- # Parses date-time string with appropreate calendar
618
+ # Creates new timestep pair object which refers `other` as other unit
428
619
  #
429
- # @option format [String]
430
- def parse (time, format: nil)
431
- return @calendar.parse(time, format: format)
620
+ # @example
621
+ # days = TimeStep.new("days since 2001-01-01 00:00:00")
622
+ # pair = days.in("hours")
623
+ # pair.forward(1)
624
+ # # => 24
625
+ #
626
+ # @param other [String, TimeStep]
627
+ #
628
+ # @return [TimeStep::Pair]
629
+ def in (unit)
630
+ other = TimeStep.new(unit, since: @origin, calendar: @calendar)
631
+ return Pair.new(self, other)
432
632
  end
433
-
434
- def index_for_period (period)
435
- if period.include_start?
436
- idx1 = index_at(period.origin).ceil
437
- else
438
- idx1 = next_index_of(period.origin)
439
- end
440
- if period.include_last?
441
- idx2 = index_at(period.last).floor
442
- else
443
- idx2 = prev_index_of(period.last)
444
- end
445
- return (idx1..idx2).to_a
633
+
634
+ # Creates new timestep pair object which refers `other` from `self`
635
+ #
636
+ # @example
637
+ # days = TimeStep.new("days since 2001-01-01 00:00:00")
638
+ # hours = TimeStep.new("hours since 2001-01-01 00:00:00")
639
+ # pair = days.to(hours)
640
+ # pair.forward(1)
641
+ # # => 24
642
+ #
643
+ # @param other [String, TimeStep]
644
+ #
645
+ # @return [TimeStep::Pair]
646
+ def to (other)
647
+ return Pair.new(self, other)
446
648
  end
447
649
 
448
- def period (start, last, boundary: "[)")
449
- case start
450
- when Numeric
451
- idx1 = start
452
- else
453
- idx1 = index_at(start)
454
- end
455
- case last
456
- when Numeric
457
- idx2 = last
458
- else
459
- idx2 = index_at(last)
460
- end
650
+ # Creates new timeperiod object corresponding given time or index for
651
+ # start and last.
652
+ #
653
+ # @example
654
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
655
+ # ts.period(0, 1)
656
+ # #=> #<TimePeriod '1 days' [2001-01-01T00:00:00+00:00, 2001-01-02T00:00:00+00:00] >
657
+ # ts.period("2001", "2002", ends: "[)")
658
+ # #=> #<TimePeriod '365 days' [2001-01-01T00:00:00+00:00, 2002-01-01T00:00:00+00:00) >
659
+ #
660
+ # @param start [Numeric, DateTime]
661
+ # @param last [Numeric, DateTime]
662
+ # @param ends [String] one of "[]", "()", "[)", "(]"
663
+ #
664
+ # @return [TimePeriod]
665
+ def period (start, last, ends: "[]")
666
+ idx1 = if start.kind_of?(Numeric)
667
+ start
668
+ else
669
+ index_at(start)
670
+ end
671
+ idx2 = if last.kind_of?(Numeric)
672
+ last
673
+ else
674
+ index_at(last)
675
+ end
461
676
  origin = time_at(idx1)
462
677
  numeric = (idx2 - idx1) * @numeric
463
678
  interval_spec = format("%g %s", numeric, @symbol)
464
- return TimePeriod.new(interval_spec, since: origin, calendar: @calendar.name, bc: @bc, boundary: boundary)
679
+ return TimePeriod.new(interval_spec, since: origin, calendar: @calendar, ends: ends)
465
680
  end
466
681
 
467
-
682
+ # Creates new timestep range object.
683
+ #
684
+ # @example
685
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
686
+ # ts.range(0, 1)
687
+ # ts.range("2001", "2002", ends: "[)")
688
+ # ts.range("2001", 1)
689
+ #
690
+ # @param start [Numeric, DateTime]
691
+ # @param last [Numeric, DateTime]
692
+ # @param count [Integer]
693
+ # @param ends [String] one of "[]", "()", "[)", "(]"
694
+ #
695
+ # @return [TimeStep::Range]
696
+ def range (start, last = nil, count: nil, ends: "[]")
697
+ return TimeStep::Range.new(self, start, last, count: count, ends: ends)
698
+ end
699
+
468
700
  end
469
701