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