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