lib/parsi-datetime.rb in parsi-date-0.1 vs lib/parsi-datetime.rb in parsi-date-0.2
- old
+ new
@@ -1,101 +1,202 @@
+# encoding: utf-8
+#
+# parsi-date.rb
+#
+# Author: Hassan Zamani 2012
+
module Parsi
+ # Class representing a date and time.
+ #
+ # See the documentation to the file parsi-date.rb for an overview.
+ #
+ # DateTime objects are immutable once created.
class DateTime < Date
- include Comparable
- attr_reader :hour, :minute, :second, :offset
+ shared_methods = Module.new do
+ def zone_to_offset zone
+ m = zone.match /(?<sign>\+|-)?(?<hour>\d{1,2}):?(?<minute>\d{,2})/
+ return 0 if m.nil?
+ offset = Rational(m[:hour].to_i, 24) + Rational(m[:minute].to_i, 1440)
+ m[:sign] == '-' ? -offset : offset
+ end
+ private :zone_to_offset
+
+ # Convert a fractional day +fraction+ to [hours, minutes, seconds, fraction_of_a_second]
+ def day_fraction_to_time fraction # :nodoc:
+ seconds, fraction = fraction.divmod SECONDS_IN_DAY
+ hours, seconds = seconds.divmod 3600
+ minutes, seconds = seconds.divmod 60
+ [hours, minutes, seconds, fraction * 86400]
+ end
+
+ # Do +hour+, +minute+, and +second+ constitute a valid time?
+ #
+ # If they do, returns their value as a fraction of a day. If not, returns nil.
+ #
+ # The 24-hour clock is used. Negative values of +hour+, +minute+, and +second+ are treating
+ # as counting backwards from the end of the next larger unit (e.g. a +minute+ of -2 is
+ # treated as 58). No wraparound is performed.
+ def _valid_time? hour, minute, second
+ hour += 24 if hour < 0
+ minute += 60 if minute < 0
+ seconds += 60 if second < 0
+ return unless ((0...24) === hour && (0...60) === minute && (0...60) === second) ||
+ (24 == hour && 0 == minute && 0 == second)
+ time_to_day_fraction hour, minute, second
+ end
+ end
+
+ extend shared_methods
+ include shared_methods
+
class << self
- def valid_time? hour=0, minute=0, second=0, offset=0
- if 0 <= hour && hour <= 23 &&
- 0 <= minute && minute <= 59 &&
- 0 <= second && second <= 59
- #TODO: Add offset validation
- true
- else
- false
- end
+
+ def valid_time? hour, min, sec
+ !!_valid_time?(hour, min, sec)
end
+ private :valid_time?
- def jd jd=0
- date = Date.jd jd.to_i
- h = (jd - jd.to_i) * 24
- m = (h - h.to_i) * 60
- s = (m - m.to_i) * 60
- DateTime.new date.year, date.month, date.day, h.to_i, m.to_i, s.to_i
+ # Create a new DateTime object corresponding to the specified Julian Day Number +jd+
+ # and +hour+, +minute+, +second+.
+ #
+ # The 24-hour clock is used. If an invalid time portion is specified, an ArgumentError
+ # is raised.
+ def jd jd=0, hour=0, minute=0, second=0, zone="00:00"
+ fraction = _valid_time? hour, minute, second
+ raise ArgumentError, 'invalid time' if fraction.nil?
+
+ offset = zone_to_offset zone
+
+ new! jd_to_ajd(jd, fraction, offset), offset
end
+ # Create a new DateTime object corresponding to the specified Astronomical Julian Day
+ # Number +ajd+ in given +offset+.
+ def ajd ajd=0, zone="00:00"
+ new! ajd, zone_to_offset(zone)
+ end
+
+ # Create a new DateTime object corresponding to the specified Ordinal Date and +hour+,
+ # +minute+, +second+.
+ #
+ # The 24-hour clock is used. If an invalid time portion is specified, an ArgumentError
+ # is raised.
+ def ordinal year=0, yday=1, hour=0, minute=0, second=0, zone="00:00"
+ jd = _valid_ordinal? year, yday
+ fraction = _valid_time?(hour, minute, second)
+ raise ArgumentError, 'invalid date' if jd.nil? or fraction.nil?
+
+ offset = zone_to_offset zone
+
+ new! jd_to_ajd(jd, fr, offset), offset
+ end
+
+ # Create a new DateTime object corresponding to the specified Civil Date and +hour+,
+ # +minute+, +second+.
+ #
+ # The 24-hour clock is used. If an invalid time portion is specified, an ArgumentError is
+ # raised.
+ #
+ # +offset+ is the offset from UTC as a fraction of a day (defaults to 0).
+ def civil year=0, month=1, day=1, hour=0, minute=0, second=0, zone="00:00"
+ jd = _valid_civil? year, month, day
+ fraction = _valid_time? hour, minute, second
+ raise ArgumentError, 'invalid date' if jd.nil? or fraction.nil?
+
+ offset = zone_to_offset zone
+
+ new! jd_to_ajd(jd, fraction, offset), offset
+ end
+ alias_method :new, :civil
+
+ # Create a new DateTime object representing the current time.
def now
- now = ::DateTime.now
- today = now.to_date.to_parsi
- DateTime.new today.year, today.month, today.day, now.hour, now.minute, now.second, now.offset
+ ::DateTime.now.to_parsi
end
+
+ private :today
end
- def initialize year=0, month=1, day=1, hour=0, minute=0, second=0, offset=0
- raise ArgumentError.new 'invalid time' unless
- DateTime.valid_time? hour, minute, second
-
- super year, month, day
- @hour, @minute, @second, @offset = hour, minute, second, offset
+ # Get the time of this date as [hours, minutes, seconds,
+ # fraction_of_a_second]
+ def time # :nodoc:
+ @time ||= day_fraction_to_time day_fraction
end
+ private :time
- def zone sep=':'
- f = offset * 24.0
- "%s%02d%s%02d" % [(f >= 0 ? '+' : '-'), f.floor, sep, (f % 1) * 60]
+ # Get the hour of this date.
+ def hour() time[0] end
+
+ # Get the minute of this date.
+ def min() time[1] end
+
+ # Get the second of this date.
+ def sec() time[2] end
+
+ # Get the fraction-of-a-second of this date.
+ def sec_fraction() time[3] end
+
+ alias_method :minute, :min
+ alias_method :second, :sec
+ alias_method :second_fraction, :sec_fraction
+
+ def to_s
+ format('%.4d-%02d-%02dT%02d:%02d:%02d%s', year, mon, mday, hour, min, sec, zone)
end
- def to_s sep='/'
- "%sT%02d:%02d:%02d%s" % [super(sep), hour, minute, second, zone]
+ public :offset
+
+ def new_offset zone
+ offset = zone_to_offset zone
+ self.class.new! ajd, offset
end
- def inspect
- "#<#{self.class}: #{to_s('-')}>"
+ def zone
+ o = offset * 24
+ format("%s%02d:%02d", (o >= 0 ? '+' : '-'), o.to_i, (o - o.to_i) * 60)
end
def strftime format='%Y/%m/%d %H:%M:%S'
gregorian.strftime super format
end
- def to_date
- Date.new year, month, day
- end
-
- def to_gregorian
+ def gregorian
@gregorian ||= begin
- ::DateTime.new super.year, super.month, super.day, hour, minute, second, zone
+ ::DateTime.jd jd, hour, minute, second, zone
end
end
- alias :gregorian :to_gregorian
+ alias :to_gregorian :gregorian
- def + days
- date = super
- DateTime.new date.year, date.month, date.day, hour, minute, second, offset
+ def to_time
+ gregorian.to_time
end
- def >> monthes
- date = super
- DateTime.new date.year, date.month, date.day, hour, minute, second, offset
+ def to_date
+ Date.new! jd_to_ajd(jd, 0, 0), 0
end
- def <=> other
- if other.is_a? Date
- to_gregorian <=> other.to_gregorian
- elsif other.is_a? ::Date
- to_gregorian <=> other
- else
- raise ArgumentError.new "comparison of #{self.class} with #{other.class} failed"
- end
+ def to_datetime
+ self
end
end
end
class DateTime
+ class << self
+ # Creates a DateTime object corresponding to the specified Jalali Date (+year+, +month+ and
+ # +day+) and time (+hour+, +minute+ and +second+) in given +zone+.
+ def parsi year=0, month=1, day=1, hour=0, minute=0, second=0, zone="00:00"
+ Parsi::DateTime.civil year, month, day, hour, minute, second, zone
+ end
+ alias :jalali :parsi
+ end
+
+ # Returns a Parsi::DateTime object representing same date in Jalali calendar
def to_parsi
- date = super
- Parsi::DateTime.new date.year, date.month, date.day, hour, minute, second, offset
+ Parsi::DateTime.new! ajd, offset
end
- alias :to_persian :to_parsi
- alias :to_jalali :to_parsi
- alias :parsi :to_parsi
alias :jalali :to_parsi
+ alias :to_jalali :to_parsi
+ alias :to_persian :to_parsi
end