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