timesteps 0.9.3 → 0.9.5

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,34 @@
1
+ class DateTime
2
+
3
+ # datetime class represents `allleap` or `366_day` calendar
4
+ #
5
+ class AllLeap < DateTimeLike
6
+
7
+ extend DateTimeLikeExtension
8
+
9
+ # Number of days per year
10
+ DPY = 366
11
+
12
+ # Numbers of days per months
13
+ DPM = [0,31,29,31,30,31,30,31,31,30,31,30,31]
14
+
15
+ # Astronomical Julian day number of UNIX epoch
16
+ UNIX_EPOCH_IN_AJD = Rational(4891223,2)
17
+
18
+ def valid_date?
19
+ if @month != 2
20
+ return Date.valid_date?(@year, @month, @day)
21
+ else
22
+ return ( @day >= 1 and @day <= 29 )
23
+ end
24
+ end
25
+
26
+ private :valid_date?
27
+
28
+ def leap?
29
+ true
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,39 @@
1
+
2
+ class DateTime
3
+
4
+ # datetime class represents `noleap` or `365_day` calendar
5
+ #
6
+ class NoLeap < DateTimeLike
7
+
8
+ extend DateTimeLikeExtension
9
+
10
+ # Number of days per year
11
+ DPY = 365
12
+
13
+ # Numbers of days per months
14
+ DPM = [0,31,28,31,30,31,30,31,31,30,31,30,31]
15
+
16
+ # Astronomical Julian day number of UNIX epoch
17
+ UNIX_EPOCH_IN_AJD = Rational(4877859,2)
18
+
19
+ def valid_date?
20
+ if @month != 2
21
+ return Date.valid_date?(@year, @month, @day)
22
+ else
23
+ return ( @day >= 1 and @day <= 28 )
24
+ end
25
+ end
26
+
27
+ private :valid_date?
28
+
29
+ def strftime (spec)
30
+ DateTime.new(@year, @month, @day, @hour, @minute, @second, @offset).strftime(spec)
31
+ end
32
+
33
+ def leap?
34
+ false
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -424,107 +424,3 @@ class DateTimeLike
424
424
 
425
425
  end
426
426
 
427
- class DateTime
428
-
429
- # datetime class represents `noleap` or `365_day` calendar
430
- #
431
- class NoLeap < DateTimeLike
432
-
433
- extend DateTimeLikeExtension
434
-
435
- # Number of days per year
436
- DPY = 365
437
-
438
- # Numbers of days per months
439
- DPM = [0,31,28,31,30,31,30,31,31,30,31,30,31]
440
-
441
- # Astronomical Julian day number of UNIX epoch
442
- UNIX_EPOCH_IN_AJD = Rational(4877859,2)
443
-
444
- def valid_date?
445
- if @month != 2
446
- return Date.valid_date?(@year, @month, @day)
447
- else
448
- return ( @day >= 1 and @day <= 28 )
449
- end
450
- end
451
-
452
- private :valid_date?
453
-
454
- def strftime (spec)
455
- DateTime.new(@year, @month, @day, @hour, @minute, @second, @offset).strftime(spec)
456
- end
457
-
458
- def leap?
459
- false
460
- end
461
-
462
- end
463
-
464
- # datetime class represents `allleap` or `366_day` calendar
465
- #
466
- class AllLeap < DateTimeLike
467
-
468
- extend DateTimeLikeExtension
469
-
470
- # Number of days per year
471
- DPY = 366
472
-
473
- # Numbers of days per months
474
- DPM = [0,31,29,31,30,31,30,31,31,30,31,30,31]
475
-
476
- # Astronomical Julian day number of UNIX epoch
477
- UNIX_EPOCH_IN_AJD = Rational(4891223,2)
478
-
479
- def valid_date?
480
- if @month != 2
481
- return Date.valid_date?(@year, @month, @day)
482
- else
483
- return ( @day >= 1 and @day <= 29 )
484
- end
485
- end
486
-
487
- private :valid_date?
488
-
489
- def leap?
490
- true
491
- end
492
-
493
- end
494
-
495
- # datetime class represents `360_day` calendar
496
- #
497
- class Fixed360Day < DateTimeLike
498
-
499
- extend DateTimeLikeExtension
500
-
501
- # Number of days per year
502
- DPY = 360
503
-
504
- # Numbers of days per months
505
- DPM = [0,30,30,30,30,30,30,30,30,30,30,30,30]
506
-
507
- # Astronomical Julian day number of UNIX epoch
508
- UNIX_EPOCH_IN_AJD = Rational(4811039,2)
509
-
510
- def valid_date?
511
- if @day >= 31
512
- return false
513
- end
514
- if @month != 2
515
- return Date.valid_date?(@year, @month, @day)
516
- else
517
- return ( @day >= 1 and @day <= 30 )
518
- end
519
- end
520
-
521
- private :valid_date?
522
-
523
- def leap?
524
- raise NotImplementedError
525
- end
526
-
527
- end
528
-
529
- end
530
-
@@ -1,48 +1,63 @@
1
1
  class DateTime
2
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)
3
+ # @private
4
+ SYM_365_day = "365_day".intern
5
+ # @private
6
+ SYM_366_day = "366_day".intern
7
+ # @private
8
+ SYM_360_day = "360_day".intern
9
+
10
+ # Parses the given datetime expression and creates an instance.
11
+ # `DateTime._parse()` is called internally.
12
+ #
13
+ # @param spec [String]
14
+ # @option bc [Boolean]
15
+ #
16
+ # @return [DateTimeFixedDPY]
17
+
18
+ def self.parse_timestamp (spec, calendar: "standard", bc: false, format: nil)
19
+ raise "invalid option 'calendar'" if self != DateTime
20
+ case calendar.downcase.intern
21
+ when :standard, :gregorian
22
+ klass = DateTime
23
+ start = Date::ITALY
24
+ when :proleptic_gregorian
25
+ klass = DateTime
26
+ start = Date::GREGORIAN
27
+ when :proleptic_julian, :julian
28
+ klass = DateTime
29
+ start = Date::JULIAN
30
+ when :noleap, SYM_365_day
31
+ klass = DateTime::NoLeap
32
+ start = nil
33
+ when :allleap, SYM_366_day
34
+ klass = DateTime::AllLeap
35
+ start = nil
36
+ when SYM_360_day
37
+ klass = DateTime::Fixed360Day
38
+ start = nil
39
+ end
40
+ if format
41
+ hash = DateTime._strptime(spec, format)
42
+ raise "timestring doesn't match with format" unless hash
43
+ else
14
44
  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
45
  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
+ year, month, day, hour, minute, second, sec_fraction, offset =
47
+ hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset)
48
+ if bc and year < 0
49
+ year = year + 1
50
+ end
51
+ hour ||= 0
52
+ minute ||= 0
53
+ second ||= 0.0
54
+ sec_fraction ||= 0.0
55
+ offset ||= 0
56
+ if hour == 24 && minute == 0 && second == 0.0
57
+ klass.new(year, month, day, 23, minute, second + sec_fraction, offset.quo(86400), start) + 1.quo(24)
58
+ else
59
+ klass.new(year, month, day, hour, minute, second + sec_fraction, offset.quo(86400), start)
60
+ end
46
61
  end
47
62
 
48
63
  end
@@ -13,12 +13,19 @@ class TimeStep
13
13
  # * "3 days since 2001-01-01 00:00:00 +00:00"
14
14
  # * "10 years since 1901-01-01 00:00:00 +00:00"
15
15
  # The symbol for time unit symbols should be one of
16
+ # * ayears, ayear (astronomical year: 365.242198781 day)
16
17
  # * years, year
17
18
  # * months, month
18
19
  # * days, day, d
19
- # * hours, hr, h
20
- # * minutes, minute, min
21
- # * seconds, second, sec, s
20
+ # * hours, hour, hrs, hr, h
21
+ # * minutes, minute, mins, min
22
+ # * seconds, second, secs, sec, s
23
+ # * milliseconds, millisecond, msecs, msec, ms
24
+ # * microseconds, microsecond, msecs, msec, ms
25
+ # If you have already origin time object or general date string,
26
+ # you can use `since` option,
27
+ # TimeStep.new("3 hours", since: time)
28
+ # TimeStep.new("3 hours", since: "2001010121", format: '%Y%m%d%H')
22
29
  # The option `calendar` specifies the calendar for datetime calculation,
23
30
  # * standard, gregorian -> DateTime with Date::ITALY as start
24
31
  # * proleptic_gregorian -> DateTime with Date::GREGORIAN as start
@@ -32,30 +39,43 @@ class TimeStep
32
39
  # some methods (#each etc).
33
40
  #
34
41
  # @param spec [String]
42
+ # @option since [DateTime, DateTimeLike, String]
43
+ # @option format [String]
35
44
  # @option calendar [String, TimeStep::Calendar]
36
45
  # @option bc [Boolean]
37
46
  # @option count [Integer] number of time steps (as hint for some methods)
38
47
  #
39
- def initialize (spec, calendar: "standard", bc: false, count: nil)
48
+ def initialize (spec, since: nil, format: nil, count: nil, calendar: "standard", bc: false)
40
49
  case calendar
41
50
  when String
42
51
  @calendar = Calendar.new(calendar, bc: bc)
43
52
  else
44
53
  @calendar = calendar
45
54
  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)
55
+ if since
56
+ parse_interval(spec)
57
+ case since
58
+ when String
59
+ @origin = @calendar.parse(since, format: format)
60
+ else
61
+ raise "mismatch time object with calendar" unless @calendar.valid_datetime_type?(since)
62
+ @origin = since
63
+ end
64
+ else
65
+ intervalspec, originspec = spec.split(/\s+since\s+/)
66
+ parse_interval(intervalspec)
67
+ @origin = @calendar.parse(originspec)
68
+ end
69
+ @intervalspec = format("%g %s", @numeric, @symbol)
70
+ @originspec = @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
71
+ @definition = format("%s since %s", @intervalspec, @originspec)
52
72
  @count = count
53
73
  end
54
74
 
55
75
  # @private
56
76
  PATTERN_NUMERIC = '[\+\-]?\d*(?:\.\d+)?(?:[eE][\+\-]?\d+)?'
57
77
  # @private
58
- PATTERN_UNITS = 'years?|months?|days?|hours?|minutes?|seconds?|d|hr|h|min|sec|s'
78
+ PATTERN_UNITS = 'ayears?|years?|months?|days?|hours?|minutes?|seconds?|d|hrs?|hrs?|h|mins?|secs?|s|milliseconds?|msecs?|ms|microseconds?'
59
79
 
60
80
  def parse_interval (spec)
61
81
  if spec.strip =~ /(#{PATTERN_NUMERIC}|)\s*(#{PATTERN_UNITS})/
@@ -70,29 +90,39 @@ class TimeStep
70
90
  end
71
91
  @interval = @numeric
72
92
  case symbol
73
- when "years", "year"
93
+ when /\Aayears?/
94
+ @symbol = :days
95
+ @numeric *= 365.242198781.to_r
96
+ @interval = @numeric * 86400
97
+ when /\Ayears?/
74
98
  unless numeric.denominator == 1
75
99
  raise "numeric factor for year should be an integer"
76
100
  end
77
101
  @symbol = :years
78
102
  @interval *= 356.242*86400
79
- when "months", "month"
103
+ when /\Amonths?/
80
104
  unless numeric.denominator == 1
81
105
  raise "numeric factor for month should be an integer"
82
106
  end
83
107
  @symbol = :months
84
108
  @interval *= 30.4368*86400
85
- when "days", "day", "d"
109
+ when /\A(days?|d)/
86
110
  @symbol = :days
87
111
  @interval *= 86400
88
- when "hours", "hour", "hr", "h"
112
+ when /\A(hours?|hrs?|h)/
89
113
  @symbol = :hours
90
114
  @interval *= 3600
91
- when "minutes", "minute", "min"
115
+ when /\A(minutes?|mins?)/
92
116
  @symbol = :minutes
93
117
  @interval *= 60
94
- when "seconds", "second", "sec", "s"
118
+ when /\A(seconds?|secs?|s)/
95
119
  @symbol = :seconds
120
+ when /\A(milliseconds?|msecs?|ms)/
121
+ @symbol = :seconds
122
+ @interval *= 1.quo(1000)
123
+ when /\Amicroseconds?/
124
+ @symbol = :seconds
125
+ @interval *= 1.quo(1000000)
96
126
  end
97
127
  end
98
128
 
@@ -100,7 +130,7 @@ class TimeStep
100
130
 
101
131
  attr_reader :definition,
102
132
  :intervalspec,
103
- :timespec,
133
+ :originspec,
104
134
  :numeric,
105
135
  :symbol,
106
136
  :interval,
@@ -131,6 +161,14 @@ class TimeStep
131
161
  end
132
162
  return limit
133
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
+ end
134
172
 
135
173
  def valid? (*indices)
136
174
  if @count
@@ -153,11 +191,11 @@ class TimeStep
153
191
  end
154
192
  end
155
193
 
156
- def info
194
+ def debug_info
157
195
  return {
158
196
  "definition" => @definition.clone,
159
197
  "intervalspec" => @intervalspec.clone,
160
- "timespec" => @timespec.clone,
198
+ "originspec" => @originspec.clone,
161
199
  "calendar" => @calendar.name,
162
200
  "numeric" => @numeric,
163
201
  "symbol" => @symbol.to_s,
@@ -168,6 +206,10 @@ class TimeStep
168
206
  }
169
207
  end
170
208
 
209
+ private :debug_info
210
+
211
+ # Returns the value as a string for inspection.
212
+ #
171
213
  def inspect
172
214
  "#<TimeStep definition='#{definition}' calendar='#{calendar.name}'>"
173
215
  end
@@ -188,6 +230,8 @@ class TimeStep
188
230
  return ( 86400 * index.to_r ).quo(@interval)
189
231
  end
190
232
 
233
+ private :user_to_days, :days_to_user
234
+
191
235
  # Returns the time represented by the given amount as DateTime object
192
236
  #
193
237
  # @param indices [Numeric,Array<Numeric>]
@@ -220,58 +264,110 @@ class TimeStep
220
264
  end
221
265
  end
222
266
 
267
+ # Calculate the time difference in days from origin at the index.
268
+ #
269
+ # @param indices [Array]
270
+ # @return [DateTime, Array<DateTime>]
271
+ def days_at (*indices)
272
+ if indices.size == 1
273
+ index = indices.first
274
+ case @symbol
275
+ when :years
276
+ unless index.denominator == 1
277
+ raise "index for years should be an integer"
278
+ end
279
+ days = @origin.next_year(@numeric*index) - @origin
280
+ when :months
281
+ unless index.denominator == 1
282
+ raise "index for years should be an integer"
283
+ end
284
+ days = @origin.next_month(@numeric*index) - @origin
285
+ else
286
+ days = user_to_days(index)
287
+ end
288
+ days = days.to_i if days.denominator == 1
289
+ return days
290
+ else
291
+ return indices.map{ |index| days_at(index) }
292
+ end
293
+ end
294
+
223
295
  # Returns the index for the given time in the unit represented by the object
224
296
  #
225
297
  # @param times [DateTime] time object
226
298
  #
227
299
  # @return [Numeric]
228
- def index_at (*times)
300
+ def index_at (*times, format: nil)
229
301
  if times.size == 1
230
302
  time = times.first
231
- time = @calendar.parse(time) if time.is_a?(String)
303
+ time = @calendar.parse(time, format: format) if time.is_a?(String)
232
304
  case @symbol
233
305
  when :years
234
- return time.difference_in_years(@origin).quo(@numeric.to_i)
306
+ index = time.difference_in_years(@origin).quo(@numeric.to_i)
235
307
  when :months
236
- return time.difference_in_months(@origin).quo(@numeric.to_i)
308
+ index = time.difference_in_months(@origin).quo(@numeric.to_i)
237
309
  else
238
310
  jday = @calendar.date2jday(time.year, time.month, time.day)
239
- fday = time.fraction
311
+ fday = time.fraction
240
312
  udays = days_to_user(jday - @origin.jd)
241
- utime = days_to_user(fday - @origin.fraction)
242
- return udays + utime
313
+ utime = days_to_user(time.fraction - time.offset - (@origin.fraction - @origin.offset))
314
+ index = udays + utime
243
315
  end
316
+ index = index.to_i if index.denominator == 1
317
+ return index
244
318
  else
245
- return times.map{|time| index_at(time) }
319
+ return times.map{|time| index_at(time, format: format) }
246
320
  end
247
321
  end
248
322
 
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"
323
+ def range (ge: nil, gt: nil, lt: nil, le: nil)
324
+ raise "lower limit is not given" if ge.nil? and gt.nil?
325
+ raise "upper limit is not given" if lt.nil? and le.nil?
326
+ raise "lower limits are duplicated" if ge and gt
327
+ raise "upper limits are duplicated" if lt and le
328
+ if ge
329
+ case ge
330
+ when Numeric
331
+ min = ge.floor
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
260
343
  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"
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
265
355
  end
266
- return @origin.next_month(@numeric*index) - @origin
267
356
  else
268
- return user_to_days(index)
357
+ max = prev_index_of(lt)
269
358
  end
270
- else
271
- return indices.map{ |index| days_at(index) }
272
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
273
369
  end
274
-
370
+
275
371
  # Returns TimeStep object which has origin time determined
276
372
  # by the given `index`.
277
373
  #
@@ -280,7 +376,12 @@ class TimeStep
280
376
  # @return [TimeStep]
281
377
  def shift_origin (index)
282
378
  time = time_at(index)
283
- return TimeStep.new(@intervalspec + " since " + time.strftime("%Y-%m-%d %H:%M:%S.%N %:z"), calendar: @calendar)
379
+ return TimeStep.new(@intervalspec, since: time, calendar: @calendar)
380
+ end
381
+
382
+ def shift_origin_with_days (days)
383
+ time = @origin + days
384
+ return TimeStep.new(@intervalspec, since: time, calendar: @calendar)
284
385
  end
285
386
 
286
387
  # Returns TimeStep object which has origin time specified
@@ -326,7 +427,7 @@ class TimeStep
326
427
  #
327
428
  #
328
429
  def in (unit)
329
- return TimeStep::Pair.new(self, format("%s since %s", unit, @timespec), calendar: @calendar)
430
+ return TimeStep::Pair.new(self, format("%s since %s", unit, @originspec), calendar: @calendar)
330
431
  end
331
432
 
332
433
  def next_index_of (time)
@@ -336,7 +437,7 @@ class TimeStep
336
437
  end
337
438
  index = index_at(time).to_r
338
439
  if index.denominator == 1
339
- return index + 1
440
+ return index.to_i + 1
340
441
  else
341
442
  return index.ceil
342
443
  end
@@ -354,8 +455,8 @@ class TimeStep
354
455
  return time_at(prev_index_of(time))
355
456
  end
356
457
 
357
- def parse (time)
358
- return @calendar.parse(time)
458
+ def parse (time, format: nil)
459
+ return @calendar.parse(time, format: format)
359
460
  end
360
461
 
361
462
  end