1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html
3/********************************************************************
4 * COPYRIGHT:
5 * Copyright (c) 1996-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 ********************************************************************/
8
9/* Test CalendarAstronomer for C++ */
10
11#include "unicode/utypes.h"
12#include "string.h"
13#include "unicode/locid.h"
14
15#if !UCONFIG_NO_FORMATTING
16
17#include "astro.h"
18#include "astrotst.h"
19#include "cmemory.h"
20#include "gregoimp.h" // for Math
21#include "unicode/simpletz.h"
22
23
24#define CASE(id,test) case id: name = #test; if (exec) { logln(#test "---"); logln((UnicodeString)""); test(); } break
25
26AstroTest::AstroTest(): astro(NULL), gc(NULL) {
27}
28
29void AstroTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
30{
31    if (exec) logln("TestSuite AstroTest");
32    switch (index) {
33      // CASE(0,FooTest);
34      CASE(0,TestSolarLongitude);
35      CASE(1,TestLunarPosition);
36      CASE(2,TestCoordinates);
37      CASE(3,TestCoverage);
38      CASE(4,TestSunriseTimes);
39      CASE(5,TestBasics);
40      CASE(6,TestMoonAge);
41    default: name = ""; break;
42    }
43}
44
45#undef CASE
46
47#define ASSERT_OK(x)   if(U_FAILURE(x)) { dataerrln("%s:%d: %s\n", __FILE__, __LINE__, u_errorName(x)); return; }
48
49
50void AstroTest::initAstro(UErrorCode &status) {
51  if(U_FAILURE(status)) return;
52
53  if((astro != NULL) || (gc != NULL)) {
54    dataerrln("Err: initAstro() called twice!");
55    closeAstro(status);
56    if(U_SUCCESS(status)) {
57      status = U_INTERNAL_PROGRAM_ERROR;
58    }
59  }
60
61  if(U_FAILURE(status)) return;
62
63  astro = new CalendarAstronomer();
64  gc = Calendar::createInstance(TimeZone::getGMT()->clone(), status);
65}
66
67void AstroTest::closeAstro(UErrorCode &/*status*/) {
68  if(astro != NULL) {
69    delete astro;
70    astro = NULL;
71  }
72  if(gc != NULL) {
73    delete gc;
74    gc = NULL;
75  }
76}
77
78void AstroTest::TestSolarLongitude(void) {
79  UErrorCode status = U_ZERO_ERROR;
80  initAstro(status);
81  ASSERT_OK(status);
82
83  struct {
84    int32_t d[5]; double f ;
85  } tests[] = {
86    { { 1980, 7, 27, 0, 00 },  124.114347 },
87    { { 1988, 7, 27, 00, 00 },  124.187732 }
88  };
89
90  logln("");
91  for (uint32_t i = 0; i < UPRV_LENGTHOF(tests); i++) {
92    gc->clear();
93    gc->set(tests[i].d[0], tests[i].d[1]-1, tests[i].d[2], tests[i].d[3], tests[i].d[4]);
94
95    astro->setDate(gc->getTime(status));
96
97    double longitude = astro->getSunLongitude();
98    //longitude = 0;
99    CalendarAstronomer::Equatorial result;
100    astro->getSunPosition(result);
101    logln((UnicodeString)"Sun position is " + result.toString() + (UnicodeString)";  " /* + result.toHmsString()*/ + " Sun longitude is " + longitude );
102  }
103  closeAstro(status);
104  ASSERT_OK(status);
105}
106
107
108
109void AstroTest::TestLunarPosition(void) {
110  UErrorCode status = U_ZERO_ERROR;
111  initAstro(status);
112  ASSERT_OK(status);
113
114  static const double tests[][7] = {
115    { 1979, 2, 26, 16, 00,  0, 0 }
116  };
117  logln("");
118
119  for (int32_t i = 0; i < UPRV_LENGTHOF(tests); i++) {
120    gc->clear();
121    gc->set((int32_t)tests[i][0], (int32_t)tests[i][1]-1, (int32_t)tests[i][2], (int32_t)tests[i][3], (int32_t)tests[i][4]);
122    astro->setDate(gc->getTime(status));
123
124    const CalendarAstronomer::Equatorial& result = astro->getMoonPosition();
125    logln((UnicodeString)"Moon position is " + result.toString() + (UnicodeString)";  " /* + result->toHmsString()*/);
126  }
127
128  closeAstro(status);
129  ASSERT_OK(status);
130}
131
132
133
134void AstroTest::TestCoordinates(void) {
135  UErrorCode status = U_ZERO_ERROR;
136  initAstro(status);
137  ASSERT_OK(status);
138
139  CalendarAstronomer::Equatorial result;
140  astro->eclipticToEquatorial(result, 139.686111 * CalendarAstronomer::PI / 180.0, 4.875278* CalendarAstronomer::PI / 180.0);
141  logln((UnicodeString)"result is " + result.toString() + (UnicodeString)";  " /* + result.toHmsString()*/ );
142  closeAstro(status);
143  ASSERT_OK(status);
144}
145
146
147
148void AstroTest::TestCoverage(void) {
149  UErrorCode status = U_ZERO_ERROR;
150  initAstro(status);
151  ASSERT_OK(status);
152  GregorianCalendar *cal = new GregorianCalendar(1958, UCAL_AUGUST, 15,status);
153  UDate then = cal->getTime(status);
154  CalendarAstronomer *myastro = new CalendarAstronomer(then);
155  ASSERT_OK(status);
156
157  //Latitude:  34 degrees 05' North
158  //Longitude:  118 degrees 22' West
159  double laLat = 34 + 5./60, laLong = 360 - (118 + 22./60);
160  CalendarAstronomer *myastro2 = new CalendarAstronomer(laLong, laLat);
161
162  double eclLat = laLat * CalendarAstronomer::PI / 360;
163  double eclLong = laLong * CalendarAstronomer::PI / 360;
164
165  CalendarAstronomer::Ecliptic ecl(eclLat, eclLong);
166  CalendarAstronomer::Equatorial eq;
167  CalendarAstronomer::Horizon hor;
168
169  logln("ecliptic: " + ecl.toString());
170  CalendarAstronomer *myastro3 = new CalendarAstronomer();
171  myastro3->setJulianDay((4713 + 2000) * 365.25);
172
173  CalendarAstronomer *astronomers[] = {
174    myastro, myastro2, myastro3, myastro2 // check cache
175  };
176
177  for (uint32_t i = 0; i < UPRV_LENGTHOF(astronomers); ++i) {
178    CalendarAstronomer *anAstro = astronomers[i];
179
180    //logln("astro: " + astro);
181    logln((UnicodeString)"   date: " + anAstro->getTime());
182    logln((UnicodeString)"   cent: " + anAstro->getJulianCentury());
183    logln((UnicodeString)"   gw sidereal: " + anAstro->getGreenwichSidereal());
184    logln((UnicodeString)"   loc sidereal: " + anAstro->getLocalSidereal());
185    logln((UnicodeString)"   equ ecl: " + (anAstro->eclipticToEquatorial(eq,ecl)).toString());
186    logln((UnicodeString)"   equ long: " + (anAstro->eclipticToEquatorial(eq, eclLong)).toString());
187    logln((UnicodeString)"   horiz: " + (anAstro->eclipticToHorizon(hor, eclLong)).toString());
188    logln((UnicodeString)"   sunrise: " + (anAstro->getSunRiseSet(TRUE)));
189    logln((UnicodeString)"   sunset: " + (anAstro->getSunRiseSet(FALSE)));
190    logln((UnicodeString)"   moon phase: " + anAstro->getMoonPhase());
191    logln((UnicodeString)"   moonrise: " + (anAstro->getMoonRiseSet(TRUE)));
192    logln((UnicodeString)"   moonset: " + (anAstro->getMoonRiseSet(FALSE)));
193    logln((UnicodeString)"   prev summer solstice: " + (anAstro->getSunTime(CalendarAstronomer::SUMMER_SOLSTICE(), FALSE)));
194    logln((UnicodeString)"   next summer solstice: " + (anAstro->getSunTime(CalendarAstronomer::SUMMER_SOLSTICE(), TRUE)));
195    logln((UnicodeString)"   prev full moon: " + (anAstro->getMoonTime(CalendarAstronomer::FULL_MOON(), FALSE)));
196    logln((UnicodeString)"   next full moon: " + (anAstro->getMoonTime(CalendarAstronomer::FULL_MOON(), TRUE)));
197  }
198
199  delete myastro2;
200  delete myastro3;
201  delete myastro;
202  delete cal;
203
204  closeAstro(status);
205  ASSERT_OK(status);
206}
207
208
209
210void AstroTest::TestSunriseTimes(void) {
211  UErrorCode status = U_ZERO_ERROR;
212  initAstro(status);
213  ASSERT_OK(status);
214
215  //  logln("Sunrise/Sunset times for San Jose, California, USA");
216  //  CalendarAstronomer *astro2 = new CalendarAstronomer(-121.55, 37.20);
217  //  TimeZone *tz = TimeZone::createTimeZone("America/Los_Angeles");
218
219  // We'll use a table generated by the UNSO website as our reference
220  // From: http://aa.usno.navy.mil/
221  //-Location: W079 25, N43 40
222  //-Rise and Set for the Sun for 2001
223  //-Zone:  4h West of Greenwich
224  int32_t USNO[] = {
225    6,59, 19,45,
226    6,57, 19,46,
227    6,56, 19,47,
228    6,54, 19,48,
229    6,52, 19,49,
230    6,50, 19,51,
231    6,48, 19,52,
232    6,47, 19,53,
233    6,45, 19,54,
234    6,43, 19,55,
235    6,42, 19,57,
236    6,40, 19,58,
237    6,38, 19,59,
238    6,36, 20, 0,
239    6,35, 20, 1,
240    6,33, 20, 3,
241    6,31, 20, 4,
242    6,30, 20, 5,
243    6,28, 20, 6,
244    6,27, 20, 7,
245    6,25, 20, 8,
246    6,23, 20,10,
247    6,22, 20,11,
248    6,20, 20,12,
249    6,19, 20,13,
250    6,17, 20,14,
251    6,16, 20,16,
252    6,14, 20,17,
253    6,13, 20,18,
254    6,11, 20,19,
255  };
256
257  logln("Sunrise/Sunset times for Toronto, Canada");
258  // long = 79 25", lat = 43 40"
259  CalendarAstronomer *astro3 = new CalendarAstronomer(-(79+25/60), 43+40/60);
260
261  // As of ICU4J 2.8 the ICU4J time zones implement pass-through
262  // to the underlying JDK.  Because of variation in the
263  // underlying JDKs, we have to use a fixed-offset
264  // SimpleTimeZone to get consistent behavior between JDKs.
265  // The offset we want is [-18000000, 3600000] (raw, dst).
266  // [aliu 10/15/03]
267
268  // TimeZone tz = TimeZone.getTimeZone("America/Montreal");
269  TimeZone *tz = new SimpleTimeZone(-18000000 + 3600000, "Montreal(FIXED)");
270
271  GregorianCalendar *cal = new GregorianCalendar(tz->clone(), Locale::getUS(), status);
272  GregorianCalendar *cal2 = new GregorianCalendar(tz->clone(), Locale::getUS(), status);
273  cal->clear();
274  cal->set(UCAL_YEAR, 2001);
275  cal->set(UCAL_MONTH, UCAL_APRIL);
276  cal->set(UCAL_DAY_OF_MONTH, 1);
277  cal->set(UCAL_HOUR_OF_DAY, 12); // must be near local noon for getSunRiseSet to work
278
279  DateFormat *df_t  = DateFormat::createTimeInstance(DateFormat::MEDIUM,Locale::getUS());
280  DateFormat *df_d  = DateFormat::createDateInstance(DateFormat::MEDIUM,Locale::getUS());
281  DateFormat *df_dt = DateFormat::createDateTimeInstance(DateFormat::MEDIUM, DateFormat::MEDIUM, Locale::getUS());
282  if(!df_t || !df_d || !df_dt) {
283    dataerrln("couldn't create dateformats.");
284    return;
285  }
286  df_t->adoptTimeZone(tz->clone());
287  df_d->adoptTimeZone(tz->clone());
288  df_dt->adoptTimeZone(tz->clone());
289
290  for (int32_t i=0; i < 30; i++) {
291    logln("setDate\n");
292    astro3->setDate(cal->getTime(status));
293    logln("getRiseSet(TRUE)\n");
294    UDate sunrise = astro3->getSunRiseSet(TRUE);
295    logln("getRiseSet(FALSE)\n");
296    UDate sunset  = astro3->getSunRiseSet(FALSE);
297    logln("end of getRiseSet\n");
298
299    cal2->setTime(cal->getTime(status), status);
300    cal2->set(UCAL_SECOND,      0);
301    cal2->set(UCAL_MILLISECOND, 0);
302
303    cal2->set(UCAL_HOUR_OF_DAY, USNO[4*i+0]);
304    cal2->set(UCAL_MINUTE,      USNO[4*i+1]);
305    UDate exprise = cal2->getTime(status);
306    cal2->set(UCAL_HOUR_OF_DAY, USNO[4*i+2]);
307    cal2->set(UCAL_MINUTE,      USNO[4*i+3]);
308    UDate expset = cal2->getTime(status);
309    // Compute delta of what we got to the USNO data, in seconds
310    int32_t deltarise = (int32_t)uprv_fabs((sunrise - exprise) / 1000);
311    int32_t deltaset = (int32_t)uprv_fabs((sunset - expset) / 1000);
312
313    // Allow a deviation of 0..MAX_DEV seconds
314    // It would be nice to get down to 60 seconds, but at this
315    // point that appears to be impossible without a redo of the
316    // algorithm using something more advanced than Duffett-Smith.
317    int32_t MAX_DEV = 180;
318    UnicodeString s1, s2, s3, s4, s5;
319    if (deltarise > MAX_DEV || deltaset > MAX_DEV) {
320      if (deltarise > MAX_DEV) {
321        errln("FAIL: (rise) " + df_d->format(cal->getTime(status),s1) +
322              ", Sunrise: " + df_dt->format(sunrise, s2) +
323              " (USNO " + df_t->format(exprise,s3) +
324              " d=" + deltarise + "s)");
325      } else {
326        logln(df_d->format(cal->getTime(status),s1) +
327              ", Sunrise: " + df_dt->format(sunrise,s2) +
328              " (USNO " + df_t->format(exprise,s3) + ")");
329      }
330      s1.remove(); s2.remove(); s3.remove(); s4.remove(); s5.remove();
331      if (deltaset > MAX_DEV) {
332        errln("FAIL: (set)  " + df_d->format(cal->getTime(status),s1) +
333              ", Sunset:  " + df_dt->format(sunset,s2) +
334              " (USNO " + df_t->format(expset,s3) +
335              " d=" + deltaset + "s)");
336      } else {
337        logln(df_d->format(cal->getTime(status),s1) +
338              ", Sunset: " + df_dt->format(sunset,s2) +
339              " (USNO " + df_t->format(expset,s3) + ")");
340      }
341    } else {
342      logln(df_d->format(cal->getTime(status),s1) +
343            ", Sunrise: " + df_dt->format(sunrise,s2) +
344            " (USNO " + df_t->format(exprise,s3) + ")" +
345            ", Sunset: " + df_dt->format(sunset,s4) +
346            " (USNO " + df_t->format(expset,s5) + ")");
347    }
348    cal->add(UCAL_DATE, 1, status);
349  }
350
351  //        CalendarAstronomer a = new CalendarAstronomer(-(71+5/60), 42+37/60);
352  //        cal.clear();
353  //        cal.set(cal.YEAR, 1986);
354  //        cal.set(cal.MONTH, cal.MARCH);
355  //        cal.set(cal.DATE, 10);
356  //        cal.set(cal.YEAR, 1988);
357  //        cal.set(cal.MONTH, cal.JULY);
358  //        cal.set(cal.DATE, 27);
359  //        a.setDate(cal.getTime());
360  //        long r = a.getSunRiseSet2(true);
361  delete astro3;
362  delete tz;
363  delete cal;
364  delete cal2;
365  delete df_t;
366  delete df_d;
367  delete df_dt;
368  closeAstro(status);
369  ASSERT_OK(status);
370}
371
372
373
374void AstroTest::TestBasics(void) {
375  UErrorCode status = U_ZERO_ERROR;
376  initAstro(status);
377  if (U_FAILURE(status)) {
378    dataerrln("Got error: %s", u_errorName(status));
379    return;
380  }
381
382  // Check that our JD computation is the same as the book's (p. 88)
383  GregorianCalendar *cal3 = new GregorianCalendar(TimeZone::getGMT()->clone(), Locale::getUS(), status);
384  DateFormat *d3 = DateFormat::createDateTimeInstance(DateFormat::MEDIUM,DateFormat::MEDIUM,Locale::getUS());
385  d3->setTimeZone(*TimeZone::getGMT());
386  cal3->clear();
387  cal3->set(UCAL_YEAR, 1980);
388  cal3->set(UCAL_MONTH, UCAL_JULY);
389  cal3->set(UCAL_DATE, 2);
390  logln("cal3[a]=%.1lf, d=%d\n", cal3->getTime(status), cal3->get(UCAL_JULIAN_DAY,status));
391  {
392    UnicodeString s;
393    logln(UnicodeString("cal3[a] = ") + d3->format(cal3->getTime(status),s));
394  }
395  cal3->clear();
396  cal3->set(UCAL_YEAR, 1980);
397  cal3->set(UCAL_MONTH, UCAL_JULY);
398  cal3->set(UCAL_DATE, 27);
399  logln("cal3=%.1lf, d=%d\n", cal3->getTime(status), cal3->get(UCAL_JULIAN_DAY,status));
400
401  ASSERT_OK(status);
402  {
403    UnicodeString s;
404    logln(UnicodeString("cal3 = ") + d3->format(cal3->getTime(status),s));
405  }
406  astro->setTime(cal3->getTime(status));
407  double jd = astro->getJulianDay() - 2447891.5;
408  double exp = -3444.;
409  if (jd == exp) {
410    UnicodeString s;
411    logln(d3->format(cal3->getTime(status),s) + " => " + jd);
412  } else {
413    UnicodeString s;
414    errln("FAIL: " + d3->format(cal3->getTime(status), s) + " => " + jd +
415          ", expected " + exp);
416  }
417
418  //        cal3.clear();
419  //        cal3.set(cal3.YEAR, 1990);
420  //        cal3.set(cal3.MONTH, Calendar.JANUARY);
421  //        cal3.set(cal3.DATE, 1);
422  //        cal3.add(cal3.DATE, -1);
423  //        astro.setDate(cal3.getTime());
424  //        astro.foo();
425
426  delete cal3;
427  delete d3;
428  ASSERT_OK(status);
429  closeAstro(status);
430  ASSERT_OK(status);
431
432}
433
434void AstroTest::TestMoonAge(void){
435	UErrorCode status = U_ZERO_ERROR;
436	initAstro(status);
437	ASSERT_OK(status);
438
439	// more testcases are around the date 05/20/2012
440	//ticket#3785  UDate ud0 = 1337557623000.0;
441	static const double testcase[][10] = {{2012, 5, 20 , 16 , 48, 59},
442	                {2012, 5, 20 , 16 , 47, 34},
443	                {2012, 5, 21, 00, 00, 00},
444	                {2012, 5, 20, 14, 55, 59},
445	                {2012, 5, 21, 7, 40, 40},
446	                {2023, 9, 25, 10,00, 00},
447	                {2008, 7, 7, 15, 00, 33},
448	                {1832, 9, 24, 2, 33, 41 },
449	                {2016, 1, 31, 23, 59, 59},
450	                {2099, 5, 20, 14, 55, 59}
451	        };
452	// Moon phase angle - Got from http://www.moonsystem.to/checkupe.htm
453	static const double angle[] = {356.8493418421329, 356.8386760059673, 0.09625415252237701, 355.9986960782416, 3.5714026601303317, 124.26906744384183, 59.80247650195558,
454									357.54163205513123, 268.41779281511094, 4.82340276581624};
455	static const double precision = CalendarAstronomer::PI/32;
456	for (int32_t i = 0; i < UPRV_LENGTHOF(testcase); i++) {
457		gc->clear();
458		logln((UnicodeString)"CASE["+i+"]: Year "+(int32_t)testcase[i][0]+" Month "+(int32_t)testcase[i][1]+" Day "+
459		                                    (int32_t)testcase[i][2]+" Hour "+(int32_t)testcase[i][3]+" Minutes "+(int32_t)testcase[i][4]+
460		                                    " Seconds "+(int32_t)testcase[i][5]);
461		gc->set((int32_t)testcase[i][0], (int32_t)testcase[i][1]-1, (int32_t)testcase[i][2], (int32_t)testcase[i][3], (int32_t)testcase[i][4], (int32_t)testcase[i][5]);
462		astro->setDate(gc->getTime(status));
463		double expectedAge = (angle[i]*CalendarAstronomer::PI)/180;
464		double got = astro->getMoonAge();
465		//logln(testString);
466		if(!(got>expectedAge-precision && got<expectedAge+precision)){
467			errln((UnicodeString)"FAIL: expected " + expectedAge +
468					" got " + got);
469		}else{
470			logln((UnicodeString)"PASS: expected " + expectedAge +
471					" got " + got);
472		}
473	}
474	closeAstro(status);
475	ASSERT_OK(status);
476}
477
478
479// TODO: try finding next new moon after  07/28/1984 16:00 GMT
480
481
482#endif
483
484
485
486