// Parts of this come unmodified from _Core_Java_ (First Edition).
// Not all the commenting has been made consistent.

import java.util.*;

public class Day 
{
    // Interface Invariant:  Once created and until destroyed, this
    //    instance contains a valid date.  getYear() != 0 && 
    //    1 <= getMonth() <= 12 && 1 <= getDay() <= #days in getMonth().  
    //    Also calendar date getMonth()/getDay()/getYear() does not
    //    fall in the gap formed by the change to the modern
    //    (Gregorian) calendar.  

  // Constructors

    public Day()
    //  Pre:   true
    //  Post:  the new instance's day, month, and year set to today's
    //             date (i.e., the date of creation of the instance)
    {   java.util.Date today = new java.util.Date();
        year  = today.getYear() + 1900;
        month = today.getMonth() + 1;
        day   = today.getDay();
    }
 
    public Day(int y, int m, int d) throws IllegalArgumentException
    //  Pre:   y != 0 && 1 <= m <= 12 && 1 <= d <= #days in month m
    //         (y,m,d) does not fall in the gap formed by the change
    //            to the modern (Gregorian) calendar.
    //  Post:  the new instance's day, month, and year set to y, m,
    //             and d, respectively  
    //  Exception:  IllegalArgumentException if y m d not a valid date
    {   year  = y;
        month = m;
        day   = d;
        if (!isValid())
            throw new IllegalArgumentException();
    }


  // Mutators

    public void setDay(int y, int m, int d)  
                throws IllegalArgumentException
    //  Pre:   y != 0 && 1 <= m <= 12 && 1 <= d <= #days in month m
    //         (y,m,d) does not fall in the gap formed by the
    //             change to the modern (Gregorian) calendar.
    //  Post:  this instance's day, month, and year set to year, month
    //             and day set to y, m, and d, respectively 
    //  Exception:  IllegalArgumentException if y m d not a valid date
    {   year  = y;
        month = m;
        day   = d;
        if (!isValid())
            throw new IllegalArgumentException();
    }

    public void advance(int n)
    //  Pre:   true
    //  Post:  this instance's date moved n days later.  (Negative n
    //             moves to an earlier date.) 
    {   fromJulian(toJulian() + n);
    }

  // Accessors

    public int getDay()
    //  Pre:   true
    //  Post:  returns the day from this instance, where 
    //             1 <= getDay() <= #days in this instance's month
    {   return day;
    }

    public int getMonth()
    //  Pre:   true
    //  Post:  returns the month from this instance's date, where
    //             1 <= getMonth() <= 12
    {   return month;
    }

    public int getYear()
    //  Pre:   true
    //  Post:  returns the year from this instance's date, where 
    //             getYear() != 0
    {   return year;
    }

    public int getWeekday()
    // Pre:   true
    // Post:  returns the day of the week upon which this instance
    //            falls, where 0 <= getWeekday() <= 6;
    //            0 == Sunday, 1 == Monday, ..., 6 == Saturday
    {   //  calculate day of week
        return (toJulian() + 1)% 7;
    }

    public boolean equals(Day dd)
    //  Pre:   dd is a valid instance of Day
    //  Post:  returns true if and only if this instance and instance
    //             dd denote the same calendar date
    {   return (year == dd.getYear() && month == dd.getMonth()
                && day == dd.getDay());
    }

    public int daysBetween(Day dd)
    //  Pre:   dd is a valid instance of Day
    //  Post:  returns the number of calendar days from the dd
    //             instance's date to this instance's date, where
    //             equals(dd.advance(n)) would hold
    {   //  implementation code
        return toJulian() - dd.toJulian();
    }

    public String toString()
    //  Pre:   true
    //  Post:  returns this instance's date expressed in the format
    //             "Day[year,month,day]"
    {   //  implementation code
        return "Day[" + year + "," + month + "," + day + "]";
    }

  //  Destructors -- None needed

  //  Private Methods -- Mostly borrowed from _Core_Java_

 
   /**
    * Computes the number of days between two dates
    * @return true iff this is a valid date
    */
   private boolean isValid()
   {  Day t = new Day();
      t.fromJulian(this.toJulian());
      return t.day == day && t.month == month 
         && t.year == year;
   }

   private int toJulian()
   /**
    * @return The Julian day number that begins at noon of 
    * this day
    * Positive year signifies A.D., negative year B.C. 
    * Remember that the year after 1 B.C. was 1 A.D.
    *
    * A convenient reference point is that May 23, 1968 noon
    * is Julian day 2440000.
    *
    * Julian day 0 is a Monday.
    *
    * This algorithm is from Press et al., Numerical Recipes
    * in C, 2nd ed., Cambridge University Press 1992
    */
    {   int jy = year;
        if (year < 0) 
	    jy++;
        int jm = month;
	if (month > 2) 
	    jm++;
        else
        {   jy--;
            jm += 13;
        }
        int jul = (int) (java.lang.Math.floor(365.25 * jy) 
          + java.lang.Math.floor(30.6001*jm) + day + 1720995.0); 

        int IGREG = 15 + 31*(10+12*1582);
            // Gregorian Calendar adopted Oct. 15, 1582

        if (day + 31 * (month + 12 * year) >= IGREG)
            // change over to Gregorian calendar
        {   int ja = (int)(0.01 * jy);
            jul += 2 - ja + (int)(0.25 * ja);
        }
        return jul;
    }

    private void fromJulian(int j)
    /**
     * Converts a Julian day to a calendar date
     * @param j  the Julian date
     * This algorithm is from Press et al., Numerical Recipes
     * in C, 2nd ed., Cambridge University Press 1992
     */
    {   int ja = j;
   
        int JGREG = 2299161;
            /* the Julian date of the adoption of the Gregorian
               calendar  
             */ 

        if (j >= JGREG)
        /* cross-over to Gregorian Calendar produces this
           correction
         */ 
        {   int jalpha = (int)(((float)(j - 1867216) - 0.25)
                / 36524.25);
            ja += 1 + jalpha - (int)(0.25 * jalpha);
        }
        int jb = ja + 1524;
        int jc = (int)(6680.0 + ((float)(jb-2439870) - 122.1)/365.25);
        int jd = (int)(365 * jc + (0.25 * jc));
        int je = (int)((jb - jd)/30.6001);
        day = jb - jd - (int)(30.6001 * je);
        month = je - 1;
        if (month > 12) 
	    month -= 12;
        year = jc - 4715;
        if (month > 2) 
	    year--;
        if (year <= 0) 
	    year--;
    }

    //  Implementation Invariants:
    //      year != 0 && 1 <= month <= 12 && 1 <= day <= #days in month
    //      (year,month,day) not in gap formed by the change to the
    //      modern (Gregorian) calendar

    private int year;
    private int month;
    private int day; 
}
