Reactos
at master 715 lines 24 kB view raw
1// 2// tzset.cpp 3// 4// Copyright (c) Microsoft Corporation. All rights reserved. 5// 6// Defines the _tzset() function which updates the global time zone state, and 7// the _isindst() function, which tests whether a time is in Daylight Savings 8// Time or not. 9// 10#include <corecrt_internal_time.h> 11#include <locale.h> 12 13 14 15_DEFINE_SET_FUNCTION(_set_daylight, int, _daylight) 16_DEFINE_SET_FUNCTION(_set_dstbias, long, _dstbias ) 17_DEFINE_SET_FUNCTION(_set_timezone, long, _timezone) 18 19 20 21// Pointer to a saved copy of the TZ value obtained in the previous call to the 22// tzset functions, if one is available: 23static wchar_t* last_wide_tz = nullptr; 24 25// If the time zone was last updated by calling the system API, then the tz_info 26// variable contains the time zone information and tz_api_used is set to true. 27static int tz_api_used; 28static TIME_ZONE_INFORMATION tz_info; 29 30static __crt_state_management::dual_state_global<long> tzset_init_state; 31 32namespace 33{ 34 // Structure used to represent DST transition date/times: 35 struct transitiondate 36 { 37 int yr; // year of interest 38 int yd; // day of year 39 int ms; // milli-seconds in the day 40 }; 41 42 enum class date_type 43 { 44 absolute_date, 45 day_in_month 46 }; 47 48 enum class transition_type 49 { 50 start_of_dst, 51 end_of_dst 52 }; 53 54 size_t const local_env_buffer_size = 256; 55 int const milliseconds_per_day = 24 * 60 * 60 * 1000; 56} 57 58// DST start and end structures: 59static transitiondate dststart = { -1, 0, 0 }; 60static transitiondate dstend = { -1, 0, 0 }; 61 62 63 64//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 65// 66// The _tzset() family of functions 67// 68//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 69// Gets the value of the TZ environment variable. If there is no TZ environment 70// variable or if we do not have access to the environment, nullptr is returned. 71// If the value of the TZ variable fits into the local_buffer, it is stored there 72// and a pointer to the local_buffer is returned. Otherwise, a buffer is 73// dynamically allocated, the value is stored into that buffer, and a pointer to 74// that buffer is returned. In this case, the caller is responsible for freeing 75// the buffer. 76static wchar_t* get_tz_environment_variable(wchar_t (&local_buffer)[local_env_buffer_size]) throw() 77{ 78 size_t required_length; 79 errno_t const status = _wgetenv_s(&required_length, local_buffer, local_env_buffer_size, L"TZ"); 80 if (status == 0) 81 { 82 return local_buffer; 83 } 84 85 if (status != ERANGE) 86 { 87 return nullptr; 88 } 89 90 __crt_unique_heap_ptr<wchar_t> dynamic_buffer(_malloc_crt_t(wchar_t, required_length)); 91 if (dynamic_buffer.get() == nullptr) 92 { 93 return nullptr; 94 } 95 96 size_t actual_length; 97 if (_wgetenv_s(&actual_length, dynamic_buffer.get(), required_length, L"TZ") != 0) 98 { 99 return nullptr; 100 } 101 102 return dynamic_buffer.detach(); 103} 104 105static void __cdecl tzset_os_copy_to_tzname(const wchar_t * const timezone_name, wchar_t * const wide_tzname, char * const narrow_tzname, unsigned int const code_page) 106{ 107 // Maximum time zone name from OS is 32 characters long 108 // (see https://docs.microsoft.com/en-us/windows/desktop/api/timezoneapi/ns-timezoneapi-_time_zone_information) 109 _ERRCHECK(wcsncpy_s(wide_tzname, _TZ_STRINGS_SIZE, timezone_name, 32)); 110 111 // Invalid characters are replaced by closest approximation or default character. 112 // On other failure, leave narrow tzname blank. 113 __acrt_WideCharToMultiByte( 114 code_page, 115 0, 116 timezone_name, 117 -1, 118 narrow_tzname, 119 _TZ_STRINGS_SIZE, // Passing -1 as source size, so null terminator included. 120 nullptr, 121 nullptr 122 ); 123} 124 125// Handles the _tzset if and only if there is no TZ environment variable. In 126// this case, we attempt to use the time zone information from the system. 127static void __cdecl tzset_from_system_nolock() throw() 128{ 129 _BEGIN_SECURE_CRT_DEPRECATION_DISABLE 130 char** tzname = _tzname; 131 wchar_t** wide_tzname = __wide_tzname(); 132 _END_SECURE_CRT_DEPRECATION_DISABLE 133 134 long timezone = 0; 135 int daylight = 0; 136 long dstbias = 0; 137 _ERRCHECK(_get_timezone(&timezone)); 138 _ERRCHECK(_get_daylight(&daylight)); 139 _ERRCHECK(_get_dstbias (&dstbias )); 140 141 // If there is a last_wide_tz already, discard it: 142 _free_crt(last_wide_tz); 143 last_wide_tz = nullptr; 144 145 if (GetTimeZoneInformation(&tz_info) != 0xFFFFFFFF) 146 { 147 // Record that the API was used: 148 tz_api_used = 1; 149 150 // Derive _timezone value from Bias and StandardBias fields. 151 timezone = tz_info.Bias * 60; 152 153 if (tz_info.StandardDate.wMonth != 0) 154 timezone += tz_info.StandardBias * 60; 155 156 // Check to see if there is a daylight time bias. Since the StandardBias 157 // has been added into _timezone, it must be compensated for in the 158 // value computed for _dstbias: 159 if (tz_info.DaylightDate.wMonth != 0 && tz_info.DaylightBias != 0) 160 { 161 daylight = 1; 162 dstbias = (tz_info.DaylightBias - tz_info.StandardBias) * 60; 163 } 164 else 165 { 166 daylight = 0; 167 168 // Set the bias to 0 because GetTimeZoneInformation may return 169 // TIME_ZONE_ID_DAYLIGHT even though there is no DST (e.g., in NT 170 // 3.51, this can happen if automatic DST adjustment is disabled 171 // in the Control Panel. 172 dstbias = 0; 173 } 174 175 memset(wide_tzname[0], 0, _TZ_STRINGS_SIZE * sizeof(wchar_t)); 176 memset(wide_tzname[1], 0, _TZ_STRINGS_SIZE * sizeof(wchar_t)); 177 memset(tzname[0], 0, _TZ_STRINGS_SIZE); 178 memset(tzname[1], 0, _TZ_STRINGS_SIZE); 179 180 // Try to grab the name strings for both the time zone and the daylight 181 // zone. Note the wide character strings in tz_info must be converted 182 // to multibyte character strings. The locale code page must be used 183 // for this. Note that if setlocale() has not yet been called with 184 // LC_ALL or LC_CTYPE, then the code page will be 0, which is CP_ACP, 185 // so we will use the host's default ANSI code page. 186 // 187 // CRT_REFACTOR TODO We use the current locale for this transformation. 188 // If per-thread locale has been enabled for this thread, then we'll be 189 // using this thread's locale to update a global variable that is 190 // accessed from multiple threads. Does the time zone information also 191 // need to be stored per-thread? 192 unsigned const code_page = ___lc_codepage_func(); 193 194 tzset_os_copy_to_tzname(tz_info.StandardName, wide_tzname[0], tzname[0], code_page); 195 tzset_os_copy_to_tzname(tz_info.DaylightName, wide_tzname[1], tzname[1], code_page); 196 } 197 198 _set_timezone(timezone); 199 _set_daylight(daylight); 200 _set_dstbias(dstbias); 201} 202 203static void __cdecl tzset_env_copy_to_tzname(const wchar_t * const tz_env, wchar_t * const wide_tzname, char * const narrow_tzname, rsize_t const tzname_length) 204{ 205 _ERRCHECK(wcsncpy_s(wide_tzname, _TZ_STRINGS_SIZE, tz_env, tzname_length)); 206 207 // Historically when getting _tzname via TZ, the narrow environment was used to populate _tzname when getting _tzname. 208 // The narrow environment is always encoded in the ACP (so _tzname was encoded in the ACP when coming from TZ), but 209 // when getting _tzname from the OS, the current active code page (set via setlocale()) was used instead. 210 // To maintain behavior compatibility, we remain intentionally inconsistent with 211 // how _tzname is generated when getting time zone information from the OS by explicitly encoding with the ACP. 212 // UTF-8 mode is opt-in, so we can correct this inconsistency when the current code page is UTF-8. 213 214 // Invalid characters are replaced by closest approximation or default character. 215 // On other failure, simply leave _tzname blank. 216 __acrt_WideCharToMultiByte( 217 __acrt_get_utf8_acp_compatibility_codepage(), 218 0, 219 wide_tzname, 220 static_cast<int>(tzname_length), 221 narrow_tzname, 222 _TZ_STRINGS_SIZE - 1, // Leave room for null terminator 223 nullptr, 224 nullptr); 225} 226 227static void __cdecl tzset_from_environment_nolock(_In_z_ wchar_t* tz_env) throw() 228{ 229 _BEGIN_SECURE_CRT_DEPRECATION_DISABLE 230 char** tzname = _tzname; 231 wchar_t** wide_tzname = __wide_tzname(); 232 _END_SECURE_CRT_DEPRECATION_DISABLE 233 234 long timezone = 0; 235 int daylight = 0; 236 _ERRCHECK(_get_timezone(&timezone)); 237 _ERRCHECK(_get_daylight(&daylight)); 238 239 // Check to see if the TZ value is unchanged from an earlier call to this 240 // function. If it hasn't changed, we have no work to do: 241 if (last_wide_tz != nullptr && wcscmp(tz_env, last_wide_tz) == 0) 242 { 243 return; 244 } 245 246 // Update the global last_wide_tz variable: 247 auto new_wide_tz = _malloc_crt_t(wchar_t, wcslen(tz_env) + 1); 248 if (!new_wide_tz) 249 { 250 return; 251 } 252 253 _free_crt(last_wide_tz); 254 last_wide_tz = new_wide_tz.detach(); 255 256 _ERRCHECK(wcscpy_s(last_wide_tz, wcslen(tz_env) + 1, tz_env)); 257 258 // Process TZ value and update _tzname, _timezone and _daylight. 259 memset(wide_tzname[0], 0, _TZ_STRINGS_SIZE * sizeof(wchar_t)); 260 memset(wide_tzname[1], 0, _TZ_STRINGS_SIZE * sizeof(wchar_t)); 261 memset(tzname[0], 0, _TZ_STRINGS_SIZE); 262 memset(tzname[1], 0, _TZ_STRINGS_SIZE); 263 264 rsize_t const tzname_length = 3; 265 266 // Copy standard time zone name (index 0) 267 tzset_env_copy_to_tzname(tz_env, wide_tzname[0], tzname[0], tzname_length); 268 269 // Skip first few characters if present. 270 for (rsize_t i = 0; i < tzname_length; ++i) 271 { 272 if (*tz_env) 273 { 274 ++tz_env; 275 } 276 } 277 278 // The time difference is of the form: 279 // [+|-]hh[:mm[:ss]] 280 // Check for the minus sign first: 281 bool const is_negative_difference = *tz_env == L'-'; 282 if (is_negative_difference) 283 { 284 ++tz_env; 285 } 286 287 wchar_t * dummy; 288 int const decimal_base = 10; 289 290 // process, then skip over, the hours 291 timezone = wcstol(tz_env, &dummy, decimal_base) * 3600; 292 while (*tz_env == '+' || (*tz_env >= L'0' && *tz_env <= L'9')) 293 { 294 ++tz_env; 295 } 296 297 298 // Check if minutes were specified: 299 if (*tz_env == L':') 300 { 301 // Process, then skip over, the minutes 302 timezone += wcstol(++tz_env, &dummy, decimal_base) * 60; 303 while (*tz_env >= L'0' && *tz_env <= L'9') 304 { 305 ++tz_env; 306 } 307 308 // Check if seconds were specified: 309 if (*tz_env == L':') 310 { 311 // Process, then skip over, the seconds: 312 timezone += wcstol(++tz_env, &dummy, decimal_base); 313 while (*tz_env >= L'0' && *tz_env <= L'9') 314 { 315 ++tz_env; 316 } 317 } 318 } 319 320 if (is_negative_difference) 321 { 322 timezone = -timezone; 323 } 324 325 // Finally, check for a DST zone suffix: 326 daylight = *tz_env ? 1 : 0; 327 328 if (daylight) 329 { 330 // Copy daylight time zone name (index 1) 331 tzset_env_copy_to_tzname(tz_env, wide_tzname[1], tzname[1], tzname_length); 332 } 333 334 _set_timezone(timezone); 335 _set_daylight(daylight); 336} 337 338static void __cdecl tzset_nolock() throw() 339{ 340 // Clear the flag indicated whether GetTimeZoneInformation was used. 341 tz_api_used = 0; 342 343 // Set year fields of dststart and dstend structures to -1 to ensure 344 // they are recomputed as after this 345 dststart.yr = dstend.yr = -1; 346 347 // Get the value of the TZ environment variable: 348 wchar_t local_env_buffer[local_env_buffer_size]; 349 wchar_t* const tz_env = get_tz_environment_variable(local_env_buffer); 350 351 // If the buffer ended up being dynamically allocated, make sure we 352 // clean it up before we return: 353 __crt_unique_heap_ptr<wchar_t> tz_env_cleanup(tz_env == local_env_buffer 354 ? nullptr 355 : tz_env); 356 357 // If the environment variable is not available for whatever reason, update 358 // without using the environment (note that unless the Desktop CRT is loaded 359 // and we have access to non-MSDK APIs, we will always tak this path). 360 if (tz_env == nullptr || tz_env[0] == '\0') 361 return tzset_from_system_nolock(); 362 363 return tzset_from_environment_nolock(tz_env); 364} 365 366 367 368// Sets the time zone information and calculates whether we are currently in 369// Daylight Savings Time. This reads the TZ environment variable, if that 370// variable exists and can be read by the process; otherwise, the system is 371// queried for the current time zone state. The _daylight, _timezone, and 372// _tzname global variables are updated accordingly. 373extern "C" void __cdecl _tzset() 374{ 375 __acrt_lock(__acrt_time_lock); 376 __try 377 { 378 tzset_nolock(); 379 } 380 __finally 381 { 382 __acrt_unlock(__acrt_time_lock); 383 } 384 __endtry 385} 386 387 388 389// This function may be called to ensure that the time zone information ha sbeen 390// set at least once. If the time zone information has not yet been set, this 391// function sets it. 392extern "C" void __cdecl __tzset() 393{ 394 auto const first_time = tzset_init_state.dangerous_get_state_array() + __crt_state_management::get_current_state_index(); 395 396 if (__crt_interlocked_read(first_time) != 0) 397 { 398 return; 399 } 400 401 __acrt_lock(__acrt_time_lock); 402 __try 403 { 404 if (__crt_interlocked_read(first_time) != 0) 405 { 406 __leave; 407 } 408 409 tzset_nolock(); 410 411 _InterlockedIncrement(first_time); 412 } 413 __finally 414 { 415 __acrt_unlock(__acrt_time_lock); 416 } 417 __endtry 418} 419 420 421 422//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 423// 424// The _isindst() family of functions 425// 426//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 427// Converts the format of a transition date specification to a value of a 428// transitiondate structure. The dststart and dstend global variables are 429// filled in with the converted date. 430static void __cdecl cvtdate( 431 transition_type const trantype, // start or end of DST 432 date_type const datetype, // Day-in-month or absolute date 433 int const year, // Year, as an offset from 1900 434 int const month, // Month, where 0 is January 435 int const week, // Week of month, if datetype is day-in-month 436 int const dayofweek, // Day of week, if datetype is day-in-month 437 int const date, // Date of month (1 - 31) 438 int const hour, // Hours (0 - 23) 439 int const min, // Minutes (0 - 59) 440 int const sec, // Seconds (0 - 59) 441 int const msec // Milliseconds (0 - 999) 442 ) throw() 443{ 444 int yearday; 445 int monthdow; 446 long dstbias = 0; 447 448 if (datetype == date_type::day_in_month) 449 { 450 // Figure out the year-day of the start of the month: 451 yearday = 1 + (__crt_time_is_leap_year(year) 452 ? _lpdays[month - 1] 453 : _days[month - 1]); 454 455 // Figureo ut the day of the week of the start of the month: 456 monthdow = (yearday + ((year - 70) * 365) + 457 __crt_time_elapsed_leap_years(year) + _BASE_DOW) % 7; 458 459 // Figure out the year-day of the transition date: 460 if (monthdow <= dayofweek) 461 yearday += (dayofweek - monthdow) + (week - 1) * 7; 462 else 463 yearday += (dayofweek - monthdow) + week * 7; 464 465 // We may have to adjust the calculation above if week == 5 (meaning the 466 // last instance of the day in the month). Check if the year falls 467 // beyond after month and adjust accordingly: 468 int const days_to_compare = __crt_time_is_leap_year(year) 469 ? _lpdays[month] 470 : _days[month]; 471 472 if (week == 5 && yearday > days_to_compare) 473 { 474 yearday -= 7; 475 } 476 } 477 else 478 { 479 yearday = __crt_time_is_leap_year(year) 480 ? _lpdays[month - 1] 481 : _days[month - 1]; 482 483 yearday += date; 484 } 485 486 if (trantype == transition_type::start_of_dst) 487 { 488 dststart.yd = yearday; 489 dststart.ms = msec + (1000 * (sec + 60 * (min + 60 * hour))); 490 491 // Set the year field of dststart so that unnecessary calls to cvtdate() 492 // may be avoided: 493 dststart.yr = year; 494 } 495 else // end_of_dst 496 { 497 dstend.yd = yearday; 498 dstend.ms = msec + (1000 * (sec + 60 * (min + 60 * hour))); 499 500 // The converted date is still a DST date. We must convert to a standard 501 // (local) date while being careful the millisecond field does not 502 // overflow or underflow 503 _ERRCHECK(_get_dstbias(&dstbias)); 504 dstend.ms += (dstbias * 1000); 505 if (dstend.ms < 0) 506 { 507 dstend.ms += milliseconds_per_day; 508 dstend.yd--; 509 } 510 else if (dstend.ms >= milliseconds_per_day) 511 { 512 dstend.ms -= milliseconds_per_day; 513 dstend.yd++; 514 } 515 516 // Set the year field of dstend so that unnecessary calls to cvtdate() 517 // may be avoided: 518 dstend.yr = year; 519 } 520 521 return; 522} 523 524 525 526// Implementation Details: Note that there are two ways that the Daylight 527// Savings Time transition data may be returned by GetTimeZoneInformation. The 528// first is a day-in-month format, which is similar to what is used in the USA. 529// The transition date is given as the n'th occurrence of a specified day of the 530// week in a specified month. The second is as an absolute date. The two cases 531// are distinguished by the value of the wYear field of the SYSTEMTIME structure 532// (zero denotes a day-in-month format). 533static int __cdecl _isindst_nolock(tm* const tb) throw() 534{ 535 int daylight = 0; 536 _ERRCHECK(_get_daylight(&daylight)); 537 if (daylight == 0) 538 return 0; 539 540 // Compute (or recompute) the transition dates for Daylight Savings Time 541 // if necessary. The yr fields of dststart and dstend are compared to the 542 // year of interest to determine necessity. 543 if (tb->tm_year != dststart.yr || tb->tm_year != dstend.yr) 544 { 545 if (tz_api_used) 546 { 547 // Convert the start of daylight savings time to dststart: 548 if (tz_info.DaylightDate.wYear == 0) 549 { 550 cvtdate( 551 transition_type::start_of_dst, 552 date_type::day_in_month, 553 tb->tm_year, 554 tz_info.DaylightDate.wMonth, 555 tz_info.DaylightDate.wDay, 556 tz_info.DaylightDate.wDayOfWeek, 557 0, 558 tz_info.DaylightDate.wHour, 559 tz_info.DaylightDate.wMinute, 560 tz_info.DaylightDate.wSecond, 561 tz_info.DaylightDate.wMilliseconds); 562 } 563 else 564 { 565 cvtdate( 566 transition_type::start_of_dst, 567 date_type::absolute_date, 568 tb->tm_year, 569 tz_info.DaylightDate.wMonth, 570 0, 571 0, 572 tz_info.DaylightDate.wDay, 573 tz_info.DaylightDate.wHour, 574 tz_info.DaylightDate.wMinute, 575 tz_info.DaylightDate.wSecond, 576 tz_info.DaylightDate.wMilliseconds); 577 } 578 579 // Convert start of standard time to dstend: 580 if (tz_info.StandardDate.wYear == 0) 581 { 582 cvtdate( 583 transition_type::end_of_dst, 584 date_type::day_in_month, 585 tb->tm_year, 586 tz_info.StandardDate.wMonth, 587 tz_info.StandardDate.wDay, 588 tz_info.StandardDate.wDayOfWeek, 589 0, 590 tz_info.StandardDate.wHour, 591 tz_info.StandardDate.wMinute, 592 tz_info.StandardDate.wSecond, 593 tz_info.StandardDate.wMilliseconds); 594 } 595 else 596 { 597 cvtdate( 598 transition_type::end_of_dst, 599 date_type::absolute_date, 600 tb->tm_year, 601 tz_info.StandardDate.wMonth, 602 0, 603 0, 604 tz_info.StandardDate.wDay, 605 tz_info.StandardDate.wHour, 606 tz_info.StandardDate.wMinute, 607 tz_info.StandardDate.wSecond, 608 tz_info.StandardDate.wMilliseconds); 609 } 610 } 611 else 612 { 613 // The GetTimeZoneInformation API was not used, or failed. We use 614 // the USA Daylight Savings Time rules as a fallback. 615 int startmonth = 3; // March 616 int startweek = 2; // Second week 617 int endmonth = 11;// November 618 int endweek = 1; // First week 619 620 // The rules changed in 2007: 621 if (107 > tb->tm_year) 622 { 623 startmonth = 4; // April 624 startweek = 1; // first week 625 endmonth = 10;// October 626 endweek = 5; // last week 627 } 628 629 cvtdate( 630 transition_type::start_of_dst, 631 date_type::day_in_month, 632 tb->tm_year, 633 startmonth, 634 startweek, 635 0, // Sunday 636 0, 637 2, // 02:00 (2 AM) 638 0, 639 0, 640 0); 641 642 cvtdate( 643 transition_type::end_of_dst, 644 date_type::day_in_month, 645 tb->tm_year, 646 endmonth, 647 endweek, 648 0, // Sunday 649 0, 650 2, // 02:00 (2 AM) 651 0, 652 0, 653 0); 654 } 655 } 656 657 // Handle simple cases first: 658 if (dststart.yd < dstend.yd) 659 { 660 // Northern hemisphere ordering: 661 if (tb->tm_yday < dststart.yd || tb->tm_yday > dstend.yd) 662 return 0; 663 664 if (tb->tm_yday > dststart.yd && tb->tm_yday < dstend.yd) 665 return 1; 666 } 667 else 668 { 669 // Southern hemisphere ordering: 670 if (tb->tm_yday < dstend.yd || tb->tm_yday > dststart.yd) 671 return 1; 672 673 if (tb->tm_yday > dstend.yd && tb->tm_yday < dststart.yd) 674 return 0; 675 } 676 677 long const ms = 1000 * (tb->tm_sec + 60 * tb->tm_min + 3600 * tb->tm_hour); 678 679 if (tb->tm_yday == dststart.yd) 680 { 681 682 return ms >= dststart.ms ? 1 : 0; 683 } 684 else 685 { 686 return ms < dstend.ms ? 1 : 0; 687 } 688} 689 690 691 692// Tests if the time represented by the tm structure falls in Daylight Savings 693// Time or not. The Daylight Savings Time rules are obtained from the operating 694// system if GetTimeZoneInformation was used by _tzset() to obtain the time zone 695// information; otherwise, the USA Daylight Savings Time rules (post-1986) are 696// used. 697// 698// Returns 1 if the time is in Daylight Savings Time; returns 0 otherwise. 699extern "C" int __cdecl _isindst(tm* const tb) 700{ 701 int retval = 0; 702 703 __acrt_lock(__acrt_time_lock); 704 __try 705 { 706 retval = _isindst_nolock(tb); 707 } 708 __finally 709 { 710 __acrt_unlock(__acrt_time_lock); 711 } 712 __endtry 713 714 return retval; 715}