islamicCalendar.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. define(
  2. [ "../impl/Record", "../impl/calendarFunctions"], function (Record, calendarFunctions) {
  3. return {
  4. SYNODIC_MONTH : 29.530588853,
  5. TROPICAL_YEAR : 365.242191,
  6. CIVIL_EPOC : 1948440,
  7. JD_EPOCH : 2447891.5,
  8. EPOCH_JULIAN_DAY : 2440588,
  9. SUN_ETA_G : 279.403303 * Math.PI / 180,
  10. SUN_OMEGA_G : 282.768422 * Math.PI / 180,
  11. SUN_E : 0.016713,
  12. DAY_MS : 24 * 60 * 60 * 1000,
  13. JULIAN_EPOCH_MS : -210866760000000,
  14. HIJRA_MS : -42521587200000,
  15. MOON_L0 : 318.351648 * Math.PI / 180,
  16. MOON_P0 : 36.340410 * Math.PI / 180,
  17. MOON_N0 : 318.510107 * Math.PI / 180,
  18. MOON_I : 5.145366 * Math.PI / 180,
  19. fromGregorian: function (/*Date*/ gdate) {
  20. // summary:
  21. // This function returns the equivalent Islamic Date value for the Gregorian Date
  22. var date = new Date(gdate);
  23. var localMillis = date.getTime() - date.getTimezoneOffset() * 60 * 1000;
  24. var julianDay = this._floorDivide(localMillis, this.DAY_MS) + this.EPOCH_JULIAN_DAY;
  25. var days = julianDay - this.CIVIL_EPOC;
  26. // Guess at the number of elapsed full months since the epoch
  27. var months = Math.floor(days / this.SYNODIC_MONTH);
  28. var monthStart = Math.floor(months * this.SYNODIC_MONTH - 1);
  29. if (days - monthStart >= 25 && this._moonAge(date.getTime()) > 0) {
  30. // If we're near the end of the month, assume next month and search backwards
  31. months++;
  32. }
  33. // Find out the last time that the new moon was actually visible at this longitude
  34. // This returns midnight the night that the moon was visible at sunset.
  35. while ((monthStart = this._trueMonthStart(months)) > days) {
  36. // If it was after the date in question, back up a month and try again
  37. months--;
  38. }
  39. var year = Math.floor(months / 12) + 1;
  40. var month = months % 12;
  41. var day = Math.floor(days - this._monthStart(year, month)) + 1;
  42. this.date = day;
  43. this.month = month;
  44. this.year = year;
  45. this.hours = date.getHours();
  46. this.minutes = date.getMinutes();
  47. this.seconds = date.getSeconds();
  48. this.milliseconds = date.getMilliseconds();
  49. this.day = date.getDay();
  50. return this;
  51. },
  52. toLocalTime: function (date, timeZone) {
  53. var islamicDate = this.fromGregorian(date);
  54. var dt = new Date(date);
  55. var result = new Record();
  56. result.set("weekday", timeZone === "UTC" ? dt.getUTCDay() : dt.getDay());
  57. result.set("era", 0);
  58. result.set("year", islamicDate.year);
  59. result.set("month", islamicDate.month);
  60. result.set("day", islamicDate.date);
  61. calendarFunctions.setTimeFields(dt, timeZone, result);
  62. return result;
  63. },
  64. _floorDivide: function (numerator, denominator) {
  65. // summary:
  66. // Divide two long integers, returning the floor of the quotient.
  67. // Unlike the built-in division, this is mathematically well-behaved.
  68. // E.g., -1/4 => 0
  69. // but _floorDivide(-1,4) => -1.
  70. // numerator: Long
  71. // The numerator
  72. // denominator: Long
  73. // A divisor which must be > 0
  74. // returns:
  75. // The floor of the quotient.
  76. // We do this computation in order to handle
  77. // a numerator of Long.MIN_VALUE correctly
  78. return Math.floor((numerator >= 0) ? numerator / denominator : ((numerator + 1) / denominator) - 1);
  79. },
  80. _monthStart: function (year, month) {
  81. // summary:
  82. // Return the day # on which the given month starts. Days are counted
  83. // From the Hijri epoch, origin 0.
  84. // year: Integer
  85. // The hijri year
  86. // month: Integer
  87. // The hijri month, 0-based
  88. // Normalize year/month in case month is outside the normal bounds, which may occur
  89. // in the case of an add operation
  90. var realYear = year + Math.floor(month / 12);
  91. var realMonth = month % 12;
  92. var ms = this._trueMonthStart(12 * (realYear - 1) + realMonth);
  93. return ms;
  94. },
  95. _trueMonthStart: function (month) {
  96. // summary:
  97. // Find the day number on which a particular month of the true/lunar
  98. // Islamic calendar starts.
  99. // month:
  100. // The month in question, origin 0 from the Hijri epoch
  101. // returns:
  102. // The day number on which the given month starts.
  103. // Make a guess at when the month started, using the average length
  104. var origin = this.HIJRA_MS + Math.floor(month * this.SYNODIC_MONTH) * this.DAY_MS;
  105. var age = this._moonAge(origin);
  106. if (this._moonAge(origin) >= 0) {
  107. // The month has already started
  108. do {
  109. origin -= this.DAY_MS;
  110. age = this._moonAge(origin);
  111. } while (age >= 0);
  112. }
  113. else {
  114. // Preceding month has not ended yet.
  115. do {
  116. origin += this.DAY_MS;
  117. age = this._moonAge(origin);
  118. } while (age < 0);
  119. }
  120. var start = Math.floor((origin - this.HIJRA_MS) / this.DAY_MS) + 1;
  121. return start;
  122. },
  123. _moonAge: function (time) {
  124. // summary:
  125. // Return the "age" of the moon at the given time; this is the difference
  126. // In ecliptic latitude between the moon and the sun. This method simply
  127. // Calls this._getMoonAge, converts to degrees,
  128. // And adjusts the result to be in the range [-180, 180].
  129. // time: Long
  130. // The time at which the moon's age is desired, in millis since 1/1/1970.
  131. this._time = time;
  132. var age = this._getMoonAge();
  133. // Convert to degrees and normalize...
  134. age = age * 180 / Math.PI;
  135. if (age > 180) {
  136. age = age - 360;
  137. }
  138. return age;
  139. },
  140. _getJulianDay: function () {
  141. // summary:
  142. // Get the current time of this object,
  143. // Expressed as a "julian day number", which is the number of elapsed
  144. // Days since 1/1/4713 BC (Julian), 12:00 GMT.
  145. var julianDay = (this._time - this.JULIAN_EPOCH_MS) / this.DAY_MS;
  146. return julianDay;
  147. },
  148. _getSunLongitude: function (julian) {
  149. // summary:
  150. // The longitude of the sun at the time specified by this object.
  151. // The longitude is measured in radians along the ecliptic
  152. // from the "first point of Aries," the point at which the ecliptic
  153. // Crosses the earth's equatorial plane at the vernal equinox.
  154. // Currently, this method uses an approximation of the two-body Kepler's
  155. // Equation for the earth and the sun. It does not take into account the
  156. // Perturbations caused by the other planets, the moon, etc.
  157. // julian: Double
  158. // The current time expressed as a julian day number
  159. // returns: Object
  160. var day = julian - this.JD_EPOCH; // Days since epoch
  161. // Find the angular distance the sun in a fictitious
  162. // circular orbit has travelled since the epoch.
  163. var epochAngle = this._norm2PI(2 * Math.PI / this.TROPICAL_YEAR * day);
  164. // The epoch wasn't at the sun's perigee; find the angular distance
  165. // since perigee, which is called the "mean anomaly"
  166. var meanAnomaly = this._norm2PI(epochAngle + this.SUN_ETA_G - this.SUN_OMEGA_G);
  167. // Now find the "true anomaly", e.g. the real solar longitude
  168. // by solving Kepler's equation for an elliptical orbit
  169. // NOTE: The 3rd ed. of the book lists omega_g and eta_g in different
  170. // equations; omega_g is to be correct.
  171. return {
  172. sunLongitude : this._norm2PI(this._trueAnomaly(meanAnomaly, this.SUN_E) + this.SUN_OMEGA_G),
  173. meanAnomalySun: meanAnomaly
  174. };
  175. },
  176. _getMoonAge: function () {
  177. // summary:
  178. // The "age" of the moon at the time specified in this object.
  179. // This is really the angle between the
  180. // Current ecliptic longitudes of the sun and the moon,
  181. // Measured in radians.
  182. // Calculate the solar longitude. Has the side effect of
  183. // filling in "meanAnomalySun" as well.
  184. var ret = this._getSunLongitude(this._getJulianDay());
  185. var sunLongitude = ret.sunLongitude;
  186. var meanAnomalySun = ret.meanAnomalySun;
  187. //
  188. // Find the # of days since the epoch of our orbital parameters.
  189. //
  190. var day = this._getJulianDay() - this.JD_EPOCH; // Days since epoch
  191. // Calculate the mean longitude and anomaly of the moon, based on
  192. // a circular orbit. Similar to the corresponding solar calculation.
  193. var meanLongitude = this._norm2PI(13.1763966 * Math.PI / 180 * day + this.MOON_L0);
  194. var meanAnomalyMoon = this._norm2PI(meanLongitude - 0.1114041 * Math.PI / 180 * day - this.MOON_P0);
  195. //
  196. // Calculate the following corrections:
  197. // Evection: the sun's gravity affects the moon's eccentricity
  198. // Annual Eqn: variation in the effect due to earth-sun distance
  199. // A3: correction factor (for ???)
  200. //
  201. var evection = 1.2739 * Math.PI / 180 * Math.sin(2 * (meanLongitude - sunLongitude) - meanAnomalyMoon);
  202. var annual = 0.1858 * Math.PI / 180 * Math.sin(meanAnomalySun);
  203. var a3 = 0.3700 * Math.PI / 180 * Math.sin(meanAnomalySun);
  204. meanAnomalyMoon += evection - annual - a3;
  205. //
  206. // More correction factors:
  207. // center equation of the center correction
  208. // a4 yet another error correction (???)
  209. //
  210. var center = 6.2886 * Math.PI / 180 * Math.sin(meanAnomalyMoon);
  211. var a4 = 0.2140 * Math.PI / 180 * Math.sin(2 * meanAnomalyMoon);
  212. // Now find the moon's corrected longitude
  213. var moonLongitude = meanLongitude + evection + center - annual + a4;
  214. //
  215. // And finally, find the variation, caused by the fact that the sun's
  216. // gravitational pull on the moon varies depending on which side of
  217. // the earth the moon is on
  218. //
  219. var variation = 0.6583 * Math.PI / 180 * Math.sin(2 * (moonLongitude - sunLongitude));
  220. moonLongitude += variation;
  221. //
  222. // What we've calculated so far is the moon's longitude in the plane
  223. // of its own orbit. Now map to the ecliptic to get the latitude
  224. // and longitude. First we need to find the longitude of the ascending
  225. // node, the position on the ecliptic where it is crossed by the moon's
  226. // orbit as it crosses from the southern to the northern hemisphere.
  227. //
  228. var nodeLongitude = this._norm2PI(this.MOON_N0 - 0.0529539 * Math.PI / 180 * day);
  229. nodeLongitude -= 0.16 * Math.PI / 180 * Math.sin(meanAnomalySun);
  230. var y = Math.sin(moonLongitude - nodeLongitude);
  231. var x = Math.cos(moonLongitude - nodeLongitude);
  232. var moonEclipLong = Math.atan2(y * Math.cos(this.MOON_I), x) + nodeLongitude;
  233. return this._norm2PI(moonEclipLong - sunLongitude);
  234. },
  235. _norm2PI: function (angle) {
  236. // summary:
  237. // Normalize an angle so that it's in the range 0 - 2pi.
  238. // For positive angles this is just (angle % 2pi), but the Java
  239. // mod operator doesn't work that way for negative numbers....
  240. // angle: Double
  241. // The angle to be normalized in radians
  242. // returns:
  243. // The normalized angle
  244. return angle - 2 * Math.PI * Math.floor(angle / (2 * Math.PI));
  245. },
  246. _trueAnomaly: function (meanAnomaly, eccentricity) {
  247. // summary:
  248. // Find the "true anomaly" (longitude) of an object from
  249. // Its mean anomaly and the eccentricity of its orbit. This uses
  250. // An iterative solution to Kepler's equation.
  251. // meanAnomaly: Double
  252. // The object's longitude calculated as if it were in
  253. // A regular, circular orbit, measured in radians
  254. // From the point of perigee.
  255. // eccentricity: Double
  256. // The eccentricity of the orbit
  257. // returns:
  258. // The true anomaly (longitude) measured in radians
  259. // First, solve Kepler's equation iteratively
  260. // Duffett-Smith, p.90
  261. var delta;
  262. var E = meanAnomaly;
  263. do {
  264. delta = E - eccentricity * Math.sin(E) - meanAnomaly;
  265. E = E - delta / (1 - eccentricity * Math.cos(E));
  266. }
  267. while (Math.abs(delta) > 1e-5); // epsilon = 1e-5 rad
  268. return 2.0 * Math.atan(Math.tan(E / 2) * Math.sqrt((1 + eccentricity) / (1 - eccentricity)));
  269. }
  270. };
  271. });