From 9ca343c5e443147ddea7d82c7e88bbe2e4dafbe9 Mon Sep 17 00:00:00 2001 From: Jonas 'Sortie' Termansen Date: Wed, 28 May 2014 19:51:30 +0200 Subject: [PATCH] Add wcsftime(3). --- libc/Makefile | 1 + libc/include/wchar.h | 2 +- libc/time/strftime.cpp | 274 +++++++++++++++++++++++----------------- libc/wchar/wcsftime.cpp | 30 +++++ 4 files changed, 193 insertions(+), 114 deletions(-) create mode 100644 libc/wchar/wcsftime.cpp diff --git a/libc/Makefile b/libc/Makefile index e63b8dc9..790671ad 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -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 \ diff --git a/libc/include/wchar.h b/libc/include/wchar.h index aef37e33..61356f7b 100644 --- a/libc/include/wchar.h +++ b/libc/include/wchar.h @@ -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 diff --git a/libc/time/strftime.cpp b/libc/time/strftime.cpp index 51c2a1ad..f913509a 100644 --- a/libc/time/strftime.cpp +++ b/libc/time/strftime.cpp @@ -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 + #include +#include #include #include +#include -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; } diff --git a/libc/wchar/wcsftime.cpp b/libc/wchar/wcsftime.cpp new file mode 100644 index 00000000..acb08c88 --- /dev/null +++ b/libc/wchar/wcsftime.cpp @@ -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 . + + 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"