Add wcsftime(3).

This commit is contained in:
Jonas 'Sortie' Termansen 2014-05-28 19:51:30 +02:00
parent a1e9c15bca
commit 9ca343c5e4
4 changed files with 193 additions and 114 deletions

View File

@ -250,6 +250,7 @@ wchar/wcscmp.o \
wchar/wcscoll.o \
wchar/wcscpy.o \
wchar/wcscspn.o \
wchar/wcsftime.o \
wchar/wcslen.o \
wchar/wcsncat.o \
wchar/wcsncmp.o \

View File

@ -182,7 +182,7 @@ wchar_t* wmemset(wchar_t*, wchar_t, size_t);
float wcstof(const wchar_t* __restrict, wchar_t** __restrict);
long double wcstold(const wchar_t* __restrict, wchar_t** __restrict);
/* TODO: int vwscanf(const wchar_t* __restrict, va_list); */
/* TODO: size_t wcsftime(wchar_t* __restrict, size_t, const wchar_t* __restrict, const struct tm* __restrict); */
size_t wcsftime(wchar_t* __restrict, size_t, const wchar_t* __restrict, const struct tm* __restrict);
long long wcstoll(const wchar_t* __restrict, wchar_t** __restrict, int);
unsigned long long wcstoull(const wchar_t* __restrict, wchar_t** __restrict, int);
#endif

View File

@ -1,6 +1,6 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013.
Copyright(C) Jonas 'Sortie' Termansen 2013, 2014.
This file is part of the Sortix C Library.
@ -22,96 +22,134 @@
*******************************************************************************/
#ifndef STRFTIME
#define STRFTIME strftime
#define STRFTIME_CHAR char
#define STRFTIME_L(x) x
#endif
#include <sys/cdefs.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include <wchar.h>
static const char* GetWeekdayAbbreviated(const struct tm* tm)
static const STRFTIME_CHAR* GetWeekdayAbbreviated(const struct tm* tm)
{
switch ( tm->tm_wday % 7 )
{
case 0: return "Sun";
case 1: return "Mon";
case 2: return "Tue";
case 3: return "Wed";
case 4: return "Thu";
case 5: return "Fri";
case 6: return "Sat";
case 0: return STRFTIME_L("Sun");
case 1: return STRFTIME_L("Mon");
case 2: return STRFTIME_L("Tue");
case 3: return STRFTIME_L("Wed");
case 4: return STRFTIME_L("Thu");
case 5: return STRFTIME_L("Fri");
case 6: return STRFTIME_L("Sat");
default: __builtin_unreachable();
}
}
static const char* GetWeekday(const struct tm* tm)
static const STRFTIME_CHAR* GetWeekday(const struct tm* tm)
{
switch ( tm->tm_wday % 7 )
{
case 0: return "Sunday";
case 1: return "Monday";
case 2: return "Tuesday";
case 3: return "Wednesday";
case 4: return "Thursday";
case 5: return "Friday";
case 6: return "Saturday";
case 0: return STRFTIME_L("Sunday");
case 1: return STRFTIME_L("Monday");
case 2: return STRFTIME_L("Tuesday");
case 3: return STRFTIME_L("Wednesday");
case 4: return STRFTIME_L("Thursday");
case 5: return STRFTIME_L("Friday");
case 6: return STRFTIME_L("Saturday");
default: __builtin_unreachable();
}
}
static const char* GetMonthAbbreviated(const struct tm* tm)
static const STRFTIME_CHAR* GetMonthAbbreviated(const struct tm* tm)
{
switch ( tm->tm_mon % 12 )
{
case 0: return "Jan";
case 1: return "Feb";
case 2: return "Mar";
case 3: return "Apr";
case 4: return "May";
case 5: return "Jun";
case 6: return "Jul";
case 7: return "Aug";
case 8: return "Sep";
case 9: return "Oct";
case 10: return "Nov";
case 11: return "Dec";
case 0: return STRFTIME_L("Jan");
case 1: return STRFTIME_L("Feb");
case 2: return STRFTIME_L("Mar");
case 3: return STRFTIME_L("Apr");
case 4: return STRFTIME_L("May");
case 5: return STRFTIME_L("Jun");
case 6: return STRFTIME_L("Jul");
case 7: return STRFTIME_L("Aug");
case 8: return STRFTIME_L("Sep");
case 9: return STRFTIME_L("Oct");
case 10: return STRFTIME_L("Nov");
case 11: return STRFTIME_L("Dec");
default: __builtin_unreachable();
}
}
static const char* GetMonth(const struct tm* tm)
static const STRFTIME_CHAR* GetMonth(const struct tm* tm)
{
switch ( tm->tm_mon % 12 )
{
case 0: return "January";
case 1: return "February";
case 2: return "March";
case 3: return "April";
case 4: return "May";
case 5: return "June";
case 6: return "July";
case 7: return "August";
case 8: return "Sepember";
case 9: return "October";
case 10: return "November";
case 11: return "December";
case 0: return STRFTIME_L("January");
case 1: return STRFTIME_L("February");
case 2: return STRFTIME_L("March");
case 3: return STRFTIME_L("April");
case 4: return STRFTIME_L("May");
case 5: return STRFTIME_L("June");
case 6: return STRFTIME_L("July");
case 7: return STRFTIME_L("August");
case 8: return STRFTIME_L("Sepember");
case 9: return STRFTIME_L("October");
case 10: return STRFTIME_L("November");
case 11: return STRFTIME_L("December");
default: __builtin_unreachable();
}
}
static
size_t strftime_convert_uintmax(STRFTIME_CHAR* destination, uintmax_t value)
{
size_t result = 1;
uintmax_t copy = value;
while ( 10 <= copy )
copy /= 10, result++;
for ( size_t i = result; i != 0; i-- )
destination[i-1] = STRFTIME_L('0') + value % 10,
value /= 10;
destination[result] = STRFTIME_L('\0');
return result;
}
static
size_t strftime_convert_int(STRFTIME_CHAR* destination, int value)
{
if ( value < 0 )
{
*destination++ = STRFTIME_L('-');
return 1 + strftime_convert_uintmax(destination, - (uintmax_t) (intmax_t) value);
}
return strftime_convert_uintmax(destination, (uintmax_t) value);
}
extern "C"
size_t strftime(char* s, size_t max, const char* format, const struct tm* tm)
size_t STRFTIME(STRFTIME_CHAR* s,
size_t max,
const STRFTIME_CHAR* format,
const struct tm* tm)
{
const char* orig_format = format;
const STRFTIME_CHAR* orig_format = format;
size_t ret = 0;
#define OUTPUT_CHAR(expr) \
do { \
if ( ret == max ) \
return errno = ERANGE, 0; \
s[ret++] = expr; \
s[ret++] = (expr); \
} while ( 0 )
#define OUTPUT_STRING(expr) \
do { \
const char* out_str = expr; \
const STRFTIME_CHAR* out_str = (expr); \
while ( *out_str ) \
OUTPUT_CHAR(*out_str++); \
} while ( 0 )
@ -120,7 +158,7 @@ size_t strftime(char* s, size_t max, const char* format, const struct tm* tm)
do { \
int old_errno = errno; \
errno = 0; \
size_t subret = strftime(s + ret, max - ret, expr, tm); \
size_t subret = STRFTIME(s + ret, max - ret, (expr), tm); \
if ( !subret && errno ) \
return 0; \
errno = old_errno; \
@ -129,46 +167,56 @@ size_t strftime(char* s, size_t max, const char* format, const struct tm* tm)
#define OUTPUT_INT_PADDED(valexpr, widthexpr, padexpr) \
do { \
int val = valexpr; \
size_t width = widthexpr; \
char pad = padexpr; \
char str[sizeof(int) * 3]; str[0] = '\0'; \
size_t got = (size_t) snprintf(str, sizeof(str), "%i", val); \
int val = (valexpr); \
size_t width = (widthexpr); \
STRFTIME_CHAR pad = (padexpr); \
STRFTIME_CHAR str[sizeof(int) * 3]; \
str[0] = STRFTIME_L('\0'); \
size_t got = strftime_convert_int(str, val); \
while ( pad && got < width-- ) \
OUTPUT_CHAR(pad); \
OUTPUT_STRING(str); \
} while ( 0 )
#define OUTPUT_INT(valexpr) OUTPUT_INT_PADDED(valexpr, 0,'\0')
#define OUTPUT_INT(valexpr) OUTPUT_INT_PADDED(valexpr, 0, STRFTIME_L('\0'))
#if defined(STRFTIME_IS_WCHAR)
#define OUTPUT_UNSUPPORTED() \
do { \
fprintf(stderr, "%s:%u: strftime: error: unsupported format string \"%s\" around \"%%%s\"\n", \
__FILE__, __LINE__, orig_format, specifiers_begun_at); \
fprintf(stderr, "%s:%u: %s: error: unsupported format string \"%ls\" around \"%%%ls\"\n", \
__FILE__, __LINE__, __STRINGIFY(STRFTIME), orig_format, specifiers_begun_at); \
return 0; \
} while ( 0 )
#else
#define OUTPUT_UNSUPPORTED() \
do { \
fprintf(stderr, "%s:%u: %s: error: unsupported format string \"%s\" around \"%%%s\"\n", \
__FILE__, __LINE__, __STRINGIFY(STRFTIME), orig_format, specifiers_begun_at); \
return 0; \
} while ( 0 )
#endif
#define OUTPUT_UNDEFINED() OUTPUT_UNSUPPORTED()
while ( char c = *format++ )
while ( STRFTIME_CHAR c = *format++ )
{
if ( c != '%' )
if ( c != STRFTIME_L('%') )
{
OUTPUT_CHAR(c);
continue;
}
const char* specifiers_begun_at = format;
const STRFTIME_CHAR* specifiers_begun_at = format;
c = *format++;
// Process any optional flags.
char padding_char = ' ';
STRFTIME_CHAR padding_char = STRFTIME_L(' ');
bool plus_padding = false;
while ( true )
{
switch ( c )
{
case '0': padding_char = '0'; break;
case '+': padding_char = '0'; plus_padding = true; break;
case STRFTIME_L('0'): padding_char = STRFTIME_L('0'); break;
case STRFTIME_L('+'): padding_char = STRFTIME_L('0'); plus_padding = true; break;
default: goto end_of_flags;
}
c = *format++;
@ -181,17 +229,17 @@ size_t strftime(char* s, size_t max, const char* format, const struct tm* tm)
// Process the optional minimum field width.
size_t field_width = 0;
while ( '0' <= c && c <= '9' )
field_width = field_width * 10 + c - '0',
while ( STRFTIME_L('0') <= c && c <= STRFTIME_L('9') )
field_width = field_width * 10 + c - STRFTIME_L('0'),
c = *format++;
// Process an optional E or O modifier.
bool e_modifier = false;
bool o_modifier = false;
if ( c == 'E' )
if ( c == STRFTIME_L('E') )
e_modifier = true,
c = *format++;
else if ( c == 'O' )
else if ( c == STRFTIME_L('O') )
o_modifier = true,
c = *format++;
@ -201,85 +249,85 @@ size_t strftime(char* s, size_t max, const char* format, const struct tm* tm)
switch ( c )
{
case 'a': OUTPUT_STRING(GetWeekdayAbbreviated(tm)); break;
case 'A': OUTPUT_STRING(GetWeekday(tm)); break;
case 'b': OUTPUT_STRING(GetMonthAbbreviated(tm)); break;
case 'B': OUTPUT_STRING(GetMonth(tm)); break;
case 'c': /*E*/
case STRFTIME_L('a'): OUTPUT_STRING(GetWeekdayAbbreviated(tm)); break;
case STRFTIME_L('A'): OUTPUT_STRING(GetWeekday(tm)); break;
case STRFTIME_L('b'): OUTPUT_STRING(GetMonthAbbreviated(tm)); break;
case STRFTIME_L('B'): OUTPUT_STRING(GetMonth(tm)); break;
case STRFTIME_L('c'): /*E*/
OUTPUT_STRING(GetWeekday(tm));
OUTPUT_STRING(" ");
OUTPUT_STRING(STRFTIME_L(" "));
OUTPUT_STRING(GetMonthAbbreviated(tm));
OUTPUT_STRING(" ");
OUTPUT_INT_PADDED(tm->tm_mday, 2, '0');
OUTPUT_STRING(" ");
OUTPUT_INT_PADDED(tm->tm_hour, 2, '0');
OUTPUT_STRING(":");
OUTPUT_INT_PADDED(tm->tm_min, 2, '0');
OUTPUT_STRING(":");
OUTPUT_INT_PADDED(tm->tm_sec, 2, '0');
OUTPUT_STRING(STRFTIME_L(" "));
OUTPUT_INT_PADDED(tm->tm_mday, 2, STRFTIME_L('0'));
OUTPUT_STRING(STRFTIME_L(" "));
OUTPUT_INT_PADDED(tm->tm_hour, 2, STRFTIME_L('0'));
OUTPUT_STRING(STRFTIME_L(":"));
OUTPUT_INT_PADDED(tm->tm_min, 2, STRFTIME_L('0'));
OUTPUT_STRING(STRFTIME_L(":"));
OUTPUT_INT_PADDED(tm->tm_sec, 2, STRFTIME_L('0'));
break;
case 'C': /*E*/
case STRFTIME_L('C'): /*E*/
if ( !field_width )
field_width = 2;
OUTPUT_INT_PADDED((tm->tm_year + 1900) / 100, field_width, padding_char);
break;
case 'd': OUTPUT_INT_PADDED(tm->tm_mday, 2, '0'); break; /*O*/
case 'D': OUTPUT_STRFTIME("%m/%d/%y"); break;
case 'e': OUTPUT_INT_PADDED(tm->tm_mday, 2, ' '); break; /*O*/
case 'F':
case STRFTIME_L('d'): OUTPUT_INT_PADDED(tm->tm_mday, 2, STRFTIME_L('0')); break; /*O*/
case STRFTIME_L('D'): OUTPUT_STRFTIME(STRFTIME_L("%m/%d/%y")); break;
case STRFTIME_L('e'): OUTPUT_INT_PADDED(tm->tm_mday, 2, STRFTIME_L(' ')); break; /*O*/
case STRFTIME_L('F'):
// TODO: Revisit this.
OUTPUT_UNSUPPORTED();
break;
case 'g':
case STRFTIME_L('g'):
// TODO: These require a bit of intelligence.
OUTPUT_UNSUPPORTED();
break;
case 'G':
case STRFTIME_L('G'):
// TODO: These require a bit of intelligence.
OUTPUT_UNSUPPORTED();
break;
case 'h': OUTPUT_STRFTIME("%b"); break;
case 'H': OUTPUT_INT_PADDED(tm->tm_hour, 2, '0'); break; /*O*/
case 'I': OUTPUT_INT_PADDED(tm->tm_hour % 12 + 1, 2, '0'); break; /*O*/
case 'j': OUTPUT_INT_PADDED(tm->tm_yday + 1, 3, '0'); break;
case 'm': OUTPUT_INT_PADDED(tm->tm_mon + 1, 2, '0'); break; /*O*/
case 'M': OUTPUT_INT_PADDED(tm->tm_min, 2, '0'); break; /*O*/
case 'n': OUTPUT_CHAR('\n'); break;
case 'p': OUTPUT_STRING(tm->tm_hour < 12 ? "AM" : "PM"); break;
case 'r': OUTPUT_STRFTIME("%I:%M:%S %p"); break;
case 'R': OUTPUT_STRFTIME("%H:%M"); break;
case 'S': OUTPUT_INT_PADDED(tm->tm_sec, 2, '0'); break; /*O*/
case 't': OUTPUT_CHAR('\t'); break;
case 'T': OUTPUT_STRFTIME("%H:%M:%S"); break;
case 'u': OUTPUT_INT(tm->tm_yday); break; /*O*/
case 'U': /*O*/
case STRFTIME_L('h'): OUTPUT_STRFTIME(STRFTIME_L("%b")); break;
case STRFTIME_L('H'): OUTPUT_INT_PADDED(tm->tm_hour, 2, STRFTIME_L('0')); break; /*O*/
case STRFTIME_L('I'): OUTPUT_INT_PADDED(tm->tm_hour % 12 + 1, 2, STRFTIME_L('0')); break; /*O*/
case STRFTIME_L('j'): OUTPUT_INT_PADDED(tm->tm_yday + 1, 3, STRFTIME_L('0')); break;
case STRFTIME_L('m'): OUTPUT_INT_PADDED(tm->tm_mon + 1, 2, STRFTIME_L('0')); break; /*O*/
case STRFTIME_L('M'): OUTPUT_INT_PADDED(tm->tm_min, 2, STRFTIME_L('0')); break; /*O*/
case STRFTIME_L('n'): OUTPUT_CHAR(STRFTIME_L('\n')); break;
case STRFTIME_L('p'): OUTPUT_STRING(tm->tm_hour < 12 ? STRFTIME_L("AM") : STRFTIME_L("PM")); break;
case STRFTIME_L('r'): OUTPUT_STRFTIME(STRFTIME_L("%I:%M:%S %p")); break;
case STRFTIME_L('R'): OUTPUT_STRFTIME(STRFTIME_L("%H:%M")); break;
case STRFTIME_L('S'): OUTPUT_INT_PADDED(tm->tm_sec, 2, STRFTIME_L('0')); break; /*O*/
case STRFTIME_L('t'): OUTPUT_CHAR(STRFTIME_L('\t')); break;
case STRFTIME_L('T'): OUTPUT_STRFTIME(STRFTIME_L("%H:%M:%S")); break;
case STRFTIME_L('u'): OUTPUT_INT(tm->tm_yday); break; /*O*/
case STRFTIME_L('U'): /*O*/
// TODO: These require a bit of intelligence.
OUTPUT_UNSUPPORTED();
break;
case 'V': /*O*/
case STRFTIME_L('V'): /*O*/
// TODO: These require a bit of intelligence.
OUTPUT_UNSUPPORTED();
break;
case 'w': OUTPUT_INT(tm->tm_wday); break; /*O*/
case 'W': /*O*/
case STRFTIME_L('w'): OUTPUT_INT(tm->tm_wday); break; /*O*/
case STRFTIME_L('W'): /*O*/
// TODO: These require a bit of intelligence.
OUTPUT_UNSUPPORTED();
break;
case 'x': OUTPUT_STRFTIME("%m/%d/%y"); break; /*E*/
case 'X': OUTPUT_STRFTIME("%H:%M:%S"); break; /*E*/
case 'y': OUTPUT_INT_PADDED((tm->tm_year + 1900) % 100, 2, '0'); break; /*EO*/
case 'Y': OUTPUT_INT(tm->tm_year + 1900); break; /*E*/
case 'z':
case STRFTIME_L('x'): OUTPUT_STRFTIME(STRFTIME_L("%m/%d/%y")); break; /*E*/
case STRFTIME_L('X'): OUTPUT_STRFTIME(STRFTIME_L("%H:%M:%S")); break; /*E*/
case STRFTIME_L('y'): OUTPUT_INT_PADDED((tm->tm_year + 1900) % 100, 2, STRFTIME_L('0')); break; /*EO*/
case STRFTIME_L('Y'): OUTPUT_INT(tm->tm_year + 1900); break; /*E*/
case STRFTIME_L('z'):
// TODO: struct tm doesn't have all this information available!
break;
case 'Z':
case STRFTIME_L('Z'):
// TODO: struct tm doesn't have all this information available!
break;
case '%': OUTPUT_CHAR('%'); break;
case STRFTIME_L('%'): OUTPUT_CHAR(STRFTIME_L('%')); break;
default: OUTPUT_UNDEFINED(); break;
}
}
if ( ret == max )
return errno = ERANGE, 0;
return s[ret] = '\0', ret;
return s[ret] = STRFTIME_L('\0'), ret;
}

30
libc/wchar/wcsftime.cpp Normal file
View File

@ -0,0 +1,30 @@
/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013, 2014.
This file is part of the Sortix C Library.
The Sortix C Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
The Sortix C Library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
License for more details.
You should have received a copy of the GNU Lesser General Public License
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
wchar/wcsftime.cpp
Format time and date into a string.
*******************************************************************************/
#define STRFTIME wcsftime
#define STRFTIME_CHAR wchar_t
#define STRFTIME_L(x) L##x
#define STRFTIME_IS_WCHAR 1
#include "../time/strftime.cpp"