1 /* strftime( char * restrict, size_t, const char * restrict, const struct tm * restrict )
3 This file is part of the Public Domain C Library (PDCLib).
4 Permission is granted to use, modify, and / or redistribute at will.
14 /* TODO: Alternative representations / numerals not supported. Multibyte support missing. */
16 /* This implementation's code is highly repetitive, but I did not really
17 care for putting it into a number of macros / helper functions.
20 size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB_restrict format, const struct tm * _PDCLIB_restrict timeptr )
24 while ( rc < maxsize )
28 if ( ( s[rc] = *format++ ) == '\0' )
44 /* flag = *format++; */
54 /* tm_wday abbreviated */
55 const char * day = _PDCLIB_lconv.day_name_abbr[ timeptr->tm_wday ];
56 size_t len = strlen( day );
57 if ( rc < ( maxsize - len ) )
59 strcpy( s + rc, day );
71 const char * day = _PDCLIB_lconv.day_name_full[ timeptr->tm_wday ];
72 size_t len = strlen( day );
73 if ( rc < ( maxsize - len ) )
75 strcpy( s + rc, day );
87 /* tm_mon abbreviated */
88 const char * month = _PDCLIB_lconv.month_name_abbr[ timeptr->tm_mon ];
89 size_t len = strlen( month );
90 if ( rc < ( maxsize - len ) )
92 strcpy( s + rc, month );
104 const char * month = _PDCLIB_lconv.month_name_full[ timeptr->tm_mon ];
105 size_t len = strlen( month );
106 if ( rc < ( maxsize - len ) )
108 strcpy( s + rc, month );
119 /* locale's date / time representation, %a %b %e %T %Y for C locale */
120 /* 'E' for locale's alternative representation */
121 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.date_time_format, timeptr );
134 /* tm_year divided by 100, truncated to decimal (00-99) */
135 /* 'E' for base year (period) in locale's alternative representation */
136 if ( rc < ( maxsize - 2 ) )
138 div_t period = div( ( ( timeptr->tm_year + 1900 ) / 100 ), 10 );
139 s[rc++] = '0' + period.quot;
140 s[rc++] = '0' + period.rem;
150 /* tm_mday as decimal (01-31) */
151 /* 'O' for locale's alternative numeric symbols */
152 if ( rc < ( maxsize - 2 ) )
154 div_t day = div( timeptr->tm_mday, 10 );
155 s[rc++] = '0' + day.quot;
156 s[rc++] = '0' + day.rem;
167 size_t count = strftime( s + rc, maxsize - rc, "%m/%d/%y", timeptr );
180 /* tm_mday as decimal ( 1-31) */
181 /* 'O' for locale's alternative numeric symbols */
182 if ( rc < ( maxsize - 2 ) )
184 div_t day = div( timeptr->tm_mday, 10 );
185 s[rc++] = ( day.quot > 0 ) ? '0' + day.quot : ' ';
186 s[rc++] = '0' + day.rem;
197 size_t count = strftime( s + rc, maxsize - rc, "%Y-%m-%d", timeptr );
210 /* last 2 digits of the week-based year as decimal (00-99) */
216 /* week-based year as decimal (e.g. 1997) */
222 /* tm_hour as 24h decimal (00-23) */
223 /* 'O' for locale's alternative numeric symbols */
224 if ( rc < ( maxsize - 2 ) )
226 div_t hour = div( timeptr->tm_hour, 10 );
227 s[rc++] = '0' + hour.quot;
228 s[rc++] = '0' + hour.rem;
238 /* tm_hour as 12h decimal (01-12) */
239 /* 'O' for locale's alternative numeric symbols */
240 if ( rc < ( maxsize - 2 ) )
242 div_t hour = div( ( timeptr->tm_hour + 1 ) % 12, 10 );
243 s[rc++] = '0' + hour.quot;
244 s[rc++] = '0' + hour.rem;
254 /* tm_yday as decimal (001-366) */
255 if ( rc < ( maxsize - 3 ) )
257 div_t yday = div( timeptr->tm_yday, 100 );
258 s[rc++] = '0' + yday.quot;
259 s[rc++] = '0' + yday.rem / 10;
260 s[rc++] = '0' + yday.rem % 10;
270 /* tm_mon as decimal (01-12) */
271 /* 'O' for locale's alternative numeric symbols */
272 if ( rc < ( maxsize - 2 ) )
274 div_t mon = div( timeptr->tm_mon + 1, 10 );
275 s[rc++] = '0' + mon.quot;
276 s[rc++] = '0' + mon.rem;
286 /* tm_min as decimal (00-59) */
287 /* 'O' for locale's alternative numeric symbols */
288 if ( rc < ( maxsize - 2 ) )
290 div_t min = div( timeptr->tm_min, 10 );
291 s[rc++] = '0' + min.quot;
292 s[rc++] = '0' + min.rem;
308 /* tm_hour locale's AM/PM designations */
309 const char * designation = _PDCLIB_lconv.am_pm[ timeptr->tm_hour > 11 ];
310 size_t len = strlen( designation );
311 if ( rc < ( maxsize - len ) )
313 strcpy( s + rc, designation );
324 /* tm_hour / tm_min / tm_sec as locale's 12-hour clock time, %I:%M:%S %p for C locale */
325 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.time_format_12h, timeptr );
339 size_t count = strftime( s + rc, maxsize - rc, "%H:%M", timeptr );
352 /* tm_sec as decimal (00-60) */
353 /* 'O' for locale's alternative numeric symbols */
354 if ( rc < ( maxsize - 2 ) )
356 div_t sec = div( timeptr->tm_sec, 10 );
357 s[rc++] = '0' + sec.quot;
358 s[rc++] = '0' + sec.rem;
375 size_t count = strftime( s + rc, maxsize - rc, "%H:%M:%S", timeptr );
388 /* tm_wday as decimal (1-7) with Monday == 1 */
389 /* 'O' for locale's alternative numeric symbols */
390 s[rc++] = ( timeptr->tm_wday == 0 ) ? '7' : '0' + timeptr->tm_wday;
395 /* week number of the year (first Sunday as the first day of week 1) as decimal (00-53) */
396 /* 'O' for locale's alternative numeric symbols */
402 /* week number as decimal (01-53) */
403 /* 'O' for locale's alternative numeric symbols */
409 /* tm_wday as decimal number (0-6) with Sunday == 0 */
410 /* 'O' for locale's alternative numeric symbols */
411 s[rc++] = '0' + timeptr->tm_wday;
416 /* week number of the year (first Monday as the first day of week 1) as decimal (00-53) */
417 /* 'O' for locale's alternative numeric symbols */
423 /* locale's date representation, %m/%d/%y for C locale */
424 /* 'E' for locale's alternative representation */
425 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.date_format, timeptr );
438 /* locale's time representation, %T for C locale */
439 /* 'E' for locale's alternative representation */
440 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.time_format, timeptr );
453 /* last 2 digits of tm_year as decimal (00-99) */
454 /* 'E' for offset from %EC (year only) in locale's alternative representation */
455 /* 'O' for locale's alternative numeric symbols */
456 if ( rc < ( maxsize - 2 ) )
458 div_t year = div( ( timeptr->tm_year % 100 ), 10 );
459 s[rc++] = '0' + year.quot;
460 s[rc++] = '0' + year.rem;
470 /* tm_year as decimal (e.g. 1997) */
471 /* 'E' for locale's alternative representation */
472 if ( rc < ( maxsize - 4 ) )
474 int year = timeptr->tm_year + 1900;
476 for ( int i = 3; i >= 0; --i )
478 div_t digit = div( year, 10 );
479 s[ rc + i ] = '0' + digit.rem;
493 /* tm_isdst / UTC offset in ISO8601 format (e.g. -0430 meaning 4 hours 30 minutes behind Greenwich), or no characters */
499 /* tm_isdst / locale's time zone name or abbreviation, or no characters */
520 #include "_PDCLIB_test.h"
525 /* Replace with a call to mktime() once that is implemented. */
526 struct tm timeptr = { 59, 30, 12, 1, 9, 72, 0, 274, -1 };
528 TESTCASE( strftime( buffer, 100, "%a ", &timeptr ) == 4 );
529 TESTCASE( strcmp( buffer, "Sun " ) == 0 );
530 TESTCASE( strftime( buffer, 100, "%A ", &timeptr ) == 7 );
531 TESTCASE( strcmp( buffer, "Sunday " ) == 0 );
532 TESTCASE( strftime( buffer, 100, "%b ", &timeptr ) == 4 );
533 TESTCASE( strcmp( buffer, "Oct " ) == 0 );
534 TESTCASE( strftime( buffer, 100, "%h ", &timeptr ) == 4 );
535 TESTCASE( strcmp( buffer, "Oct " ) == 0 );
536 TESTCASE( strftime( buffer, 100, "%B ", &timeptr ) == 8 );
537 TESTCASE( strcmp( buffer, "October " ) == 0 );
538 TESTCASE( strftime( buffer, 100, "%c ", &timeptr ) == 25 );
539 TESTCASE( strcmp( buffer, "Sun Oct 1 12:30:59 1972 " ) == 0 );
540 TESTCASE( strftime( buffer, 100, "%C ", &timeptr ) == 3 );
541 TESTCASE( strcmp( buffer, "19 " ) == 0 );
542 TESTCASE( strftime( buffer, 100, "%d ", &timeptr ) == 3 );
543 TESTCASE( strcmp( buffer, "01 " ) == 0 );
544 TESTCASE( strftime( buffer, 100, "%D ", &timeptr ) == 9 );
545 TESTCASE( strcmp( buffer, "10/01/72 " ) == 0 );
546 TESTCASE( strftime( buffer, 100, "%e ", &timeptr ) == 3 );
547 TESTCASE( strcmp( buffer, " 1 " ) == 0 );
548 TESTCASE( strftime( buffer, 100, "%F ", &timeptr ) == 11 );
549 TESTCASE( strcmp( buffer, "1972-10-01 " ) == 0 );
550 TESTCASE( strftime( buffer, 100, "%H ", &timeptr ) == 3 );
551 TESTCASE( strcmp( buffer, "12 " ) == 0 );
552 TESTCASE( strftime( buffer, 100, "%I ", &timeptr ) == 3 );
553 TESTCASE( strcmp( buffer, "01 " ) == 0 );
554 TESTCASE( strftime( buffer, 100, "%j ", &timeptr ) == 4 );
555 TESTCASE( strcmp( buffer, "274 " ) == 0 );
556 TESTCASE( strftime( buffer, 100, "%m ", &timeptr ) == 3 );
557 TESTCASE( strcmp( buffer, "10 " ) == 0 );
558 TESTCASE( strftime( buffer, 100, "%M ", &timeptr ) == 3 );
559 TESTCASE( strcmp( buffer, "30 " ) == 0 );
560 TESTCASE( strftime( buffer, 100, "%p ", &timeptr ) == 3 );
561 TESTCASE( strcmp( buffer, "PM " ) == 0 );
562 TESTCASE( strftime( buffer, 100, "%r ", &timeptr ) == 12 );
563 TESTCASE( strcmp( buffer, "01:30:59 PM " ) == 0 );
564 TESTCASE( strftime( buffer, 100, "%R ", &timeptr ) == 6 );
565 TESTCASE( strcmp( buffer, "12:30 " ) == 0 );
566 TESTCASE( strftime( buffer, 100, "%S ", &timeptr ) == 3 );
567 TESTCASE( strcmp( buffer, "59 " ) == 0 );
568 TESTCASE( strftime( buffer, 100, "%T ", &timeptr ) == 9 );
569 TESTCASE( strcmp( buffer, "12:30:59 " ) == 0 );
570 TESTCASE( strftime( buffer, 100, "%u ", &timeptr ) == 2 );
571 TESTCASE( strcmp( buffer, "7 " ) == 0 );
572 TESTCASE( strftime( buffer, 100, "%w ", &timeptr ) == 2 );
573 TESTCASE( strcmp( buffer, "0 " ) == 0 );
574 TESTCASE( strftime( buffer, 100, "%x ", &timeptr ) == 9 );
575 TESTCASE( strcmp( buffer, "10/01/72 " ) == 0 );
576 TESTCASE( strftime( buffer, 100, "%X ", &timeptr ) == 9 );
577 TESTCASE( strcmp( buffer, "12:30:59 " ) == 0 );
578 TESTCASE( strftime( buffer, 100, "%y ", &timeptr ) == 3 );
579 TESTCASE( strcmp( buffer, "72 " ) == 0 );
580 TESTCASE( strftime( buffer, 100, "%Y ", &timeptr ) == 5 );
581 TESTCASE( strcmp( buffer, "1972 " ) == 0 );
582 TESTCASE( strftime( buffer, 100, "%% ", &timeptr ) == 2 );
583 TESTCASE( strcmp( buffer, "% " ) == 0 );
584 TESTCASE( strftime( buffer, 100, "%n ", &timeptr ) == 2 );
585 TESTCASE( strcmp( buffer, "\n " ) == 0 );
586 TESTCASE( strftime( buffer, 100, "%t ", &timeptr ) == 2 );
587 TESTCASE( strcmp( buffer, "\t " ) == 0 );