123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- define(
- [ "../impl/Record", "../impl/calendarFunctions"], function (Record, calendarFunctions) {
-
- return {
-
- SYNODIC_MONTH : 29.530588853,
- TROPICAL_YEAR : 365.242191,
- CIVIL_EPOC : 1948440,
- JD_EPOCH : 2447891.5,
- EPOCH_JULIAN_DAY : 2440588,
- SUN_ETA_G : 279.403303 * Math.PI / 180,
- SUN_OMEGA_G : 282.768422 * Math.PI / 180,
- SUN_E : 0.016713,
- DAY_MS : 24 * 60 * 60 * 1000,
- JULIAN_EPOCH_MS : -210866760000000,
- HIJRA_MS : -42521587200000,
- MOON_L0 : 318.351648 * Math.PI / 180,
- MOON_P0 : 36.340410 * Math.PI / 180,
- MOON_N0 : 318.510107 * Math.PI / 180,
- MOON_I : 5.145366 * Math.PI / 180,
- fromGregorian: function (/*Date*/ gdate) {
- // summary:
- // This function returns the equivalent Islamic Date value for the Gregorian Date
- var date = new Date(gdate);
- var localMillis = date.getTime() - date.getTimezoneOffset() * 60 * 1000;
- var julianDay = this._floorDivide(localMillis, this.DAY_MS) + this.EPOCH_JULIAN_DAY;
- var days = julianDay - this.CIVIL_EPOC;
-
- // Guess at the number of elapsed full months since the epoch
- var months = Math.floor(days / this.SYNODIC_MONTH);
- var monthStart = Math.floor(months * this.SYNODIC_MONTH - 1);
- if (days - monthStart >= 25 && this._moonAge(date.getTime()) > 0) {
- // If we're near the end of the month, assume next month and search backwards
- months++;
- }
- // Find out the last time that the new moon was actually visible at this longitude
- // This returns midnight the night that the moon was visible at sunset.
- while ((monthStart = this._trueMonthStart(months)) > days) {
- // If it was after the date in question, back up a month and try again
- months--;
- }
- var year = Math.floor(months / 12) + 1;
- var month = months % 12;
- var day = Math.floor(days - this._monthStart(year, month)) + 1;
- this.date = day;
- this.month = month;
- this.year = year;
- this.hours = date.getHours();
- this.minutes = date.getMinutes();
- this.seconds = date.getSeconds();
- this.milliseconds = date.getMilliseconds();
- this.day = date.getDay();
- return this;
- },
- toLocalTime: function (date, timeZone) {
- var islamicDate = this.fromGregorian(date);
- var dt = new Date(date);
- var result = new Record();
- result.set("weekday", timeZone === "UTC" ? dt.getUTCDay() : dt.getDay());
- result.set("era", 0);
- result.set("year", islamicDate.year);
- result.set("month", islamicDate.month);
- result.set("day", islamicDate.date);
- calendarFunctions.setTimeFields(dt, timeZone, result);
- return result;
- },
- _floorDivide: function (numerator, denominator) {
- // summary:
- // Divide two long integers, returning the floor of the quotient.
- // Unlike the built-in division, this is mathematically well-behaved.
- // E.g., -1/4 => 0
- // but _floorDivide(-1,4) => -1.
- // numerator: Long
- // The numerator
- // denominator: Long
- // A divisor which must be > 0
- // returns:
- // The floor of the quotient.
- // We do this computation in order to handle
- // a numerator of Long.MIN_VALUE correctly
- return Math.floor((numerator >= 0) ? numerator / denominator : ((numerator + 1) / denominator) - 1);
- },
- _monthStart: function (year, month) {
- // summary:
- // Return the day # on which the given month starts. Days are counted
- // From the Hijri epoch, origin 0.
- // year: Integer
- // The hijri year
- // month: Integer
- // The hijri month, 0-based
- // Normalize year/month in case month is outside the normal bounds, which may occur
- // in the case of an add operation
- var realYear = year + Math.floor(month / 12);
- var realMonth = month % 12;
- var ms = this._trueMonthStart(12 * (realYear - 1) + realMonth);
- return ms;
- },
- _trueMonthStart: function (month) {
- // summary:
- // Find the day number on which a particular month of the true/lunar
- // Islamic calendar starts.
- // month:
- // The month in question, origin 0 from the Hijri epoch
- // returns:
- // The day number on which the given month starts.
- // Make a guess at when the month started, using the average length
- var origin = this.HIJRA_MS + Math.floor(month * this.SYNODIC_MONTH) * this.DAY_MS;
- var age = this._moonAge(origin);
- if (this._moonAge(origin) >= 0) {
- // The month has already started
- do {
- origin -= this.DAY_MS;
- age = this._moonAge(origin);
- } while (age >= 0);
- }
- else {
- // Preceding month has not ended yet.
- do {
- origin += this.DAY_MS;
- age = this._moonAge(origin);
- } while (age < 0);
- }
- var start = Math.floor((origin - this.HIJRA_MS) / this.DAY_MS) + 1;
- return start;
- },
- _moonAge: function (time) {
- // summary:
- // Return the "age" of the moon at the given time; this is the difference
- // In ecliptic latitude between the moon and the sun. This method simply
- // Calls this._getMoonAge, converts to degrees,
- // And adjusts the result to be in the range [-180, 180].
- // time: Long
- // The time at which the moon's age is desired, in millis since 1/1/1970.
- this._time = time;
- var age = this._getMoonAge();
- // Convert to degrees and normalize...
- age = age * 180 / Math.PI;
- if (age > 180) {
- age = age - 360;
- }
- return age;
- },
- _getJulianDay: function () {
- // summary:
- // Get the current time of this object,
- // Expressed as a "julian day number", which is the number of elapsed
- // Days since 1/1/4713 BC (Julian), 12:00 GMT.
- var julianDay = (this._time - this.JULIAN_EPOCH_MS) / this.DAY_MS;
- return julianDay;
- },
- _getSunLongitude: function (julian) {
- // summary:
- // The longitude of the sun at the time specified by this object.
- // The longitude is measured in radians along the ecliptic
- // from the "first point of Aries," the point at which the ecliptic
- // Crosses the earth's equatorial plane at the vernal equinox.
- // Currently, this method uses an approximation of the two-body Kepler's
- // Equation for the earth and the sun. It does not take into account the
- // Perturbations caused by the other planets, the moon, etc.
- // julian: Double
- // The current time expressed as a julian day number
- // returns: Object
- var day = julian - this.JD_EPOCH; // Days since epoch
- // Find the angular distance the sun in a fictitious
- // circular orbit has travelled since the epoch.
- var epochAngle = this._norm2PI(2 * Math.PI / this.TROPICAL_YEAR * day);
- // The epoch wasn't at the sun's perigee; find the angular distance
- // since perigee, which is called the "mean anomaly"
- var meanAnomaly = this._norm2PI(epochAngle + this.SUN_ETA_G - this.SUN_OMEGA_G);
- // Now find the "true anomaly", e.g. the real solar longitude
- // by solving Kepler's equation for an elliptical orbit
- // NOTE: The 3rd ed. of the book lists omega_g and eta_g in different
- // equations; omega_g is to be correct.
- return {
- sunLongitude : this._norm2PI(this._trueAnomaly(meanAnomaly, this.SUN_E) + this.SUN_OMEGA_G),
- meanAnomalySun: meanAnomaly
- };
- },
- _getMoonAge: function () {
- // summary:
- // The "age" of the moon at the time specified in this object.
- // This is really the angle between the
- // Current ecliptic longitudes of the sun and the moon,
- // Measured in radians.
- // Calculate the solar longitude. Has the side effect of
- // filling in "meanAnomalySun" as well.
- var ret = this._getSunLongitude(this._getJulianDay());
- var sunLongitude = ret.sunLongitude;
- var meanAnomalySun = ret.meanAnomalySun;
- //
- // Find the # of days since the epoch of our orbital parameters.
- //
- var day = this._getJulianDay() - this.JD_EPOCH; // Days since epoch
-
- // Calculate the mean longitude and anomaly of the moon, based on
- // a circular orbit. Similar to the corresponding solar calculation.
- var meanLongitude = this._norm2PI(13.1763966 * Math.PI / 180 * day + this.MOON_L0);
- var meanAnomalyMoon = this._norm2PI(meanLongitude - 0.1114041 * Math.PI / 180 * day - this.MOON_P0);
- //
- // Calculate the following corrections:
- // Evection: the sun's gravity affects the moon's eccentricity
- // Annual Eqn: variation in the effect due to earth-sun distance
- // A3: correction factor (for ???)
- //
- var evection = 1.2739 * Math.PI / 180 * Math.sin(2 * (meanLongitude - sunLongitude) - meanAnomalyMoon);
- var annual = 0.1858 * Math.PI / 180 * Math.sin(meanAnomalySun);
- var a3 = 0.3700 * Math.PI / 180 * Math.sin(meanAnomalySun);
- meanAnomalyMoon += evection - annual - a3;
- //
- // More correction factors:
- // center equation of the center correction
- // a4 yet another error correction (???)
- //
- var center = 6.2886 * Math.PI / 180 * Math.sin(meanAnomalyMoon);
- var a4 = 0.2140 * Math.PI / 180 * Math.sin(2 * meanAnomalyMoon);
- // Now find the moon's corrected longitude
- var moonLongitude = meanLongitude + evection + center - annual + a4;
- //
- // And finally, find the variation, caused by the fact that the sun's
- // gravitational pull on the moon varies depending on which side of
- // the earth the moon is on
- //
- var variation = 0.6583 * Math.PI / 180 * Math.sin(2 * (moonLongitude - sunLongitude));
- moonLongitude += variation;
- //
- // What we've calculated so far is the moon's longitude in the plane
- // of its own orbit. Now map to the ecliptic to get the latitude
- // and longitude. First we need to find the longitude of the ascending
- // node, the position on the ecliptic where it is crossed by the moon's
- // orbit as it crosses from the southern to the northern hemisphere.
- //
- var nodeLongitude = this._norm2PI(this.MOON_N0 - 0.0529539 * Math.PI / 180 * day);
- nodeLongitude -= 0.16 * Math.PI / 180 * Math.sin(meanAnomalySun);
- var y = Math.sin(moonLongitude - nodeLongitude);
- var x = Math.cos(moonLongitude - nodeLongitude);
-
- var moonEclipLong = Math.atan2(y * Math.cos(this.MOON_I), x) + nodeLongitude;
- return this._norm2PI(moonEclipLong - sunLongitude);
- },
- _norm2PI: function (angle) {
- // summary:
- // Normalize an angle so that it's in the range 0 - 2pi.
- // For positive angles this is just (angle % 2pi), but the Java
- // mod operator doesn't work that way for negative numbers....
- // angle: Double
- // The angle to be normalized in radians
- // returns:
- // The normalized angle
- return angle - 2 * Math.PI * Math.floor(angle / (2 * Math.PI));
- },
- _trueAnomaly: function (meanAnomaly, eccentricity) {
- // summary:
- // Find the "true anomaly" (longitude) of an object from
- // Its mean anomaly and the eccentricity of its orbit. This uses
- // An iterative solution to Kepler's equation.
- // meanAnomaly: Double
- // The object's longitude calculated as if it were in
- // A regular, circular orbit, measured in radians
- // From the point of perigee.
- // eccentricity: Double
- // The eccentricity of the orbit
- // returns:
- // The true anomaly (longitude) measured in radians
- // First, solve Kepler's equation iteratively
- // Duffett-Smith, p.90
- var delta;
- var E = meanAnomaly;
- do {
- delta = E - eccentricity * Math.sin(E) - meanAnomaly;
- E = E - delta / (1 - eccentricity * Math.cos(E));
- }
- while (Math.abs(delta) > 1e-5); // epsilon = 1e-5 rad
- return 2.0 * Math.atan(Math.tan(E / 2) * Math.sqrt((1 + eccentricity) / (1 - eccentricity)));
- }
- };
- });
|