2013-03-21 19:13:30 -04:00
|
|
|
/*******************************************************************************
|
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
Copyright(C) Jonas 'Sortie' Termansen 2013, 2014.
|
2013-03-21 19:13:30 -04:00
|
|
|
|
|
|
|
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/>.
|
|
|
|
|
|
|
|
time/strftime.cpp
|
|
|
|
Format time and date into a string.
|
|
|
|
|
|
|
|
*******************************************************************************/
|
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
#ifndef STRFTIME
|
|
|
|
#define STRFTIME strftime
|
|
|
|
#define STRFTIME_CHAR char
|
|
|
|
#define STRFTIME_L(x) x
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <sys/cdefs.h>
|
|
|
|
|
2013-03-21 19:13:30 -04:00
|
|
|
#include <errno.h>
|
2014-05-28 13:51:30 -04:00
|
|
|
#include <stdint.h>
|
2013-03-21 19:13:30 -04:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <time.h>
|
2014-05-28 13:51:30 -04:00
|
|
|
#include <wchar.h>
|
2013-03-21 19:13:30 -04:00
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
static const STRFTIME_CHAR* GetWeekdayAbbreviated(const struct tm* tm)
|
2013-03-21 19:13:30 -04:00
|
|
|
{
|
|
|
|
switch ( tm->tm_wday % 7 )
|
|
|
|
{
|
2014-05-28 13:51:30 -04:00
|
|
|
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");
|
2013-03-21 19:13:30 -04:00
|
|
|
default: __builtin_unreachable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
static const STRFTIME_CHAR* GetWeekday(const struct tm* tm)
|
2013-03-21 19:13:30 -04:00
|
|
|
{
|
|
|
|
switch ( tm->tm_wday % 7 )
|
|
|
|
{
|
2014-05-28 13:51:30 -04:00
|
|
|
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");
|
2013-03-21 19:13:30 -04:00
|
|
|
default: __builtin_unreachable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
static const STRFTIME_CHAR* GetMonthAbbreviated(const struct tm* tm)
|
2013-03-21 19:13:30 -04:00
|
|
|
{
|
|
|
|
switch ( tm->tm_mon % 12 )
|
|
|
|
{
|
2014-05-28 13:51:30 -04:00
|
|
|
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");
|
2013-03-21 19:13:30 -04:00
|
|
|
default: __builtin_unreachable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
static const STRFTIME_CHAR* GetMonth(const struct tm* tm)
|
2013-03-21 19:13:30 -04:00
|
|
|
{
|
|
|
|
switch ( tm->tm_mon % 12 )
|
|
|
|
{
|
2014-05-28 13:51:30 -04:00
|
|
|
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");
|
2013-03-21 19:13:30 -04:00
|
|
|
default: __builtin_unreachable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2013-03-21 19:13:30 -04:00
|
|
|
extern "C"
|
2014-05-28 13:51:30 -04:00
|
|
|
size_t STRFTIME(STRFTIME_CHAR* s,
|
|
|
|
size_t max,
|
|
|
|
const STRFTIME_CHAR* format,
|
|
|
|
const struct tm* tm)
|
2013-03-21 19:13:30 -04:00
|
|
|
{
|
2014-05-28 13:51:30 -04:00
|
|
|
const STRFTIME_CHAR* orig_format = format;
|
2013-03-21 19:13:30 -04:00
|
|
|
size_t ret = 0;
|
|
|
|
|
|
|
|
#define OUTPUT_CHAR(expr) \
|
|
|
|
do { \
|
|
|
|
if ( ret == max ) \
|
|
|
|
return errno = ERANGE, 0; \
|
2014-05-28 13:51:30 -04:00
|
|
|
s[ret++] = (expr); \
|
2013-03-21 19:13:30 -04:00
|
|
|
} while ( 0 )
|
|
|
|
|
|
|
|
#define OUTPUT_STRING(expr) \
|
|
|
|
do { \
|
2014-05-28 13:51:30 -04:00
|
|
|
const STRFTIME_CHAR* out_str = (expr); \
|
2013-03-21 19:13:30 -04:00
|
|
|
while ( *out_str ) \
|
|
|
|
OUTPUT_CHAR(*out_str++); \
|
|
|
|
} while ( 0 )
|
|
|
|
|
|
|
|
#define OUTPUT_STRFTIME(expr) \
|
|
|
|
do { \
|
|
|
|
int old_errno = errno; \
|
|
|
|
errno = 0; \
|
2014-05-28 13:51:30 -04:00
|
|
|
size_t subret = STRFTIME(s + ret, max - ret, (expr), tm); \
|
2013-03-21 19:13:30 -04:00
|
|
|
if ( !subret && errno ) \
|
|
|
|
return 0; \
|
|
|
|
errno = old_errno; \
|
|
|
|
ret += subret; \
|
|
|
|
} while ( 0 )
|
|
|
|
|
|
|
|
#define OUTPUT_INT_PADDED(valexpr, widthexpr, padexpr) \
|
|
|
|
do { \
|
2014-05-28 13:51:30 -04:00
|
|
|
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); \
|
2013-03-21 19:13:30 -04:00
|
|
|
while ( pad && got < width-- ) \
|
|
|
|
OUTPUT_CHAR(pad); \
|
|
|
|
OUTPUT_STRING(str); \
|
|
|
|
} while ( 0 )
|
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
#define OUTPUT_INT(valexpr) OUTPUT_INT_PADDED(valexpr, 0, STRFTIME_L('\0'))
|
2013-03-21 19:13:30 -04:00
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
#if defined(STRFTIME_IS_WCHAR)
|
|
|
|
#define OUTPUT_UNSUPPORTED() \
|
|
|
|
do { \
|
|
|
|
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
|
2013-03-21 19:13:30 -04:00
|
|
|
#define OUTPUT_UNSUPPORTED() \
|
|
|
|
do { \
|
2014-05-28 13:51:30 -04:00
|
|
|
fprintf(stderr, "%s:%u: %s: error: unsupported format string \"%s\" around \"%%%s\"\n", \
|
|
|
|
__FILE__, __LINE__, __STRINGIFY(STRFTIME), orig_format, specifiers_begun_at); \
|
2013-03-21 19:13:30 -04:00
|
|
|
return 0; \
|
|
|
|
} while ( 0 )
|
2014-05-28 13:51:30 -04:00
|
|
|
#endif
|
2013-03-21 19:13:30 -04:00
|
|
|
|
|
|
|
#define OUTPUT_UNDEFINED() OUTPUT_UNSUPPORTED()
|
|
|
|
|
2014-05-28 13:51:30 -04:00
|
|
|
while ( STRFTIME_CHAR c = *format++ )
|
2013-03-21 19:13:30 -04:00
|
|
|
{
|
2014-05-28 13:51:30 -04:00
|
|
|
if ( c != STRFTIME_L('%') )
|
2013-03-21 19:13:30 -04:00
|
|
|
{
|
|
|
|
OUTPUT_CHAR(c);
|
|
|
|
continue;
|
|
|
|
}
|
2014-05-28 13:51:30 -04:00
|
|
|
const STRFTIME_CHAR* specifiers_begun_at = format;
|
2013-03-21 19:13:30 -04:00
|
|
|
c = *format++;
|
|
|
|
|
|
|
|
// Process any optional flags.
|
2014-05-28 13:51:30 -04:00
|
|
|
STRFTIME_CHAR padding_char = STRFTIME_L(' ');
|
2013-03-21 19:13:30 -04:00
|
|
|
bool plus_padding = false;
|
|
|
|
while ( true )
|
|
|
|
{
|
|
|
|
switch ( c )
|
|
|
|
{
|
2014-05-28 13:51:30 -04:00
|
|
|
case STRFTIME_L('0'): padding_char = STRFTIME_L('0'); break;
|
|
|
|
case STRFTIME_L('+'): padding_char = STRFTIME_L('0'); plus_padding = true; break;
|
2013-03-21 19:13:30 -04:00
|
|
|
default: goto end_of_flags;
|
|
|
|
}
|
|
|
|
c = *format++;
|
|
|
|
}
|
|
|
|
end_of_flags:
|
|
|
|
|
|
|
|
// TODO: Support the '+' flag!
|
|
|
|
if ( plus_padding )
|
|
|
|
OUTPUT_UNSUPPORTED();
|
|
|
|
|
|
|
|
// Process the optional minimum field width.
|
|
|
|
size_t field_width = 0;
|
2014-05-28 13:51:30 -04:00
|
|
|
while ( STRFTIME_L('0') <= c && c <= STRFTIME_L('9') )
|
|
|
|
field_width = field_width * 10 + c - STRFTIME_L('0'),
|
2013-03-21 19:13:30 -04:00
|
|
|
c = *format++;
|
|
|
|
|
|
|
|
// Process an optional E or O modifier.
|
|
|
|
bool e_modifier = false;
|
|
|
|
bool o_modifier = false;
|
2014-05-28 13:51:30 -04:00
|
|
|
if ( c == STRFTIME_L('E') )
|
2013-03-21 19:13:30 -04:00
|
|
|
e_modifier = true,
|
|
|
|
c = *format++;
|
2014-05-28 13:51:30 -04:00
|
|
|
else if ( c == STRFTIME_L('O') )
|
2013-03-21 19:13:30 -04:00
|
|
|
o_modifier = true,
|
|
|
|
c = *format++;
|
|
|
|
|
|
|
|
// TODO: Support the E and O modifiers where marked with the E and O
|
|
|
|
// comments in the below switch.
|
|
|
|
if ( e_modifier || o_modifier ) { }
|
|
|
|
|
|
|
|
switch ( c )
|
|
|
|
{
|
2014-05-28 13:51:30 -04:00
|
|
|
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*/
|
2013-03-21 19:13:30 -04:00
|
|
|
OUTPUT_STRING(GetWeekday(tm));
|
2014-05-28 13:51:30 -04:00
|
|
|
OUTPUT_STRING(STRFTIME_L(" "));
|
2013-03-21 19:13:30 -04:00
|
|
|
OUTPUT_STRING(GetMonthAbbreviated(tm));
|
2014-05-28 13:51:30 -04:00
|
|
|
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'));
|
2013-03-21 19:13:30 -04:00
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
case STRFTIME_L('C'): /*E*/
|
2013-03-21 19:13:30 -04:00
|
|
|
if ( !field_width )
|
|
|
|
field_width = 2;
|
|
|
|
OUTPUT_INT_PADDED((tm->tm_year + 1900) / 100, field_width, padding_char);
|
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
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'):
|
2013-03-21 19:13:30 -04:00
|
|
|
// TODO: Revisit this.
|
|
|
|
OUTPUT_UNSUPPORTED();
|
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
case STRFTIME_L('g'):
|
2013-03-21 19:13:30 -04:00
|
|
|
// TODO: These require a bit of intelligence.
|
|
|
|
OUTPUT_UNSUPPORTED();
|
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
case STRFTIME_L('G'):
|
2013-03-21 19:13:30 -04:00
|
|
|
// TODO: These require a bit of intelligence.
|
|
|
|
OUTPUT_UNSUPPORTED();
|
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
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*/
|
2013-03-21 19:13:30 -04:00
|
|
|
// TODO: These require a bit of intelligence.
|
|
|
|
OUTPUT_UNSUPPORTED();
|
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
case STRFTIME_L('V'): /*O*/
|
2013-03-21 19:13:30 -04:00
|
|
|
// TODO: These require a bit of intelligence.
|
|
|
|
OUTPUT_UNSUPPORTED();
|
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
case STRFTIME_L('w'): OUTPUT_INT(tm->tm_wday); break; /*O*/
|
|
|
|
case STRFTIME_L('W'): /*O*/
|
2013-03-21 19:13:30 -04:00
|
|
|
// TODO: These require a bit of intelligence.
|
|
|
|
OUTPUT_UNSUPPORTED();
|
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
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'):
|
2013-03-21 19:13:30 -04:00
|
|
|
// TODO: struct tm doesn't have all this information available!
|
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
case STRFTIME_L('Z'):
|
2013-03-21 19:13:30 -04:00
|
|
|
// TODO: struct tm doesn't have all this information available!
|
|
|
|
break;
|
2014-05-28 13:51:30 -04:00
|
|
|
case STRFTIME_L('%'): OUTPUT_CHAR(STRFTIME_L('%')); break;
|
2013-03-21 19:13:30 -04:00
|
|
|
default: OUTPUT_UNDEFINED(); break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ( ret == max )
|
|
|
|
return errno = ERANGE, 0;
|
2014-05-28 13:51:30 -04:00
|
|
|
return s[ret] = STRFTIME_L('\0'), ret;
|
2013-03-21 19:13:30 -04:00
|
|
|
}
|