From: Martin Baute Date: Thu, 31 Mar 2016 06:22:43 +0000 (+0200) Subject: ISO week calculation. X-Git-Url: https://pd.if.org/git/?p=pdclib;a=commitdiff_plain;h=3b7e3a6f07410a5757339fb8037c149c1655385e ISO week calculation. --- diff --git a/functions/_PDCLIB/_PDCLIB_is_leap.c b/functions/_PDCLIB/_PDCLIB_is_leap.c new file mode 100644 index 0000000..80ea70b --- /dev/null +++ b/functions/_PDCLIB/_PDCLIB_is_leap.c @@ -0,0 +1,39 @@ +/* _PDCLIB_is_leap( int ) + + This file is part of the Public Domain C Library (PDCLib). + Permission is granted to use, modify, and / or redistribute at will. +*/ + +#include "_PDCLIB_int.h" + +#ifndef REGTEST + +int _PDCLIB_is_leap( int year_offset ) +{ + /* year given as offset from 1900, matching tm.tm_year in */ + long long year = year_offset + 1900ll; + return ( ( year % 4 ) == 0 && ( ( year % 25 ) != 0 || ( year % 400 ) == 0 ) ); +} + +#endif + +#ifdef TEST + +#include "_PDCLIB_test.h" + +int main( void ) +{ +#ifndef REGTEST + /* 1901 not leap */ + TESTCASE( ! _PDCLIB_is_leap( 1 ) ); + /* 1904 leap */ + TESTCASE( _PDCLIB_is_leap( 4 ) ); + /* 1900 not leap */ + TESTCASE( ! _PDCLIB_is_leap( 0 ) ); + /* 2000 leap */ + TESTCASE( _PDCLIB_is_leap( 100 ) ); +#endif + return TEST_RESULTS; +} + +#endif diff --git a/functions/time/strftime.c b/functions/time/strftime.c index 92e6062..7716061 100644 --- a/functions/time/strftime.c +++ b/functions/time/strftime.c @@ -17,6 +17,69 @@ care for putting it into a number of macros / helper functions. */ +static int iso_week( const struct tm * timeptr ) +{ + /* calculations below rely on Sunday == 7 */ + int wday = timeptr->tm_wday; + if ( wday == 0 ) + { + wday = 7; + } + /* https://en.wikipedia.org/wiki/ISO_week_date */ + int week = ( timeptr->tm_yday - wday + 11 ) / 7; + if ( week == 53 ) + { + /* date *may* belong to the *next* year, if: + * it is 31.12. and Monday - Wednesday + * it is 30.12. and Monday - Tuesday + * it is 29.12. and Monday + We can safely assume December... + */ + if ( ( timeptr->tm_mday - wday ) > 27 ) + { + week = 1; + } + } + else if ( week == 0 ) + { + /* date *does* belong to *previous* year, + i.e. has week 52 *unless*... + * current year started on a Friday, or + * previous year is leap and this year + started on a Saturday. + */ + int firstday = timeptr->tm_wday - ( timeptr->tm_yday % 7 ); + if ( firstday < 0 ) + { + firstday += 7; + } + if ( ( firstday == 5 ) || ( _PDCLIB_is_leap( timeptr->tm_year - 1 ) && firstday == 6 ) ) + { + week = 53; + } + else + { + week = 52; + } + } + return week; +} + +static int sprints( char * _PDCLIB_restrict dest, const char * _PDCLIB_restrict src, size_t maxsize, size_t * rc ) +{ + size_t len = strlen( src ); + if ( *rc < ( maxsize - len ) ) + { + strcpy( dest + *rc, src ); + *rc += len; + return 1; + } + else + { + return 0; + } +} + size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB_restrict format, const struct tm * _PDCLIB_restrict timeptr ) { size_t rc = 0; @@ -52,14 +115,7 @@ size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB case 'a': { /* tm_wday abbreviated */ - const char * day = _PDCLIB_lconv.day_name_abbr[ timeptr->tm_wday ]; - size_t len = strlen( day ); - if ( rc < ( maxsize - len ) ) - { - strcpy( s + rc, day ); - rc += len; - } - else + if ( ! sprints( s, _PDCLIB_lconv.day_name_abbr[ timeptr->tm_wday ], maxsize, &rc ) ) { return 0; } @@ -68,14 +124,7 @@ size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB case 'A': { /* tm_wday full */ - const char * day = _PDCLIB_lconv.day_name_full[ timeptr->tm_wday ]; - size_t len = strlen( day ); - if ( rc < ( maxsize - len ) ) - { - strcpy( s + rc, day ); - rc += len; - } - else + if ( ! sprints( s, _PDCLIB_lconv.day_name_full[ timeptr->tm_wday ], maxsize, &rc ) ) { return 0; } @@ -85,14 +134,7 @@ size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB case 'h': { /* tm_mon abbreviated */ - const char * month = _PDCLIB_lconv.month_name_abbr[ timeptr->tm_mon ]; - size_t len = strlen( month ); - if ( rc < ( maxsize - len ) ) - { - strcpy( s + rc, month ); - rc += len; - } - else + if ( ! sprints( s, _PDCLIB_lconv.month_name_abbr[ timeptr->tm_mon ], maxsize, &rc ) ) { return 0; } @@ -101,14 +143,7 @@ size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB case 'B': { /* tm_mon full */ - const char * month = _PDCLIB_lconv.month_name_full[ timeptr->tm_mon ]; - size_t len = strlen( month ); - if ( rc < ( maxsize - len ) ) - { - strcpy( s + rc, month ); - rc += len; - } - else + if ( ! sprints( s, _PDCLIB_lconv.month_name_full[ timeptr->tm_mon ], maxsize, &rc ) ) { return 0; } @@ -208,13 +243,56 @@ size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB case 'g': { /* last 2 digits of the week-based year as decimal (00-99) */ - /* TODO: 'g' */ + if ( rc < ( maxsize - 2 ) ) + { + int week = iso_week( timeptr ); + int bias = 0; + if ( week >= 52 && timeptr->tm_mon == 0 ) + { + --bias; + } + else if ( week == 1 && timeptr->tm_mon == 11 ) + { + ++bias; + } + div_t year = div( timeptr->tm_year % 100 + bias, 10 ); + s[rc++] = '0' + year.quot; + s[rc++] = '0' + year.rem; + } + else + { + return 0; + } break; } case 'G': { /* week-based year as decimal (e.g. 1997) */ - /* TODO: 'G' */ + if ( rc < ( maxsize - 4 ) ) + { + int week = iso_week( timeptr ); + int year = timeptr->tm_year + 1900; + if ( week >= 52 && timeptr->tm_mon == 0 ) + { + --year; + } + else if ( week == 1 && timeptr->tm_mon == 11 ) + { + ++year; + } + for ( int i = 3; i >= 0; --i ) + { + div_t digit = div( year, 10 ); + s[ rc + i ] = '0' + digit.rem; + year = digit.quot; + } + + rc += 4; + } + else + { + return 0; + } break; } case 'H': @@ -239,7 +317,7 @@ size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB /* 'O' for locale's alternative numeric symbols */ if ( rc < ( maxsize - 2 ) ) { - div_t hour = div( ( timeptr->tm_hour + 1 ) % 12, 10 ); + div_t hour = div( ( timeptr->tm_hour + 11 ) % 12 + 1, 10 ); s[rc++] = '0' + hour.quot; s[rc++] = '0' + hour.rem; } @@ -254,7 +332,7 @@ size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB /* tm_yday as decimal (001-366) */ if ( rc < ( maxsize - 3 ) ) { - div_t yday = div( timeptr->tm_yday, 100 ); + div_t yday = div( timeptr->tm_yday + 1, 100 ); s[rc++] = '0' + yday.quot; s[rc++] = '0' + yday.rem / 10; s[rc++] = '0' + yday.rem % 10; @@ -399,9 +477,18 @@ size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB } case 'V': { - /* week number as decimal (01-53) */ + /* ISO week number as decimal (01-53) */ /* 'O' for locale's alternative numeric symbols */ - /* TODO: 'V' */ + if ( rc < ( maxsize - 2 ) ) + { + div_t week = div( iso_week( timeptr ), 10 ); + s[rc++] = '0' + week.quot; + s[rc++] = '0' + week.rem; + } + else + { + return 0; + } break; } case 'w': @@ -519,12 +606,14 @@ size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB #include "_PDCLIB_test.h" +#define MKTIME( tm, sec, min, hour, day, month, year, wday, yday ) tm.tm_sec = sec; tm.tm_min = min; tm.tm_hour = hour; tm.tm_mday = day; tm.tm_mon = month; tm.tm_year = year; tm.tm_wday = wday; tm.tm_yday = yday; tm.tm_isdst = -1; + int main( void ) { -#ifndef REGTEST - /* Replace with a call to mktime() once that is implemented. */ - struct tm timeptr = { 59, 30, 12, 1, 9, 72, 0, 274, -1 }; char buffer[100]; + /* Basic functionality */ + struct tm timeptr; + MKTIME( timeptr, 59, 30, 12, 1, 9, 72, 0, 274 ); TESTCASE( strftime( buffer, 100, "%a ", &timeptr ) == 4 ); TESTCASE( strcmp( buffer, "Sun " ) == 0 ); TESTCASE( strftime( buffer, 100, "%A ", &timeptr ) == 7 ); @@ -550,9 +639,9 @@ int main( void ) TESTCASE( strftime( buffer, 100, "%H ", &timeptr ) == 3 ); TESTCASE( strcmp( buffer, "12 " ) == 0 ); TESTCASE( strftime( buffer, 100, "%I ", &timeptr ) == 3 ); - TESTCASE( strcmp( buffer, "01 " ) == 0 ); + TESTCASE( strcmp( buffer, "12 " ) == 0 ); TESTCASE( strftime( buffer, 100, "%j ", &timeptr ) == 4 ); - TESTCASE( strcmp( buffer, "274 " ) == 0 ); + TESTCASE( strcmp( buffer, "275 " ) == 0 ); TESTCASE( strftime( buffer, 100, "%m ", &timeptr ) == 3 ); TESTCASE( strcmp( buffer, "10 " ) == 0 ); TESTCASE( strftime( buffer, 100, "%M ", &timeptr ) == 3 ); @@ -560,7 +649,7 @@ int main( void ) TESTCASE( strftime( buffer, 100, "%p ", &timeptr ) == 3 ); TESTCASE( strcmp( buffer, "PM " ) == 0 ); TESTCASE( strftime( buffer, 100, "%r ", &timeptr ) == 12 ); - TESTCASE( strcmp( buffer, "01:30:59 PM " ) == 0 ); + TESTCASE( strcmp( buffer, "12:30:59 PM " ) == 0 ); TESTCASE( strftime( buffer, 100, "%R ", &timeptr ) == 6 ); TESTCASE( strcmp( buffer, "12:30 " ) == 0 ); TESTCASE( strftime( buffer, 100, "%S ", &timeptr ) == 3 ); @@ -585,7 +674,66 @@ int main( void ) TESTCASE( strcmp( buffer, "\n " ) == 0 ); TESTCASE( strftime( buffer, 100, "%t ", &timeptr ) == 2 ); TESTCASE( strcmp( buffer, "\t " ) == 0 ); -#endif + /* ISO week calculation */ + MKTIME( timeptr, 0, 0, 0, 27, 11, 3, 0, 360 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "52 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 28, 11, 3, 1, 361 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "53 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 31, 11, 3, 4, 364 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "53 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 1, 0, 4, 5, 0 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "53 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 3, 0, 4, 0, 2 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "53 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "03 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 ); + TESTCASE( strcmp( buffer, "1903 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 4, 0, 4, 1, 3 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "01 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "04 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 ); + TESTCASE( strcmp( buffer, "1904 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 1, 0, 5, 0, 0 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "52 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "04 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 ); + TESTCASE( strcmp( buffer, "1904 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 24, 11, 100, 0, 358 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "51 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 25, 11, 100, 1, 359 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "52 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 31, 11, 100, 0, 365 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "52 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "00 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 ); + TESTCASE( strcmp( buffer, "2000 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 1, 0, 101, 1, 0 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "01 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "01 " ) == 0 ); + TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 ); + TESTCASE( strcmp( buffer, "2001 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 7, 0, 101, 7, 6 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "01 " ) == 0 ); + MKTIME( timeptr, 0, 0, 0, 8, 0, 101, 1, 7 ); + TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 ); + TESTCASE( strcmp( buffer, "02 " ) == 0 ); return TEST_RESULTS; } diff --git a/internals/_PDCLIB_int.h b/internals/_PDCLIB_int.h index d9e7f07..5c9cce5 100644 --- a/internals/_PDCLIB_int.h +++ b/internals/_PDCLIB_int.h @@ -404,6 +404,9 @@ int _PDCLIB_prepwrite( struct _PDCLIB_file_t * stream ); /* Closing all streams on program exit */ void _PDCLIB_closeall( void ); +/* Check if a given year is a leap year. Parameter is offset to 1900. */ +int _PDCLIB_is_leap( int year_offset ); + /* -------------------------------------------------------------------------- */ /* errno */ /* -------------------------------------------------------------------------- */