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.
28 static int weeknr( const struct tm * timeptr, int wstart )
30 int wday = ( timeptr->tm_wday + 7 - wstart ) % 7;
31 div_t week = div( timeptr->tm_yday, 7 );
32 if ( week.rem > wday )
39 static int iso_week( const struct tm * timeptr )
41 /* calculations below rely on Sunday == 7 */
42 int wday = timeptr->tm_wday;
47 /* https://en.wikipedia.org/wiki/ISO_week_date */
48 int week = ( timeptr->tm_yday - wday + 11 ) / 7;
51 /* date *may* belong to the *next* year, if:
52 * it is 31.12. and Monday - Wednesday
53 * it is 30.12. and Monday - Tuesday
54 * it is 29.12. and Monday
55 We can safely assume December...
57 if ( ( timeptr->tm_mday - wday ) > 27 )
64 /* date *does* belong to *previous* year,
65 i.e. has week 52 *unless*...
66 * current year started on a Friday, or
67 * previous year is leap and this year
68 started on a Saturday.
70 int firstday = timeptr->tm_wday - ( timeptr->tm_yday % 7 );
75 if ( ( firstday == 5 ) || ( _PDCLIB_is_leap( timeptr->tm_year - 1 ) && firstday == 6 ) )
87 static int sprints( char * _PDCLIB_restrict dest, const char * _PDCLIB_restrict src, size_t maxsize, size_t * rc )
89 size_t len = strlen( src );
90 if ( *rc < ( maxsize - len ) )
92 strcpy( dest + *rc, src );
102 size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB_restrict format, const struct tm * _PDCLIB_restrict timeptr )
106 while ( rc < maxsize )
108 if ( *format != '%' )
110 if ( ( s[rc] = *format++ ) == '\0' )
126 /* flag = *format++; */
136 /* tm_wday abbreviated */
137 if ( ! sprints( s, _PDCLIB_lconv.day_name_abbr[ timeptr->tm_wday ], maxsize, &rc ) )
146 if ( ! sprints( s, _PDCLIB_lconv.day_name_full[ timeptr->tm_wday ], maxsize, &rc ) )
155 /* tm_mon abbreviated */
156 if ( ! sprints( s, _PDCLIB_lconv.month_name_abbr[ timeptr->tm_mon ], maxsize, &rc ) )
165 if ( ! sprints( s, _PDCLIB_lconv.month_name_full[ timeptr->tm_mon ], maxsize, &rc ) )
173 /* locale's date / time representation, %a %b %e %T %Y for C locale */
174 /* 'E' for locale's alternative representation */
175 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.date_time_format, timeptr );
188 /* tm_year divided by 100, truncated to decimal (00-99) */
189 /* 'E' for base year (period) in locale's alternative representation */
190 if ( rc < ( maxsize - 2 ) )
192 div_t period = div( ( ( timeptr->tm_year + 1900 ) / 100 ), 10 );
193 s[rc++] = '0' + period.quot;
194 s[rc++] = '0' + period.rem;
204 /* tm_mday as decimal (01-31) */
205 /* 'O' for locale's alternative numeric symbols */
206 if ( rc < ( maxsize - 2 ) )
208 div_t day = div( timeptr->tm_mday, 10 );
209 s[rc++] = '0' + day.quot;
210 s[rc++] = '0' + day.rem;
221 size_t count = strftime( s + rc, maxsize - rc, "%m/%d/%y", timeptr );
234 /* tm_mday as decimal ( 1-31) */
235 /* 'O' for locale's alternative numeric symbols */
236 if ( rc < ( maxsize - 2 ) )
238 div_t day = div( timeptr->tm_mday, 10 );
239 s[rc++] = ( day.quot > 0 ) ? '0' + day.quot : ' ';
240 s[rc++] = '0' + day.rem;
251 size_t count = strftime( s + rc, maxsize - rc, "%Y-%m-%d", timeptr );
264 /* last 2 digits of the week-based year as decimal (00-99) */
265 if ( rc < ( maxsize - 2 ) )
267 int week = iso_week( timeptr );
269 if ( week >= 52 && timeptr->tm_mon == 0 )
273 else if ( week == 1 && timeptr->tm_mon == 11 )
277 div_t year = div( timeptr->tm_year % 100 + bias, 10 );
278 s[rc++] = '0' + year.quot;
279 s[rc++] = '0' + year.rem;
289 /* week-based year as decimal (e.g. 1997) */
290 if ( rc < ( maxsize - 4 ) )
292 int week = iso_week( timeptr );
293 int year = timeptr->tm_year + 1900;
294 if ( week >= 52 && timeptr->tm_mon == 0 )
298 else if ( week == 1 && timeptr->tm_mon == 11 )
302 for ( int i = 3; i >= 0; --i )
304 div_t digit = div( year, 10 );
305 s[ rc + i ] = '0' + digit.rem;
319 /* tm_hour as 24h decimal (00-23) */
320 /* 'O' for locale's alternative numeric symbols */
321 if ( rc < ( maxsize - 2 ) )
323 div_t hour = div( timeptr->tm_hour, 10 );
324 s[rc++] = '0' + hour.quot;
325 s[rc++] = '0' + hour.rem;
335 /* tm_hour as 12h decimal (01-12) */
336 /* 'O' for locale's alternative numeric symbols */
337 if ( rc < ( maxsize - 2 ) )
339 div_t hour = div( ( timeptr->tm_hour + 11 ) % 12 + 1, 10 );
340 s[rc++] = '0' + hour.quot;
341 s[rc++] = '0' + hour.rem;
351 /* tm_yday as decimal (001-366) */
352 if ( rc < ( maxsize - 3 ) )
354 div_t yday = div( timeptr->tm_yday + 1, 100 );
355 s[rc++] = '0' + yday.quot;
356 s[rc++] = '0' + yday.rem / 10;
357 s[rc++] = '0' + yday.rem % 10;
367 /* tm_mon as decimal (01-12) */
368 /* 'O' for locale's alternative numeric symbols */
369 if ( rc < ( maxsize - 2 ) )
371 div_t mon = div( timeptr->tm_mon + 1, 10 );
372 s[rc++] = '0' + mon.quot;
373 s[rc++] = '0' + mon.rem;
383 /* tm_min as decimal (00-59) */
384 /* 'O' for locale's alternative numeric symbols */
385 if ( rc < ( maxsize - 2 ) )
387 div_t min = div( timeptr->tm_min, 10 );
388 s[rc++] = '0' + min.quot;
389 s[rc++] = '0' + min.rem;
405 /* tm_hour locale's AM/PM designations */
406 const char * designation = _PDCLIB_lconv.am_pm[ timeptr->tm_hour > 11 ];
407 size_t len = strlen( designation );
408 if ( rc < ( maxsize - len ) )
410 strcpy( s + rc, designation );
421 /* tm_hour / tm_min / tm_sec as locale's 12-hour clock time, %I:%M:%S %p for C locale */
422 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.time_format_12h, timeptr );
436 size_t count = strftime( s + rc, maxsize - rc, "%H:%M", timeptr );
449 /* tm_sec as decimal (00-60) */
450 /* 'O' for locale's alternative numeric symbols */
451 if ( rc < ( maxsize - 2 ) )
453 div_t sec = div( timeptr->tm_sec, 10 );
454 s[rc++] = '0' + sec.quot;
455 s[rc++] = '0' + sec.rem;
472 size_t count = strftime( s + rc, maxsize - rc, "%H:%M:%S", timeptr );
485 /* tm_wday as decimal (1-7) with Monday == 1 */
486 /* 'O' for locale's alternative numeric symbols */
487 s[rc++] = ( timeptr->tm_wday == 0 ) ? '7' : '0' + timeptr->tm_wday;
492 /* week number of the year (first Sunday as the first day of week 1) as decimal (00-53) */
493 /* 'O' for locale's alternative numeric symbols */
494 if ( rc < ( maxsize - 2 ) )
496 div_t week = div( weeknr( timeptr, E_SUNDAY ), 10 );
497 s[rc++] = '0' + week.quot;
498 s[rc++] = '0' + week.rem;
508 /* ISO week number as decimal (01-53) */
509 /* 'O' for locale's alternative numeric symbols */
510 if ( rc < ( maxsize - 2 ) )
512 div_t week = div( iso_week( timeptr ), 10 );
513 s[rc++] = '0' + week.quot;
514 s[rc++] = '0' + week.rem;
524 /* tm_wday as decimal number (0-6) with Sunday == 0 */
525 /* 'O' for locale's alternative numeric symbols */
526 s[rc++] = '0' + timeptr->tm_wday;
531 /* week number of the year (first Monday as the first day of week 1) as decimal (00-53) */
532 /* 'O' for locale's alternative numeric symbols */
533 if ( rc < ( maxsize - 2 ) )
535 div_t week = div( weeknr( timeptr, E_MONDAY ), 10 );
536 s[rc++] = '0' + week.quot;
537 s[rc++] = '0' + week.rem;
547 /* locale's date representation, %m/%d/%y for C locale */
548 /* 'E' for locale's alternative representation */
549 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.date_format, timeptr );
562 /* locale's time representation, %T for C locale */
563 /* 'E' for locale's alternative representation */
564 size_t count = strftime( s + rc, maxsize - rc, _PDCLIB_lconv.time_format, timeptr );
577 /* last 2 digits of tm_year as decimal (00-99) */
578 /* 'E' for offset from %EC (year only) in locale's alternative representation */
579 /* 'O' for locale's alternative numeric symbols */
580 if ( rc < ( maxsize - 2 ) )
582 div_t year = div( ( timeptr->tm_year % 100 ), 10 );
583 s[rc++] = '0' + year.quot;
584 s[rc++] = '0' + year.rem;
594 /* tm_year as decimal (e.g. 1997) */
595 /* 'E' for locale's alternative representation */
596 if ( rc < ( maxsize - 4 ) )
598 int year = timeptr->tm_year + 1900;
600 for ( int i = 3; i >= 0; --i )
602 div_t digit = div( year, 10 );
603 s[ rc + i ] = '0' + digit.rem;
617 /* tm_isdst / UTC offset in ISO8601 format (e.g. -0430 meaning 4 hours 30 minutes behind Greenwich), or no characters */
623 /* tm_isdst / locale's time zone name or abbreviation, or no characters */
644 #include "_PDCLIB_test.h"
646 #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;
651 /* Basic functionality */
653 MKTIME( timeptr, 59, 30, 12, 1, 9, 72, 0, 274 );
654 TESTCASE( strftime( buffer, 100, "%a ", &timeptr ) == 4 );
655 TESTCASE( strcmp( buffer, "Sun " ) == 0 );
656 TESTCASE( strftime( buffer, 100, "%A ", &timeptr ) == 7 );
657 TESTCASE( strcmp( buffer, "Sunday " ) == 0 );
658 TESTCASE( strftime( buffer, 100, "%b ", &timeptr ) == 4 );
659 TESTCASE( strcmp( buffer, "Oct " ) == 0 );
660 TESTCASE( strftime( buffer, 100, "%h ", &timeptr ) == 4 );
661 TESTCASE( strcmp( buffer, "Oct " ) == 0 );
662 TESTCASE( strftime( buffer, 100, "%B ", &timeptr ) == 8 );
663 TESTCASE( strcmp( buffer, "October " ) == 0 );
664 TESTCASE( strftime( buffer, 100, "%c ", &timeptr ) == 25 );
665 TESTCASE( strcmp( buffer, "Sun Oct 1 12:30:59 1972 " ) == 0 );
666 TESTCASE( strftime( buffer, 100, "%C ", &timeptr ) == 3 );
667 TESTCASE( strcmp( buffer, "19 " ) == 0 );
668 TESTCASE( strftime( buffer, 100, "%d ", &timeptr ) == 3 );
669 TESTCASE( strcmp( buffer, "01 " ) == 0 );
670 TESTCASE( strftime( buffer, 100, "%D ", &timeptr ) == 9 );
671 TESTCASE( strcmp( buffer, "10/01/72 " ) == 0 );
672 TESTCASE( strftime( buffer, 100, "%e ", &timeptr ) == 3 );
673 TESTCASE( strcmp( buffer, " 1 " ) == 0 );
674 TESTCASE( strftime( buffer, 100, "%F ", &timeptr ) == 11 );
675 TESTCASE( strcmp( buffer, "1972-10-01 " ) == 0 );
676 TESTCASE( strftime( buffer, 100, "%H ", &timeptr ) == 3 );
677 TESTCASE( strcmp( buffer, "12 " ) == 0 );
678 TESTCASE( strftime( buffer, 100, "%I ", &timeptr ) == 3 );
679 TESTCASE( strcmp( buffer, "12 " ) == 0 );
680 TESTCASE( strftime( buffer, 100, "%j ", &timeptr ) == 4 );
681 TESTCASE( strcmp( buffer, "275 " ) == 0 );
682 TESTCASE( strftime( buffer, 100, "%m ", &timeptr ) == 3 );
683 TESTCASE( strcmp( buffer, "10 " ) == 0 );
684 TESTCASE( strftime( buffer, 100, "%M ", &timeptr ) == 3 );
685 TESTCASE( strcmp( buffer, "30 " ) == 0 );
686 TESTCASE( strftime( buffer, 100, "%p ", &timeptr ) == 3 );
687 TESTCASE( strcmp( buffer, "PM " ) == 0 );
688 TESTCASE( strftime( buffer, 100, "%r ", &timeptr ) == 12 );
689 TESTCASE( strcmp( buffer, "12:30:59 PM " ) == 0 );
690 TESTCASE( strftime( buffer, 100, "%R ", &timeptr ) == 6 );
691 TESTCASE( strcmp( buffer, "12:30 " ) == 0 );
692 TESTCASE( strftime( buffer, 100, "%S ", &timeptr ) == 3 );
693 TESTCASE( strcmp( buffer, "59 " ) == 0 );
694 TESTCASE( strftime( buffer, 100, "%T ", &timeptr ) == 9 );
695 TESTCASE( strcmp( buffer, "12:30:59 " ) == 0 );
696 TESTCASE( strftime( buffer, 100, "%u ", &timeptr ) == 2 );
697 TESTCASE( strcmp( buffer, "7 " ) == 0 );
698 TESTCASE( strftime( buffer, 100, "%w ", &timeptr ) == 2 );
699 TESTCASE( strcmp( buffer, "0 " ) == 0 );
700 TESTCASE( strftime( buffer, 100, "%x ", &timeptr ) == 9 );
701 TESTCASE( strcmp( buffer, "10/01/72 " ) == 0 );
702 TESTCASE( strftime( buffer, 100, "%X ", &timeptr ) == 9 );
703 TESTCASE( strcmp( buffer, "12:30:59 " ) == 0 );
704 TESTCASE( strftime( buffer, 100, "%y ", &timeptr ) == 3 );
705 TESTCASE( strcmp( buffer, "72 " ) == 0 );
706 TESTCASE( strftime( buffer, 100, "%Y ", &timeptr ) == 5 );
707 TESTCASE( strcmp( buffer, "1972 " ) == 0 );
708 TESTCASE( strftime( buffer, 100, "%% ", &timeptr ) == 2 );
709 TESTCASE( strcmp( buffer, "% " ) == 0 );
710 TESTCASE( strftime( buffer, 100, "%n ", &timeptr ) == 2 );
711 TESTCASE( strcmp( buffer, "\n " ) == 0 );
712 TESTCASE( strftime( buffer, 100, "%t ", &timeptr ) == 2 );
713 TESTCASE( strcmp( buffer, "\t " ) == 0 );
714 /* ISO week calculation */
715 MKTIME( timeptr, 0, 0, 0, 27, 11, 3, 0, 360 );
716 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
717 TESTCASE( strcmp( buffer, "52 " ) == 0 );
718 MKTIME( timeptr, 0, 0, 0, 28, 11, 3, 1, 361 );
719 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
720 TESTCASE( strcmp( buffer, "53 " ) == 0 );
721 MKTIME( timeptr, 0, 0, 0, 31, 11, 3, 4, 364 );
722 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
723 TESTCASE( strcmp( buffer, "53 " ) == 0 );
724 MKTIME( timeptr, 0, 0, 0, 1, 0, 4, 5, 0 );
725 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
726 TESTCASE( strcmp( buffer, "53 " ) == 0 );
727 MKTIME( timeptr, 0, 0, 0, 3, 0, 4, 0, 2 );
728 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
729 TESTCASE( strcmp( buffer, "53 " ) == 0 );
730 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
731 TESTCASE( strcmp( buffer, "03 " ) == 0 );
732 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
733 TESTCASE( strcmp( buffer, "1903 " ) == 0 );
734 MKTIME( timeptr, 0, 0, 0, 4, 0, 4, 1, 3 );
735 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
736 TESTCASE( strcmp( buffer, "01 " ) == 0 );
737 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
738 TESTCASE( strcmp( buffer, "04 " ) == 0 );
739 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
740 TESTCASE( strcmp( buffer, "1904 " ) == 0 );
741 MKTIME( timeptr, 0, 0, 0, 1, 0, 5, 0, 0 );
742 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
743 TESTCASE( strcmp( buffer, "52 " ) == 0 );
744 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
745 TESTCASE( strcmp( buffer, "04 " ) == 0 );
746 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
747 TESTCASE( strcmp( buffer, "1904 " ) == 0 );
748 MKTIME( timeptr, 0, 0, 0, 24, 11, 100, 0, 358 );
749 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
750 TESTCASE( strcmp( buffer, "51 " ) == 0 );
751 MKTIME( timeptr, 0, 0, 0, 25, 11, 100, 1, 359 );
752 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
753 TESTCASE( strcmp( buffer, "52 " ) == 0 );
754 MKTIME( timeptr, 0, 0, 0, 31, 11, 100, 0, 365 );
755 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
756 TESTCASE( strcmp( buffer, "52 " ) == 0 );
757 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
758 TESTCASE( strcmp( buffer, "00 " ) == 0 );
759 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
760 TESTCASE( strcmp( buffer, "2000 " ) == 0 );
761 MKTIME( timeptr, 0, 0, 0, 1, 0, 101, 1, 0 );
762 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
763 TESTCASE( strcmp( buffer, "01 " ) == 0 );
764 TESTCASE( strftime( buffer, 100, "%g ", &timeptr ) == 3 );
765 TESTCASE( strcmp( buffer, "01 " ) == 0 );
766 TESTCASE( strftime( buffer, 100, "%G ", &timeptr ) == 5 );
767 TESTCASE( strcmp( buffer, "2001 " ) == 0 );
768 MKTIME( timeptr, 0, 0, 0, 7, 0, 101, 7, 6 );
769 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
770 TESTCASE( strcmp( buffer, "01 " ) == 0 );
771 MKTIME( timeptr, 0, 0, 0, 8, 0, 101, 1, 7 );
772 TESTCASE( strftime( buffer, 100, "%V ", &timeptr ) == 3 );
773 TESTCASE( strcmp( buffer, "02 " ) == 0 );
774 /* Sunday week calculation */
775 MKTIME( timeptr, 0, 0, 0, 2, 0, 116, 6, 1 );
776 TESTCASE( strftime( buffer, 100, "%U ", &timeptr ) == 3 );
777 TESTCASE( strcmp( buffer, "00 " ) == 0 );
778 MKTIME( timeptr, 0, 0, 0, 3, 0, 116, 0, 2 );
779 TESTCASE( strftime( buffer, 100, "%U ", &timeptr ) == 3 );
780 TESTCASE( strcmp( buffer, "01 " ) == 0 );
781 MKTIME( timeptr, 0, 0, 0, 31, 11, 116, 6, 365 );
782 TESTCASE( strftime( buffer, 100, "%U ", &timeptr ) == 3 );
783 TESTCASE( strcmp( buffer, "52 " ) == 0 );
784 MKTIME( timeptr, 0, 0, 0, 1, 0, 117, 0, 1 );
785 TESTCASE( strftime( buffer, 100, "%U ", &timeptr ) == 3 );
786 TESTCASE( strcmp( buffer, "01 " ) == 0 );
787 /* Monday week calculation */
788 MKTIME( timeptr, 0, 0, 0, 3, 0, 116, 0, 2 );
789 TESTCASE( strftime( buffer, 100, "%W ", &timeptr ) == 3 );
790 TESTCASE( strcmp( buffer, "00 " ) == 0 );
791 MKTIME( timeptr, 0, 0, 0, 4, 0, 116, 1, 3 );
792 TESTCASE( strftime( buffer, 100, "%W ", &timeptr ) == 3 );
793 TESTCASE( strcmp( buffer, "01 " ) == 0 );
794 MKTIME( timeptr, 0, 0, 0, 31, 11, 116, 6, 365 );
795 TESTCASE( strftime( buffer, 100, "%W ", &timeptr ) == 3 );
796 TESTCASE( strcmp( buffer, "52 " ) == 0 );
797 MKTIME( timeptr, 0, 0, 0, 1, 0, 117, 0, 1 );
798 TESTCASE( strftime( buffer, 100, "%W ", &timeptr ) == 3 );
799 TESTCASE( strcmp( buffer, "00 " ) == 0 );
800 MKTIME( timeptr, 0, 0, 0, 2, 0, 117, 1, 2 );
801 TESTCASE( strftime( buffer, 100, "%W ", &timeptr ) == 3 );
802 TESTCASE( strcmp( buffer, "01 " ) == 0 );