]> pd.if.org Git - pdclib/blob - functions/time/strftime.c
92e6062fe43ecb6a1007b5af6539905d82c42a5f
[pdclib] / functions / time / strftime.c
1 /* strftime( char * restrict, size_t, const char * restrict, const struct tm * restrict )
2
3    This file is part of the Public Domain C Library (PDCLib).
4    Permission is granted to use, modify, and / or redistribute at will.
5 */
6
7 #include <time.h>
8 #include <stdlib.h>
9 #include <locale.h>
10 #include <string.h>
11
12 #ifndef REGTEST
13
14 /* TODO: Alternative representations / numerals not supported. Multibyte support missing. */
15
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.
18 */
19
20 size_t strftime( char * _PDCLIB_restrict s, size_t maxsize, const char * _PDCLIB_restrict format, const struct tm * _PDCLIB_restrict timeptr )
21 {
22     size_t rc = 0;
23
24     while ( rc < maxsize )
25     {
26         if ( *format != '%' )
27         {
28             if ( ( s[rc] = *format++ ) == '\0' )
29             {
30                 return rc;
31             }
32             else
33             {
34                 ++rc;
35             }
36         }
37         else
38         {
39             /* char flag = 0; */
40             switch ( *++format )
41             {
42                 case 'E':
43                 case 'O':
44                     /* flag = *format++; */
45                     break;
46                 default:
47                     /* EMPTY */
48                     break;
49             }
50             switch( *format++ )
51             {
52                 case 'a':
53                     {
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 ) )
58                         {
59                             strcpy( s + rc, day );
60                             rc += len;
61                         }
62                         else
63                         {
64                             return 0;
65                         }
66                         break;
67                     }
68                 case 'A':
69                     {
70                         /* tm_wday full */
71                         const char * day = _PDCLIB_lconv.day_name_full[ timeptr->tm_wday ];
72                         size_t len = strlen( day );
73                         if ( rc < ( maxsize - len ) )
74                         {
75                             strcpy( s + rc, day );
76                             rc += len;
77                         }
78                         else
79                         {
80                             return 0;
81                         }
82                         break;
83                     }
84                 case 'b':
85                 case 'h':
86                     {
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 ) )
91                         {
92                             strcpy( s + rc, month );
93                             rc += len;
94                         }
95                         else
96                         {
97                             return 0;
98                         }
99                         break;
100                     }
101                 case 'B':
102                     {
103                         /* tm_mon full */
104                         const char * month = _PDCLIB_lconv.month_name_full[ timeptr->tm_mon ];
105                         size_t len = strlen( month );
106                         if ( rc < ( maxsize - len ) )
107                         {
108                             strcpy( s + rc, month );
109                             rc += len;
110                         }
111                         else
112                         {
113                             return 0;
114                         }
115                         break;
116                     }
117                 case 'c':
118                     {
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 );
122                         if ( count == 0 )
123                         {
124                             return 0;
125                         }
126                         else
127                         {
128                             rc += count;
129                         }
130                         break;
131                     }
132                 case 'C':
133                     {
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 ) )
137                         {
138                             div_t period = div( ( ( timeptr->tm_year + 1900 ) / 100 ), 10 );
139                             s[rc++] = '0' + period.quot;
140                             s[rc++] = '0' + period.rem;
141                         }
142                         else
143                         {
144                             return 0;
145                         }
146                         break;
147                     }
148                 case 'd':
149                     {
150                         /* tm_mday as decimal (01-31) */
151                         /* 'O' for locale's alternative numeric symbols */
152                         if ( rc < ( maxsize - 2 ) )
153                         {
154                             div_t day = div( timeptr->tm_mday, 10 );
155                             s[rc++] = '0' + day.quot;
156                             s[rc++] = '0' + day.rem;
157                         }
158                         else
159                         {
160                             return 0;
161                         }
162                         break;
163                     }
164                 case 'D':
165                     {
166                         /* %m/%d/%y */
167                         size_t count = strftime( s + rc, maxsize - rc, "%m/%d/%y", timeptr );
168                         if ( count == 0 )
169                         {
170                             return 0;
171                         }
172                         else
173                         {
174                             rc += count;
175                         }
176                         break;
177                     }
178                 case 'e':
179                     {
180                         /* tm_mday as decimal ( 1-31) */
181                         /* 'O' for locale's alternative numeric symbols */
182                         if ( rc < ( maxsize - 2 ) )
183                         {
184                             div_t day = div( timeptr->tm_mday, 10 );
185                             s[rc++] = ( day.quot > 0 ) ? '0' + day.quot : ' ';
186                             s[rc++] = '0' + day.rem;
187                         }
188                         else
189                         {
190                             return 0;
191                         }
192                         break;
193                     }
194                 case 'F':
195                     {
196                         /* %Y-%m-%d */
197                         size_t count = strftime( s + rc, maxsize - rc, "%Y-%m-%d", timeptr );
198                         if ( count == 0 )
199                         {
200                             return 0;
201                         }
202                         else
203                         {
204                             rc += count;
205                         }
206                         break;
207                     }
208                 case 'g':
209                     {
210                         /* last 2 digits of the week-based year as decimal (00-99) */
211                         /* TODO: 'g' */
212                         break;
213                     }
214                 case 'G':
215                     {
216                         /* week-based year as decimal (e.g. 1997) */
217                         /* TODO: 'G' */
218                         break;
219                     }
220                 case 'H':
221                     {
222                         /* tm_hour as 24h decimal (00-23) */
223                         /* 'O' for locale's alternative numeric symbols */
224                         if ( rc < ( maxsize - 2 ) )
225                         {
226                             div_t hour = div( timeptr->tm_hour, 10 );
227                             s[rc++] = '0' + hour.quot;
228                             s[rc++] = '0' + hour.rem;
229                         }
230                         else
231                         {
232                             return 0;
233                         }
234                         break;
235                     }
236                 case 'I':
237                     {
238                         /* tm_hour as 12h decimal (01-12) */
239                         /* 'O' for locale's alternative numeric symbols */
240                         if ( rc < ( maxsize - 2 ) )
241                         {
242                             div_t hour = div( ( timeptr->tm_hour + 1 ) % 12, 10 );
243                             s[rc++] = '0' + hour.quot;
244                             s[rc++] = '0' + hour.rem;
245                         }
246                         else
247                         {
248                             return 0;
249                         }
250                         break;
251                     }
252                 case 'j':
253                     {
254                         /* tm_yday as decimal (001-366) */
255                         if ( rc < ( maxsize - 3 ) )
256                         {
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;
261                         }
262                         else
263                         {
264                             return 0;
265                         }
266                         break;
267                     }
268                 case 'm':
269                     {
270                         /* tm_mon as decimal (01-12) */
271                         /* 'O' for locale's alternative numeric symbols */
272                         if ( rc < ( maxsize - 2 ) )
273                         {
274                             div_t mon = div( timeptr->tm_mon + 1, 10 );
275                             s[rc++] = '0' + mon.quot;
276                             s[rc++] = '0' + mon.rem;
277                         }
278                         else
279                         {
280                             return 0;
281                         }
282                         break;
283                     }
284                 case 'M':
285                     {
286                         /* tm_min as decimal (00-59) */
287                         /* 'O' for locale's alternative numeric symbols */
288                         if ( rc < ( maxsize - 2 ) )
289                         {
290                             div_t min = div( timeptr->tm_min, 10 );
291                             s[rc++] = '0' + min.quot;
292                             s[rc++] = '0' + min.rem;
293                         }
294                         else
295                         {
296                             return 0;
297                         }
298                         break;
299                     }
300                 case 'n':
301                     {
302                         /* newline */
303                         s[rc++] = '\n';
304                         break;
305                     }
306                 case 'p':
307                     {
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 ) )
312                         {
313                             strcpy( s + rc, designation );
314                             rc += len;
315                         }
316                         else
317                         {
318                             return 0;
319                         }
320                         break;
321                     }
322                 case 'r':
323                     {
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 );
326                         if ( count == 0 )
327                         {
328                             return 0;
329                         }
330                         else
331                         {
332                             rc += count;
333                         }
334                         break;
335                     }
336                 case 'R':
337                     {
338                         /* %H:%M */
339                         size_t count = strftime( s + rc, maxsize - rc, "%H:%M", timeptr );
340                         if ( count == 0 )
341                         {
342                             return 0;
343                         }
344                         else
345                         {
346                             rc += count;
347                         }
348                         break;
349                     }
350                 case 'S':
351                     {
352                         /* tm_sec as decimal (00-60) */
353                         /* 'O' for locale's alternative numeric symbols */
354                         if ( rc < ( maxsize - 2 ) )
355                         {
356                             div_t sec = div( timeptr->tm_sec, 10 );
357                             s[rc++] = '0' + sec.quot;
358                             s[rc++] = '0' + sec.rem;
359                         }
360                         else
361                         {
362                             return 0;
363                         }
364                         break;
365                     }
366                 case 't':
367                     {
368                         /* tabulator */
369                         s[rc++] = '\t';
370                         break;
371                     }
372                 case 'T':
373                     {
374                         /* %H:%M:%S */
375                         size_t count = strftime( s + rc, maxsize - rc, "%H:%M:%S", timeptr );
376                         if ( count == 0 )
377                         {
378                             return 0;
379                         }
380                         else
381                         {
382                             rc += count;
383                         }
384                         break;
385                     }
386                 case 'u':
387                     {
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;
391                         break;
392                     }
393                 case 'U':
394                     {
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 */
397                         /* TODO: 'U' */
398                         break;
399                     }
400                 case 'V':
401                     {
402                         /* week number as decimal (01-53) */
403                         /* 'O' for locale's alternative numeric symbols */
404                         /* TODO: 'V' */
405                         break;
406                     }
407                 case 'w':
408                     {
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;
412                         break;
413                     }
414                 case 'W':
415                     {
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 */
418                         /* TODO: 'W' */
419                         break;
420                     }
421                 case 'x':
422                     {
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 );
426                         if ( count == 0 )
427                         {
428                             return 0;
429                         }
430                         else
431                         {
432                             rc += count;
433                         }
434                         break;
435                     }
436                 case 'X':
437                     {
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 );
441                         if ( count == 0 )
442                         {
443                             return 0;
444                         }
445                         else
446                         {
447                             rc += count;
448                         }
449                         break;
450                     }
451                 case 'y':
452                     {
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 ) )
457                         {
458                             div_t year = div( ( timeptr->tm_year % 100 ), 10 );
459                             s[rc++] = '0' + year.quot;
460                             s[rc++] = '0' + year.rem;
461                         }
462                         else
463                         {
464                             return 0;
465                         }
466                         break;
467                     }
468                 case 'Y':
469                     {
470                         /* tm_year as decimal (e.g. 1997) */
471                         /* 'E' for locale's alternative representation */
472                         if ( rc < ( maxsize - 4 ) )
473                         {
474                             int year = timeptr->tm_year + 1900;
475
476                             for ( int i = 3; i >= 0; --i )
477                             {
478                                 div_t digit = div( year, 10 );
479                                 s[ rc + i ] = '0' + digit.rem;
480                                 year = digit.quot;
481                             }
482
483                             rc += 4;
484                         }
485                         else
486                         {
487                             return 0;
488                         }
489                         break;
490                     }
491                 case 'z':
492                     {
493                         /* tm_isdst / UTC offset in ISO8601 format (e.g. -0430 meaning 4 hours 30 minutes behind Greenwich), or no characters */
494                         /* TODO: 'z' */
495                         break;
496                     }
497                 case 'Z':
498                     {
499                         /* tm_isdst / locale's time zone name or abbreviation, or no characters */
500                         /* TODO: 'Z' */
501                         break;
502                     }
503                 case '%':
504                     {
505                         /* '%' character */
506                         s[rc++] = '%';
507                         break;
508                     }
509             }
510         }
511     }
512
513     return 0;
514 }
515
516 #endif
517
518 #ifdef TEST
519
520 #include "_PDCLIB_test.h"
521
522 int main( void )
523 {
524 #ifndef REGTEST
525     /* Replace with a call to mktime() once that is implemented. */
526     struct tm timeptr = { 59, 30, 12, 1, 9, 72, 0, 274, -1 };
527     char buffer[100];
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 );
588 #endif
589     return TEST_RESULTS;
590 }
591
592 #endif