require "time" #This class handels various time- and date-specific behaviour in a friendly way. #===Examples # datet = Knj::Datet.new #=> 2012-05-03 20:35:16 +0200 # datet = Knj::Datet.new(Time.now) #=> 2012-05-03 20:35:16 +0200 # datet.months + 5 #=> 2012-10-03 20:35:16 +0200 # datet.days + 64 #=> 2012-12-06 20:35:16 +010 class Knj::Datet attr_accessor :time #Initializes the object. Default is the current time. A time-object can be given. def initialize(time = Time.now, *args) if time.is_a?(Time) @time = time else begin @time = Time.new(*([time] | args)) rescue ArgumentError => e days_left = 0 months_left = 0 hours_left = 0 mins_left = 0 secs_left = 0 usecs_left = 0 #Check larger month the allowed. if args[0] and args[0] > 12 months_left = args[0] - 12 args[0] = 12 end #Check larger date than allowed. datet = Knj::Datet.new(time, args[0], 1) dim = datet.days_in_month if args[1] and args[1] > dim days_left = args[1] - dim args[1] = dim if days_left > 0 end #Check larger hour than allowed. if args[2] and args[2] >= 24 hours_left = args[2] + 1 args[2] = 0 end #Check larger minute than allowed. if args[3] and args[3] >= 60 mins_left = args[3] + 1 args[3] = 0 end #Check larger secs than allowed. if args[4] and args[4] >= 60 secs_left = args[4] + 1 args[4] = 0 end #Check larger usecs than allowed. if args[5] and args[5] >= 60 usecs_left = args[5] + 1 args[5] = 0 end #Generate new stamp. @time = Time.new(*([time] | args)) self.mins + mins_left if mins_left > 0 self.hours + hours_left if hours_left > 0 self.days + days_left if days_left > 0 self.months + months_left if months_left > 0 self.secs + secs_left if secs_left > 0 self.usecs + usecs_left if usecs_left > 0 end end end #Goes forward day-by-day and stops at a date matching the criteria given. # #===Examples # datet.time #=> 2012-05-03 19:36:08 +0200 # #Try to find next saturday. # datet.find(:day, :day_in_week => 5) #=> 2012-05-05 19:36:08 +0200 # #Try to find next wednesday by Time's wday-method. # datet.find(:day, :wday => 3) #=> 2012-05-09 19:36:08 +0200 def find(incr, args) count = 0 while true if args[:day_in_week] and self.day_in_week == args[:day_in_week] return self elsif args[:wday] and self.time.wday == args[:wday].to_i return self end if incr == :day self.add_days(1) elsif incr == :month self.add_months(1) else raise "Invalid increment: #{incr}." end count += 1 raise "Endless loop?" if count > 999 end end #Add a given amount of seconds to the object. def add_usecs(usecs = 1) usecs = usecs.to_i cur_usecs = @time.usec next_usec = cur_usecs + usecs if next_usec >= 60 @time = self.add_secs(1).stamp(:datet => false, :usec => 0) usecs_left = (usecs - 1) - (60 - cur_usecs) return self.add_usecs(usecs_left) if usecs_left > 0 elsif next_usec < 0 @time = self.add_secs(-1).stamp(:datet => false, :usec => 59) usecs_left = usecs + cur_usecs + 1 self.add_usecs(usecs_left) if usecs_left > 0 else @time = self.stamp(:datet => false, :usec => next_usec) end return self end #Add a given amount of seconds to the object. def add_secs(secs = 1) secs = secs.to_i cur_secs = @time.sec next_sec = cur_secs + secs if next_sec >= 60 @time = self.add_mins(1).stamp(:datet => false, :sec => 0) secs_left = (secs - 1) - (60 - cur_secs) return self.add_secs(secs_left) if secs_left > 0 elsif next_sec < 0 @time = self.add_mins(-1).stamp(:datet => false, :sec => 59) secs_left = secs + cur_secs + 1 self.add_secs(secs_left) if secs_left > 0 else @time = self.stamp(:datet => false, :sec => next_sec) end return self end #Add a given amount of minutes to the object. #===Examples # datet = Knj::Datet.new #=> 2012-05-03 17:39:45 +0200 # datet.add_mins(30) # datet.time #=> 2012-05-03 18:08:45 +0200 def add_mins(mins = 1) mins = mins.to_i cur_mins = @time.min next_min = cur_mins + mins if next_min >= 60 @time = self.add_hours(1).stamp(:datet => false, :min => 0) mins_left = (mins - 1) - (60 - cur_mins) return self.add_mins(mins_left) if mins_left > 0 elsif next_min < 0 @time = self.add_hours(-1).stamp(:datet => false, :min => 59) mins_left = mins + cur_mins + 1 self.add_mins(mins_left) if mins_left > 0 else @time = self.stamp(:datet => false, :min => next_min) end return self end #Adds a given amount of hours to the object. #===Examples # datet = Knj::Datet.new # datet.add_hours(2) def add_hours(hours = 1) hours = hours.to_i cur_hour = @time.hour next_hour = cur_hour + hours if next_hour >= 24 @time = self.add_days(1).stamp(:datet => false, :hour => 0) hours_left = (hours - 1) - (24 - cur_hour) return self.add_hours(hours_left) if hours_left > 0 elsif next_hour < 0 @time = self.add_days(-1).stamp(:datet => false, :hour => 23) hours_left = hours + cur_hour + 1 self.add_hours(hours_left) if hours_left < 0 else @time = self.stamp(:datet => false, :hour => next_hour) end return self end #Adds a given amount of days to the object. #===Examples # datet = Knj::Datet.new #=> 2012-05-03 17:42:27 +0200 # datet.add_days(29) # datet.time #=> 2012-06-01 17:42:27 +0200 def add_days(days = 1) days = days.to_i return self if days == 0 dim = self.days_in_month cur_day = @time.day next_day = cur_day + days if next_day > dim @time = self.add_months(1).stamp(:datet => false, :day => 1) days_left = (days - 1) - (dim - cur_day) self.add_days(days_left) if days_left > 0 elsif next_day <= 0 self.date = 1 self.add_months(-1) @time = self.stamp(:datet => false, :day => self.days_in_month) days_left = days + 1 self.add_days(days_left) if days_left != 0 else @time = self.stamp(:datet => false, :day => next_day) end return self end #Adds a given amount of months to the object. #===Examples # datet.time #=> 2012-06-01 17:42:27 +0200 # datet.add_months(2) # datet.time #=> 2012-08-01 17:42:27 +0200 def add_months(months = 1) months = months.to_i cur_month = @time.month cur_day = @time.day next_month = cur_month + months.to_i if next_month > 12 @time = self.add_years(1).stamp(:datet => false, :month => 1, :day => 1) months_left = (months - 1) - (12 - cur_month) return self.add_months(months_left) if months_left > 0 elsif next_month < 1 @time = self.add_years(-1).stamp(:datet => false, :month => 12) else @time = self.stamp(:datet => false, :month => next_month, :day => 1) end dim = self.days_in_month if dim < cur_day @time = self.stamp(:datet => false, :day => dim) else @time = self.stamp(:datet => false, :day => cur_day) end return self end #Adds a given amount of years to the object. #===Examples # datet.time #=> 2012-08-01 17:42:27 +0200 # datet.add_years(3) # datet.time #> 2014-08-01 17:42:27 +0200 def add_years(years = 1) next_year = @time.year + years.to_i @time = self.stamp(:datet => false, :year => next_year) return self end #Is a year a leap year in the Gregorian calendar? Copied from Date-class. #===Examples # if Knj::Datet.gregorian_leap?(2005) # print "2005 is a gregorian-leap year." # else # print "2005 is not a gregorian-leap year." # end def self.gregorian_leap?(y) if Date.respond_to?("gregorian_leap?") return Date.gregorian_leap?(y) elsif y % 4 == 0 && y % 100 != 0 return true elsif y % 400 == 0 return true else return false end end #Returns the number of days in the month. #===Examples # datet = Knj::Datet.new # print "There are #{datet.days_in_month} days in the current month." def days_in_month return 29 if month == 2 and Knj::Datet.gregorian_leap?(self.year) #Thanks to ActiveSupport: http://rubydoc.info/docs/rails/2.3.8/ActiveSupport/CoreExtensions/Time/Calculations days_in_months = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] return days_in_months[@time.month] end #Returns the day in the week. Monday being 1 and sunday being 6. def day_in_week diw = @time.strftime("%w").to_i if diw == 0 diw = 6 else diw -= 1 end return diw end #Returns the days name as a string. def day_name return @time.strftime("%A") end #Returns the months name as a string. def month_name return @time.strftime("%B") end #Returns the year as an integer. def year return @time.year end #Returns the hour as an integer. def hour return @time.hour end #Returns the minute as an integer. def min return @time.min end #Returns the seconds as an integer. def sec return @time.sec end #Returns the microsecond as an integer. def usec return @time.usec end #Changes the year to the given year. # datet = Knj::Datet.now #=> 2014-05-03 17:46:11 +0200 # datet.year = 2005 # datet.time #=> 2005-05-03 17:46:11 +0200 def year=(newyear) @time = self.stamp(:datet => false, :year => newyear) end #Returns the month as an integer. def month @mode = :months return @time.month end #Returns the day in month as an integer. def date @mode = :days return @time.day end #Returns the weekday of the week as an integer. Monday being the first and sunday being the last. def wday_mon wday = @time.wday return 0 if wday == 6 return wday - 1 end #Changes the date to a given date. #===Examples # datet.time #=> 2005-05-03 17:46:11 +0200 # datet.date = 8 # datet.time #=> 2005-05-08 17:46:11 +0200 def date=(newday) newday = newday.to_i if newday <= 0 self.add_days(newday - 1) else @time = self.stamp(:datet => false, :day => newday) end return self end #Changes the hour to a given new hour. #===Examples # datet.time #=> 2012-05-09 19:36:08 +0200 # datet.hour = 5 # datet.time #=> 2012-05-09 05:36:08 +0200 def hour=(newhour) newhour = newhour.to_i day = @time.day loop do break if newhour >= 0 day += -1 newhour += 24 end loop do break if newhour < 24 day += 1 newhour += -24 end @time = self.stamp(:datet => false, :hour => newhour) self.date = day if day != @time.day return self end #Changes the minute to a given new minute. #===Examples # datet.time #=> 2012-05-09 05:36:08 +0200 # datet.min = 35 # datet.time #=> 2012-05-09 05:35:08 +0200 def min=(newmin) @time = self.stamp(:datet => false, :min => newmin.to_i) end #Changes the second to a given new second. #===Examples # datet.time #=> 2012-05-09 05:35:08 +0200 # datet.sec = 20 # datet.time #=> 2012-05-09 05:35:20 +0200 def sec=(newsec) @time = self.stamp(:datet => false, :sec => newsec.to_i) end alias :day :date #Changes the month to a given new month. #===Examples # datet.time #=> 2012-05-09 05:35:20 +0200 # datet.month = 7 # datet.time #=> 2012-07-09 05:35:20 +0200 def month=(newmonth) @time = self.stamp(:datet => false, :month => newmonth) end #Turns the given argument into a new Time-object. #===Examples # time = Knj::Datet.arg_to_time(datet) #=>