1 // Copyright (C) 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /********************************************************************
5 * Copyright (c) 1996-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 ********************************************************************/
9 /* Test CalendarAstronomer for C++ */
11 #include "unicode/utypes.h"
13 #include "unicode/locid.h"
15 #if !UCONFIG_NO_FORMATTING
20 #include "gregoimp.h" // for Math
21 #include "unicode/simpletz.h"
24 #define CASE(id,test) case id: name = #test; if (exec) { logln(#test "---"); logln((UnicodeString)""); test(); } break
26 AstroTest::AstroTest(): astro(NULL), gc(NULL) {
29 void AstroTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
31 if (exec) logln("TestSuite AstroTest");
34 CASE(0,TestSolarLongitude);
35 CASE(1,TestLunarPosition);
36 CASE(2,TestCoordinates);
38 CASE(4,TestSunriseTimes);
41 default: name = ""; break;
47 #define ASSERT_OK(x) if(U_FAILURE(x)) { dataerrln("%s:%d: %s\n", __FILE__, __LINE__, u_errorName(x)); return; }
50 void AstroTest::initAstro(UErrorCode &status) {
51 if(U_FAILURE(status)) return;
53 if((astro != NULL) || (gc != NULL)) {
54 dataerrln("Err: initAstro() called twice!");
56 if(U_SUCCESS(status)) {
57 status = U_INTERNAL_PROGRAM_ERROR;
61 if(U_FAILURE(status)) return;
63 astro = new CalendarAstronomer();
64 gc = Calendar::createInstance(TimeZone::getGMT()->clone(), status);
67 void AstroTest::closeAstro(UErrorCode &/*status*/) {
78 void AstroTest::TestSolarLongitude(void) {
79 UErrorCode status = U_ZERO_ERROR;
84 int32_t d[5]; double f ;
86 { { 1980, 7, 27, 0, 00 }, 124.114347 },
87 { { 1988, 7, 27, 00, 00 }, 124.187732 }
91 for (uint32_t i = 0; i < UPRV_LENGTHOF(tests); i++) {
93 gc->set(tests[i].d[0], tests[i].d[1]-1, tests[i].d[2], tests[i].d[3], tests[i].d[4]);
95 astro->setDate(gc->getTime(status));
97 double longitude = astro->getSunLongitude();
99 CalendarAstronomer::Equatorial result;
100 astro->getSunPosition(result);
101 logln((UnicodeString)"Sun position is " + result.toString() + (UnicodeString)"; " /* + result.toHmsString()*/ + " Sun longitude is " + longitude );
109 void AstroTest::TestLunarPosition(void) {
110 UErrorCode status = U_ZERO_ERROR;
114 static const double tests[][7] = {
115 { 1979, 2, 26, 16, 00, 0, 0 }
119 for (int32_t i = 0; i < UPRV_LENGTHOF(tests); i++) {
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));
124 const CalendarAstronomer::Equatorial& result = astro->getMoonPosition();
125 logln((UnicodeString)"Moon position is " + result.toString() + (UnicodeString)"; " /* + result->toHmsString()*/);
134 void AstroTest::TestCoordinates(void) {
135 UErrorCode status = U_ZERO_ERROR;
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()*/ );
148 void AstroTest::TestCoverage(void) {
149 UErrorCode status = U_ZERO_ERROR;
152 GregorianCalendar *cal = new GregorianCalendar(1958, UCAL_AUGUST, 15,status);
153 UDate then = cal->getTime(status);
154 CalendarAstronomer *myastro = new CalendarAstronomer(then);
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);
162 double eclLat = laLat * CalendarAstronomer::PI / 360;
163 double eclLong = laLong * CalendarAstronomer::PI / 360;
165 CalendarAstronomer::Ecliptic ecl(eclLat, eclLong);
166 CalendarAstronomer::Equatorial eq;
167 CalendarAstronomer::Horizon hor;
169 logln("ecliptic: " + ecl.toString());
170 CalendarAstronomer *myastro3 = new CalendarAstronomer();
171 myastro3->setJulianDay((4713 + 2000) * 365.25);
173 CalendarAstronomer *astronomers[] = {
174 myastro, myastro2, myastro3, myastro2 // check cache
177 for (uint32_t i = 0; i < UPRV_LENGTHOF(astronomers); ++i) {
178 CalendarAstronomer *anAstro = astronomers[i];
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)));
210 void AstroTest::TestSunriseTimes(void) {
211 UErrorCode status = U_ZERO_ERROR;
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");
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
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);
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).
268 // TimeZone tz = TimeZone.getTimeZone("America/Montreal");
269 TimeZone *tz = new SimpleTimeZone(-18000000 + 3600000, "Montreal(FIXED)");
271 GregorianCalendar *cal = new GregorianCalendar(tz->clone(), Locale::getUS(), status);
272 GregorianCalendar *cal2 = new GregorianCalendar(tz->clone(), Locale::getUS(), status);
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
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.");
286 df_t->adoptTimeZone(tz->clone());
287 df_d->adoptTimeZone(tz->clone());
288 df_dt->adoptTimeZone(tz->clone());
290 for (int32_t i=0; i < 30; i++) {
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");
299 cal2->setTime(cal->getTime(status), status);
300 cal2->set(UCAL_SECOND, 0);
301 cal2->set(UCAL_MILLISECOND, 0);
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);
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)");
326 logln(df_d->format(cal->getTime(status),s1) +
327 ", Sunrise: " + df_dt->format(sunrise,s2) +
328 " (USNO " + df_t->format(exprise,s3) + ")");
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)");
337 logln(df_d->format(cal->getTime(status),s1) +
338 ", Sunset: " + df_dt->format(sunset,s2) +
339 " (USNO " + df_t->format(expset,s3) + ")");
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) + ")");
348 cal->add(UCAL_DATE, 1, status);
351 // CalendarAstronomer a = new CalendarAstronomer(-(71+5/60), 42+37/60);
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);
374 void AstroTest::TestBasics(void) {
375 UErrorCode status = U_ZERO_ERROR;
377 if (U_FAILURE(status)) {
378 dataerrln("Got error: %s", u_errorName(status));
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());
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));
393 logln(UnicodeString("cal3[a] = ") + d3->format(cal3->getTime(status),s));
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));
404 logln(UnicodeString("cal3 = ") + d3->format(cal3->getTime(status),s));
406 astro->setTime(cal3->getTime(status));
407 double jd = astro->getJulianDay() - 2447891.5;
411 logln(d3->format(cal3->getTime(status),s) + " => " + jd);
414 errln("FAIL: " + d3->format(cal3->getTime(status), s) + " => " + jd +
415 ", expected " + exp);
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());
434 void AstroTest::TestMoonAge(void){
435 UErrorCode status = U_ZERO_ERROR;
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}
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++) {
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();
466 if(!(got>expectedAge-precision && got<expectedAge+precision)){
467 errln((UnicodeString)"FAIL: expected " + expectedAge +
470 logln((UnicodeString)"PASS: expected " + expectedAge +
479 // TODO: try finding next new moon after 07/28/1984 16:00 GMT