• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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)   if(U_FAILURE(x)) { dataerrln("%s:%d: %s\n", __FILE__, __LINE__, u_errorName(x)); return; }
48 
49 
initAstro(UErrorCode & status)50 void 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 
closeAstro(UErrorCode &)67 void 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 
TestSolarLongitude(void)78 void 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 
TestLunarPosition(void)109 void 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 
TestCoordinates(void)134 void 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 
TestCoverage(void)148 void 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 
TestSunriseTimes(void)210 void 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 
TestBasics(void)374 void 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 
TestMoonAge(void)434 void 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