lib/date.rb


DEFINITIONS

This source file includes following functions.


   1  # date.rb: Written by Tadayoshi Funaba 1998-2002
   2  # $Id: date.rb,v 2.8 2002-06-08 00:39:51+09 tadf Exp $
   3  
   4  require 'rational'
   5  require 'date/format'
   6  
   7  class Date
   8  
   9    include Comparable
  10  
  11    MONTHNAMES = [nil] + %w(January February March April May June July
  12                            August September October November December)
  13  
  14    DAYNAMES = %w(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
  15  
  16    ABBR_MONTHNAMES = [nil] + %w(Jan Feb Mar Apr May Jun
  17                                 Jul Aug Sep Oct Nov Dec)
  18  
  19    ABBR_DAYNAMES = %w(Sun Mon Tue Wed Thu Fri Sat)
  20  
  21    ITALY     = 2299161 # 1582-10-15
  22    ENGLAND   = 2361222 # 1752-09-14
  23    JULIAN    = false
  24    GREGORIAN = true
  25  
  26    def self.os? (jd, sg)
  27      case sg
  28      when Numeric; jd < sg
  29      else;         not sg
  30      end
  31    end
  32  
  33    def self.ns? (jd, sg) not os?(jd, sg) end
  34  
  35    def self.civil_to_jd(y, m, d, sg=GREGORIAN)
  36      if m <= 2
  37        y -= 1
  38        m += 12
  39      end
  40      a = (y / 100.0).floor
  41      b = 2 - a + (a / 4.0).floor
  42      jd = (365.25 * (y + 4716)).floor +
  43        (30.6001 * (m + 1)).floor +
  44        d + b - 1524
  45      if os?(jd, sg)
  46        jd -= b
  47      end
  48      jd
  49    end
  50  
  51    def self.jd_to_civil(jd, sg=GREGORIAN)
  52      if os?(jd, sg)
  53        a = jd
  54      else
  55        x = ((jd - 1867216.25) / 36524.25).floor
  56        a = jd + 1 + x - (x / 4.0).floor
  57      end
  58      b = a + 1524
  59      c = ((b - 122.1) / 365.25).floor
  60      d = (365.25 * c).floor
  61      e = ((b - d) / 30.6001).floor
  62      dom = b - d - (30.6001 * e).floor
  63      if e <= 13
  64        m = e - 1
  65        y = c - 4716
  66      else
  67        m = e - 13
  68        y = c - 4715
  69      end
  70      return y, m, dom
  71    end
  72  
  73    def self.ordinal_to_jd(y, d, sg=GREGORIAN)
  74      civil_to_jd(y, 1, d, sg)
  75    end
  76  
  77    def self.jd_to_ordinal(jd, sg=GREGORIAN)
  78      y = jd_to_civil(jd, sg)[0]
  79      doy = jd - civil_to_jd(y - 1, 12, 31, ns?(jd, sg))
  80      return y, doy
  81    end
  82  
  83    def self.jd_to_commercial(jd, sg=GREGORIAN)
  84      ns = ns?(jd, sg)
  85      a = jd_to_civil(jd - 3, ns)[0]
  86      y = if jd >= commercial_to_jd(a + 1, 1, 1, ns) then a + 1 else a end
  87      w = 1 + (jd - commercial_to_jd(y, 1, 1, ns)) / 7
  88      d = (jd + 1) % 7
  89      if d.zero? then d = 7 end
  90      return y, w, d
  91    end
  92  
  93    def self.commercial_to_jd(y, w, d, ns=GREGORIAN)
  94      jd = civil_to_jd(y, 1, 4, ns)
  95      (jd - (((jd - 1) + 1) % 7)) +
  96        7 * (w - 1) +
  97        (d - 1)
  98    end
  99  
 100    %w(self.clfloor clfloor).each do |name|
 101      module_eval <<-"end;"
 102        def #{name}(x, y=1)
 103          q, r = x.divmod(y)
 104          q = q.to_i
 105          return q, r
 106        end
 107      end;
 108    end
 109  
 110    private_class_method :clfloor
 111    private              :clfloor
 112  
 113    def self.ajd_to_jd(ajd, of=0) clfloor(ajd + of + 1.to_r/2) end
 114    def self.jd_to_ajd(jd, fr, of=0) jd + fr - of - 1.to_r/2 end
 115  
 116    def self.day_fraction_to_time(fr)
 117      h,   fr = clfloor(fr, 1.to_r/24)
 118      min, fr = clfloor(fr, 1.to_r/1440)
 119      s,   fr = clfloor(fr, 1.to_r/86400)
 120      return h, min, s, fr
 121    end
 122  
 123    def self.time_to_day_fraction(h, min, s)
 124      h.to_r/24 + min.to_r/1440 + s.to_r/86400
 125    end
 126  
 127    def self.amjd_to_ajd(amjd) amjd + 4800001.to_r/2 end
 128    def self.ajd_to_amjd(ajd) ajd - 4800001.to_r/2 end
 129    def self.mjd_to_jd(mjd) mjd + 2400001 end
 130    def self.jd_to_mjd(jd) jd - 2400001 end
 131    def self.ld_to_jd(ld) ld + 2299160 end
 132    def self.jd_to_ld(jd) jd - 2299160 end
 133  
 134    def self.jd_to_wday(jd) (jd + 1) % 7 end
 135  
 136    def self.julian_leap? (y) y % 4 == 0 end
 137    def self.gregorian_leap? (y) y % 4 == 0 and y % 100 != 0 or y % 400 == 0 end
 138  
 139    class << self; alias_method :leap?, :gregorian_leap? end
 140    class << self; alias_method :new0, :new end
 141  
 142    def self.valid_jd? (jd, sg=ITALY) jd end
 143  
 144    def self.jd(jd=0, sg=ITALY)
 145      jd = valid_jd?(jd, sg)
 146      new0(jd_to_ajd(jd, 0, 0), 0, sg)
 147    end
 148  
 149    def self.valid_ordinal? (y, d, sg=ITALY)
 150      if d < 0
 151        ny, = clfloor(y + 1, 1)
 152        jd = ordinal_to_jd(ny, d + 1, sg)
 153        ns = ns?(jd, sg)
 154        return unless [y] == jd_to_ordinal(jd, sg)[0..0]
 155        return unless [ny, 1] == jd_to_ordinal(jd - d, ns)
 156      else
 157        jd = ordinal_to_jd(y, d, sg)
 158        return unless [y, d] == jd_to_ordinal(jd, sg)
 159      end
 160      jd
 161    end
 162  
 163    def self.ordinal(y=-4712, d=1, sg=ITALY)
 164      unless jd = valid_ordinal?(y, d, sg)
 165        raise ArgumentError, 'invalid date'
 166      end
 167      new0(jd_to_ajd(jd, 0, 0), 0, sg)
 168    end
 169  
 170    def self.valid_civil? (y, m, d, sg=ITALY)
 171      if m < 0
 172        m += 13
 173      end
 174      if d < 0
 175        ny, nm = clfloor(y * 12 + m, 12)
 176        nm,    = clfloor(nm + 1, 1)
 177        jd = civil_to_jd(ny, nm, d + 1, sg)
 178        ns = ns?(jd, sg)
 179        return unless [y, m] == jd_to_civil(jd, sg)[0..1]
 180        return unless [ny, nm, 1] == jd_to_civil(jd - d, ns)
 181      else
 182        jd = civil_to_jd(y, m, d, sg)
 183        return unless [y, m, d] == jd_to_civil(jd, sg)
 184      end
 185      jd
 186    end
 187  
 188    class << self; alias_method :valid_date?, :valid_civil? end
 189  
 190    def self.civil(y=-4712, m=1, d=1, sg=ITALY)
 191      unless jd = valid_civil?(y, m, d, sg)
 192        raise ArgumentError, 'invalid date'
 193      end
 194      new0(jd_to_ajd(jd, 0, 0), 0, sg)
 195    end
 196  
 197    class << self; alias_method :new, :civil end
 198  
 199    def self.valid_commercial? (y, w, d, sg=ITALY)
 200      if d < 0
 201        d += 8
 202      end
 203      if w < 0
 204        w = jd_to_commercial(commercial_to_jd(y + 1, 1, 1) + w * 7)[1]
 205      end
 206      jd = commercial_to_jd(y, w, d)
 207      return unless ns?(jd, sg)
 208      return unless [y, w, d] == jd_to_commercial(jd)
 209      jd
 210    end
 211  
 212    def self.commercial(y=1582, w=41, d=5, sg=ITALY)
 213      unless jd = valid_commercial?(y, w, d, sg)
 214        raise ArgumentError, 'invalid date'
 215      end
 216      new0(jd_to_ajd(jd, 0, 0), 0, sg)
 217    end
 218  
 219    def self.new_with_hash(elem, sg)
 220      elem ||= {}
 221      y, m, d = elem.select(:year, :mon, :mday)
 222      if [y, m, d].include? nil
 223        raise ArgumentError, 'invalid date'
 224      else
 225        civil(y, m, d, sg)
 226      end
 227    end
 228  
 229    private_class_method :new_with_hash
 230  
 231    def self.strptime(str='-4712-01-01', fmt='%F', sg=ITALY)
 232      elem = _strptime(str, fmt)
 233      new_with_hash(elem, sg)
 234    end
 235  
 236    def self.parse(str='-4712-01-01', comp=false, sg=ITALY)
 237      elem = _parse(str, comp)
 238      new_with_hash(elem, sg)
 239    end
 240  
 241    def self.today(sg=ITALY)
 242      jd = civil_to_jd(*(Time.now.to_a[3..5].reverse << sg))
 243      new0(jd_to_ajd(jd, 0, 0), 0, sg)
 244    end
 245  
 246    class << self
 247  
 248      def once(*ids)
 249        for id in ids
 250          module_eval <<-"end;"
 251            alias_method :__#{id.to_i}__, :#{id.to_s}
 252            private :__#{id.to_i}__
 253            def #{id.to_s}(*args, &block)
 254              (@__#{id.to_i}__ ||= [__#{id.to_i}__(*args, &block)])[0]
 255            end
 256          end;
 257        end
 258      end
 259  
 260      private :once
 261  
 262    end
 263  
 264    def initialize(ajd=0, of=0, sg=ITALY) @ajd, @of, @sg = ajd, of, sg end
 265  
 266    def ajd() @ajd end
 267    def amjd() type.ajd_to_amjd(@ajd) end
 268  
 269    once :amjd
 270  
 271    def jd() type.ajd_to_jd(@ajd, @of)[0] end
 272    def day_fraction() type.ajd_to_jd(@ajd, @of)[1] end
 273    def mjd() type.jd_to_mjd(jd) end
 274    def ld() type.jd_to_ld(jd) end
 275  
 276    once :jd, :day_fraction, :mjd, :ld
 277  
 278    def civil() type.jd_to_civil(jd, @sg) end
 279    def ordinal() type.jd_to_ordinal(jd, @sg) end
 280    def commercial() type.jd_to_commercial(jd, @sg) end
 281  
 282    once :civil, :ordinal, :commercial
 283    private :civil, :ordinal, :commercial
 284  
 285    def year() civil[0] end
 286    def yday() ordinal[1] end
 287    def mon() civil[1] end
 288    def mday() civil[2] end
 289  
 290    alias_method :month, :mon
 291    alias_method :day, :mday
 292  
 293    def time() type.day_fraction_to_time(day_fraction) end
 294  
 295    once :time
 296    private :time
 297  
 298    def hour() time[0] end
 299    def min() time[1] end
 300    def sec() time[2] end
 301    def sec_fraction() time[3] end
 302  
 303    private :hour, :min, :sec, :sec_fraction
 304  
 305    def zone
 306      ['Z',
 307        format('%+.2d%02d',
 308               (@of     / (1.to_r/24)).to_i,
 309               (@of.abs % (1.to_r/24) / (1.to_r/1440)).to_i)
 310      ][@of<=>0]
 311    end
 312  
 313    private :zone
 314  
 315    def cwyear() commercial[0] end
 316    def cweek() commercial[1] end
 317    def cwday() commercial[2] end
 318  
 319    def wday() type.jd_to_wday(jd) end
 320  
 321    once :wday
 322  
 323    def os? () type.os?(jd, @sg) end
 324    def ns? () type.ns?(jd, @sg) end
 325  
 326    once :os?, :ns?
 327  
 328    def leap?
 329      type.jd_to_civil(type.civil_to_jd(year, 3, 1, ns?) - 1,
 330                       ns?)[-1] == 29
 331    end
 332  
 333    once :leap?
 334  
 335    def start() @sg end
 336    def new_start(sg=type::ITALY) type.new0(@ajd, @of, sg) end
 337  
 338    def italy() new_start(type::ITALY) end
 339    def england() new_start(type::ENGLAND) end
 340    def julian() new_start(type::JULIAN) end
 341    def gregorian() new_start(type::GREGORIAN) end
 342  
 343    def offset() @of end
 344    def new_offset(of=0) type.new0(@ajd, of, @sg) end
 345  
 346    private :offset, :new_offset
 347  
 348    def + (n)
 349      case n
 350      when Numeric; return type.new0(@ajd + n, @of, @sg)
 351      end
 352      raise TypeError, 'expected numeric'
 353    end
 354  
 355    def - (x)
 356      case x
 357      when Numeric; return type.new0(@ajd - x, @of, @sg)
 358      when Date;    return @ajd - x.ajd
 359      end
 360      raise TypeError, 'expected numeric or date'
 361    end
 362  
 363    def <=> (other)
 364      case other
 365      when Numeric; return @ajd <=> other
 366      when Date;    return @ajd <=> other.ajd
 367      end
 368      raise TypeError, 'expected numeric or date'
 369    end
 370  
 371    def === (other)
 372      case other
 373      when Numeric; return jd == other
 374      when Date;    return jd == other.jd
 375      end
 376      raise TypeError, 'expected numeric or date'
 377    end
 378  
 379    def >> (n)
 380      y, m = clfloor(year * 12 + (mon - 1) + n, 12)
 381      m,   = clfloor(m + 1, 1)
 382      d = mday
 383      d -= 1 until jd2 = type.valid_civil?(y, m, d, ns?)
 384      self + (jd2 - jd)
 385    end
 386  
 387    def << (n) self >> -n end
 388  
 389    def step(limit, step)
 390      da = self
 391      op = [:-,:<=,:>=][step<=>0]
 392      while da.__send__(op, limit)
 393        yield da
 394        da += step
 395      end
 396      self
 397    end
 398  
 399    def upto(max, &block) step(max, +1, &block) end
 400    def downto(min, &block) step(min, -1, &block) end
 401  
 402    def succ() self + 1 end
 403  
 404    alias_method :next, :succ
 405  
 406    def eql? (other) Date === other and self == other end
 407    def hash() @ajd.hash end
 408  
 409    def inspect() format('#<%s: %s,%s,%s>', type, @ajd, @of, @sg) end
 410    def to_s() strftime end
 411  
 412    def _dump(limit) Marshal.dump([@ajd, @of, @sg], -1) end
 413  
 414  # def self._load(str) new0(*Marshal.load(str)) end
 415  
 416    def self._load(str)
 417      a = Marshal.load(str)
 418      if a.size == 2
 419        ajd,     sg = a
 420             of = 0
 421        ajd -= 1.to_r/2
 422      else
 423        ajd, of, sg = a
 424      end
 425      new0(ajd, of, sg)
 426    end
 427  
 428  end
 429  
 430  class DateTime < Date
 431  
 432    def self.valid_time? (h, min, s)
 433      h   += 24 if h   < 0
 434      min += 60 if min < 0
 435      s   += 60 if s   < 0
 436      return unless (0..24) === h and
 437                    (0..59) === min and
 438                    (0..59) === s
 439      time_to_day_fraction(h, min, s)
 440    end
 441  
 442    def self.jd(jd=0, h=0, min=0, s=0, of=0, sg=ITALY)
 443      unless (jd = valid_jd?(jd, sg)) and
 444             (fr = valid_time?(h, min, s))
 445        raise ArgumentError, 'invalid date'
 446      end
 447      new0(jd_to_ajd(jd, fr, of), of, sg)
 448    end
 449  
 450    def self.ordinal(y=-4712, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
 451      unless (jd = valid_ordinal?(y, d, sg)) and
 452             (fr = valid_time?(h, min, s))
 453        raise ArgumentError, 'invalid date'
 454      end
 455      new0(jd_to_ajd(jd, fr, of), of, sg)
 456    end
 457  
 458    def self.civil(y=-4712, m=1, d=1, h=0, min=0, s=0, of=0, sg=ITALY)
 459      unless (jd = valid_civil?(y, m, d, sg)) and
 460             (fr = valid_time?(h, min, s))
 461        raise ArgumentError, 'invalid date'
 462      end
 463      new0(jd_to_ajd(jd, fr, of), of, sg)
 464    end
 465  
 466    class << self; alias_method :new, :civil end
 467  
 468    def self.commercial(y=1582, w=41, d=5, h=0, min=0, s=0, of=0, sg=ITALY)
 469      unless (jd = valid_commercial?(y, w, d, sg)) and
 470             (fr = valid_time?(h, min, s))
 471        raise ArgumentError, 'invalid date'
 472      end
 473      new0(jd_to_ajd(jd, fr, of), of, sg)
 474    end
 475  
 476    def self.new_with_hash(elem, sg)
 477      elem ||= {}
 478      y, m, d, h, min, s, of =
 479        elem.select(:year, :mon, :mday, :hour, :min, :sec, :offset)
 480      h   ||= 0
 481      min ||= 0
 482      s   ||= 0
 483      of  ||= 0
 484      if [y, m, d].include? nil
 485        raise ArgumentError, 'invalid date'
 486      else
 487        civil(y, m, d, h, min, s, of.to_r/86400, sg)
 488      end
 489    end
 490  
 491    private_class_method :new_with_hash
 492  
 493    def self.strptime(str='-4712-01-01T00:00:00Z', fmt='%FT%T%Z', sg=ITALY)
 494      elem = _strptime(str, fmt)
 495      new_with_hash(elem, sg)
 496    end
 497  
 498    def self.parse(str='-4712-01-01T00:00:00Z', comp=false, sg=ITALY)
 499      elem = _parse(str, comp)
 500      new_with_hash(elem, sg)
 501    end
 502  
 503    class << self; undef_method :today end
 504  
 505    def self.now(sg=ITALY)
 506      i = Time.now
 507      a = i.to_a[0..5].reverse
 508      jd = civil_to_jd(*(a[0,3] << sg))
 509      fr = time_to_day_fraction(*(a[3,3])) + i.usec.to_r/86400000000
 510      d = Time.gm(*i.to_a).to_i - i.to_i
 511      d += d / d.abs if d.nonzero?
 512      of = (d / 60).to_r/1440
 513      new0(jd_to_ajd(jd, fr, of), of, sg)
 514    end
 515  
 516    public :hour, :min, :sec, :sec_fraction, :zone, :offset, :new_offset
 517  
 518  end
 519  
 520  class Date
 521  
 522    [ %w(exist1?  valid_jd?),
 523      %w(exist2?  valid_ordinal?),
 524      %w(exist3?  valid_date?),
 525      %w(exist?   valid_date?),
 526      %w(existw?  valid_commercial?),
 527      %w(new1     jd),
 528      %w(new2     ordinal),
 529      %w(new3     new),
 530      %w(neww     commercial)
 531    ].each do |old, new|
 532      module_eval <<-"end;"
 533        def self.#{old}(*args, &block)
 534          if $VERBOSE
 535            $stderr.puts("\#{caller.shift.sub(/:in .*/, '')}: " \
 536                         "warning: \#{self}::#{old} is deprecated; " \
 537                         "use \#{self}::#{new}")
 538          end
 539          #{new}(*args, &block)
 540        end
 541      end;
 542    end
 543  
 544    [ %w(sg       start),
 545      %w(newsg    new_start),
 546      %w(of       offset),
 547      %w(newof    new_offset)
 548    ].each do |old, new|
 549      module_eval <<-"end;"
 550        def #{old}(*args, &block)
 551          if $VERBOSE
 552            $stderr.puts("\#{caller.shift.sub(/:in .*/, '')}: " \
 553                         "warning: \#{type}\##{old} is deprecated; " \
 554                         "use \#{type}\##{new}")
 555          end
 556          #{new}(*args, &block)
 557        end
 558      end;
 559    end
 560  
 561    private :of, :newof
 562  
 563  end
 564  
 565  class DateTime < Date
 566  
 567    public :of, :newof
 568  
 569  end