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


146
147
148
149
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
# 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

Instance Attribute Details

#calendarObject (readonly)

Returns the value of attribute calendar.



263
264
265
# File 'lib/timesteps/timestep.rb', line 263

def calendar
  @calendar
end

#intervalObject (readonly)

Returns the value of attribute interval.



263
264
265
# File 'lib/timesteps/timestep.rb', line 263

def interval
  @interval
end

#numericObject (readonly)

Returns the value of attribute numeric.



263
264
265
# File 'lib/timesteps/timestep.rb', line 263

def numeric
  @numeric
end

#originObject (readonly)

Returns the value of attribute origin.



263
264
265
# File 'lib/timesteps/timestep.rb', line 263

def origin
  @origin
end

#symbolObject (readonly)

Returns the value of attribute symbol.



263
264
265
# File 'lib/timesteps/timestep.rb', line 263

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



87
88
89
90
91
92
93
94
95
96
97
98
99
100
# 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

Instance Method Details

#==(other) ⇒ Boolean

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

Parameters:

Returns:

  • (Boolean)


338
339
340
# File 'lib/timesteps/timestep.rb', line 338

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)


295
296
297
# File 'lib/timesteps/timestep.rb', line 295

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:



412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# 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) ⇒ 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:



629
630
631
632
# File 'lib/timesteps/timestep.rb', line 629

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


451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# File 'lib/timesteps/timestep.rb', line 451

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)


322
323
324
325
326
327
328
329
330
# 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_specString

Returns a string expression for interval section in timestem spec.

Returns:

  • (String)


272
273
274
275
276
277
278
# 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, 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:



519
520
521
522
523
524
525
526
527
528
# File 'lib/timesteps/timestep.rb', line 519

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)


564
565
566
567
# File 'lib/timesteps/timestep.rb', line 564

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:



598
599
600
# File 'lib/timesteps/timestep.rb', line 598

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

#offsetRational

Returns the time offset of origin time.

Returns:

  • (Rational)


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

def offset
  return @origin.offset
end

#origin_specString

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

Returns:

  • (String)


283
284
285
286
287
288
289
# 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) ⇒ 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:



315
316
317
# 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: "[]") ⇒ 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:



665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
# File 'lib/timesteps/timestep.rb', line 665

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)


581
582
583
584
# File 'lib/timesteps/timestep.rb', line 581

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:



614
615
616
# File 'lib/timesteps/timestep.rb', line 614

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:



696
697
698
# File 'lib/timesteps/timestep.rb', line 696

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:



710
711
712
# File 'lib/timesteps/timestep.rb', line 710

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:



493
494
495
496
497
498
499
500
501
502
# File 'lib/timesteps/timestep.rb', line 493

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:



370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# 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

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



646
647
648
# File 'lib/timesteps/timestep.rb', line 646

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:



548
549
550
# File 'lib/timesteps/timestep.rb', line 548

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