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