Coverage Report

Created: 2025-06-13 06:43

/src/php-src/ext/date/lib/astro.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * The MIT License (MIT)
3
 *
4
 * Copyright (c) 2015-2019 Derick Rethans
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in
14
 * all copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
 * THE SOFTWARE.
23
 */
24
/*
25
   | Algorithms are taken from a public domain source by Paul             |
26
   | Schlyter, who wrote this in December 1992                            |
27
 */
28
29
#include "timelib.h"
30
#include <stdio.h>
31
#include <math.h>
32
33
#define days_since_2000_Jan_0(y,m,d) \
34
  (367L*(y)-((7*((y)+(((m)+9)/12)))/4)+((275*(m))/9)+(d)-730530L)
35
36
#ifndef PI
37
0
# define PI        3.1415926535897932384
38
#endif
39
40
0
#define RADEG     ( 180.0 / PI )
41
0
#define DEGRAD    ( PI / 180.0 )
42
43
/* The trigonometric functions in degrees */
44
45
0
#define sind(x)  sin((x)*DEGRAD)
46
0
#define cosd(x)  cos((x)*DEGRAD)
47
#define tand(x)  tan((x)*DEGRAD)
48
49
#define atand(x)    (RADEG*atan(x))
50
#define asind(x)    (RADEG*asin(x))
51
0
#define acosd(x)    (RADEG*acos(x))
52
0
#define atan2d(y,x) (RADEG*atan2(y,x))
53
54
55
/* Following are some macros around the "workhorse" function __daylen__ */
56
/* They mainly fill in the desired values for the reference altitude    */
57
/* below the horizon, and also selects whether this altitude should     */
58
/* refer to the Sun's center or its upper limb.                         */
59
60
61
#include "astro.h"
62
63
/******************************************************************/
64
/* This function reduces any angle to within the first revolution */
65
/* by subtracting or adding even multiples of 360.0 until the     */
66
/* result is >= 0.0 and < 360.0                                   */
67
/******************************************************************/
68
69
0
#define INV360    (1.0 / 360.0)
70
71
/*****************************************/
72
/* Reduce angle to within 0..360 degrees */
73
/*****************************************/
74
static double astro_revolution(double x)
75
0
{
76
0
  return (x - 360.0 * floor(x * INV360));
77
0
}
78
79
/*********************************************/
80
/* Reduce angle to within +180..+180 degrees */
81
/*********************************************/
82
static double astro_rev180( double x )
83
0
{
84
0
  return (x - 360.0 * floor(x * INV360 + 0.5));
85
0
}
86
87
/*******************************************************************/
88
/* This function computes GMST0, the Greenwich Mean Sidereal Time  */
89
/* at 0h UT (i.e. the sidereal time at the Greenwhich meridian at  */
90
/* 0h UT).  GMST is then the sidereal time at Greenwich at any     */
91
/* time of the day.  I've generalized GMST0 as well, and define it */
92
/* as:  GMST0 = GMST - UT  --  this allows GMST0 to be computed at */
93
/* other times than 0h UT as well.  While this sounds somewhat     */
94
/* contradictory, it is very practical:  instead of computing      */
95
/* GMST like:                                                      */
96
/*                                                                 */
97
/*  GMST = (GMST0) + UT * (366.2422/365.2422)                      */
98
/*                                                                 */
99
/* where (GMST0) is the GMST last time UT was 0 hours, one simply  */
100
/* computes:                                                       */
101
/*                                                                 */
102
/*  GMST = GMST0 + UT                                              */
103
/*                                                                 */
104
/* where GMST0 is the GMST "at 0h UT" but at the current moment!   */
105
/* Defined in this way, GMST0 will increase with about 4 min a     */
106
/* day.  It also happens that GMST0 (in degrees, 1 hr = 15 degr)   */
107
/* is equal to the Sun's mean longitude plus/minus 180 degrees!    */
108
/* (if we neglect aberration, which amounts to 20 seconds of arc   */
109
/* or 1.33 seconds of time)                                        */
110
/*                                                                 */
111
/*******************************************************************/
112
113
static double astro_GMST0(double d)
114
0
{
115
0
  double sidtim0;
116
  /* Sidtime at 0h UT = L (Sun's mean longitude) + 180.0 degr  */
117
  /* L = M + w, as defined in sunpos().  Since I'm too lazy to */
118
  /* add these numbers, I'll let the C compiler do it for me.  */
119
  /* Any decent C compiler will add the constants at compile   */
120
  /* time, imposing no runtime or code overhead.               */
121
0
  sidtim0 = astro_revolution((180.0 + 356.0470 + 282.9404) + (0.9856002585 + 4.70935E-5) * d);
122
0
  return sidtim0;
123
0
}
124
125
/* This function computes the Sun's position at any instant */
126
127
/******************************************************/
128
/* Computes the Sun's ecliptic longitude and distance */
129
/* at an instant given in d, number of days since     */
130
/* 2000 Jan 0.0.  The Sun's ecliptic latitude is not  */
131
/* computed, since it's always very near 0.           */
132
/******************************************************/
133
static void astro_sunpos(double d, double *lon, double *r)
134
0
{
135
0
  double M,         /* Mean anomaly of the Sun */
136
0
         w,         /* Mean longitude of perihelion */
137
                    /* Note: Sun's mean longitude = M + w */
138
0
         e,         /* Eccentricity of Earth's orbit */
139
0
         E,         /* Eccentric anomaly */
140
0
         x, y,      /* x, y coordinates in orbit */
141
0
         v;         /* True anomaly */
142
143
  /* Compute mean elements */
144
0
  M = astro_revolution(356.0470 + 0.9856002585 * d);
145
0
  w = 282.9404 + 4.70935E-5 * d;
146
0
  e = 0.016709 - 1.151E-9 * d;
147
148
  /* Compute true longitude and radius vector */
149
0
  E = M + e * RADEG * sind(M) * (1.0 + e * cosd(M));
150
0
  x = cosd(E) - e;
151
0
  y = sqrt(1.0 - e*e) * sind(E);
152
0
  *r = sqrt(x*x + y*y);              /* Solar distance */
153
0
  v = atan2d(y, x);                  /* True anomaly */
154
0
  *lon = v + w;                        /* True solar longitude */
155
0
  if (*lon >= 360.0) {
156
0
    *lon -= 360.0;                   /* Make it 0..360 degrees */
157
0
  }
158
0
}
159
160
static void astro_sun_RA_dec(double d, double *RA, double *dec, double *r)
161
0
{
162
0
  double lon, obl_ecl, x, y, z;
163
164
  /* Compute Sun's ecliptical coordinates */
165
0
  astro_sunpos(d, &lon, r);
166
167
  /* Compute ecliptic rectangular coordinates (z=0) */
168
0
  x = *r * cosd(lon);
169
0
  y = *r * sind(lon);
170
171
  /* Compute obliquity of ecliptic (inclination of Earth's axis) */
172
0
  obl_ecl = 23.4393 - 3.563E-7 * d;
173
174
  /* Convert to equatorial rectangular coordinates - x is unchanged */
175
0
  z = y * sind(obl_ecl);
176
0
  y = y * cosd(obl_ecl);
177
178
  /* Convert to spherical coordinates */
179
0
  *RA = atan2d(y, x);
180
0
  *dec = atan2d(z, sqrt(x*x + y*y));
181
0
}
182
183
/**
184
 * Note: timestamp = unixtimestamp (NEEDS to be 00:00:00 UT)
185
 *       Eastern longitude positive, Western longitude negative
186
 *       Northern latitude positive, Southern latitude negative
187
 *       The longitude value IS critical in this function!
188
 *       altit = the altitude which the Sun should cross
189
 *               Set to -35/60 degrees for rise/set, -6 degrees
190
 *               for civil, -12 degrees for nautical and -18
191
 *               degrees for astronomical twilight.
192
 *         upper_limb: non-zero -> upper limb, zero -> center
193
 *               Set to non-zero (e.g. 1) when computing rise/set
194
 *               times, and to zero when computing start/end of
195
 *               twilight.
196
 *        *rise = where to store the rise time
197
 *        *set  = where to store the set  time
198
 *                Both times are relative to the specified altitude,
199
 *                and thus this function can be used to compute
200
 *                various twilight times, as well as rise/set times
201
 * Return value:  0 = sun rises/sets this day, times stored at
202
 *                    *trise and *tset.
203
 *               +1 = sun above the specified "horizon" 24 hours.
204
 *                    *trise set to time when the sun is at south,
205
 *                    minus 12 hours while *tset is set to the south
206
 *                    time plus 12 hours. "Day" length = 24 hours
207
 *               -1 = sun is below the specified "horizon" 24 hours
208
 *                    "Day" length = 0 hours, *trise and *tset are
209
 *                    both set to the time when the sun is at south.
210
 *
211
 */
212
int timelib_astro_rise_set_altitude(timelib_time *t_loc, double lon, double lat, double altit, int upper_limb, double *h_rise, double *h_set, timelib_sll *ts_rise, timelib_sll *ts_set, timelib_sll *ts_transit)
213
0
{
214
0
  double  d,  /* Days since 2000 Jan 0.0 (negative before) */
215
0
  sr,         /* Solar distance, astronomical units */
216
0
  sRA,        /* Sun's Right Ascension */
217
0
  sdec,       /* Sun's declination */
218
0
  sradius,    /* Sun's apparent radius */
219
0
  t,          /* Diurnal arc */
220
0
  tsouth,     /* Time when Sun is at south */
221
0
  sidtime;    /* Local sidereal time */
222
0
  timelib_time *t_utc;
223
0
  timelib_sll   timestamp, old_sse;
224
225
0
  int rc = 0; /* Return cde from function - usually 0 */
226
227
  /* Normalize time */
228
0
  old_sse = t_loc->sse;
229
0
  t_loc->h = 12;
230
0
  t_loc->i = t_loc->s = 0;
231
0
  timelib_update_ts(t_loc, NULL);
232
233
  /* Calculate TS belonging to UTC 00:00 of the current day, for input into
234
   * the algorithm */
235
0
  t_utc = timelib_time_ctor();
236
0
  t_utc->y = t_loc->y;
237
0
  t_utc->m = t_loc->m;
238
0
  t_utc->d = t_loc->d;
239
0
  t_utc->h = t_utc->i = t_utc->s = 0;
240
0
  timelib_update_ts(t_utc, NULL);
241
242
  /* Compute d of 12h local mean solar time */
243
0
  timestamp = t_utc->sse;
244
0
  d = timelib_ts_to_j2000(timestamp) + 2 - lon/360.0;
245
246
  /* Compute local sidereal time of this moment */
247
0
  sidtime = astro_revolution(astro_GMST0(d) + 180.0 + lon);
248
249
  /* Compute Sun's RA + Decl at this moment */
250
0
  astro_sun_RA_dec( d, &sRA, &sdec, &sr );
251
252
  /* Compute time when Sun is at south - in hours UT */
253
0
  tsouth = 12.0 - astro_rev180(sidtime - sRA) / 15.0;
254
255
  /* Compute the Sun's apparent radius, degrees */
256
0
  sradius = 0.2666 / sr;
257
258
  /* Do correction to upper limb, if necessary */
259
0
  if (upper_limb) {
260
0
    altit -= sradius;
261
0
  }
262
263
  /* Compute the diurnal arc that the Sun traverses to reach */
264
  /* the specified altitude altit: */
265
0
  {
266
0
    double cost;
267
0
    cost = (sind(altit) - sind(lat) * sind(sdec)) / (cosd(lat) * cosd(sdec));
268
0
    *ts_transit = t_utc->sse + (tsouth * 3600);
269
0
    if (cost >= 1.0) {
270
0
      rc = -1;
271
0
      t = 0.0;       /* Sun always below altit */
272
273
0
      *ts_rise = *ts_set = t_utc->sse + (tsouth * 3600);
274
0
    } else if (cost <= -1.0) {
275
0
      rc = +1;
276
0
      t = 12.0;      /* Sun always above altit */
277
278
0
      *ts_rise = t_loc->sse - (12 * 3600);
279
0
      *ts_set  = t_loc->sse + (12 * 3600);
280
0
    } else {
281
0
      t = acosd(cost) / 15.0;   /* The diurnal arc, hours */
282
283
      /* Store rise and set times - as Unix Timestamp */
284
0
      *ts_rise = ((tsouth - t) * 3600) + t_utc->sse;
285
0
      *ts_set  = ((tsouth + t) * 3600) + t_utc->sse;
286
287
0
      *h_rise = (tsouth - t);
288
0
      *h_set  = (tsouth + t);
289
0
    }
290
0
  }
291
292
  /* Kill temporary time and restore original sse */
293
0
  timelib_time_dtor(t_utc);
294
0
  t_loc->sse = old_sse;
295
296
0
  return rc;
297
0
}
298
299
double timelib_ts_to_julianday(timelib_sll ts)
300
0
{
301
0
  double tmp;
302
303
0
  tmp = (double) ts;
304
0
  tmp /= (double) 86400;
305
0
  tmp += (double) 2440587.5;
306
307
0
  return tmp;
308
0
}
309
310
double timelib_ts_to_j2000(timelib_sll ts)
311
0
{
312
0
  return timelib_ts_to_julianday(ts) - 2451545;
313
0
}