// © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /******************************************************************** * COPYRIGHT: * Copyright (c) 1996-2016, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ /* Test CalendarAstronomer for C++ */ #include "unicode/utypes.h" #include "string.h" #include "unicode/locid.h" #if !UCONFIG_NO_FORMATTING #include "astro.h" #include "astrotst.h" #include "cmemory.h" #include "gregoimp.h" // for Math #include "unicode/simpletz.h" #define CASE(id,test) case id: name = #test; if (exec) { logln(#test "---"); logln((UnicodeString)""); test(); } break AstroTest::AstroTest(): astro(NULL), gc(NULL) { } void AstroTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) { if (exec) logln("TestSuite AstroTest"); switch (index) { // CASE(0,FooTest); CASE(0,TestSolarLongitude); CASE(1,TestLunarPosition); CASE(2,TestCoordinates); CASE(3,TestCoverage); CASE(4,TestSunriseTimes); CASE(5,TestBasics); CASE(6,TestMoonAge); default: name = ""; break; } } #undef CASE #define ASSERT_OK(x) UPRV_BLOCK_MACRO_BEGIN { \ if(U_FAILURE(x)) { \ dataerrln("%s:%d: %s\n", __FILE__, __LINE__, u_errorName(x)); \ return; \ } \ } UPRV_BLOCK_MACRO_END void AstroTest::initAstro(UErrorCode &status) { if(U_FAILURE(status)) return; if((astro != NULL) || (gc != NULL)) { dataerrln("Err: initAstro() called twice!"); closeAstro(status); if(U_SUCCESS(status)) { status = U_INTERNAL_PROGRAM_ERROR; } } if(U_FAILURE(status)) return; astro = new CalendarAstronomer(); gc = Calendar::createInstance(TimeZone::getGMT()->clone(), status); } void AstroTest::closeAstro(UErrorCode &/*status*/) { if(astro != NULL) { delete astro; astro = NULL; } if(gc != NULL) { delete gc; gc = NULL; } } void AstroTest::TestSolarLongitude(void) { UErrorCode status = U_ZERO_ERROR; initAstro(status); ASSERT_OK(status); struct { int32_t d[5]; double f ; } tests[] = { { { 1980, 7, 27, 0, 00 }, 124.114347 }, { { 1988, 7, 27, 00, 00 }, 124.187732 } }; logln(""); for (uint32_t i = 0; i < UPRV_LENGTHOF(tests); i++) { gc->clear(); gc->set(tests[i].d[0], tests[i].d[1]-1, tests[i].d[2], tests[i].d[3], tests[i].d[4]); astro->setDate(gc->getTime(status)); double longitude = astro->getSunLongitude(); //longitude = 0; CalendarAstronomer::Equatorial result; astro->getSunPosition(result); logln((UnicodeString)"Sun position is " + result.toString() + (UnicodeString)"; " /* + result.toHmsString()*/ + " Sun longitude is " + longitude ); } closeAstro(status); ASSERT_OK(status); } void AstroTest::TestLunarPosition(void) { UErrorCode status = U_ZERO_ERROR; initAstro(status); ASSERT_OK(status); static const double tests[][7] = { { 1979, 2, 26, 16, 00, 0, 0 } }; logln(""); for (int32_t i = 0; i < UPRV_LENGTHOF(tests); i++) { gc->clear(); 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]); astro->setDate(gc->getTime(status)); const CalendarAstronomer::Equatorial& result = astro->getMoonPosition(); logln((UnicodeString)"Moon position is " + result.toString() + (UnicodeString)"; " /* + result->toHmsString()*/); } closeAstro(status); ASSERT_OK(status); } void AstroTest::TestCoordinates(void) { UErrorCode status = U_ZERO_ERROR; initAstro(status); ASSERT_OK(status); CalendarAstronomer::Equatorial result; astro->eclipticToEquatorial(result, 139.686111 * CalendarAstronomer::PI / 180.0, 4.875278* CalendarAstronomer::PI / 180.0); logln((UnicodeString)"result is " + result.toString() + (UnicodeString)"; " /* + result.toHmsString()*/ ); closeAstro(status); ASSERT_OK(status); } void AstroTest::TestCoverage(void) { UErrorCode status = U_ZERO_ERROR; initAstro(status); ASSERT_OK(status); GregorianCalendar *cal = new GregorianCalendar(1958, UCAL_AUGUST, 15,status); UDate then = cal->getTime(status); CalendarAstronomer *myastro = new CalendarAstronomer(then); ASSERT_OK(status); //Latitude: 34 degrees 05' North //Longitude: 118 degrees 22' West double laLat = 34 + 5./60, laLong = 360 - (118 + 22./60); CalendarAstronomer *myastro2 = new CalendarAstronomer(laLong, laLat); double eclLat = laLat * CalendarAstronomer::PI / 360; double eclLong = laLong * CalendarAstronomer::PI / 360; CalendarAstronomer::Ecliptic ecl(eclLat, eclLong); CalendarAstronomer::Equatorial eq; CalendarAstronomer::Horizon hor; logln("ecliptic: " + ecl.toString()); CalendarAstronomer *myastro3 = new CalendarAstronomer(); myastro3->setJulianDay((4713 + 2000) * 365.25); CalendarAstronomer *astronomers[] = { myastro, myastro2, myastro3, myastro2 // check cache }; for (uint32_t i = 0; i < UPRV_LENGTHOF(astronomers); ++i) { CalendarAstronomer *anAstro = astronomers[i]; //logln("astro: " + astro); logln((UnicodeString)" date: " + anAstro->getTime()); logln((UnicodeString)" cent: " + anAstro->getJulianCentury()); logln((UnicodeString)" gw sidereal: " + anAstro->getGreenwichSidereal()); logln((UnicodeString)" loc sidereal: " + anAstro->getLocalSidereal()); logln((UnicodeString)" equ ecl: " + (anAstro->eclipticToEquatorial(eq,ecl)).toString()); logln((UnicodeString)" equ long: " + (anAstro->eclipticToEquatorial(eq, eclLong)).toString()); logln((UnicodeString)" horiz: " + (anAstro->eclipticToHorizon(hor, eclLong)).toString()); logln((UnicodeString)" sunrise: " + (anAstro->getSunRiseSet(TRUE))); logln((UnicodeString)" sunset: " + (anAstro->getSunRiseSet(FALSE))); logln((UnicodeString)" moon phase: " + anAstro->getMoonPhase()); logln((UnicodeString)" moonrise: " + (anAstro->getMoonRiseSet(TRUE))); logln((UnicodeString)" moonset: " + (anAstro->getMoonRiseSet(FALSE))); logln((UnicodeString)" prev summer solstice: " + (anAstro->getSunTime(CalendarAstronomer::SUMMER_SOLSTICE(), FALSE))); logln((UnicodeString)" next summer solstice: " + (anAstro->getSunTime(CalendarAstronomer::SUMMER_SOLSTICE(), TRUE))); logln((UnicodeString)" prev full moon: " + (anAstro->getMoonTime(CalendarAstronomer::FULL_MOON(), FALSE))); logln((UnicodeString)" next full moon: " + (anAstro->getMoonTime(CalendarAstronomer::FULL_MOON(), TRUE))); } delete myastro2; delete myastro3; delete myastro; delete cal; closeAstro(status); ASSERT_OK(status); } void AstroTest::TestSunriseTimes(void) { UErrorCode status = U_ZERO_ERROR; initAstro(status); ASSERT_OK(status); // logln("Sunrise/Sunset times for San Jose, California, USA"); // CalendarAstronomer *astro2 = new CalendarAstronomer(-121.55, 37.20); // TimeZone *tz = TimeZone::createTimeZone("America/Los_Angeles"); // We'll use a table generated by the UNSO website as our reference // From: http://aa.usno.navy.mil/ //-Location: W079 25, N43 40 //-Rise and Set for the Sun for 2001 //-Zone: 4h West of Greenwich int32_t USNO[] = { 6,59, 19,45, 6,57, 19,46, 6,56, 19,47, 6,54, 19,48, 6,52, 19,49, 6,50, 19,51, 6,48, 19,52, 6,47, 19,53, 6,45, 19,54, 6,43, 19,55, 6,42, 19,57, 6,40, 19,58, 6,38, 19,59, 6,36, 20, 0, 6,35, 20, 1, 6,33, 20, 3, 6,31, 20, 4, 6,30, 20, 5, 6,28, 20, 6, 6,27, 20, 7, 6,25, 20, 8, 6,23, 20,10, 6,22, 20,11, 6,20, 20,12, 6,19, 20,13, 6,17, 20,14, 6,16, 20,16, 6,14, 20,17, 6,13, 20,18, 6,11, 20,19, }; logln("Sunrise/Sunset times for Toronto, Canada"); // long = 79 25", lat = 43 40" CalendarAstronomer astro3(-(79+25/60), 43+40/60); // As of ICU4J 2.8 the ICU4J time zones implement pass-through // to the underlying JDK. Because of variation in the // underlying JDKs, we have to use a fixed-offset // SimpleTimeZone to get consistent behavior between JDKs. // The offset we want is [-18000000, 3600000] (raw, dst). // [aliu 10/15/03] // TimeZone tz = TimeZone.getTimeZone("America/Montreal"); SimpleTimeZone tz(-18000000 + 3600000, "Montreal(FIXED)"); GregorianCalendar cal(tz.clone(), Locale::getUS(), status); GregorianCalendar cal2(tz.clone(), Locale::getUS(), status); cal.clear(); cal.set(UCAL_YEAR, 2001); cal.set(UCAL_MONTH, UCAL_APRIL); cal.set(UCAL_DAY_OF_MONTH, 1); cal.set(UCAL_HOUR_OF_DAY, 12); // must be near local noon for getSunRiseSet to work LocalPointer df_t(DateFormat::createTimeInstance(DateFormat::MEDIUM,Locale::getUS())); LocalPointer df_d(DateFormat::createDateInstance(DateFormat::MEDIUM,Locale::getUS())); LocalPointer df_dt(DateFormat::createDateTimeInstance(DateFormat::MEDIUM, DateFormat::MEDIUM, Locale::getUS())); if(!df_t.isValid() || !df_d.isValid() || !df_dt.isValid()) { dataerrln("couldn't create dateformats."); closeAstro(status); return; } df_t->adoptTimeZone(tz.clone()); df_d->adoptTimeZone(tz.clone()); df_dt->adoptTimeZone(tz.clone()); for (int32_t i=0; i < 30; i++) { logln("setDate\n"); astro3.setDate(cal.getTime(status)); logln("getRiseSet(TRUE)\n"); UDate sunrise = astro3.getSunRiseSet(TRUE); logln("getRiseSet(FALSE)\n"); UDate sunset = astro3.getSunRiseSet(FALSE); logln("end of getRiseSet\n"); cal2.setTime(cal.getTime(status), status); cal2.set(UCAL_SECOND, 0); cal2.set(UCAL_MILLISECOND, 0); cal2.set(UCAL_HOUR_OF_DAY, USNO[4*i+0]); cal2.set(UCAL_MINUTE, USNO[4*i+1]); UDate exprise = cal2.getTime(status); cal2.set(UCAL_HOUR_OF_DAY, USNO[4*i+2]); cal2.set(UCAL_MINUTE, USNO[4*i+3]); UDate expset = cal2.getTime(status); // Compute delta of what we got to the USNO data, in seconds int32_t deltarise = (int32_t)uprv_fabs((sunrise - exprise) / 1000); int32_t deltaset = (int32_t)uprv_fabs((sunset - expset) / 1000); // Allow a deviation of 0..MAX_DEV seconds // It would be nice to get down to 60 seconds, but at this // point that appears to be impossible without a redo of the // algorithm using something more advanced than Duffett-Smith. int32_t MAX_DEV = 180; UnicodeString s1, s2, s3, s4, s5; if (deltarise > MAX_DEV || deltaset > MAX_DEV) { if (deltarise > MAX_DEV) { errln("FAIL: (rise) " + df_d->format(cal.getTime(status),s1) + ", Sunrise: " + df_dt->format(sunrise, s2) + " (USNO " + df_t->format(exprise,s3) + " d=" + deltarise + "s)"); } else { logln(df_d->format(cal.getTime(status),s1) + ", Sunrise: " + df_dt->format(sunrise,s2) + " (USNO " + df_t->format(exprise,s3) + ")"); } s1.remove(); s2.remove(); s3.remove(); s4.remove(); s5.remove(); if (deltaset > MAX_DEV) { errln("FAIL: (set) " + df_d->format(cal.getTime(status),s1) + ", Sunset: " + df_dt->format(sunset,s2) + " (USNO " + df_t->format(expset,s3) + " d=" + deltaset + "s)"); } else { logln(df_d->format(cal.getTime(status),s1) + ", Sunset: " + df_dt->format(sunset,s2) + " (USNO " + df_t->format(expset,s3) + ")"); } } else { logln(df_d->format(cal.getTime(status),s1) + ", Sunrise: " + df_dt->format(sunrise,s2) + " (USNO " + df_t->format(exprise,s3) + ")" + ", Sunset: " + df_dt->format(sunset,s4) + " (USNO " + df_t->format(expset,s5) + ")"); } cal.add(UCAL_DATE, 1, status); } // CalendarAstronomer a = new CalendarAstronomer(-(71+5/60), 42+37/60); // cal.clear(); // cal.set(cal.YEAR, 1986); // cal.set(cal.MONTH, cal.MARCH); // cal.set(cal.DATE, 10); // cal.set(cal.YEAR, 1988); // cal.set(cal.MONTH, cal.JULY); // cal.set(cal.DATE, 27); // a.setDate(cal.getTime()); // long r = a.getSunRiseSet2(true); closeAstro(status); ASSERT_OK(status); } void AstroTest::TestBasics(void) { UErrorCode status = U_ZERO_ERROR; initAstro(status); if (U_FAILURE(status)) { dataerrln("Got error: %s", u_errorName(status)); return; } // Check that our JD computation is the same as the book's (p. 88) GregorianCalendar cal3(TimeZone::getGMT()->clone(), Locale::getUS(), status); LocalPointer d3(DateFormat::createDateTimeInstance(DateFormat::MEDIUM,DateFormat::MEDIUM,Locale::getUS())); if (d3.isNull()) { dataerrln("Got error: %s", u_errorName(status)); closeAstro(status); return; } d3->setTimeZone(*TimeZone::getGMT()); cal3.clear(); cal3.set(UCAL_YEAR, 1980); cal3.set(UCAL_MONTH, UCAL_JULY); cal3.set(UCAL_DATE, 2); logln("cal3[a]=%.1lf, d=%d\n", cal3.getTime(status), cal3.get(UCAL_JULIAN_DAY,status)); { UnicodeString s; logln(UnicodeString("cal3[a] = ") + d3->format(cal3.getTime(status),s)); } cal3.clear(); cal3.set(UCAL_YEAR, 1980); cal3.set(UCAL_MONTH, UCAL_JULY); cal3.set(UCAL_DATE, 27); logln("cal3=%.1lf, d=%d\n", cal3.getTime(status), cal3.get(UCAL_JULIAN_DAY,status)); ASSERT_OK(status); { UnicodeString s; logln(UnicodeString("cal3 = ") + d3->format(cal3.getTime(status),s)); } astro->setTime(cal3.getTime(status)); double jd = astro->getJulianDay() - 2447891.5; double exp = -3444.; if (jd == exp) { UnicodeString s; logln(d3->format(cal3.getTime(status),s) + " => " + jd); } else { UnicodeString s; errln("FAIL: " + d3->format(cal3.getTime(status), s) + " => " + jd + ", expected " + exp); } // cal3.clear(); // cal3.set(cal3.YEAR, 1990); // cal3.set(cal3.MONTH, Calendar.JANUARY); // cal3.set(cal3.DATE, 1); // cal3.add(cal3.DATE, -1); // astro.setDate(cal3.getTime()); // astro.foo(); ASSERT_OK(status); closeAstro(status); ASSERT_OK(status); } void AstroTest::TestMoonAge(void){ UErrorCode status = U_ZERO_ERROR; initAstro(status); ASSERT_OK(status); // more testcases are around the date 05/20/2012 //ticket#3785 UDate ud0 = 1337557623000.0; static const double testcase[][10] = {{2012, 5, 20 , 16 , 48, 59}, {2012, 5, 20 , 16 , 47, 34}, {2012, 5, 21, 00, 00, 00}, {2012, 5, 20, 14, 55, 59}, {2012, 5, 21, 7, 40, 40}, {2023, 9, 25, 10,00, 00}, {2008, 7, 7, 15, 00, 33}, {1832, 9, 24, 2, 33, 41 }, {2016, 1, 31, 23, 59, 59}, {2099, 5, 20, 14, 55, 59} }; // Moon phase angle - Got from http://www.moonsystem.to/checkupe.htm static const double angle[] = {356.8493418421329, 356.8386760059673, 0.09625415252237701, 355.9986960782416, 3.5714026601303317, 124.26906744384183, 59.80247650195558, 357.54163205513123, 268.41779281511094, 4.82340276581624}; static const double precision = CalendarAstronomer::PI/32; for (int32_t i = 0; i < UPRV_LENGTHOF(testcase); i++) { gc->clear(); logln((UnicodeString)"CASE["+i+"]: Year "+(int32_t)testcase[i][0]+" Month "+(int32_t)testcase[i][1]+" Day "+ (int32_t)testcase[i][2]+" Hour "+(int32_t)testcase[i][3]+" Minutes "+(int32_t)testcase[i][4]+ " Seconds "+(int32_t)testcase[i][5]); 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]); astro->setDate(gc->getTime(status)); double expectedAge = (angle[i]*CalendarAstronomer::PI)/180; double got = astro->getMoonAge(); //logln(testString); if(!(got>expectedAge-precision && got