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.
- checksums.yaml +4 -4
- data/Note.ja.md +9 -0
- data/README.md +5 -5
- data/lib/timesteps.rb +2 -0
- data/lib/timesteps/datetime_parse_timestamp.rb +21 -12
- data/lib/timesteps/datetime_timestep.rb +64 -0
- data/lib/timesteps/datetimelike.rb +6 -17
- data/lib/timesteps/grads.rb +39 -18
- data/lib/timesteps/timeperiod.rb +50 -47
- data/lib/timesteps/timestep.rb +477 -247
- data/lib/timesteps/timestep_calendar.rb +49 -29
- data/lib/timesteps/timestep_converter.rb +19 -22
- data/lib/timesteps/timestep_datetime_ext.rb +6 -16
- data/lib/timesteps/timestep_pair.rb +37 -24
- data/lib/timesteps/timestep_range.rb +150 -0
- data/spec/allleap_spec.rb +4 -15
- data/spec/fixed360day_spec.rb +4 -15
- data/spec/noleap_spec.rb +2 -13
- data/spec/timestep_spec.rb +4 -27
- data/timesteps.gemspec +1 -1
- metadata +4 -2
data/lib/timesteps/timestep.rb
CHANGED
@@ -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
|
-
#
|
30
|
-
#
|
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
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
# @param
|
42
|
-
# @
|
43
|
-
# @
|
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,
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
@
|
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
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
78
|
-
|
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 =~
|
88
|
-
if $1 == ""
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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 /\
|
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
|
-
|
144
|
-
|
145
|
-
#
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
276
|
+
return format("%g %s", @numeric, @symbol)
|
153
277
|
end
|
154
278
|
end
|
155
279
|
|
156
|
-
#
|
157
|
-
#
|
158
|
-
# @return [
|
159
|
-
def
|
160
|
-
if
|
161
|
-
@
|
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
|
-
@
|
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
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
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
|
319
|
+
# Returns a string for inspection.
|
217
320
|
#
|
321
|
+
# @return [String]
|
218
322
|
def inspect
|
219
|
-
|
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
|
-
|
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
|
358
|
+
# Returns the datetime object (array) for the given index (indices).
|
241
359
|
#
|
242
|
-
# @
|
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
|
-
# @
|
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
|
-
|
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
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
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
|
436
|
+
# Returns the index (indices) for the given time (array).
|
300
437
|
#
|
301
|
-
# @
|
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
|
-
# @
|
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
|
-
|
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
|
-
|
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
|
328
|
-
#
|
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
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
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
|
344
|
-
# by the given `time`.
|
503
|
+
# Returns new timestep object which holds the given time as origin.
|
345
504
|
#
|
346
|
-
# @
|
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
|
-
|
351
|
-
|
352
|
-
|
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
|
525
|
+
return TimeStep.new(interval_spec, since: time, calendar: @calendar)
|
355
526
|
end
|
356
527
|
|
357
|
-
|
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
|
-
|
386
|
-
|
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
|
-
|
392
|
-
|
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
|
-
|
406
|
-
|
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
|
-
#
|
616
|
+
# Creates new timestep pair object which refers `other` as other unit
|
428
617
|
#
|
429
|
-
# @
|
430
|
-
|
431
|
-
|
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
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
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
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
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
|
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
|
|