class TimeStep
TimeStep
class
Constants
- CALENDARS
@private
- PATTERN_NUMERIC
@private
- PATTERN_UNITS
@private
- WDAY
@private
- WDAY_NAME
@private
Attributes
Public Class Methods
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
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
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
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
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
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
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
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
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
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
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
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
Returns the time offset of origin time.
@return [Rational]
# File lib/timesteps/timestep.rb, line 302 def offset return @origin.offset end
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
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
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
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
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
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
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
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
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 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
@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
@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
@private
# File lib/timesteps/timestep.rb, line 343 def user_to_days (index) return ( @interval * index.to_r ).quo(86400) end