1 /* 2 ******************************************************************************* 3 * Copyright (C) 1996-2011, International Business Machines 4 * Corporation and others. All Rights Reserved. 5 ******************************************************************************* 6 */ 7 8 #include <typeinfo> // for 'typeid' to work 9 10 #include "unicode/utypes.h" 11 12 #if !UCONFIG_NO_FORMATTING 13 14 #include "unicode/ucal.h" 15 #include "unicode/uloc.h" 16 #include "unicode/calendar.h" 17 #include "unicode/timezone.h" 18 #include "unicode/gregocal.h" 19 #include "unicode/simpletz.h" 20 #include "unicode/ustring.h" 21 #include "unicode/strenum.h" 22 #include "cmemory.h" 23 #include "cstring.h" 24 #include "ustrenum.h" 25 #include "uenumimp.h" 26 #include "ulist.h" 27 28 U_NAMESPACE_USE 29 30 static TimeZone* 31 _createTimeZone(const UChar* zoneID, int32_t len, UErrorCode* ec) { 32 TimeZone* zone = NULL; 33 if (ec!=NULL && U_SUCCESS(*ec)) { 34 // Note that if zoneID is invalid, we get back GMT. This odd 35 // behavior is by design and goes back to the JDK. The only 36 // failure we will see is a memory allocation failure. 37 int32_t l = (len<0 ? u_strlen(zoneID) : len); 38 UnicodeString zoneStrID; 39 zoneStrID.setTo((UBool)(len < 0), zoneID, l); /* temporary read-only alias */ 40 zone = TimeZone::createTimeZone(zoneStrID); 41 if (zone == NULL) { 42 *ec = U_MEMORY_ALLOCATION_ERROR; 43 } 44 } 45 return zone; 46 } 47 48 U_CAPI UEnumeration* U_EXPORT2 49 ucal_openTimeZoneIDEnumeration(USystemTimeZoneType zoneType, const char* region, 50 const int32_t* rawOffset, UErrorCode* ec) { 51 return uenum_openFromStringEnumeration(TimeZone::createTimeZoneIDEnumeration( 52 zoneType, region, rawOffset, *ec), ec); 53 } 54 55 U_CAPI UEnumeration* U_EXPORT2 56 ucal_openTimeZones(UErrorCode* ec) { 57 return uenum_openFromStringEnumeration(TimeZone::createEnumeration(), ec); 58 } 59 60 U_CAPI UEnumeration* U_EXPORT2 61 ucal_openCountryTimeZones(const char* country, UErrorCode* ec) { 62 return uenum_openFromStringEnumeration(TimeZone::createEnumeration(country), ec); 63 } 64 65 U_CAPI int32_t U_EXPORT2 66 ucal_getDefaultTimeZone(UChar* result, int32_t resultCapacity, UErrorCode* ec) { 67 int32_t len = 0; 68 if (ec!=NULL && U_SUCCESS(*ec)) { 69 TimeZone* zone = TimeZone::createDefault(); 70 if (zone == NULL) { 71 *ec = U_MEMORY_ALLOCATION_ERROR; 72 } else { 73 UnicodeString id; 74 zone->getID(id); 75 delete zone; 76 len = id.extract(result, resultCapacity, *ec); 77 } 78 } 79 return len; 80 } 81 82 U_CAPI void U_EXPORT2 83 ucal_setDefaultTimeZone(const UChar* zoneID, UErrorCode* ec) { 84 TimeZone* zone = _createTimeZone(zoneID, -1, ec); 85 if (zone != NULL) { 86 TimeZone::adoptDefault(zone); 87 } 88 } 89 90 U_CAPI int32_t U_EXPORT2 91 ucal_getDSTSavings(const UChar* zoneID, UErrorCode* ec) { 92 int32_t result = 0; 93 TimeZone* zone = _createTimeZone(zoneID, -1, ec); 94 if (U_SUCCESS(*ec)) { 95 SimpleTimeZone* stz = dynamic_cast<SimpleTimeZone*>(zone); 96 if (stz != NULL) { 97 result = stz->getDSTSavings(); 98 } else { 99 // Since there is no getDSTSavings on TimeZone, we use a 100 // heuristic: Starting with the current time, march 101 // forwards for one year, looking for DST savings. 102 // Stepping by weeks is sufficient. 103 UDate d = Calendar::getNow(); 104 for (int32_t i=0; i<53; ++i, d+=U_MILLIS_PER_DAY*7.0) { 105 int32_t raw, dst; 106 zone->getOffset(d, FALSE, raw, dst, *ec); 107 if (U_FAILURE(*ec)) { 108 break; 109 } else if (dst != 0) { 110 result = dst; 111 break; 112 } 113 } 114 } 115 } 116 delete zone; 117 return result; 118 } 119 120 U_CAPI UDate U_EXPORT2 121 ucal_getNow() 122 { 123 124 return Calendar::getNow(); 125 } 126 127 #define ULOC_LOCALE_IDENTIFIER_CAPACITY (ULOC_FULLNAME_CAPACITY + 1 + ULOC_KEYWORD_AND_VALUES_CAPACITY) 128 129 U_CAPI UCalendar* U_EXPORT2 130 ucal_open( const UChar* zoneID, 131 int32_t len, 132 const char* locale, 133 UCalendarType caltype, 134 UErrorCode* status) 135 { 136 137 if(U_FAILURE(*status)) return 0; 138 139 TimeZone* zone = (zoneID==NULL) ? TimeZone::createDefault() 140 : _createTimeZone(zoneID, len, status); 141 142 if (U_FAILURE(*status)) { 143 return NULL; 144 } 145 146 if ( caltype == UCAL_GREGORIAN ) { 147 char localeBuf[ULOC_LOCALE_IDENTIFIER_CAPACITY]; 148 if ( locale == NULL ) { 149 locale = uloc_getDefault(); 150 } 151 uprv_strncpy(localeBuf, locale, ULOC_LOCALE_IDENTIFIER_CAPACITY); 152 uloc_setKeywordValue("calendar", "gregorian", localeBuf, ULOC_LOCALE_IDENTIFIER_CAPACITY, status); 153 if (U_FAILURE(*status)) { 154 return NULL; 155 } 156 return (UCalendar*)Calendar::createInstance(zone, Locale(localeBuf), *status); 157 } 158 return (UCalendar*)Calendar::createInstance(zone, Locale(locale), *status); 159 } 160 161 U_CAPI void U_EXPORT2 162 ucal_close(UCalendar *cal) 163 { 164 165 delete (Calendar*) cal; 166 } 167 168 U_CAPI UCalendar* U_EXPORT2 169 ucal_clone(const UCalendar* cal, 170 UErrorCode* status) 171 { 172 if(U_FAILURE(*status)) return 0; 173 174 Calendar* res = ((Calendar*)cal)->clone(); 175 176 if(res == 0) { 177 *status = U_MEMORY_ALLOCATION_ERROR; 178 return 0; 179 } 180 181 return (UCalendar*) res; 182 } 183 184 U_CAPI void U_EXPORT2 185 ucal_setTimeZone( UCalendar* cal, 186 const UChar* zoneID, 187 int32_t len, 188 UErrorCode *status) 189 { 190 191 if(U_FAILURE(*status)) 192 return; 193 194 TimeZone* zone = (zoneID==NULL) ? TimeZone::createDefault() 195 : _createTimeZone(zoneID, len, status); 196 197 if (zone != NULL) { 198 ((Calendar*)cal)->adoptTimeZone(zone); 199 } 200 } 201 202 U_CAPI int32_t U_EXPORT2 203 ucal_getTimeZoneDisplayName(const UCalendar* cal, 204 UCalendarDisplayNameType type, 205 const char *locale, 206 UChar* result, 207 int32_t resultLength, 208 UErrorCode* status) 209 { 210 211 if(U_FAILURE(*status)) return -1; 212 213 const TimeZone& tz = ((Calendar*)cal)->getTimeZone(); 214 UnicodeString id; 215 if(!(result==NULL && resultLength==0)) { 216 // NULL destination for pure preflighting: empty dummy string 217 // otherwise, alias the destination buffer 218 id.setTo(result, 0, resultLength); 219 } 220 221 switch(type) { 222 case UCAL_STANDARD: 223 tz.getDisplayName(FALSE, TimeZone::LONG, Locale(locale), id); 224 break; 225 226 case UCAL_SHORT_STANDARD: 227 tz.getDisplayName(FALSE, TimeZone::SHORT, Locale(locale), id); 228 break; 229 230 case UCAL_DST: 231 tz.getDisplayName(TRUE, TimeZone::LONG, Locale(locale), id); 232 break; 233 234 case UCAL_SHORT_DST: 235 tz.getDisplayName(TRUE, TimeZone::SHORT, Locale(locale), id); 236 break; 237 } 238 239 return id.extract(result, resultLength, *status); 240 } 241 242 U_CAPI UBool U_EXPORT2 243 ucal_inDaylightTime( const UCalendar* cal, 244 UErrorCode* status ) 245 { 246 247 if(U_FAILURE(*status)) return (UBool) -1; 248 return ((Calendar*)cal)->inDaylightTime(*status); 249 } 250 251 U_CAPI void U_EXPORT2 252 ucal_setGregorianChange(UCalendar *cal, UDate date, UErrorCode *pErrorCode) { 253 if(U_FAILURE(*pErrorCode)) { 254 return; 255 } 256 Calendar *cpp_cal = (Calendar *)cal; 257 GregorianCalendar *gregocal = dynamic_cast<GregorianCalendar *>(cpp_cal); 258 // Not if(gregocal == NULL) { 259 // because we really want to work only with a GregorianCalendar, not with 260 // its subclasses like BuddhistCalendar. 261 // BEGIN android-added. 262 // See ICU ticket#9047. 263 if (cpp_cal == NULL) { 264 // We normally don't check "this" pointers for NULL, but this here avoids 265 // compiler-generated exception-throwing code in case cal == NULL. 266 *pErrorCode = U_ILLEGAL_ARGUMENT_ERROR; 267 return; 268 } 269 // END android-added 270 if(typeid(*cpp_cal) != typeid(GregorianCalendar)) { 271 *pErrorCode = U_UNSUPPORTED_ERROR; 272 return; 273 } 274 gregocal->setGregorianChange(date, *pErrorCode); 275 } 276 277 U_CAPI UDate U_EXPORT2 278 ucal_getGregorianChange(const UCalendar *cal, UErrorCode *pErrorCode) { 279 if(U_FAILURE(*pErrorCode)) { 280 return (UDate)0; 281 } 282 const Calendar *cpp_cal = (const Calendar *)cal; 283 const GregorianCalendar *gregocal = dynamic_cast<const GregorianCalendar *>(cpp_cal); 284 // Not if(gregocal == NULL) { 285 // see comments in ucal_setGregorianChange(). 286 // BEGIN android-added. 287 // See ICU ticket#9047. 288 if (cpp_cal == NULL) { 289 // We normally don't check "this" pointers for NULL, but this here avoids 290 // compiler-generated exception-throwing code in case cal == NULL. 291 *pErrorCode = U_ILLEGAL_ARGUMENT_ERROR; 292 return (UDate)0; 293 } 294 // END android-added 295 if(typeid(*cpp_cal) != typeid(GregorianCalendar)) { 296 *pErrorCode = U_UNSUPPORTED_ERROR; 297 return (UDate)0; 298 } 299 return gregocal->getGregorianChange(); 300 } 301 302 U_CAPI int32_t U_EXPORT2 303 ucal_getAttribute( const UCalendar* cal, 304 UCalendarAttribute attr) 305 { 306 307 switch(attr) { 308 case UCAL_LENIENT: 309 return ((Calendar*)cal)->isLenient(); 310 311 case UCAL_FIRST_DAY_OF_WEEK: 312 return ((Calendar*)cal)->getFirstDayOfWeek(); 313 314 case UCAL_MINIMAL_DAYS_IN_FIRST_WEEK: 315 return ((Calendar*)cal)->getMinimalDaysInFirstWeek(); 316 317 default: 318 break; 319 } 320 return -1; 321 } 322 323 U_CAPI void U_EXPORT2 324 ucal_setAttribute( UCalendar* cal, 325 UCalendarAttribute attr, 326 int32_t newValue) 327 { 328 329 switch(attr) { 330 case UCAL_LENIENT: 331 ((Calendar*)cal)->setLenient((UBool)newValue); 332 break; 333 334 case UCAL_FIRST_DAY_OF_WEEK: 335 ((Calendar*)cal)->setFirstDayOfWeek((UCalendarDaysOfWeek)newValue); 336 break; 337 338 case UCAL_MINIMAL_DAYS_IN_FIRST_WEEK: 339 ((Calendar*)cal)->setMinimalDaysInFirstWeek((uint8_t)newValue); 340 break; 341 } 342 } 343 344 U_CAPI const char* U_EXPORT2 345 ucal_getAvailable(int32_t index) 346 { 347 348 return uloc_getAvailable(index); 349 } 350 351 U_CAPI int32_t U_EXPORT2 352 ucal_countAvailable() 353 { 354 355 return uloc_countAvailable(); 356 } 357 358 U_CAPI UDate U_EXPORT2 359 ucal_getMillis( const UCalendar* cal, 360 UErrorCode* status) 361 { 362 363 if(U_FAILURE(*status)) return (UDate) 0; 364 365 return ((Calendar*)cal)->getTime(*status); 366 } 367 368 U_CAPI void U_EXPORT2 369 ucal_setMillis( UCalendar* cal, 370 UDate dateTime, 371 UErrorCode* status ) 372 { 373 if(U_FAILURE(*status)) return; 374 375 ((Calendar*)cal)->setTime(dateTime, *status); 376 } 377 378 // TBD: why does this take an UErrorCode? 379 U_CAPI void U_EXPORT2 380 ucal_setDate( UCalendar* cal, 381 int32_t year, 382 int32_t month, 383 int32_t date, 384 UErrorCode *status) 385 { 386 387 if(U_FAILURE(*status)) return; 388 389 ((Calendar*)cal)->set(year, month, date); 390 } 391 392 // TBD: why does this take an UErrorCode? 393 U_CAPI void U_EXPORT2 394 ucal_setDateTime( UCalendar* cal, 395 int32_t year, 396 int32_t month, 397 int32_t date, 398 int32_t hour, 399 int32_t minute, 400 int32_t second, 401 UErrorCode *status) 402 { 403 if(U_FAILURE(*status)) return; 404 405 ((Calendar*)cal)->set(year, month, date, hour, minute, second); 406 } 407 408 U_CAPI UBool U_EXPORT2 409 ucal_equivalentTo( const UCalendar* cal1, 410 const UCalendar* cal2) 411 { 412 413 return ((Calendar*)cal1)->isEquivalentTo(*((Calendar*)cal2)); 414 } 415 416 U_CAPI void U_EXPORT2 417 ucal_add( UCalendar* cal, 418 UCalendarDateFields field, 419 int32_t amount, 420 UErrorCode* status) 421 { 422 423 if(U_FAILURE(*status)) return; 424 425 ((Calendar*)cal)->add(field, amount, *status); 426 } 427 428 U_CAPI void U_EXPORT2 429 ucal_roll( UCalendar* cal, 430 UCalendarDateFields field, 431 int32_t amount, 432 UErrorCode* status) 433 { 434 435 if(U_FAILURE(*status)) return; 436 437 ((Calendar*)cal)->roll(field, amount, *status); 438 } 439 440 U_CAPI int32_t U_EXPORT2 441 ucal_get( const UCalendar* cal, 442 UCalendarDateFields field, 443 UErrorCode* status ) 444 { 445 446 if(U_FAILURE(*status)) return -1; 447 448 return ((Calendar*)cal)->get(field, *status); 449 } 450 451 U_CAPI void U_EXPORT2 452 ucal_set( UCalendar* cal, 453 UCalendarDateFields field, 454 int32_t value) 455 { 456 457 ((Calendar*)cal)->set(field, value); 458 } 459 460 U_CAPI UBool U_EXPORT2 461 ucal_isSet( const UCalendar* cal, 462 UCalendarDateFields field) 463 { 464 465 return ((Calendar*)cal)->isSet(field); 466 } 467 468 U_CAPI void U_EXPORT2 469 ucal_clearField( UCalendar* cal, 470 UCalendarDateFields field) 471 { 472 473 ((Calendar*)cal)->clear(field); 474 } 475 476 U_CAPI void U_EXPORT2 477 ucal_clear(UCalendar* calendar) 478 { 479 480 ((Calendar*)calendar)->clear(); 481 } 482 483 U_CAPI int32_t U_EXPORT2 484 ucal_getLimit( const UCalendar* cal, 485 UCalendarDateFields field, 486 UCalendarLimitType type, 487 UErrorCode *status) 488 { 489 490 if(status==0 || U_FAILURE(*status)) { 491 return -1; 492 } 493 494 switch(type) { 495 case UCAL_MINIMUM: 496 return ((Calendar*)cal)->getMinimum(field); 497 498 case UCAL_MAXIMUM: 499 return ((Calendar*)cal)->getMaximum(field); 500 501 case UCAL_GREATEST_MINIMUM: 502 return ((Calendar*)cal)->getGreatestMinimum(field); 503 504 case UCAL_LEAST_MAXIMUM: 505 return ((Calendar*)cal)->getLeastMaximum(field); 506 507 case UCAL_ACTUAL_MINIMUM: 508 return ((Calendar*)cal)->getActualMinimum(field, 509 *status); 510 511 case UCAL_ACTUAL_MAXIMUM: 512 return ((Calendar*)cal)->getActualMaximum(field, 513 *status); 514 515 default: 516 break; 517 } 518 return -1; 519 } 520 521 U_CAPI const char * U_EXPORT2 522 ucal_getLocaleByType(const UCalendar *cal, ULocDataLocaleType type, UErrorCode* status) 523 { 524 if (cal == NULL) { 525 if (U_SUCCESS(*status)) { 526 *status = U_ILLEGAL_ARGUMENT_ERROR; 527 } 528 return NULL; 529 } 530 return ((Calendar*)cal)->getLocaleID(type, *status); 531 } 532 533 U_CAPI const char * U_EXPORT2 534 ucal_getTZDataVersion(UErrorCode* status) 535 { 536 return TimeZone::getTZDataVersion(*status); 537 } 538 539 U_CAPI int32_t U_EXPORT2 540 ucal_getCanonicalTimeZoneID(const UChar* id, int32_t len, 541 UChar* result, int32_t resultCapacity, UBool *isSystemID, UErrorCode* status) { 542 if(status == 0 || U_FAILURE(*status)) { 543 return 0; 544 } 545 if (isSystemID) { 546 *isSystemID = FALSE; 547 } 548 if (id == 0 || len == 0 || result == 0 || resultCapacity <= 0) { 549 *status = U_ILLEGAL_ARGUMENT_ERROR; 550 return 0; 551 } 552 int32_t reslen = 0; 553 UnicodeString canonical; 554 UBool systemID = FALSE; 555 TimeZone::getCanonicalID(UnicodeString(id, len), canonical, systemID, *status); 556 if (U_SUCCESS(*status)) { 557 if (isSystemID) { 558 *isSystemID = systemID; 559 } 560 reslen = canonical.extract(result, resultCapacity, *status); 561 } 562 return reslen; 563 } 564 565 U_CAPI const char * U_EXPORT2 566 ucal_getType(const UCalendar *cal, UErrorCode* status) 567 { 568 if (U_FAILURE(*status)) { 569 return NULL; 570 } 571 return ((Calendar*)cal)->getType(); 572 } 573 574 U_CAPI UCalendarWeekdayType U_EXPORT2 575 ucal_getDayOfWeekType(const UCalendar *cal, UCalendarDaysOfWeek dayOfWeek, UErrorCode* status) 576 { 577 if (U_FAILURE(*status)) { 578 return UCAL_WEEKDAY; 579 } 580 return ((Calendar*)cal)->getDayOfWeekType(dayOfWeek, *status); 581 } 582 583 U_CAPI int32_t U_EXPORT2 584 ucal_getWeekendTransition(const UCalendar *cal, UCalendarDaysOfWeek dayOfWeek, UErrorCode *status) 585 { 586 if (U_FAILURE(*status)) { 587 return 0; 588 } 589 return ((Calendar*)cal)->getWeekendTransition(dayOfWeek, *status); 590 } 591 592 U_CAPI UBool U_EXPORT2 593 ucal_isWeekend(const UCalendar *cal, UDate date, UErrorCode *status) 594 { 595 if (U_FAILURE(*status)) { 596 return FALSE; 597 } 598 return ((Calendar*)cal)->isWeekend(date, *status); 599 } 600 601 U_CAPI int32_t U_EXPORT2 602 ucal_getFieldDifference(UCalendar* cal, UDate target, 603 UCalendarDateFields field, 604 UErrorCode* status ) 605 { 606 if (U_FAILURE(*status)) { 607 return 0; 608 } 609 return ((Calendar*)cal)->fieldDifference(target, field, *status); 610 } 611 612 613 static const UEnumeration defaultKeywordValues = { 614 NULL, 615 NULL, 616 ulist_close_keyword_values_iterator, 617 ulist_count_keyword_values, 618 uenum_unextDefault, 619 ulist_next_keyword_value, 620 ulist_reset_keyword_values_iterator 621 }; 622 623 static const char * const CAL_TYPES[] = { 624 "gregorian", 625 "japanese", 626 "buddhist", 627 "roc", 628 "persian", 629 "islamic-civil", 630 "islamic", 631 "hebrew", 632 "chinese", 633 "indian", 634 "coptic", 635 "ethiopic", 636 "ethiopic-amete-alem", 637 NULL 638 }; 639 640 U_CAPI UEnumeration* U_EXPORT2 641 ucal_getKeywordValuesForLocale(const char * /* key */, const char* locale, UBool commonlyUsed, UErrorCode *status) { 642 // Resolve region 643 char prefRegion[ULOC_FULLNAME_CAPACITY] = ""; 644 int32_t prefRegionLength = 0; 645 prefRegionLength = uloc_getCountry(locale, prefRegion, sizeof(prefRegion), status); 646 if (prefRegionLength == 0) { 647 char loc[ULOC_FULLNAME_CAPACITY] = ""; 648 int32_t locLength = 0; 649 locLength = uloc_addLikelySubtags(locale, loc, sizeof(loc), status); 650 651 prefRegionLength = uloc_getCountry(loc, prefRegion, sizeof(prefRegion), status); 652 } 653 654 // Read preferred calendar values from supplementalData calendarPreference 655 UResourceBundle *rb = ures_openDirect(NULL, "supplementalData", status); 656 ures_getByKey(rb, "calendarPreferenceData", rb, status); 657 UResourceBundle *order = ures_getByKey(rb, prefRegion, NULL, status); 658 if (*status == U_MISSING_RESOURCE_ERROR && rb != NULL) { 659 *status = U_ZERO_ERROR; 660 order = ures_getByKey(rb, "001", NULL, status); 661 } 662 663 // Create a list of calendar type strings 664 UList *values = NULL; 665 if (U_SUCCESS(*status)) { 666 values = ulist_createEmptyList(status); 667 if (U_SUCCESS(*status)) { 668 for (int i = 0; i < ures_getSize(order); i++) { 669 int32_t len; 670 const UChar *type = ures_getStringByIndex(order, i, &len, status); 671 char *caltype = (char*)uprv_malloc(len + 1); 672 if (caltype == NULL) { 673 *status = U_MEMORY_ALLOCATION_ERROR; 674 break; 675 } 676 u_UCharsToChars(type, caltype, len); 677 *(caltype + len) = 0; 678 679 ulist_addItemEndList(values, caltype, TRUE, status); 680 if (U_FAILURE(*status)) { 681 break; 682 } 683 } 684 685 if (U_SUCCESS(*status) && !commonlyUsed) { 686 // If not commonlyUsed, add other available values 687 for (int32_t i = 0; CAL_TYPES[i] != NULL; i++) { 688 if (!ulist_containsString(values, CAL_TYPES[i], (int32_t)uprv_strlen(CAL_TYPES[i]))) { 689 ulist_addItemEndList(values, CAL_TYPES[i], FALSE, status); 690 if (U_FAILURE(*status)) { 691 break; 692 } 693 } 694 } 695 } 696 if (U_FAILURE(*status)) { 697 ulist_deleteList(values); 698 values = NULL; 699 } 700 } 701 } 702 703 ures_close(order); 704 ures_close(rb); 705 706 if (U_FAILURE(*status) || values == NULL) { 707 return NULL; 708 } 709 710 // Create string enumeration 711 UEnumeration *en = (UEnumeration*)uprv_malloc(sizeof(UEnumeration)); 712 if (en == NULL) { 713 *status = U_MEMORY_ALLOCATION_ERROR; 714 ulist_deleteList(values); 715 return NULL; 716 } 717 ulist_resetList(values); 718 memcpy(en, &defaultKeywordValues, sizeof(UEnumeration)); 719 en->context = values; 720 return en; 721 } 722 723 #endif /* #if !UCONFIG_NO_FORMATTING */ 724