timesteps 0.9.3 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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