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.
@@ -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
+