<commit_before>/**
 * Copyright (c) 2010-2014, openHAB.org and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.openhab.binding.astro.internal.calc;

import java.util.Calendar;

import org.apache.commons.lang.time.DateUtils;
import org.openhab.binding.astro.internal.model.Range;
import org.openhab.binding.astro.internal.model.Sun;
import org.openhab.binding.astro.internal.model.SunPosition;

/**
 * Calculates the SunPosition (azimuth, elevation) and Sun data.
 * 
 * @author <NAME>
 * @since 1.6.0
 * @see based on the calculations of http://www.suncalc.net
 */
public class SunCalc {
	private static final double J1970 = 2440588.0;
	private static final double J2000 = 2451545.0;
	private static final double DEG2RAD = Math.PI / 180;
	private static final double M0 = 357.5291 * DEG2RAD;
	private static final double M1 = 0.98560028 * DEG2RAD;
	private static final double J0 = 0.0009;
	private static final double J1 = 0.0053;
	private static final double J2 = -0.0069;
	private static final double C1 = 1.9148 * DEG2RAD;
	private static final double C2 = 0.0200 * DEG2RAD;
	private static final double C3 = 0.0003 * DEG2RAD;
	private static final double P = 102.9372 * DEG2RAD;
	private static final double E = 23.45 * DEG2RAD;
	private static final double TH0 = 280.1600 * DEG2RAD;
	private static final double TH1 = 360.9856235 * DEG2RAD;
	private static final double SUN_ANGLE = -0.83;
	private static final double SUN_DIAMETER = 0.53 * DEG2RAD; // sun diameter
	private static final double H0 = SUN_ANGLE * DEG2RAD;
	private static final double H1 = -6.0 * DEG2RAD; // nautical twilight angle
	private static final double H2 = -12.0 * DEG2RAD; // astronomical twilight angle
	private static final double H3 = -18.0 * DEG2RAD; // darkness angle
	private static final double MINUTES_PER_DAY = 60 * 24;
	private static final double MILLISECONDS_PER_DAY = 1000 * 60 * MINUTES_PER_DAY;
	private static final int CURVE_TIME_INTERVAL = 20; // 20 minutes
	private static final double JD_ONE_MINUTE_FRACTION = 1.0 / 60 / 24;

	/**
	 * Calculates the sun position (azimuth and elevation).
	 */
	public SunPosition getSunPosition(Calendar calendar, double latitude, double longitude) {
		double lw = -longitude * DEG2RAD;
		double phi = latitude * DEG2RAD;

		double j = dateToJulianDate(calendar);
		double m = getSolarMeanAnomaly(j);
		double c = getEquationOfCenter(m);
		double lsun = getEclipticLongitude(m, c);
		double d = getSunDeclination(lsun);
		double a = getRightAscension(lsun);
		double th = getSiderealTime(j, lw);

		double azimuth = getAzimuth(th, a, phi, d) / DEG2RAD;
		double elevation = getElevation(th, a, phi, d) / DEG2RAD;

		return new SunPosition(azimuth + 180, elevation);
	}

	/**
	 * Returns true, if the sun is up all day (no rise and set).
	 */
	private boolean isSunUpAllDay(Calendar calendar, double latitude, double longitude) {
		Calendar cal = truncateToMidnight(calendar);
		for (int minutes = 0; minutes <= MINUTES_PER_DAY; minutes += CURVE_TIME_INTERVAL) {
			double elevation = getSunPosition(cal, latitude, longitude).getElevation();
			if (elevation < SUN_ANGLE) {
				return false;
			}
			cal.add(Calendar.MINUTE, CURVE_TIME_INTERVAL);
		}
		return true;
	}

	/**
	 * Calculates all sun rise and sets at the specified coordinates.
	 */
	public Sun getSunInfo(Calendar calendar, double latitude, double longitude) {
		return getSunInfo(calendar, latitude, longitude, false);
	}

	private Sun getSunInfo(Calendar calendar, double latitude, double longitude, boolean onlyAstro) {
		double lw = -longitude * DEG2RAD;
		double phi = latitude * DEG2RAD;
		double j = midnightDateToJulianDate(calendar);
		double n = getJulianCycle(j, lw);
		double js = getApproxSolarTransit(0, lw, n);
		double m = getSolarMeanAnomaly(js);
		double c = getEquationOfCenter(m);
		double lsun = getEclipticLongitude(m, c);
		double d = getSunDeclination(lsun);
		double jtransit = getSolarTransit(js, m, lsun);
		double w0 = getHourAngle(H0, phi, d);
		double w1 = getHourAngle(H0 + SUN_DIAMETER, phi, d);
		double jset = getSunsetJulianDate(w0, m, lsun, lw, n);
		double jsetstart = getSunsetJulianDate(w1, m, lsun, lw, n);
		double jrise = getSunriseJulianDate(jtransit, jset);
		double jriseend = getSunriseJulianDate(jtransit, jsetstart);
		double w2 = getHourAngle(H1, phi, d);
		double jnau = getSunsetJulianDate(w2, m, lsun, lw, n);
		double Jciv2 = getSunriseJulianDate(jtransit, jnau);

		double w3 = getHourAngle(H2, phi, d);
		double w4 = getHourAngle(H3, phi, d);
		double jastro = getSunsetJulianDate(w3, m, lsun, lw, n);
		double jdark = getSunsetJulianDate(w4, m, lsun, lw, n);
		double jnau2 = getSunriseJulianDate(jtransit, jastro);
		double jastro2 = getSunriseJulianDate(jtransit, jdark);

		Sun sun = new Sun();
		sun.setAstroDawn(new Range(julianDateToDate(jastro2), julianDateToDate(jnau2)));
		sun.setAstroDusk(new Range(julianDateToDate(jastro), julianDateToDate(jdark)));

		if (onlyAstro) {
			return sun;
		}

		sun.setNoon(new Range(julianDateToDate(jtransit), julianDateToDate(jtransit + JD_ONE_MINUTE_FRACTION)));
		sun.setRise(new Range(julianDateToDate(jrise), julianDateToDate(jriseend)));
		sun.setSet(new Range(julianDateToDate(jsetstart), julianDateToDate(jset)));

		sun.setCivilDawn(new Range(julianDateToDate(Jciv2), julianDateToDate(jrise)));
		sun.setCivilDusk(new Range(julianDateToDate(jset), julianDateToDate(jnau)));

		sun.setNauticDawn(new Range(julianDateToDate(jnau2), julianDateToDate(Jciv2)));
		sun.setNauticDusk(new Range(julianDateToDate(jnau), julianDateToDate(jastro)));

		boolean isSunUpAllDay = isSunUpAllDay(calendar, latitude, longitude);
		
		// daylight
		Range daylightRange = new Range();
		if (sun.getRise().getStart() == null && sun.getRise().getEnd() == null) {
			if (isSunUpAllDay) {
				daylightRange = new Range(truncateToMidnight(calendar), truncateToMidnight(addDays(calendar, 1)));
			}
		} else {
			daylightRange = new Range(sun.getRise().getEnd(), sun.getSet().getStart());
		}
		sun.setDaylight(daylightRange);

		// morning night
		Sun sunYesterday = getSunInfo(addDays(calendar, -1), latitude, longitude, true);
		Range morningNightRange = null;
		if (sunYesterday.getAstroDusk().getEnd() != null
				&& DateUtils.isSameDay(sunYesterday.getAstroDusk().getEnd(), calendar)) {
			morningNightRange = new Range(sunYesterday.getAstroDusk().getEnd(), sun.getAstroDawn().getStart());
		} else if (isSunUpAllDay) {
			morningNightRange = new Range();
		} else {
			morningNightRange = new Range(truncateToMidnight(calendar), sun.getAstroDawn().getStart());
		}
		sun.setMorningNight(morningNightRange);

		// evening night
		Range eveningNightRange = null;
		if (sun.getAstroDusk().getEnd() != null && DateUtils.isSameDay(sun.getAstroDusk().getEnd(), calendar)) {
			eveningNightRange = new Range(sun.getAstroDusk().getEnd(), truncateToMidnight(addDays(calendar, 1)));
		} else {
			eveningNightRange = new Range();
		}
		sun.setEveningNight(eveningNightRange);

		// night
		if (isSunUpAllDay) {
			sun.setNight(new Range());
		}
		else {
			Sun sunTomorrow = getSunInfo(addDays(calendar, 1), latitude, longitude, true);
			sun.setNight(new Range(eveningNightRange.getStart(), sunTomorrow.getAstroDawn().getStart()));
		}
		
		return sun;
	}

	/**
	 * Returns the julian date from the calendar object.
	 */
	public double dateToJulianDate(Calendar calendar) {
		return calendar.getTimeInMillis() / MILLISECONDS_PER_DAY - 0.5 + J1970;
	}

	/**
	 * Returns the midnight julian date from the calendar object.
	 */
	public double midnightDateToJulianDate(Calendar calendar) {
		return truncateToMidnight(calendar).getTimeInMillis() / MILLISECONDS_PER_DAY + J1970;<commit_msg>Fixed start of the night.<commit_after>/**
 * Copyright (c) 2010-2014, openHAB.org and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.openhab.binding.astro.internal.calc;

import java.util.Calendar;

import org.apache.commons.lang.time.DateUtils;
import org.openhab.binding.astro.internal.model.Range;
import org.openhab.binding.astro.internal.model.Sun;
import org.openhab.binding.astro.internal.model.SunPosition;

/**
 * Calculates the SunPosition (azimuth, elevation) and Sun data.
 * 
 * @author <NAME>
 * @since 1.6.0
 * @see based on the calculations of http://www.suncalc.net
 */
public class SunCalc {
	private static final double J1970 = 2440588.0;
	private static final double J2000 = 2451545.0;
	private static final double DEG2RAD = Math.PI / 180;
	private static final double M0 = 357.5291 * DEG2RAD;
	private static final double M1 = 0.98560028 * DEG2RAD;
	private static final double J0 = 0.0009;
	private static final double J1 = 0.0053;
	private static final double J2 = -0.0069;
	private static final double C1 = 1.9148 * DEG2RAD;
	private static final double C2 = 0.0200 * DEG2RAD;
	private static final double C3 = 0.0003 * DEG2RAD;
	private static final double P = 102.9372 * DEG2RAD;
	private static final double E = 23.45 * DEG2RAD;
	private static final double TH0 = 280.1600 * DEG2RAD;
	private static final double TH1 = 360.9856235 * DEG2RAD;
	private static final double SUN_ANGLE = -0.83;
	private static final double SUN_DIAMETER = 0.53 * DEG2RAD; // sun diameter
	private static final double H0 = SUN_ANGLE * DEG2RAD;
	private static final double H1 = -6.0 * DEG2RAD; // nautical twilight angle
	private static final double H2 = -12.0 * DEG2RAD; // astronomical twilight angle
	private static final double H3 = -18.0 * DEG2RAD; // darkness angle
	private static final double MINUTES_PER_DAY = 60 * 24;
	private static final double MILLISECONDS_PER_DAY = 1000 * 60 * MINUTES_PER_DAY;
	private static final int CURVE_TIME_INTERVAL = 20; // 20 minutes
	private static final double JD_ONE_MINUTE_FRACTION = 1.0 / 60 / 24;

	/**
	 * Calculates the sun position (azimuth and elevation).
	 */
	public SunPosition getSunPosition(Calendar calendar, double latitude, double longitude) {
		double lw = -longitude * DEG2RAD;
		double phi = latitude * DEG2RAD;

		double j = dateToJulianDate(calendar);
		double m = getSolarMeanAnomaly(j);
		double c = getEquationOfCenter(m);
		double lsun = getEclipticLongitude(m, c);
		double d = getSunDeclination(lsun);
		double a = getRightAscension(lsun);
		double th = getSiderealTime(j, lw);

		double azimuth = getAzimuth(th, a, phi, d) / DEG2RAD;
		double elevation = getElevation(th, a, phi, d) / DEG2RAD;

		return new SunPosition(azimuth + 180, elevation);
	}

	/**
	 * Returns true, if the sun is up all day (no rise and set).
	 */
	private boolean isSunUpAllDay(Calendar calendar, double latitude, double longitude) {
		Calendar cal = truncateToMidnight(calendar);
		for (int minutes = 0; minutes <= MINUTES_PER_DAY; minutes += CURVE_TIME_INTERVAL) {
			double elevation = getSunPosition(cal, latitude, longitude).getElevation();
			if (elevation < SUN_ANGLE) {
				return false;
			}
			cal.add(Calendar.MINUTE, CURVE_TIME_INTERVAL);
		}
		return true;
	}

	/**
	 * Calculates all sun rise and sets at the specified coordinates.
	 */
	public Sun getSunInfo(Calendar calendar, double latitude, double longitude) {
		return getSunInfo(calendar, latitude, longitude, false);
	}

	private Sun getSunInfo(Calendar calendar, double latitude, double longitude, boolean onlyAstro) {
		double lw = -longitude * DEG2RAD;
		double phi = latitude * DEG2RAD;
		double j = midnightDateToJulianDate(calendar);
		double n = getJulianCycle(j, lw);
		double js = getApproxSolarTransit(0, lw, n);
		double m = getSolarMeanAnomaly(js);
		double c = getEquationOfCenter(m);
		double lsun = getEclipticLongitude(m, c);
		double d = getSunDeclination(lsun);
		double jtransit = getSolarTransit(js, m, lsun);
		double w0 = getHourAngle(H0, phi, d);
		double w1 = getHourAngle(H0 + SUN_DIAMETER, phi, d);
		double jset = getSunsetJulianDate(w0, m, lsun, lw, n);
		double jsetstart = getSunsetJulianDate(w1, m, lsun, lw, n);
		double jrise = getSunriseJulianDate(jtransit, jset);
		double jriseend = getSunriseJulianDate(jtransit, jsetstart);
		double w2 = getHourAngle(H1, phi, d);
		double jnau = getSunsetJulianDate(w2, m, lsun, lw, n);
		double Jciv2 = getSunriseJulianDate(jtransit, jnau);

		double w3 = getHourAngle(H2, phi, d);
		double w4 = getHourAngle(H3, phi, d);
		double jastro = getSunsetJulianDate(w3, m, lsun, lw, n);
		double jdark = getSunsetJulianDate(w4, m, lsun, lw, n);
		double jnau2 = getSunriseJulianDate(jtransit, jastro);
		double jastro2 = getSunriseJulianDate(jtransit, jdark);

		Sun sun = new Sun();
		sun.setAstroDawn(new Range(julianDateToDate(jastro2), julianDateToDate(jnau2)));
		sun.setAstroDusk(new Range(julianDateToDate(jastro), julianDateToDate(jdark)));

		if (onlyAstro) {
			return sun;
		}

		sun.setNoon(new Range(julianDateToDate(jtransit), julianDateToDate(jtransit + JD_ONE_MINUTE_FRACTION)));
		sun.setRise(new Range(julianDateToDate(jrise), julianDateToDate(jriseend)));
		sun.setSet(new Range(julianDateToDate(jsetstart), julianDateToDate(jset)));

		sun.setCivilDawn(new Range(julianDateToDate(Jciv2), julianDateToDate(jrise)));
		sun.setCivilDusk(new Range(julianDateToDate(jset), julianDateToDate(jnau)));

		sun.setNauticDawn(new Range(julianDateToDate(jnau2), julianDateToDate(Jciv2)));
		sun.setNauticDusk(new Range(julianDateToDate(jnau), julianDateToDate(jastro)));

		boolean isSunUpAllDay = isSunUpAllDay(calendar, latitude, longitude);
		
		// daylight
		Range daylightRange = new Range();
		if (sun.getRise().getStart() == null && sun.getRise().getEnd() == null) {
			if (isSunUpAllDay) {
				daylightRange = new Range(truncateToMidnight(calendar), truncateToMidnight(addDays(calendar, 1)));
			}
		} else {
			daylightRange = new Range(sun.getRise().getEnd(), sun.getSet().getStart());
		}
		sun.setDaylight(daylightRange);

		// morning night
		Sun sunYesterday = getSunInfo(addDays(calendar, -1), latitude, longitude, true);
		Range morningNightRange = null;
		if (sunYesterday.getAstroDusk().getEnd() != null
				&& DateUtils.isSameDay(sunYesterday.getAstroDusk().getEnd(), calendar)) {
			morningNightRange = new Range(sunYesterday.getAstroDusk().getEnd(), sun.getAstroDawn().getStart());
		} else if (isSunUpAllDay) {
			morningNightRange = new Range();
		} else {
			morningNightRange = new Range(truncateToMidnight(calendar), sun.getAstroDawn().getStart());
		}
		sun.setMorningNight(morningNightRange);

		// evening night
		Range eveningNightRange = null;
		if (sun.getAstroDusk().getEnd() != null && DateUtils.isSameDay(sun.getAstroDusk().getEnd(), calendar)) {
			eveningNightRange = new Range(sun.getAstroDusk().getEnd(), truncateToMidnight(addDays(calendar, 1)));
		} else {
			eveningNightRange = new Range();
		}
		sun.setEveningNight(eveningNightRange);

		// night
		if (isSunUpAllDay) {
			sun.setNight(new Range());
		}
		else {
			Sun sunTomorrow = getSunInfo(addDays(calendar, 1), latitude, longitude, true);
			sun.setNight(new Range(sun.getAstroDusk().getEnd(), sunTomorrow.getAstroDawn().getStart()));
		}
		
		return sun;
	}

	/**
	 * Returns the julian date from the calendar object.
	 */
	public double dateToJulianDate(Calendar calendar) {
		return calendar.getTimeInMillis() / MILLISECONDS_PER_DAY - 0.5 + J1970;
	}

	/**
	 * Returns the midnight julian date from the calendar object.
	 */
	public double midnightDateToJulianDate(Calendar calendar) {
		return truncateToMidnight(calendar).getTimeInMillis() / MILLISECONDS_PER_DAY + J1970;
{'fraction_non_alphanumeric': 0.11308807699613753, 'fraction_numerical': 0.028746913189387702, 'mean_word_length': 4.691531531531531, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5665684', 'n_tokens_mistral': 6244, 'n_tokens_neox': 5406, 'n_words': 1373}