The TjTime class extends the original Ruby class Time with lots of TaskJuggler specific additional functionality. This is mostly for handling time zones.
The number of days per month. Leap years are taken care of separately.
Check if zone
is a valid time zone.
# File lib/taskjuggler/TjTime.rb, line 63 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
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 41 def initialize(t = nil) @timeZone = @@tz case t when nil @time = Time.now when Time @time = t @timeZone = nil when TjTime @time = t.time @timeZone = nil when String parse(t) when Array @time = Time.mktime(*t) else @time = Time.at(t) end end
Set a new active time zone. zone must be a valid String known to the underlying operating system.
# File lib/taskjuggler/TjTime.rb, line 92 def TjTime.setTimeZone(zone) unless zone && TjTime.checkTimeZone(zone) raise "Illegal time zone #{zone}" end oldTimeZone = @@tz @@tz = zone ENV['TZ'] = zone oldTimeZone end
Return the name of the currently active time zone.
# File lib/taskjuggler/TjTime.rb, line 106 def TjTime.timeZone @@tz end
Convert the time to seconds since Epoch and return the module of val.
# File lib/taskjuggler/TjTime.rb, line 143 def %(val) @time.to_i % val end
Add secs number of seconds to the time.
# File lib/taskjuggler/TjTime.rb, line 128 def +(secs) TjTime.new(@time.to_i + secs) end
Substract arg number of seconds or return the number of seconds between arg and this time.
# File lib/taskjuggler/TjTime.rb, line 134 def -(arg) if arg.is_a?(TjTime) @time - arg.time else TjTime.new(@time.to_i - arg) end end
Return true if time is smaller than t.
# File lib/taskjuggler/TjTime.rb, line 148 def <(t) return false unless t @time < t.time end
Return true if time is smaller or equal than t.
# File lib/taskjuggler/TjTime.rb, line 154 def <=(t) return false unless t @time <= t.time end
Coparison operator for time with another time t.
# File lib/taskjuggler/TjTime.rb, line 178 def <=>(t) return -1 unless t @time <=> t.time end
Return true if time and t are identical.
# File lib/taskjuggler/TjTime.rb, line 172 def ==(t) return false unless t @time == t.time end
Return true if time is larger than t.
# File lib/taskjuggler/TjTime.rb, line 160 def >(t) return true unless t @time > t.time end
Return true if time is larger or equal than t.
# File lib/taskjuggler/TjTime.rb, line 166 def >=(t) return true unless t @time >= t.time end
Align the date to a time grid. The grid distance is determined by clock.
# File lib/taskjuggler/TjTime.rb, line 111 def align(clock) TjTime.new((localtime.to_i / clock) * clock) end
Normalize time to the beginning of the current hour.
# File lib/taskjuggler/TjTime.rb, line 194 def beginOfHour sec, min, hour, day, month, year = localtime.to_a sec = min = 0 TjTime.new([ year, month, day, hour, min, sec, 0 ]) end
Normalize time to the beginning of the current month.
# File lib/taskjuggler/TjTime.rb, line 223 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
Normalize time to the beginning of the current quarter.
# File lib/taskjuggler/TjTime.rb, line 231 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
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 209 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
Normalize time to the beginning of the current year.
# File lib/taskjuggler/TjTime.rb, line 240 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
Return the day of the month (1..n).
# File lib/taskjuggler/TjTime.rb, line 405 def day localtime.day end
Return the number of days between this time and date. The result is always rounded up.
# File lib/taskjuggler/TjTime.rb, line 339 def daysTo(date) countIntervals(date, :sameTimeNextDay) end
Return the hours of the day (0..23)
# File lib/taskjuggler/TjTime.rb, line 400 def hour localtime.hour end
Return a new time that is hours later than time.
# File lib/taskjuggler/TjTime.rb, line 248 def hoursLater(hours) TjTime.new(@time + hours * 3600) end
Return the number of hours between this time and date. The result is always rounded up.
# File lib/taskjuggler/TjTime.rb, line 332 def hoursTo(date) t1, t2 = order(date) ((t2 - t1) / 3600).ceil end
Normalize time to the beginning of the current day.
# File lib/taskjuggler/TjTime.rb, line 201 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
Return the month of the year (1..12)
# File lib/taskjuggler/TjTime.rb, line 410 def month localtime.month end
Return the number of months between this time and date. The result is always rounded up.
# File lib/taskjuggler/TjTime.rb, line 351 def monthsTo(date) countIntervals(date, :sameTimeNextMonth) end
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 322 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
Return the number of quarters between this time and date. The result is always rounded up.
# File lib/taskjuggler/TjTime.rb, line 357 def quartersTo(date) countIntervals(date, :sameTimeNextQuarter) end
Return a new time that is 1 day later than time but at the same time of day.
# File lib/taskjuggler/TjTime.rb, line 259 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
Return a new time that is 1 hour later than time.
# File lib/taskjuggler/TjTime.rb, line 253 def sameTimeNextHour hoursLater(1) end
Return a new time that is 1 month later than time but at the same time of day.
# File lib/taskjuggler/TjTime.rb, line 287 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
Return a new time that is 1 quarter later than time but at the same time of day.
# File lib/taskjuggler/TjTime.rb, line 300 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
Return a new time that is 1 week later than time but at the same time of day.
# File lib/taskjuggler/TjTime.rb, line 273 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
Return a new time that is 1 year later than time but at the same time of day.
# File lib/taskjuggler/TjTime.rb, line 311 def sameTimeNextYear sec, min, hour, day, month, year = localtime.to_a year += 1 TjTime.new([ year, month, day, hour, min, sec, 0]) end
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 122 def secondsOfDay(tz = nil) lt = localtime (lt.to_i + lt.gmt_offset) % (60 * 60 * 24) end
# File lib/taskjuggler/TjTime.rb, line 390 def strftime(format) localtime.strftime(format) end
# File lib/taskjuggler/TjTime.rb, line 386 def to_a localtime.to_a end
Return the seconds since Epoch.
# File lib/taskjuggler/TjTime.rb, line 382 def to_i localtime.to_i end
This function is just a wrapper around Time.strftime(). In case @time is nil, it returns 'unkown'.
# File lib/taskjuggler/TjTime.rb, line 369 def to_s(format = nil) return 'unknown' if @time.nil? 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/, "#{((localtime.mon - 1) / 3) + 1}") end # Always report values in local timezone localtime.strftime(fmt) end
Iterator that executes the block until time has reached endDate increasing time by step on each iteration.
# File lib/taskjuggler/TjTime.rb, line 185 def upto(endDate, step = 1) t = @time while t < endDate.time yield(TjTime.new(t)) t += step end end
Return the time object in UTC.
# File lib/taskjuggler/TjTime.rb, line 116 def utc TjTime.new(@time.dup.gmtime) end
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
Return the number of weeks between this time and date. The result is always rounded up.
# File lib/taskjuggler/TjTime.rb, line 345 def weeksTo(date) countIntervals(date, :sameTimeNextWeek) end
Return the year.
# File lib/taskjuggler/TjTime.rb, line 417 def year localtime.year end
Return the number of years between this time and date. The result is always rounded up.
# File lib/taskjuggler/TjTime.rb, line 363 def yearsTo(date) countIntervals(date, :sameTimeNextYear) end
# File lib/taskjuggler/TjTime.rb, line 527 def countIntervals(date, stepFunc) i = 0 t1, t2 = order(date) while t1 < t2 t1 = t1.send(stepFunc) i += 1 end i end
# File lib/taskjuggler/TjTime.rb, line 537 def lastDayOfMonth(month, year) month == 2 && leapYear?(year) ? 29 : MON_MAX[month] end
# File lib/taskjuggler/TjTime.rb, line 541 def leapYear?(year) case when year % 400 == 0 true when year % 100 == 0 false else year % 4 == 0 end end
# File lib/taskjuggler/TjTime.rb, line 552 def localtime return @time if @timeZone == @@tz 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
# File lib/taskjuggler/TjTime.rb, line 523 def order(date) self < date ? [ self, date ] : [ date, self ] end
# 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 if tzHour < 0 || tzHour > 12 raise TjException.new, "Time zone adjustment hour out of range " + "(0 - 12) but is #{tzHour}" end 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 += sign * (tzHour * 3600 + tzMinute * 60) @timeZone = 'UTC' else @time = Time.mktime(year, month, day, hour, minute, second) end end