class TimeStep

TimeStep class

Constants

CALENDARS

@private

PATTERN_NUMERIC

@private

PATTERN_UNITS

@private

WDAY

@private

WDAY_NAME

@private

Attributes

calendar[R]
interval[R]
numeric[R]
origin[R]
symbol[R]

Public Class Methods

new(spec, since: nil, offset: nil, format: nil, calendar: "standard", tz: nil) click to toggle source

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

@param spec [String] timestep specification @param since [DateTime, String]

@param offset [Numeric, String] offset in origin time @param format [String] template string for strptime for parsing time @param calendar [String, TimeStep::Calendar]

# File lib/timesteps/timestep.rb, line 146
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
split_interval_spec(spec) click to toggle source

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

@example

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

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

@param [String] interval specification (ex. “12 months”, “3 hours”, “year”) @return [Array(Numeric, String)] A pair of `numeric` and `symbol`

# File lib/timesteps/timestep.rb, line 87
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

Public Instance Methods

==(other) click to toggle source

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

@param other [TimeStep]

@return [Boolean]

# File lib/timesteps/timestep.rb, line 338
def == (other)
  return definition == other.definition && @calendar == other.calendar 
end
[](*indices)
Alias for: time_at
definition() click to toggle source

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

@return [String]

# File lib/timesteps/timestep.rb, line 295
def definition
  format("%s since %s", interval_spec, origin_spec)
end
duration_at(*indices) click to toggle source

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

@example

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

@param indices [Array<Numeric>]

@return [DateTime, Array<DateTime>]

# File lib/timesteps/timestep.rb, line 412
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) click to toggle source

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

@example

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

@param other [String, TimeStep]

@return [TimeStep::Pair]

# File lib/timesteps/timestep.rb, line 627
def in (unit)
  other = TimeStep.new(unit, since: @origin, calendar: @calendar)
 return Pair.new(self, other)
end
index_at(*times, format: nil) click to toggle source

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

@example

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

@param times [Array<DateTime>] @param format [String] template string for strptime for parsing time

@return [Numeric, Array<Numeric>]

# File lib/timesteps/timestep.rb, line 451
def index_at (*times, format: nil)
  if times.size == 1
    time = times.first
    time = @calendar.parse(time, format: format, offset: @origin.offset) if time.is_a?(String)
    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
inspect() click to toggle source

Returns a string for inspection.

@return [String]

# File lib/timesteps/timestep.rb, line 322
def inspect
  options = ""
  case @calendar.name
  when "standard", "gregorian"
  else
    options << " calendar='#{calendar.name}'"
  end
  "#<TimeStep definition='#{definition}'#{options}>"      
end
interval_spec() click to toggle source

Returns a string expression for interval section in timestem spec.

@return [String]

# File lib/timesteps/timestep.rb, line 272
def interval_spec
  if @wday
    return format("%g %s", @numeric, WDAY_NAME[@wday])      
  else
    return format("%g %s", @numeric, @symbol)
  end
end
new_origin(time) click to toggle source

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

@example

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'>

@param time [DateTime, String]

@return [TimeStep]

# File lib/timesteps/timestep.rb, line 518
def new_origin (time)
  time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
  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) click to toggle source

Returns next integer index of the given time

@example

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

@param time [DateTime, String]

@return [Numeric]

# File lib/timesteps/timestep.rb, line 562
def next_index_of (time)
  time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
  return index_at(time).floor + 1
end
next_time_of(time) click to toggle source

Returns next time of the given time

@example

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

@param time [DateTime, String]

@return [DateTime]

# File lib/timesteps/timestep.rb, line 596
def next_time_of (time)
  return time_at(next_index_of(time))
end
offset() click to toggle source

Returns the time offset of origin time.

@return [Rational]

# File lib/timesteps/timestep.rb, line 302
def offset
  return @origin.offset
end
origin_spec() click to toggle source

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

@return [String]

# File lib/timesteps/timestep.rb, line 283
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) click to toggle source

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.

@param time [String] string to be parsed @param format [String] template string for strptime for parsing time

@return [DateTime]

# File lib/timesteps/timestep.rb, line 315
def parse (time, format: nil)
  return @calendar.parse(time, format: format, offset: @origin.offset)
end
period(start, last, ends: "[]") click to toggle source

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

@example

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) >

@param start [Numeric, DateTime] @param last [Numeric, DateTime] @param ends [String] one of “[]”, “()”, “[)”, “(]”

@return [TimePeriod]

# File lib/timesteps/timestep.rb, line 663
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) click to toggle source

Returns previous integer index of the given time

@example

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

@param time [DateTime, String]

@return [Numeric]

# File lib/timesteps/timestep.rb, line 579
def prev_index_of (time)
  time = @calendar.parse(time, offset: @origin.offset) if time.is_a?(String)
  return index_at(time).ceil - 1
end
prev_time_of(time) click to toggle source

Returns previous time of the given time

@example

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

@param time [String, DateTime]

@return [DateTime]

# File lib/timesteps/timestep.rb, line 612
def prev_time_of (time)
  return time_at(prev_index_of(time))  
end
range(start, last = nil, count: nil, ends: "[]") click to toggle source

Creates new timestep range object.

@example

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

@param start [Numeric, DateTime] @param last [Numeric, DateTime] @param count [Integer] @param ends [String] one of “[]”, “()”, “[)”, “(]”

@return [TimeStep::Range]

# File lib/timesteps/timestep.rb, line 694
def range (start, last = nil, count: nil, ends: "[]")
  return TimeStep::Range.new(self, start, last, count: count, ends: ends)
end
shift_origin(index, with: "index") click to toggle source

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

@example

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'>

@param index [Numeric] @param with [String, Symbol] “index” : shift by index , “duration” : shift by duration

@return [TimeStep]

# File lib/timesteps/timestep.rb, line 492
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) click to toggle source

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

@example

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

@param indices [Array<Numeric>]

@return [DateTime, Array<DateTime>]

# File lib/timesteps/timestep.rb, line 370
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
Also aliased as: []
to(other) click to toggle source

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

@example

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

@param other [String, TimeStep]

@return [TimeStep::Pair]

# File lib/timesteps/timestep.rb, line 644
def to (other)
  return Pair.new(self, other)
end
truncate(time) click to toggle source

Truncate the given datetime to the unit of the object.

@example

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

@param time [DateTime, String]

@return [DateTime]

# File lib/timesteps/timestep.rb, line 546
def truncate (time)
  return time_at(index_at(time).floor)
end

Private Instance Methods

days_to_user(index) click to toggle source

@private

# File lib/timesteps/timestep.rb, line 348
def days_to_user (index)
  if @interval == 0
    return 0
  else
    return ( 86400 * index.to_r ).quo(@interval)
  end
end
parse_interval(spec) click to toggle source

@private

# File lib/timesteps/timestep.rb, line 196
def parse_interval (spec)
  if spec.strip =~ /\A(#{PATTERN_NUMERIC}|)\s*(#{PATTERN_UNITS})\z/i
    @numeric = if $1 == ""
                 1.to_r
               else
                 Float($1).to_r
               end
    symbol = $2
  else
    raise "the interval specification '#{spec}' is invalid."
  end
  @interval = @numeric
  case symbol
  when /\Ayears?\z/i
    unless numeric.denominator == 1
      raise "numeric factor for year should be an integer"
    end
    @symbol = :years
    @interval *= 356.242*86400
  when /\Amonths?\z/i
    unless numeric.denominator == 1
      raise "numeric factor for month should be an integer"
    end
    @symbol = :months
    @interval *= 30.4368*86400
  when /\A(days?|d)\z/i
    @symbol = :days
    @interval *= 86400
  when /\A(hours?|hrs?|h)\z/i
    @symbol = :hours
    @interval *= 3600
  when /\A(minutes?|mins?)\z/i
    @symbol = :minutes
    @interval *= 60
  when /\A(seconds?|secs?|s)\z/i
    @symbol = :seconds
  when /\A(milliseconds?|msecs?|ms)\z/i
    @symbol = :seconds
    @interval *= 1.quo(1000)
  when /\Amicroseconds?\z/i
    @symbol = :seconds
    @interval *= 1.quo(1000000)
  when /\A(weeks?|w)\-(sun|mon|tue|wed|thu|fri|sat)\z/i
    symbol =~ /\A(weeks?|w)\-(\w{3})/i
    @wday   = $2.downcase.intern
    @symbol = WDAY_NAME[@wday].intern ### :sundays, :mondays, ...
    @interval *= 7*86400
  when /\A(weeks?|w)\z/i
    @symbol = :weeks
    @interval *= 7*86400
  when /\Asundays?|mondays?|tuesdays?|wednesdays?|thursday?|fridays?|saturdays?\z/i
    symbol =~ /\A(\w{3})/i
    @wday   = $1.downcase.intern
    @symbol = WDAY_NAME[@wday].intern ### :sundays, :mondays, ...
    @interval *= 7*86400
  when /\Aayears?\z/i
    @symbol = :days
    @numeric *= 365.242198781.to_r
    @interval = @numeric * 86400
  end
  if @numeric.denominator == 1
    @numeric = @numeric.to_i
  end
end
user_to_days(index) click to toggle source

@private

# File lib/timesteps/timestep.rb, line 343
def user_to_days (index)
  return ( @interval * index.to_r ).quo(86400)
end