reactos/sdk/lib/ucrt/time/wcsftime.cpp

1207 lines
39 KiB
C++

//
// wcsftime.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The wcsftime family of functions, which format time data into a wide string,
// and related functionality.
//
#include <corecrt_internal_time.h>
#include <stdlib.h>
#include <locale.h>
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Day and Month Name and Time Locale Information Fetching Functions
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
extern "C" wchar_t* __cdecl _W_Getdays()
{
_LocaleUpdate locale_update(nullptr);
__crt_lc_time_data const* const time_data = locale_update.GetLocaleT()->locinfo->lc_time_curr;
size_t length = 0;
for (size_t n = 0; n < 7; ++n)
{
length += wcslen(time_data->_W_wday_abbr[n]) + wcslen(time_data->_W_wday[n]) + 2;
}
__crt_unique_heap_ptr<wchar_t> buffer(_malloc_crt_t(wchar_t, length + 1));
if (buffer.get() == nullptr)
return nullptr;
wchar_t* it = buffer.get();
for (size_t n = 0; n < 7; ++n)
{
*it++ = L':';
_ERRCHECK(wcscpy_s(it, (length + 1) - (it - buffer.get()), time_data->_W_wday_abbr[n]));
it += wcslen(it);
*it++ = L':';
_ERRCHECK(wcscpy_s(it, (length + 1) - (it - buffer.get()), time_data->_W_wday[n]));
it += wcslen(it);
}
*it++ = L'\0';
return buffer.detach();
}
extern "C" wchar_t* __cdecl _W_Getmonths()
{
_LocaleUpdate locale_update(nullptr);
__crt_lc_time_data const* const time_data = locale_update.GetLocaleT()->locinfo->lc_time_curr;
size_t length = 0;
for (size_t n = 0; n < 12; ++n)
{
length += wcslen(time_data->_W_month_abbr[n]) + wcslen(time_data->_W_month[n]) + 2;
}
__crt_unique_heap_ptr<wchar_t> buffer(_malloc_crt_t(wchar_t, length + 1));
if (buffer.get() == nullptr)
return nullptr;
wchar_t* it = buffer.get();
for (size_t n = 0; n < 12; ++n)
{
*it++ = L':';
_ERRCHECK(wcscpy_s(it, (length + 1) - (it - buffer.get()), time_data->_W_month_abbr[n]));
it += wcslen(it);
*it++ = L':';
_ERRCHECK(wcscpy_s(it, (length + 1) - (it - buffer.get()), time_data->_W_month[n]));
it += wcslen(it);
}
*it++ = L'\0';
return buffer.detach();
}
extern "C" void* __cdecl _W_Gettnames()
{
_LocaleUpdate locale_update(nullptr);
__crt_lc_time_data const* const src = locale_update.GetLocaleT()->locinfo->lc_time_curr;
#define PROCESS_STRING(STR, CHAR, CPY, LEN) \
while (bytes % sizeof(CHAR) != 0) \
{ \
++bytes; \
} \
if (phase == 1) \
{ \
dest->STR = ((CHAR *) dest) + bytes / sizeof(CHAR); \
_ERRCHECK(CPY(dest->STR, (total_bytes - bytes) / sizeof(CHAR), src->STR)); \
} \
bytes += (LEN(src->STR) + 1) * sizeof(CHAR);
#define PROCESS_NARROW_STRING(STR) \
PROCESS_STRING(STR, char, strcpy_s, strlen)
#define PROCESS_WIDE_STRING(STR) \
PROCESS_STRING(STR, wchar_t, wcscpy_s, wcslen)
#define PROCESS_NARROW_ARRAY(ARR) \
for (size_t idx = 0; idx < _countof(src->ARR); ++idx) \
{ \
PROCESS_NARROW_STRING(ARR[idx]) \
}
#define PROCESS_WIDE_ARRAY(ARR) \
for (size_t idx = 0; idx < _countof(src->ARR); ++idx) \
{ \
PROCESS_WIDE_STRING(ARR[idx]) \
}
size_t total_bytes = 0;
size_t bytes = sizeof(__crt_lc_time_data);
__crt_lc_time_data* dest = nullptr;
for (int phase = 0; phase < 2; ++phase)
{
if (phase == 1)
{
dest = static_cast<__crt_lc_time_data*>(_malloc_crt(bytes));
if (!dest) {
return nullptr;
}
memset(dest, 0, bytes);
total_bytes = bytes;
bytes = sizeof(__crt_lc_time_data);
}
PROCESS_NARROW_ARRAY(wday_abbr)
PROCESS_NARROW_ARRAY(wday)
PROCESS_NARROW_ARRAY(month_abbr)
PROCESS_NARROW_ARRAY(month)
PROCESS_NARROW_ARRAY(ampm)
PROCESS_NARROW_STRING(ww_sdatefmt)
PROCESS_NARROW_STRING(ww_ldatefmt)
PROCESS_NARROW_STRING(ww_timefmt)
if (phase == 1)
{
dest->ww_caltype = src->ww_caltype;
dest->refcount = 0;
}
PROCESS_WIDE_ARRAY(_W_wday_abbr)
PROCESS_WIDE_ARRAY(_W_wday)
PROCESS_WIDE_ARRAY(_W_month_abbr)
PROCESS_WIDE_ARRAY(_W_month)
PROCESS_WIDE_ARRAY(_W_ampm)
PROCESS_WIDE_STRING(_W_ww_sdatefmt)
PROCESS_WIDE_STRING(_W_ww_ldatefmt)
PROCESS_WIDE_STRING(_W_ww_timefmt)
PROCESS_WIDE_STRING(_W_ww_locale_name)
}
return dest;
}
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Local Functions Used In Time String Formatting
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// Values for __crt_lc_time_data ww_* fields for store_winword:
#define WW_SDATEFMT 0
#define WW_LDATEFMT 1
#define WW_TIMEFMT 2
// Note: annotation does not account for fact < *count bytes may be written if null terminator hit
#define _CrtWcstime_Writes_and_advances_ptr_(count) \
_Outptr_result_buffer_(count)
// Copies the supplied time string 'in' into the output buffer 'out' until either
// (a) the end of the time string is reached or (b) '*count' becomes zero (and we
// run out of buffer space). The '*out' pointer is updated to point to the next
// character in the buffer (one-past-the-end of the insertion), and the '*count'
// value is updated to reflect the number of characters written.
static void __cdecl store_string(
_In_reads_or_z_(*count) wchar_t const* in,
_CrtWcstime_Writes_and_advances_ptr_(*count) wchar_t** const out,
_Inout_ size_t* const count
) throw()
{
while (*count != 0 && *in != L'\0')
{
*(*out)++ = *in++;
--*count;
}
}
// Converts a positive integer ('value') into a string and stores it in the
// 'output' buffer. It stops writing when either (a) the full value has been
// printed, or (b) '*count' becomes zero (and we run out of buffer space). The
// '*out' pointer is updated to point to the next character in the buffer (one-
// past-the-end of the insertion), and the '*count' value is updated to reflect
// the number of characters written.
static void __cdecl store_number_without_lead_zeroes(
int value,
_CrtWcstime_Writes_and_advances_ptr_(*count) wchar_t** const out,
_Inout_ size_t* const count
) throw()
{
// Put the digits in the buffer in reverse order:
wchar_t* out_it = *out;
if (*count > 1)
{
do
{
*out_it++ = static_cast<wchar_t>(value % 10 + L'0');
value /= 10;
--*count;
}
while (value > 0 && *count > 1);
}
else
{
// Indicate buffer too small.
*out -= *count;
*count = 0;
return;
}
wchar_t* left = *out;
wchar_t* right = out_it - 1;
// Update the output iterator to point to the next space:
*out = out_it;
// Reverse the buffer:
while (left < right)
{
wchar_t const x = *right;
*right-- = *left;
*left++ = x;
}
}
// Converts a positive integer ('value') into a string and stores it in the
// 'output' buffer. Both '*out' and '*count' are updated to reflect the
// write.
static void __cdecl store_number(
int value,
int digits,
_CrtWcstime_Writes_and_advances_ptr_(*count) wchar_t** const out,
_Inout_ size_t* const count,
wchar_t const pad_character
) throw()
{
if (pad_character == '\0')
{
store_number_without_lead_zeroes(value, out, count);
return;
}
if (static_cast<size_t>(digits) < *count)
{
int temp = 0;
for (digits--; digits + 1 != 0; --digits)
{
if (value != 0)
{
(*out)[digits] = static_cast<wchar_t>(L'0' + value % 10);
}
else
{
(*out)[digits] = pad_character;
}
value /= 10;
temp++;
}
*out += temp;
*count -= temp;
}
else
{
*count = 0;
}
}
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// Local Functions Used for ISO Week-Based Year Computations
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
enum
{
sunday = 0,
monday = 1,
tuesday = 2,
wednesday = 3,
thursday = 4,
friday = 5,
saturday = 6
};
static int compute_week_of_year(int const wstart, int const wday, int const yday) throw()
{
int const adjusted_wday{(wday + 7 - wstart) % 7};
return (yday + 7 - adjusted_wday) / 7;
}
static int __cdecl compute_iso_week_internal(int year, int wday, int yday) throw()
{
int const week_number{compute_week_of_year(monday, wday, yday)};
bool const is_leap_year{__crt_time_is_leap_year(year)};
int const yunleap{yday - is_leap_year};
int const jan1{(371 - yday + wday) % 7};
int const dec32{(jan1 + is_leap_year + 365) % 7};
if ((364 <= yunleap && dec32 == tuesday ) ||
(363 <= yunleap && dec32 == wednesday) ||
(362 <= yunleap && dec32 == thursday ))
{
return -1; // Push into the next year
}
else if (jan1 == tuesday || jan1 == wednesday || jan1 == thursday)
{
return week_number + 1;
}
return week_number;
}
static int __cdecl compute_iso_week(int const year, int const wday, int const yday) throw()
{
int const week_number{compute_iso_week_internal(year, wday, yday)};
if (week_number == 0)
return compute_iso_week_internal(year - 1, wday + 7 - yday, __crt_time_is_leap_year(year - 1) ? 366 : 365);
if (0 < week_number)
return week_number;
return 1;
}
static int __cdecl compute_iso_year(int const year, int const wday, int const yday) throw()
{
int const week_number{compute_iso_week_internal(year, wday, yday)};
if (week_number == 0)
return year - 1;
if (0 < week_number)
return year;
return year + 1;
}
// store_winword and expand_time are mutually recursive
_Success_(return)
static bool __cdecl expand_time(
_locale_t locale,
wchar_t specifier,
tm const* tmptr,
_CrtWcstime_Writes_and_advances_ptr_(*count) wchar_t** out,
_Inout_ size_t* count,
__crt_lc_time_data const* lc_time,
bool alternate_form
) throw();
// Formats the date and time in the supplied WinWord format and stores the
// formatted result in the supplied buffer. For simple localized Gregorian
// calendars (calendar type 1), the WinWord format is converted token-by-token
// to wcsftime conversion specifiers. expand_time is then called to do the
// heavy lifting. For other calendar types, the Windows APIs GetDateFormatEx
// and GetTimeFormatEx are instead used to do all the formatting, so this
// function does not need to know about era and period strings, year offsets,
// etc. Returns true on success; false on failure.
_Success_(return)
static bool __cdecl store_winword(
_locale_t const locale,
int const field_code,
tm const* const tmptr,
_CrtWcstime_Writes_and_advances_ptr_(*count) wchar_t** const out,
_Inout_ size_t* const count,
__crt_lc_time_data const* const lc_time
) throw()
{
wchar_t const* format;
switch (field_code)
{
case WW_SDATEFMT:
format = lc_time->_W_ww_sdatefmt;
break;
case WW_LDATEFMT:
format = lc_time->_W_ww_ldatefmt;
break;
case WW_TIMEFMT:
default:
format = lc_time->_W_ww_timefmt;
break;
}
if (lc_time->ww_caltype != 1)
{
// We have something other than the basic Gregorian calendar
bool const is_time_format = field_code == WW_TIMEFMT;
// We leave the verification of the SYSTEMTIME up to the Windows API
// that we call; if one of those functions returns zero to indicate
// failure, we fall through and call expand_time() again.
SYSTEMTIME system_time;
system_time.wYear = static_cast<WORD>(tmptr->tm_year + 1900);
system_time.wMonth = static_cast<WORD>(tmptr->tm_mon + 1);
system_time.wDay = static_cast<WORD>(tmptr->tm_mday);
system_time.wHour = static_cast<WORD>(tmptr->tm_hour);
system_time.wMinute = static_cast<WORD>(tmptr->tm_min);
system_time.wSecond = static_cast<WORD>(tmptr->tm_sec);
system_time.wMilliseconds = 0;
// Find buffer size required:
int cch;
if (is_time_format)
cch = __acrt_GetTimeFormatEx(lc_time->_W_ww_locale_name, 0, &system_time, format, nullptr, 0);
else
cch = __acrt_GetDateFormatEx(lc_time->_W_ww_locale_name, 0, &system_time, format, nullptr, 0, nullptr);
if (cch != 0)
{
__crt_scoped_stack_ptr<wchar_t> const buffer(_malloca_crt_t(wchar_t, cch));
if (buffer.get() != nullptr)
{
// Do actual date/time formatting:
if (is_time_format)
cch = __acrt_GetTimeFormatEx(lc_time->_W_ww_locale_name, 0, &system_time, format, buffer.get(), cch);
else
cch = __acrt_GetDateFormatEx(lc_time->_W_ww_locale_name, 0, &system_time, format, buffer.get(), cch, nullptr);
// Copy to output buffer:
wchar_t const* buffer_it = buffer.get();
while (--cch > 0 && *count > 0)
{
*(*out)++ = *buffer_it++;
(*count)--;
}
return true;
}
}
// If an error occurs, just fall through to localized Gregorian...
}
while (*format && *count != 0)
{
wchar_t specifier = 0;
bool no_lead_zeros = false;
// Count the number of repetitions of this character
int repeat = 0;
wchar_t const* p = format;
for (; *p++ == *format; ++repeat);
// Leave p pointing to the beginning of the next token
p--;
// Switch on ASCII format character and determine specifier:
switch (*format)
{
case L'M':
{
switch (repeat)
{
case 1: no_lead_zeros = true; // fall through
case 2: specifier = L'm'; break;
case 3: specifier = L'b'; break;
case 4: specifier = L'B'; break;
}
break;
}
case L'd':
{
switch (repeat)
{
case 1: no_lead_zeros = true; // fall through
case 2: specifier = L'd'; break;
case 3: specifier = L'a'; break;
case 4: specifier = L'A'; break;
}
break;
}
case L'y':
{
switch (repeat)
{
case 2: specifier = L'y'; break;
case 4: specifier = L'Y'; break;
}
break;
}
case L'h':
{
switch (repeat)
{
case 1: no_lead_zeros = true; // fall through
case 2: specifier = L'I'; break;
}
break;
}
case L'H':
{
switch (repeat)
{
case 1: no_lead_zeros = true; // fall through
case 2: specifier = L'H'; break;
}
break;
}
case L'm':
{
switch (repeat)
{
case 1: no_lead_zeros = true; // fall through
case 2: specifier = L'M'; break;
}
break;
}
case L's': // for compatibility; not strictly WinWord
{
switch (repeat)
{
case 1: no_lead_zeros = true; // fall through
case 2: specifier = L'S'; break;
}
break;
}
case L'A':
case L'a':
{
if (!_wcsicmp(format, L"am/pm"))
{
p = format + 5;
}
else if (!_wcsicmp(format, L"a/p"))
{
p = format + 3;
}
specifier = L'p';
break;
}
case L't': // t or tt time marker suffix
{
wchar_t* ampmstr = tmptr->tm_hour <= 11
? lc_time->_W_ampm[0]
: lc_time->_W_ampm[1];
if (repeat == 1 && *count > 0)
{
*(*out)++ = *ampmstr++;
(*count)--;
}
else
{
while (*ampmstr != 0 && *count > 0)
{
*(*out)++ = *ampmstr++;
--*count;
}
}
format = p;
continue;
}
case L'\'': // literal string
{
if (repeat % 2 == 0) // even number
{
format += repeat;
}
else // odd number
{
format += repeat;
while (*format && *count != 0)
{
if (*format == L'\'')
{
format++;
break;
}
*(*out)++ = *format++;
--*count;
}
}
continue;
}
default: // non-control char, print it
{
break;
}
}
// expand specifier, or copy literal if specifier not found
if (specifier)
{
_VALIDATE_RETURN_NOEXC(expand_time(locale, specifier, tmptr, out, count, lc_time, no_lead_zeros), EINVAL, false);
format = p; // bump format up to the next token
}
else
{
*(*out)++ = *format++;
--*count;
}
}
return true;
}
// Expands the conversion specifier using the time struct and stores the result
// into the supplied buffer. The expansion is locale-dependent. Returns true
// on success; false on failure.
static bool __cdecl expand_time(
_locale_t const locale,
wchar_t const specifier,
tm const* const timeptr,
wchar_t** const string,
size_t* const left,
__crt_lc_time_data const* const lc_time,
bool const alternate_form
) throw()
{
switch (specifier)
{
case L'a': // abbreviated weekday name
{
_VALIDATE_RETURN(timeptr->tm_wday >= 0 && timeptr->tm_wday <= 6, EINVAL, false);
store_string(lc_time->_W_wday_abbr[timeptr->tm_wday], string, left);
return true;
}
case L'A': // full weekday name
{
_VALIDATE_RETURN(timeptr->tm_wday >= 0 && timeptr->tm_wday <= 6, EINVAL, false);
store_string(lc_time->_W_wday[timeptr->tm_wday], string, left);
return true;
}
case L'b': // abbreviated month name
{
_VALIDATE_RETURN(timeptr->tm_mon >= 0 && timeptr->tm_mon <= 11, EINVAL, false);
store_string(lc_time->_W_month_abbr[timeptr->tm_mon], string, left);
return true;
}
case L'B': // full month name
{
_VALIDATE_RETURN(timeptr->tm_mon >= 0 && timeptr->tm_mon <= 11, EINVAL, false);
store_string(lc_time->_W_month[timeptr->tm_mon], string, left);
return true;
}
case L'c': // appropriate date and time representation
{
// In the C locale, %c is equivalent to "%a %b %e %T %Y". This format
// is not achievable using the Windows API date and time format APIs
// (it's hard to interleave date and time together, and there's no way
// to format %e). Therefore, we special case this specifier for the C
// locale.
if (lc_time == &__lc_time_c && !alternate_form)
{
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'a', timeptr, string, left, lc_time, false), EINVAL, false);
store_string(L" ", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'b', timeptr, string, left, lc_time, false), EINVAL, false);
store_string(L" ", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'e', timeptr, string, left, lc_time, false), EINVAL, false);
store_string(L" ", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'T', timeptr, string, left, lc_time, false), EINVAL, false);
store_string(L" ", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'Y', timeptr, string, left, lc_time, false), EINVAL, false);
}
// Otherwise, if we're not in the C locale, use the locale-provided
// format strings:
else
{
int const field_code = alternate_form ? WW_LDATEFMT : WW_SDATEFMT;
_VALIDATE_RETURN_NOEXC(store_winword(locale, field_code, timeptr, string, left, lc_time), EINVAL, false);
store_string(L" ", string, left);
_VALIDATE_RETURN_NOEXC(store_winword(locale, WW_TIMEFMT, timeptr, string, left, lc_time), EINVAL, false);
}
return true;
}
case L'C': // century in decimal (00-99)
{
_VALIDATE_RETURN(timeptr->tm_year >= -1900 && timeptr->tm_year <= 8099, EINVAL, false);
store_number(__crt_get_century(timeptr->tm_year), 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'd': // day of the month in decimal (01-31)
{
_VALIDATE_RETURN(timeptr->tm_mday >= 1 && timeptr->tm_mday <= 31, EINVAL, false);
store_number(timeptr->tm_mday, 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'D': // equivalent to "%m/%d/%y"
{
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'm', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L"/", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'd', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L"/", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'y', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
return true;
}
case L'e': // day of month as a decimal number (1-31); space padded:
{
_VALIDATE_RETURN(timeptr->tm_mday >= 1 && timeptr->tm_mday <= 31, EINVAL, false);
store_number(timeptr->tm_mday, 2, string, left, alternate_form ? '\0' : ' ');
return true;
}
case L'F': // equivalent to "%Y-%m-%d" (ISO 8601):
{
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'Y', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L"-", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'm', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L"-", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, L'd', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
return true;
}
case L'g': // last two digits of the week-based year:
{
_VALIDATE_RETURN(timeptr->tm_year >= -1900 && timeptr->tm_year <= 8099, EINVAL, false);
int const iso_year{compute_iso_year(timeptr->tm_year, timeptr->tm_wday, timeptr->tm_yday) + 1900};
store_number(iso_year % 100, 2, string, left, '0');
return true;
}
case L'G': // week-based year:
{
_VALIDATE_RETURN(timeptr->tm_year >= -1900 && timeptr->tm_year <= 8099, EINVAL, false);
int const iso_year{compute_iso_year(timeptr->tm_year, timeptr->tm_wday, timeptr->tm_yday) + 1900};
store_number(iso_year, 4, string, left, '0');
return true;
}
case L'h': // equivalent to "%b":
{
return expand_time(locale, L'b', timeptr, string, left, lc_time, alternate_form);
}
case L'H': // 24-hour decimal (00-23)
{
_VALIDATE_RETURN(timeptr->tm_hour >= 0 && timeptr->tm_hour <= 23, EINVAL, false);
store_number(timeptr->tm_hour, 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'I': // 12-hour decimal (01-12)
{
_VALIDATE_RETURN(timeptr->tm_hour >= 0 && timeptr->tm_hour <= 23, EINVAL, false);
unsigned hour = timeptr->tm_hour % 12;
if (hour == 0)
hour = 12;
store_number(hour, 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'j': // yday in decimal (001-366)
{
_VALIDATE_RETURN(timeptr->tm_yday >= 0 && timeptr->tm_yday <= 365, EINVAL, false);
store_number(timeptr->tm_yday + 1, 3, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'm': // month in decimal (01-12)
{
_VALIDATE_RETURN(timeptr->tm_mon >= 0 && timeptr->tm_mon <= 11, EINVAL, false);
store_number(timeptr->tm_mon + 1, 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'M': // minute in decimal (00-59)
{
_VALIDATE_RETURN(timeptr->tm_min >= 0 && timeptr->tm_min <= 59, EINVAL, false);
store_number(timeptr->tm_min, 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'n': // newline character
{
store_string(L"\n", string, left);
return true;
}
case L'p': // AM/PM designation
{
_VALIDATE_RETURN(timeptr->tm_hour >= 0 && timeptr->tm_hour <= 23, EINVAL, false);
wchar_t const* const ampm_string = timeptr->tm_hour <= 11
? lc_time->_W_ampm[0]
: lc_time->_W_ampm[1];
store_string(ampm_string, string, left);
return true;
}
case L'r': // Locale-specific 12-hour clock time
{
// In the C locale, %r is equivalent to "%I:%M:%S %p". This is the only
// locale in which we guarantee that %r is a 12-hour time; in all other
// locales we only have one time format which may or may not be a 12-hour
// format.
if (lc_time == &__lc_time_c)
{
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'I', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L":", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'M', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L":", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'S', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L" ", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'p', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
}
else
{
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'X', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
}
return true;
}
case L'R': // Equivalent to "%H:%M"
{
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'H', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L":", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'M', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
return true;
}
case L'S': // seconds in decimal (00-60) allowing for a leap second
{
_VALIDATE_RETURN(timeptr->tm_sec >= 0 && timeptr->tm_sec <= 60, EINVAL, false);
store_number(timeptr->tm_sec, 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L't': // tab character
{
store_string(L"\t", string, left);
return true;
}
case L'T': // Equivalent to "%H:%M:%S" (ISO 8601)
{
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'H', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L":", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'M', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
store_string(L":", string, left);
_VALIDATE_RETURN_NOEXC(expand_time(locale, 'S', timeptr, string, left, lc_time, alternate_form), EINVAL, false);
return true;
}
case L'u': // week day in decimal (1-7)
case L'w': // week day in decimal (0-6)
{
_VALIDATE_RETURN(timeptr->tm_wday >= 0 && timeptr->tm_wday <= 6, EINVAL, false);
int const weekday_number = timeptr->tm_wday == 0 && specifier == L'u'
? 7
: timeptr->tm_wday;
store_number(weekday_number, 1, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'U': // sunday week number (00-53)
case L'W': // monday week number (00-53)
{
_VALIDATE_RETURN(timeptr->tm_wday >= 0 && timeptr->tm_wday <= 6, EINVAL, false);
int wdaytemp = timeptr->tm_wday;
if (specifier == L'W')
{
if (timeptr->tm_wday == 0) // Monday-based
wdaytemp = 6;
else
wdaytemp = timeptr->tm_wday - 1;
}
_VALIDATE_RETURN(timeptr->tm_yday >= 0 && timeptr->tm_yday <= 365, EINVAL, false);
unsigned week_number = 0;
if (timeptr->tm_yday >= wdaytemp)
{
week_number = timeptr->tm_yday / 7;
if (timeptr->tm_yday % 7 >= wdaytemp)
++week_number;
}
store_number(week_number, 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'V': // ISO 8601 week number (01-53):
{
int const iso_week{compute_iso_week(timeptr->tm_year, timeptr->tm_wday, timeptr->tm_yday)};
store_number(iso_week, 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'x': // date display
{
int const field_code = alternate_form ? WW_LDATEFMT : WW_SDATEFMT;
_VALIDATE_RETURN_NOEXC(store_winword(locale, field_code, timeptr, string, left, lc_time), EINVAL, false);
return true;
}
case L'X': // time display
{
_VALIDATE_RETURN_NOEXC(store_winword(locale, WW_TIMEFMT, timeptr, string, left, lc_time), EINVAL, false);
return true;
}
case L'y': // year without century (00-99)
{
_VALIDATE_RETURN(timeptr->tm_year >= -1900 && timeptr->tm_year <= 8099, EINVAL, false);
unsigned const two_digit_year = __crt_get_2digit_year(timeptr->tm_year);
store_number(two_digit_year, 2, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'Y': // year with century
{
_VALIDATE_RETURN(timeptr->tm_year >= -1900 && timeptr->tm_year <= 8099, EINVAL, false);
unsigned const full_year = timeptr->tm_year + 1900;
store_number(full_year, 4, string, left, alternate_form ? '\0' : '0');
return true;
}
case L'z': // time zone in ISO 8601 form ("-0430" = 4 hours 30 minutes)
{
__tzset();
// Get the current time zone offset from UTC and, if we are currently in
// daylight savings time, adjust appropriately:
long offset{};
_VALIDATE_RETURN(_get_timezone(&offset) == 0, EINVAL, false);
if (timeptr->tm_isdst)
{
long dst_bias{};
_VALIDATE_RETURN(_get_dstbias(&dst_bias) == 0, EINVAL, false);
offset += dst_bias;
}
long const positive_offset{offset < 0 ? -offset : offset};
long const hours_offset {(positive_offset / 60) / 60};
long const minutes_offset{(positive_offset / 60) % 60};
// This looks wrong, but it is correct: The offset is the difference
// between UTC and the local time zone, so it is a positive value if
// the local time zone is behind UTC.
wchar_t const* const sign_string{offset <= 0 ? L"+" : L"-"};
store_string(sign_string, string, left);
store_number(hours_offset, 2, string, left, '0');
store_number(minutes_offset, 2, string, left, '0');
return true;
}
case L'Z': // time zone name, if any
{
__tzset();
store_string(__wide_tzname()[timeptr->tm_isdst ? 1 : 0], string, left);
return true;
}
case L'%': // percent sign
{
store_string(L"%", string, left);
return true;
}
default: // unknown format directive
{
// We do not raise the invalid parameter handler here. Our caller will
// raise the invalid parameter handler when we return failure.
return false;
}
}
// Unreachable. All switch case statements return.
}
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//
// The _wcsftime family of functions
//
//-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// These functions format a time as a string using a given locale. They place
// characters into the user's output buffer, expanding time format directives as
// described in the provided control string. The lc_time_arg and locale are
// used for locale data.
//
// If the total number of characters that need to be written (including the null
// terminator) is less than the max_size, then the number of characters written
// (not including the null terminator) is returned. Otherwise, zero is returned.
extern "C" size_t __cdecl _Wcsftime_l(
wchar_t* const string,
size_t const max_size,
wchar_t const* const format,
tm const* const timeptr,
void* const lc_time_arg,
_locale_t const locale
)
{
_VALIDATE_RETURN(string != nullptr, EINVAL, 0)
_VALIDATE_RETURN(max_size != 0, EINVAL, 0)
*string = L'\0';
_VALIDATE_RETURN(format != nullptr, EINVAL, 0)
_LocaleUpdate locale_update(locale);
__crt_lc_time_data const* const lc_time = lc_time_arg == 0
? locale_update.GetLocaleT()->locinfo->lc_time_curr
: static_cast<__crt_lc_time_data*>(lc_time_arg);
// Copy the input string to the output string expanding the format
// designations appropriately. Stop copying when one of the following
// is true: (1) we hit a null char in the input stream, or (2) there's
// no room left in the output stream.
wchar_t* string_it = string;
wchar_t const* format_it = format;
bool failed = false;
size_t remaining = max_size;
while (remaining > 0)
{
switch (*format_it)
{
case L'\0':
{
// End of format input string
goto done;
}
case L'%':
{
// Format directive. Take appropriate action based on format control character.
_VALIDATE_RETURN(timeptr != nullptr, EINVAL, 0);
++format_it; // Skip '%'
// Process flags:
bool alternate_form = false;
if (*format_it == L'#')
{
alternate_form = true;
++format_it;
}
// Skip ISO E and O alternative representation format modifiers. We
// do not support alternative formats in any locale.
if (*format_it == L'E' || *format_it == L'O')
{
++format_it;
}
if (!expand_time(locale_update.GetLocaleT(), *format_it, timeptr, &string_it, &remaining, lc_time, alternate_form))
{
// if we don't have any space left, do not set the failure flag:
// we will simply return ERANGE and do not call the invalid
// parameter handler (see below)
if (remaining > 0)
failed = true;
goto done;
}
++format_it; // Skip format char
break;
}
default:
{
// store character, bump pointers, decrement the char count:
*string_it++ = *format_it++;
--remaining;
break;
}
}
}
// All done. See if we terminated because we hit a null char or because
// we ran out of space:
done:
if (!failed && remaining > 0)
{
// Store a terminating null char and return the number of chars we
// stored in the output string:
*string_it = L'\0';
return max_size - remaining;
}
else
{
// Error: return an empty string:
*string = L'\0';
// Now return our error/insufficient buffer indication:
if (!failed && remaining <= 0)
{
// Do not report this as an error to allow the caller to resize:
errno = ERANGE;
}
else
{
_VALIDATE_RETURN(false, EINVAL, 0);
}
return 0;
}
}
extern "C" size_t __cdecl _Wcsftime(
wchar_t* const buffer,
size_t const max_size,
wchar_t const* const format,
tm const* const timeptr,
void* const lc_time_arg
)
{
return _Wcsftime_l(buffer, max_size, format, timeptr, lc_time_arg, nullptr);
}
extern "C" size_t __cdecl _wcsftime_l(
wchar_t* const buffer,
size_t const max_size,
wchar_t const* const format,
tm const* const timeptr,
_locale_t const locale
)
{
return _Wcsftime_l(buffer, max_size, format, timeptr, nullptr, locale);
}
extern "C" size_t __cdecl wcsftime(
wchar_t* const buffer,
size_t const max_size,
wchar_t const* const format,
tm const* const timeptr
)
{
return _Wcsftime_l(buffer, max_size, format, timeptr, nullptr, nullptr);
}
/*
* Copyright (c) 1992-2013 by P.J. Plauger. ALL RIGHTS RESERVED.
* Consult your license regarding permissions and restrictions.
V6.40:0009 */