/* * @(#)BigDate.java * * Summary: Manipulate pure dates, without time or timezone. * * Copyright: (c) 1997-2010 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.1+ * * Created with: IntelliJ IDEA IDE. * * Version History: * 1.0 1997-05-03 - initial release. * 1.1 1997-05-04 - add setOrdinal, setYYYY, setMM, setDD. * 1.2 1997-05-05 - add public static toOrdinal. * 1.3 1997-05-06 - add getTimeStamp, warn about lead 0 meaning octal * 1.4 1997-05-06 - add getDayOfWeek, getDDD, rename NULL_ORDINAL, NULL_TIMESTAMP * 1.5 1997-05-11 - rename getJulian to getOrdinal to avoid confusion with * old Julian vs new Gregorian calendars. Redo 1582 code with named * constants to facilitate changing the switchover point between the old and * new calendars. * 1.6 1997-05-12 - shorten the names of constants. * 1.7 1997-06-21 - cache last result to avoid recalculation. * 1.8 1997-07-09 - add null constructor * 1.9 1997-12-17 - add documentation on how to convert to the British * calendar. Modify code to make it simple to switch to the British scheme. * Allow for fact British 100 and 400 Leap year rules kicked in at different * times. Add today method. Calculate constants based on others rather than * specifying individually to make change easier. expose default methods to * extenders of this class, making them protected. Fix bug. Null constructor * was not creating a properly initialised NULL date. There was confusion * then between a null date and a zero date = 1997-01-01. * 2.0 1998-01-26 - Peter V. Gadjokov" <pvg@infoscape.com> pointed out a * bug in isValid. The dd was not tested properly to make sure it was <= 31 * The bug did not cause incorrect execution, just slowed it, since a finer * check was done later. * 2.1 1998-01-30 - implement Cloneable, Serializable, equals, compareTo, * hashCode add Javadoc comments. isBritish compile time switch. * alphabetise. private, protected status changed. * 2.2 1998-06-18 - add getISOWeekNumber, getISODayOfWeek. * 2.3 1998-11-19 - new mailing address and phone. * 2.4 1998-11-27 - more examples on how to use BigDate in TestDate. * warnings about today and time zones. today is now two methods localToday * and UTCToday. added age method to compute age in years, months and days. * exposed daysInMonth as public. made most methods final for speed. Since * you have source, you can always unfinal them if you need to. * 2.5 1998-11-28 - added test harness to prove age routine is working * correctly. Note about ISO 8601:1988 international standard for dates is * YYYY-MM-DD. Corrected major bugs in age. Now works for mixed BC/AD. Works * if as of date is not today. More comments in age routine on how it works. * Now accepts two BigDates instead of two ordinals. Added more examples of * how to use BigDate. * 2.6 1998-11-30 - Added more examples to TestDate. * 2.7 1999-08-23 - getTimeStamp renamed to getUTCTimeStamp add * getLocalTimeStamp, getUTCDate, getLocalDate today(TimeZone) * 2.8 1999-09-04 - add daysInMonth to take the year instead of a * boolean. That way you can usually avoid the isLeap calculation. * 2.9 1999-09-08 - add sample isHolidayimplementationn to TestDate. add * dayOfWeek and isoDayOfWeek static versions. * 3.0 1999-09-08 - add nthXXXDay and ordinalOfnthXXXDay. * 3.1 1999-09-15 - add constructor and access for Astronomer's * Proleptic Julian day numbers. to be consistent with the US Naval * observatory, BC leap years are now 1 5 9, not 4, 8, 12. * 3.2 1999-09-20 - add getWeekNumber to complement getISOWeekNumber. * 3.3 1999-09-22 - correct toString to display very old or very future * dates correctly * 3.4 1999-10-18 - speed up leap year calcs by replacing yyyy % 4 with * yyyy & 3 * 3.5 1999-11-23 - give Timezone.getOffset a 1-based Sunday. Avoid * IllegalArgumentException in some JVMs. Place warnings about Sunday base * incompatibilities. * 3.6 1999-11-24 - add getCalendarDayOfWeek and calendarDayOfWeek * 3.7 2001-01-26 - properly implement Comparable with Object instead of * BigDate * 3.8 2001-03-17 - add code to TestDate to calculate how many sleeps until * Christmas. * 3.9 2002-03-16 - make toString non-final so you can override it. * 4.0 2002-04-10 - addDays convenience method toString now displays ISO * format yyyy-mm-dd TestDate examples changed to use addDays. * 4.1 2003-01-01 * 4.2 2003-01-01 * 4.3 2003-05-18 - Default is now British rather than Pope Gregory's rules * for when the missing days were dealt with. * 4.4 2003-08-05 - getDowMMDDYY -- alternative to toString, mainly to show * how to roll your own methods. * 4.5 2003-09-12 - Added constructor that takes a Date and TimeZone. added * setDateAtTime. * 4.6 2004-05-15 - getSeason * 4.7 2005-07-15 - isValid( yyyy-mm-dd), new package, com.mindprod.common11, * getCopyright * 4.8 2005-08-28 - added constructor that takes a String * argument. * 4.9 2006-03-04 - convert to Intellij. Make code more robust by moving * initialisation to a static block so field reordering will not screw it up. * 5.0 2007-10-15 - new methods, nearestXXXDay, dayOfWeek, calendarDayOfWeek. * Improved documentation. * 5.1 2008-02-01 - add named constants for the months and days of the week. * 5.2 2008-03-20 - add dayAbbr, dayName, monthAbbr, monthName * 5.3 2008-12-15 - toString now handles BC and non-4-digit years. toYYYYString, Constructor handles BC and non-4-digit years. * 5.4 2009-04-29 - add parseYYYYmmdd to parse incomplete date strings. * 5.5 2009-05-05 - add isAnniversary * 5.6 2009-12-09 - parsing of String dates is now more relaxed. Now ignores spaces, commas. Accepts single digit months and days. */ package com.mindprod.common11; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.Date; import java.util.TimeZone; /** * Manipulate pure dates, without time or timezone. * <p/> * Convert Gregorian YYYY MM DD back and forth to ordinal days since 1970-01-01, Thursday (sometimes called Julian or * datestamp form). BigDate objects are not designed for <b>storing</b> dates in a database, just for conversion. Long * term storage should store the ordinal either as an int, or possibly as a short. The BigDate constructor stores the * date both in ordinal and Gregorian forms internally. If you store one, it creates the other. * <p/> * The standard Sun Date won't handle dates prior to 1970 among other problems. BigDate handles dates 999,999 BC Jan 1 * to 999,999 AD Dec 31, 0 = 1970-01-01. * <p/> * Are the following quirks of the calendar considered in this code? * <p/> * 1900 is not a leap year (mod 100): <b>yes</b>. * <p/> * 2000 is a leap year (mod 400): <b>yes</b>. * <p/> * The 10 missing days in 1582 October.: <b>yes</b> (Pope Gregory's correction) 1582-10-05 to 1582-10-14 never happened. * <p/> * Britain and its territories (including the USA and Canada) adopted the Gregorian correction in 1752: <b>Yes</b>. By * then, 11 days had to be dropped. 1752-09-03 to 1752-09-13 never happened. However, you can modify constants in * BigDate to use the British calendar. Such a change only affects dates prior to 1753 since BigDate calendar is based * on 1970-01-01. toOrdinal with the Gregorian and British scheme will give the same number for recent dates. <b>You * must recompile BigDate with the <b>isBritish</b> boolean changed to true.</b>. It was used by Britain and its * colonies which later became the USA and Canada. However Nova Scotia used Pope Gregory's calendar. see * http://mindprod.com/jgloss/missingdays.html PLEASE CONFIGURE isBritish AND RECOMPILE BigDate IF YOU WISH TO USE THE * BRITISH CALENDAR. * <p/> * missing year 0 between 1 BC and 1 AD. <b>yes</b>. * <p/> * in Roman times leap years occurred at irregular intervals, Considered inauspicious, they were avoided during war. * <b>no</b>. Instead we presume leap years every 4 years even back to 999,999BC. * <p/> * leap seconds: <b>no</b> * <p/> * Normally all you need is one BigDate object that you use for all interconversions with set(ordinal), set(yyy,mm,dd) * and getOrdinal(), getYYYY(), getMM(), getDD(). * <p/> * java.util.Date has some odd habits, using 101 to represent the year 2001, and 11 to represent December. BigDate is * more conventional. You use 2001 to represent the year 2001 and 12 to represent December. * <p/> * BigDate implements proleptic Gregorian and Julian calendars. That is, dates are computed by extrapolating the current * rules indefinitely far backward and forward in time. As a result, BigDate may be used for all years to generate * meaningful and consistent results. However, dates obtained using BigDate are historically accurate only from March 1, * 4 AD onward, when modern Julian calendar rules were adopted. Before this date, leap year rules were applied * irregularly, and before 45 BC the Julian calendar did not even exist. Prior to the institution of the Gregorian * calendar, New Year's Day was March 25. To avoid confusion, this calendar always uses January 1. * <p/> * TODO Future enhancements: - handle time, and timezones, interconversion with GregorianCalendar dates. * <p/> * * @author Roedy Green, Canadian Mind Products * @version 5.6 2009-12-09 - parsing of String dates is now more relaxed. Now ignores spaces, commas. Accepts single * digit months and days. * @noinspection WeakerAccess,UnusedDeclaration * @since 1997-05-03 */ public final class BigDate implements Cloneable, Serializable, Comparable<BigDate> { // ------------------------------ CONSTANTS ------------------------------ /** * PLEASE CONFIGURE isBritish BEFORE COMPILING. Mysterious missing days in the calendar. Pope Gregory: 1582-10-04 * Thursday, was followed immediately by 1582-10-15 Friday dropping 10 days. British: 1752-09-02 Wednesday was * followed immediately by 1752-09-14 Thursday dropping 12 days. Constant: true if you want the British calender, * false if Pope Gregory's. You must recompile for it to have effect. For Britain, the USA and Canada it should be * true. */ public static final boolean isBritish = true; /** * Constant: adjustment to make ordinal 0 come out on 1970-01-01 for AD date calculations. This number was computed * by making an estimate, seeing what value toOrdinal gave for 1970-01-01 and then adjusting this constant so that * 1970-01-01 would come out Ordinal 0. */ private static final int AD_epochAdjustment; /** * April is 4 */ public static final int APR = 4; /** * April is 4 */ public static final int APRIL = 4; /** * August is 8 */ public static final int AUG = 8; /** * August is 8 */ public static final int AUGUST = 8; /** * Constant: adjustment to make ordinal 0 come out to 1970-01-01 for BC date calculations. Account for missing year * 0. */ private static final int BC_epochAdjustment; /** * Constant: when passed to a constructor, it means caller guarantees YYYY MM DD are valid including leap year * effects and missing day effects. BigDate will not bother to check them. * * @noinspection WeakerAccess * @see #CHECK * @see #NORMALIZE * @see #NORMALISE */ public static final int BYPASSCHECK = 1; /** * constant: when passed to a contructor it means BigDate should check that YYYY MM DD are valid. * * @noinspection WeakerAccess * @see #BYPASSCHECK * @see #NORMALIZE * @see #NORMALISE */ public static final int CHECK = 0; /** * December is 12 */ public static final int DEC = 12; /** * December is 12 */ public static final int DECEMBER = 12; /** * February is 2 */ public static final int FEB = 2; /** * February is */ public static final int FEBRUARY = 2; /** * Friday in BigDate is 5 */ public static final int FRI = 5; /** * Friday in BigDate is 5 */ public static final int FRIDAY = 5; /** * Constant: day of the first day of the month of the Gregorian Calendar Pope Gregory: 1582-10-15, British: 1752 Sep * 14 */ private static final int GC_firstDD; /** * Constant: Ordinal for 1582 Dec 31, the last day of first year of the Gregorian calendar. */ private static final int GC_firstDec_31; /** * Constant: month of the first date of the Gregorian Calendar. Pope Gregory: 1582-10-15, British: 1752-09-14 * * @noinspection FieldCanBeLocal */ private static final int GC_firstMM; /** * Constant: Ordinal for 1582-10-15, the first day of the Gregorian calendar. */ private static final int GC_firstOrdinal; /** * Constant: year of the first date (1752-10-15) of the Gregorian Calendar = 1582. Just after the missing daysp. * Different parts of the world made the transition at different times. Pope Gregory: 1582-10-04, British: * 1752-09-02. */ private static final int GC_firstYYYY; /** * January is 1 */ public static final int JAN = 1; /** * Constant: Ordinal for 1 AD Jan 01 */ private static final int Jan_01_0001; // don't move this up with other publics since it requires privates // for its definition. /** * Constant: Ordinal for 1 BC Jan 01 */ private static final int Jan_01_0001BC; /** * Constant: Ordinal for 4 AD Jan 01 */ private static final int Jan_01_0004; /** * Constant: ordinal of 1600 Jan 01, the first year when the mod 100 leap year rule first had any effect. */ private static final int Jan_01_Leap100RuleYear; /** * Constant: ordinal of 1600 Jan 01, the year when the mod 400 leap year rule first had any effect. */ private static final int Jan_01_Leap400RuleYear; /** * January is 1 */ public static final int JANUARY = 1; /** * July is 7 */ public static final int JUL = 7; /** * July is 7 */ public static final int JULY = 7; /** * June is 6 */ public static final int JUN = 6; /** * June is 6 */ public static final int JUNE = 6; /** * Constant: year that the mod 100 rule first had any effect. For The Gregorian Calendar, 1600. For the British * Calendar, 1800. Round up to next 100 years after the missing day anomaly. */ private static final int Leap100RuleYYYY; /** * Constant: year that the mod 400 rule first had any effect. For The Gregorian Calendar, 1600. For the British * Calendar, 2000. Round up to next 400 years after the missing day anomaly. */ private static final int Leap400RuleYYYY; /** * March is 3 */ public static final int MAR = 3; /** * March is 3 */ public static final int MARCH = 3; /** * Constant: biggest ordinal that BigDate will accept, corresponds to 999,999 Dec 31 AD. */ public static final int MAX_ORDINAL; /** * Constant: biggest year that BigDate handles, 999,999 AD. */ public static final int MAX_YEAR = 999999; /** * May is 5 */ public static final int MAY = 5; /** * Constant: earliest ordinal that BigDate handles; corresponds to 999,999 Jan 01 BC. */ public static final int MIN_ORDINAL; /** * Constant: earliest year that BigDate handles, 999,999 BC. * * @noinspection WeakerAccess */ public static final int MIN_YEAR = -999999; /** * Constant: how many days were lost during the Gregorian correction, 10 for the Gregorian calendar, 11 for the * British. */ private static final int missingDays; /** * Monday in BigDate is 1 */ public static final int MON = 1; /** * Monday in BigDate is 1 */ public static final int MONDAY = 1; /** * Adjustment to make Monday come out as day 0 after doing 7 modulus. Accounts for fact MIN_ORDINAL was not a Monday */ private static final int MondayIsZeroAdjustment = 3; /** * Constant: when passed to a constructor, it means any invalid dates are converted into the equivalent valid ones. * e.g. 1954-09-31 -> 1954-10-01. 1954-10- minus 1 -> 1954-09-30 1954-13-01 -> 1955-01-01. * * @see #CHECK * @see #BYPASSCHECK * @see #NORMALIZE */ public static final int NORMALISE = 2; /** * Constant: American spelling alias for NORMALISE. * * @see #CHECK * @see #BYPASSCHECK * @see #NORMALISE */ public static final int NORMALIZE; /** * November is 11 */ public static final int NOV = 11; /** * November is 11 */ public static final int NOVEMBER = 11; /** * Constant: ordinal to represent a null date -2,147,483,648, null Gregorian is 0,0,0. */ public static final int NULL_ORDINAL = Integer.MIN_VALUE; /** * October is 10 */ public static final int OCT = 10; /** * October is 10 */ public static final int OCTOBER = 10; /** * Constant: day of the last day of the month of the old Julian calendar. Pope Gregory: 1582-10-04, British: 1752 * Sep 2 */ private static final int OJC_lastDD; /** * Constant: month of the last date of the old Julian Calendar Pope Gregory: 1582-10-04, British: 1752-09-02 */ private static final int OJC_lastMM; /** * Constant: year of the last date (1582-10-04) of the old Julian calendar, just prior to the missing 10 days, = * 1852. Different parts of the world made the transition at different times. Usually 1582-10-04. For British * calender it would be 1752-09-02 . */ private static final int OJC_lastYYYY; /** * Saturday in BigDate is 6 */ public static final int SAT = 6; /** * Saturday in BigDate is 6 */ public static final int SATURDAY = 6; /** * September is 9 */ public static final int SEP = 9; /** * September is 9 */ public static final int SEPTEMBER = 9; /** * Sunday is 0 */ public static final int SUN = 0; /** * Sunday is 0 */ public static final int SUNDAY = 0; /** * Adjustment to make Sunday come out as day 0 after doing 7 modulus. Accounts for fact MIN_ORDINAL was not a * Sunday. */ private static final int SundayIsZeroAdjustment = 4; /** * Thursday in BigDate is 4 */ public static final int THU = 4; /** * Thursday in BigDate is 4 */ public static final int THURSDAY = 4; /** * Tuesday in BigDate is 2 */ public static final int TUE = 2; /** * Tuesday in BigDate is 2 */ public static final int TUESDAY = 2; /** * Wednesday in BigDate is 3 */ public static final int WED = 3; /** * Wednesday in BigDate is 3 */ public static final int WEDNESDAY = 3; /** * Constant : value for a null TimeStamp -9,223,372,036,854,775,808 * * @noinspection WeakerAccess */ public static final long NULL_TIMESTAMP = Long.MIN_VALUE; /** * used to identify this version of serialised BigDate objects */ static final long serialVersionUID = 34L; /** * days of the week 3 letter abbreviations in English. Index Sunday = 0. */ private static final String[] dayAbbr = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; /** * full days of the week in English. Index Sunday = 0. */ private static final String[] dayName = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; /** * 3-letter Months of the year in English. index January = 1 */ private static final String[] monthAbbr = { "???", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; /** * full Months of the year in English. index January = 1 */ private static final String[] monthName = { "unknown", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; /** * Constant array: how many days in the year prior to the first of the given month in a leap year. Indexed by Jan=0. */ private static final int[] leap_daysInYearPriorToMonthTable = { 0, 31, 60, /* J F M */ 91, 121, 152, /* A M J */ 182, 213, 244, /* J A S */ 274, 305, 335 };/* O N D */ /** * Constant array: how many days in the year prior to the first of the given month in a non-leap year. Indexed by * Jan=0. */ private static final int[] usual_daysInYearPriorToMonthTable = { 0, 31, 59, /* J F M */ 90, 120, 151, /* A M J */ 181, 212, 243, /* J A S */ 273, 304, 334 };/* O N D */ // P R I V A T E _ T A B L E S _ F O R _ D A T E _ C O N V E R S I O N /** * Constant array: how many days are in there in a month, (not a leap year). Indexed by Jan = 0. */ private static final int[] usual_DaysPerMonthTable = { 31, 28, 31, /* J F M */ 30, 31, 30, /* A M J */ 31, 31, 30, /* J A S */ 31, 30, 31 };/* O N D */ /** * Constant array: what month does the indexing day number fall in a leap year? Indexed by ddd Jan 1 = 0. */ private static int[] leap_dddToMMTable; /** * Constant array: what month does the indexing day number fall in a non-leap year? Indexed by ddd Jan 1 = 0. */ private static int[] usual_dddToMMTable; // ------------------------------ FIELDS ------------------------------ /** * Day, 1 to 31. If size of BigDate objects were a consideration, you could make this a byte. * * @noinspection WeakerAccess */ protected transient int dd = 0; /** * Month, 1 to 12. If size of BigDate objects were a consideration, you could make this a byte. * * @noinspection WeakerAccess */ protected transient int mm = 0; /** * Ordinal days since Jan 01, 1970. -365968798 to 364522971. i.e. 999,999 BC to 999,999 AD. */ protected int ordinal = NULL_ORDINAL; /** * Year, -999,999 to +999,999, negative is BC, positive is AD, 0 is null. If I were rewriting this, I would likely * encode year 1 BC as 0, and convert on output. That would simplify calculation over the 1AD -1BC barrrier. * * @noinspection WeakerAccess */ protected transient int yyyy = 0; // -------------------------- PUBLIC STATIC METHODS -------------------------- /** * Returns a BigDate object initialised to today's UTC (Greenwich GMT) date, in other words the date and time in * Greenwich England right now without any summer time correction. It works even if Java's default Timezone is not * configured correctly, but it requires your system clock accurately set to UTC time. Experiment setting your * system date/time to various values and making sure you are getting the expected results. Note the date in the * created object does not keep updating every time you reference it with methods like getOrdinal or getDD. You * always get the date the object was created. * * @return BigDate object initialised to today, in Greenwich. * @see #localToday * @see #today */ public static BigDate UTCToday() { // 86,400,000 = 1000 * 60 * 60 * 24 = milliseconds per day return new BigDate((int) (System.currentTimeMillis() / 86400000L)); }// end UTCToday /** * calculate the age in years, months and days. To compute elapsed time between two dates, use the first as the * birthDate and the second as the asOf. DeathDate for asOf gives age at time of death. Today for asOf gives age * today. * * @param birthDate usually the birth of a person. * @param asOf usually today, the day you want the age as of. asOf must come after birthDate to get a meaningful * result. Usually asOf > birthDate. Difference is always positive no matter if asOf is > or < birthDate * @return array of three ints (not Integers). [0]=age in years, [1]=age in months, [2]=age in days. * @see #localToday * @see #today * @see #UTCToday * @see #isAnniversary */ public static int[] age(BigDate birthDate, BigDate asOf) { if (birthDate.getOrdinal() >= asOf.getOrdinal()) { if (birthDate.getOrdinal() == asOf.getOrdinal()) { return new int[] { 0, 0, 0 }; } else { // age to date in future, exchange dates so difference positive final BigDate temp = asOf; asOf = birthDate; birthDate = temp; } } int birthYYYY = birthDate.getYYYY(); int birthMM = birthDate.getMM(); int birthDD = birthDate.getDD(); int asOfYYYY = asOf.getYYYY(); int asOfMM = asOf.getMM(); int asOfDD = asOf.getDD(); int ageInYears = asOfYYYY - birthYYYY; int ageInMonths = asOfMM - birthMM; int ageInDays = asOfDD - birthDD; if (ageInDays < 0) { // This does not need to be a while loop because // birthDD is always less than daysInbirthMM month. // Guaranteed after this single treatment, ageInDays will be >= 0. // i.e. ageInDays = asOfDD - birthDD + daysInBirthMM. ageInDays += BigDate.daysInMonth(birthMM, birthYYYY); ageInMonths--; } if (ageInMonths < 0) { ageInMonths += 12; ageInYears--; } if (birthYYYY < 0 && asOfYYYY > 0) { ageInYears--; } if (ageInYears < 0) { ageInYears = 0; ageInMonths = 0; ageInDays = 0; } int[] result = new int[3]; result[0] = ageInYears; result[1] = ageInMonths; result[2] = ageInDays; return result; }// end age /** * Get day of week for given ordinal. It is one-based starting with Sunday. * * @param ordinal days since 1970-01-01 to test. * @return day of week 1=Sunday 2=Monday 3=Tuesday 4=Wednesday 5=Thursday 6=Friday 7=Saturday Compatible with Sun's * 1=Calendar.SUNDAY Not compatible with BigDate.getDayOfWeek. * @see #isoDayOfWeek * @see #dayOfWeek * @see #getCalendarDayOfWeek */ public static int calendarDayOfWeek(int ordinal) { return dayOfWeek(ordinal) + 1; } /** * Get 3-char abbreviation of a given day of the week * * @param dayOfWeek sunday = 0 * @return abbreviation for day of week, e.g. "sun", all lower case. */ public static String dayAbbr(int dayOfWeek) { return dayAbbr[dayOfWeek]; } /** * Get full name of given day of the week * * @param dayOfWeek sunday = 0 * @return name of day of week e.g. "Sunday" */ public static String dayName(int dayOfWeek) { return dayName[dayOfWeek]; } /** * Get day of week for given ordinal. Is it zero-based starting with Sunday. * * @param ordinal days since 1970-01-01 to test. * @return day of week 0=Sunday 1=Monday 2=Tuesday 3=Wednesday 4=Thursday 5=Friday 6=Saturday WARNING: not compatible * with 1=Calendar.SUNDAY * @see #calendarDayOfWeek * @see #isoDayOfWeek * @see #getDayOfWeek */ public static int dayOfWeek(int ordinal) { // modulus in Java is "broken" for negative numbers // so we adjust to make the dividend positive. // By "broken" I mean the official rules for what // is supposed to happen for negative dividends // won't give the desired result in this case. // See modulus in the Java glossary for more details. return (ordinal == NULL_ORDINAL) ? 0 : ((ordinal + SundayIsZeroAdjustment - MIN_ORDINAL) % 7); } /** * How many days are there in a given month? * * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @param leap true if you are interested in a leap year * @return how many days are in that month */ public static int daysInMonth(int mm, boolean leap) { if (mm != 2) { return usual_DaysPerMonthTable[mm - 1]; } else { return leap ? 29 : 28; } }// end daysInMonth /** * How many days are there in a given month? o * * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @param yyyy year of interest. * @return how many days are in that month */ public static int daysInMonth(int mm, int yyyy) { if (mm != 2) { return usual_DaysPerMonthTable[mm - 1]; } else { return isLeap(yyyy) ? 29 : 28; } }// end daysInMonth /** * Multiply then divide using floored rather than the usual truncated arithmetic, using a long intermediate. * * @param multiplicand one of two numbers to multiply together * @param multiplier one of two numbers to multiply together * @param divisor number to divide by * @return (multiplicand * multiplier) / divisor */ public static int flooredMulDiv(int multiplicand, int multiplier, int divisor) { long result = (long) multiplicand * (long) multiplier; if (result >= 0) { return (int) (result / divisor); } else { return (int) ((result - divisor + 1) / divisor); } }// end flooredMulDiv /** * Embeds copyright notice * * @return copyright notice * @noinspection SameReturnValue */ public static String getCopyright() { return "BigDate 5.1 freeware copyright (c) 1997-2010 Roedy Green, Canadian Mind Products, http://mindprod.com roedyg@mindprod.com"; } // p u b l i c m e t h o d s /** * Determine if this date is someone's birthdate, or the aniversary of their death, or the anniversary of a marriage * etc. For example boolean todayIsYourBirthday = BigDate.isAnniversary( born, BigDate.localToday() ); boolean * todayIsYourWeddingAnniversary = BigDate.isAnniversary( weddingDate, BigDate.localToday() ); boolean * todayAnniversaryDeathOfBach = BigDate.isAnniversary( new BigDate("1750-07-28"), BigDate.localToday()); * <p/> * To compute relative anniversary between two dates, use the first as the birthDate and the second as the asof. * <p/> * February 29 is treated specially, to handle the way people people born on Feb 29 celebrate their birth days on * Feb 28 in non-leap years. If the asOf year is not a leap year then Feb 28 is considered a match for Feb 29. * However, if the asOf date is Feb 29 then Feb 28 in the birth year is not considered a match. * * @param birthDate usually the birth of a person, or possibly a date. * @param asOf usually today, the day you want the to know if it is an anniversary. asof must come after birthDate * to get a meaningful result. * @return true if asOf date in as even number of years after the birthdate. If the asOf date is <= birthDate it will * return false. * @see #age */ public static boolean isAnniversary(BigDate birthDate, BigDate asOf) { if (asOf.compareTo(birthDate) < 0) { return false; } int asOfMM = asOf.getMM(); int birthMM = birthDate.getMM(); if (birthMM != asOfMM) { return false; } int birthDD = birthDate.getDD(); int asOfDD = asOf.getDD(); if (birthDD == asOfDD) { return true; } // at this point, months matched, but days did not. // celebrate feb 29 birthday on feb 28 in non leap year. return birthDD == 29 && asOfDD == 28 && birthMM == 2 /* && asOfMM == 2 */&& !isLeap(asOf.getYYYY()); } /** * Is the given year a leap year, considering history, mod 100 and mod 400 rules? By 1582, this excess of leap years * had built up noticeably. At the suggestion of astronomers Luigi Lilio and Christopher Clavius, Pope Gregory XIII * dropped 10 days from the calendar. Thursday 1582 October 4 Julian was followed immediately by Friday 1582 October * 15 Gregorian. He decreed that every 100 years, a leap year should be dropped except that every 400 years the leap * year should be restored. Only Italy, Poland, Portugual and Spain went along with the new calendar immediately. * One by one other countries adopted it in different years. Britain and its territories (including the USA and * Canada) adopted it in 1752. By then, 11 days had to be dropped. 1752-09-02 was followed immediately by * 1752-09-14. The Gregorian calendar is the most widely used scheme. This is the scheme endorsed by the US Naval * observatory. It corrects the year to 365.2425. It gets ahead 1 day every 3289 years. For BC dates, the years the * years 1, 5, 9 are leap years, not 4, 8, 12 as you might expect, from the general rule. * * @param yyyy year to test. * @return true if the year is a leap year. * @noinspection SimplifiableIfStatement */ public static boolean isLeap(int yyyy) { // if you change this code, make sure you make corresponding changes to // jan01OfYear, toGregorian, MondayIsZeroAdjustment, // SundayIsZeroAdjustment, // AD_epochAdjustment and BC_epochAdjustment // yyyy & 3 is a fast way of saying yyyy % 4 if (yyyy < Leap100RuleYYYY) { if (yyyy < 0) { return ((yyyy + 1) & 3) == 0; } else { return (yyyy & 3) == 0; } } if ((yyyy & 3) != 0) { return false; } if (yyyy % 100 != 0) { return true; } if (yyyy < Leap400RuleYYYY) { return false; } return yyyy % 400 == 0; } /** * Test to see if the given yyyy-mm-dd is a date as a String is legitimate. must have 4-digit years, and use dashes * between the number and no sign Does extensive checks considering leap years, missing days etc. * * @param yyyy_mm_dd string of form "yyyy-mm-dd". yyyy-mm-dd y-mm-ddBC 2009-12-31 1234-5-31AD 1970/3/3 AD 3.01.12 bc * 2000_12_31 It will not accept a date missing the year, month or day field. Spaces and commas are * ignored. * @return true if that represents a valid date. */ public static boolean isValid(String yyyy_mm_dd) { final int[] ymd = relaxedParse(yyyy_mm_dd); return ymd != null && BigDate.isValid(ymd[0], ymd[1], ymd[2]); } /** * Test to see if the given yyyy, mm, dd date is legitimate. Does extensive checks considering leap years, missing * days etc. * * @param yyyy -999,999 (BC) to +999,999 (AD) * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @param dd day 1 to 31, no lead 0. * @return true if yyyy mm dd is a valid date. * @noinspection SimplifiableIfStatement */ public static boolean isValid(int yyyy, int mm, int dd) { // null date 0000 00 00 is considered valid // but otherwise year 0000 never happened. if (yyyy == 0) { return (mm == 0) && (dd == 0); } if ((yyyy < MIN_YEAR) || (yyyy > MAX_YEAR) || (mm < 1) || (mm > 12) || (dd < 1) || (dd > 31)) { return false; } // account for missing 10 days in 1582. // Thursday 1582 October 4 Julian was followed // immediately by Friday 1582 October 15 // Similarly for the British Calendar if (yyyy == OJC_lastYYYY && mm == OJC_lastMM && OJC_lastDD < dd && dd < GC_firstDD) { return false; } return dd <= daysInMonth(mm, yyyy); }// end isValid /** * Get day of week 1 to 7 for this ordinal according to the ISO standard IS-8601. It is one-based starting with * Monday. * * @param ordinal days since 1970-01-01 to test. * @return day of week 1=Monday to 7=Sunday, 0 for null date. WARNING: not compatible with 1=Calendar.SUNDAY. * @see BigDate#dayOfWeek(int) */ public static int isoDayOfWeek(int ordinal) { // modulus in Java is "broken" for negative numbers // so we adjust to make the dividend positive. // By "broken" I mean the official rules for what // is supposed to happen for negative dividends // won't give the desired result in this case. // See modulus in the Java glossary for more details. return (ordinal == NULL_ORDINAL) ? 0 : ((ordinal + MondayIsZeroAdjustment - MIN_ORDINAL) % 7) + 1; } /** * Returns a BigDate object initialised to today's local date. It depends on Java's default Timezone being * configured, and your system clock accurately set to UTC time. Experiment setting your system date/time to various * values and making sure you are getting the expected results. Note the date in the created object does not keep * updating every time you reference it with methods like getOrdinal or getDD. You always get the date the object * was created. It is quite a production to get the local date. Best to ask once and save the today object. * * @return BigDate object initialised to today, local time. * @see #today #see #UTCToday */ public static BigDate localToday() { return today(TimeZone.getDefault()); }// end localToday /** * Get 3-char abbreviation of a given month of the year. * * @param mm month number jan = 1 * @return abbreviation for month, e.g. "Jan" */ public static String monthAbbr(int mm) { return monthAbbr[mm]; } /** * Get full name of a given month of the year. * * @param mm month number jan = 1 * @return name of month e.g. "January" */ public static String monthName(int mm) { return monthName[mm]; } /** * Find the first monday in a given month, the 3rd monday or the last Thursday... * * @param which 1=first 2=second 3=third 4=fourth 5=last (might be 4th or 5th) * @param dayOfWeek 0=Sunday 1=Monday 2=Tuesday 3=Wednesday 4=Thursday 5=Friday 6=Saturday WARNING: not compatible * with 1=Calendar.SUNDAY. * @param yyyy year of interest. * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @return day of month 1..31 */ public static int nthXXXDay(int which, int dayOfWeek, int yyyy, int mm) { int dayOfWeekOf1st = BigDate.dayOfWeek(BigDate.toOrdinal(yyyy, mm, 1)); int dayOfMonthOfFirstDesiredDay = (dayOfWeek - dayOfWeekOf1st + 7) % 7 + 1; int dayOfMonthOfNthDesiredDay = dayOfMonthOfFirstDesiredDay + (which - 1) * 7; if (which >= 5 && dayOfMonthOfNthDesiredDay > daysInMonth(mm, yyyy)) { dayOfMonthOfNthDesiredDay -= 7; } return dayOfMonthOfNthDesiredDay; }// end nthXXXDay /** * Find the first monday in a given month, the 3rd monday or the last Thursday... * * @param which 1=first 2=second 3=third 4=fourth 5=last (might be 4th or 5th) * @param dayOfWeek 0=Sunday 1=Monday 2=Tuesday 3=Wednesday 4=Thursday 5=Friday 6=Saturday WARNING: not compatible * with 1=Calendar.SUNDAY. * @param yyyy year of interest. * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @return day of month 1..31 */ public static int ordinalOfnthXXXDay(int which, int dayOfWeek, int yyyy, int mm) { int dayOfMonthOfNthDesiredDay = nthXXXDay(which, dayOfWeek, yyyy, mm); return BigDate.toOrdinal(yyyy, mm, dayOfMonthOfNthDesiredDay); }// end ordinalOfnthXXXDay /** * parse a string into yyyy which may have form: 1948 230BC 1234AD It does not allow a space before the BC or AD. * * @param yyyyString date string. * @return yyyy, negative for BC. * @throws NumberFormatException */ public static int parseYYYY(String yyyyString) throws NumberFormatException { int ye = yyyyString.length(); boolean bc = false; if (yyyyString.endsWith("BC")) { ye -= 2; bc = true; } else if (yyyyString.endsWith("AD")) { ye -= 2; } int yyyy = Integer.parseInt(yyyyString.substring(0, ye)); if (bc) { yyyy = -yyyy; } return yyyy; } /** * Parse a, possibly incomplete, date String of form yyyy-yy-dd or yyyy-mm or yyyy into a BigDate Also handles * AD/BC, 1-digit months, 1-digit days and 1, 2 or 3-digit years. Date must be a valid normalised date. * * @param incomplete Date string, usually of form YYYY-MM-DD, possibly just YYYY-MM or YYYY. * @return BigDate first of year for YYYY or first of month for YYYY-MM, or precise date for YYYY-MM-DD * @see #BigDate(String) */ public static BigDate parseYYYYmmdd(String incomplete) { final String complete; // count dashes in string switch (StringTools.countInstances(incomplete, '-')) { case 2: // date already yyyy-mm-dd complete = incomplete; break; case 1: // was yyyy-mm if (incomplete.endsWith("BC") || incomplete.endsWith("AD")) { complete = incomplete.substring(0, incomplete.length() - 2) + "-01" + incomplete.substring(incomplete.length() - 2); } else { complete = incomplete + "-01"; } break; case 0: // was yyyy if (incomplete.endsWith("BC") || incomplete.endsWith("AD")) { complete = incomplete.substring(0, incomplete.length() - 2) + "-01-01" + incomplete.substring(incomplete.length() - 2); } else { complete = incomplete + "-01-01"; } break; default: throw new IllegalArgumentException("BigDate.parseYYYYmmdd expects YYYY-MM-DD or YYYY-MM or YYYY."); } return new BigDate(complete); } /** * Convert date in form YYYY MM DD into days since the 1970-01-01. This method lets you convert directly from * Gregorian to ordinal without creating a BigDate object. yyyy mm dd must be a valid date. * * @param yyyy -999,999 (BC) to +999,999 (AD) * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @param dd day 1 to 31, no lead 0. * @return ordinal, days since 1970-01-01. */ public static int toOrdinal(int yyyy, int mm, int dd) { // treat null date as a special case if ((yyyy == 0) && (mm == 0) && (dd == 0)) { return NULL_ORDINAL; } // jan01OfYear handles missing day adjustment for years > 1582 // We only need to handle year = 1582 here. int missingDayAdjust = (yyyy == OJC_lastYYYY && ((mm == OJC_lastMM && dd > OJC_lastDD) || mm > OJC_lastMM)) ? missingDays : 0; return jan01OfYear(yyyy) + daysInYearPriorToMonth(mm, isLeap(yyyy)) - missingDayAdjust + dd - 1; }// end toOrdinal /** * Returns a BigDate object initialised to the date right now in the given timezone. It depends on your system clock * accurately set to UTC time. Experiment setting your system date/time to various values and making sure you are * getting the expected results. Note the date in the created object does not keep updating every time you reference * it with methods like getOrdinal or getDD. You always get the date the object was created. It is quite a * production to get the this date. Best to ask once and save the today object. * * @param timeZone in which we want to know the today's date. * @return BigDate object initialised to today at given timezone. * @see #localToday * @see #UTCToday */ public static BigDate today(TimeZone timeZone) { BigDate d = new BigDate(); d.setDateAtTime(System.currentTimeMillis(), timeZone); return d; }// end today // -------------------------- PUBLIC INSTANCE METHODS -------------------------- // don't move this up with other publics since it requires privates // for its definition. // c o n s t r u c t o r s /** * Constructor for the null date. Gets set to null date, NOT current date like Java Date!!. BigDate.localToday() * will create an object initialised to today's date. * * @see #localToday * @see #UTCToday * @see #today */ public BigDate() { } /** * Construct a BigDate object given the Proleptic Julian day number. The Proleptic Julian calendar that astronomers * use starts with 0 at noon on 4713 BCE January 1. In contrast, BigDate's Ordinal base is 1970-01-01. * * @param prolepticJulianDay days since 4713 BC Jan 1 noon. Such numbers usually arise in astronomical calculation. * You don't need to concern yourself with the strangeness of the Julian calendar, just its simple day * numbering. BEWARE! after adjusting for noon, fractional parts are discarded. BigDate tracks only * dates, not dates and times. e.g. 2000-03-20 noon is 2,451,624 in proleptic day numbers. 1970-1-1 is * 2,440,588 1600-1-1 is 2,305,448 1500-1-1 is 2,268,933 0001-1-1 is 1,721,424 -0001-12-31 is 1,721,423 * -0006-1-1 is 1,719,232 -4713-1-1 is 0 */ public BigDate(double prolepticJulianDay) { setOrdinal((int) ((long) (prolepticJulianDay + 0.5/* * noon adjust, .5 or bigger really part of next day */) - 2440588L/* * i.e. diff in base epochs of calendars */)); }// end BigDate constructor /** * Ordinal constructor. The ordinal must be NULL_ORDINAL or in the range -365968798 to 364522971 i.e. 999,999 BC to * 999,999 AD * * @param ordinal days since 1970-01-01. */ public BigDate(int ordinal) { // save ordinal field and compute Gregorian equivalent set(ordinal); }// end BigDate constructor /** * Create a BigDate object from a String of the form: yyyy-mm-dd must have 4-digit years, and use dashes between the * number and no sign Does extensive checks considering leap years, missing days etc. * * @param yyyy_mm_dd string of form "yyyy-mm-dd". yyyy-mm-dd y-mm-ddBC 2009-12-31 1234-5-31AD 1970/3/3 AD 3.01.12 bc * 2000_12_31 It will not accept a date missing the year, month or day field. Spaces and commas are * ignored. */ public BigDate(String yyyy_mm_dd) { final int[] ymd = relaxedParse(yyyy_mm_dd); if (ymd == null) { throw new IllegalArgumentException("unparseable date: " + yyyy_mm_dd); } // will throw IllegalArgument Exception for an invalid date. set(ymd[0], ymd[1], ymd[2], CHECK); } /** * Copy constructor * * @param b an existing BigDate object to use as a model for cloning another. */ public BigDate(BigDate b) { ordinal = b.ordinal; yyyy = b.yyyy; mm = b.mm; dd = b.dd; } /** * Constructor from Date, loses time information. * * @param utc Date ( UTC date/time stamp ) * @param timeZone Which timeZone do you want to know the date for that UTC time. e.g. TimeZone.getDefault(), new * TimeZone("GMT") */ public BigDate(Date utc, TimeZone timeZone) { setDateAtTime(utc.getTime(), timeZone); } /** * Construct a BigDate object given a Gregorian date yyyy, mm, dd; always rejects invalid dates. A null date is * yyyy,mm,dd=0. BEWARE! In Java a lead 0 on an integer implies OCTAL. * * @param yyyy -999,999 (BC) to +999,999 (AD) * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @param dd day 1 to 31, no lead 0. * @throws IllegalArgumentException for invalid yyyy mm dd */ public BigDate(int yyyy, int mm, int dd) { set(yyyy, mm, dd, CHECK); }// end BigDate constructor /** * Construct a BigDate object given a Gregorian date yyyy, mm, dd; allows control of how invalid dates are handled. * A null date is yyyy,mm,dd=0. BEWARE! In Java a lead 0 on an integer implies OCTAL. * * @param yyyy -999,999 (BC) to +999,999 (AD) * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @param dd day 1 to 31, no lead 0. * @param how one of CHECK BYPASSCHECK NORMALIZE NORMALISE */ public BigDate(int yyyy, int mm, int dd, int how) { // save yyyy, mm, dd, and compete the ordinal equivalent set(yyyy, mm, dd, how); }// end BigDate constructor /** * increment this date by a number of days. * * @param days positive or negative, -1 gets day before this one. */ public final void addDays(int days) { if (days == 0) { return; } ordinal += days; toGregorian(); }// end addDays /** * Get day of week for this BigDate. It is one-based starting with Sunday. * * @return day of week 1=Sunday 2=Monday 3=Tuesday 4=Wednesday 5=Thursday 6=Friday 7=Saturday Compatible with Sun's * 1=Calendar.SUNDAY Not compatible with BigDate.getDayOfWeek. * @see #isoDayOfWeek * @see #dayOfWeek * @see #getCalendarDayOfWeek */ public int calendarDayOfWeek() { return calendarDayOfWeek(ordinal); } /** * Compare BigDates in ascending date order. Defines default the sort order for BigDate Objects. Compare this * BigDate with another BigDate. Compares ordinal. Informally, returns (this-other) or +ve if this is more positive * than other. * * @param other other BigDate to compare with this one * @return +ve if this>other, 0 if this==other, -ve if this<other */ public final int compareTo(BigDate other) { return ordinal - other.ordinal; } /** * Get day of week for this BigDate. Is it zero-based starting with Sunday. * * @return day of week 0=Sunday 1=Monday 2=Tuesday 3=Wednesday 4=Thursday 5=Friday 6=Saturday WARNING: not * compatible with 1=Calendar.SUNDAY * @see #calendarDayOfWeek * @see #isoDayOfWeek * @see #getDayOfWeek */ public int dayOfWeek() { return dayOfWeek(ordinal); } /** * Compares with another BigDate to see if they refer to the same date. * * @param d other BigDate to compare with this one. * @return true if BigDate d refers to the same date */ @Override public final boolean equals(Object d) { return d == this || d instanceof BigDate && (ordinal == ((BigDate) d).getOrdinal()); } /** * Get day of week for this BigDate. Is it one-based starting with Sunday. * * @return day of week 1=Sunday 2=Monday 3=Tuesday 4=Wednesday 5=Thursday 6=Friday 7=Saturday Compatible with Sun's * 1=Calendar.SUNDAY Not compatible with BigDate.getDayOfWee * @see #getISODayOfWeek * @see #getDayOfWeek * @see #calendarDayOfWeek */ public final int getCalendarDayOfWeek() { return calendarDayOfWeek(ordinal); } /** * get day of month for this BigDate. * * @return day 1 to 31, 0 for null date. */ public final int getDD() { return dd; } /** * Get day number in the year for this BigDate. * * @return day number Jan 01 = 1, 1 to 366 */ public final int getDDD() { return (ordinal == NULL_ORDINAL) ? 0 : (ordinal - jan01OfYear(yyyy) + 1); } /** * Get java.util.Date object corresponding to this BigDate, * * @param timeZone We consider this BigDate to have an implied time of 0:00 in this timeZone. * @return Date or null Result is undefined if BigDate is outside the range handled by Date. * @see #getLocalTimeStamp * @see #getUTCTimeStamp * @see #getTimeStamp * @see #getUTCDate * @see #getLocalDate */ public final java.util.Date getDate(TimeZone timeZone) { return ordinal == NULL_ORDINAL ? null : new java.util.Date(getTimeStamp(timeZone)); } /** * Get day of week for this BigDate. It is zero-based starting with Sunday. * * @return day of week 0=Sunday 1=Monday 2=Tuesday 3=Wednesday 4=Thursday 5=Friday 6=Saturday WARNING: not * compatible with 1=Calendar.SUNDAY * @see #getCalendarDayOfWeek * @see #getISODayOfWeek * @see #dayOfWeek */ public final int getDayOfWeek() { return dayOfWeek(ordinal); } /** * Get day of week 1 to 7 for this BigDate according to the ISO standard IS-8601. It is one-based starting with * Monday. * * @return day of week 1=Monday to 7=Sunday, 0 for null date. WARNING: not compatible with 1=Calendar.SUNDAY * @see #getCalendarDayOfWeek * @see #getDayOfWeek * @see #isoDayOfWeek */ public final int getISODayOfWeek() { return isoDayOfWeek(ordinal); } /** * Get week number 1 to 53 of the year this date falls in, according to the rules of ISO standard IS-8601. A week * that lies partly in one year and partly in another is assigned a number in the year in which most of its days * lie. This means that week 1 of any year is the week that contains 4 January, or equivalently week 1 of any year * is the week that contains the first Thursday in January. Most years have 52 weeks, but years that start on a * Thursday and leap years that start on a Wednesday have 53 weeks. Jan 1 may well be in week 53 of the previous * year! Only defined for dates on or after 1600 Jan 01. You can find out how many ISO weeks there are per year with * new BigDate( year, 12, 31).getISOWeekNumber(); * * @return week number 1..53, 0 for null or invalid date. * @see <a href="http://www.pip.dknet.dk/~pip10160/calendar.faq3.txt">Calendar FAQ</a> */ public final int getISOWeekNumber() { if (ordinal < Jan_01_Leap100RuleYear) { return 0; } int jan04Ordinal = jan01OfYear(yyyy) + 3; int jan04DayOfWeek = (jan04Ordinal + MondayIsZeroAdjustment - MIN_ORDINAL) % 7;// 0=Monday // 6=Sunday int week1StartOrdinal = jan04Ordinal - jan04DayOfWeek; if (ordinal < week1StartOrdinal) {// we are part of the previous year. Don't worry about year 0. jan04Ordinal = jan01OfYear(yyyy - 1) + 3; jan04DayOfWeek = (jan04Ordinal + MondayIsZeroAdjustment - MIN_ORDINAL) % 7;// 0=Monday // 6=Sunday week1StartOrdinal = jan04Ordinal - jan04DayOfWeek; } else if (mm == 12) {// see if we are part of next year. Don't worry about year 0. jan04Ordinal = jan01OfYear(yyyy + 1) + 3; jan04DayOfWeek = (jan04Ordinal + MondayIsZeroAdjustment - MIN_ORDINAL) % 7;// 0=Monday // 6=Sunday int week1StartNextOrdinal = jan04Ordinal - jan04DayOfWeek; if (ordinal >= week1StartNextOrdinal) { week1StartOrdinal = week1StartNextOrdinal; } } return ((ordinal - week1StartOrdinal) / 7) + 1; }// end getISOWeekNumber /** * Get java.util.Date object corresponding to this BigDate. We consider this BigDate to have an implied time of 0:00 * local time. * * @return Date or null Result is undefined if BigDate is outside the range handled by Date. * @see #getLocalTimeStamp */ public final java.util.Date getLocalDate() { return ordinal == NULL_ORDINAL ? null : new java.util.Date(getLocalTimeStamp()); } /** * Get milliseconds since 1970-01-01 00:00 GMT for this BigDate. Does not account for leap seconds primarily because * we do not know them in advance. N.B. returns long, not int as in many Unix implementations. This the long that a * Sun Date constructor wants. We consider this BigDate to have an implied time of 0:00 local time. * * @return milliseconds since 1970-01-01 00:00 GMT, or NULL_TIMESTAMP. This is NOT a JDBC Timestamp! You can use * this timestamp with java.util.Date.setTime, java.sql.TimeStamp.setTime or java.sql.Date.setTime or in the * constructors. To interconvert, just cast. * @see #getLocalDate */ public final long getLocalTimeStamp() { return getTimeStamp(TimeZone.getDefault()); } /** * Get month of year for this BigDate. * * @return month 1 to 12, 0 for null date. */ public final int getMM() { return mm; } /** * get days since 1970-01-01 for this BigDate. 1970-01-01 = day 0. * * @return days since 1970-01-01. This is NOT what you want for the java.util.Date constructor. * @see #getLocalTimeStamp * @see #getUTCTimeStamp * @see #getTimeStamp */ public final int getOrdinal() { return ordinal; } /** * @return Julian day number of noon of the date BigDate represents. See notes on the Julian Proleptic calendar * under the constructor. */ public final double getProlepticJulianDay() { if (ordinal == NULL_ORDINAL) { return Double.MIN_VALUE; } return ordinal + 2440588L/* diff in base epochs of calendars */ /* no need for noon adjust */; }// getProlepticJulianDay /** * Get season of year for this BigDate. * * @return 0=spring (Mar, Apr, May) 1=summer (Jun, Jul, Aug) 2=fall (Sep, Oct, Dec) 3=winter Dec, Jan, Feb */ public final int getSeason() { // 1 > 3, 2 > 3, 3 > 0, 4 > 0, 5 > 0, 6 > 1, 7 > 1, 8 > 1, 9 > 2, 10 > // 2, 11 > 2, 12 > 3 return ((mm + (12 - 3)) % 12) / 3; } /** * Get milliseconds since 1970-01-01 00:00 GMT for this BigDate, as at the start of day 0:00. Does not account for * leap seconds primarily because we do not know them in advance. N.B. returns long, not int as in many Unix * implementations. This the long that a Sun Date constructor wants. * * @param timeZone We consider this BigDate to have an implied time of 0:00 in this timeZone. * @return milliseconds since 1970-01-01 00:00 GMT, or NULL_TIMESTAMP. This is NOT a JDBC Timestamp! You can use this * timestamp with java.util.Date.setTime, java.sql.TimeStamp.setTime or java.sql.Date.setTime or in the * constructors. To interconvert, just cast. * @see #getDate * @see #getUTCTimeStamp * @see #getLocalTimeStamp */ public final long getTimeStamp(TimeZone timeZone) { if (ordinal == NULL_ORDINAL) { return NULL_TIMESTAMP; } /* * Gets the time zone offset, for current date, modified in case of daylight savings. This is the offset to add * *to* UTC to get local time. Places east of Greenwich have a positive offset. :era the era of the given date, * AD = 1 BC = 0 :year the year in the given date. Docs are unclear if 1900=0 or 1900. Does not use year in * calcs anyway. :month the month in the given date. Month is 0-based. e.g., 0 for January. :day the * day-in-month of the given date. :dayOfWeek the day-of-week of the given date. 1=Calendar.SUNDAY. * :milliseconds the millis in day in <em>standard</em> local time. :return the offset in millis to add *to* GMT * to get local time. */ int offsetInMillis = timeZone.getOffset(ordinal >= Jan_01_0001 ? 1 : 0, yyyy, mm - 1, dd, getCalendarDayOfWeek(), 0); // 86,400,000 = 1000 * 60 * 60 * 24 = milliseconds per day return ordinal * 86400000L - offsetInMillis; } /** * Get java.util.Date object corresponding to this BigDate. We consider this BigDate to have an implied time of 0:00 * UTC (Greenwich GMT). * * @return Date or null Result is undefined if BigDate is outside the range handled by Date. * @see #getUTCTimeStamp */ public final java.util.Date getUTCDate() { return ordinal == NULL_ORDINAL ? null : new java.util.Date(getUTCTimeStamp()); } /** * Get milliseconds since 1970-01-01 00:00 GMT for this BigDate. Does not account for leap seconds primarily because * we do not know them in advance. N.B. returns long, not int as in many Unix implementations. This the long that a * Sun Date constructor wants. We consider this BigDate to have an implied time of 0:00 UTC (Greenwich GMT). * * @return milliseconds since 1970-01-01 00:00 GMT, or NULL_TIMESTAMP. This is NOT a JDBC Timestamp! You can use * this timestamp with java.util.Date.setTime, java.sql.TimeStamp.setTime or java.sql.Date.setTime or in the * constructors. To interconvert, just cast. * @see #getUTCDate */ public final long getUTCTimeStamp() { // 86,400,000 = 1000 * 60 * 60 * 24 = milliseconds per day return ordinal == NULL_ORDINAL ? NULL_TIMESTAMP : (ordinal * 86400000L); } /** * Get week number 1 to 53 of the year this date falls in. This does NOT follow the rules of ISO standard IS-8601 * section 5.5. Week 1 is the first week with any days in the current year. Weeks start on Sunday. Jan 1 and Dec 31 * are always considered part of the current year. Only defined for dates on or after 1600 Jan 01. * * @return week number 1..53, 0 for null or invalid date. * @see #getISOWeekNumber */ public final int getWeekNumber() { if (ordinal < Jan_01_Leap100RuleYear) { return 0; } int jan01 = jan01OfYear(yyyy); int jan01DayOfWeek = dayOfWeek(jan01);// 0=Sunday 1=Monday // 2=Tuesday 3=Wednesday // 4=Thursday 5=Friday // 6=Saturday int sundayOnOrBeforeJan01Ordinal = jan01 - jan01DayOfWeek; return ((ordinal - sundayOnOrBeforeJan01Ordinal) / 7) + 1; }// end getWeekNumber /** * Get year for this BigDate. * * @return year -999,999 to 999,999. 0 for null date. negative is BC, positive AD. */ public final int getYYYY() { return yyyy; } /** * hashCode for use in Hashtable lookup * * @return the ordinal which is perfectly unique for the date. */ @Override public final int hashCode() { return ordinal; } /** * Find the BigDate with date closest to this one with the given day of week. * * @param dayOfWeek 0=Sunday 1=Monday 2=Tuesday 3=Wednesday 4=Thursday 5=Friday 6=Saturday WARNING: not compatible * with 1=Calendar.SUNDAY. * @return BigDate object with nearest date to this one with the given day of week. */ public BigDate nearestXXXDay(int dayOfWeek) { // positive or negative difference -6..0..+6 int diff = dayOfWeek - this.dayOfWeek(); if (diff >= 4) { diff -= 7; } else if (diff <= -4) { diff += 7; } return new BigDate(ordinal + diff); } /** * Set the ordinal field, and compute the equivalent internal Gregorian yyyy mm dd fields. alias setOrdinal. * * @param ordinal days since 1970-01-01. */ public final void set(int ordinal) { if (this.ordinal == ordinal) { return; } this.ordinal = ordinal; toGregorian(); }// end set /** * Set the yyyy mm dd Gregorian fields, and compute the internal ordinal equivalent. yyyy mm dd are checked for * validity. * * @param yyyy -999,999 (BC) to +999,999 (AD) * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @param dd day 1 to 31, no lead 0. */ public final void set(int yyyy, int mm, int dd) { set(yyyy, mm, dd, CHECK); } /** * Set the Gregorian fields, and compute the ordinal equivalent with the same modifiers CHECK, NORMALIZE, * BYPASSCHECK as the constructor. BEWARE! In Java a lead 0 on an integer implies OCTAL. * * @param yyyy -999,999 (BC) to +999,999 (AD) * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @param dd day 1 to 31, no lead 0. * @param how one of CHECK BYPASSCHECK NORMALIZE NORMALISE * @see #CHECK * @see #BYPASSCHECK * @see #NORMALIZE * @see #NORMALISE */ public final void set(int yyyy, int mm, int dd, int how) { if (this.yyyy == yyyy && this.mm == mm && this.dd == dd) { return; } this.yyyy = yyyy; this.mm = mm; this.dd = dd; switch (how) { case CHECK: if (!isValid(yyyy, mm, dd)) { throw new IllegalArgumentException("invalid date: " + yyyy + "/" + mm + "/" + dd); } break; case NORMALISE: normalise(); break; case BYPASSCHECK: break; }// end switch toOrdinal(); }// end set /** * Sets the date that corresponding to a given utc timestamp at a given TimeZone. * * @param utcTimestamp milliseconds since 1970 in UTC time. E.g. Date.getTime * @param timeZone Timezone you want to know the date in at that time. TimeZone.getDefault() TimeZone.getU */ public void setDateAtTime(long utcTimestamp, TimeZone timeZone) { // get date approximately right. setOrdinal((int) (utcTimestamp / 86400000L)); /* * Gets the time zone offset, for current date, modified in case of daylight savings. This is the offset to add * *to* UTC to get local time. Places east of Greenwich have a positive offset. :era the era of the given date, * AD = 1 BC = 0 :year the year in the given date. Docs are unclear if 1900=0 or 1900. Does not use year in * calcs anyway. :month the month in the given date. Month is 0-based. e.g., 0 for January. :day the * day-in-month of the given date. :dayOfWeek the day-of-week of the given date. 1=Calendar.SUNDAY :milliseconds * the millis in day in <em>standard</em> local time. :return the offset in millis to add *to* GMT to get local * time. */ int offsetInMillis = timeZone.getOffset(getOrdinal() >= Jan_01_0001 ? 1 : 0, getYYYY(), getMM() - 1, getDD(), getCalendarDayOfWeek(), (int) (utcTimestamp % 86400000L) /* wrong! Should be local standard time. How to get it?? */); // do a correction setOrdinal((int) ((utcTimestamp + offsetInMillis) / 86400000L)); } /** * Set the ordinal field, and compute the equivalent internal Gregorian yyyy mm dd fields. alias set. * * @param ordinal days since 1970-01-01. */ public final void setOrdinal(int ordinal) { if (this.ordinal == ordinal) { return; } this.ordinal = ordinal; toGregorian(); }// end setOrdinal /** * Convert date to a human-readable String [wed mm/dd/yy] * * @return this BigDate as a String in form fri 12/31/03 */ public String toDowMMDDYY() { if (ordinal == NULL_ORDINAL) { return ""; } return dayAbbr[getDayOfWeek()] + " " + StringTools.toLZ(mm, 2) + "/" + StringTools.toLZ(dd, 2) + "/" + StringTools.toLZ(yyyy % 100, 2); }// end toDowMMDDYY /** * Convert date to a human-readable String. Handles BC with trailing _BC. * * @return this BigDate as a String in form YYYY-MM-DD ISO 8601:1988 international standard format. NOT final so you * can override to suit yourself. */ @Override public String toString() { if (ordinal == NULL_ORDINAL) { return ""; } StringBuffer sb = new StringBuffer(13); sb.append(Integer.toString(Math.abs(yyyy))); sb.append("-"); sb.append(StringTools.toLZ(mm, 2)); sb.append("-"); sb.append(StringTools.toLZ(dd, 2)); if (yyyy < 1000) { if (yyyy < 0) { sb.append(" BC"); } else { // 0 .. 999 sb.append(" AD"); } } return sb.toString(); }// end toString /** * Convert just year part of date to a human-readable String. Handles BC with trailing BC. * * @return this BigDate as a String in form YYYYBC or yyyyy */ public String toYYYYString() { if (ordinal == NULL_ORDINAL) { return ""; } StringBuffer sb = new StringBuffer(7); sb.append(Integer.toString(Math.abs(yyyy))); if (yyyy < 1000) { if (yyyy < 0) { sb.append(" BC"); } else { // 0 .. 999 sb.append(" AD"); } } return sb.toString(); }// end toYYYYString // -------------------------- STATIC METHODS -------------------------- static { /* * Initialisation order is tricky so we collect all the dependencies here. This way no matter how fields are * ordered, initialisation still works. */ NORMALIZE = NORMALISE; OJC_lastYYYY = isBritish ? 1752 : 1582; OJC_lastMM = isBritish ? 9 : 10; OJC_lastDD = isBritish ? 2 : 4; GC_firstYYYY = isBritish ? 1752 : 1582; GC_firstMM = isBritish ? 9 : 10; GC_firstDD = isBritish ? 14 : 15; Leap100RuleYYYY = ((GC_firstYYYY + 99) / 100) * 100; Leap400RuleYYYY = ((GC_firstYYYY + 399) / 400) * 400; missingDays = GC_firstDD - OJC_lastDD - 1; AD_epochAdjustment = -719529; BC_epochAdjustment = AD_epochAdjustment + 365; MIN_ORDINAL = BigDate.toOrdinal(MIN_YEAR, 1, 1); Jan_01_0001BC = BigDate.toOrdinal(-1, 1, 1); Jan_01_0001 = BigDate.toOrdinal(1, 1, 1); Jan_01_0004 = BigDate.toOrdinal(4, 1, 1); GC_firstOrdinal = BigDate.toOrdinal(GC_firstYYYY, GC_firstMM, GC_firstDD); GC_firstDec_31 = BigDate.toOrdinal(GC_firstYYYY, 12, 31); Jan_01_Leap100RuleYear = BigDate.toOrdinal(Leap100RuleYYYY, 1, 1); Jan_01_Leap400RuleYear = BigDate.toOrdinal(Leap400RuleYYYY, 1, 1); MAX_ORDINAL = BigDate.toOrdinal(MAX_YEAR, 12, 31); } static { // <clinit> static initialisation code for usual_dddToMMTable usual_dddToMMTable = new int[365]; int dddi = 0; for (int mmi = 1; mmi <= 12; mmi++) { int last = daysInMonth(mmi, false); for (int ddi = 0; ddi < last; ddi++) { usual_dddToMMTable[dddi++] = mmi; }// end for dd }// end for mmm }// end static init static { // <clinit> static initialisation code for leap_dddToMMTable leap_dddToMMTable = new int[366]; int dddi = 0; for (int mmi = 1; mmi <= 12; mmi++) { int last = daysInMonth(mmi, true); for (int ddi = 0; ddi < last; ddi++) { leap_dddToMMTable[dddi++] = mmi; } } }// end static init // P R O T E C T E D M E T H O D S /** * How many days were there in the year prior to the first day of the given month? * * @param mm month 1 to 12 (not 0 to 11 as in Sun's Date), no lead 0. * @param leap true if you are interested in a leap year. * @return how many days in year prior to the start of that month. */ protected static int daysInYearPriorToMonth(int mm, boolean leap) { return leap ? leap_daysInYearPriorToMonthTable[mm - 1] : usual_daysInYearPriorToMonthTable[mm - 1]; }// end daysPriorToMonth /** * Convert day number ddd in year to month. * * @param ddd day number in year Jan 01 = 1, 1 to 366. * @param leap true if year of interest is boolean. * @return month that day number would fall in. */ protected static int dddToMM(int ddd, boolean leap) { return leap ? leap_dddToMMTable[ddd - 1] : usual_dddToMMTable[ddd - 1]; } /** * Ordinal date of Jan 01 of the given year. * * @param yyyy year of interest * @return ordinal of Jan 01 of that year. */ protected static int jan01OfYear(int yyyy) { int leapAdjustment; if (yyyy < 0) { // years -1 -5 -9 were leap years. // adjustment -1->1 -2->1 -3->1 -4->1 -5->2 -6->2 leapAdjustment = (3 - yyyy) / 4; return (yyyy * 365) - leapAdjustment + BC_epochAdjustment; } // years 4 8 12 were leap years // adjustment 1->0 2->0, 3->0, 4->0, 5->1, 6->1, 7->1, 8->1, 9->2 leapAdjustment = (yyyy - 1) / 4; int missingDayAdjust = (yyyy > GC_firstYYYY) ? missingDays : 0; // mod 100 and mod 400 rules started in 1600 for Gregorian, // but 1800/2000 in the British scheme if (yyyy > Leap100RuleYYYY) { leapAdjustment -= (yyyy - Leap100RuleYYYY + 99) / 100; } if (yyyy > Leap400RuleYYYY) { leapAdjustment += (yyyy - Leap400RuleYYYY + 399) / 400; } return yyyy * 365 + leapAdjustment - missingDayAdjust + AD_epochAdjustment; }// end jan01OfYear /** * parse a string into yyyy mm dd, where date may have form: yyyy-mm-dd y-mm-ddBC 2009-12-31 1234-5-31AD 1970/3/3 AD * 3.01.12 bc 2000_12_31 It will not accept a date missing the year, month or day field. Spaces and commas are * ignored. It does does not check leap year validity, max days in month. * * @param yyyy_mm_dd date string. * @return array[] yyyy, mm, dd, with yyyy negative for BC. null for unparseable date. */ private static int[] relaxedParse(final String yyyy_mm_dd) { try { // A regex would be lot simpler, but we can't use it since we are constrained to JDK 1.1 // extract the year final StringBuffer yearSB = new StringBuffer(6); final int yyyy_mm_dd_length = yyyy_mm_dd.length(); String mm_dd = ""; extractYearLoop: for (int i = 0; i < yyyy_mm_dd_length; i++) { char c = yyyy_mm_dd.charAt(i); switch (c) { case '-': case '.': case '/': case '_': // hit field terminator mm_dd = yyyy_mm_dd.substring(i + 1); break extractYearLoop; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': yearSB.append(c); break; case ' ': case ',': // ignore spaces and commas break; default: // hit something that should not be there return null; } } if (!(1 <= yearSB.length() && yearSB.length() <= 6)) { return null; } final int yyyy = Integer.parseInt(yearSB.toString()); // extract the month final StringBuffer monthSB = new StringBuffer(2); final int mm_dd_length = mm_dd.length(); String dd_ad = ""; extractMonthLoop: for (int i = 0; i < mm_dd_length; i++) { char c = mm_dd.charAt(i); switch (c) { case '-': case '.': case '/': case '_': // hit field terminator dd_ad = mm_dd.substring(i + 1); break extractMonthLoop; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': monthSB.append(c); break; case ' ': case ',': // ignore spaces and commas break; default: // hit something that should not be there return null; } } if (!(1 <= monthSB.length() && monthSB.length() <= 2)) { return null; } final int mm = Integer.parseInt(monthSB.toString()); // extract the day final StringBuffer daySB = new StringBuffer(2); final int dd_ad_length = dd_ad.length(); String ad = ""; extractDayLoop: for (int i = 0; i < dd_ad_length; i++) { char c = dd_ad.charAt(i); switch (c) { case 'a': case 'A': case 'b': case 'B': // hit AD/BC ad = dd_ad.substring(i); break extractDayLoop; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': daySB.append(c); break; case ' ': case ',': // ignore spaces and commas break; default: // hit something that should not be there return null; } } if (!(1 <= daySB.length() && daySB.length() <= 2)) { return null; } final int dd = Integer.parseInt(daySB.toString()); if (!(1 <= dd && dd <= 31)) { return null; } // extract the ad/bc final StringBuffer adSB = new StringBuffer(2); final int ad_length = ad.length(); // extract AD loop for (int i = 0; i < ad_length; i++) { char c = ad.charAt(i); switch (c) { case 'A': case 'B': case 'C': case 'D': // AD BC adSB.append(c); break; case 'a': case 'b': case 'c': case 'd': // ad bc convert to AD BC adSB.append(Character.toUpperCase(c)); break; case ' ': case ',': // ignore spaces and commas break; default: // hit something that should not be there return null; } } final String adString = adSB.toString(); if (adString.length() == 0 || adString.equals("AD")) { return new int[] { yyyy, mm, dd }; } else if (adString.equals("BC")) { return new int[] { -yyyy, mm, dd }; } else { return null; } } catch (NumberFormatException e) { // should never happen since we have already filtered out any junk return null; } } // -------------------------- OTHER METHODS -------------------------- /** * Clean up an invalid date, leaving the results internally e.g. 1954-09-31 -> 1954-10-01. 1954-10-(minus 1) -> * 1954-09-30 1954-13-01 -> 1955-01-01. This lets you do year, month or day arithmetic. normalise does not recompute * the ordinal. */ protected final void normalise() { // yyyy, mm, dd must be set at this point if (isValid(yyyy, mm, dd)) { return; } else if (mm > 12) { yyyy += (mm - 1) / 12; mm = ((mm - 1) % 12) + 1; if (isValid(yyyy, mm, dd)) { return; } } else if (mm <= 0) { // Java's definition of modulus means we need to handle negatives as // a special case. yyyy -= -mm / 12 + 1; mm = 12 - (-mm % 12); if (isValid(yyyy, mm, dd)) { return; } } if (isValid(yyyy, mm, 1)) { int olddd = dd; dd = 1; toOrdinal(); ordinal += olddd - 1; toGregorian(); if (isValid(yyyy, mm, dd)) { return; } } throw new IllegalArgumentException("date cannot be normalised: " + yyyy + "/" + mm + "/" + dd); }// end normalise /** * read back a serialized BigDate object and reconstruct the missing transient fields. readObject leaves results in * this. It does not create a new object. * * @param s stream to read from. * @throws IOException if can't read object * @throws ClassNotFoundException if unexpected class in the stream. */ private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { s.defaultReadObject(); try { toGregorian();// restore transient fields } catch (IllegalArgumentException e) { throw new java.io.IOException("bad serialized BigDate"); } }// end readObject /** * converts ordinal to YYYY MM DD, leaving results internally. */ protected final void toGregorian() { // ordinal must be set at this point. // handle the null date as a special case if (ordinal == NULL_ORDINAL) { yyyy = 0; mm = 0; dd = 0; return; } if (ordinal > MAX_ORDINAL) { throw new IllegalArgumentException("invalid ordinal date: " + ordinal); } else if (ordinal >= GC_firstOrdinal) { yyyy = Leap400RuleYYYY + flooredMulDiv(ordinal - Jan_01_Leap400RuleYear, 10000, 3652425);/* * 365 + 0.25 - * 0.01 - 0.0025 */ /* * division may be done on a negative number. That's ok. We don't need to mess with the 100RuleYear. The * 400RuleYear handles it all. */ } else if (ordinal >= Jan_01_0001) { // Jan_01_0001 to Oct_04_1582 // year 4 was first AD leap year. yyyy = 4 + flooredMulDiv(ordinal - Jan_01_0004, 100, 36525); /* 365 + 0.25 */ } else if (ordinal >= MIN_ORDINAL) { // LowestDate to Dec_31_0001BC // -1 was first BC leap year. // dividend will be negative yyyy = -1 + flooredMulDiv(ordinal - Jan_01_0001BC, 100, 36525); /* 365 + 0.25 */ } else { throw new IllegalArgumentException("invalid ordinal date: " + ordinal); } int aim = ordinal + 1; // Oct_15_1582 <= ordinal && ordinal <= Dec_31_1582 if (GC_firstOrdinal <= ordinal && ordinal <= GC_firstDec_31) { aim += missingDays; } // ddd should be 1..366 int ddd = aim - jan01OfYear(yyyy); while (ddd <= 0) { // our approximation was too high yyyy--; ddd = aim - jan01OfYear(yyyy); } boolean leap = isLeap(yyyy); while (ddd > (leap ? 366 : 365)) { // our approximation was too low yyyy++; ddd = aim - jan01OfYear(yyyy); leap = isLeap(yyyy); } mm = dddToMM(ddd, leap); dd = ddd - daysInYearPriorToMonth(mm, leap); // at this point yyyy, mm and dd have been computed. }// end toGregorian /** * Convert date in form YYYY MM DD into days since the epoch, leaving results internally. * * @see #getOrdinal() */ protected final void toOrdinal() { ordinal = toOrdinal(yyyy, mm, dd); }// end toOrdinal /** * Serialize and write the BigDate object. Only the ordinal will be written. Other fields are transient. * * @param s stream to write to * @throws IOException standard */ private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); }// end writeObject }