fixup! Add strptime(3).
This commit is contained in:
parent
d2c98889bd
commit
aacec27864
|
@ -20,6 +20,7 @@
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
@ -28,16 +29,15 @@
|
||||||
#define strptime mystrptime
|
#define strptime mystrptime
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const char* wdays[] = {"sunday", "monday", "tuesday", "wednesday",
|
static const char* wdays[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
|
||||||
"thursday", "friday", "saturday", NULL};
|
"Thursday", "Friday", "Saturday", NULL};
|
||||||
static const char* months[] = {"january", "february", "march", "april", "may",
|
static const char* months[] = {"January", "February", "March", "April", "May",
|
||||||
"june", "july", "august", "september", "october",
|
"June", "July", "August", "September", "October",
|
||||||
"november", "december", NULL};
|
"November", "December", NULL};
|
||||||
|
|
||||||
// TODO: Maximum width.
|
static const char* strptime_str(const char* str,
|
||||||
static const char* strptime_match(const char* str,
|
int* output,
|
||||||
int* output,
|
const char* const* list)
|
||||||
const char* const* list)
|
|
||||||
{
|
{
|
||||||
for ( int i = 0; list[i]; i++ )
|
for ( int i = 0; list[i]; i++ )
|
||||||
{
|
{
|
||||||
|
@ -50,21 +50,29 @@ static const char* strptime_match(const char* str,
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Maximum width.
|
static const char* strptime_num(const char* str,
|
||||||
static const char* strptime_digits(const char* str,
|
int* output,
|
||||||
int* output,
|
int minimum,
|
||||||
int minimum,
|
int maximum,
|
||||||
int maximum,
|
size_t min_digits,
|
||||||
int offset)
|
size_t max_digits,
|
||||||
|
int offset)
|
||||||
{
|
{
|
||||||
if ( *str < '0'|| '9' < *str )
|
|
||||||
return NULL;
|
|
||||||
// TODO: More exact with number of digits.
|
|
||||||
int value = 0;
|
int value = 0;
|
||||||
while ( '0' <= *str && *str <= '9' )
|
size_t i = 0;
|
||||||
if ( __builtin_mul_overflow(value, 10, &value) ||
|
while ( i < max_digits )
|
||||||
__builtin_add_overflow(value, *str++ - '0', &value) )
|
{
|
||||||
|
if ( str[i] < '0'|| '9' < str[i] )
|
||||||
|
{
|
||||||
|
if ( min_digits <= i )
|
||||||
|
break;
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
if ( __builtin_mul_overflow(value, 10, &value) ||
|
||||||
|
__builtin_add_overflow(value, str[i++] - '0', &value) )
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
str += i;
|
||||||
if ( value < minimum || maximum < value )
|
if ( value < minimum || maximum < value )
|
||||||
return NULL;
|
return NULL;
|
||||||
if ( __builtin_add_overflow(value, offset, &value) )
|
if ( __builtin_add_overflow(value, offset, &value) )
|
||||||
|
@ -78,6 +86,7 @@ char* strptime(const char* restrict str,
|
||||||
{
|
{
|
||||||
bool pm = false;
|
bool pm = false;
|
||||||
bool need_mktime = false;
|
bool need_mktime = false;
|
||||||
|
int year_high = -1, year_low = -1;
|
||||||
for ( size_t i = 0; format[i]; )
|
for ( size_t i = 0; format[i]; )
|
||||||
{
|
{
|
||||||
if ( isspace((unsigned char) format[i]) )
|
if ( isspace((unsigned char) format[i]) )
|
||||||
|
@ -100,6 +109,7 @@ char* strptime(const char* restrict str,
|
||||||
i++;
|
i++;
|
||||||
if ( format[i] == '0' || format[i] == '+' )
|
if ( format[i] == '0' || format[i] == '+' )
|
||||||
i++;
|
i++;
|
||||||
|
bool has_width = '0' <= format[i] && format[i] <= '9';
|
||||||
size_t width = 0;
|
size_t width = 0;
|
||||||
while ( '0' <= format[i] && format[i] <= '9' )
|
while ( '0' <= format[i] && format[i] <= '9' )
|
||||||
width = width * 10 + (format[i++] - '0');
|
width = width * 10 + (format[i++] - '0');
|
||||||
|
@ -113,35 +123,40 @@ char* strptime(const char* restrict str,
|
||||||
switch ( format[i] )
|
switch ( format[i] )
|
||||||
{
|
{
|
||||||
case 'a':
|
case 'a':
|
||||||
case 'A': str = strptime_match(str, &tm->tm_wday, wdays); break;
|
case 'A': str = strptime_str(str, &tm->tm_wday, wdays); break;
|
||||||
case 'b':
|
case 'b':
|
||||||
case 'B':
|
case 'B':
|
||||||
case 'h':
|
case 'h':
|
||||||
str = strptime_match(str, &tm->tm_mon, months);
|
str = strptime_str(str, &tm->tm_mon, months);
|
||||||
need_mktime = true;
|
need_mktime = true;
|
||||||
break;
|
break;
|
||||||
// TODO: %c locale time and date
|
case 'c': str = strptime(str, "%a %b %e %H:%M:%S %Y", tm); break;
|
||||||
// TODO: %C century
|
case 'C': str = strptime_num(str, &year_high, 0, 99, 1, 2, 0); break;
|
||||||
case 'd':
|
case 'd':
|
||||||
case 'e':
|
case 'e':
|
||||||
str = strptime_digits(str, &tm->tm_mday, 1, 31, 0);
|
str = strptime_num(str, &tm->tm_mday, 1, 31, 1, 2, 0);
|
||||||
need_mktime = true;
|
need_mktime = true;
|
||||||
break;
|
break;
|
||||||
case 'D': str = strptime(str, "%m/%d/%y", tm); break;
|
case 'D': str = strptime(str, "%m/%d/%y", tm); break;
|
||||||
case 'H': str = strptime_digits(str, &tm->tm_hour, 0, 23, 0); break;
|
case 'H': str = strptime_num(str, &tm->tm_hour, 0, 23, 1, 2, 0); break;
|
||||||
case 'I':
|
case 'I':
|
||||||
str = strptime_digits(str, &tm->tm_hour, 1, 12, 0);
|
str = strptime_num(str, &tm->tm_hour, 1, 12, 1, 2, 0);
|
||||||
if ( tm->tm_hour == 12 )
|
if ( tm->tm_hour == 12 )
|
||||||
tm->tm_hour = 0;
|
tm->tm_hour = 0;
|
||||||
break;
|
break;
|
||||||
case 'j': str = strptime_digits(str, &tm->tm_yday, 1, 366, -1); break;
|
case 'j': str = strptime_num(str, &tm->tm_yday, 1, 366, 3, 3, -1); break;
|
||||||
case 'm':
|
case 'm':
|
||||||
str = strptime_digits(str, &tm->tm_mon, 1, 12, -1);
|
str = strptime_num(str, &tm->tm_mon, 1, 12, 1, 2, -1);
|
||||||
need_mktime = true;
|
need_mktime = true;
|
||||||
break;
|
break;
|
||||||
case 'M': str = strptime_digits(str, &tm->tm_min, 0, 59, 0); break;
|
case 'M': str = strptime_num(str, &tm->tm_min, 0, 59, 1, 2, 0); break;
|
||||||
// TODO: %n whitesapce
|
case 'n':
|
||||||
// TODO: %t whitespace
|
case 't':
|
||||||
|
if ( !isspace((unsigned char) *str) )
|
||||||
|
return NULL;
|
||||||
|
do str++;
|
||||||
|
while ( isspace((unsigned char) *str) );
|
||||||
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
if ( !strncasecmp(str, "am", 2) )
|
if ( !strncasecmp(str, "am", 2) )
|
||||||
str += 2, pm = false;
|
str += 2, pm = false;
|
||||||
|
@ -152,17 +167,22 @@ char* strptime(const char* restrict str,
|
||||||
break;
|
break;
|
||||||
case 'r': str = strptime(str, "%I:%M:%S %p", tm); break;
|
case 'r': str = strptime(str, "%I:%M:%S %p", tm); break;
|
||||||
case 'R': str = strptime(str, "%H:%M", tm); break;
|
case 'R': str = strptime(str, "%H:%M", tm); break;
|
||||||
case 'S': str = strptime_digits(str, &tm->tm_sec, 0, 60, 0); break;
|
case 'S': str = strptime_num(str, &tm->tm_sec, 0, 60, 1, 2, 0); break;
|
||||||
case 'T': str = strptime(str, "%H:%M:%S", tm); break;
|
case 'T': str = strptime(str, "%H:%M:%S", tm); break;
|
||||||
// TODO: %U week number
|
// TODO: %U week number
|
||||||
case 'w': str = strptime_digits(str, &tm->tm_wday, 0, 6, 0); break;
|
case 'w': str = strptime_num(str, &tm->tm_wday, 0, 6, 1, 1, 0); break;
|
||||||
// TODO: %W week number
|
// TODO: %W week number
|
||||||
case 'x': str = strptime(str, "%m/%d/%Y", tm); break;
|
case 'x': str = strptime(str, "%m/%d/%Y", tm); break;
|
||||||
case 'X': str = strptime(str, "%H:%M:%S", tm); break;
|
case 'X': str = strptime(str, "%H:%M:%S", tm); break;
|
||||||
// TODO: %y non-century year
|
case 'y': str = strptime_num(str, &year_low, 0, 99, 1, 2, 0); break;
|
||||||
case 'Y':
|
case 'Y':
|
||||||
// TODO: Minimum yield width.
|
// POSIX divergence: Avoid year 10k problem by allowing more than
|
||||||
str = strptime_digits(str, &tm->tm_year, INT_MIN, INT_MAX, -1900);
|
// four characters by default.
|
||||||
|
if ( !has_width )
|
||||||
|
width = SIZE_MAX;
|
||||||
|
// TODO: Allow leading + and - per POSIX.
|
||||||
|
str = strptime_num(str, &tm->tm_year, INT_MIN, INT_MAX, 1, width,
|
||||||
|
-1900);
|
||||||
need_mktime = true;
|
need_mktime = true;
|
||||||
break;
|
break;
|
||||||
case 'z':
|
case 'z':
|
||||||
|
@ -170,8 +190,8 @@ char* strptime(const char* restrict str,
|
||||||
if ( *str != '-' || *str != '+' )
|
if ( *str != '-' || *str != '+' )
|
||||||
return NULL;
|
return NULL;
|
||||||
int hours, minutes;
|
int hours, minutes;
|
||||||
if ( !(str = strptime_digits(str, &hours, -12, 12, 0)) ||
|
if ( !(str = strptime_num(str, &hours, -12, 12, 2, 2, 0)) ||
|
||||||
!(str = strptime_digits(str, &minutes, 0, 59, 0)) )
|
!(str = strptime_num(str, &minutes, 0, 59, 2, 2, 0)) )
|
||||||
return NULL;
|
return NULL;
|
||||||
tm->tm_isdst = 0;
|
tm->tm_isdst = 0;
|
||||||
// TODO: What is done with this timezone information?
|
// TODO: What is done with this timezone information?
|
||||||
|
@ -198,8 +218,22 @@ char* strptime(const char* restrict str,
|
||||||
{
|
{
|
||||||
if ( pm )
|
if ( pm )
|
||||||
tm->tm_hour += 12;
|
tm->tm_hour += 12;
|
||||||
|
if ( year_high != -1 || year_low != -1 )
|
||||||
|
{
|
||||||
|
if ( year_high == -1 )
|
||||||
|
year_high = year_low < 70 ? 20 : 19;
|
||||||
|
else if ( year_low == -1 )
|
||||||
|
year_low = 0;
|
||||||
|
tm->tm_year = year_high * 100 + year_low - 1900;
|
||||||
|
need_mktime = true;
|
||||||
|
}
|
||||||
if ( need_mktime )
|
if ( need_mktime )
|
||||||
mktime(tm);
|
{
|
||||||
|
struct tm copy = *tm;
|
||||||
|
mktime(©);
|
||||||
|
tm->tm_wday = copy.tm_wday;
|
||||||
|
tm->tm_yday = copy.tm_yday;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return (char*) str;
|
return (char*) str;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue