timesteps 0.9.6 → 0.9.7

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.
@@ -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,34 @@ 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 = @calendar.parse(time, format: format, offset: @origin.offset) if time.is_a?(String)
308
455
  case @symbol
309
456
  when :years
310
- index = time.difference_in_years(@origin).quo(@numeric.to_i)
457
+ diff = time.difference_in_years(@origin)
458
+ frac = diff - diff.floor
459
+ index = diff.floor.quo(@numeric.to_i) + frac
311
460
  when :months
312
- index = time.difference_in_months(@origin).quo(@numeric.to_i)
461
+ diff = time.difference_in_months(@origin)
462
+ frac = diff - diff.floor
463
+ index = diff.floor.quo(@numeric.to_i) + frac
313
464
  else
314
465
  jday = @calendar.date2jday(time.year, time.month, time.day)
315
466
  fday = time.fraction
@@ -324,146 +475,225 @@ class TimeStep
324
475
  end
325
476
  end
326
477
 
327
- # Returns TimeStep object which has origin time determined
328
- # by the given `index`.
478
+ # Returns new timestep object which has new origin time specified by `index`.
479
+ #
480
+ # @example
481
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
482
+ # # => #<TimeStep definition='1 days since 2001-01-01 00:00:00.000000000 +00:00'>
483
+ # ts.shift_origin(14)
484
+ # # => #<TimeStep definition='1 days since 2001-01-15 00:00:00.000000000 +00:00'>
485
+ # ts.shift_origin(365)
486
+ # # => #<TimeStep definition='1 days since 2002-01-01 00:00:00.000000000 +00:00'>
329
487
  #
330
488
  # @param index [Numeric]
489
+ # @param with [String, Symbol] "index" : shift by index , "duration" : shift by duration
331
490
  #
332
491
  # @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?)
492
+ def shift_origin (index, with: "index")
493
+ case with
494
+ when :index, "index"
495
+ time = time_at(index)
496
+ return TimeStep.new(interval_spec, since: time, calendar: @calendar)
497
+ when :duration, "duration", :days, "days"
498
+ time = @origin + index
499
+ return TimeStep.new(interval_spec, since: time, calendar: @calendar)
500
+ end
341
501
  end
342
502
 
343
- # Returns TimeStep object which has origin time specified
344
- # by the given `time`.
503
+ # Returns new timestep object which holds the given time as origin.
345
504
  #
346
- # @param time [DateTime]
505
+ # @example
506
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
507
+ # # => #<TimeStep definition='1 days since 2001-01-01 00:00:00.000000000 +00:00'>
508
+ # ts.new_origin(ts.parse("2001-01-15"))
509
+ # # => #<TimeStep definition='1 days since 2001-01-15 00:00:00.000000000 +00:00'>
510
+ # ts.new_origin("2001-01-15")
511
+ # # => #<TimeStep definition='1 days since 2001-01-15 00:00:00.000000000 +00:00'>
512
+ # ts.new_origin("2002")
513
+ # # => #<TimeStep definition='1 days since 2002-01-01 00:00:00.000000000 +00:00'>
514
+ #
515
+ # @param time [DateTime, String]
347
516
  #
348
517
  # @return [TimeStep]
349
518
  def new_origin (time)
350
- case time
351
- when String
352
- time = @calendar.parse(time)
519
+ time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
520
+ if @wday
521
+ origin = time - time.wday + WDAY[@wday]
522
+ origin -= 7 unless time >= origin
523
+ time = origin
353
524
  end
354
- return TimeStep.new(interval_spec, since: time, calendar: @calendar.name, bc: @calendar.bc?)
525
+ return TimeStep.new(interval_spec, since: time, calendar: @calendar)
355
526
  end
356
527
 
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
528
+ # Truncate the given datetime to the unit of the object.
383
529
  #
530
+ # @example
531
+ # hours = TimeStep.new("hours since 2001-01-01 00:00:00")
532
+ # hours.truncate("2001-01-15 12:35:00")
533
+ # # => #<DateTime: 2001-01-15T12:00:00+00:00 ...>
384
534
  #
385
- def in (unit)
386
- return TimeStep::Pair.new(self, format("%s since %s", unit, origin_spec), calendar: @calendar.name, bc: @calendar.bc?)
535
+ # days = TimeStep.new("days since 2001-01-01 00:00:00")
536
+ # days.truncate("2001-01-15 12:00:00")
537
+ # # => #<DateTime: 2001-01-15T00:00:00+00:00 ...>
538
+ #
539
+ # months = TimeStep.new("months since 2001-01-01 00:00:00")
540
+ # months.truncate("2001-05-15 12:00:00")
541
+ # # => #<DateTime: 2001-05-01T00:00:00+00:00 ...>
542
+ #
543
+ # @param time [DateTime, String]
544
+ #
545
+ # @return [DateTime]
546
+ def truncate (time)
547
+ return time_at(index_at(time).floor)
387
548
  end
388
-
549
+
389
550
  # Returns next integer index of the given time
551
+ #
552
+ # @example
553
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
554
+ # ts.next_index_of("2001-01-14 12:00:00")
555
+ # #=> 14
556
+ # ts.next_index_of("2001-01-15 00:00:00")
557
+ # #=> 15
558
+ #
559
+ # @param time [DateTime, String]
560
+ #
561
+ # @return [Numeric]
390
562
  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
563
+ time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
564
+ return index_at(time).floor + 1
401
565
  end
402
566
 
403
567
  # Returns previous integer index of the given time
568
+ #
569
+ # @example
570
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
571
+ # ts.prev_index_of("2001-01-14 12:00:00")
572
+ # #=> 13
573
+ # ts.prev_index_of("2001-01-15 00:00:00")
574
+ # #=> 13
575
+ #
576
+ # @param time [DateTime, String]
577
+ #
578
+ # @return [Numeric]
404
579
  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
580
+ time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
581
+ return index_at(time).ceil - 1
415
582
  end
416
583
 
417
584
  # Returns next time of the given time
585
+ #
586
+ # @example
587
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
588
+ # ts.next_time_of("2001-01-14 12:00:00")
589
+ # #=> #<DateTime: 2001-01-15T00:00:00+00:00 ...>
590
+ # ts.next_time_of("2001-01-15 00:00:00")
591
+ # #=> #<DateTime: 2001-01-16T00:00:00+00:00 ...>
592
+ #
593
+ # @param time [DateTime, String]
594
+ #
595
+ # @return [DateTime]
418
596
  def next_time_of (time)
419
597
  return time_at(next_index_of(time))
420
598
  end
421
599
 
422
600
  # Returns previous time of the given time
601
+ #
602
+ # @example
603
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
604
+ # ts.prev_time_of("2001-01-14 12:00:00")
605
+ # #=> #<DateTime: 2001-01-14T00:00:00+00:00 ...>
606
+ # ts.prev_time_of("2001-01-15 00:00:00")
607
+ # #=> #<DateTime: 2001-01-14T00:00:00+00:00 ...>
608
+ #
609
+ # @param time [String, DateTime]
610
+ #
611
+ # @return [DateTime]
423
612
  def prev_time_of (time)
424
613
  return time_at(prev_index_of(time))
425
614
  end
426
615
 
427
- # Parses date-time string with appropreate calendar
616
+ # Creates new timestep pair object which refers `other` as other unit
428
617
  #
429
- # @option format [String]
430
- def parse (time, format: nil)
431
- return @calendar.parse(time, format: format)
618
+ # @example
619
+ # days = TimeStep.new("days since 2001-01-01 00:00:00")
620
+ # pair = days.in("hours")
621
+ # pair.forward(1)
622
+ # # => 24
623
+ #
624
+ # @param other [String, TimeStep]
625
+ #
626
+ # @return [TimeStep::Pair]
627
+ def in (unit)
628
+ other = TimeStep.new(unit, since: @origin, calendar: @calendar)
629
+ return Pair.new(self, other)
432
630
  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
631
+
632
+ # Creates new timestep pair object which refers `other` from `self`
633
+ #
634
+ # @example
635
+ # days = TimeStep.new("days since 2001-01-01 00:00:00")
636
+ # hours = TimeStep.new("hours since 2001-01-01 00:00:00")
637
+ # pair = days.to(hours)
638
+ # pair.forward(1)
639
+ # # => 24
640
+ #
641
+ # @param other [String, TimeStep]
642
+ #
643
+ # @return [TimeStep::Pair]
644
+ def to (other)
645
+ return Pair.new(self, other)
446
646
  end
447
647
 
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
648
+ # Creates new timeperiod object corresponding given time or index for
649
+ # start and last.
650
+ #
651
+ # @example
652
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
653
+ # ts.period(0, 1)
654
+ # #=> #<TimePeriod '1 days' [2001-01-01T00:00:00+00:00, 2001-01-02T00:00:00+00:00] >
655
+ # ts.period("2001", "2002", ends: "[)")
656
+ # #=> #<TimePeriod '365 days' [2001-01-01T00:00:00+00:00, 2002-01-01T00:00:00+00:00) >
657
+ #
658
+ # @param start [Numeric, DateTime]
659
+ # @param last [Numeric, DateTime]
660
+ # @param ends [String] one of "[]", "()", "[)", "(]"
661
+ #
662
+ # @return [TimePeriod]
663
+ def period (start, last, ends: "[]")
664
+ idx1 = if start.kind_of?(Numeric)
665
+ start
666
+ else
667
+ index_at(start)
668
+ end
669
+ idx2 = if last.kind_of?(Numeric)
670
+ last
671
+ else
672
+ index_at(last)
673
+ end
461
674
  origin = time_at(idx1)
462
675
  numeric = (idx2 - idx1) * @numeric
463
676
  interval_spec = format("%g %s", numeric, @symbol)
464
- return TimePeriod.new(interval_spec, since: origin, calendar: @calendar.name, bc: @bc, boundary: boundary)
677
+ return TimePeriod.new(interval_spec, since: origin, calendar: @calendar, ends: ends)
465
678
  end
466
679
 
467
-
680
+ # Creates new timestep range object.
681
+ #
682
+ # @example
683
+ # ts = TimeStep.new("days since 2001-01-01 00:00:00")
684
+ # ts.range(0, 1)
685
+ # ts.range("2001", "2002", ends: "[)")
686
+ # ts.range("2001", 1)
687
+ #
688
+ # @param start [Numeric, DateTime]
689
+ # @param last [Numeric, DateTime]
690
+ # @param count [Integer]
691
+ # @param ends [String] one of "[]", "()", "[)", "(]"
692
+ #
693
+ # @return [TimeStep::Range]
694
+ def range (start, last = nil, count: nil, ends: "[]")
695
+ return TimeStep::Range.new(self, start, last, count: count, ends: ends)
696
+ end
697
+
468
698
  end
469
699