Class: TimeStep

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/timesteps/grads.rb,
lib/timesteps/calendar.rb,
lib/timesteps/timestep.rb,
lib/timesteps/timestepdatetimeext.rb

Defined Under Namespace

Modules: DateTimeExt Classes: Calendar, Converter, Pair

Constant Summary collapse

REGEXP_GRADS_TDEF =
/\A(?:TDEF\s+)?(\d+)\s+LINEAR\s+([^\s]*?)\s+([^\s]*?)\s*\z/i
GRADS_INCREMENT =
{
  "mn" => "minutes",
  "hr" => "hours",
  "dy" => "days",
  "mo" => "months",
  "yr" => "years",
}
PATTERN_NUMERIC =
'[\+\-]?\d*(?:\.\d+)?(?:[eE][\+\-]?\d+)?'
PATTERN_UNITS =
'years?|months?|days?|hours?|minutes?|seconds?|d|hr|h|min|sec|s'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(spec, calendar: "standard", bc: false, count: 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

* years, year
* months, month
* days, day, d
* hours, hr, h
* minutes, minute, min
* seconds, second, sec, s

The option ‘calendar` specifies the 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

The option ‘bc` is a flag whether AD/BC or EC when parsing timestamp for negative year. The option count specifies the number of time steps, which is hint for some methods (#each etc).

Parameters:

  • spec (String)
  • calendar (Hash) (defaults to: "standard")

    a customizable set of options

  • bc (Hash) (defaults to: false)

    a customizable set of options

  • count (Hash) (defaults to: nil)

    a customizable set of options

Options Hash (calendar:):

Options Hash (bc:):

  • (Boolean)

Options Hash (count:):

  • number (Integer)

    of time steps (as hint for some methods)



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/timesteps/timestep.rb', line 39

def initialize (spec, calendar: "standard", bc: false, count: nil)
  case calendar
  when String
    @calendar = Calendar.new(calendar, bc: bc)
  else
    @calendar = calendar
  end
  intervalspec, timespec = spec.split(/\s+since\s+/)
  parse_interval(intervalspec)
  @origin       = @calendar.parse(timespec)
  @intervalspec = format("%g %s", @numeric, symbol)
  @timespec     = @origin.strftime("%Y-%m-%d %H:%M:%S.%N %:z")
  @definition   = format("%s since %s", @intervalspec, @timespec)
  @count = count
end

Instance Attribute Details

#calendarObject (readonly)

Returns the value of attribute calendar.



101
102
103
# File 'lib/timesteps/timestep.rb', line 101

def calendar
  @calendar
end

#countObject

Returns the value of attribute count.



110
111
112
# File 'lib/timesteps/timestep.rb', line 110

def count
  @count
end

#definitionObject (readonly)

Returns the value of attribute definition.



101
102
103
# File 'lib/timesteps/timestep.rb', line 101

def definition
  @definition
end

#intervalObject (readonly)

Returns the value of attribute interval.



101
102
103
# File 'lib/timesteps/timestep.rb', line 101

def interval
  @interval
end

#intervalspecObject (readonly)

Returns the value of attribute intervalspec.



101
102
103
# File 'lib/timesteps/timestep.rb', line 101

def intervalspec
  @intervalspec
end

#numericObject (readonly)

Returns the value of attribute numeric.



101
102
103
# File 'lib/timesteps/timestep.rb', line 101

def numeric
  @numeric
end

#originObject (readonly)

Returns the value of attribute origin.



101
102
103
# File 'lib/timesteps/timestep.rb', line 101

def origin
  @origin
end

#symbolObject (readonly)

Returns the value of attribute symbol.



101
102
103
# File 'lib/timesteps/timestep.rb', line 101

def symbol
  @symbol
end

#timespecObject (readonly)

Returns the value of attribute timespec.



101
102
103
# File 'lib/timesteps/timestep.rb', line 101

def timespec
  @timespec
end

Class Method Details

.parse_grads_tdef(tdef_string) ⇒ Object



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/timesteps/grads.rb', line 17

def self.parse_grads_tdef (tdef_string)
  if tdef_string.strip =~ REGEXP_GRADS_TDEF
    count = $1.to_i
    time  = $2
    increment = $3
  else
    raise "invalid grads tdef string"
  end
  if time =~ /(((\d{2})?(:(\d{2}))?Z)?(\d{2}))?(\w{3})(\d{4})/i
    hour = $3 || "00"
    min  = $5 || "00"
    day  = $6 || "01"
    mon  = $7
    year = $8
    origin = DateTime.parse("#{year}#{mon}#{day} #{hour}:#{min}:00")
  else
    raise "invalid time format"
  end
  
  increment = increment.sub(/(mn|hr|dy|mo|yr)/i) {|s| GRADS_INCREMENT[s.downcase]}
  
  spec = format("%s since %s", increment, origin.strftime("%F %T"))
  
  return TimeStep.new(spec, count: count)
end

Instance Method Details

#==(other) ⇒ Boolean

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

Returns:

  • (Boolean)


179
180
181
# File 'lib/timesteps/timestep.rb', line 179

def == (other)
  return @definition == other.definition && @calendar == other.calendar 
end

#days_at(*indices) ⇒ DateTime+

Calculate the time difference in days from origin at the index.

Parameters:

  • indices (Array)

Returns:



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/timesteps/timestep.rb', line 253

def days_at (*indices)
  if indices.size == 1
    index = indices.first
    case @symbol
    when :years
      unless index.denominator == 1
        raise "index for years should be an integer"
      end
      return @origin.next_year(@numeric*index) - @origin
    when :months
      unless index.denominator == 1
        raise "index for years should be an integer"
      end
      return @origin.next_month(@numeric*index) - @origin
    else
      return user_to_days(index)
    end
  else
    return indices.map{ |index| days_at(index) }            
  end
end

#days_to_user(index) ⇒ Object



187
188
189
# File 'lib/timesteps/timestep.rb', line 187

def days_to_user (index)
  return ( 86400 * index.to_r ).quo(@interval)
end

#each(limit = nil, incr = 1, &block) ⇒ Object



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/timesteps/timestep.rb', line 302

def each (limit = nil, incr = 1, &block)
  if limit.nil? 
    raise "step method require count" unless @count
    limit = @count - 1
  end
  if block
    0.step(limit, incr) do |k|
      block.call(time_at(k))
    end
  else
    return Enumerator.new {|y|
      0.step(limit, incr) do |k|
        y << time_at(k)
      end      
    }
  end
end

#in(unit) ⇒ Object

Creates TimeStep::Pair



328
329
330
# File 'lib/timesteps/timestep.rb', line 328

def in (unit)
  return TimeStep::Pair.new(self, format("%s since %s", unit, @timespec), calendar: @calendar)
end

#index_at(*times) ⇒ Numeric

Returns the index for the given time in the unit represented by the object

Parameters:

Returns:

  • (Numeric)


228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/timesteps/timestep.rb', line 228

def index_at (*times)
  if times.size == 1
    time = times.first
    time = @calendar.parse(time) if time.is_a?(String)
    case @symbol
    when :years
      return time.difference_in_years(@origin).quo(@numeric.to_i)
    when :months
      return time.difference_in_months(@origin).quo(@numeric.to_i)
    else
      jday  = @calendar.date2jday(time.year, time.month, time.day)
    	fday  = time.fraction
    	udays = days_to_user(jday - @origin.jd)
    	utime = days_to_user(fday - @origin.fraction)
    	return udays + utime
    end
  else
    return times.map{|time| index_at(time) }      
  end
end

#infoObject



156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/timesteps/timestep.rb', line 156

def info
  return {
    "definition"   => @definition.clone,
    "intervalspec" => @intervalspec.clone,
    "timespec"     => @timespec.clone,
    "calendar"     => @calendar.name,
    "numeric"      => @numeric,
    "symbol"       => @symbol.to_s,
    "interval"     => @interval,
    "origin"       => @origin.clone,
    "count"        => @count,
    "bc"           => @calendar.bc?,
  }
end

#inspectObject



171
172
173
# File 'lib/timesteps/timestep.rb', line 171

def inspect
  "#<TimeStep definition='#{definition}' calendar='#{calendar.name}'>"
end

#limitDateTime?

Returns limit time if the object has ‘count`, otherwise returns nil.

Returns:



115
116
117
118
119
120
121
# File 'lib/timesteps/timestep.rb', line 115

def limit
  if @count
    return time_at(@count - 1)
  else
    return nil
  end
end

#limit=(time) ⇒ DateTime?

Sets count by giving maximum time.

Returns:



126
127
128
129
130
131
132
133
# File 'lib/timesteps/timestep.rb', line 126

def limit= (time)
  if time
    @count = (prev_index_of(time) + 1).to_i
  else
    @count = nil
  end
  return limit
end

#new_origin(time) ⇒ TimeStep

Returns TimeStep object which has origin time specified by the given ‘time`.

Parameters:

Returns:



292
293
294
295
296
297
298
# File 'lib/timesteps/timestep.rb', line 292

def new_origin (time)
  case time
  when String
    time = @calendar.parse(time)
  end
  return TimeStep.new(@intervalspec + " since " + time.strftime("%Y-%m-%d %H:%M:%S.%N %:z"), calendar: @calendar)
end

#next_index_of(time) ⇒ Object



332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/timesteps/timestep.rb', line 332

def next_index_of (time)
  case time
  when String
    time = @calendar.parse(time)
  end
  index = index_at(time).to_r
  if index.denominator == 1
    return index + 1
  else
    return index.ceil
  end
end

#next_time_of(time) ⇒ Object



349
350
351
# File 'lib/timesteps/timestep.rb', line 349

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

#parse(time) ⇒ Object



357
358
359
# File 'lib/timesteps/timestep.rb', line 357

def parse (time)
  return @calendar.parse(time)
end

#prev_index_of(time) ⇒ Object



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

def prev_index_of (time)
  return next_index_of(time) - 1
end

#prev_time_of(time) ⇒ Object



353
354
355
# File 'lib/timesteps/timestep.rb', line 353

def prev_time_of (time)
  return time_at(prev_index_of(time))  
end

#shift_origin(index) ⇒ TimeStep

Returns TimeStep object which has origin time determined by the given ‘index`.

Parameters:

  • index (Numeric)

Returns:



281
282
283
284
# File 'lib/timesteps/timestep.rb', line 281

def shift_origin (index)
  time = time_at(index)
  return TimeStep.new(@intervalspec + " since " + time.strftime("%Y-%m-%d %H:%M:%S.%N %:z"), calendar: @calendar)
end

#time_at(*indices) ⇒ DateTime

Returns the time represented by the given amount as DateTime object

Parameters:

  • indices (Numeric, Array<Numeric>)

Returns:



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/timesteps/timestep.rb', line 196

def time_at (*indices)
  if indices.size == 1
    index = indices.first
    raise ArgumentError, "index should be numeric" unless index.is_a?(Numeric)
    index = index.to_r
    case @symbol
    when :years
      unless index.denominator == 1
        raise "index for years should be an integer"
      end
      return @origin.next_year(index*@numeric)
    when :months
      unless index.denominator == 1
        raise "index for years should be an integer"
      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

#times(&block) ⇒ Object



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

def times (&block)
  raise "step method require count" unless @count
  (0...@count).each(&block)
end

#user_to_days(index) ⇒ Object



183
184
185
# File 'lib/timesteps/timestep.rb', line 183

def user_to_days (index)
  return ( @interval * index.to_r ).quo(86400)
end

#valid?(*indices) ⇒ Boolean

Returns:

  • (Boolean)


135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/timesteps/timestep.rb', line 135

def valid? (*indices)
  if @count
    if indices.size == 1
      index = indices.first
      if index >= 0 and index < @count 
        return true
      else
        return false
      end
    else
      return indices.map{|index| valid?(index) }
    end
  else
    if indices.size == 1
      return true
    else
      return indices.map{|index| true }
    end
  end
end