• 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) 1997-2015, International Business Machines Corporation
6  * and others. All Rights Reserved.
7  ***********************************************************************/
8 
9 #include "unicode/utypes.h"
10 
11 #if !UCONFIG_NO_FORMATTING
12 
13 #include "callimts.h"
14 #include "caltest.h"
15 #include "unicode/calendar.h"
16 #include "unicode/gregocal.h"
17 #include "unicode/datefmt.h"
18 #include "unicode/smpdtfmt.h"
19 #include "cstring.h"
20 #include "mutex.h"
21 #include "putilimp.h"
22 #include "simplethread.h"
23 
24 U_NAMESPACE_USE
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)25 void CalendarLimitTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
26 {
27     if (exec) logln("TestSuite TestCalendarLimit");
28     switch (index) {
29         // Re-enable this later
30         case 0:
31             name = "TestCalendarExtremeLimit";
32             if (exec) {
33                 logln("TestCalendarExtremeLimit---"); logln("");
34                 TestCalendarExtremeLimit();
35             }
36             break;
37         case 1:
38             name = "TestLimits";
39             if (exec) {
40                 logln("TestLimits---"); logln("");
41                 TestLimits();
42             }
43             break;
44 
45         default: name = ""; break;
46     }
47 }
48 
49 
50 // *****************************************************************************
51 // class CalendarLimitTest
52 // *****************************************************************************
53 
54 // -------------------------------------
55 void
test(UDate millis,icu::Calendar * cal,icu::DateFormat * fmt)56 CalendarLimitTest::test(UDate millis, icu::Calendar* cal, icu::DateFormat* fmt)
57 {
58   static const UDate kDrift = 1e-10;
59     UErrorCode exception = U_ZERO_ERROR;
60     UnicodeString theDate;
61     UErrorCode status = U_ZERO_ERROR;
62     cal->setTime(millis, exception);
63     if (U_SUCCESS(exception)) {
64         fmt->format(millis, theDate);
65         UDate dt = fmt->parse(theDate, status);
66         // allow a small amount of error (drift)
67         if(! withinErr(dt, millis, kDrift)) {
68           errln("FAIL:round trip for large milli, got: %.1lf wanted: %.1lf. (delta %.2lf greater than %.2lf)",
69                 dt, millis, uprv_fabs(millis-dt), uprv_fabs(dt*kDrift));
70           logln(UnicodeString("   ") + theDate + " " + CalendarTest::calToStr(*cal));
71           } else {
72             logln(UnicodeString("OK: got ") + dt + ", wanted " + millis);
73             logln(UnicodeString("    ") + theDate);
74         }
75     }
76 }
77 
78 // -------------------------------------
79 
80 // bug 986c: deprecate nextDouble/previousDouble
81 //|double
82 //|CalendarLimitTest::nextDouble(double a)
83 //|{
84 //|    return uprv_nextDouble(a, TRUE);
85 //|}
86 //|
87 //|double
88 //|CalendarLimitTest::previousDouble(double a)
89 //|{
90 //|    return uprv_nextDouble(a, FALSE);
91 //|}
92 
93 UBool
withinErr(double a,double b,double err)94 CalendarLimitTest::withinErr(double a, double b, double err)
95 {
96     return ( uprv_fabs(a - b) < uprv_fabs(a * err) );
97 }
98 
99 void
TestCalendarExtremeLimit()100 CalendarLimitTest::TestCalendarExtremeLimit()
101 {
102     UErrorCode status = U_ZERO_ERROR;
103     Calendar *cal = Calendar::createInstance(status);
104     if (failure(status, "Calendar::createInstance", TRUE)) return;
105     cal->adoptTimeZone(TimeZone::createTimeZone("GMT"));
106     DateFormat *fmt = DateFormat::createDateTimeInstance();
107     if(!fmt || !cal) {
108        dataerrln("can't open cal and/or fmt");
109        return;
110     }
111     fmt->adoptCalendar(cal);
112     ((SimpleDateFormat*) fmt)->applyPattern("HH:mm:ss.SSS Z, EEEE, MMMM d, yyyy G");
113 
114 
115     // This test used to test the algorithmic limits of the dates that
116     // GregorianCalendar could handle.  However, the algorithm has
117     // been rewritten completely since then and the prior limits no
118     // longer apply.  Instead, we now do basic round-trip testing of
119     // some extreme (but still manageable) dates.
120     UDate m;
121     logln("checking 1e16..1e17");
122     for ( m = 1e16; m < 1e17; m *= 1.1) {
123         test(m, cal, fmt);
124     }
125     logln("checking -1e14..-1e15");
126     for ( m = -1e14; m > -1e15; m *= 1.1) {
127         test(m, cal, fmt);
128     }
129 
130     // This is 2^52 - 1, the largest allowable mantissa with a 0
131     // exponent in a 64-bit double
132     UDate VERY_EARLY_MILLIS = - 4503599627370495.0;
133     UDate VERY_LATE_MILLIS  =   4503599627370495.0;
134 
135     // I am removing the previousDouble and nextDouble calls below for
136     // two reasons: 1. As part of jitterbug 986, I am deprecating
137     // these methods and removing calls to them.  2. This test is a
138     // non-critical boundary behavior test.
139     test(VERY_EARLY_MILLIS, cal, fmt);
140     //test(previousDouble(VERY_EARLY_MILLIS), cal, fmt);
141     test(VERY_LATE_MILLIS, cal, fmt);
142     //test(nextDouble(VERY_LATE_MILLIS), cal, fmt);
143     delete fmt;
144 }
145 
146 namespace {
147 
148 struct TestCase {
149     const char *type;
150     UBool hasLeapMonth;
151     UDate actualTestStart;
152     int32_t actualTestEnd;
153 };
154 
155 const UDate DEFAULT_START = 944006400000.0; // 1999-12-01T00:00Z
156 const int32_t DEFAULT_END = -120; // Default for non-quick is run 2 minutes
157 
158 TestCase TestCases[] = {
159         {"gregorian",       FALSE,      DEFAULT_START, DEFAULT_END},
160         {"japanese",        FALSE,      596937600000.0, DEFAULT_END}, // 1988-12-01T00:00Z, Showa 63
161         {"buddhist",        FALSE,      DEFAULT_START, DEFAULT_END},
162         {"roc",             FALSE,      DEFAULT_START, DEFAULT_END},
163         {"persian",         FALSE,      DEFAULT_START, DEFAULT_END},
164         {"islamic-civil",   FALSE,      DEFAULT_START, DEFAULT_END},
165         {"islamic",         FALSE,      DEFAULT_START, 800000}, // Approx. 2250 years from now, after which
166                                                                 // some rounding errors occur in Islamic calendar
167         {"hebrew",          TRUE,       DEFAULT_START, DEFAULT_END},
168         {"chinese",         TRUE,       DEFAULT_START, DEFAULT_END},
169         {"dangi",           TRUE,       DEFAULT_START, DEFAULT_END},
170         {"indian",          FALSE,      DEFAULT_START, DEFAULT_END},
171         {"coptic",          FALSE,      DEFAULT_START, DEFAULT_END},
172         {"ethiopic",        FALSE,      DEFAULT_START, DEFAULT_END},
173         {"ethiopic-amete-alem", FALSE,  DEFAULT_START, DEFAULT_END}
174 };
175 
176 struct {
177     int32_t fIndex;
next__anone6e4d7180111::__anone6e4d7180208178     UBool next (int32_t &rIndex) {
179         Mutex lock;
180         if (fIndex >= UPRV_LENGTHOF(TestCases)) {
181             return FALSE;
182         }
183         rIndex = fIndex++;
184         return TRUE;
185     }
reset__anone6e4d7180111::__anone6e4d7180208186     void reset() {
187         fIndex = 0;
188     }
189 } gTestCaseIterator;
190 
191 }  // anonymous name space
192 
193 void
TestLimits(void)194 CalendarLimitTest::TestLimits(void) {
195     gTestCaseIterator.reset();
196 
197     ThreadPool<CalendarLimitTest> threads(this, threadCount, &CalendarLimitTest::TestLimitsThread);
198     threads.start();
199     threads.join();
200 }
201 
202 
TestLimitsThread(int32_t threadNum)203 void CalendarLimitTest::TestLimitsThread(int32_t threadNum) {
204     logln("thread %d starting", threadNum);
205     int32_t testIndex = 0;
206     LocalPointer<Calendar> cal;
207     while (gTestCaseIterator.next(testIndex)) {
208         TestCase &testCase = TestCases[testIndex];
209         logln("begin test of %s calendar.", testCase.type);
210         UErrorCode status = U_ZERO_ERROR;
211         char buf[64];
212         uprv_strcpy(buf, "root@calendar=");
213         strcat(buf, testCase.type);
214         cal.adoptInstead(Calendar::createInstance(buf, status));
215         if (failure(status, "Calendar::createInstance", TRUE)) {
216             continue;
217         }
218         if (uprv_strcmp(cal->getType(), testCase.type) != 0) {
219             errln((UnicodeString)"FAIL: Wrong calendar type: " + cal->getType()
220                 + " Requested: " + testCase.type);
221             continue;
222         }
223         doTheoreticalLimitsTest(*(cal.getAlias()), testCase.hasLeapMonth);
224         doLimitsTest(*(cal.getAlias()), testCase.actualTestStart, testCase.actualTestEnd);
225         logln("end test of %s calendar.", testCase.type);
226     }
227 }
228 
229 
230 void
doTheoreticalLimitsTest(Calendar & cal,UBool leapMonth)231 CalendarLimitTest::doTheoreticalLimitsTest(Calendar& cal, UBool leapMonth) {
232     const char* calType = cal.getType();
233 
234     int32_t nDOW = cal.getMaximum(UCAL_DAY_OF_WEEK);
235     int32_t maxDOY = cal.getMaximum(UCAL_DAY_OF_YEAR);
236     int32_t lmaxDOW = cal.getLeastMaximum(UCAL_DAY_OF_YEAR);
237     int32_t maxWOY = cal.getMaximum(UCAL_WEEK_OF_YEAR);
238     int32_t lmaxWOY = cal.getLeastMaximum(UCAL_WEEK_OF_YEAR);
239     int32_t maxM = cal.getMaximum(UCAL_MONTH) + 1;
240     int32_t lmaxM = cal.getLeastMaximum(UCAL_MONTH) + 1;
241     int32_t maxDOM = cal.getMaximum(UCAL_DAY_OF_MONTH);
242     int32_t lmaxDOM = cal.getLeastMaximum(UCAL_DAY_OF_MONTH);
243     int32_t maxDOWIM = cal.getMaximum(UCAL_DAY_OF_WEEK_IN_MONTH);
244     int32_t lmaxDOWIM = cal.getLeastMaximum(UCAL_DAY_OF_WEEK_IN_MONTH);
245     int32_t maxWOM = cal.getMaximum(UCAL_WEEK_OF_MONTH);
246     int32_t lmaxWOM = cal.getLeastMaximum(UCAL_WEEK_OF_MONTH);
247     int32_t minDaysInFirstWeek = cal.getMinimalDaysInFirstWeek();
248 
249     // Day of year
250     int32_t expected;
251     if (!leapMonth) {
252         expected = maxM*maxDOM;
253         if (maxDOY > expected) {
254             errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of DAY_OF_YEAR is too big: "
255                 + maxDOY + "/expected: <=" + expected);
256         }
257         expected = lmaxM*lmaxDOM;
258         if (lmaxDOW < expected) {
259             errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of DAY_OF_YEAR is too small: "
260                 + lmaxDOW + "/expected: >=" + expected);
261         }
262     }
263 
264     // Week of year
265     expected = maxDOY/nDOW + 1;
266     if (maxWOY > expected) {
267         errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of WEEK_OF_YEAR is too big: "
268             + maxWOY + "/expected: <=" + expected);
269     }
270     expected = lmaxDOW/nDOW;
271     if (lmaxWOY < expected) {
272         errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of WEEK_OF_YEAR is too small: "
273             + lmaxWOY + "/expected >=" + expected);
274     }
275 
276     // Day of week in month
277     expected = (maxDOM + nDOW - 1)/nDOW;
278     if (maxDOWIM != expected) {
279         errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
280             + maxDOWIM + "/expected: " + expected);
281     }
282     expected = (lmaxDOM + nDOW - 1)/nDOW;
283     if (lmaxDOWIM != expected) {
284         errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of DAY_OF_WEEK_IN_MONTH is incorrect: "
285             + lmaxDOWIM + "/expected: " + expected);
286     }
287 
288     // Week of month
289     expected = (maxDOM + (nDOW - 1) + (nDOW - minDaysInFirstWeek)) / nDOW;
290     if (maxWOM != expected) {
291         errln((UnicodeString)"FAIL: [" + calType + "] Maximum value of WEEK_OF_MONTH is incorrect: "
292             + maxWOM + "/expected: " + expected);
293     }
294     expected = (lmaxDOM + (nDOW - minDaysInFirstWeek)) / nDOW;
295     if (lmaxWOM != expected) {
296         errln((UnicodeString)"FAIL: [" + calType + "] Least maximum value of WEEK_OF_MONTH is incorrect: "
297             + lmaxWOM + "/expected: " + expected);
298     }
299 }
300 
301 void
doLimitsTest(Calendar & cal,UDate startDate,int32_t endTime)302 CalendarLimitTest::doLimitsTest(Calendar& cal, UDate startDate, int32_t endTime) {
303     int32_t testTime = quick ? ( endTime / 40 ) : endTime;
304     doLimitsTest(cal, NULL /*default fields*/, startDate, testTime);
305 }
306 
307 void
doLimitsTest(Calendar & cal,const int32_t * fieldsToTest,UDate startDate,int32_t testDuration)308 CalendarLimitTest::doLimitsTest(Calendar& cal,
309                                 const int32_t* fieldsToTest,
310                                 UDate startDate,
311                                 int32_t testDuration) {
312     static const int32_t FIELDS[] = {
313         UCAL_ERA,
314         UCAL_YEAR,
315         UCAL_MONTH,
316         UCAL_WEEK_OF_YEAR,
317         UCAL_WEEK_OF_MONTH,
318         UCAL_DAY_OF_MONTH,
319         UCAL_DAY_OF_YEAR,
320         UCAL_DAY_OF_WEEK_IN_MONTH,
321         UCAL_YEAR_WOY,
322         UCAL_EXTENDED_YEAR,
323         -1,
324     };
325 
326     static const char* FIELD_NAME[] = {
327         "ERA", "YEAR", "MONTH", "WEEK_OF_YEAR", "WEEK_OF_MONTH",
328         "DAY_OF_MONTH", "DAY_OF_YEAR", "DAY_OF_WEEK",
329         "DAY_OF_WEEK_IN_MONTH", "AM_PM", "HOUR", "HOUR_OF_DAY",
330         "MINUTE", "SECOND", "MILLISECOND", "ZONE_OFFSET",
331         "DST_OFFSET", "YEAR_WOY", "DOW_LOCAL", "EXTENDED_YEAR",
332         "JULIAN_DAY", "MILLISECONDS_IN_DAY",
333         "IS_LEAP_MONTH"
334     };
335 
336     UErrorCode status = U_ZERO_ERROR;
337     int32_t i, j;
338     UnicodeString ymd;
339 
340     GregorianCalendar greg(status);
341     if (failure(status, "new GregorianCalendar")) {
342         return;
343     }
344     greg.setTime(startDate, status);
345     if (failure(status, "GregorianCalendar::setTime")) {
346         return;
347     }
348     logln((UnicodeString)"Start: " + startDate);
349 
350     if (fieldsToTest == NULL) {
351         fieldsToTest = FIELDS;
352     }
353 
354 
355     // Keep a record of minima and maxima that we actually see.
356     // These are kept in an array of arrays of hashes.
357     int32_t limits[UCAL_FIELD_COUNT][4];
358     for (j = 0; j < UCAL_FIELD_COUNT; j++) {
359         limits[j][0] = INT32_MAX;
360         limits[j][1] = INT32_MIN;
361         limits[j][2] = INT32_MAX;
362         limits[j][3] = INT32_MIN;
363     }
364 
365     // This test can run for a long time; show progress.
366     UDate millis = ucal_getNow();
367     UDate mark = millis + 5000; // 5 sec
368     millis -= testDuration * 1000; // stop time if testDuration<0
369 
370     for (i = 0;
371          testDuration > 0 ? i < testDuration
372                         : ucal_getNow() < millis;
373          ++i) {
374         if (ucal_getNow() >= mark) {
375             logln((UnicodeString)"(" + i + " days)");
376             mark += 5000; // 5 sec
377         }
378         UDate testMillis = greg.getTime(status);
379         cal.setTime(testMillis, status);
380         cal.setMinimalDaysInFirstWeek(1);
381         if (failure(status, "Calendar set/getTime")) {
382             return;
383         }
384         for (j = 0; fieldsToTest[j] >= 0; ++j) {
385             UCalendarDateFields f = (UCalendarDateFields)fieldsToTest[j];
386             int32_t v = cal.get(f, status);
387             int32_t minActual = cal.getActualMinimum(f, status);
388             int32_t maxActual = cal.getActualMaximum(f, status);
389             int32_t minLow = cal.getMinimum(f);
390             int32_t minHigh = cal.getGreatestMinimum(f);
391             int32_t maxLow = cal.getLeastMaximum(f);
392             int32_t maxHigh = cal.getMaximum(f);
393 
394             if (limits[j][0] > minActual) {
395                 // the minimum
396                 limits[j][0] = minActual;
397             }
398             if (limits[j][1] < minActual) {
399                 // the greatest minimum
400                 limits[j][1] = minActual;
401             }
402             if (limits[j][2] > maxActual) {
403                 // the least maximum
404                 limits[j][2] = maxActual;
405             }
406             if (limits[j][3] < maxActual) {
407                 // the maximum
408                 limits[j][3] = maxActual;
409             }
410 
411             if (minActual < minLow || minActual > minHigh) {
412                 errln((UnicodeString)"Fail: [" + cal.getType() + "] " +
413                       ymdToString(cal, ymd) +
414                       " Range for min of " + FIELD_NAME[f] + "(" + f +
415                       ")=" + minLow + ".." + minHigh +
416                       ", actual_min=" + minActual);
417             }
418             if (maxActual < maxLow || maxActual > maxHigh) {
419                 if ( uprv_strcmp(cal.getType(), "chinese") == 0 &&
420                         testMillis >= 1802044800000.0 &&
421                      logKnownIssue("12620", "chinese calendar failures for some actualMax tests")) {
422                     logln((UnicodeString)"KnownFail: [" + cal.getType() + "] " +
423                           ymdToString(cal, ymd) +
424                           " Range for max of " + FIELD_NAME[f] + "(" + f +
425                           ")=" + maxLow + ".." + maxHigh +
426                           ", actual_max=" + maxActual);
427                 } else {
428                     errln((UnicodeString)"Fail: [" + cal.getType() + "] " +
429                           ymdToString(cal, ymd) +
430                           " Range for max of " + FIELD_NAME[f] + "(" + f +
431                           ")=" + maxLow + ".." + maxHigh +
432                           ", actual_max=" + maxActual);
433                 }
434             }
435             if (v < minActual || v > maxActual) {
436                 // timebomb per #9967, fix with #9972
437                 if ( uprv_strcmp(cal.getType(), "dangi") == 0 &&
438                         testMillis >= 1865635198000.0  &&
439                      logKnownIssue("9972", "as per #9967")) { // Feb 2029 gregorian, end of dangi 4361
440                     logln((UnicodeString)"KnownFail: [" + cal.getType() + "] " +
441                           ymdToString(cal, ymd) +
442                           " " + FIELD_NAME[f] + "(" + f + ")=" + v +
443                           ", actual=" + minActual + ".." + maxActual +
444                           ", allowed=(" + minLow + ".." + minHigh + ")..(" +
445                           maxLow + ".." + maxHigh + ")");
446                 } else if ( uprv_strcmp(cal.getType(), "chinese") == 0 &&
447                         testMillis >= 1832544000000.0 &&
448                      logKnownIssue("12620", "chinese calendar failures for some actualMax tests")) {
449                     logln((UnicodeString)"KnownFail: [" + cal.getType() + "] " +
450                           ymdToString(cal, ymd) +
451                           " " + FIELD_NAME[f] + "(" + f + ")=" + v +
452                           ", actual=" + minActual + ".." + maxActual +
453                           ", allowed=(" + minLow + ".." + minHigh + ")..(" +
454                           maxLow + ".." + maxHigh + ")");
455                 } else {
456                     errln((UnicodeString)"Fail: [" + cal.getType() + "] " +
457                           ymdToString(cal, ymd) +
458                           " " + FIELD_NAME[f] + "(" + f + ")=" + v +
459                           ", actual=" + minActual + ".." + maxActual +
460                           ", allowed=(" + minLow + ".." + minHigh + ")..(" +
461                           maxLow + ".." + maxHigh + ")");
462                 }
463             }
464         }
465         greg.add(UCAL_DAY_OF_YEAR, 1, status);
466         if (failure(status, "Calendar::add")) {
467             return;
468         }
469     }
470 
471     // Check actual maxima and minima seen against ranges returned
472     // by API.
473     UnicodeString buf;
474     for (j = 0; fieldsToTest[j] >= 0; ++j) {
475         int32_t rangeLow, rangeHigh;
476         UBool fullRangeSeen = TRUE;
477         UCalendarDateFields f = (UCalendarDateFields)fieldsToTest[j];
478 
479         buf.remove();
480         buf.append((UnicodeString)"[" + cal.getType() + "] " + FIELD_NAME[f]);
481 
482         // Minumum
483         rangeLow = cal.getMinimum(f);
484         rangeHigh = cal.getGreatestMinimum(f);
485         if (limits[j][0] != rangeLow || limits[j][1] != rangeHigh) {
486             fullRangeSeen = FALSE;
487         }
488         buf.append((UnicodeString)" minima range=" + rangeLow + ".." + rangeHigh);
489         buf.append((UnicodeString)" minima actual=" + limits[j][0] + ".." + limits[j][1]);
490 
491         // Maximum
492         rangeLow = cal.getLeastMaximum(f);
493         rangeHigh = cal.getMaximum(f);
494         if (limits[j][2] != rangeLow || limits[j][3] != rangeHigh) {
495             fullRangeSeen = FALSE;
496         }
497         buf.append((UnicodeString)" maxima range=" + rangeLow + ".." + rangeHigh);
498         buf.append((UnicodeString)" maxima actual=" + limits[j][2] + ".." + limits[j][3]);
499 
500         if (fullRangeSeen) {
501             logln((UnicodeString)"OK: " + buf);
502         } else {
503             // This may or may not be an error -- if the range of dates
504             // we scan over doesn't happen to contain a minimum or
505             // maximum, it doesn't mean some other range won't.
506             logln((UnicodeString)"Warning: " + buf);
507         }
508     }
509 
510     logln((UnicodeString)"End: " + greg.getTime(status));
511 }
512 
513 UnicodeString&
ymdToString(const Calendar & cal,UnicodeString & str)514 CalendarLimitTest::ymdToString(const Calendar& cal, UnicodeString& str) {
515     UErrorCode status = U_ZERO_ERROR;
516     str.remove();
517     str.append((UnicodeString)"" + cal.get(UCAL_EXTENDED_YEAR, status)
518         + "/" + (cal.get(UCAL_MONTH, status) + 1)
519         + (cal.get(UCAL_IS_LEAP_MONTH, status) == 1 ? "(leap)" : "")
520         + "/" + cal.get(UCAL_DATE, status)
521         + " " + cal.get(UCAL_HOUR_OF_DAY, status)
522         + ":" + cal.get(UCAL_MINUTE, status)
523         + " zone(hrs) " + cal.get(UCAL_ZONE_OFFSET, status)/(60.0*60.0*1000.0)
524         + " dst(hrs) " + cal.get(UCAL_DST_OFFSET, status)/(60.0*60.0*1000.0)
525         + ", time(millis)=" + cal.getTime(status));
526     return str;
527 }
528 
529 #endif /* #if !UCONFIG_NO_FORMATTING */
530 
531 // eof
532