Class: Parsi::Date

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/parsi-date.rb,
lib/version.rb

Overview

Class representing a date.

See the documentation to the file parsi-date.rb for an overview.

Internally, the date is represented as an Astronomical Julian Day Number, ajd. (There is also an offset field for a time zone offset, but this is only for the use of the DateTime subclass.)

A new Date object is created using one of the object creation class methods named after the corresponding date format, and the arguments appropriate to that date format; for instance, Date::civil() (aliased to Date::new()) with year, month, and day-of-month, or Date::ordinal() with year and day-of-year.

Date objects are immutable once created.

Once a Date has been created, date values can be retrieved for the different date formats supported using instance methods. For instance, #mon() gives the Civil month and #yday() gives the Ordinal day of the year. Date values can be retrieved in any format, regardless of what format was used to create the Date instance.

The Date class includes the Comparable module, allowing date objects to be compared and sorted, ranges of dates to be created, and so forth.

Direct Known Subclasses

DateTime

Constant Summary collapse

VERSION =
"0.2.5"
MONTHNAMES =

Full month names, in Farsi. Months count from 1 to 12; a month's numerical representation indexed into this array gives the name of that month (hence the first element is nil).

[nil] + %w(فروردین اردیبهشت خرداد تیر مرداد شهریور مهر آبان آذر دی بهمن اسفند)
EN_MONTHNAMES =

Full month names, in English. Months count from 1 to 12;

[nil] + %w(farvardin ordibehesht khordad tir mordad shahrivar mehr aban azar day bahman esfand)
DAYNAMES =

Full names of days of the week, in Farsi. Days of the week count from 0 to 6; a day's numerical representation indexed into this array gives the name of that day.

%w(یک‌شنبه دوشنبه سه‌شنبه چهارشنبه پنج‌شنبه جمعه شنبه)
EN_DAYNAMES =

Full names of days of the week, in English. Days of the week count from 0 to 6; a day's numerical representation indexed into this array gives the name of that day.

%w(yekshanbe doshanbe seshanbe chaharshanbe panjshanbe jomee shanbe)
ABBR_MONTHNAMES =

Abbreviated month names, in English.

We don't have Farsi abbreviated month names, as they are not useful

[nil] + %w(far ord kho tir mor sha meh abn azr day bah esf)
ABBR_DAYNAMES =

Abbreviated day names, in Farsi.

%w(۱ش ۲ش ۳ش ۴ش ۵ش ج ش)
ABBR_EN_DAYNAMES =

Abbreviated day names, in English.

%w(ye do se ch pj jo sh)
HALF_DAYS_IN_DAY =

:nodoc:

Rational(1, 2)
HOURS_IN_DAY =

:nodoc:

Rational(1, 24)
MINUTES_IN_DAY =

:nodoc:

Rational(1, 1440)
SECONDS_IN_DAY =

:nodoc:

Rational(1, 86400)
MILLISECONDS_IN_DAY =

:nodoc:

Rational(1, 86400*10**3)
NANOSECONDS_IN_DAY =

:nodoc:

Rational(1, 86400*10**9)
MILLISECONDS_IN_SECOND =

:nodoc:

Rational(1, 10**3)
NANOSECONDS_IN_SECOND =

:nodoc:

Rational(1, 10**9)
JALALI_EPOCH_IN_AJD =

:nodoc:

Rational(3896641, 2)
MJD_EPOCH_IN_AJD =

1858-11-17 # :nodoc:

Rational(4800001, 2)
UNIX_EPOCH_IN_AJD =

1970-01-01 # :nodoc:

Rational(4881175, 2)
JALALI_EPOCH_IN_CJD =

:nodoc:

1948321
MJD_EPOCH_IN_CJD =

:nodoc:

2400001
UNIX_EPOCH_IN_CJD =

:nodoc:

2440588
LD_EPOCH_IN_CJD =

:nodoc:

2299160
DAYS_IN_MONTH =

:nodoc:

[nil, 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29]

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ajd = 0, offset = 0) ⇒ Date

Create a new Date object.

ajd is the Astronomical Julian Day Number. offset is the offset from UTC as a fraction of a day.


392
393
394
# File 'lib/parsi-date.rb', line 392

def initialize ajd=0, offset=0
  @ajd, @offset = ajd, offset
end

Class Method Details

.civil(year = 1, month = 1, day = 1) ⇒ Object

Create a new Date object for the Civil Date specified by year, month, and day-of-month day.

Raises:

  • (ArgumentError)

350
351
352
353
# File 'lib/parsi-date.rb', line 350

def civil year=1, month=1, day=1
  raise ArgumentError, 'invalid date' unless jd = _valid_civil?(year, month, day)
  new! jd_to_ajd(jd, 0, 0), 0
end

.jd(jday = 0) ⇒ Object

Create a new Date object from a Julian Day Number.

jday is the Julian Day Number; if not specified, it defaults to 0.

examples:

Parsi::Date.jd 2456229     # => #<Parsi::Date: 1391-08-07>
Parsi::Date.jd 2456230     # => #<Parsi::Date: 1391-08-08>
Parsi::Date.jd             # => #<Parsi::Date: -5335-09-01>

330
331
332
333
# File 'lib/parsi-date.rb', line 330

def jd jday=0
  jd = _valid_jd? jday
  new! jd_to_ajd(jday, 0, 0), 0
end

.newObject Also known as: new!

Create a new Date object for the Civil Date specified by year, month, and day-of-month day.


354
355
356
357
# File 'lib/parsi-date.rb', line 354

def civil year=1, month=1, day=1
  raise ArgumentError, 'invalid date' unless jd = _valid_civil?(year, month, day)
  new! jd_to_ajd(jd, 0, 0), 0
end

.ordinal(year = 0, yday = 1) ⇒ Object

Create a new Date object from an Ordinal Date, specified by year and day-of-year yday. yday can be negative, in which it counts backwards from the end of the year.

examples:

Parsi::Date.ordinal 1390      # => #<Parsi::Date: 1390-01-01>
Parsi::Date.ordinal 1391, 120 # => #<Parsi::Date: 1391-04-27>
Parsi::Date.ordinal 1390, -1  # => #<Parsi::Date: 1389-12-29>

Raises:

  • (ArgumentError)

343
344
345
346
# File 'lib/parsi-date.rb', line 343

def ordinal year=0, yday=1
  raise ArgumentError, 'invalid date' unless jd = _valid_ordinal?(year, yday)
  new! jd_to_ajd(jd, 0, 0), 0
end

.parse(string, comp = true) ⇒ Object

Parses the given representation of date and time, and creates a date object.

If the optional second argument is true and the detected year is in the range “00” to “99”, considers the year a 2-digit form and makes it full.

For


362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/parsi-date.rb', line 362

def parse string, comp=true
  # TODO: Add more parse options, for example parse '۴ام فروردین ۱۳۹۱'
  m   = string.match /(?<year>\d+)(\/|-| )(?<month>\d+)(\/|-| )(?<day>\d+)/
  m ||= string.match /(?<year>\d+)(?<month>\d{2})(?<day>\d{2})/
  if m.nil?
    raise ArgumentError.new 'invalid date'
  else
    year, month, day = m[:year].to_i, m[:month].to_i, m[:day].to_i
    if comp && m[:year].length == 2
      centry = Date.today.year / 100
      year = (m[:year].prepend centry.to_s).to_i
    end
    if jd = _valid_civil?(year, month, day)
      new! jd_to_ajd(jd, 0, 0), 0
    else
      raise ArgumentError.new 'invalid date'
    end
  end
end

.todayObject

Create a new Date object representing today.


383
384
385
# File 'lib/parsi-date.rb', line 383

def today
  ::Date.today.to_parsi
end

.valid_civil?(year, month, day) ⇒ Boolean Also known as: valid_date?, valid?, exist?

Returns:

  • (Boolean)

314
315
316
# File 'lib/parsi-date.rb', line 314

def valid_civil? year, month, day
  !!_valid_civil?(year, month, day)
end

.valid_jd?(jd) ⇒ Boolean

Returns:

  • (Boolean)

306
307
308
# File 'lib/parsi-date.rb', line 306

def valid_jd? jd
  !!_valid_jd?(jd)
end

.valid_ordinal?(year, yday) ⇒ Boolean

Returns:

  • (Boolean)

310
311
312
# File 'lib/parsi-date.rb', line 310

def valid_ordinal? year, yday
  !!_valid_ordinal?(year, yday)
end

Instance Method Details

#+(n) ⇒ Object

Return a new Date object that is n days later than the current one.

n may be a negative value, in which case the new Date is earlier than the current one; however, #-() might be more intuitive.

If n is not a Numeric, a TypeError will be thrown. In particular, two Dates cannot be added to each other.

Raises:

  • (TypeError)

483
484
485
486
487
488
489
# File 'lib/parsi-date.rb', line 483

def + n
  case n
  when Numeric
    return self.class.new!(ajd + n, offset)
  end
  raise TypeError, 'expected numeric'
end

#-(x) ⇒ Object

If x is a Numeric value, create a new Date object that is x days earlier than the current one.

If x is a Date, return the number of days between the two dates; or, more precisely, how many days later the current date is than x.

If x is neither Numeric nor a Date, a TypeError is raised.

Raises:

  • (TypeError)

498
499
500
501
502
503
504
505
506
# File 'lib/parsi-date.rb', line 498

def - x
  case x
  when Numeric
    return self.class.new!(ajd - x, offset)
  when Date
    return ajd - x.ajd
  end
  raise TypeError, 'expected numeric or date'
end

#<<(n) ⇒ Object

Return a new Date object that is n months earlier than the current one.

If the day-of-the-month of the current Date is greater than the last day of the target month, the day-of-the-month of the returned Date will be the last day of the target month.


584
# File 'lib/parsi-date.rb', line 584

def << (n) self >> -n end

#<=>(other) ⇒ Object

Compare this date with another date.

other can also be a Numeric value, in which case it is interpreted as an Astronomical Julian Day Number.

Comparison is by Astronomical Julian Day Number, including fractional days. This means that both the time and the timezone offset are taken into account when comparing two DateTime instances. When comparing a DateTime instance with a Date instance, the time of the latter will be considered as falling on midnight UTC.


518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/parsi-date.rb', line 518

def <=> other
  case other
  when Numeric
    return ajd <=> other
  when Date
    return ajd <=> other.ajd
  when ::Date
    return ajd <=> other.ajd
  else
    begin
      left, right = other.coerce(self)
      return left <=> right
    rescue NoMethodError
    end
  end
  nil
end

#===(other) ⇒ Object

The relationship operator for Date.

Compares dates by Julian Day Number. When comparing two DateTime instances, or a DateTime with a Date, the instances will be regarded as equivalent if they fall on the same date in local time.


541
542
543
544
545
546
547
548
549
550
551
552
553
554
# File 'lib/parsi-date.rb', line 541

def === (other)
  case other
  when Numeric
    return jd == other
  when Date; return jd == other.jd
  else
    begin
      l, r = other.coerce(self)
      return l === r
    rescue NoMethodError
    end
  end
  false
end

#>>(n) ⇒ Object

Return a new Date object that is n months later than the current one.

If the day-of-the-month of the current Date is greater than the last day of the target month, the day-of-the-month of the returned Date will be the last day of the target month.


568
569
570
571
572
573
574
575
576
577
# File 'lib/parsi-date.rb', line 568

def >> n
  y, m = (year * 12 + (mon - 1) + n).divmod(12)
  m, = (m + 1) .divmod(1)
  d = mday
  until jd2 = _valid_civil?(y, m, d)
    d -= 1
    raise ArgumentError, 'invalid date' unless d > 0
  end
  self + (jd2 - jd)
end

#ajdObject

Get the date as an Astronomical Julian Day Number.


397
398
399
# File 'lib/parsi-date.rb', line 397

def ajd
  @ajd
end

#amjdObject

Get the date as an Astronomical Modified Julian Day Number.


407
408
409
# File 'lib/parsi-date.rb', line 407

def amjd
  @amjd ||= ajd_to_amjd ajd
end

#day_fractionObject

Get any fractional day part of the date.


417
418
419
# File 'lib/parsi-date.rb', line 417

def day_fraction
  @day_fraction ||= ajd_to_jd(ajd, offset).last
end

#downto(min, &block) ⇒ Object

Step backward one day at a time until we reach min (inclusive), yielding each date as we go.


695
696
697
# File 'lib/parsi-date.rb', line 695

def downto min, &block # :yield: date
  step min, -1, &block
end

#eql?(other) ⇒ Boolean

Is this Date equal to other?

other must both be a Date object, and represent the same date.

Returns:

  • (Boolean)

702
# File 'lib/parsi-date.rb', line 702

def eql? (other) self.class === other && self == other end

#hashObject

Calculate a hash value for this date.


705
# File 'lib/parsi-date.rb', line 705

def hash() ajd.hash end

#inspectObject

Return internal object state as a programmer-readable string.


708
709
710
# File 'lib/parsi-date.rb', line 708

def inspect
  format('#<%s: %s (%s,%s)>', self.class, to_s, ajd, offset)
end

#jdObject

Get the date as a Julian Day Number.


412
413
414
# File 'lib/parsi-date.rb', line 412

def jd
  @jd ||= ajd_to_jd(ajd, offset).first
end

#ldObject

Get the date as the number of days since the Day of Calendar Reform (in Italy and the Catholic countries).


428
429
430
# File 'lib/parsi-date.rb', line 428

def ld
  @ld ||= jd_to_ld jd
end

#marshal_dumpObject

Dump to Marshal format.


718
# File 'lib/parsi-date.rb', line 718

def marshal_dump() [@ajd, @offset] end

#marshal_load(a) ⇒ Object

Load from Marshal format.


721
# File 'lib/parsi-date.rb', line 721

def marshal_load(a) @ajd, @of, = a end

#mdayObject Also known as: day

Get the day-of-the-month of this date.


454
# File 'lib/parsi-date.rb', line 454

def mday() civil[2] end

#mjdObject

Get the date as a Modified Julian Day Number.


422
423
424
# File 'lib/parsi-date.rb', line 422

def mjd
  @mjd ||= jd_to_mjd jd
end

#monObject Also known as: month

Get the month of this date.

Farvardin is month 1.


450
# File 'lib/parsi-date.rb', line 450

def mon() civil[1] end

#nextObject Also known as: succ

Return a new Date one day after this one.


560
# File 'lib/parsi-date.rb', line 560

def next() next_day end

#next_day(n = 1) ⇒ Object


556
# File 'lib/parsi-date.rb', line 556

def next_day(n=1) self + n end

#next_month(n = 1) ⇒ Object


586
# File 'lib/parsi-date.rb', line 586

def next_month(n=1) self >> n end

#next_year(n = 1) ⇒ Object


589
# File 'lib/parsi-date.rb', line 589

def next_year(n=1) self >> n * 12 end

#prev_day(n = 1) ⇒ Object


557
# File 'lib/parsi-date.rb', line 557

def prev_day(n=1) self - n end

#prev_month(n = 1) ⇒ Object


587
# File 'lib/parsi-date.rb', line 587

def prev_month(n=1) self << n end

#prev_year(n = 1) ⇒ Object


590
# File 'lib/parsi-date.rb', line 590

def prev_year(n=1) self << n * 12 end

#step(limit, step = 1) ⇒ Object

Step the current date forward step days at a time (or backward, if step is negative) until we reach limit (inclusive), yielding the resultant date at each step.


675
676
677
678
679
680
681
682
683
684
685
# File 'lib/parsi-date.rb', line 675

def step limit, step=1
  return to_enum(:step, limit, step) unless block_given?

  date = self
  comp_op = %w(== <= >=)[step <=> 0]
  while date.send comp_op, limit
    yield date
    date += step
  end
  self
end

#strftime(format = '%Y/%m/%d') ⇒ Object

Formats time according to the directives in the given format string. The directives begins with a percent (%) character. Any text not listed as a directive will be passed through to the output string.

The directive consists of a percent (%) character, zero or more flags, optional minimum field width, optional modifier and a conversion specifier as follows.

%<flags><width><modifier><conversion>

flags and conversion are as in Time exept that the E flag is not egnored any more, it forse useing English names


630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
# File 'lib/parsi-date.rb', line 630

def strftime format='%Y/%m/%d'
  format.
    gsub('%%', 'PERCENT_SUBSTITUTION_MARKER').
    gsub('%+', '%a %b %e %H:%M:%S %Z %Y').
    gsub('%c', '%a %-d %B %Y').
    gsub('%x', '%D').
    gsub('%D', '%y/%m/%d').
    gsub('%F', '%Y-%m-%d').
    gsub('%v', '%e-%B-%Y').
    gsub('%Y', year.to_s).
    gsub('%C', (year / 100).to_s).
    gsub('%y', (year % 100).to_s).
    gsub('%m',  '%02d' % month).
    gsub('%_m', '%2d' % month).
    gsub('%-m', month.to_s).
    gsub('%^B', '%B').
    gsub('%B', MONTHNAMES[month]).
    gsub('%E^B', '%^EB').
    gsub('%^EB', EN_MONTHNAMES[month].capitalize).
    gsub('%EB', EN_MONTHNAMES[month]).
    gsub('%h', '%b').
    gsub('%^h', '%^b').
    gsub('%b', ABBR_MONTHNAMES[month]).
    gsub('%^b', ABBR_MONTHNAMES[month].capitalize).
    gsub('%d', '%02d' % day).
    gsub('%e', '%2d' % day).
    gsub('%-d', day.to_s).
    gsub('%j', '%03d' % yday.to_s).
    gsub('%A', DAYNAMES[wday]).
    gsub('%a', ABBR_DAYNAMES[wday]).
    gsub('%EA', EN_DAYNAMES[wday]).
    gsub('%Ea', ABBR_EN_DAYNAMES[wday]).
    gsub('%E^A', '%^EA').
    gsub('%^EA', EN_DAYNAMES[wday].capitalize).
    gsub('%E^a', '%^Ea').
    gsub('%^Ea', ABBR_EN_DAYNAMES[wday].capitalize).
    gsub('%w', wday.to_s).
    gsub('%n', "\n").
    gsub('%t', "\t").
    gsub('PERCENT_SUBSTITUTION_MARKER', '%')
end

#to_dateObject


601
602
603
# File 'lib/parsi-date.rb', line 601

def to_date
  self
end

#to_datetimeObject


605
606
607
# File 'lib/parsi-date.rb', line 605

def to_datetime
  DateTime.new! jd_to_ajd(jd, 0, 0), 0
end

#to_gregorianObject Also known as: gregorian


592
593
594
# File 'lib/parsi-date.rb', line 592

def to_gregorian
  ::Date.jd jd
end

#to_parsiObject Also known as: jalali, to_jalali, to_persian


609
610
611
# File 'lib/parsi-date.rb', line 609

def to_parsi
  self
end

#to_sObject

Return the date as a human-readable string.

The format used is YYYY-MM-DD.


715
# File 'lib/parsi-date.rb', line 715

def to_s() format('%.4d-%02d-%02d', year, mon, mday) end

#to_timeObject


597
598
599
# File 'lib/parsi-date.rb', line 597

def to_time
  gregorian.to_time
end

#upto(max, &block) ⇒ Object

Step forward one day at a time until we reach max (inclusive), yielding each date as we go.


689
690
691
# File 'lib/parsi-date.rb', line 689

def upto max, &block # :yield: date
  step max, 1, &block
end

#wdayObject

Get the week day of this date. Sunday is day-of-week 0; Saturday is day-of-week 6.


464
465
466
# File 'lib/parsi-date.rb', line 464

def wday
  @wday ||= jd_to_wday jd
end

#ydayObject

Get the day-of-the-year of this date.

January 1 is day-of-the-year 1


460
# File 'lib/parsi-date.rb', line 460

def yday; ordinal[1] end

#yearObject

Get the year of this date.


445
# File 'lib/parsi-date.rb', line 445

def year() civil[0] end