class TaskJuggler::TjTime

The TjTime class extends the original Ruby class Time with lots of TaskJuggler specific additional functionality. This is mostly for handling time zones.

Constants

MON_MAX

The number of days per month. Leap years are taken care of separately.

Attributes

time[R]

Public Class Methods

checkTimeZone(zone) click to toggle source

Check if zone is a valid time zone.

# File lib/taskjuggler/TjTime.rb, line 61
def TjTime.checkTimeZone(zone)
  return true if zone == 'UTC'

  # Valid time zones must be of the form 'Region/City'
  return false unless zone.include?('/')

  # Save curent value of TZ
  tz = ENV['TZ']
  ENV['TZ'] = zone
  newZone = Time.new.zone
  # If the time zone is valid, the OS can convert a zone like
  # 'America/Denver' into 'MST'. Unknown time zones are either not
  # converted or cause a fallback to UTC.
  # Since glibc 2.10 Time.new.zone only return the region for illegal
  # zones instead of the full zone string like it does on earlier
  # versions.
  region = zone[0..zone.index('/') - 1]
  res = (newZone != zone && newZone != region && newZone != 'UTC')
  # Restore TZ if it was set earlier.
  if tz
    ENV['TZ'] = tz
  else
    ENV.delete('TZ')
  end
  res
end
TjTime() → TjTime (now) click to toggle source
TjTime(tjtime) → TjTime
TjTime(time, timezone) → TjTime
TjTime(str) → TjTime
TjTime(secs) → TjTime

The constructor is overloaded and accepts 4 kinds of arguments. If t is a Time object it’s assumed to be in local time. If it’s a string, it is parsed as a date. Or else it is interpreted as seconds after Epoch.

# File lib/taskjuggler/TjTime.rb, line 43
def initialize(t = nil)
  case t
  when nil
    @time = Time.now
  when Time
    @time = t
  when TjTime
    @time = t.time
  when String
    parse(t)
  when Array
    @time = Time.mktime(*t)
  else
    @time = Time.at(t)
  end
end
setTimeZone(zone) click to toggle source

Set a new active time zone. zone must be a valid String known to the underlying operating system.

# File lib/taskjuggler/TjTime.rb, line 90
def TjTime.setTimeZone(zone)
  unless zone && TjTime.checkTimeZone(zone)
    raise "Illegal time zone #{zone}"
  end

  oldTimeZone = @@tz

  @@tz = zone
  ENV['TZ'] = zone

  oldTimeZone
end
timeZone() click to toggle source

Return the name of the currently active time zone.

# File lib/taskjuggler/TjTime.rb, line 104
def TjTime.timeZone
  @@tz
end

Public Instance Methods

%(val) click to toggle source

Convert the time to seconds since Epoch and return the module of val.

# File lib/taskjuggler/TjTime.rb, line 141
def %(val)
  @time.to_i % val
end
+(secs) click to toggle source

Add secs number of seconds to the time.

# File lib/taskjuggler/TjTime.rb, line 126
def +(secs)
  TjTime.new(@time.to_i + secs)
end
-(arg) click to toggle source

Substract arg number of seconds or return the number of seconds between arg and this time.

# File lib/taskjuggler/TjTime.rb, line 132
def -(arg)
  if arg.is_a?(TjTime)
    @time - arg.time
  else
    TjTime.new(@time.to_i - arg)
  end
end
<(t) click to toggle source

Return true if time is smaller than t.

# File lib/taskjuggler/TjTime.rb, line 146
def <(t)
  return false unless t
  @time < t.time
end
<=(t) click to toggle source

Return true if time is smaller or equal than t.

# File lib/taskjuggler/TjTime.rb, line 152
def <=(t)
  return false unless t
  @time <= t.time
end
<=>(t) click to toggle source

Coparison operator for time with another time t.

# File lib/taskjuggler/TjTime.rb, line 176
def <=>(t)
  return -1 unless t
  @time <=> t.time
end
==(t) click to toggle source

Return true if time and t are identical.

# File lib/taskjuggler/TjTime.rb, line 170
def ==(t)
  return false unless t
  @time == t.time
end
>(t) click to toggle source

Return true if time is larger than t.

# File lib/taskjuggler/TjTime.rb, line 158
def >(t)
  return true unless t
  @time > t.time
end
>=(t) click to toggle source

Return true if time is larger or equal than t.

# File lib/taskjuggler/TjTime.rb, line 164
def >=(t)
  return true unless t
  @time >= t.time
end
align(clock) click to toggle source

Align the date to a time grid. The grid distance is determined by clock.

# File lib/taskjuggler/TjTime.rb, line 109
def align(clock)
  TjTime.new((localtime.to_i / clock) * clock)
end
beginOfHour() click to toggle source

Normalize time to the beginning of the current hour.

# File lib/taskjuggler/TjTime.rb, line 192
def beginOfHour
  sec, min, hour, day, month, year = localtime.to_a
  sec = min = 0
  TjTime.new([ year, month, day, hour, min, sec, 0 ])
end
beginOfMonth() click to toggle source

Normalize time to the beginning of the current month.

# File lib/taskjuggler/TjTime.rb, line 221
def beginOfMonth
  sec, min, hour, day, month, year = localtime.to_a
  sec = min = hour = 0
  day = 1
  TjTime.new([ year, month, day, hour, min, sec, 0 ])
end
beginOfQuarter() click to toggle source

Normalize time to the beginning of the current quarter.

# File lib/taskjuggler/TjTime.rb, line 229
def beginOfQuarter
  sec, min, hour, day, month, year = localtime.to_a
  sec = min = hour = 0
  day = 1
  month = ((month - 1) % 3 ) + 1
  TjTime.new([ year, month, day, hour, min, sec, 0 ])
end
beginOfWeek(startMonday) click to toggle source

Normalize time to the beginning of the current week. startMonday determines whether the week should start on Monday or Sunday.

# File lib/taskjuggler/TjTime.rb, line 207
def beginOfWeek(startMonday)
  t = localtime.to_a
  # Set time to noon, 12:00:00
  t[0, 3] = [ 0, 0, 12 ]
  weekday = t[6]
  t.slice!(6, 4)
  t.reverse!
  # Substract the number of days determined by the weekday t[6] and set time
  # to midnight of that day.
  (TjTime.new(Time.local(*t)) -
   (weekday - (startMonday ? 1 : 0)) * 60 * 60 * 24).midnight
end
beginOfYear() click to toggle source

Normalize time to the beginning of the current year.

# File lib/taskjuggler/TjTime.rb, line 238
def beginOfYear
  sec, min, hour, day, month, year = localtime.to_a
  sec = min = hour = 0
  day = month = 1
  TjTime.new([ year, month, day, hour, min, sec, 0 ])
end
day() click to toggle source

Return the day of the month (1..n).

# File lib/taskjuggler/TjTime.rb, line 405
def day
  localtime.day
end
daysTo(date) click to toggle source

Return the number of days between this time and date. The result is always rounded up.

# File lib/taskjuggler/TjTime.rb, line 337
def daysTo(date)
  countIntervals(date, :sameTimeNextDay)
end
hour() click to toggle source

Return the hours of the day (0..23)

# File lib/taskjuggler/TjTime.rb, line 400
def hour
  localtime.hour
end
hoursLater(hours) click to toggle source

Return a new time that is hours later than time.

# File lib/taskjuggler/TjTime.rb, line 246
def hoursLater(hours)
  TjTime.new(@time + hours * 3600)
end
hoursTo(date) click to toggle source

Return the number of hours between this time and date. The result is always rounded up.

# File lib/taskjuggler/TjTime.rb, line 330
def hoursTo(date)
  t1, t2 = order(date)
  ((t2 - t1) / 3600).ceil
end
midnight() click to toggle source

Normalize time to the beginning of the current day.

# File lib/taskjuggler/TjTime.rb, line 199
def midnight
  sec, min, hour, day, month, year = localtime.to_a
  sec = min = hour = 0
  TjTime.new([ year, month, day, hour, min, sec, 0 ])
end
mon()
Alias for: month
month() click to toggle source

Return the month of the year (1..12)

# File lib/taskjuggler/TjTime.rb, line 410
def month
  localtime.month
end
Also aliased as: mon
monthsTo(date) click to toggle source

Return the number of months between this time and date. The result is always rounded up.

# File lib/taskjuggler/TjTime.rb, line 349
def monthsTo(date)
  countIntervals(date, :sameTimeNextMonth)
end
nextDayOfWeek(dow) click to toggle source

Return the start of the next dow day of week after date. dow must be 0 for Sundays, 1 for Mondays and 6 for Saturdays. If date is a Tuesday and dow is 5 (Friday) the date of next Friday 0:00 will be returned. If date is a Tuesday and dow is 2 (Tuesday) the date of the next Tuesday will be returned.

# File lib/taskjuggler/TjTime.rb, line 320
def nextDayOfWeek(dow)
  raise "Day of week must be 0 - 6." unless dow >= 0 && dow <= 6
  d = midnight.sameTimeNextDay
  currentDoW = d.strftime('%w').to_i
  1.upto((dow + 7 - currentDoW) % 7) { |i| d = d.sameTimeNextDay }
  d
end
quartersTo(date) click to toggle source

Return the number of quarters between this time and date. The result is always rounded up.

# File lib/taskjuggler/TjTime.rb, line 355
def quartersTo(date)
  countIntervals(date, :sameTimeNextQuarter)
end
sameTimeNextDay() click to toggle source

Return a new time that is 1 day later than time but at the same time of day.

# File lib/taskjuggler/TjTime.rb, line 257
def sameTimeNextDay
  sec, min, hour, day, month, year = localtime.to_a
  if (day += 1) > lastDayOfMonth(month, year)
    day = 1
    if (month += 1) > 12
      month = 1
      year += 1
    end
  end
  TjTime.new([ year, month, day, hour, min, sec, 0 ])
end
sameTimeNextHour() click to toggle source

Return a new time that is 1 hour later than time.

# File lib/taskjuggler/TjTime.rb, line 251
def sameTimeNextHour
  hoursLater(1)
end
sameTimeNextMonth() click to toggle source

Return a new time that is 1 month later than time but at the same time of day.

# File lib/taskjuggler/TjTime.rb, line 285
def sameTimeNextMonth
  sec, min, hour, day, month, year = localtime.to_a
  monMax = month == 2 && leapYear?(year) ? 29 : MON_MAX[month]
  if (month += 1) > 12
    month = 1
    year += 1
  end
  day = monMax if day >= lastDayOfMonth(month, year)
  TjTime.new([ year, month, day, hour, min, sec, 0 ])
end
sameTimeNextQuarter() click to toggle source

Return a new time that is 1 quarter later than time but at the same time of day.

# File lib/taskjuggler/TjTime.rb, line 298
def sameTimeNextQuarter
  sec, min, hour, day, month, year = localtime.to_a
  if (month += 3) > 12
    month -= 12
    year += 1
  end
  TjTime.new([ year, month, day, hour, min, sec, 0 ])
end
sameTimeNextWeek() click to toggle source

Return a new time that is 1 week later than time but at the same time of day.

# File lib/taskjuggler/TjTime.rb, line 271
def sameTimeNextWeek
  sec, min, hour, day, month, year = localtime.to_a
  if (day += 7) > lastDayOfMonth(month, year)
    day -= lastDayOfMonth(month, year)
    if (month += 1) > 12
      month = 1
      year += 1
    end
  end
  TjTime.new([ year, month, day, hour, min, sec, 0 ])
end
sameTimeNextYear() click to toggle source

Return a new time that is 1 year later than time but at the same time of day.

# File lib/taskjuggler/TjTime.rb, line 309
def sameTimeNextYear
  sec, min, hour, day, month, year = localtime.to_a
  year += 1
  TjTime.new([ year, month, day, hour, min, sec, 0])
end
secondsOfDay(tz = nil) click to toggle source

Returns the total number of seconds of the day. The time is assumed to be in the time zone specified by tz.

# File lib/taskjuggler/TjTime.rb, line 120
def secondsOfDay(tz = nil)
  lt = localtime
  (lt.to_i + lt.gmt_offset) % (60 * 60 * 24)
end
strftime(format) click to toggle source
# File lib/taskjuggler/TjTime.rb, line 390
def strftime(format)
  localtime.strftime(format)
end
to_a() click to toggle source
# File lib/taskjuggler/TjTime.rb, line 386
def to_a
  localtime.to_a
end
to_i() click to toggle source

Return the seconds since Epoch.

# File lib/taskjuggler/TjTime.rb, line 382
def to_i
  localtime.to_i
end
to_s(format = nil, tz = nil) click to toggle source

This function is just a wrapper around Time.strftime(). In case @time is nil, it returns ‘unkown’.

# File lib/taskjuggler/TjTime.rb, line 367
def to_s(format = nil, tz = nil)
  return 'unknown' if @time.nil?

  t = tz == 'UTC' ? gmtime : localtime
  if format.nil?
    fmt = '%Y-%m-%d-%H:%M' + (@time.sec == 0 ? '' : ':%S') + '-%z'
  else
    # Handle TJ specific extensions to the strftime format.
    fmt = format.sub(/%Q/, "#{((t.mon - 1) / 3) + 1}")
  end
  # Always report values in local timezone
  t.strftime(fmt)
end
upto(endDate, step = 1) { |tj_time| ... } click to toggle source

Iterator that executes the block until time has reached endDate increasing time by step on each iteration.

# File lib/taskjuggler/TjTime.rb, line 183
def upto(endDate, step = 1)
  t = @time
  while t < endDate.time
    yield(TjTime.new(t))
    t += step
  end
end
utc() click to toggle source

Return the time object in UTC.

# File lib/taskjuggler/TjTime.rb, line 114
def utc
  TjTime.new(@time.dup.gmtime)
end
wday() click to toggle source

Return the day of the week. 0 for Sunday, 1 for Monday and so on.

# File lib/taskjuggler/TjTime.rb, line 395
def wday
  localtime.wday
end
weeksTo(date) click to toggle source

Return the number of weeks between this time and date. The result is always rounded up.

# File lib/taskjuggler/TjTime.rb, line 343
def weeksTo(date)
  countIntervals(date, :sameTimeNextWeek)
end
year() click to toggle source

Return the year.

# File lib/taskjuggler/TjTime.rb, line 417
def year
  localtime.year
end
yearsTo(date) click to toggle source

Return the number of years between this time and date. The result is always rounded up.

# File lib/taskjuggler/TjTime.rb, line 361
def yearsTo(date)
  countIntervals(date, :sameTimeNextYear)
end

Private Instance Methods

countIntervals(date, stepFunc) click to toggle source
# File lib/taskjuggler/TjTime.rb, line 533
def countIntervals(date, stepFunc)
  i = 0
  t1, t2 = order(date)
  while t1 < t2
    t1 = t1.send(stepFunc)
    i += 1
  end
  i
end
gmtime() click to toggle source
# File lib/taskjuggler/TjTime.rb, line 558
def gmtime
  if @time.utc?
    # @time is already in the right zone (UTC)
    @time
  else
    # To convert a Time object from local time to UTC.
    @time.dup.gmtime
  end
end
lastDayOfMonth(month, year) click to toggle source
# File lib/taskjuggler/TjTime.rb, line 543
def lastDayOfMonth(month, year)
  month == 2 && leapYear?(year) ? 29 : MON_MAX[month]
end
leapYear?(year) click to toggle source
# File lib/taskjuggler/TjTime.rb, line 547
def leapYear?(year)
  case
  when year % 400 == 0
    true
  when year % 100 == 0
    false
  else
    year % 4 == 0
  end
end
localtime() click to toggle source
# File lib/taskjuggler/TjTime.rb, line 568
def localtime
  if @time.utc?
    if @@tz == 'UTC'
      # @time is already in the right zone (UTC)
      @time
    else
      @time.dup.localtime
    end
  elsif @@tz == 'UTC'
    # @time is not in UTC, so convert it to local time.
    @time.dup.gmtime
  else
    # To convert a Time object from one local time to another, we need to
    # conver to UTC first and then to the new local time.
    @time.dup.gmtime.localtime
  end
end
order(date) click to toggle source
# File lib/taskjuggler/TjTime.rb, line 529
def order(date)
  self < date ? [ self, date ] : [ date, self ]
end
parse(t) click to toggle source
# File lib/taskjuggler/TjTime.rb, line 423
def parse(t)
  year, month, day, time, zone = t.split('-', 5)

  # Check the year
  if year
    year = year.to_i
    if year < 1970 || year > 2035
      raise TjException.new, "Year #{year} out of range (1970 - 2035)"
    end
  else
    raise TjException.new, "Year not specified"
  end

  # Check the month
  if month
    month = month.to_i
    if month < 1 || month > 12
      raise TjException.new, "Month #{month} out of range (1 - 12)"
    end
  else
    raise TjException.new, "Month not specified"
  end

  # Check the day
  if day
    day = day.to_i
    maxDay = [ 0, 31, Date.gregorian_leap?(year) ? 29 : 28, 31, 30, 31,
               30, 31, 31, 30, 31, 30, 31 ]
    if month < 1 || month > maxDay[month]
      raise TjException.new, "Day #{day} out of range (1 - #{maxDay[month]})"
    end
  else
    raise TjException.new, "Day not specified"
  end

  # The time is optional. Will be expanded to 00:00:00.
  if time
    hour, minute, second = time.split(':')

    # Check hour
    if hour
      hour = hour.to_i
      if hour < 0 || hour > 23
        raise TjException.new, "Hour #{hour} out of range (0 - 23)"
      end
    else
      raise TjException.new, "Hour not specified"
    end

    if minute
      minute = minute.to_i
      if minute < 0 || minute > 59
        raise TjException.new, "Minute #{minute} out of range (0 - 59)"
      end
    else
      raise TjException.new, "Minute not specified"
    end

    # Check sencond. This value is optional and defaults to 0.
    if second
      second = second.to_i
      if second < 0 || second > 59
        raise TjException.new, "Second #{second} out of range (0 - 59)"
      end
    else
      second = 0
    end
  else
    hour = minute = second = 0
  end

  # The zone is optional and defaults to the current time zone.
  if zone
    if zone[0] != ?- && zone[0] != ?+
      raise TjException.new, "Time zone adjustment must be prefixed by " +
                             "+ or -, not #{zone[0]}"
    end
    if zone.length != 5
      raise TjException.new, "Time zone adjustment must use (+/-)HHMM format"
    end

    @time = Time.utc(year, month, day, hour, minute, second)
    sign = zone[0] == ?- ? -1 : 1
    tzHour = zone[1..2].to_i
    tzMinute = zone[3..4].to_i
    if tzMinute < 0 || tzMinute > 59
      raise TjException.new, "Time zone adjustment minute out of range " +
                             "(0 - 59) but is #{tzMinute}"
    end

    time_offset = sign * (tzHour * 3600 + tzMinute * 60)
    # UTC-1200 is the most westerly time zone but UTC+1400 is the most
    # easterly time zone (Republic of Kiribati).
    if time_offset < -12 * 3600 || time_offset > 14 * 3600
      raise TjException.new, "Time zone adjustment out of range " +
                             "(-1200 - +1400} but is #{zone})"
    end

    # The time offset must be substracted from the base time to convert it
    # to UTC.
    @time -= time_offset
  else
    @time = Time.mktime(year, month, day, hour, minute, second)
  end
end