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


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

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.



267
268
269
# File 'lib/timesteps/timestep.rb', line 267

def calendar
  @calendar
end

#intervalObject (readonly)

Returns the value of attribute interval.



267
268
269
# File 'lib/timesteps/timestep.rb', line 267

def interval
  @interval
end

#numericObject (readonly)

Returns the value of attribute numeric.



267
268
269
# File 'lib/timesteps/timestep.rb', line 267

def numeric
  @numeric
end

#originObject (readonly)

Returns the value of attribute origin.



267
268
269
# File 'lib/timesteps/timestep.rb', line 267

def origin
  @origin
end

#symbolObject (readonly)

Returns the value of attribute symbol.



267
268
269
# File 'lib/timesteps/timestep.rb', line 267

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



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

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)


342
343
344
# File 'lib/timesteps/timestep.rb', line 342

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)


299
300
301
# File 'lib/timesteps/timestep.rb', line 299

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:



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

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:



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

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


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

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


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

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)


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

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:



523
524
525
526
527
528
529
530
531
532
# File 'lib/timesteps/timestep.rb', line 523

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)


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

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



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

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

#offsetRational

Returns the time offset of origin time.

Returns:

  • (Rational)


306
307
308
# File 'lib/timesteps/timestep.rb', line 306

def offset
  return @origin.offset
end

#origin_specString

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

Returns:

  • (String)


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

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:



319
320
321
# File 'lib/timesteps/timestep.rb', line 319

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:



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

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)


585
586
587
588
# File 'lib/timesteps/timestep.rb', line 585

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



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

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)
  • last (Numeric, DateTime) (defaults to: nil)
  • count (Integer) (defaults to: nil)
  • ends (String) (defaults to: "[]")

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

Returns:



700
701
702
# File 'lib/timesteps/timestep.rb', line 700

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:



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

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:



497
498
499
500
501
502
503
504
505
506
# File 'lib/timesteps/timestep.rb', line 497

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:



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

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:



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

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:



552
553
554
# File 'lib/timesteps/timestep.rb', line 552

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