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)


344
345
346
# File 'lib/timesteps/timestep.rb', line 344

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:



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
# File 'lib/timesteps/timestep.rb', line 418

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:



636
637
638
639
# File 'lib/timesteps/timestep.rb', line 636

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


457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/timesteps/timestep.rb', line 457

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)


328
329
330
331
332
333
334
335
336
# File 'lib/timesteps/timestep.rb', line 328

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_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:



526
527
528
529
530
531
532
533
534
535
# File 'lib/timesteps/timestep.rb', line 526

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)


571
572
573
574
# File 'lib/timesteps/timestep.rb', line 571

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:



605
606
607
# File 'lib/timesteps/timestep.rb', line 605

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:



321
322
323
# File 'lib/timesteps/timestep.rb', line 321

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:



672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
# File 'lib/timesteps/timestep.rb', line 672

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)


588
589
590
591
# File 'lib/timesteps/timestep.rb', line 588

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:



621
622
623
# File 'lib/timesteps/timestep.rb', line 621

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:



703
704
705
# File 'lib/timesteps/timestep.rb', line 703

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:



717
718
719
# File 'lib/timesteps/timestep.rb', line 717

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:



500
501
502
503
504
505
506
507
508
509
# File 'lib/timesteps/timestep.rb', line 500

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:



376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# File 'lib/timesteps/timestep.rb', line 376

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:



653
654
655
# File 'lib/timesteps/timestep.rb', line 653

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:



555
556
557
# File 'lib/timesteps/timestep.rb', line 555

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