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 static int iso_week( const struct tm * timeptr )
22 /* calculations below rely on Sunday == 7 */
23 int wday = timeptr->tm_wday;
28 /* https://en.wikipedia.org/wiki/ISO_week_date */
29 int week = ( timeptr->tm_yday - wday + 11 ) / 7;
32 /* date *may* belong to the *next* year, if:
33 * it is 31.12. and Monday - Wednesday
34 * it is 30.12. and Monday - Tuesday
35 * it is 29.12. and Monday
36 We can safely assume December...
38 if ( ( timeptr->tm_mday - wday ) > 27 )
45 /* date *does* belong to *previous* year,
46 i.e. has week 52 *unless*...
47 * current year started on a Friday, or
48 * previous year is leap and this year
49 started on a Saturday.
51 int firstday = timeptr->tm_wday - ( timeptr->tm_yday % 7 );
56 if ( ( firstday == 5 ) || ( _PDCLIB_is_leap( timeptr->tm_year - 1 ) && firstday == 6 ) )
68 static int sprints( char * _PDCLIB_restrict dest, const char * _PDCLIB_restrict src, size_t maxsize, size_t * rc )
70 size_t len = strlen( src );
71 if ( *rc < ( maxsize - len ) )
73 strcpy( dest + *rc, src );
83 size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB_restrict format, const struct tm * _PDCLIB_restrict timeptr )
87 while ( rc < maxsize )
91 if ( ( s[rc] = *format++ ) == '\0' )
107 /* flag = *format++; */
117 /* tm_wday abbreviated */
118 if ( ! sprints( s, _PDCLIB_lconv.day_name_abbr[ timeptr->tm_wday ], maxsize, &rc ) )
127 if ( ! sprints( s, _PDCLIB_lconv.day_name_full[ timeptr->tm_wday ], maxsize, &rc ) )
136 /* tm_mon abbreviated */
137 if ( ! sprints( s, _PDCLIB_lconv.month_name_abbr[ timeptr->tm_mon ], maxsize, &rc ) )
146 if ( ! sprints( s, _PDCLIB_lconv.month_name_full[ timeptr->tm_mon ], maxsize, &rc ) )
154 /* locale's date / time representation, %a %b %e %T %Y for C locale */
155 /* 'E' for locale's alternative representation */
156 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.date_time_format, timeptr );
169 /* tm_year divided by 100, truncated to decimal (00-99) */
170 /* 'E' for base year (period) in locale's alternative representation */
171 if ( rc < ( maxsize - 2 ) )
173 div_t period = div( ( ( timeptr->tm_year + 1900 ) / 100 ), 10 );
174 s[rc++] = '0' + period.quot;
175 s[rc++] = '0' + period.rem;
185 /* tm_mday as decimal (01-31) */
186 /* 'O' for locale's alternative numeric symbols */
187 if ( rc < ( maxsize - 2 ) )
189 div_t day = div( timeptr->tm_mday, 10 );
190 s[rc++] = '0' + day.quot;
191 s[rc++] = '0' + day.rem;
202 size_t count = strftime( s + rc, maxsize - rc, "%m/%d/%y", timeptr );
215 /* tm_mday as decimal ( 1-31) */
216 /* 'O' for locale's alternative numeric symbols */
217 if ( rc < ( maxsize - 2 ) )
219 div_t day = div( timeptr->tm_mday, 10 );
220 s[rc++] = ( day.quot > 0 ) ? '0' + day.quot : ' ';
221 s[rc++] = '0' + day.rem;
232 size_t count = strftime( s + rc, maxsize - rc, "%Y-%m-%d", timeptr );
245 /* last 2 digits of the week-based year as decimal (00-99) */
246 if ( rc < ( maxsize - 2 ) )
248 int week = iso_week( timeptr );
250 if ( week >= 52 && timeptr->tm_mon == 0 )
254 else if ( week == 1 && timeptr->tm_mon == 11 )
258 div_t year = div( timeptr->tm_year % 100 + bias, 10 );
259 s[rc++] = '0' + year.quot;
260 s[rc++] = '0' + year.rem;
270 /* week-based year as decimal (e.g. 1997) */
271 if ( rc < ( maxsize - 4 ) )
273 int week = iso_week( timeptr );
274 int year = timeptr->tm_year + 1900;
275 if ( week >= 52 && timeptr->tm_mon == 0 )
279 else if ( week == 1 && timeptr->tm_mon == 11 )
283 for ( int i = 3; i >= 0; --i )
285 div_t digit = div( year, 10 );
286 s[ rc + i ] = '0' + digit.rem;
300 /* tm_hour as 24h decimal (00-23) */
301 /* 'O' for locale's alternative numeric symbols */
302 if ( rc < ( maxsize - 2 ) )
304 div_t hour = div( timeptr->tm_hour, 10 );
305 s[rc++] = '0' + hour.quot;
306 s[rc++] = '0' + hour.rem;
316 /* tm_hour as 12h decimal (01-12) */
317 /* 'O' for locale's alternative numeric symbols */
318 if ( rc < ( maxsize - 2 ) )
320 div_t hour = div( ( timeptr->tm_hour + 11 ) % 12 + 1, 10 );
321 s[rc++] = '0' + hour.quot;
322 s[rc++] = '0' + hour.rem;
332 /* tm_yday as decimal (001-366) */
333 if ( rc < ( maxsize - 3 ) )
335 div_t yday = div( timeptr->tm_yday + 1, 100 );
336 s[rc++] = '0' + yday.quot;
337 s[rc++] = '0' + yday.rem / 10;
338 s[rc++] = '0' + yday.rem % 10;
348 /* tm_mon as decimal (01-12) */
349 /* 'O' for locale's alternative numeric symbols */
350 if ( rc < ( maxsize - 2 ) )
352 div_t mon = div( timeptr->tm_mon + 1, 10 );
353 s[rc++] = '0' + mon.quot;
354 s[rc++] = '0' + mon.rem;
364 /* tm_min as decimal (00-59) */
365 /* 'O' for locale's alternative numeric symbols */
366 if ( rc < ( maxsize - 2 ) )
368 div_t min = div( timeptr->tm_min, 10 );
369 s[rc++] = '0' + min.quot;
370 s[rc++] = '0' + min.rem;
386 /* tm_hour locale's AM/PM designations */
387 const char * designation = _PDCLIB_lconv.am_pm[ timeptr->tm_hour > 11 ];
388 size_t len = strlen( designation );
389 if ( rc < ( maxsize - len ) )
391 strcpy( s + rc, designation );
402 /* tm_hour / tm_min / tm_sec as locale's 12-hour clock time, %I:%M:%S %p for C locale */
403 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.time_format_12h, timeptr );
417 size_t count = strftime( s + rc, maxsize - rc, "%H:%M", timeptr );
430 /* tm_sec as decimal (00-60) */
431 /* 'O' for locale's alternative numeric symbols */
432 if ( rc < ( maxsize - 2 ) )
434 div_t sec = div( timeptr->tm_sec, 10 );
435 s[rc++] = '0' + sec.quot;
436 s[rc++] = '0' + sec.rem;
453 size_t count = strftime( s + rc, maxsize - rc, "%H:%M:%S", timeptr );
466 /* tm_wday as decimal (1-7) with Monday == 1 */
467 /* 'O' for locale's alternative numeric symbols */
468 s[rc++] = ( timeptr->tm_wday == 0 ) ? '7' : '0' + timeptr->tm_wday;
473 /* week number of the year (first Sunday as the first day of week 1) as decimal (00-53) */
474 /* 'O' for locale's alternative numeric symbols */
480 /* ISO week number as decimal (01-53) */
481 /* 'O' for locale's alternative numeric symbols */
482 if ( rc < ( maxsize - 2 ) )
484 div_t week = div( iso_week( timeptr ), 10 );
485 s[rc++] = '0' + week.quot;
486 s[rc++] = '0' + week.rem;
496 /* tm_wday as decimal number (0-6) with Sunday == 0 */
497 /* 'O' for locale's alternative numeric symbols */
498 s[rc++] = '0' + timeptr->tm_wday;
503 /* week number of the year (first Monday as the first day of week 1) as decimal (00-53) */
504 /* 'O' for locale's alternative numeric symbols */
510 /* locale's date representation, %m/%d/%y for C locale */
511 /* 'E' for locale's alternative representation */
512 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.date_format, timeptr );
525 /* locale's time representation, %T for C locale */
526 /* 'E' for locale's alternative representation */
527 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.time_format, timeptr );
540 /* last 2 digits of tm_year as decimal (00-99) */
541 /* 'E' for offset from %EC (year only) in locale's alternative representation */
542 /* 'O' for locale's alternative numeric symbols */
543 if ( rc < ( maxsize - 2 ) )
545 div_t year = div( ( timeptr->tm_year % 100 ), 10 );
546 s[rc++] = '0' + year.quot;
547 s[rc++] = '0' + year.rem;
557 /* tm_year as decimal (e.g. 1997) */
558 /* 'E' for locale's alternative representation */
559 if ( rc < ( maxsize - 4 ) )
561 int year = timeptr->tm_year + 1900;
563 for ( int i = 3; i >= 0; --i )
565 div_t digit = div( year, 10 );
566 s[ rc + i ] = '0' + digit.rem;
580 /* tm_isdst / UTC offset in ISO8601 format (e.g. -0430 meaning 4 hours 30 minutes behind Greenwich), or no characters */
586 /* tm_isdst / locale's time zone name or abbreviation, or no characters */
607 #include "_PDCLIB_test.h"
609 #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;
614 /* Basic functionality */
616 MKTIME( timeptr, 59, 30, 12, 1, 9, 72, 0, 274 );
617 TESTCASE( strftime( buffer, 100, "%a ", &timeptr ) == 4 );
618 TESTCASE( strcmp( buffer, "Sun " ) == 0 );
619 TESTCASE( strftime( buffer, 100, "%A ", &timeptr ) == 7 );
620 TESTCASE( strcmp( buffer, "Sunday " ) == 0 );
621 TESTCASE( strftime( buffer, 100, "%b ", &timeptr ) == 4 );
622 TESTCASE( strcmp( buffer, "Oct " ) == 0 );
623 TESTCASE( strftime( buffer, 100, "%h ", &timeptr ) == 4 );
624 TESTCASE( strcmp( buffer, "Oct " ) == 0 );
625 TESTCASE( strftime( buffer, 100, "%B ", &timeptr ) == 8 );
626 TESTCASE( strcmp( buffer, "October " ) == 0 );
627 TESTCASE( strftime( buffer, 100, "%c ", &timeptr ) == 25 );
628 TESTCASE( strcmp( buffer, "Sun Oct 1 12:30:59 1972 " ) == 0 );
629 TESTCASE( strftime( buffer, 100, "%C ", &timeptr ) == 3 );
630 TESTCASE( strcmp( buffer, "19 " ) == 0 );
631 TESTCASE( strftime( buffer, 100, "%d ", &timeptr ) == 3 );
632 TESTCASE( strcmp( buffer, "01 " ) == 0 );
633 TESTCASE( strftime( buffer, 100, "%D ", &timeptr ) == 9 );
634 TESTCASE( strcmp( buffer, "10/01/72 " ) == 0 );
635 TESTCASE( strftime( buffer, 100, "%e ", &timeptr ) == 3 );
636 TESTCASE( strcmp( buffer, " 1 " ) == 0 );
637 TESTCASE( strftime( buffer, 100, "%F ", &timeptr ) == 11 );
638 TESTCASE( strcmp( buffer, "1972-10-01 " ) == 0 );
639 TESTCASE( strftime( buffer, 100, "%H ", &timeptr ) == 3 );
640 TESTCASE( strcmp( buffer, "12 " ) == 0 );
641 TESTCASE( strftime( buffer, 100, "%I ", &timeptr ) == 3 );
642 TESTCASE( strcmp( buffer, "12 " ) == 0 );
643 TESTCASE( strftime( buffer, 100, "%j ", &timeptr ) == 4 );
644 TESTCASE( strcmp( buffer, "275 " ) == 0 );
645 TESTCASE( strftime( buffer, 100, "%m ", &timeptr ) == 3 );
646 TESTCASE( strcmp( buffer, "10 " ) == 0 );
647 TESTCASE( strftime( buffer, 100, "%M ", &timeptr ) == 3 );
648 TESTCASE( strcmp( buffer, "30 " ) == 0 );
649 TESTCASE( strftime( buffer, 100, "%p ", &timeptr ) == 3 );
650 TESTCASE( strcmp( buffer, "PM " ) == 0 );
651 TESTCASE( strftime( buffer, 100, "%r ", &timeptr ) == 12 );
652 TESTCASE( strcmp( buffer, "12:30:59 PM " ) == 0 );
653 TESTCASE( strftime( buffer, 100, "%R ", &timeptr ) == 6 );
654 TESTCASE( strcmp( buffer, "12:30 " ) == 0 );
655 TESTCASE( strftime( buffer, 100, "%S ", &timeptr ) == 3 );
656 TESTCASE( strcmp( buffer, "59 " ) == 0 );
657 TESTCASE( strftime( buffer, 100, "%T ", &timeptr ) == 9 );
658 TESTCASE( strcmp( buffer, "12:30:59 " ) == 0 );
659 TESTCASE( strftime( buffer, 100, "%u ", &timeptr ) == 2 );
660 TESTCASE( strcmp( buffer, "7 " ) == 0 );
661 TESTCASE( strftime( buffer, 100, "%w ", &timeptr ) == 2 );
662 TESTCASE( strcmp( buffer, "0 " ) == 0 );
663 TESTCASE( strftime( buffer, 100, "%x ", &timeptr ) == 9 );
664 TESTCASE( strcmp( buffer, "10/01/72 " ) == 0 );
665 TESTCASE( strftime( buffer, 100, "%X ", &timeptr ) == 9 );
666 TESTCASE( strcmp( buffer, "12:30:59 " ) == 0 );
667 TESTCASE( strftime( buffer, 100, "%y ", &timeptr ) == 3 );
668 TESTCASE( strcmp( buffer, "72 " ) == 0 );
669 TESTCASE( strftime( buffer, 100, "%Y ", &timeptr ) == 5 );
670 TESTCASE( strcmp( buffer, "1972 " ) == 0 );
671 TESTCASE( strftime( buffer, 100, "%% ", &timeptr ) == 2 );
672 TESTCASE( strcmp( buffer, "% " ) == 0 );
673 TESTCASE( strftime( buffer, 100, "%n ", &timeptr ) == 2 );
674 TESTCASE( strcmp( buffer, "\n " ) == 0 );
675 TESTCASE( strftime( buffer, 100, "%t ", &timeptr ) == 2 );
676 TESTCASE( strcmp( buffer, "\t " ) == 0 );
677 /* ISO week calculation */
678 MKTIME( timeptr, 0, 0, 0, 27, 11, 3, 0, 360 );
679 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
680 TESTCASE( strcmp( buffer, "52 " ) == 0 );
681 MKTIME( timeptr, 0, 0, 0, 28, 11, 3, 1, 361 );
682 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
683 TESTCASE( strcmp( buffer, "53 " ) == 0 );
684 MKTIME( timeptr, 0, 0, 0, 31, 11, 3, 4, 364 );
685 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
686 TESTCASE( strcmp( buffer, "53 " ) == 0 );
687 MKTIME( timeptr, 0, 0, 0, 1, 0, 4, 5, 0 );
688 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
689 TESTCASE( strcmp( buffer, "53 " ) == 0 );
690 MKTIME( timeptr, 0, 0, 0, 3, 0, 4, 0, 2 );
691 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
692 TESTCASE( strcmp( buffer, "53 " ) == 0 );
693 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
694 TESTCASE( strcmp( buffer, "03 " ) == 0 );
695 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
696 TESTCASE( strcmp( buffer, "1903 " ) == 0 );
697 MKTIME( timeptr, 0, 0, 0, 4, 0, 4, 1, 3 );
698 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
699 TESTCASE( strcmp( buffer, "01 " ) == 0 );
700 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
701 TESTCASE( strcmp( buffer, "04 " ) == 0 );
702 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
703 TESTCASE( strcmp( buffer, "1904 " ) == 0 );
704 MKTIME( timeptr, 0, 0, 0, 1, 0, 5, 0, 0 );
705 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
706 TESTCASE( strcmp( buffer, "52 " ) == 0 );
707 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
708 TESTCASE( strcmp( buffer, "04 " ) == 0 );
709 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
710 TESTCASE( strcmp( buffer, "1904 " ) == 0 );
711 MKTIME( timeptr, 0, 0, 0, 24, 11, 100, 0, 358 );
712 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
713 TESTCASE( strcmp( buffer, "51 " ) == 0 );
714 MKTIME( timeptr, 0, 0, 0, 25, 11, 100, 1, 359 );
715 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
716 TESTCASE( strcmp( buffer, "52 " ) == 0 );
717 MKTIME( timeptr, 0, 0, 0, 31, 11, 100, 0, 365 );
718 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
719 TESTCASE( strcmp( buffer, "52 " ) == 0 );
720 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
721 TESTCASE( strcmp( buffer, "00 " ) == 0 );
722 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
723 TESTCASE( strcmp( buffer, "2000 " ) == 0 );
724 MKTIME( timeptr, 0, 0, 0, 1, 0, 101, 1, 0 );
725 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
726 TESTCASE( strcmp( buffer, "01 " ) == 0 );
727 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
728 TESTCASE( strcmp( buffer, "01 " ) == 0 );
729 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
730 TESTCASE( strcmp( buffer, "2001 " ) == 0 );
731 MKTIME( timeptr, 0, 0, 0, 7, 0, 101, 7, 6 );
732 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
733 TESTCASE( strcmp( buffer, "01 " ) == 0 );
734 MKTIME( timeptr, 0, 0, 0, 8, 0, 101, 1, 7 );
735 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
736 TESTCASE( strcmp( buffer, "02 " ) == 0 );