/*****************************************************************************\
* DateOps.java
\*****************************************************************************/
package com.mhuss.AstroLib;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.SimpleTimeZone;
//import com.mhuss.Util.DateU;
//---------------------------------------------------------------------------
/**
* A support class for DateOps
*/
class DateConversionData {
/**
* Gregorian/Julian calendar values
*/
static final int sMonthDays[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0 };
/**
* Number of entries in MonthDays table
*/
public static final int MONTH_DAYS = 13;
/**
* Number of days in each month
*/
int monthDays[];
/**
* Number of days to end of year
*/
long yearEndDays;
/**
* Number of days to end of following year
*/
long nextYearEndDays;
int year;
/**
* Constructor
*
* @param y year
*/
DateConversionData(int y) {
monthDays = new int[MONTH_DAYS];
for (int i=0; i
* Note that the Julian Day Number (JD), widely used in astronomical
* calculations, is different from and not directly related to the
* Julian Calendar discussed below.
*
* Based on code by Bill Gray (www.projectpluto.com)
*
* Bill Gray's Comments:
* General calendrical comments:
*
* This code supports conversions between JD and two calendrical
* systems: Julian and Gregorian. Comments pertaining to specific
* calendars are found near the code for those calendars.
*
* [mhuss: The original C code, which these comments are paraphrased
* from, supported other calendars - these may be supported here in
* the future, but for now we're pretty Gregorian.]
*
* For each calendar, there is a (cal)DateConversionData" class,
* used only within this code module. This class takes a particular
* year number, and computes the JD corresponding to "new years day"
* (first day of the first month) in that calendar in that year. It
* also figures out the number of days in each month of that year,
* returned in the array month_data[]. There can be up to 13 months,
* because the Hebrew calendar (and some others that may someday be
* added) can include an "intercalary month". If a month doesn't
* exist, then the month_data[] entry for it will be zero (thus, in
* the Gregorian and Julian and Islamic calendars, month_data[12] is
* always zero, since these calendars have only 12 months.)
*
* [mhuss: The code here only supports Gregorian and Julian, so the
* class is just called DateConversionData. When I get
* around to doing the rest, DateConversionData will be
* the interface or base class used by all.]
*
* The next level up is the getCalendarData() function, which
* can get the JD of New Years Day and the array of months for any given
* year for any (supported) calendar. Above this point, all calendars
* can be treated in a common way; one is shielded from the oddities
* of individual calendrical systems.
*
* Finally, at the top level, we reach the only functionality that is
* exported for the rest of the world to use: dmyToDay() and
* dayToDmy(). The first (and its variations) takes a day,
* month, year, and optionally, a calendar system. It calls
* getCalendarData() for the given year, adds in the days in
* the months intervening New Years Day and the desired month, adds
* in the day of the month, and returns the resulting Julian Day.
*
* dayToDmy() reverses this process. It finds an "approximate" year
* corresponding to an input JD, and calls getCalendarData()
* for that year. By adding all the monthData[] values for that year,
* it can also find the JD for the end of that year; if the
* input JD is outside that range, it may have to back up a year or
* add in a year. Once it finds the year where
* "JD of New Years Day < JD < JD of New Years Eve",
* it's a simple matter to track down which month and day of the month
* corresponds to the input JD.
*/
public class DateOps {
/**
* Abbreviated month names
*/
final String monthNames[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
/**
* Pseudo-enum for calendar type
*/
public static final int GREGORIAN = 0, JULIAN = 1;
/**
* Converts a day/month/year to a Julian day in long form.
*
* @param day Day of the month (1..31)
* @param month Month of the Year (1..12)
* @param year Year
* @param calendar GREGORIAN or JULIAN
*
* @return The corresponding Julian day number
*/
public static long dmyToDay(
int day, int month, int year, int calendar )
{
/*
* Bill Gray's Comments:
* This function gets calendar data for the current year,
* including the Julian day of New Years Day for that year. After
* that, all it has to do is add up the days in intervening months,
* plus the day of the month, and it's done.
*/
DateConversionData dcd = new DateConversionData( year );
long jd = 0;
if( getCalendarData( dcd, calendar ) ) {
jd = dcd.yearEndDays;
for( int i=0; i<(month-1); i++ ) {
jd += dcd.monthDays[i];
}
jd += (long)(day - 1);
}
return jd;
}
/**
* Converts a GREGORIAN day/month/year to a Julian day in long
* form.
*
* @param day Day of the month (1..31)
* @param month Month of the Year (1..12)
* @param year Year
*
* @return The corresponding Julian day number
*/
public static long dmyToDay( int day, int month, int year ) {
return dmyToDay( day, month, year, GREGORIAN );
}
/**
* Converts an AstroDate to a Julian day in long form.
*
* This function uses the GREGORIAN calendar.
*
* @param d AstroDate to convert
*
* @return The corresponding Julian day number
*/
public static long dmyToDay( AstroDate d ) {
return dmyToDay( d.day, d.month, d.year, GREGORIAN );
}
/**
* Converts a day/month/year to a Julian day in double form.
*
* This function uses the GREGORIAN calendar.
*
* @param d AstroDate to convert
*
* @return The corresponding Julian day number
*/
public static double dmyToDoubleDay( AstroDate d ) {
return d.second / Astro.SECONDS_PER_DAY - .5 +
dmyToDay( d.day, d.month, d.year, GREGORIAN );
}
/**
* Converts a Julian day to an AstroDate.
*
* @param jd Julian day to convert
* @param d AstroDate to put the results into
* @param calendar GREGORIAN or JULIAN
*/
static void dayToDmy( long jd, AstroDate d, int calendar )
{
/*
* Bill Gray's Comments:
* Estimates the year corresponding to an input JD and calls
* getCalendarData() for that year. Occasionally, it will find
* that the guesstimate was off; in such cases, it moves ahead
* or back a year and tries again. Once it's done,
* jd - yearEndDays gives the number of days since New Years Day;
* by subtracting monthDays[] values, we quickly determine which
* month and day of month we're in.
*/
d.day = -1; /* to signal an error */
switch( calendar) {
case GREGORIAN:
case JULIAN:
d.year = (int)((jd - E_JULIAN_GREGORIAN) / 365L);
break;
default: /* undefined calendar */
return;
} // end switch()
DateConversionData dcd = new DateConversionData( d.year );
do {
if( !getCalendarData( dcd, calendar ) )
return;
if( dcd.yearEndDays > jd)
d.year--;
if( dcd.nextYearEndDays <= jd)
d.year++;
dcd.year = d.year;
} while( dcd.yearEndDays > jd || dcd.nextYearEndDays <= jd );
long currJd = dcd.yearEndDays;
d.month = -1;
for( int i = 0; i < DateConversionData.MONTH_DAYS; i++) {
d.day = (int)( jd - currJd );
if( d.day < dcd.monthDays[i] ) {
d.month = i + 1;
d.day++;
return;
}
currJd += (long)( dcd.monthDays[i] );
}
return;
}
/**
* Converts a Julian day in long form to a GREGORIAN
* AstroDate.
*
* @param jd Julian day to convert
* @param d AstroDate to insert results into
*/
static void dayToDmy( long jd, AstroDate d ) {
dayToDmy( jd, d, GREGORIAN );
}
/**
* Converts a Julian day in double form to an AstroDate.
*
* This function has higher precision than the long version.
*
* @param jd Julian day to convert
* @param d AstroDate to insert results into
* @param calendar GREGORIAN or JULIAN
*/
static void dayToDmy( double jd, AstroDate d, int calendar ) {
dayToDmy( (long)jd, d, calendar );
d.second = (jd - Math.floor(jd)) * Astro.SECONDS_PER_DAY;
}
/**
* Converts a Julian day in double form to a GREGORIAN
* AstroDate.
* This function has higher precision than the long version.
*
* @param jd Julian day to convert
* @param d AstroDate to insert results into
*/
static void dayToDmy( double jd, AstroDate d ) {
dayToDmy( jd, d, GREGORIAN );
}
//--------------------------------------------------------------------------
/**
* Convert a Java Calendar to Julian day value in
* long form.
*
* @param cal java.util.Calendar to convert
*
* @return The corresponding Julian day
*/
public static long calendarToDay( Calendar cal ) {
return dmyToDay(
cal.get(Calendar.DATE),
cal.get(Calendar.MONTH)+1, // Calendar has 0-based months (!)
cal.get(Calendar.YEAR)
);
}
/**
* Convert a Java Calendar to a Julian day value in double
* form.
*
* @param cal java.util.Calendar to convert
*
* @return The corresponding Julian day
*/
public static double calendarToDoubleDay( Calendar cal ) {
double hours = (double)cal.get(Calendar.HOUR_OF_DAY)
+ cal.get(Calendar.MINUTE) / Astro.MINUTES_PER_HOUR
+ cal.get(Calendar.SECOND) / (double)Astro.SECONDS_PER_HOUR
- TimeOps.tzOffset(cal);
return (double) calendarToDay( cal ) + hours/Astro.HOURS_PER_DAY - 0.5;
}
/**
* Calculate the current Julian day value in double
* form.
*
* @return The corresponding Julian day
*/
public static double nowToDoubleDay() {
return calendarToDoubleDay( new GregorianCalendar() );
}
/**
* Calculate the current Julian day value in long
* form.
*
* @return The corresponding Julian day
*/
public static long nowToDay() {
return calendarToDay( new GregorianCalendar() );
}
/**
* Calculate the Julian day number for the date when Daylight time
* starts in a given year.
*
* The chosen start day is the first Sunday in April, and therefore
* this function may not be applicable outside the U.S.
*
* @param year The year of interest
*
* @return The corresponding Julian day
*/
public static long dstStart(int year)
{
// first Sunday in April
long jdStart = dmyToDay( 1, 4, year );
while ( 6 != (jdStart % 7 ) ) // Sunday
jdStart++;
return jdStart;
}
/**
* Calculate the Julian day number for the date when Daylight time
* ends in a given year.
*
* This chosen day is the last Sunday in October, and therefore
* this function may not be applicable outside the U.S.
*
* @param year The year of interest
*
* @return The corresponding Julian day
*/
public static long dstEnd(int year)
{
// last Sunday in October
long jdEnd = dmyToDay( 31, 10, year );
while ( 6 != (jdEnd % 7 ) ) // Sunday
jdEnd--;
return jdEnd;
}
//-------------------------------------------------------------------------
private static final long E_JULIAN_GREGORIAN = 1721060L;
/**
* Gregorian and Julian calendar calculations (combined for simplicity)
*
* Bill Gray's Comments:
* It's common to implement Gregorian/Julian calendar code with the
* aid of cryptic formulae, rather than through simple lookup tables.
* For example, consider this formula from Fliegel and Van Flandern,
* to convert Gregorian (D)ay, (M)onth, (Y)ear to JD:
*
*
* JD = (1461*(Y+4800+(M-14)/12))/4+(367*(M-2-12*((M-14)/12)))/12
* -(3*((Y+4900+(M-14)/12)/100))/4+D-32075
*
*
* The only way to verify that they work is to feed through all
* possible cases. Personally, I like to be able to look at a chunk
* of code and see what it means. It should resemble the Reformation
* view of the Bible: anyone can read it and witness the truth thereof.
*
* @param dcd The DateConversionData to use
* @param julian true for Julian calendar, else Gregorian
*/
private static void getJulGregYearData(
DateConversionData dcd, boolean julian )
{
if( dcd.year >= 0 ) {
dcd.yearEndDays = dcd.year * 365 + dcd.year / 4;
if( !julian )
dcd.yearEndDays += -dcd.year / 100L + dcd.year / 400L;
}
else {
dcd.yearEndDays = (dcd.year * 365) + (dcd.year - 3) / 4;
if( !julian )
dcd.yearEndDays += -(dcd.year - 99) / 100 + (dcd.year - 399) / 400;
}
if( julian )
dcd.yearEndDays -= 2;
if( 0 == (dcd.year % 4)) {
if( 0 !=(dcd.year % 100) || 0 == (dcd.year % 400) || julian ) {
dcd.monthDays[1] = 29;
dcd.yearEndDays--;
}
}
dcd.yearEndDays += E_JULIAN_GREGORIAN + 1;
}
//-------------------------------------------------------------------------
/**
* @param dcd The DateConversionData to use
* @param julian true for Julian calendar, else Gregorian
*
* @return true if successful, false otherwise.
*/
private static boolean getCalendarData(
DateConversionData dcd, int calendar )
{
boolean isOk = true;
//dcd.monthDays[0] = 0;
switch( calendar)
{
case GREGORIAN:
case JULIAN:
getJulGregYearData( dcd, (JULIAN == calendar) );
break;
default:
isOk = false;
break;
}
if ( isOk ) {
//
// days[1] = JD of "New Years Eve" + 1; that is, New Years Day of the
// following year. If you have days[0] <= JD < days[1], JD is in the
// current year.
//
dcd.nextYearEndDays = dcd.yearEndDays;
for( int i=0; i
Ads help maintain this website.