/* JDate.java Paul Carlisle - November 13, 1997 This class stores a date as a double value, whose integer portion is the Julian date, and whose fractional part represents the fraction of a twenty-four hour day. Julian dates enumerate dates sequentially, beginning with January 1, 4713 BC. For a full explanation, see Jean Meeus, "Astronomical Algorithms," Willman- Bell, 1991. The algorithms used here for converting to an dfrom Julian dates and calendar dates are taken from this reference. For dates prior to October 15, 1582, the class uses the Julian Calendar; dates after this time are calculated in the Gregorian calendar. Years BC are counted as negative, starting from the year 1 BC = 0, 2 BC = -1, and so on. The class provides the following methods: JDate() - A zero-argument constructor, which sets the date to the system's current date. JDate(int year, int month, double day) - A constructor which sets the date to the one provided in the argument. Invalid dates are rejected, leaving the currently set date untouched. The success or failure of constructing or setting a date can be checked with the isValid() method. Negative years are accepted, with the year 0 representing 1 BC, -1 representing 2 BC, and so forth. public boolean isValid() - Returns true if the date currently set resulted from a call or constructor argument which was a valid date, false otherwise. Invalid dates are those whose month is outside the range 1 - 12, whose day is outside the range 1 - the length of the month, or whose year is less than 4713 BC. Attempting to set with an invalid date leaves the current date information unchanged. public void setToCurrent() - Sets the date to the system's current date. public double getJD() - Returns the Julian date as a double, as described above. public int getYear() - Returns the year. This is 0 for the year 1 BC, -1 for the year 2 BC, and 1 for the year 1 AD. public int getMonth() - Returns the month (1 - 12). public double getDay() - Returns the day (1 - 31, plus fractional part representing fraction of 24-hour interval. public String getMonthString() - Returns the name of the month. public int getDayOfWeek() - returns a number repreenting the day of the week, with Sunday = 0, Monday = 1, ... , Saturday = 6. public String getWeekdayString() - Returns the name of the weekday. public String getDateString() - Returns a formatted String for the entire date, in the format "January 12, 1997 AD". public int getMonthLength() - Returns the number of days in the month. public boolean setDate (int year, int month, double day) - Sets the date to the values. If the date is invalid, leaves current setting untouched, and returns false. Returns true otherwise, and sets the date accordingly. public int[][] Grid() - Returns a 6-row by 7-column array representing the calendar layout of the current month. Days outside the month have elements filled with zero, while days within the month have elements filled with the corresponding date. */ import java.util.Date; import DateConstants; public class JDate { // Date in internal format. private double JD = 0; // Holds the offset, in hours, of the current time with respect // to GMT. It defaults to the system's idea of what the offset is, // but may be set manually in special circumstances. // The offset is added to the local time to get GMT, subtracted // to convert from GMT to local time.In practice, the reverse is // never done. private double timeZoneOffset; // Keeps track of whether date is valid or not; a date could be // invalid if argument constructor or setDate() is called with: // - a date before 1/1/4713 bc. // - a date whose day exceeds the number of days in that month. // - a date whose day is les than 1. private boolean dmyValid = true; // Constants used to determine behavior of dmyHelper(). // A way to simulate an enumerated type. private static int yr = 1; private static int mn = 2; private static int dy = 3; // Zero-argument constructor. Sets the date to the current machine date. public JDate () { Date machineDate = new Date(); timeZoneOffset = machineDate.getTimezoneOffset() / 60; setToCurrent(); } // JDate constructor. // Takes year, month and day as arguments. public JDate (int year, int month, double day) { dmyValid = setDate(year, month, day, 0, 0, 0); } // Add a fraction of a twenty-four hour day to JD. public void addFrac(double frac) { JD += frac; } // Increments the JD by hours. public void addHours(double hours) { JD += hours / 24; } // Increments JD by minutes. public void addMinutes(double minutes) { JD += minutes / 1440; } // Increments JD by seconds. public void addSeconds(double seconds) { JD += seconds / 86400; } // Returns either the year, month or day, depending on what is passed // in param; if param = yr, returns year, // = mn, returns month, // = dy, returns day. // Always returns a double, since this is how day is interpreted. Convert // back to int yourself if that's what you need. private double dmyHelper( int param ) { int B, C, D, E, m, y; double F, d; double temp = JD + 0.5; int Z = (int)temp; int A = 0; F = temp - Z; // Check if date is in Julian calendar. if (Z < 2299161) A = Z; else { int alpha = (int)( (Z - 1867216.25)/36524.25 ); A = Z + 1 + alpha - alpha/4; } // Preliminaries... B = A + 1524; C = (int)( (B - 122.1)/365.25 ); D = (int)(365.25 * C); E = (int)( (B - D)/30.6001 ); // Day. d = B - D - (int)(30.6001 * E) + F; if (param == dy) return d; // Month if (E < 14) m = E - 1; else m = E - 13; if (param == mn) return m; // Year if (m > 2) y = C - 4716; else y = C - 4715; // If we made it this far, the user get's the year by default. return y; } // Returns a string containing the date: // "Month day, year epoch" // i.e., "January 13, 1996 AD" public String getDateString() { String suffix = " AD"; int tempYr = getYear(); if (getYear() <= 0) { suffix = " BC"; tempYr = Math.abs(getYear()) + 1; } return (getMonthString() + " " + (int)getDay() + ", " + tempYr + suffix); } // Return a double representing the day of the month - // Integer portion represents day, fractional portion // represents fraction of day (i.e., time). public double getDay() { return dmyHelper(dy); } // Returns an integer value representing the day of the week, // with Sunday = 0, Monday = 1, ... , Saturday = 6. public int getDayOfWeek() { JDate temp = new JDate(getYear(), getMonth(), (int)getDay()); double tempJD = temp.getJD() + 1.5; return (int)tempJD % 7; } // Returns only the fractional part of the JD, representing // the time. This is normal, local time, starting at midnight // = 0. public double getFracTime() { double temp = 0.5 + JD - (int)JD; // This implementation of Java, at least, fails to do comparisons // properly; we have to add a small number to get the comparison // operator to "see" the difference. temp += 1e-16; if (temp >= 1.0) temp -= 1.0; if (temp < 0) temp += 1.0; return temp; } // Returns only the fractional part of the JD, representing // the time in UT. This is normal, UT time, starting at midnight // = 0. public double getFracUTTime() { return getFracTime() + timeZoneOffset / 24; } // Returns a string of hours, minutes and seconds, in the form // hh:mm:ss xx, i.e., " 6:34:08 pm" public String getHMString() { String suffix = " am"; int tempHours = getHours(); if (tempHours > 12) { tempHours -= 12; suffix = " pm"; } else if (tempHours == 0) tempHours = 12; else if (tempHours == 12) suffix = " pm"; String hrString = Integer.toString(tempHours); if (tempHours < 10) hrString = ' ' + hrString; int tempMinutes = getMinutes(); String minString = Integer.toString(tempMinutes); if (tempMinutes < 10) minString = '0' + minString; int tempSeconds = getSeconds(); String secString = Integer.toString(tempSeconds); if (tempSeconds < 10) secString = '0' + secString; String timeString = hrString + ':' + minString + ':' + secString + suffix; return timeString; } /***** Time routines - Added 12-1-97 ******/ /* These treat the fractional part of the JDate as a fraction of a twenty-four hour day. */ // Return the hours portion of the time, as an integer // ranging from 0 - 24. public int getHours() { double temp = getDay(); return (int)( (temp - (int)temp) * 24); } // Return the Julian date. public double getJD () { return JD; } // Returns the JD, corrected for the time zone offset, // to yield a JD expressing GMT. public double getJDUT() { return JD + timeZoneOffset / 24; } // Return the minutes portion of the time, as an integer // ranging from 0 - 59. public int getMinutes() { double temp = getDay(); temp = (temp - (int)temp) * 24; temp = (temp - (int)temp); return (int)(temp * 60); } // Return an integer representing the month (1 - 12). public int getMonth() { return (int) dmyHelper(mn); } // Return the number of days in the month. public int getMonthLength() { int length = 0; if ( (LeapYearQ(getYear()) && (getMonth() == 2)) ) length = 29; else length = DateConstants.MonthLength[getMonth() - 1]; return length; } // Return a string containing the name of the month. public String getMonthString() { return DateConstants.MonthNames[getMonth() - 1]; } // Return the seconds portion of the time as a double // ranging from 0 - 59.999... public int getSeconds() { double temp = getDay(); temp = (temp - (int)temp) * 24; temp = (temp - (int)temp) * 60; return (int)( (temp - (int)temp) * 60); } // Returns a string containing the name of the weekday. public String getWeekdayString() { int temp = getDayOfWeek(); return DateConstants.WeekdayNames[temp]; } // Return the year. public int getYear() { return (int) dmyHelper(yr); } // Returns the time zone offset, in hours. public double getZone() { return timeZoneOffset; } // Returns a 6 by 7 array representing a calendar layout. // A month may have as many as 31 days, spread over as many as // 6 weeks in the owrst case. The elements of this array are // zero if the corresponding date is not in the given month, // or the day number if they are. public int[][] Grid() { int[][] calgrid = new int[6][7]; int x, y, d; JDate date = new JDate(getYear(), getMonth(), 1); int year = date.getYear(); int month = date.getMonth(); int day = (int)date.getDay(); int length = date.getMonthLength(); int start = date.getDayOfWeek(); // Make a minor adjustment for compact February; start // grid on second week for neater layout. if ( (month == 2) && (length == 28) && (start == 0) ) start = 7; for (int i = 0; i < length; i++) { // Figure out column and row. We go through all // these contortions to accomodate the change from // the Julian to Gregorian calendar systems. date.setDate(year, month, i + 1, 0, 0, 0); // Figure out column. x = date.getDayOfWeek(); // Figure out row. d = (int)date.getDay(); // Adjust for switch from Julian to Gregorian system. if ( (year == 1582) && (month == 10) && (d > 16) ) start = 5; y = (d + start - 1)/7; calgrid[y][x] = d; } return calgrid; } // Answer to query if date is a valid one. public boolean isValid() { return dmyValid; } // Determine if a year was a leap year. private boolean LeapYearQ (int year) { // Pessimistic assumption: not a leap year. boolean bisextile = false; // Most years, if divisible by 4, are leap years. int rem4 = year % 4; // All years in Julian calendar which are divisible // by 4 are leap years. if (year < 1600) { if (rem4 == 0) bisextile = true; } // In the Gregorian calendar, years are leap years if // 1: they are divisible by 4, and // 2: if a century year, they are divisible by 400. else { int rem400 = year % 400; if ( (rem4 == 0) && (rem400 != 100) && (rem400 != 200) && (rem400 != 300) ) bisextile = true; } return bisextile; } // A date-only constructor, for the lacadaisical. public boolean setDate (int year, int month, double day) { return setDate(year, month, day, 0, 0, 0); } // Sets the date, with validity checks. // If invalid, JD defaults to 0. // If invalid, returns false; true otherwise. // day is a double to accomodate extension by time // of day. public boolean setDate (int year, int month, double day, int hours, int minutes, int seconds) { int mLength = 0; boolean error = false; // Check year validity. // Julian dates don't extend before -4712. if (year < -4712) error = true; // Check month validity. else if ( (month < 1) || (month > 12) ) error = true; else { if ( (LeapYearQ(year)) && (month == 2) ) mLength = 29; else mLength = DateConstants.MonthLength[month - 1]; // Check day validity. if ( (day < 1) || ((int)day > mLength) ) error = true; } if (error) { // Leave current information unchanged if date is bad. return false; } else // Calculate Julian Date. { int y = year; int m = month; int a = 0; int b = 0; if (month <= 2) { y = year - 1; m = month + 12; } // Correction for Gregorian calendar reform. if ( (year*10000 + month*100 + day) >= 15821015 ) { a = y/100; b = 2 - a + a/4; } day = day + ((double)hours + (double)minutes/60 + (double)seconds/3600)/24; JD = (int)(365.25 * (y + 4716)) + (int)(30.6001 * (m + 1)) + day + b - 1524.5; } dmyValid = true; return true; } // Set the Julian date. public void setJD(double jd) { JD = jd; } // Set the date to the current machine date. public void setToCurrent() { Date machineDate = new Date(); setDate(machineDate.getYear() + 1900, machineDate.getMonth() + 1, machineDate.getDate(), machineDate.getHours(), machineDate.getMinutes(), machineDate.getSeconds()); } // Sets the time zone offset. Probably never a good idea to use this. // Does minimal error checking, to ensure that offset is between // -12 and 12 hours. public void setZone(double zone) { if (Math.abs(zone) <= 12) timeZoneOffset = zone; } }