Class: TimeStep

Inherits:
Object
  • Object
show all
Defined in:
lib/timesteps/timestep.rb,
lib/timesteps/timestep_query.rb,
lib/timesteps/timestep_calendar.rb,
lib/timesteps/timestep_datetime_ext.rb

Overview

TimeStep class

Direct Known Subclasses

TimePeriod

Defined Under Namespace

Modules: DateTimeExt Classes: Calendar, Converter, Pair, Query, Range

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(spec, since: nil, offset: nil, format: nil, calendar: "standard", tz: nil) ⇒ TimeStep

Constructs the object.

The argument ‘spec` specifies the time step definition, which has the form,

"<INTERVAL> since <TIME>"

For example,

* "second since 1970-01-01 00:00:00 +00:00" 
* "hour since 2001-01-01 00:00:00 JST" 
* "3 days since 2001-01-01 00:00:00 +00:00" 
* "10 years since 1901-01-01 00:00:00 +00:00"

The symbol for time unit symbols should be one of

* ayears, ayear (astronomical year: 365.242198781 day)
* years, year
* months, month
* days, day, d
* hours, hour, hrs, hr, h
* minutes, minute, mins, min
* seconds, second, secs, sec, s
* milliseconds, millisecond, msecs, msec, ms
* microseconds, microsecond

If you have already origin time object or general date string, you can use ‘since` option,

TimeStep.new("3 hours", since: time)
TimeStep.new("3 hours", since: "2001010121", format: '%Y%m%d%H')

When origin time is specified in both ‘spec’ and ‘since’ option, the origin time in ‘spec’ has priority. If origin time is not specified in neither ‘spec’ and ‘since’ option, the default value is set to the origin time (“0000-01-01 00:00:00” for date and “1970-01-01 00:00:00” for time). The time offset from UTC can be set by ‘offset’ option. The option ‘calendar` specifies the name of calendar for datetime calculation,

* "standard", "gregorian"      -> DateTime with Date::ITALY as start
* "proleptic_gregorian"        -> DateTime with Date::GREGORIAN as start
* "proleptic_julian", "julian" -> DateTime with Date::JULIAN as start
* "noleap", "365_day"          -> DateTimeNoLeap
* "allleap", "366_day"         -> DateTimeAllLeap
* "360_day"                    -> DateTimeFixed360Day

Parameters:

  • spec (String)

    timestep specification

  • since (DateTime, String) (defaults to: nil)
  • offset (Numeric, String) (defaults to: nil)

    offset in origin time

  • format (String) (defaults to: nil)

    template string for strptime for parsing time

  • calendar (String, TimeStep::Calendar) (defaults to: "standard")


152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/timesteps/timestep.rb', line 152

def initialize (spec, since: nil, offset: nil, format: nil, calendar: "standard", tz: nil)

  case calendar
  when String
    if tz
      raise "tz option can be used only with 'standard' calendar type" if calendar != "standard"
      @calendar = Calendar.new(calendar, tz: tz) 
    else
      @calendar = CALENDARS[calendar]
      raise "specified calendar type '#{calendar}' is invalid" unless @calendar
    end
  when TimeStep::Calendar
    @calendar = calendar
  else
    raise "invalid object for option 'calendar'" 
  end

  if spec =~ /\s+since\s+/
    interval_spec, time_spec = $~.pre_match, $~.post_match
    parse_interval(interval_spec)
    @origin = @calendar.parse(time_spec, offset: offset)
  else
    parse_interval(spec)
    @origin = case since
              when nil
                case @symbol
                when :hours, :minutes, :seconds
                  @calendar.parse("1970-1-1", offset: offset)          
                else
                  @calendar.parse("0000-1-1", offset: offset)          
                end
              when String
                @calendar.parse(since, format: format, offset: offset)
              when Time
                since.to_datetime
              else
                raise "datetime mismatched with calendar type" unless @calendar.valid_datetime_type?(since)
                since
              end
  end
  
  if @wday
    origin = @origin - @origin.wday + WDAY[@wday]
    origin -= 7 unless @origin >= origin
    @origin = origin
  end
  
end

Instance Attribute Details

#calendarObject (readonly)

Returns the value of attribute calendar.



269
270
271
# File 'lib/timesteps/timestep.rb', line 269

def calendar
  @calendar
end

#intervalObject (readonly)

Returns the value of attribute interval.



269
270
271
# File 'lib/timesteps/timestep.rb', line 269

def interval
  @interval
end

#numericObject (readonly)

Returns the value of attribute numeric.



269
270
271
# File 'lib/timesteps/timestep.rb', line 269

def numeric
  @numeric
end

#originObject (readonly)

Returns the value of attribute origin.



269
270
271
# File 'lib/timesteps/timestep.rb', line 269

def origin
  @origin
end

#symbolObject (readonly)

Returns the value of attribute symbol.



269
270
271
# File 'lib/timesteps/timestep.rb', line 269

def symbol
  @symbol
end

Class Method Details

.split_interval_spec(spec) ⇒ Array(Numeric, String)

Extracts numeric part and symbol part from the given interval specification.

Examples:

TimeStep.split_interval_spec("12 months")
# => [12, "months"]

TimeStep.split_interval_spec("month-end")
# => [1, "month-end"]

Parameters:

  • interval (String)

    specification (ex. “12 months”, “3 hours”, “year”)

Returns:

  • (Array(Numeric, String))

    A pair of ‘numeric` and `symbol`



93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/timesteps/timestep.rb', line 93

def self.split_interval_spec (spec)
  if spec.strip =~ /\A(#{PATTERN_NUMERIC}|)\s*((?:#{PATTERN_UNITS}).*)\z/i
    numeric = if $1 == ""
                1
              else
                Float($1)
              end
    numeric = numeric.to_i if numeric.denominator == 1
    symbol = $2
  else
    raise "the interval specification '#{spec}' is invalid."
  end
  return numeric, symbol
end

Instance Method Details

#==(other) ⇒ Boolean

Returns true if other has same contents of ‘definition` and `calendar` as self has.

Parameters:

Returns:

  • (Boolean)


356
357
358
# File 'lib/timesteps/timestep.rb', line 356

def == (other)
  return definition == other.definition && @calendar == other.calendar 
end

#definitionString

Returns a string expression of definition of timestep. The return value can be used for constructs other TimeStep object.

Returns:

  • (String)


301
302
303
# File 'lib/timesteps/timestep.rb', line 301

def definition
  format("%s since %s", interval_spec, origin_spec)
end

#duration_at(*indices) ⇒ DateTime+

Calculate the duration (array) in day unit since origin time at the given index (indices).

Examples:

ts = TimeStep.new("hours since 2001-01-01 00:00:00")
ts.duration_at(0)
# => 0                   ### 0 days
ts.duration_at(12)
# => (1/2)               ### half of a day
ts.duration_at(14*24)
# => 14                  ### 14 days

Parameters:

  • indices (Array<Numeric>)

Returns:



430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/timesteps/timestep.rb', line 430

def duration_at (*indices)
  if indices.size == 1
    index = indices.first
    days = case @symbol
           when :years
             unless (index*@numeric).denominator == 1
               raise ArgumentError, "index argument should be an integer for years"
             end
             @origin.next_year(@numeric*index) - @origin
           when :months
             unless (index*@numeric).denominator == 1
               raise ArgumentError, "index argument should be an integer for months"
             end
             @origin.next_month(@numeric*index) - @origin
           else
             user_to_days(index)
           end
    days = days.to_i if days.denominator == 1
    return days
  else
    return indices.map{ |index| duration_at(index) }            
  end
end

#in(unit) ⇒ TimeStep::Pair

Creates new timestep pair object which refers ‘other` as other unit

Examples:

days = TimeStep.new("days since 2001-01-01 00:00:00")
pair = days.in("hours")
pair.forward(1)
# => 24

Parameters:

Returns:



648
649
650
651
# File 'lib/timesteps/timestep.rb', line 648

def in (unit)
  other = TimeStep.new(unit, since: @origin, calendar: @calendar)
  return Pair.new(self, other)
end

#index_at(*times, format: nil) ⇒ Numeric+

Returns the index (indices) for the given time (array).

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
ts.index_at(ts.parse("2001-01-01 00:00:00"))
# => 0 
ts.index_at("2001-01-15 00:00:00")
# => 14
ts.index_at("2002")
# => 365

Parameters:

  • times (Array<DateTime>)
  • format (String) (defaults to: nil)

    template string for strptime for parsing time

Returns:

  • (Numeric, Array<Numeric>)


469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/timesteps/timestep.rb', line 469

def index_at (*times, format: nil)
  if times.size == 1
    time = times.first
    time = time.to_datetime if time.is_a?(Time)
    time = @calendar.parse(time, format: format, offset: @origin.offset) if time.is_a?(String)
    time = time.new_offset(@origin.offset) if time.offset != @origin.offset
    case @symbol
    when :years
      diff = time.difference_in_years(@origin)
      frac = diff - diff.floor
      index = diff.floor.quo(@numeric.to_i) + frac
    when :months
      diff = time.difference_in_months(@origin)
      frac = diff - diff.floor
      index = diff.floor.quo(@numeric.to_i) + frac
    else
      jday  = @calendar.date2jday(time.year, time.month, time.day)
    	fday  = time.fraction 
    	udays = days_to_user(jday - @origin.jd)
    	utime = days_to_user(time.fraction - time.offset - (@origin.fraction - @origin.offset))
    	index = udays + utime
    end
    index = index.to_i if index.denominator == 1
    return index
  else
    return times.map{|time| index_at(time, format: format) }      
  end
end

#inspectString

Returns a string for inspection.

Returns:

  • (String)


340
341
342
343
344
345
346
347
348
# File 'lib/timesteps/timestep.rb', line 340

def inspect
  options = ""
  case @calendar.name
  when "standard", "gregorian"
  else
    options << " calendar='#{calendar.name}'"
  end
  "#<TimeStep definition='#{definition}'#{options}>"      
end

#interval_specString

Returns a string expression for interval section in timestem spec.

Returns:

  • (String)


278
279
280
281
282
283
284
# File 'lib/timesteps/timestep.rb', line 278

def interval_spec
  if @wday
    return format("%g %s", @numeric, WDAY_NAME[@wday])      
  else
    return format("%g %s", @numeric, @symbol)
  end
end

#new_offset(offset) ⇒ TimeStep

Returns the timestep object with new_offset

Returns:



316
317
318
319
320
321
322
# File 'lib/timesteps/timestep.rb', line 316

def new_offset (offset)
  obj = clone
  obj.instance_eval {
    @origin = @origin.new_offset(offset)
  }
  return obj
end

#new_origin(time, truncate: false) ⇒ TimeStep

Returns new timestep object which holds the given time as origin.

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
# => #<TimeStep definition='1 days since 2001-01-01 00:00:00.000000000 +00:00'>
ts.new_origin(ts.parse("2001-01-15"))
# => #<TimeStep definition='1 days since 2001-01-15 00:00:00.000000000 +00:00'>
ts.new_origin("2001-01-15")
# => #<TimeStep definition='1 days since 2001-01-15 00:00:00.000000000 +00:00'>
ts.new_origin("2002")
# => #<TimeStep definition='1 days since 2002-01-01 00:00:00.000000000 +00:00'>

Parameters:

Returns:



538
539
540
541
542
543
544
545
546
547
# File 'lib/timesteps/timestep.rb', line 538

def new_origin (time, truncate: false)
  time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
  time = self.truncate(time) if truncate
  if @wday
    origin = time - time.wday + WDAY[@wday]
    origin -= 7 unless time >= origin
    time = origin
  end
  return TimeStep.new(interval_spec, since: time, calendar: @calendar)
end

#next_index_of(time) ⇒ Numeric

Returns next integer index of the given time

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
ts.next_index_of("2001-01-14 12:00:00")
#=> 14
ts.next_index_of("2001-01-15 00:00:00")
#=> 15

Parameters:

Returns:

  • (Numeric)


583
584
585
586
# File 'lib/timesteps/timestep.rb', line 583

def next_index_of (time)
  time = @calendar.parse(time) if time.is_a?(String)
  return index_at(time).floor + 1
end

#next_time_of(time) ⇒ DateTime

Returns next time of the given time

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
ts.next_time_of("2001-01-14 12:00:00")
#=> #<DateTime: 2001-01-15T00:00:00+00:00 ...>
ts.next_time_of("2001-01-15 00:00:00")
#=> #<DateTime: 2001-01-16T00:00:00+00:00 ...>

Parameters:

Returns:



617
618
619
# File 'lib/timesteps/timestep.rb', line 617

def next_time_of (time)
  return time_at(next_index_of(time))
end

#offsetRational

Returns the time offset of origin time.

Returns:

  • (Rational)


308
309
310
# File 'lib/timesteps/timestep.rb', line 308

def offset
  return @origin.offset
end

#origin_specString

Returns a string expression for origin time section in timestep spec.

Returns:

  • (String)


289
290
291
292
293
294
295
# File 'lib/timesteps/timestep.rb', line 289

def origin_spec
  if @calendar.tz
    return @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z %Z")
  else
    return @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
  end
end

#parse(time, format: nil) ⇒ DateTime

Parses datetime string and return datetime object. In the parsing, the calendar of the object is used. If ‘format` option is given, `strptime` method is used for the parsing. Otherwise, the `parse` is used.

Parameters:

  • time (String)

    string to be parsed

  • format (String) (defaults to: nil)

    template string for strptime for parsing time

Returns:



333
334
335
# File 'lib/timesteps/timestep.rb', line 333

def parse (time, format: nil)
  return @calendar.parse(time, format: format, offset: @origin.offset)
end

#period(start, last, ends: "[]") ⇒ TimePeriod

Creates new timeperiod object corresponding given time or index for start and last.

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
ts.period(0, 1)
#=> #<TimePeriod '1 days' [2001-01-01T00:00:00+00:00, 2001-01-02T00:00:00+00:00] >
ts.period("2001", "2002", ends: "[)")
#=> #<TimePeriod '365 days' [2001-01-01T00:00:00+00:00, 2002-01-01T00:00:00+00:00) >

Parameters:

  • start (Numeric, DateTime)
  • last (Numeric, DateTime)
  • ends (String) (defaults to: "[]")

    one of “[]”, “()”, “[)”, “(]”

Returns:



684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
# File 'lib/timesteps/timestep.rb', line 684

def period (start, last, ends: "[]")
  idx1 = if start.kind_of?(Numeric)
           start
         else
           index_at(start)
         end
  idx2 = if last.kind_of?(Numeric)
           last      
         else
           index_at(last)
         end
  origin  = time_at(idx1)
  numeric = (idx2 - idx1) * @numeric
  interval_spec = format("%g %s", numeric, @symbol)
  return TimePeriod.new(interval_spec, since: origin, calendar: @calendar, ends: ends)    
end

#prev_index_of(time) ⇒ Numeric

Returns previous integer index of the given time

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
ts.prev_index_of("2001-01-14 12:00:00")
#=> 13
ts.prev_index_of("2001-01-15 00:00:00")
#=> 13

Parameters:

Returns:

  • (Numeric)


600
601
602
603
# File 'lib/timesteps/timestep.rb', line 600

def prev_index_of (time)
  time = @calendar.parse(time) if time.is_a?(String)
  return index_at(time).ceil - 1
end

#prev_time_of(time) ⇒ DateTime

Returns previous time of the given time

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
ts.prev_time_of("2001-01-14 12:00:00")
#=> #<DateTime: 2001-01-14T00:00:00+00:00 ...>
ts.prev_time_of("2001-01-15 00:00:00")
#=> #<DateTime: 2001-01-14T00:00:00+00:00 ...>

Parameters:

Returns:



633
634
635
# File 'lib/timesteps/timestep.rb', line 633

def prev_time_of (time)
  return time_at(prev_index_of(time))  
end

#query(format = nil) ⇒ Object



52
53
54
# File 'lib/timesteps/timestep_query.rb', line 52

def query (format = nil)
  return TimeStep::Query.new(self, format: format)
end

#range(start, last = nil, count: nil, ends: "[]") ⇒ TimeStep::Range

Creates new timestep range object.

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
ts.range(0, 1)
ts.range("2001", "2002", ends: "[)")
ts.range("2001", 1)

Parameters:

  • start (Numeric, DateTime, String)
  • last (Numeric, DateTime, String) (defaults to: nil)
  • count (Integer) (defaults to: nil)
  • ends (String) (defaults to: "[]")

    one of “[]”, “()”, “[)”, “(]”

Returns:



715
716
717
# File 'lib/timesteps/timestep.rb', line 715

def range (start, last = nil, count: nil, ends: "[]")
  return TimeStep::Range.new(self, start, last, count: count, ends: ends)
end

#right_time?(time) ⇒ TimeStep::Range

Check whether the given time is right or not for timestep.

Examples:

ts = TimeStep.new("1 hour")
ts.right_time?(ts.parse("2001-01-01 01:00:00"))
# => true
ts.right_time?(ts.parse("2001-01-01 01:30:00"))
# => false

Returns:



729
730
731
# File 'lib/timesteps/timestep.rb', line 729

def right_time? (time)
  return index_at(time).integer?
end

#shift_origin(index, with: "index") ⇒ TimeStep

Returns new timestep object which has new origin time specified by ‘index`.

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
# => #<TimeStep definition='1 days since 2001-01-01 00:00:00.000000000 +00:00'>
ts.shift_origin(14)
# => #<TimeStep definition='1 days since 2001-01-15 00:00:00.000000000 +00:00'>
ts.shift_origin(365)
# => #<TimeStep definition='1 days since 2002-01-01 00:00:00.000000000 +00:00'>

Parameters:

  • index (Numeric)
  • with (String, Symbol) (defaults to: "index")

    “index” : shift by index , “duration” : shift by duration

Returns:



512
513
514
515
516
517
518
519
520
521
# File 'lib/timesteps/timestep.rb', line 512

def shift_origin (index, with: "index")
  case with
  when :index, "index"
    time = time_at(index)
    return TimeStep.new(interval_spec, since: time, calendar: @calendar)
  when :duration, "duration", :days, "days"
    time = @origin + index
    return TimeStep.new(interval_spec, since: time, calendar: @calendar)
  end
end

#time_at(*indices) ⇒ DateTime+ Also known as: []

Returns the datetime object (array) for the given index (indices).

Examples:

ts = TimeStep.new("days since 2001-01-01 00:00:00")
ts.time_at(0)
# => #<DateTime: 2001-01-01T00:00:00+00:00 ...>
ts.time_at(14)
# => #<DateTime: 2001-01-15T00:00:00+00:00 ...>

Parameters:

  • indices (Array<Numeric>)

Returns:



388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# File 'lib/timesteps/timestep.rb', line 388

def time_at (*indices)
  if indices.size == 1
    index = indices.first
    raise ArgumentError, "index argument should be a numeric" unless index.is_a?(Numeric)
    case @symbol
    when :years
      unless (index*@numeric).denominator == 1
        raise ArgumentError, "index argument should be an integer for years"
      end
      return @origin.next_year(index*@numeric)
    when :months
      unless (index*@numeric).denominator == 1
        raise ArgumentError, "index argument should be an integer for months"
      end
      return @origin.next_month(index*@numeric)
    else
      days = user_to_days(index) + @origin.jd + @origin.fraction - @origin.offset
      jday = days.floor
      fday = days - days.floor
      return (@calendar.jday2date(jday) + fday).new_offset(@origin.offset)
    end
  else
    return indices.map{|index| time_at(index) }      
  end
end

#to(other) ⇒ TimeStep::Pair

Creates new timestep pair object which refers ‘other` from `self`

Examples:

days = TimeStep.new("days since 2001-01-01 00:00:00")
hours = TimeStep.new("hours since 2001-01-01 00:00:00")
pair = days.to(hours)
pair.forward(1)
# => 24

Parameters:

Returns:



665
666
667
# File 'lib/timesteps/timestep.rb', line 665

def to (other)
  return Pair.new(self, other)
end

#truncate(time) ⇒ DateTime

Truncate the given datetime to the unit of the object.

Examples:

hours = TimeStep.new("hours since 2001-01-01 00:00:00")
hours.truncate("2001-01-15 12:35:00")
# => #<DateTime: 2001-01-15T12:00:00+00:00 ...>

days = TimeStep.new("days since 2001-01-01 00:00:00")
days.truncate("2001-01-15 12:00:00")
# => #<DateTime: 2001-01-15T00:00:00+00:00 ...>

months = TimeStep.new("months since 2001-01-01 00:00:00")
months.truncate("2001-05-15 12:00:00")
# => #<DateTime: 2001-05-01T00:00:00+00:00 ...>

Parameters:

Returns:



567
568
569
# File 'lib/timesteps/timestep.rb', line 567

def truncate (time)
  return time_at(index_at(time).floor)
end