280 lines
7.8 KiB
C
280 lines
7.8 KiB
C
/*
|
|
* Copyright (c) 2023 Jonas 'Sortie' Termansen.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* time/strptime.c
|
|
* Parse date and time.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#ifdef TEST
|
|
#include <stdio.h>
|
|
#define strptime mystrptime
|
|
#endif
|
|
|
|
static const char* wdays[] = {"sunday", "monday", "tuesday", "wednesday",
|
|
"thursday", "friday", "saturday", NULL};
|
|
static const char* months[] = {"january", "february", "march", "april", "may",
|
|
"june", "july", "august", "september", "october",
|
|
"november", "december", NULL};
|
|
|
|
// TODO: Maximum width.
|
|
static const char* strptime_match(const char* str,
|
|
int* output,
|
|
const char* const* list)
|
|
{
|
|
for ( int i = 0; list[i]; i++ )
|
|
{
|
|
size_t len = strlen(list[i]);
|
|
if ( !strncasecmp(str, list[i], len) )
|
|
return *output = i, str + len;
|
|
if ( !strncasecmp(str, list[i], 3) )
|
|
return *output = i, str + 3;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// TODO: Maximum width.
|
|
static const char* strptime_digits(const char* str,
|
|
int* output,
|
|
int minimum,
|
|
int maximum,
|
|
int offset)
|
|
{
|
|
if ( *str < '0'|| '9' < *str )
|
|
return NULL;
|
|
// TODO: More exact with number of digits.
|
|
int value = 0;
|
|
while ( '0' <= *str && *str <= '9' )
|
|
if ( __builtin_mul_overflow(value, 10, &value) ||
|
|
__builtin_add_overflow(value, *str++ - '0', &value) )
|
|
return NULL;
|
|
if ( value < minimum || maximum < value )
|
|
return NULL;
|
|
if ( __builtin_add_overflow(value, offset, &value) )
|
|
return NULL;
|
|
return *output = value, str;
|
|
}
|
|
|
|
char* strptime(const char* restrict str,
|
|
const char* restrict format,
|
|
struct tm* restrict tm)
|
|
{
|
|
bool pm = false;
|
|
bool need_mktime = false;
|
|
for ( size_t i = 0; format[i]; )
|
|
{
|
|
if ( isspace((unsigned char) format[i]) )
|
|
{
|
|
do i++;
|
|
while ( isspace((unsigned char) format[i]) );
|
|
if ( !isspace((unsigned char) *str) )
|
|
return NULL;
|
|
do str++;
|
|
while ( isspace((unsigned char) *str) );
|
|
continue;
|
|
}
|
|
else if ( format[i] != '%' )
|
|
{
|
|
|
|
if ( format[i++] != *str++ )
|
|
return NULL;
|
|
continue;
|
|
}
|
|
i++;
|
|
if ( format[i] == '0' || format[i] == '+' )
|
|
i++;
|
|
size_t width = 0;
|
|
while ( '0' <= format[i] && format[i] <= '9' )
|
|
width = width * 10 + (format[i++] - '0');
|
|
// TODO: Maximum width.
|
|
bool modifier_E = false, modifier_O = false;
|
|
if ( format[i] == 'E' )
|
|
modifier_E = true, i++;
|
|
else if ( format[i] == 'O' )
|
|
modifier_O = true, i++;
|
|
(void) modifier_E, (void) modifier_O;
|
|
switch ( format[i] )
|
|
{
|
|
case 'a':
|
|
case 'A': str = strptime_match(str, &tm->tm_wday, wdays); break;
|
|
case 'b':
|
|
case 'B':
|
|
case 'h':
|
|
str = strptime_match(str, &tm->tm_mon, months);
|
|
need_mktime = true;
|
|
break;
|
|
// TODO: %c locale time and date
|
|
// TODO: %C century
|
|
case 'd':
|
|
case 'e':
|
|
str = strptime_digits(str, &tm->tm_mday, 1, 31, 0);
|
|
need_mktime = true;
|
|
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 'I':
|
|
str = strptime_digits(str, &tm->tm_hour, 1, 12, 0);
|
|
if ( tm->tm_hour == 12 )
|
|
tm->tm_hour = 0;
|
|
break;
|
|
case 'j': str = strptime_digits(str, &tm->tm_yday, 1, 366, -1); break;
|
|
case 'm':
|
|
str = strptime_digits(str, &tm->tm_mon, 1, 12, -1);
|
|
need_mktime = true;
|
|
break;
|
|
case 'M': str = strptime_digits(str, &tm->tm_min, 0, 59, 0); break;
|
|
// TODO: %n whitesapce
|
|
// TODO: %t whitespace
|
|
case 'p':
|
|
if ( !strncasecmp(str, "am", 2) )
|
|
str += 2, pm = false;
|
|
else if ( !strncasecmp(str, "pm", 2) )
|
|
str += 2, pm = true;
|
|
else
|
|
return NULL;
|
|
break;
|
|
case 'r': str = strptime(str, "%I:%M:%S %p", 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 'T': str = strptime(str, "%H:%M:%S", tm); break;
|
|
// TODO: %U week number
|
|
case 'w': str = strptime_digits(str, &tm->tm_wday, 0, 6, 0); break;
|
|
// TODO: %W week number
|
|
case 'x': str = strptime(str, "%m/%d/%Y", tm); break;
|
|
case 'X': str = strptime(str, "%H:%M:%S", tm); break;
|
|
// TODO: %y non-century year
|
|
case 'Y':
|
|
// TODO: Minimum yield width.
|
|
str = strptime_digits(str, &tm->tm_year, INT_MIN, INT_MAX, -1900);
|
|
need_mktime = true;
|
|
break;
|
|
case 'z':
|
|
// TODO: More exact.
|
|
if ( *str != '-' || *str != '+' )
|
|
return NULL;
|
|
int hours, minutes;
|
|
if ( !(str = strptime_digits(str, &hours, -12, 12, 0)) ||
|
|
!(str = strptime_digits(str, &minutes, 0, 59, 0)) )
|
|
return NULL;
|
|
tm->tm_isdst = 0;
|
|
// TODO: What is done with this timezone information?
|
|
break;
|
|
case 'Z':
|
|
// TODO: Other timezones.
|
|
if ( strncmp(str, "UTC", 3) != 0 )
|
|
return NULL;
|
|
str += 3;
|
|
tm->tm_isdst = 0;
|
|
// TODO: What is done with this timezone information?
|
|
break;
|
|
case '%':
|
|
if ( *str++ != '%' )
|
|
return NULL;
|
|
break;
|
|
default: NULL;
|
|
}
|
|
if ( !str )
|
|
return NULL;
|
|
i++;
|
|
}
|
|
if ( str )
|
|
{
|
|
if ( pm )
|
|
tm->tm_hour += 12;
|
|
if ( need_mktime )
|
|
mktime(tm);
|
|
}
|
|
return (char*) str;
|
|
}
|
|
|
|
#ifdef TEST
|
|
#undef strptime
|
|
|
|
#include <err.h>
|
|
#include <stdio.h>
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
if ( argc < 3 )
|
|
err(1, "usage");
|
|
const char* str = argv[1];
|
|
const char* format = argv[2];
|
|
struct tm my_tm = {0};
|
|
char* my_end = mystrptime(str, format, &my_tm);
|
|
struct tm c_tm = {0};
|
|
char* c_end = strptime(str, format, &c_tm);
|
|
if ( !my_end && c_end )
|
|
errx(1, "rejected but c allowed it");
|
|
else if ( !my_end )
|
|
errx(1, "rejected correctly");
|
|
else if ( !c_end )
|
|
printf("allowed but c rejected\n");
|
|
else if ( my_end != c_end )
|
|
errx(1, "mismatch my end \"%s\" vs c end \"%s\"", my_end, c_end);
|
|
|
|
if ( my_tm.tm_sec == c_tm.tm_sec )
|
|
printf("tm_sec=%i\n", my_tm.tm_sec);
|
|
else
|
|
printf("tm_sec=%i but C is %i\n", my_tm.tm_sec, c_tm.tm_sec);
|
|
|
|
if ( my_tm.tm_min == c_tm.tm_min )
|
|
printf("tm_min=%i\n", my_tm.tm_min);
|
|
else
|
|
printf("tm_min=%i but C is %i\n", my_tm.tm_min, c_tm.tm_min);
|
|
|
|
if ( my_tm.tm_hour == c_tm.tm_hour )
|
|
printf("tm_hour=%i\n", my_tm.tm_hour);
|
|
else
|
|
printf("tm_hour=%i but C is %i\n", my_tm.tm_hour, c_tm.tm_hour);
|
|
|
|
if ( my_tm.tm_mday == c_tm.tm_mday )
|
|
printf("tm_mday=%i\n", my_tm.tm_mday);
|
|
else
|
|
printf("tm_mday=%i but C is %i\n", my_tm.tm_mday, c_tm.tm_mday);
|
|
|
|
if ( my_tm.tm_mon == c_tm.tm_mon )
|
|
printf("tm_mon=%i\n", my_tm.tm_mon);
|
|
else
|
|
printf("tm_mon=%i but C is %i\n", my_tm.tm_mon, c_tm.tm_mon);
|
|
|
|
if ( my_tm.tm_year == c_tm.tm_year )
|
|
printf("tm_year=%i\n", my_tm.tm_year);
|
|
else
|
|
printf("tm_year=%i but C is %i\n", my_tm.tm_year, c_tm.tm_year);
|
|
|
|
if ( my_tm.tm_wday == c_tm.tm_wday )
|
|
printf("tm_wday=%i\n", my_tm.tm_wday);
|
|
else
|
|
printf("tm_wday=%i but C is %i\n", my_tm.tm_wday, c_tm.tm_wday);
|
|
|
|
if ( my_tm.tm_yday == c_tm.tm_yday )
|
|
printf("tm_yday=%i\n", my_tm.tm_yday);
|
|
else
|
|
printf("tm_yday=%i but C is %i\n", my_tm.tm_yday, c_tm.tm_yday);
|
|
|
|
if ( my_tm.tm_isdst == c_tm.tm_isdst )
|
|
printf("tm_isdst=%i\n", my_tm.tm_isdst);
|
|
else
|
|
printf("tm_isdst=%i but C is %i\n", my_tm.tm_isdst, c_tm.tm_isdst);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|