timesteps 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,63 @@
1
+ require "timesteps"
2
+
3
+ class TimeStep::Converter
4
+
5
+ def initialize (reference, calendar: "standard", bc: false)
6
+ @calendar = calendar
7
+ @bc = bc
8
+ case reference
9
+ when String
10
+ @reference = TimeStep.new(reference, calendar: calendar, bc: bc)
11
+ when TimeStep
12
+ @reference = reference
13
+ else
14
+ raise "first argument should be TimeStep or string"
15
+ end
16
+ @pair = {}
17
+ @target = {}
18
+ self["reference"] = reference
19
+ end
20
+
21
+ # Append or reset new target time step for the given name.
22
+ #
23
+ def []= (name, spec)
24
+ @pair[name] = TimeStep::Pair.new(@reference, spec, calendar: @calendar, bc: @bc)
25
+ @target[name] = @pair[name].to
26
+ return @target[name]
27
+ end
28
+
29
+ # Return target time step of the given name.
30
+ #
31
+ def [] (name)
32
+ return @target[name]
33
+ end
34
+
35
+ # Relete target of the given name.
36
+ #
37
+ def delete (name)
38
+ @pair.delete(name)
39
+ @target.delete(name)
40
+ end
41
+
42
+ def forward (*indices)
43
+ hash = {}
44
+ @pair.each do |name, conv|
45
+ hash[name] = conv.forward(*indices)
46
+ end
47
+ return hash
48
+ end
49
+
50
+ def valid? (*indices)
51
+ hash = {}
52
+ @pair.each do |name, conv|
53
+ hash[name] = conv.to.valid?(*conv.forward(*indices))
54
+ end
55
+ return hash
56
+ end
57
+
58
+ def time_at (index)
59
+ return @from.time_at(index)
60
+ end
61
+
62
+ end
63
+
@@ -0,0 +1,79 @@
1
+
2
+ class TimeStep
3
+
4
+ module DateTimeExt
5
+
6
+ refine DateTime do
7
+
8
+ # Returns time fraction in day units.
9
+ #
10
+ # @return [Rational]
11
+ def fraction ()
12
+ return (60*(60*hour + minute) + second + second_fraction).quo(86400)
13
+ end
14
+
15
+ def compare_md (other)
16
+ sday = self.day + self.fraction + self.offset
17
+ oday = other.day + other.fraction + other.offset
18
+ if self.month > other.month
19
+ return 1
20
+ elsif self.month < other.month
21
+ return -1
22
+ else
23
+ if sday > oday
24
+ return 1
25
+ elsif sday < oday
26
+ return -1
27
+ else
28
+ return 0
29
+ end
30
+ end
31
+ end
32
+
33
+ def compare_d (other)
34
+ sday = self.day + self.fraction + self.offset
35
+ oday = other.day + other.fraction + other.offset
36
+ if sday > oday
37
+ return 1
38
+ elsif sday < oday
39
+ return -1
40
+ else
41
+ return 0
42
+ end
43
+ end
44
+
45
+ # Calculate difference between the object and other object in years.
46
+ #
47
+ # @return [Integer]
48
+ def difference_in_years (other)
49
+ extra = 0
50
+ if self.year > other.year && self.compare_md(other) < 0
51
+ extra = -1
52
+ end
53
+ if self.year < other.year && self.compare_md(other) > 0
54
+ extra = 1
55
+ end
56
+ return self.year - other.year + extra
57
+ end
58
+
59
+ # Calculate difference between the object and other object in months.
60
+ #
61
+ # @return [Integer]
62
+ def difference_in_months (other)
63
+ extra = 0
64
+ if self.month > other.month && self.compare_d(other) < 0
65
+ extra = -1
66
+ end
67
+ if self.month < other.month && self.compare_d(other) > 0
68
+ extra = 1
69
+ end
70
+ return 12*(self.year - other.year) + self.month - other.month + extra
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+
@@ -0,0 +1,202 @@
1
+
2
+ class TimeStep::Pair
3
+
4
+ using TimeStep::DateTimeExt
5
+
6
+ # Constructs the object.
7
+ #
8
+ # The `from` and `to` arguments are either TimeStep or a string representing
9
+ # the time step definition ("<INTERVAL> since <TIME>").
10
+ # If a string representing the time step definition is given as an argument
11
+ # `from` or `to`, the options `calendar` and `bc` are used to construct
12
+ # TimeStep. The option `calendar` specifies the calendar for datetime
13
+ # calculation. The option `bc` is a flag whether AD/BC or EC when parsing
14
+ # timestamp for negative year.
15
+ #
16
+ # @param from [TimeStep, String]
17
+ # @param to [TimeStep, String]
18
+ # @option calendar [String, TimeStep::Calendar]
19
+ # @option bc [Boolean]
20
+ #
21
+
22
+ def initialize (from, to, calendar: "standard", bc: false)
23
+
24
+ case from
25
+ when String
26
+ @from = TimeStep.new(from, calendar: calendar, bc: bc)
27
+ when TimeStep
28
+ @from = from
29
+ else
30
+ raise "first argument should be TimeStep or string"
31
+ end
32
+
33
+ case to
34
+ when String
35
+ @to = TimeStep.new(to, calendar: calendar, bc: bc)
36
+ when TimeStep
37
+ @to = to
38
+ else
39
+ raise "second argument should be TimeStep or string"
40
+ end
41
+
42
+ @from_origin = @from.origin
43
+ @to_origin = @to.origin
44
+
45
+ @from_interval = @from.interval
46
+ @to_interval = @to.interval
47
+
48
+ @from_symbol = @from.symbol
49
+ @to_symbol = @to.symbol
50
+
51
+ if @from_symbol == :years or @from_symbol == :months or
52
+ @to_symbol == :years or @to_symbol == :months
53
+ @numeric_conversion = false
54
+ else
55
+ @numeric_conversion = true
56
+ end
57
+
58
+ @difference = ( (@from_origin.jd + @from_origin.fraction - @from_origin.offset) -
59
+ (@to_origin.jd + @to_origin.fraction - @to_origin.offset) ) * 86400
60
+ end
61
+
62
+ attr_reader :from, :to, :difference
63
+
64
+ def inspect
65
+ "#<TimeStep::Pair from='#{from.inspect}' to='#{to.inspect}'>"
66
+ end
67
+
68
+ # Returns true if other has same contents of `from` and `to` as self has.
69
+ #
70
+ # @return [Boolean]
71
+ def == (other)
72
+ return @from == other.from &&
73
+ @to == other.to
74
+ end
75
+
76
+ def time_at (*indices)
77
+ @from.time_at(*indices)
78
+ end
79
+
80
+ def forward (*indices, &block)
81
+ if indices.size == 1
82
+ index = indices.first.to_r
83
+ if @numeric_conversion
84
+ value = (index*@from_interval + @difference).quo(@to_interval)
85
+ else
86
+ case @from_symbol
87
+ when :years, :months
88
+ target = @from.time_at(index)
89
+ else
90
+ target = @from_origin + index*(@from_interval.quo(86400))
91
+ end
92
+ case @to_symbol
93
+ when :years
94
+ value = target.difference_in_years(@to_origin).quo(@to.numeric)
95
+ when :months
96
+ value = target.difference_in_months(@to_origin).quo(@to.numeric)
97
+ else
98
+ value = (target.ajd - @to_origin.ajd).quo(@to_interval)
99
+ end
100
+ end
101
+ value = value.to_i if value.denominator == 1
102
+ if block
103
+ return block[value]
104
+ else
105
+ return value
106
+ end
107
+ else
108
+ if block
109
+ return indices.map { |index| block[forward(index)] }
110
+ else
111
+ return indices.map { |index| forward(index) }
112
+ end
113
+ end
114
+ end
115
+
116
+ alias [] forward
117
+
118
+ def forward_time (index)
119
+ if @numeric_conversion
120
+ return @to.time_at((index*@from_interval + @difference).quo(@to_interval))
121
+ else
122
+ case @from_symbol
123
+ when :years, :months
124
+ target = @from.time_at(index)
125
+ else
126
+ target = @from_origin + index*(@from_interval.quo(86400))
127
+ end
128
+ case @to_symbol
129
+ when :years
130
+ index = (target.year - @from_origin.year).quo(@to.numeric)
131
+ when :months
132
+ ms = 12*(target.year - @from_origin.year)
133
+ ms += target.month - @from_origin.month
134
+ index = ms.quo(@to.numeric)
135
+ else
136
+ index = (target.ajd - @from_origin.ajd).quo(@to_interval)
137
+ end
138
+ return @to.time_at(index)
139
+ end
140
+ end
141
+
142
+ def inverse (*indices, &block)
143
+ if indices.size == 1
144
+ index = indices.first.to_r
145
+ if @numeric_conversion
146
+ value = (index*@to_interval - @difference).quo(@from_interval)
147
+ else
148
+ case @to_symbol
149
+ when :years, :months
150
+ target = @to.time_at(index)
151
+ else
152
+ target = @to_origin + index*(@to_interval.quo(86400))
153
+ end
154
+ case @from_symbol
155
+ when :years
156
+ value = target.difference_in_years(@from_origin).quo(@from.numeric)
157
+ when :months
158
+ value = target.difference_in_months(@from_origin).quo(@from.numeric)
159
+ else
160
+ value = (target.ajd - @from_origin.ajd).quo(@from_interval)
161
+ end
162
+ if block
163
+ return block[value]
164
+ else
165
+ return value
166
+ end
167
+ end
168
+ else
169
+ if block
170
+ return indices.map { |index| block[inverse(index)] }
171
+ else
172
+ return indices.map { |index| inverse(index) }
173
+ end
174
+ end
175
+ end
176
+
177
+ #
178
+ def inverse_time (index)
179
+ if @numeric_conversion
180
+ return @from.time_at((index*@to_interval - @difference).quo(@from_interval))
181
+ else
182
+ case @to_symbol
183
+ when :years, :months
184
+ target = @to.time_at(index)
185
+ else
186
+ target = @to_origin + index*(@to_interval.quo(86400))
187
+ end
188
+ case @from_symbol
189
+ when :years
190
+ index = (target.year - @to_origin.year).quo(@from.numeric)
191
+ when :months
192
+ ms = 12*(target.year - @to_origin.year)
193
+ ms += target.month - @to_origin.month
194
+ index = ms.quo(@from.numeric)
195
+ else
196
+ index = (target.ajd - @to_origin.ajd).quo(@from_interval)
197
+ end
198
+ return @from.time_at(index)
199
+ end
200
+ end
201
+
202
+ end
data/lib/timesteps.rb ADDED
@@ -0,0 +1,10 @@
1
+
2
+ require "date"
3
+ require "timesteps/datetimelike"
4
+ require "timesteps/parse_timestamp"
5
+ require "timesteps/format"
6
+ require "timesteps/timestepdatetimeext"
7
+ require "timesteps/calendar"
8
+ require "timesteps/timestep"
9
+ require "timesteps/timesteppair"
10
+ require "timesteps/timestepconverter"
@@ -0,0 +1,330 @@
1
+ # rspec-power_assert
2
+ require 'timesteps'
3
+ require 'rspec-power_assert'
4
+
5
+ describe "AllLeap.new" do
6
+
7
+ example 'full arguments' do
8
+
9
+ is_asserted_by {
10
+ DateTime::AllLeap.new(-4712,1,1,0,0,0,0).is_a?(DateTime::AllLeap)
11
+ }
12
+
13
+ d = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
14
+
15
+ is_asserted_by { d.year == 2001 }
16
+ is_asserted_by { d.month == 3 }
17
+ is_asserted_by { d.day == 5 }
18
+ is_asserted_by { d.hour == 9 }
19
+ is_asserted_by { d.minute == 35 }
20
+ is_asserted_by { d.second + d.second_fraction == 12.5 }
21
+ is_asserted_by { d.offset == 9.quo(24) }
22
+
23
+ end
24
+
25
+ example 'ommitted arguments' do
26
+
27
+ is_asserted_by {
28
+ DateTime::AllLeap.new().is_a?(DateTime::AllLeap)
29
+ }
30
+
31
+ d = DateTime::AllLeap.new()
32
+
33
+ is_asserted_by { d.year == -4712 }
34
+ is_asserted_by { d.month == 1 }
35
+ is_asserted_by { d.day == 1 }
36
+ is_asserted_by { d.hour == 0 }
37
+ is_asserted_by { d.minute == 0 }
38
+ is_asserted_by { d.second + d.second_fraction == 0.0 }
39
+ is_asserted_by { d.offset == 0 }
40
+
41
+ end
42
+
43
+ example "raised for invalid date" do
44
+
45
+ # leap day for allleap calendar
46
+ is_asserted_by {
47
+ DateTime::AllLeap.new(2020,2,29).is_a?(DateTime::AllLeap)
48
+ }
49
+
50
+ # date not exist
51
+ expect { DateTime::AllLeap.new(2020,4,31) }.to raise_error(RuntimeError)
52
+
53
+ end
54
+
55
+ end
56
+
57
+ describe "AllLeap.jd" do
58
+
59
+ example "epoch" do
60
+
61
+ d = DateTime::AllLeap.jd(0)
62
+ is_asserted_by { d.jd == 0 }
63
+
64
+ end
65
+
66
+ example "unixpoch" do
67
+
68
+ jd = DateTime::AllLeap::UNIX_EPOCH_IN_AJD.ceil
69
+
70
+ d = DateTime::AllLeap.jd(jd)
71
+ is_asserted_by { d.jd == jd }
72
+ is_asserted_by { d.year == 1970 }
73
+ is_asserted_by { d.month == 1 }
74
+ is_asserted_by { d.day == 1 }
75
+
76
+ end
77
+
78
+ end
79
+
80
+ describe "AllLeap#<=>" do
81
+
82
+ example {
83
+
84
+ d1 = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
85
+ d2 = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
86
+
87
+ is_asserted_by { d1 == d2 }
88
+
89
+ }
90
+
91
+ end
92
+
93
+ describe "AllLeap.parse_timestamp" do
94
+
95
+ example '2001-03-05T09:35:12.5+09:00' do
96
+
97
+ d1 = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
98
+
99
+ [
100
+ '2001-03-05T09:35:12.5+09:00',
101
+ '2001-03-05 09:35:12.5 +09:00',
102
+ '2001-03-05 09:35:12.5 +0900',
103
+ '2001-03-05 09:35:12.5 +900',
104
+ '2001-03-05 09:35:12.5 +09',
105
+ '2001-03-05 09:35:12.5 +9',
106
+ '2001/03/05 09:35:12.5 +09:00',
107
+ '2001-3-5 9:35:12.5 +09:00',
108
+ '05,Mar,2001 09:35:12.5 +09:00',
109
+ '05-Mar-2001 09:35:12.5 +09:00',
110
+ ].each do |string|
111
+
112
+ d2 = DateTime::AllLeap.parse_timestamp(string)
113
+
114
+ is_asserted_by { d2 == d1 }
115
+ end
116
+
117
+ end
118
+
119
+ example 'raise for "2000-02-29"' do
120
+
121
+ # leap day for noleap calendar
122
+ is_asserted_by { DateTime::AllLeap.parse_timestamp("2000-2-29").is_a?(DateTime::AllLeap) }
123
+
124
+ end
125
+
126
+ example "with bc option" do
127
+
128
+ d1 = DateTime::AllLeap.new(0, 1, 1, 0, 0, 0, 0)
129
+ d2 = DateTime::AllLeap.parse_timestamp('BC 0001-1-1 00:00:00')
130
+ d3 = DateTime::AllLeap.parse_timestamp('-0001-1-1 00:00:00', bc: true)
131
+
132
+ is_asserted_by { d2 == d1 }
133
+ is_asserted_by { d3 == d1 }
134
+
135
+ end
136
+
137
+ example '2000-12-31 24:00:00' do
138
+
139
+ d1 = DateTime::AllLeap.new(2001, 1, 1, 0, 0, 0, 0)
140
+ d2 = DateTime::AllLeap.parse_timestamp('2000-12-31 24:00:00')
141
+
142
+ is_asserted_by { d2 == d1 }
143
+
144
+ end
145
+
146
+ end
147
+
148
+ describe "AllLeap#jd" do
149
+
150
+ example 'epoch' do
151
+
152
+ d = DateTime::AllLeap.new()
153
+ is_asserted_by { d.jd == 0 }
154
+
155
+ end
156
+
157
+ example 'few-years' do
158
+
159
+ d = DateTime::AllLeap.new(-4712,12,31)
160
+ is_asserted_by { d.jd == 365 }
161
+
162
+ d = DateTime::AllLeap.new(-4711,1,1)
163
+ is_asserted_by { d.jd == 366 }
164
+
165
+ d = DateTime::AllLeap.new(-4708,1,1)
166
+ is_asserted_by { d.jd == 366*4 }
167
+
168
+ d = DateTime::AllLeap.new(-4713,1,1)
169
+ is_asserted_by { d.jd == -366 }
170
+
171
+ d = DateTime::AllLeap.new(-4716,1,1)
172
+ is_asserted_by { d.jd == -366*4 }
173
+
174
+ end
175
+
176
+ end
177
+
178
+ describe "AllLeap#ajd" do
179
+
180
+ example 'epoch' do
181
+
182
+ d = DateTime::AllLeap.new()
183
+ is_asserted_by { d.ajd == -1.quo(2) }
184
+
185
+ end
186
+
187
+ example 'few-years' do
188
+
189
+ d = DateTime::AllLeap.new(-4712,12,31)
190
+ is_asserted_by { d.ajd == 365-1.quo(2) }
191
+
192
+ d = DateTime::AllLeap.new(-4711,1,1)
193
+ is_asserted_by { d.ajd == 366-1.quo(2) }
194
+
195
+ d = DateTime::AllLeap.new(-4708,1,1)
196
+ is_asserted_by { d.ajd == 366*4-1.quo(2) }
197
+
198
+ d = DateTime::AllLeap.new(-4713,1,1)
199
+ is_asserted_by { d.ajd == -366-1.quo(2) }
200
+
201
+ d = DateTime::AllLeap.new(-4716,1,1)
202
+ is_asserted_by { d.ajd == -366*4-1.quo(2) }
203
+
204
+ end
205
+
206
+ end
207
+
208
+ describe "AllLeap#strftime" do
209
+
210
+ example 'standard' do
211
+
212
+ d = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
213
+
214
+ is_asserted_by { d.strftime("%FT%T.%L%:z") == "2001-03-05T09:35:12.500+09:00" }
215
+ is_asserted_by { d.strftime("%Y%m%d%H%M%S") == "20010305093512" }
216
+
217
+ end
218
+
219
+ end
220
+
221
+
222
+ describe "AllLeap#next_XXX" do
223
+
224
+ example "next_year" do
225
+
226
+ d = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
227
+
228
+ is_asserted_by { d.next_year().year == 2002 }
229
+ is_asserted_by { d.next_year(10).year == 2011 }
230
+
231
+ is_asserted_by { d.prev_year().year == 2000 }
232
+ is_asserted_by { d.prev_year(10).year == 1991 }
233
+
234
+ end
235
+
236
+ example "next_month" do
237
+
238
+ d = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
239
+
240
+ is_asserted_by { d.next_month().month == 4 }
241
+ is_asserted_by { d.next_month(10).month == 1 }
242
+ is_asserted_by { (d>>1).month == 4 }
243
+ is_asserted_by { (d>>10).month == 1 }
244
+
245
+ is_asserted_by { d.prev_month().month == 2 }
246
+ is_asserted_by { d.prev_month(10).month == 5 }
247
+ is_asserted_by { (d<<1).month == 2 }
248
+ is_asserted_by { (d<<10).month == 5 }
249
+
250
+ is_asserted_by { (d>>12).year == 2002 }
251
+ is_asserted_by { (d>>10*12).year == 2011 }
252
+ is_asserted_by { (d<<12).year == 2000 }
253
+ is_asserted_by { (d<<10*12).year == 1991 }
254
+
255
+ end
256
+
257
+ example "next_day" do
258
+
259
+ d = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
260
+
261
+ is_asserted_by { d.next_day().day == 6 }
262
+ is_asserted_by { d.next_day(10).day == 15 }
263
+ is_asserted_by { d.next_day(31).day == 5 }
264
+
265
+ is_asserted_by { (d + 1).day == 6 }
266
+ is_asserted_by { (d + 10).day == 15 }
267
+ is_asserted_by { (d + 31).day == 5 }
268
+
269
+ is_asserted_by { d.prev_day().day == 4 }
270
+ is_asserted_by { d.prev_day(10).day == 24 }
271
+ is_asserted_by { d.prev_day(31).day == 3 }
272
+
273
+ is_asserted_by { (d - 1).day == 4 }
274
+ is_asserted_by { (d - 10).day == 24 }
275
+ is_asserted_by { (d - 31).day == 3 }
276
+
277
+ end
278
+
279
+ end
280
+
281
+ describe "AllLeap#fraction" do
282
+
283
+ example do
284
+
285
+ d1 = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
286
+ d2 = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.893, 9.quo(24))
287
+
288
+ is_asserted_by { d1.fraction == 9.quo(24) + 35.quo(1440) + 12.5.to_r.quo(86400) }
289
+ is_asserted_by { d2.fraction == 9.quo(24) + 35.quo(1440) + 12.893.to_r.quo(86400) }
290
+
291
+ end
292
+
293
+ end
294
+
295
+ describe "AllLeap#difference_in_years" do
296
+
297
+ example do
298
+
299
+ d1 = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
300
+ d2 = DateTime::AllLeap.new(2002, 3, 4, 9, 35, 12.5, 9.quo(24))
301
+ d3 = DateTime::AllLeap.new(2002, 3, 5, 9, 35, 11.5, 9.quo(24))
302
+ d4 = DateTime::AllLeap.new(2002, 3, 5, 9, 35, 12.5, 9.quo(24))
303
+
304
+ is_asserted_by { d2.difference_in_years(d1) == 0 }
305
+ is_asserted_by { d3.difference_in_years(d1) == 0 }
306
+ is_asserted_by { d4.difference_in_years(d1) == 1 }
307
+
308
+ end
309
+
310
+ end
311
+
312
+ describe "AllLeap#difference_in_months" do
313
+
314
+ example do
315
+
316
+ d1 = DateTime::AllLeap.new(2001, 3, 5, 9, 35, 12.5, 9.quo(24))
317
+ d2 = DateTime::AllLeap.new(2001, 4, 4, 9, 35, 12.5, 9.quo(24))
318
+ d3 = DateTime::AllLeap.new(2001, 4, 5, 9, 35, 11.5, 9.quo(24))
319
+ d4 = DateTime::AllLeap.new(2001, 4, 5, 9, 35, 12.5, 9.quo(24))
320
+ d5 = DateTime::AllLeap.new(2002, 3, 5, 9, 35, 12.5, 9.quo(24))
321
+
322
+ is_asserted_by { d2.difference_in_months(d1) == 0 }
323
+ is_asserted_by { d3.difference_in_months(d1) == 0 }
324
+ is_asserted_by { d4.difference_in_months(d1) == 1 }
325
+ is_asserted_by { d5.difference_in_months(d1) == 12 }
326
+
327
+ end
328
+
329
+ end
330
+