require 'date' require 'facets/time/stamp' # = Date # # This new version of Date extension has been largely improved by # porting some of the methods used by ActiveSupport. The old version # already had much in common with the Active Support library, so it # was decided to take it a step further in that direction for the # sake of interoparability. # # Hopefully most of these methods will find there way into Ruby's # own standard library eventually. # # The biggest difference with ActiveSupport is the lack of many # of the "English-esque" methods, and that we use #stamp with # Date::FORMAT, instead of #to_formmated_s with Date::DATE_FORMATS. # We do not override the standard #to_s method like ActiveSupport does. class Date FORMAT = { :short => "%e %b", :long => "%B %e, %Y", :db => "%Y-%m-%d", :number => "%Y%m%d", :rfc822 => "%e %b %Y", :default => "%Y-%m-%d", nil => "%Y-%m-%d" } # Returns a new Date representing the date 1 day ago (i.e. yesterday's date). def self.yesterday ::Date.today.yesterday end # Returns a new Date representing the date 1 day after today (i.e. tomorrow's date). def self.tomorrow ::Date.today.tomorrow end # Returns Time.zone.today when config.time_zone is set, otherwise just returns Date.today. def self.current ::Time.zone_default ? ::Time.zone.today : ::Date.today end # A method to keep Time, Date and DateTime instances interchangeable # on conversions. In this case, it simply returns +self+. def to_date self end unless method_defined?(:to_date) # 1.9+ ? # Converts a Date instance to a DateTime, where the time is set to the beginning of the day # and UTC offset is set to 0. # # ==== Example: # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 # # date.to_datetime # => Sat, 10 Nov 2007 00:00:00 0000 def to_datetime ::DateTime.civil(year, month, day, 0, 0, 0, 0) end unless method_defined?(:to_datetime) # 1.9+ ? # Converts a Date instance to a Time, where the time is set to the beginning of the day. # The timezone can be either :local or :utc (default :local). # # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 # # date.to_time # => Sat Nov 10 00:00:00 0800 2007 # date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007 # # date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007 # def to_time(form=:local) ::Time.send(form, year, month, day) #::Time.send("#{form}_time", year, month, day) end # def xmlschema to_time.xmlschema end # Returns the number of days in the date's month. # # Date.new(2004,2).days_in_month #=> 28 # # CREDIT: Ken Kunz. def days_in_month Date.civil(year, month, -1).day end def days_of_month (1..days_in_month).to_a end # Get the month name for this date object # # CREDIT: Benjamin Oakes def month_name MONTHNAMES[self.month] end # Convert to a formatted string. See DATE_FORMATS for predefined formats. # # This method is aliased to to_s. # # ==== Examples: # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 # # date.stamp(:db) # => "2007-11-10" # date.stamp(:short) # => "10 Nov" # date.stamp(:long) # => "November 10, 2007" # date.stamp(:rfc822) # => "10 Nov 2007" # # == Adding your own formats to stamp # You can add your own formats to the Date::FORMAT hash. # Use the format name as the hash key and a strftime string # as the value. Eg. # # Date::FORMAT[:month_and_year] = "%B %Y" # def stamp(format=:default) if formatter = FORMAT[format] strftime(formatter) else to_s end end # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005" #def inspect # strftime("%a, %d %b %Y") #end # Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with # any of these keys: :years, :months, :weeks, :days. def advance(options) d = self d = d >> options.delete(:years) * 12 if options[:years] d = d >> options.delete(:months) if options[:months] d = d + options.delete(:weeks) * 7 if options[:weeks] d = d + options.delete(:days) if options[:days] d end # Returns a new Date where one or more of the elements have been changed according to the +options+ parameter. # # Examples: # # Date.new(2007, 5, 12).change(:day => 1) # => Date.new(2007, 5, 1) # Date.new(2007, 5, 12).change(:year => 2005, :month => 1) # => Date.new(2005, 1, 12) def change(options) ::Date.new( options[:year] || self.year, options[:month] || self.month, options[:day] || self.day ) end # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) # and then subtracts the specified number of seconds def ago(seconds) to_time.since(-seconds) end # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) # and then adds the specified number of seconds def since(seconds) to_time.since(seconds) end alias :in :since # Converts Date to a Time (or DateTime if necessary) with the time portion set to the beginning of the day (0:00) def beginning_of_day to_time end alias :midnight :beginning_of_day # Convenience method which returns a new Date/DateTime representing the time 1 day ago def yesterday self - 1 end # Convenience method which returns a new Date/DateTime representing the time 1 day since the instance time def tomorrow self + 1 end end class DateTime # def self.local_offset ::Time.local(2007).utc_offset.to_r / 86400 end def future? self > ::DateTime.current end def past? self < ::DateTime.current end # Converts self to a Ruby Date object; time portion is discarded def to_date ::Date.new(year, month, day) end # Attempts to convert self to a Ruby Time object; returns self if # out of range of Ruby Time class. If self has an offset other than 0, # self will just be returned unaltered, since there's no clean way # to map it to a Time. def to_time self.offset == 0 ? ::Time.utc_time(year, month, day, hour, min, sec) : self end # To be able to keep Times, Dates and DateTimes interchangeable on conversions def to_datetime self end # Convert to a formatted string. See Time::FORMAT for predefined formats. # # This method is aliased to to_s. # # === Examples: # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000 # # datetime.stamp(:db) # => "2007-12-04 00:00:00" # datetime.stamp(:db) # => "2007-12-04 00:00:00" # datetime.stamp(:number) # => "20071204000000" # datetime.stamp(:short) # => "04 Dec 00:00" # datetime.stamp(:long) # => "December 04, 2007 00:00" # datetime.stamp(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000" # # == Adding your own datetime formats to stamp # DateTime formats are shared with Time. You can add your own to the # Time::FORMAT hash. Use the format name as the hash key and # a strftime string as the value. Eg. # # Time::FORMAT[:month_and_year] = "%B %Y" # def stamp(format=:default) if formatter = ::Time::FORMAT[format] strftime(formatter) else to_s end end # Seconds since midnight: DateTime.now.seconds_since_midnight def seconds_since_midnight self.sec + (self.min * 60) + (self.hour * 3600) end # Returns a new DateTime where one or more of the elements have been # changed according to the +options+ parameter. The time options # (hour, minute, sec) reset cascadingly, so if only the hour is # passed, then minute and sec is set to 0. If the hour and # minute is passed, then sec is set to 0. def change(options) ::DateTime.civil( options[:year] || self.year, options[:month] || self.month, options[:day] || self.day, options[:hour] || self.hour, options[:min] || (options[:hour] ? 0 : self.min), options[:sec] || ((options[:hour] || options[:min]) ? 0 : self.sec), options[:offset] || self.offset, options[:start] || self.start ) end # Uses Date to provide precise Time calculations for years, months, and days. # The +options+ parameter takes a hash with any of these keys: :years, # :months, :weeks, :days, :hours, # :minutes, :seconds. def advance(options) d = to_date.advance(options) datetime_advanced_by_date = change(:year => d.year, :month => d.month, :day => d.day) seconds_to_advance = (options[:seconds] || 0) + (options[:minutes] || 0) * 60 + (options[:hours] || 0) * 3600 seconds_to_advance == 0 ? datetime_advanced_by_date : datetime_advanced_by_date.since(seconds_to_advance) end # Returns a new DateTime representing the time a number of seconds ago # Do not use this method in combination with x.months, use months_ago instead! def ago(seconds) self.since(-seconds) end # Returns a new DateTime representing the time a number of seconds since the instance time # Do not use this method in combination with x.months, use months_since instead! def since(seconds) self + Rational(seconds.round, 86400) end alias :in :since # Returns a new DateTime representing the start of the day (0:00) def beginning_of_day change(:hour => 0) end alias :midnight :beginning_of_day # Returns a new DateTime representing the end of the day (23:59:59) def end_of_day change(:hour => 23, :min => 59, :sec => 59) end # Adjusts DateTime to UTC by adding its offset value; offset is set to 0 # # Example: # # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600 # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000 # def utc new_offset(0) end alias_method :getutc, :utc # Returns true if offset == 0 def utc? offset == 0 end # Returns the offset value in seconds def utc_offset (offset * 86400).to_i end # Converts datetime to an appropriate format for use in XML def xmlschema strftime("%Y-%m-%dT%H:%M:%S%Z") end unless method_defined?(:xmlschema) # 1.9+ ? # Converts self to a floating-point number of seconds since the Unix epoch def to_f days_since_unix_epoch = self - ::DateTime.civil(1970) (days_since_unix_epoch * 86_400).to_f end end class Time # def self.local_time(*args) time_with_datetime_fallback(:local, *args) end # def self.utc_time(*args) time_with_datetime_fallback(:utc, *args) end # def self.time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0) ::Time.send(utc_or_local, year, month, day, hour, min, sec, usec) rescue offset = utc_or_local.to_sym == :local ? ::DateTime.local_offset : 0 ::DateTime.civil(year, month, day, hour, min, sec, offset) end public :to_date public :to_datetime # Converts a Time object to a Date, dropping hour, minute, and second precision. # # my_time = Time.now # => Mon Nov 12 22:59:51 -0500 2007 # my_time.to_date # => Mon, 12 Nov 2007 # # your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009 # your_time.to_date # => Tue, 13 Jan 2009 # def to_date ::Date.new(year, month, day) end # # Convert a Time to a Date. Time is a superset of Date. # # It is the year, month and day that are carried over. # # def to_date # require 'date' # just in case # jd = Date.__send__(:civil_to_jd, year, mon, mday, Date::ITALY) # Date.new!(Date.__send__(:jd_to_ajd, jd, 0, 0), 0, Date::ITALY) # end # Converts a Time instance to a Ruby DateTime instance, preserving UTC offset. # # my_time = Time.now # => Mon Nov 12 23:04:21 -0500 2007 # my_time.to_datetime # => Mon, 12 Nov 2007 23:04:21 -0500 # # your_time = Time.parse("1/13/2009 1:13:03 P.M.") # => Tue Jan 13 13:13:03 -0500 2009 # your_time.to_datetime # => Tue, 13 Jan 2009 13:13:03 -0500 # def to_datetime ::DateTime.civil(year, month, day, hour, min, sec, Rational(utc_offset, 86400)) end end class String # def to_time(form = :utc) ::Time.__send__("#{form}_time", *::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec).map{|arg| arg || 0 }) end # Convert string to DateTime. def to_datetime date = ::Date._parse(self, false).values_at(:year, :mon, :mday, :hour, :min, :sec).map { |arg| arg || 0 } ::DateTime.civil(*date) end # Parse data from string. def to_date #::Date::civil(*ParseDate.parsedate(self)[0..2]) ::Date.new(*::Date._parse(self, false).values_at(:year, :mon, :mday)) end end