timesteps 0.9.5 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/{LICENSES → LICENSE} +0 -0
- data/NEWS.md +8 -0
- data/Note.ja.md +9 -0
- data/README.md +20 -29
- data/lib/timesteps/{parse_timestamp.rb → datetime_parse_timestamp.rb} +25 -13
- data/lib/timesteps/datetime_timestep.rb +64 -0
- data/lib/timesteps/datetimelike.rb +12 -23
- data/lib/timesteps/{format.rb → datetimelike_format.rb} +0 -0
- data/lib/timesteps/grads.rb +39 -18
- data/lib/timesteps/time.rb +14 -0
- data/lib/timesteps/timeperiod.rb +205 -0
- data/lib/timesteps/timestep.rb +505 -268
- data/lib/timesteps/{calendar.rb → timestep_calendar.rb} +54 -29
- data/lib/timesteps/timestep_converter.rb +20 -23
- data/lib/timesteps/timestep_datetime_ext.rb +6 -16
- data/lib/timesteps/timestep_pair.rb +39 -26
- data/lib/timesteps/timestep_query.rb +57 -0
- data/lib/timesteps/timestep_range.rb +153 -0
- data/lib/timesteps.rb +8 -3
- 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 +24 -47
- data/spec/timesteppair_spec.rb +2 -2
- data/timesteps.gemspec +3 -3
- metadata +14 -7
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
|
@@ -21,236 +118,268 @@ class TimeStep
|
|
21
118
|
# * minutes, minute, mins, min
|
22
119
|
# * seconds, second, secs, sec, s
|
23
120
|
# * milliseconds, millisecond, msecs, msec, ms
|
24
|
-
# * microseconds, microsecond
|
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
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
# *
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
# @
|
43
|
-
# @
|
44
|
-
# @
|
45
|
-
# @
|
46
|
-
# @
|
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.
|
132
|
+
# The option `calendar` specifies the name of calendar for datetime calculation,
|
133
|
+
# * "standard", "gregorian" -> DateTime with Date::ITALY as start
|
134
|
+
# * "proleptic_gregorian" -> DateTime with Date::GREGORIAN as start
|
135
|
+
# * "proleptic_julian", "julian" -> DateTime with Date::JULIAN as start
|
136
|
+
# * "noleap", "365_day" -> DateTimeNoLeap
|
137
|
+
# * "allleap", "366_day" -> DateTimeAllLeap
|
138
|
+
# * "360_day" -> DateTimeFixed360Day
|
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,
|
146
|
+
def initialize (spec, since: nil, offset: nil, format: nil, calendar: "standard", tz: nil)
|
147
|
+
|
49
148
|
case calendar
|
50
149
|
when String
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
if since
|
56
|
-
parse_interval(spec)
|
57
|
-
case since
|
58
|
-
when String
|
59
|
-
@origin = @calendar.parse(since, format: format)
|
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)
|
60
153
|
else
|
61
|
-
|
62
|
-
|
154
|
+
@calendar = CALENDARS[calendar]
|
155
|
+
raise "specified calendar type '#{calendar}' is invalid" unless @calendar
|
63
156
|
end
|
157
|
+
when TimeStep::Calendar
|
158
|
+
@calendar = calendar
|
159
|
+
else
|
160
|
+
raise "invalid object for option 'calendar'"
|
161
|
+
end
|
162
|
+
|
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)
|
64
167
|
else
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
68
191
|
end
|
69
|
-
|
70
|
-
@originspec = @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
|
71
|
-
@definition = format("%s since %s", @intervalspec, @originspec)
|
72
|
-
@count = count
|
192
|
+
|
73
193
|
end
|
74
194
|
|
75
195
|
# @private
|
76
|
-
PATTERN_NUMERIC = '[\+\-]?\d*(?:\.\d+)?(?:[eE][\+\-]?\d+)?'
|
77
|
-
# @private
|
78
|
-
PATTERN_UNITS = 'ayears?|years?|months?|days?|hours?|minutes?|seconds?|d|hrs?|hrs?|h|mins?|secs?|s|milliseconds?|msecs?|ms|microseconds?'
|
79
|
-
|
80
196
|
def parse_interval (spec)
|
81
|
-
if spec.strip =~
|
82
|
-
if $1 == ""
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
87
203
|
symbol = $2
|
88
204
|
else
|
89
|
-
raise "
|
205
|
+
raise "the interval specification '#{spec}' is invalid."
|
90
206
|
end
|
91
207
|
@interval = @numeric
|
92
208
|
case symbol
|
93
|
-
when /\
|
94
|
-
@symbol = :days
|
95
|
-
@numeric *= 365.242198781.to_r
|
96
|
-
@interval = @numeric * 86400
|
97
|
-
when /\Ayears?/
|
209
|
+
when /\Ayears?\z/i
|
98
210
|
unless numeric.denominator == 1
|
99
211
|
raise "numeric factor for year should be an integer"
|
100
212
|
end
|
101
213
|
@symbol = :years
|
102
214
|
@interval *= 356.242*86400
|
103
|
-
when /\Amonths
|
215
|
+
when /\Amonths?\z/i
|
104
216
|
unless numeric.denominator == 1
|
105
217
|
raise "numeric factor for month should be an integer"
|
106
218
|
end
|
107
219
|
@symbol = :months
|
108
220
|
@interval *= 30.4368*86400
|
109
|
-
when /\A(days?|d)/
|
221
|
+
when /\A(days?|d)\z/i
|
110
222
|
@symbol = :days
|
111
223
|
@interval *= 86400
|
112
|
-
when /\A(hours?|hrs?|h)/
|
224
|
+
when /\A(hours?|hrs?|h)\z/i
|
113
225
|
@symbol = :hours
|
114
226
|
@interval *= 3600
|
115
|
-
when /\A(minutes?|mins?)/
|
227
|
+
when /\A(minutes?|mins?)\z/i
|
116
228
|
@symbol = :minutes
|
117
229
|
@interval *= 60
|
118
|
-
when /\A(seconds?|secs?|s)/
|
230
|
+
when /\A(seconds?|secs?|s)\z/i
|
119
231
|
@symbol = :seconds
|
120
|
-
when /\A(milliseconds?|msecs?|ms)/
|
232
|
+
when /\A(milliseconds?|msecs?|ms)\z/i
|
121
233
|
@symbol = :seconds
|
122
234
|
@interval *= 1.quo(1000)
|
123
|
-
when /\Amicroseconds
|
235
|
+
when /\Amicroseconds?\z/i
|
124
236
|
@symbol = :seconds
|
125
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
|
126
258
|
end
|
127
259
|
end
|
128
260
|
|
129
261
|
private :parse_interval
|
130
262
|
|
131
|
-
attr_reader :
|
132
|
-
:intervalspec,
|
133
|
-
:originspec,
|
134
|
-
:numeric,
|
263
|
+
attr_reader :numeric,
|
135
264
|
:symbol,
|
136
265
|
:interval,
|
137
266
|
:origin,
|
138
267
|
:calendar
|
139
268
|
|
140
|
-
|
141
|
-
|
142
|
-
#
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
if @count
|
147
|
-
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])
|
148
275
|
else
|
149
|
-
return
|
276
|
+
return format("%g %s", @numeric, @symbol)
|
150
277
|
end
|
151
278
|
end
|
152
279
|
|
153
|
-
#
|
154
|
-
#
|
155
|
-
# @return [
|
156
|
-
def
|
157
|
-
if
|
158
|
-
@
|
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")
|
159
286
|
else
|
160
|
-
@
|
287
|
+
return @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
|
161
288
|
end
|
162
|
-
return limit
|
163
|
-
end
|
164
|
-
|
165
|
-
def set_limit (time, format: nil)
|
166
|
-
case time
|
167
|
-
when String
|
168
|
-
time = @calendar.parse(time, format: format)
|
169
|
-
end
|
170
|
-
self.limit = time
|
171
289
|
end
|
172
290
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
else
|
180
|
-
return false
|
181
|
-
end
|
182
|
-
else
|
183
|
-
return indices.map{|index| valid?(index) }
|
184
|
-
end
|
185
|
-
else
|
186
|
-
if indices.size == 1
|
187
|
-
return true
|
188
|
-
else
|
189
|
-
return indices.map{|index| true }
|
190
|
-
end
|
191
|
-
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)
|
192
297
|
end
|
193
298
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
"calendar" => @calendar.name,
|
200
|
-
"numeric" => @numeric,
|
201
|
-
"symbol" => @symbol.to_s,
|
202
|
-
"interval" => @interval,
|
203
|
-
"origin" => @origin.clone,
|
204
|
-
"count" => @count,
|
205
|
-
"bc" => @calendar.bc?,
|
206
|
-
}
|
299
|
+
# Returns the time offset of origin time.
|
300
|
+
#
|
301
|
+
# @return [Rational]
|
302
|
+
def offset
|
303
|
+
return @origin.offset
|
207
304
|
end
|
208
305
|
|
209
|
-
|
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
|
210
318
|
|
211
|
-
# Returns
|
319
|
+
# Returns a string for inspection.
|
212
320
|
#
|
321
|
+
# @return [String]
|
213
322
|
def inspect
|
214
|
-
|
323
|
+
options = ""
|
324
|
+
case @calendar.name
|
325
|
+
when "standard", "gregorian"
|
326
|
+
else
|
327
|
+
options << " calendar='#{calendar.name}'"
|
328
|
+
end
|
329
|
+
"#<TimeStep definition='#{definition}'#{options}>"
|
215
330
|
end
|
216
331
|
|
217
332
|
# Returns true if other has same contents of `definition` and `calendar`
|
218
333
|
# as self has.
|
219
334
|
#
|
335
|
+
# @param other [TimeStep]
|
336
|
+
#
|
220
337
|
# @return [Boolean]
|
221
338
|
def == (other)
|
222
|
-
return
|
339
|
+
return definition == other.definition && @calendar == other.calendar
|
223
340
|
end
|
224
341
|
|
342
|
+
# @private
|
225
343
|
def user_to_days (index)
|
226
344
|
return ( @interval * index.to_r ).quo(86400)
|
227
345
|
end
|
228
346
|
|
347
|
+
# @private
|
229
348
|
def days_to_user (index)
|
230
|
-
|
349
|
+
if @interval == 0
|
350
|
+
return 0
|
351
|
+
else
|
352
|
+
return ( 86400 * index.to_r ).quo(@interval)
|
353
|
+
end
|
231
354
|
end
|
232
355
|
|
233
356
|
private :user_to_days, :days_to_user
|
234
357
|
|
235
|
-
# Returns the
|
358
|
+
# Returns the datetime object (array) for the given index (indices).
|
236
359
|
#
|
237
|
-
# @
|
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 ...>
|
238
366
|
#
|
239
|
-
# @
|
367
|
+
# @param indices [Array<Numeric>]
|
368
|
+
#
|
369
|
+
# @return [DateTime, Array<DateTime>]
|
240
370
|
def time_at (*indices)
|
241
371
|
if indices.size == 1
|
242
372
|
index = indices.first
|
243
|
-
raise ArgumentError, "index should be numeric" unless index.is_a?(Numeric)
|
244
|
-
index = index.to_r
|
373
|
+
raise ArgumentError, "index argument should be a numeric" unless index.is_a?(Numeric)
|
245
374
|
case @symbol
|
246
375
|
when :years
|
247
|
-
unless index.denominator == 1
|
248
|
-
raise "index
|
376
|
+
unless (index*@numeric).denominator == 1
|
377
|
+
raise ArgumentError, "index argument should be an integer for years"
|
249
378
|
end
|
250
379
|
return @origin.next_year(index*@numeric)
|
251
380
|
when :months
|
252
|
-
unless index.denominator == 1
|
253
|
-
raise "index
|
381
|
+
unless (index*@numeric).denominator == 1
|
382
|
+
raise ArgumentError, "index argument should be an integer for months"
|
254
383
|
end
|
255
384
|
return @origin.next_month(index*@numeric)
|
256
385
|
else
|
@@ -264,48 +393,75 @@ class TimeStep
|
|
264
393
|
end
|
265
394
|
end
|
266
395
|
|
267
|
-
|
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>]
|
268
410
|
#
|
269
|
-
# @param indices [Array]
|
270
411
|
# @return [DateTime, Array<DateTime>]
|
271
|
-
def
|
412
|
+
def duration_at (*indices)
|
272
413
|
if indices.size == 1
|
273
414
|
index = indices.first
|
274
|
-
case @symbol
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
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
|
288
429
|
days = days.to_i if days.denominator == 1
|
289
430
|
return days
|
290
431
|
else
|
291
|
-
return indices.map{ |index|
|
432
|
+
return indices.map{ |index| duration_at(index) }
|
292
433
|
end
|
293
434
|
end
|
294
435
|
|
295
|
-
# Returns the index for the given time
|
436
|
+
# Returns the index (indices) for the given time (array).
|
296
437
|
#
|
297
|
-
# @
|
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
|
298
446
|
#
|
299
|
-
# @
|
447
|
+
# @param times [Array<DateTime>]
|
448
|
+
# @param format [String] template string for strptime for parsing time
|
449
|
+
#
|
450
|
+
# @return [Numeric, Array<Numeric>]
|
300
451
|
def index_at (*times, format: nil)
|
301
452
|
if times.size == 1
|
302
453
|
time = times.first
|
303
|
-
time =
|
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)
|
304
456
|
case @symbol
|
305
457
|
when :years
|
306
|
-
|
458
|
+
diff = time.difference_in_years(@origin)
|
459
|
+
frac = diff - diff.floor
|
460
|
+
index = diff.floor.quo(@numeric.to_i) + frac
|
307
461
|
when :months
|
308
|
-
|
462
|
+
diff = time.difference_in_months(@origin)
|
463
|
+
frac = diff - diff.floor
|
464
|
+
index = diff.floor.quo(@numeric.to_i) + frac
|
309
465
|
else
|
310
466
|
jday = @calendar.date2jday(time.year, time.month, time.day)
|
311
467
|
fday = time.fraction
|
@@ -319,145 +475,226 @@ class TimeStep
|
|
319
475
|
return times.map{|time| index_at(time, format: format) }
|
320
476
|
end
|
321
477
|
end
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
else
|
333
|
-
min = prev_index_of(ge)
|
334
|
-
end
|
335
|
-
end
|
336
|
-
if gt
|
337
|
-
case gt
|
338
|
-
when Numeric
|
339
|
-
if lt.to_r.denominator == 1
|
340
|
-
min = gt.to_i + 1
|
341
|
-
else
|
342
|
-
min = gt.ceil
|
343
|
-
end
|
344
|
-
else
|
345
|
-
min = next_index_of(gt)
|
346
|
-
end
|
347
|
-
end
|
348
|
-
if lt
|
349
|
-
case lt
|
350
|
-
when Numeric
|
351
|
-
if lt.to_r.denominator == 1
|
352
|
-
max = lt.to_i - 1
|
353
|
-
else
|
354
|
-
max = lt.floor
|
355
|
-
end
|
356
|
-
else
|
357
|
-
max = prev_index_of(lt)
|
358
|
-
end
|
359
|
-
end
|
360
|
-
if le
|
361
|
-
case le
|
362
|
-
when Numeric
|
363
|
-
max = le.ceil
|
364
|
-
else
|
365
|
-
max = next_index_of(le)
|
366
|
-
end
|
367
|
-
end
|
368
|
-
return (min..max).to_a
|
369
|
-
end
|
370
|
-
|
371
|
-
# Returns TimeStep object which has origin time determined
|
372
|
-
# by the given `index`.
|
478
|
+
|
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'>
|
373
488
|
#
|
374
489
|
# @param index [Numeric]
|
490
|
+
# @param with [String, Symbol] "index" : shift by index , "duration" : shift by duration
|
375
491
|
#
|
376
492
|
# @return [TimeStep]
|
377
|
-
def shift_origin (index)
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
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
|
385
502
|
end
|
386
503
|
|
387
|
-
# Returns
|
388
|
-
#
|
504
|
+
# Returns new timestep object which holds the given time as origin.
|
505
|
+
#
|
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'>
|
389
515
|
#
|
390
|
-
# @param time [DateTime]
|
516
|
+
# @param time [DateTime, String]
|
391
517
|
#
|
392
518
|
# @return [TimeStep]
|
393
519
|
def new_origin (time)
|
394
|
-
|
395
|
-
|
396
|
-
|
520
|
+
time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
|
521
|
+
if @wday
|
522
|
+
origin = time - time.wday + WDAY[@wday]
|
523
|
+
origin -= 7 unless time >= origin
|
524
|
+
time = origin
|
397
525
|
end
|
398
|
-
return TimeStep.new(
|
526
|
+
return TimeStep.new(interval_spec, since: time, calendar: @calendar)
|
399
527
|
end
|
400
528
|
|
401
|
-
|
402
|
-
|
403
|
-
def each (limit = nil, incr = 1, &block)
|
404
|
-
if limit.nil?
|
405
|
-
raise "step method require count" unless @count
|
406
|
-
limit = @count - 1
|
407
|
-
end
|
408
|
-
if block
|
409
|
-
0.step(limit, incr) do |k|
|
410
|
-
block.call(time_at(k))
|
411
|
-
end
|
412
|
-
else
|
413
|
-
return Enumerator.new {|y|
|
414
|
-
0.step(limit, incr) do |k|
|
415
|
-
y << time_at(k)
|
416
|
-
end
|
417
|
-
}
|
418
|
-
end
|
419
|
-
end
|
420
|
-
|
421
|
-
def times (&block)
|
422
|
-
raise "step method require count" unless @count
|
423
|
-
(0...@count).each(&block)
|
424
|
-
end
|
425
|
-
|
426
|
-
# Creates TimeStep::Pair
|
529
|
+
# Truncate the given datetime to the unit of the object.
|
427
530
|
#
|
531
|
+
# @example
|
532
|
+
# hours = TimeStep.new("hours since 2001-01-01 00:00:00")
|
533
|
+
# hours.truncate("2001-01-15 12:35:00")
|
534
|
+
# # => #<DateTime: 2001-01-15T12:00:00+00:00 ...>
|
428
535
|
#
|
429
|
-
|
430
|
-
|
536
|
+
# days = TimeStep.new("days since 2001-01-01 00:00:00")
|
537
|
+
# days.truncate("2001-01-15 12:00:00")
|
538
|
+
# # => #<DateTime: 2001-01-15T00:00:00+00:00 ...>
|
539
|
+
#
|
540
|
+
# months = TimeStep.new("months since 2001-01-01 00:00:00")
|
541
|
+
# months.truncate("2001-05-15 12:00:00")
|
542
|
+
# # => #<DateTime: 2001-05-01T00:00:00+00:00 ...>
|
543
|
+
#
|
544
|
+
# @param time [DateTime, String]
|
545
|
+
#
|
546
|
+
# @return [DateTime]
|
547
|
+
def truncate (time)
|
548
|
+
return time_at(index_at(time).floor)
|
431
549
|
end
|
432
|
-
|
550
|
+
|
551
|
+
# Returns next integer index of the given time
|
552
|
+
#
|
553
|
+
# @example
|
554
|
+
# ts = TimeStep.new("days since 2001-01-01 00:00:00")
|
555
|
+
# ts.next_index_of("2001-01-14 12:00:00")
|
556
|
+
# #=> 14
|
557
|
+
# ts.next_index_of("2001-01-15 00:00:00")
|
558
|
+
# #=> 15
|
559
|
+
#
|
560
|
+
# @param time [DateTime, String]
|
561
|
+
#
|
562
|
+
# @return [Numeric]
|
433
563
|
def next_index_of (time)
|
434
|
-
|
435
|
-
|
436
|
-
time = @calendar.parse(time)
|
437
|
-
end
|
438
|
-
index = index_at(time).to_r
|
439
|
-
if index.denominator == 1
|
440
|
-
return index.to_i + 1
|
441
|
-
else
|
442
|
-
return index.ceil
|
443
|
-
end
|
564
|
+
time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
|
565
|
+
return index_at(time).floor + 1
|
444
566
|
end
|
445
567
|
|
568
|
+
# Returns previous integer index of the given time
|
569
|
+
#
|
570
|
+
# @example
|
571
|
+
# ts = TimeStep.new("days since 2001-01-01 00:00:00")
|
572
|
+
# ts.prev_index_of("2001-01-14 12:00:00")
|
573
|
+
# #=> 13
|
574
|
+
# ts.prev_index_of("2001-01-15 00:00:00")
|
575
|
+
# #=> 13
|
576
|
+
#
|
577
|
+
# @param time [DateTime, String]
|
578
|
+
#
|
579
|
+
# @return [Numeric]
|
446
580
|
def prev_index_of (time)
|
447
|
-
|
581
|
+
time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
|
582
|
+
return index_at(time).ceil - 1
|
448
583
|
end
|
449
584
|
|
585
|
+
# Returns next time of the given time
|
586
|
+
#
|
587
|
+
# @example
|
588
|
+
# ts = TimeStep.new("days since 2001-01-01 00:00:00")
|
589
|
+
# ts.next_time_of("2001-01-14 12:00:00")
|
590
|
+
# #=> #<DateTime: 2001-01-15T00:00:00+00:00 ...>
|
591
|
+
# ts.next_time_of("2001-01-15 00:00:00")
|
592
|
+
# #=> #<DateTime: 2001-01-16T00:00:00+00:00 ...>
|
593
|
+
#
|
594
|
+
# @param time [DateTime, String]
|
595
|
+
#
|
596
|
+
# @return [DateTime]
|
450
597
|
def next_time_of (time)
|
451
598
|
return time_at(next_index_of(time))
|
452
599
|
end
|
453
600
|
|
601
|
+
# Returns previous time of the given time
|
602
|
+
#
|
603
|
+
# @example
|
604
|
+
# ts = TimeStep.new("days since 2001-01-01 00:00:00")
|
605
|
+
# ts.prev_time_of("2001-01-14 12:00:00")
|
606
|
+
# #=> #<DateTime: 2001-01-14T00:00:00+00:00 ...>
|
607
|
+
# ts.prev_time_of("2001-01-15 00:00:00")
|
608
|
+
# #=> #<DateTime: 2001-01-14T00:00:00+00:00 ...>
|
609
|
+
#
|
610
|
+
# @param time [String, DateTime]
|
611
|
+
#
|
612
|
+
# @return [DateTime]
|
454
613
|
def prev_time_of (time)
|
455
614
|
return time_at(prev_index_of(time))
|
456
615
|
end
|
457
616
|
|
458
|
-
|
459
|
-
|
617
|
+
# Creates new timestep pair object which refers `other` as other unit
|
618
|
+
#
|
619
|
+
# @example
|
620
|
+
# days = TimeStep.new("days since 2001-01-01 00:00:00")
|
621
|
+
# pair = days.in("hours")
|
622
|
+
# pair.forward(1)
|
623
|
+
# # => 24
|
624
|
+
#
|
625
|
+
# @param other [String, TimeStep]
|
626
|
+
#
|
627
|
+
# @return [TimeStep::Pair]
|
628
|
+
def in (unit)
|
629
|
+
other = TimeStep.new(unit, since: @origin, calendar: @calendar)
|
630
|
+
return Pair.new(self, other)
|
631
|
+
end
|
632
|
+
|
633
|
+
# Creates new timestep pair object which refers `other` from `self`
|
634
|
+
#
|
635
|
+
# @example
|
636
|
+
# days = TimeStep.new("days since 2001-01-01 00:00:00")
|
637
|
+
# hours = TimeStep.new("hours since 2001-01-01 00:00:00")
|
638
|
+
# pair = days.to(hours)
|
639
|
+
# pair.forward(1)
|
640
|
+
# # => 24
|
641
|
+
#
|
642
|
+
# @param other [String, TimeStep]
|
643
|
+
#
|
644
|
+
# @return [TimeStep::Pair]
|
645
|
+
def to (other)
|
646
|
+
return Pair.new(self, other)
|
460
647
|
end
|
461
648
|
|
649
|
+
# Creates new timeperiod object corresponding given time or index for
|
650
|
+
# start and last.
|
651
|
+
#
|
652
|
+
# @example
|
653
|
+
# ts = TimeStep.new("days since 2001-01-01 00:00:00")
|
654
|
+
# ts.period(0, 1)
|
655
|
+
# #=> #<TimePeriod '1 days' [2001-01-01T00:00:00+00:00, 2001-01-02T00:00:00+00:00] >
|
656
|
+
# ts.period("2001", "2002", ends: "[)")
|
657
|
+
# #=> #<TimePeriod '365 days' [2001-01-01T00:00:00+00:00, 2002-01-01T00:00:00+00:00) >
|
658
|
+
#
|
659
|
+
# @param start [Numeric, DateTime]
|
660
|
+
# @param last [Numeric, DateTime]
|
661
|
+
# @param ends [String] one of "[]", "()", "[)", "(]"
|
662
|
+
#
|
663
|
+
# @return [TimePeriod]
|
664
|
+
def period (start, last, ends: "[]")
|
665
|
+
idx1 = if start.kind_of?(Numeric)
|
666
|
+
start
|
667
|
+
else
|
668
|
+
index_at(start)
|
669
|
+
end
|
670
|
+
idx2 = if last.kind_of?(Numeric)
|
671
|
+
last
|
672
|
+
else
|
673
|
+
index_at(last)
|
674
|
+
end
|
675
|
+
origin = time_at(idx1)
|
676
|
+
numeric = (idx2 - idx1) * @numeric
|
677
|
+
interval_spec = format("%g %s", numeric, @symbol)
|
678
|
+
return TimePeriod.new(interval_spec, since: origin, calendar: @calendar, ends: ends)
|
679
|
+
end
|
680
|
+
|
681
|
+
# Creates new timestep range object.
|
682
|
+
#
|
683
|
+
# @example
|
684
|
+
# ts = TimeStep.new("days since 2001-01-01 00:00:00")
|
685
|
+
# ts.range(0, 1)
|
686
|
+
# ts.range("2001", "2002", ends: "[)")
|
687
|
+
# ts.range("2001", 1)
|
688
|
+
#
|
689
|
+
# @param start [Numeric, DateTime]
|
690
|
+
# @param last [Numeric, DateTime]
|
691
|
+
# @param count [Integer]
|
692
|
+
# @param ends [String] one of "[]", "()", "[)", "(]"
|
693
|
+
#
|
694
|
+
# @return [TimeStep::Range]
|
695
|
+
def range (start, last = nil, count: nil, ends: "[]")
|
696
|
+
return TimeStep::Range.new(self, start, last, count: count, ends: ends)
|
697
|
+
end
|
698
|
+
|
462
699
|
end
|
463
700
|
|