/*
 ****************************************************************************
    JavaScript sunrise/sunset calculator
    Author: Jeff Conrad
    Date: 19 October 1998
    Revision History:
	1.9 12 Feb 2005  Updated magnetic declinations
	1.8 17 Jun 2004  Simplified Maxalt(); changed to include
			 refraction; revised date display; revised day
			 length calculation; updated magnetic
			 declinations; fixed DST w/southern latitudes
	1.7 14 Jun 2003  Increased MAX_CALC from 53 to 366 days
	1.6 14 Jun 2003  Updated magnetic declinations
	1.5 22 Oct 2002  Updated magnetic declinations
	1.4 19 Oct 2000  Removed timer
        1.3  2 Aug 2000  Revised structure; added Sun position
	1.2 27 Jul 2000  Added location database, help file, display of
			 azimuths in true or magnetic north
 ****************************************************************************
    Copyright 1998-2004 Jeff Conrad
 ****************************************************************************
*/

var
	// constants
    F_CIRCUMPOLAR = 9999,
    F0_NO_PHENOM = 999,			// no phenomenon--set value
    F_NO_PHENOM = 99,			// no phenomenon--test value

    D_to_R = Math.PI / 180,		// degrees to radians
    D_to_MSEC = 24 * 3600 * 1e3,	// days to msec

	// indices to places array
    NAME = 0,
    LAT = 1,
    LONG = 2,
    TZ = 3,
    DST = 4,				// uses DST? yes=1, no=0
    MAGDEC = 5,
	// calculation type
    TIMES = 0,
    POSITION = 1;

	// HTML tags
var
    VSPACE = "<br><br>",
    SP = "&nbsp;",
    SP2 = SP + SP,
    SP4 = SP2 + SP2,
    PAD4 = "    ",
    PAD8 = PAD4 + PAD4,
    BR = "<br>",
    B = "<b>",
    BE = "</b>",
    EM = "<em>",
    EME = "</em>",
    HT = "\t",
    LF = "\n",

    TABLE_START = "<table width=80% cols=11 border=3 cellspacing=0 cellpadding=5>";
    TABLE_START2 = "<table width=40% cols=4 border=3 cellspacing=0 cellpadding=5>";
    TABLE_END = "</table>";
    THD = "<thead>",
    THDE = "</thead>",
    TBD = "<tbody>",
    TBDE = "</tbody>",
    TR = PAD4 + "<tr>",
    TR_R = PAD4 + '<tr align="right">',
    TRE = PAD4 + "</tr>",

    TH11 = PAD8 + "<th>",
    TH21 = PAD8 + "<th rowspan=2>",
    TH12 = PAD8 + "<th colspan=2>",
    TH8 = PAD8 + "<th colspan=8>",
    THE = "</th>",

    TD = PAD8 + "<td>",
    TD_C = PAD8 + '<td align="center">',
    TD_L = PAD8 + '<td align="left">',
    TD_N = PAD8 + '<td align="left" nowrap>',
    TDE = "</td>";

    // HTML 4 named character entities
if (navigator.appName == "Netscape")
    var MINUS = "&#8211;";	// fake with en dash
else
    var MINUS = "&#8722;";

var MDASH = "&#8212;";

var day_of_week = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ];

var months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
	       "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
];

var monthdays = [
    // 1   2   3   4   5   6   7   8   9  10  11  12
    [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ],
    [ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
];

    // magnetic declinations as of 12 February 2005
var places = [
    [ "invalid" ],
    [ "Alturas, CA", "41:29:14", "120:32:28", 8, 1, 15.55 ],
    [ "Baker, CA", "35:15:54", "116:04:25", 8, 1, 13.02 ],
    [ "Bakersfield, CA", "35:22:24", "119:01:05", 8, 1, 13.66 ],
    [ "Barstow, CA", "34:53:55", "117:01:19", 8, 1, 13.15 ],
    [ "Bishop, CA", "37:21:49", "118:23:38", 8, 1, 14.02 ],
    [ "Blythe, CA", "33:36:37", "114:35:46", 8, 1, 12.32 ],
    [ "Borrego Springs, CA", "33:15:21", "116:22:27", 8, 1, 12.65 ],
    [ "Crescent City, CA", "41:45:22", "124:12:04", 8, 1, 16.25 ],
    [ "Death Valley NP", "36:27:26", "116:51:55", 8, 1, 13.48 ],
    [ "Eureka, CA", "40:48:08", "124:09:47", 8, 1, 15.96 ],
    [ "Fort Bragg, CA", "39:26:45", "123:48:14", 8, 1, 15.51 ],
    [ "Fresno, CA", "36:44:52", "119:46:16", 8, 1, 14.14 ],
    [ "Gorman, CA", "34:47:46", "118:51:06", 8, 1, 13.49 ],
    [ "Kings Canyon NP", "36:47:27", "118:40:10", 8, 1, 13.94 ],
    [ "Lee Vining, CA", "37:57:27", "119:07:15", 8, 1, 14.32 ],
    [ "Lone Pine, CA", "36:36:22", "118:03:43", 8, 1, 13.77 ],
    [ "Los Angeles, CA", "34:03:08", "118:14:35", 8, 1, 13.20 ],
    [ "Los Osos, CA", "35:18:40", "120:49:53", 8, 1, 13.96 ],
    [ "Mojave, CA", "35:03:09", "118:10:23", 8, 1, 13.42 ],
    [ "Monterey, CA", "36:36:01", "121:53:38", 8, 1, 14.45 ],
    [ "Needles, CA", "34:50:53", "114:36:47", 8, 1, 12.58 ],
    [ "Nevada City, CA", "39:15:42", "121:00:54", 8, 1, 15.03 ],
    [ "Orick, CA", "41:17:13", "124:03:31", 8, 1, 16.09 ],
    [ "Redding, CA", "40:35:12", "122:23:28", 8, 1, 15.63 ],
    [ "Sacramento, CA", "38:34:54", "121:29:35", 8, 1, 14.92 ],
    [ "San Diego, CA", "32:42:55", "117:09:22", 8, 1, 12.69 ],
    [ "San Francisco, CA", "37:46:30", "122:25:05", 8, 1, 14.85 ],
    [ "San Luis Obispo, CA", "35:16:58", "120:39:32", 8, 1, 13.92 ],
    [ "Santa Barbara, CA", "34:25:15", "119:41:49", 8, 1, 13.55 ],
    [ "Santa Rosa, CA", "38:26:26", "122:42:47", 8, 1, 15.07 ],
    [ "Susanville, CA", "40:24:59", "120:39:07", 8, 1, 15.28 ],
    [ "Truckee, CA", "39:19:41", "120:10:55", 8, 1, 14.89 ],
    [ "Twentynine Palms, CA", "34:08:08", "116:03:11", 8, 1, 12.77 ],
    [ "Yosemite NP, CA", "37:44:43", "119:35:50", 8, 1, 14.36 ],
    [ "Yreka, CA", "41:44:08", "122:37:59", 8, 1, 16.01 ],
    [ "invalid" ],
    [ "invalid" ],
    [ "Albuquerque, NM", "35:05:04", "106:39:04", 7, 1, 9.96 ],
    [ "Anchorage, AK", "61:13:05", "149:54:00", 9, 1, 20.58 ],
    [ "Atlanta, GA", "33:44:56", "84:23:17", 5, 1, -4.02 ],
    [ "Augusta, ME", "44:18:38", "69:46:48", 5, 1, -16.80 ],
    [ "Austin, TX", "30:16:01", "97:44:34", 6, 1, 5.34 ],
    [ "Barrow, AK", "71:17:26", "156:47:20", 9, 1, 20.84 ],
    [ "Boise, ID", "43:36:49", "116:12:11", 7, 1, 15.07 ],
    [ "Boston, MA", "42:21:30", "71:03:37", 5, 1, -15.44 ],
    [ "Chicago, IL", "41:51:00", "87:39:00", 6, 1, -3.01 ],
    [ "Cincinnati, OH", "39:09:43", "84:27:25", 5, 1, -5.09 ],
    [ "Dallas, TX", "32:47:00", "96:48:00", 6, 1, 4.78 ],
    [ "Denver, CO", "39:44:21", "104:59:02", 7, 1, 9.73 ],
    [ "Detroit, MI", "42:19:53", "83:02:45", 5, 1, -7.09 ],
    [ "El Paso, TX", "31:45:31", "106:29:10", 7, 1, 9.50 ],
    [ "Fairbanks, AK", "64:50:16", "147:42:58", 9, 1, 22.85 ],
    [ "Hernandez, NM", "36:03:46", "106:07:08", 7, 1, 9.85 ],
    [ "Honolulu, HI", "21:18:25", "157:51:29", 10, 0, 9.98 ],
    [ "Jackson, WY", "43:28:48", "110:45:43", 7, 1, 13.05 ],
    [ "Juneau, AK", "58:18:07", "134:25:12", 9, 1, 23.21 ],
    [ "Kalispell, MT", "48:11:45", "114:18:43", 7, 1, 15.68 ],
    [ "Lynchburg, TN", "35:16:59", "86:22:27", 6, 1, -2.79 ],
    [ "Miami, FL", "25:46:26", "80:11:38", 5, 1, -5.46 ],
    [ "Minneapolis, MN", "44:58:48", "93:15:49", 6, 1, 1.37 ],
    [ "Moab, UT", "38:34:24", "109:32:56", 7, 1, 11.65 ],
    [ "Navy Town, AK", "52:50:27", "-173:10:32", 10, 0, 1.13 ],
    [ "New York, NY", "40:42:51", "74:00:23", 5, 1, -13.22 ],
    [ "Nome, AK", "64:30:04", "165:24:22", 9, 1, 13.87 ],
    [ "Page, AZ", "36:54:31", "111:28:23", 7, 0, 12.06 ],
    [ "Phoenix, AZ", "33:26:54", "112:04:23", 7, 0, 11.62 ],
    [ "Pittsburgh, PA", "40:26:26", "79:59:46", 5, 1, -8.98 ],
    [ "Portland, OR", "45:31:25", "122:40:30", 8, 1, 17.20 ],
    [ "Rapid City, SD", "44:04:50", "103:13:52", 7, 1, 9.13 ],
    [ "Reno, NV", "39:31:47", "119:48:47", 8, 1, 14.87 ],
    [ "Rochester, NY", "43:09:17", "77:36:57", 5, 1, -11.66 ],
    [ "Saint Louis, MO", "38:37:38", "90:11:52", 6, 1, -0.31 ],
    [ "Salt Lake City, UT", "40:45:39", "111:53:24", 7, 1, 12.95 ],
    [ "Seattle, WA", "47:36:23", "122:19:52", 8, 1, 17.84 ],
    [ "Sedona, AZ", "34:52:11", "111:45:36", 7, 0, 11.78 ],
    [ "Washington, DC", "38:53:42", "77:02:12", 5, 1, -10.68 ],
    [ "invalid" ],
    [ "invalid" ],
    [ "Calgary, AB", "51:02:28", "114:03:39", 7, 1, 16.45 ],
    [ "Edmonton, AB", "53:32:44", "113:30:18", 7, 1, 17.03 ],
    [ "Guadalajara, Mexico", "20:40", "103:21", 6, 0, 7.57 ],
    [ "Halifax, NS", "44:39:52", "63:36:30", 4, 1, -19.02 ],
    [ "Mexico City, Mexico", "19:26", "99:08", 6, 0, 6.10 ],
    [ "Monterrey, Mexico", "25:41", "100:19", 6, 0, 6.56 ],
    [ "Montreal, PQ", "45:31:22", "73:34:00", 5, 1, -15.32 ],
    [ "Ottawa, ON", "45:25:34", "75:42:04", 5, 1, -13.91 ],
    [ "Regina, SK", "50:26:54", "104:36:38", 6, 1, 10.72 ],
    [ "San Lucas, Mexico", "22:54", "109:54", 7, 0, 9.48 ],
    [ "Toronto, ON", "43:39:51", "79:21:26", 5, 1, -10.53 ],
    [ "Vancouver, BC", "49:15:45", "123:06:00", 8, 1, 18.59 ],
    [ "Winnipeg, MB", "49:53:57", "97:08:57", 6, 1, 4.33 ],
    [ "invalid" ],
    [ "invalid" ],
    [ "Bogota, Columbia", "4:37", "74:04", 5, 0, -5.53 ],
    [ "Buenos Aires, Argentina", "-34:37", "58:22", 3, 0, -6.96 ],
    [ "Caracas, Venezuela", "10:30", "66:55", 4, 0, -11.12 ],
    [ "La Paz, Bolivia", "-16:30", "68:09", 4, 0, -5.58 ],
    [ "Lima, Peru", "-12:04", "77:03", 5, 0, 0.18 ],
    [ "Punta Arenas, Chile", "-53:09", "70:56", 4, 1, 14.27 ],
    [ "Rio de Janeiro, Brazil", "-22:54", "43:13", 3, 1, -21.52 ],
    [ "Santiago, Chile", "-33:28", "70:39", 4, 1, 3.62 ],
    [ "invalid" ],
    [ "invalid" ],
    [ "Amsterdam, Netherlands", "52:23", "-4:53", -1, 1, -0.70 ],
    [ "Athens, Greece", "37:58", "-23:44", -2, 1, 3.21 ],
    [ "Berlin, Germany", "52:32", "-13:25", -1, 1, 2.08 ],
    [ "Bern, Switzerland", "46:57", "-7:27", -1, 1, 0.48 ],
    [ "Copenhagen, Denmark", "55:41", "-12:34", -1, 1, 1.83 ],
    [ "Helsinki, Finland", "60:10", "-24:57", -2, 1, 6.99 ],
    [ "London, UK", "51:30", "0:06", 0, 1, -2.40 ],
    [ "Madrid, Spain", "40:26", "3:43", -1, 1, -2.38 ],
    [ "Munich, Germany", "48:08", "-11:34", -1, 1, 1.54 ],
    [ "Moscow, Russia", "55:45", "-37:42", -3, 1, 9.58 ],
    [ "Oslo, Norway", "59:55", "-10:46", -1, 1, 1.09 ],
    [ "Paris, France", "48:50", "-2:20", -1, 1, -1.20 ],
    [ "Reykjavik, Iceland", "64:08", "21:54", 0, 0, -17.38 ],
    [ "Rome, Italy", "41:53", "-12:30", -1, 1, 1.69 ],
    [ "Stockholm, Sweden", "59:20", "-18:02", -1, 1, 4.11 ],
    [ "Vienna, Austria", "48:13", "-16:21", -1, 1, 2.71 ],
    [ "Warsaw, Poland", "52:15", "-21:00", -1, 1, 4.22 ],
    [ "invalid" ],
    [ "invalid" ],
    [ "Baghdad, Iraq", "33:20", "-44:25", -3, 1, 3.78 ],
    [ "Beijing, China", "39:54", "-116:28", -8, 0, -6.20 ],
    [ "Islamabad, Pakistan", "33:43", "-73:04", -5, 0, 2.07 ],
    [ "Istanbul, Turkey", "41:01", "-28:57", -2, 1, 4.18 ],
    [ "Jakarta, Indonesia", "-6:11", "-106:50", -7, 0, 0.76 ],
    [ "Jerusalem, Israel", "31:47", "-35:13", -2, 1, 3.61 ],
    [ "Kabul, Afghanistan", "34:32", "-69:08", -4, 0, 2.55 ],
    [ "Manila, Philippines", "14:35", "-120:59", -8, 0, -1.27 ],
    [ "Mecca, Saudi Arabia", "21:26", "-39:49", -3, 0, 2.65 ],
    [ "Mumbai, India", "18:57", "-72:50", -5, 0, -0.88 ],
    [ "New Delhi, India", "28:38", "-77:12", -5, 0, 0.68 ],
    [ "Seoul, Korea", "37:33", "-127:02", -9, 0, -7.76 ],
    [ "Shanghai, China", "39:14", "-121:29", -8, 0, -7.32 ],
    [ "Singapore, Singapore", "1:18", "-103:51", -8, 0, 0.28 ],
    [ "Taipei, Taiwan", "25:02", "-121:31", -8, 0, -3.64 ],
    [ "Tehran, Iran", "35:42", "-51:25", -3, 0, 3.97 ],
    [ "Tokyo, Japan", "35:35", "-139:45", -9, 0, -6.89 ],
    [ "invalid" ],
    [ "invalid" ],
    [ "Addis Ababa, Ethiopia", "9:01", "-38:45", -3, 0, 1.62 ],
    [ "Antananarivo, Madagascar", "-18:55", "-47:32", -3, 0, -15.08 ],
    [ "Cairo, Egypt", "30:02", "-31:15", -2, 1, 3.26 ],
    [ "Capetown, South Africa", "-33:55", "-18:26", -2, 0, -24.04 ],
    [ "Casablanca, Morocco", "33:36", "-7:37", 0, 0, 0.55 ],
    [ "Dar es Salaam, Tanzania", "-6:49", "-39:17", -3, 0, -2.33 ],
    [ "Dakar, Senegal", "14:40", "17:26", 0, 0, -8.93 ],
    [ "Nairobi, Kenya", "-1:17", "-36:49", -3, 0, 0.03 ],
    [ "Tripoli, Libya", "32:52", "-13:11", -1, 0, 1.44 ],
    [ "Windhoek, Namibia", "-22:34", "-17:05", -2, 0, -13.43 ],
    [ "invalid" ],
    [ "invalid" ],
    [ "Brisbane, Australia", "-27:28", "-153:02", -10, 1, 11.12 ],
    [ "Perth, Australia", "-31:57:09", "-115:51:24", -8, 1, -1.78 ],
    [ "Sydney, Australia", "-33:52:00", "-151:12:00", -10, 1, 12.61 ],
    [ "invalid" ],
    [ "invalid" ],
    [ "Auckland, New Zealand", "-36:50", "-174:45", -12, 1, 19.34 ],
    [ "Grytviken, South Georgia", "-54:16", "36:31", 2, 0, -6.99 ],
];

var place_name;

function isleap(year)
{
    if ((year) % 4 == 0 && (year) % 100 != 0 || (year) % 400 == 0)
	return (1);
    else
	return (0);
}

// ************* Number and String methods **********
Number.prototype.SetRange = function(y)
{
    var x = this;

    x %= y;
    if (x < 0)
	x += y;
    return (x);
}

// convert decimal angle to dd:mm
Number.prototype.toDMS = function()
{
    var 
	angle = this,
	degrees,
	minutes,
	seconds,
	sign = 1;

    if (angle < 0.0) {
	angle = -angle;
	sign = -1;
    }
    degrees = Math.floor(angle)
    minutes = Math.round((angle - degrees) * 60.0);
    if (minutes > 59.0) {
	minutes = 0.0;
	degrees++;
    }

    return ((sign == -1 ? "-" : "")
	    + degrees + ":" + (minutes < 10 ? "0" : "") + minutes);
}

String.prototype.DMSto = function()
{
    var
	angle = this,
	d_angle,
	sign,
	temparr = angle.split(":");

    if (angle.indexOf("-") != -1)
	sign = -1;
    else
	sign = 1;

    if(sign == -1)
	temparr[0] *= -1;

    d_angle = (temparr[0] - 0);
    if (temparr.length > 1)		// minutes given
	d_angle += ((temparr[1] - 0) / 60);
    if (temparr.length > 2)		// seconds given
	d_angle += ((temparr[2] - 0) / 3600);
    return sign * d_angle;
}

Math.rnd = function(x, d)
{
    var multiplier = Math.pow(10, d);

    return Math.round(x * multiplier) / multiplier;
}

// ************* Date properties and methods ********

Date.prototype.JD1970 = 2440587.50;	// JD of 0h UT 1 Jan 1970

// set 0h LCT
Date.prototype.setZeroHours = function()
{
    this.setHours(0);
    this.setMinutes(0);
    this.setSeconds(0);
    this.setMilliseconds(0);
}

// Julian Day Number
Date.prototype.getJulian = function()
{
    // JD of 0h UT
    return (
	Math.floor(
	    this.JD1970 + this.getTime() / D_to_MSEC
	    - this.getTimezoneOffset() / 60 / 24
	) + 0.5
    );
}

// *************** Place object *****************************
function Place(place)
{
    if (arguments.length) {
	this.name = place[NAME];
	this.latitude = String(place[LAT]).DMSto();
	this.longitude = String(place[LONG]).DMSto();
	this.timezone = Number(place[TZ]);
	this.use_dst = Number(place[DST]);
	this.mag_dec = String(place[MAGDEC]).DMSto();
    }
}

Place.prototype.timezones = [
    //   0     1   2   3       4         5          6         7            8
       "UT",  "", "", "", "Atlantic", "Eastern", "Central", "Mountain", "Pacific", 
    //        9               10
       "Alaska/Yukon", "Hawaii/Aleutian"
];

Place.prototype.tznames = [
    // 0     1   2   3    4      5      6      7      8      9     10 
    [ "UT", "", "", "", "AST", "EST", "CST", "MST", "PST", "YST", "HST" ],
    [ "BST", "", "", "", "ADT", "EDT", "CDT", "MDT", "PDT", "YDT", "HDT" ]
];

Place.prototype.getTimezone = function()
{
    var
	tz_int = Math.floor(this.timezone);
    if (this.timezones[tz_int])
	return (this.timezones[tz_int]);
    else
	return (null);
}

Place.prototype.get_tzname = function()
{
    var
	daylite = this.dst_offset ? 1 : 0,
	tz_int = Math.floor(this.timezone),
	tzname;

	    /* show British and North American time zone names */
    if (tz_int >= 0 && tz_int < this.tznames[0].length && this.latitude > 15.0
	   && this.tznames[daylite][tz_int] != "")
	tzname = this.tznames[daylite][tz_int];
	    /* show UT */
    else if (tz_int == 0 && daylite == 0)
	tzname = this.tznames[daylite][tz_int];
	    /* just show standard vs. daylight saving time */
    else if (daylite)
	tzname =  "DST";
    else
	tzname =  "STD";

    return tzname;
}

// determine daylight saving status
// DST rules for the computer's location are applied to all locations,
// so "dst_offset" isn't really as general as it appears
Place.prototype.is_dst = function(CalcDate)
{
    var
	dst_offset = homeloc.std_TZ_offset - CalcDate.getTimezoneOffset() / 60,
	hemisign = this.latitude * homeloc.dst_offset;

    if (homeloc.dst_offset == 0)		// no way to determine DST status
	this.dst_offset = 0;			// can only compute standard times
    else if (hemisign < 0 && dst_offset == 0)	// location and computer in opp hemispheres
	this.dst_offset = homeloc.dst_offset;	// turn on computer's DST
    else if (hemisign < 0)			// DST in effect for computer
	this.dst_offset = 0;			// turn off DST
    else					// location and computer in same hemisphere;
	this.dst_offset = dst_offset;		// apply computer's current DST

    return(this.dst_offset * 1);
}

// get time offset from UT
Place.prototype.get_t_offset = function(CalcDate)
{
    var t_offset = this.timezone;
    if (this.use_dst == 1)
	t_offset -= this.is_dst(CalcDate);
    this.t_offset = t_offset;

    return (t_offset * 1);
}

// get the DST offset for the computer's location
function get_std_TZ_offset()
{
    var 
	SummerSolstice = new Date("21 Jun 2000"),
	WinterSolstice = new Date("21 Dec 2000"),
	summer_tOffset = SummerSolstice.getTimezoneOffset() / 60,
	winter_tOffset = WinterSolstice.getTimezoneOffset() / 60;

    if (winter_tOffset < summer_tOffset)		// southern hemisphere
	homeloc.std_TZ_offset = summer_tOffset;
    else						// northern hemisphere
	homeloc.std_TZ_offset = winter_tOffset;

	// positive in northern hemisphere
    homeloc.dst_offset = winter_tOffset - summer_tOffset;

    delete SummerSolstice, WinterSolstice;
}

// ********************* Sun position object ***************************
// constructor
function SunPos()
{
    ;
}

// calculate UT of rise/set/transit
// returns: UT or +/- infinity if no phenomenon
SunPos.prototype.getTime = function(jday, loc, type)
{
    var
	cos_H,		// cosine of local hour angle
	longitude = loc.longitude,
	ntrys,
	sign,
	sin_h,		// sine of altitude
	t,
	ut = 12.0 + loc.timezone,	// approximate local noon
	ut0 = -1;

    var
	sin_lat = Math.sin(loc.latitude * D_to_R),
	cos_lat = Math.cos(loc.latitude * D_to_R);

    switch(type) {
	case "M":
	case "R":
	    sign = 1;
	    break;
	case "T":
	    sign = 0;
	    break;
	case "S":
	case "E":
	    sign = -1;
	    break;
    }

    switch(type) {
	case "M":
	case "E":
	    sin_h = Math.sin(-6 * D_to_R);
	    break;
	default:
	    sin_h = Math.sin(-5/6 * D_to_R);
    }
      // hack to make things work at a few places (e.g., Navy Town, AK)
      // near the date line
    if (loc.timezone > 0 && longitude < 0)
	longitude += 360;
    else if (loc.timezone < 0 && longitude > 0)
	longitude -= 360;

    ut0 = 0;
    ntrys = 0;
    while (Math.abs(ut - ut0) > 0.001) {
	if (++ntrys > 10)
	    break;
	ut0 = ut;
	    // calculate position
	this.calcPos(jday, ut);

	    // calculate hour angle
	if (type == "T")
	    cos_H = 1;		// transit at H = 0
	else
	    cos_H = (sin_h - sin_lat * Math.sin(this.declination)) /
		    (cos_lat * Math.cos(this.declination))

	if (cos_H > 1)		// circumpolar; never rises
	    t = 0;
	else if (cos_H < -1)	// circumpolar; never sets
	    t = 180;
	else
	    t = Math.acos(cos_H) / D_to_R;
	ut = ut0 - (this.gha - longitude + sign * t) / 15.0;
    }

    this.declination /= D_to_R;
    if (type != "T") {
	if (cos_H > 1)
	    ut = Number.NEGATIVE_INFINITY;	// circumpolar; never rises
	else if (cos_H < -1)
	    ut = Number.POSITIVE_INFINITY;	// circumpolar; never sets
	else if (ntrys > 10)
	    ut = F0_NO_PHENOM;			// phenomenon does not occur
    }

    return (ut);
}

// length of time Sun is above horizon
SunPos.prototype.getDayLen = function(rise, set)
{
    var day;

    // handle set before rise
    if (Math.abs(rise) < F_CIRCUMPOLAR && Math.abs(set) < F_CIRCUMPOLAR) {
	if (rise < set)
	    day = set - rise;
	else
	    day = 24 - (rise - set);
    }
    else if (rise >= F_CIRCUMPOLAR || set >= F_CIRCUMPOLAR)
	day = 24;
    else if (rise <= -F_CIRCUMPOLAR || set <= -F_CIRCUMPOLAR)
	day = 0;

    return day;
}

// Low-precision Sun ephemeris from the Explanatory Supplement to the
// Astronomical Almanac, rev. ed., Chapter 9
//
// Greenwich Hour Angle (GHA) in degrees
// declination in radians
SunPos.prototype.calcPos = function(jday, ut)
{
    var
	J2000 = 2451545.0;			// JD of 12:00 UT 1 Jan 2000
    var
	anomaly,	// mean anomaly
	eq_time,	// equation of time
	lambda,		// ecliptic longitude
	long_mean,	// mean longitude
	obliquity,	// obliquity of the ecliptic
	T;		// centuries since J2000

    T = (jday + ut / 24.0 - J2000) / 36525.0;
    long_mean = (280.46 + 36000.77 * T).SetRange(360);
    anomaly = (357.528 + 35999.05 * T).SetRange(360.0) * D_to_R;
    lambda = (long_mean + 1.915 * Math.sin(anomaly)
           + 0.02 * Math.sin(2 * anomaly)) * D_to_R;
    lambda = lambda.SetRange(2.0 * Math.PI);
    obliquity =  (23.4393 - 0.013 * T) * D_to_R;
    eq_time = (-1.915 * Math.sin(anomaly)
               -0.02 * Math.sin(2.0 * anomaly)
               +2.466 * Math.sin(2.0 * lambda)
               -0.053 * Math.sin(4.0 * lambda)) * D_to_R;

    this.gha = 15.0 * ut - 180.0 + eq_time / D_to_R;
    this.declination = Math.asin(Math.sin(obliquity) * Math.sin(lambda));
}

SunPos.prototype.calcAltAz = function(loc)
{
    var
	lha = (this.gha - loc.longitude) * D_to_R,	// Local hour angle
	decl = this.declination;
	lat = loc.latitude * D_to_R;
    this.azimuth = Math.atan2(
		      Math.sin(lha),
		      Math.cos(lha) * Math.sin(lat) - Math.tan(decl) * Math.cos(lat)
		  ) / D_to_R + 180;
    this.altitude = Math.asin(
		      Math.sin(lat) * Math.sin(decl)
		    + Math.cos(lat) * Math.cos(decl) * Math.cos(lha)
	           ) / D_to_R;
}

// compute apparent altitude from true altitude
SunPos.prototype.getAppAlt = function()
{
    var alt = this.altitude;

    // avoid singularity
    if (alt < -5.0016 || alt > 89.891)
	return alt;
    else
	return alt + 1.02 / Math.tan((alt + 10.3 / (alt + 5.11)) * D_to_R) / 60.0;
}

// compute azimuth at time of rise/set
SunPos.prototype.getAzimuth = function(loc, altitude, type)
{
    var
	cos_az,
	azimuth;

    altitude *= D_to_R;
    latitude = loc.latitude * D_to_R;
    declination = this.declination * D_to_R;

    cos_az = (
	Math.sin(declination) - Math.sin(latitude) * Math.sin(altitude))
	 / (Math.cos(latitude) * Math.cos(altitude)
     );
    if (cos_az > 1.0)
	azimuth = 0.0;
    else if (cos_az < -1.0)
	azimuth = 180.0;
    else
	azimuth = Math.acos(cos_az) / D_to_R;

    return (type == "R" ? azimuth : 360 - azimuth).SetRange(360);
}

// compute altitude of body at transit
SunPos.prototype.getMaxalt = function(loc)
{
    this.altitude = 90.0 - Math.abs(this.declination - loc.latitude);
    return this.getAppAlt();
}

function initialize_form()
{
    var form = document.Options;
    if (form.status.value == 0)		// initial load
	reset_form(form);
}

function reset_date(form)
{
    var Today = new Date();

    Today.setZeroHours();
    form.StartDay.selectedIndex = Today.getDate() - 1;
    form.StartYear.value = Today.getFullYear();
    form.StartMonth.selectedIndex = Today.getMonth();

    form.EndDay.selectedIndex = form.StartDay.selectedIndex;
    form.EndMonth.selectedIndex = form.StartMonth.selectedIndex;
    form.EndYear.value = form.StartYear.value;

    form.CalcDay.selectedIndex = Today.getDate() - 1;
    form.CalcYear.value = Today.getFullYear();
    form.CalcMonth.selectedIndex = Today.getMonth();

    form.end[0].checked = true;
    form.status.value = 1;		// mark as loaded

    delete Today;
}

function reset_form(form)
{
    form.reset();
    reset_date(form);
    loc = new Place();
}

function clear_name()
{
    place_name = "Unknown";
}

// set latitude and longitude to values for the selected location
function copy_lat_long(form)
{
    var ndx = form.city.selectedIndex;
    place_name = places[ndx][NAME];
    if ((latitude = places[ndx][LAT].DMSto()) < 0) {
	form.latitude.value = (-latitude).toDMS();
	form.lat_sign.selectedIndex = 1;
    }
    else {
	form.latitude.value = latitude.toDMS();
	form.lat_sign.selectedIndex = 0;
    }
    if ((longitude = places[ndx][LONG].DMSto()) < 0) {
	form.longitude.value = (-longitude).toDMS();
	form.long_sign.selectedIndex = 1;
    }
    else {
	form.longitude.value = longitude.toDMS();
	form.long_sign.selectedIndex = 0;
    }
    if (places[ndx][TZ]);
	// can't handle fractional hour offsets
    form.timezone.options.selectedIndex = Math.round(places[ndx][TZ]) + 13;
    form.use_dst.options.selectedIndex = places[ndx][DST];
    if (places[ndx][MAGDEC] < 0) {
	form.magdec.value = (-places[ndx][MAGDEC]).toDMS();
	form.magdec_sign.selectedIndex = 0;
    }
    else {
	form.magdec.value = (places[ndx][MAGDEC]).toDMS();
	form.magdec_sign.selectedIndex = 1;
    }
}

function check_start_day(form)
{
    var
	day = form.StartDay.selectedIndex + 1,
	ndx = form.StartMonth.selectedIndex,
	year = form.StartYear.value,
	maxdays = monthdays[isleap(year)][ndx];
    if (day > maxdays)
	form.StartDay.selectedIndex = maxdays - 1;
}

function check_end_day(form)
{
    var
	day = form.EndDay.selectedIndex + 1,
	ndx = form.EndMonth.selectedIndex,
	year = form.EndYear.value,
	maxdays = monthdays[isleap(year)][ndx];
    if (day > maxdays)
	form.EndDay.selectedIndex = maxdays - 1;
}

function check_calc_day(form)
{
    var
	day = form.CalcDay.selectedIndex + 1,
	ndx = form.CalcMonth.selectedIndex,
	year = form.CalcYear.value,
	maxdays = monthdays[isleap(year)][ndx];
    if (day > maxdays)
	form.CalcDay.selectedIndex = maxdays - 1;
}

function check_year(field)
{
    if (isNaN(field.value)) {
	alert("Bad value (" + field.value + ") for year");
	field.focus();
	field.select();
    }
}

// ensure that date offset is nonnegative
function check_nonnegative(field)
{
    if (isNaN(field.value) || field.value < 0) {
	alert("Nonnegative number required");
	field.focus();
	field.select();
    }
}

// ensure that value is positive
function check_value_positive(value)
{
    if (isNaN(value - 0) || value <= 0)
	alert("Positive number required");
}

function help()
{
    window.location.href = "SunCalcHelp.htm";
    return true;
}

var
    std_TZ_offset;
    flags = new Object(),
    homeloc = new Object(),	// home location
    s_pos = new Object();

function calculate()
{
    var
	params = document.Options;	// save some typing
    var
	MAX_CALC = 366,
	calc_type,
	n_calcs,
	sun = new Object(),
	place = new Array(),
	start_day,		// days since 0h UT 1 January 1970
	end_day,		// days since 0h UT 1 January 1970
	offset,
	calc_step = params.step.value - 0,
	time_zone,
	ndx;

    var Today = new Date();

    var copyright = BR + SP2 + "SunCalc &copy; " + Today.getFullYear() + " Jeff Conrad";
    delete Today;

    get_std_TZ_offset();

	// ************ set location *************************
    if (params.loc_type[0].checked == true) {		// look up location
	ndx = params.city.selectedIndex;
	if (places[ndx][NAME] == "invalid") {
	    alert(
		"Please select a location"
	    );
	    return;
	}	
	else if (params.city.options[ndx].text != places[ndx][NAME]) {
	    alert(
		"Location lookup error:"
		+ "\n" + params.city.options[ndx].text
		+ "\n          vs."
		+ "\n" + places[ndx][NAME]
	    );
	    return;
	}
	else
	    place = places[ndx];
    }
    else {						// just set lat/long
	if (place_name)
	    place[NAME] = place_name;
	else
	    place[NAME] = "Unknown";

	if (isNaN(place[LAT] = params.latitude.value.DMSto())) {
	    alert("Invalid latitude (" + params.latitude.value + ")\n"
	          + "Format: dd:[mm[:ss]] or dd.nn");
	    params.latitude.focus();
	    params.latitude.select();
	    return;
	}
	else if (Math.abs(params.latitude.value) > 90) {
	    alert("Invalid latitude (" + params.latitude.value + ")\n"
		  + "Range is -90 to 90");
	    return;
	}
	ndx = params.lat_sign.selectedIndex;
	place[LAT] *= params.lat_sign.options[ndx].value;

	if (isNaN((place[LONG] = params.longitude.value.DMSto()))) {
	    alert("Invalid longitude (" + params.longitude.value + ")\n"
	          + "Format: dd:[mm[:ss]] or dd.nn");
	    return;
	}
	else if (Math.abs(place[LONG]) > 180) {
	    alert("Invalid longitude (" + params.longitude.value + ")\n"
		  + "Range is -180 to 180");
	    return;
	}
	ndx = params.long_sign.selectedIndex;
	place[LONG] *= params.long_sign.options[ndx].value;

	ndx = params.timezone.selectedIndex;
	var timezone = params.timezone.options[ndx].value;
	if (timezone == "")
	    place[TZ] = Math.round(place[LONG] / 15);
	else if (isNaN((place[TZ] = timezone * 1))) {
	    alert("Invalid time zone (" + timezone + ")");
	    return;
	}
	else if (Math.abs(place[TZ]) > 12) {
	    alert("Invalid time zone (" + timezone + ")");
	    return;
	}
	ndx = params.use_dst.selectedIndex;
	place[DST] = params.use_dst.options[ndx].value;

	if (isNaN((place[MAGDEC] = params.magdec.value.DMSto()))) {
	    alert("Invalid magnetic declination (" + params.magdec.value + ")\n"
	          + "Format: dd:[mm[:ss]] or dd.nn");
	    return;
	}
	else if (Math.abs(place[MAGDEC]) > 180) {
	    alert("Invalid magnetic declination (" + params.magdec.value + ")\n"
		  + "Range is -180 to 180");
	    return;
	}
	ndx = params.magdec_sign.selectedIndex;
	place[MAGDEC] *= params.magdec_sign.options[ndx].value;

    }
    loc = new Place(place);

    ndx = params.magnorth.selectedIndex;
    flags.show_mag_north = params.magnorth.options[ndx].value;

    if (params.calc_type[0].checked == true) {
	    // **************** set dates (0h LCT) ********************
	calc_type = TIMES;
	StartDate = new Date(params.StartYear.value,
			     params.StartMonth.selectedIndex,
			     params.StartDay.selectedIndex + 1);
	start_day = StartDate.getTime() / D_to_MSEC;

	if (params.end[1].checked == true) {		// offset to start date
	    if (isNaN((offset = params.DateOffset.value - 0)) || offset < 0) {
		alert("Bad value (" + offset + ") for date offset");
		return;
	    }
	    ndx = params.o_units.selectedIndex;
	    end_day = start_day + offset * params.o_units.options[ndx].value;
	}
	else {						// calendar date
	    EndDate = new Date(params.EndYear.value,
				 params.EndMonth.selectedIndex,
				 params.EndDay.selectedIndex + 1);
	    end_day = EndDate.getTime() / D_to_MSEC;

	    // ensure that end_date differs from start_date by an
	    // integral number of days
	    offset = Math.round(end_day - start_day)
	    end_day = start_day + offset;
	}

	if (end_day < start_day) {
	    params.EndYear.value = params.StartYear.value;
	    params.EndMonth.selectedIndex = params.StartMonth.selectedIndex;
	    params.EndDay.selectedIndex = params.StartDay.selectedIndex;
	    EndDate = new Date(params.EndYear.value,
				 params.EndMonth.selectedIndex,
				 params.EndDay.selectedIndex + 1);
	    end_day = EndDate.getTime() / D_to_MSEC;
	}

	ndx = params.s_units.selectedIndex;
	calc_step *= params.s_units.options[ndx].value;

	if (((n_calcs = end_day - start_day) / calc_step) > MAX_CALC) {
	    alert("Too many calculations (" + n_calcs + ")--maximum is " + MAX_CALC);
	    return;
	}
    }
    else {
	    // **************** set date (0h LCT) ********************
	calc_type = POSITION;
	StartDate = new Date(params.CalcYear.value,
			     params.CalcMonth.selectedIndex,
			     params.CalcDay.selectedIndex + 1);
	start_day = end_day = StartDate.getTime() / D_to_MSEC;

	ndx = params.time_step.selectedIndex;
	time_step = params.time_step.options[ndx].value;
    }

    if (navigator.appName.indexOf("Netscape") != -1) {
	var
	    wid = 0.85 * screen.availWidth,
	    ht = 0.85 * screen.availHeight,
	    xpos = 0.02 * screen.availWidth,
	    ypos = 0.02 * screen.availHeight;

	ResultsWindow = window.open(
	    "",
	    "Results",
	      "height=" + ht
	    + ",width=" + wid
	    + ",resizable=1,menubar=1,toolbar=0,statusbar=1,scrollbars=1"
	);
	ResultsWindow.moveTo(xpos, ypos);
	ResultsWindow.focus();

	var d = ResultsWindow.document;
    }
    else
	var d = document;

    d.close();
    d.writeln("<HTML>");
    d.writeln("<HEAD>");
    d.writeln('<META http-equiv="charset" content="UTF-8">');
    d.writeln("<TITLE>Sunrise/Sunset Results</TITLE>");
    d.writeln("</HEAD>");
    d.writeln("<BODY text='#000040' bgcolor='#ffffff'>");

    d.writeln(SP + B + "Location: " + loc.name + BE + BR);

    if (loc.latitude < 0)
	d.writeln(SP, B, "Latitude: ", (-loc.latitude).toDMS(), " S", BE, BR);
    else
	d.writeln(SP, B, "Latitude: ", loc.latitude.toDMS(), " N", BE, BR);
    if (loc.longitude < 0)
	d.writeln(SP, B, "Longitude: ", (-loc.longitude).toDMS(), " E", BE, BR);
    else
	d.writeln(SP, B, "Longitude: ", loc.longitude.toDMS(), " W", BE, BR);
    d.write(SP, B, "Time Zone: ", loc.timezone);
    if ((time_zone = loc.getTimezone()))
	d.write(" (", time_zone + ")");
    d.writeln(BE + BR);
    if (loc.mag_dec < 0)
	d.writeln(SP, B, "Magnetic Declination: ", (-loc.mag_dec).toDMS(), " W", BE, BR);
    else
	d.writeln(SP, B, "Magnetic Declination: ", loc.mag_dec.toDMS(), " E", BE, BR);
    d.writeln(SP, "Azimuths Relative to ", B,
	      (flags.show_mag_north == true ? "Magnetic" : "True"),
	      " North" + BE + BR);

    // start = new Date();		// for timing program

    calc_times(start_day, end_day, calc_step, sun, loc, d)

    if (calc_type == POSITION)
	calc_positions(start_day, time_step, sun, loc, d)

    d.writeln(copyright);

    // end = new Date();

    // d.writeln("(", (end.getTime() - start.getTime()), " msec)", BR);

    if (navigator.appName.indexOf("Netscape") != -1) {
	d.writeln("<form name=closeme>");
	d.writeln("<center>");
	d.writeln("<input type='button' value='Close' onClick='window.close()'>");
	d.writeln("</center>");
	d.writeln("</form>");
    }
    d.close();
}

// calculate Sun azimuths and altitudes between sunrise and sunset
function calc_positions(calc_day, time_step, sun, loc, d)
{
    var
	altitude,
	calc_time,
	spos = new SunPos(),	// Sun position
	CalcDate = new Date((calc_day) * D_to_MSEC),
	t_offset = loc.get_t_offset(CalcDate),
	jday = CalcDate.getJulian();

    // don't bother if the Sun never rises
    if (sun.rise < -F_CIRCUMPOLAR && sun.set < -F_CIRCUMPOLAR)
	return;
    if (sun.rise > F_CIRCUMPOLAR)
	sun.rise = 0;
    if (sun.set > F_CIRCUMPOLAR)
	sun.set = 24;

    d.writeln(BR);
    d.writeln(TABLE_START2);
    d.writeln(THD);
    d.writeln(TR);
    d.writeln(TH11 + "Time" + THE);
    d.writeln(TH11 + (flags.show_mag_north == true? "Azimuth*" : "Azimuth") + THE);
    d.writeln(TH11 + "Altitude" + THE);
    d.writeln(TH11 + "Shadow", BR, "Length"  + THE);
    d.writeln(TRE);
    d.writeln(THDE);
    d.writeln(TBD);

    time_step /= 60;
    var
	start_time = Math.ceil(sun.rise / time_step) * time_step;
	end_time = Math.floor(sun.set / time_step) * time_step;

    // if set precedes rise, show the entire day
    if (start_time > end_time) {
	start_time = 0;
	end_time = 24;
    }
    
    var nn = 0;
    for (calc_time = start_time; calc_time <= end_time; calc_time += time_step) {
	if (++nn > 500)
	    break;	// prevent an infinite loop
	spos.calcPos(jday, calc_time + t_offset);
	spos.calcAltAz(loc);
	if (flags.show_mag_north == true)
	    spos.azimuth = (spos.azimuth - loc.mag_dec).SetRange(360);
	d.writeln(TR);
	d.write(TD_C);
	if (time_step < 0.5 && Math.rnd(calc_time, 3) % 1 == 0)
	    d.write(B);
	if (calc_time < 0)
	    d.write((calc_time + 24).toDMS(), MINUS);
	else if (calc_time > 24)
	    d.write((calc_time - 24).toDMS(), "+");
	else
	    d.write(calc_time.toDMS());
	if (time_step < 0.5 && Math.rnd(calc_time, 3) % 1 == 0)
	    d.write(BE);
	d.writeln(TDE);
	d.writeln(TD_C, Math.rnd(spos.azimuth, 1), TDE);
	if ((altitude = Math.rnd(spos.getAppAlt(), 1)) < 0)
	    d.writeln(TD_C, MINUS, -altitude, TDE);
	else
	    d.writeln(TD_C, altitude, TDE);
	if (altitude > 0)
	    d.writeln(TD_C, Math.rnd(1 / Math.tan(altitude * D_to_R), 2), TDE);
	else
	    d.writeln(TD_C, MDASH, TDE);
	d.writeln(TRE);
    }
    d.writeln(TBDE);
    d.writeln(TABLE_END);

    delete CalcDate;
}

function calc_times(start_day, end_day, calc_step, sun, loc, d)
{
    d.writeln(BR);
    d.writeln(TABLE_START);
    d.writeln(THD);
    d.writeln(TR);
    d.writeln(TH21 + "Day" + THE);
    d.writeln(TH21 + "Date" + THE);
    d.writeln(TH21 + "Dawn" + THE);
    d.writeln(TH21 + "Rise"  + THE);
    d.writeln(TH21 + "Transit" + THE);
    d.writeln(TH21 + "Set" + THE);
    d.writeln(TH21 + "Dusk" + THE);
    d.writeln(TH21 + "Day" + BR + "Length" + THE);
    d.writeln(TH12 + (flags.show_mag_north == true? "Azimuth*" : "Azimuth") + THE);
    d.writeln(TH21 + "Max" + BR + "Alt" + THE);
    d.writeln(TRE);
    d.writeln(TR);
    d.writeln(TH11 + "Rise" + THE);
    d.writeln(TH11 + "Set" + THE);
    d.writeln(TRE);
    d.writeln(THDE);
    d.writeln(TBD);

    var
	h = -5/6,		// standard altitude for rise/set
	hours,
	jday,			// Julian day number (JD)
	spos = new SunPos(),	// Sun position
	dst_offset,
	t_offset;

    var dst_offset = homeloc.dst_offset;

    for (calc_day = start_day; calc_day <= end_day + 1.001 / 24; calc_day += calc_step) {
	    // CalcDate is at 0h local time to get JD
	var CalcDate = new Date((calc_day) * D_to_MSEC);
	jday = CalcDate.getJulian();
	delete CalcDate;

	    // CalcDate is at 12h local time to determine DST
	    // and ensure correct date display
	var CalcDate = new Date((calc_day + 0.5) * D_to_MSEC);
	t_offset = loc.get_t_offset(CalcDate);

	sun.dawn = spos.getTime(jday, loc, "M") - t_offset;
	sun.rise = spos.getTime(jday, loc, "R") - t_offset;
	sun.transit = spos.getTime(jday, loc, "T") - t_offset;
	sun.set = spos.getTime(jday, loc, "S") - t_offset;
	sun.dusk = spos.getTime(jday, loc, "E") - t_offset;
	sun.day = spos.getDayLen(sun.rise, sun.set);
	sun.az_rise = spos.getAzimuth(loc, h, "R");
	sun.az_set = spos.getAzimuth(loc, h, "S");
	sun.maxalt = spos.getMaxalt(loc);

	if (flags.show_mag_north == true) {
	    sun.az_rise = (sun.az_rise - loc.mag_dec).SetRange(360);
	    sun.az_set = (sun.az_set - loc.mag_dec).SetRange(360);
	}
	output_times(CalcDate, sun, d);
	delete CalcDate;
    }

    d.writeln(TBDE);
    d.writeln(TABLE_END);
}

function output_times(cdate, s, d)
{
    var
	NO_PHENOM = MDASH,	// phenomenon does not occur [em dash]
	ABOVE = "****",		// circumpolar; never sets
	BELOW = "-----",	// circumpolar; never rises
	LIGHT = "/////",	// twilight never ends
	DARK  = "===";		// twilight never begins

    d.writeln(TR_R);
    d.writeln(TD_L + day_of_week[cdate.getDay()] + TDE);
    d.writeln(
	TD_N + ((day = cdate.getDate()) < 10 ? SP : "")
        + day
        + " " + months[cdate.getMonth()]
        + " " + cdate.getFullYear()
        + " (" + loc.get_tzname() + ")"
        + TDE
       );

    if (s.dawn <= -F_CIRCUMPOLAR)
	d.writeln(TD_C, DARK, TDE);
    else if (s.dawn >= F_CIRCUMPOLAR)
	d.writeln(TD_C, LIGHT, TDE);
    else if (s.dawn > F_NO_PHENOM)
	d.writeln(TD_C, NO_PHENOM, TDE);
    else if (s.dawn < 0)	// previous day
	d.writeln(TD_C, (s.dawn + 24).toDMS() + MINUS, TDE);
    else
	d.writeln(TD_C, s.dawn.toDMS(), TDE);

	// sunrise
    if (s.rise <= -F_CIRCUMPOLAR)
	d.writeln(TD_C, BELOW, TDE);
    else if (s.rise >= F_CIRCUMPOLAR)
	d.writeln(TD_C, ABOVE, TDE);
    else if (s.rise > F_NO_PHENOM)
	d.writeln(TD_C, NO_PHENOM, TDE);
    else if (s.rise < 0)	// previous day
	d.writeln(TD_C, (s.rise + 24).toDMS() + MINUS, TDE);
    else
	d.writeln(TD_C + s.rise.toDMS() + TDE);

    if (s.transit < 0)
	d.writeln(TD_C, (s.transit + 24).toDMS() + MINUS, TDE);
    else if (s.transit > 24)
	d.writeln(TD_C, (s.transit - 24).toDMS() + "+", TDE);
    else
	d.writeln(TD_C, s.transit.toDMS(), TDE);

	// sunset
    if (s.set <= -F_CIRCUMPOLAR)
	d.writeln(TD_C, BELOW, TDE);
    else if (s.set >= F_CIRCUMPOLAR)
	d.writeln(TD_C, ABOVE, TDE);
    else if (s.set > F_NO_PHENOM)
	d.writeln(TD_C, NO_PHENOM, TDE);
    else if (s.set > 24)	// next day
	d.writeln(TD_C, (s.set - 24).toDMS() + "+", TDE);
    else
	d.writeln(TD_C, s.set.toDMS(), TDE);

    if (s.dusk <= -F_CIRCUMPOLAR)
	d.writeln(TD_C, DARK, TDE);
    else if (s.dusk >= F_CIRCUMPOLAR)
	d.writeln(TD_C, LIGHT, TDE);
    else if (s.dusk > F_NO_PHENOM)
	d.writeln(TD_C, NO_PHENOM, TDE);
    else if (s.dusk > 24)	// next day
	d.writeln(TD_C, (s.dusk - 24).toDMS() + "+", TDE);
    else
	d.writeln(TD_C, s.dusk.toDMS(), TDE);

	// length of day
    if (Math.abs(s.day) < F_CIRCUMPOLAR)
	d.write(TD, (s.day).toDMS(), SP, TDE);
    else
	d.writeln(TD_C, NO_PHENOM, TDE);

	// rise/set azimuths
    if (Math.abs(s.rise) > F_NO_PHENOM)
	d.writeln(TD_C, NO_PHENOM, TDE);
    else
	d.writeln(TD, Math.round(s.az_rise), TDE);
    if (Math.abs(s.set) > F_NO_PHENOM)
	d.writeln(TD_C, NO_PHENOM, TDE);
    else
	d.writeln(TD, Math.round(s.az_set), TDE);

	// maximum altitude
    if (s.maxalt < 0)
	d.writeln(TD, MINUS + Math.rnd(-s.maxalt, 1), SP, TDE);
    else if (s.maxalt < 10)
	d.writeln(TD, Math.rnd(s.maxalt, 1), SP, TDE);
    else
	d.writeln(TD, Math.round(s.maxalt), SP, TDE);

    d.writeln(TRE);
}

function showprop(object)
{
    var properties = "Properties:\n";
    for (var propname in object)
	properties += propname + ": " + object[propname] + "\n";
    alert(properties);
}
