├── ChangeLog ├── Makefile ├── NOTES ├── README ├── date.1 ├── date.c ├── strftime.3 └── strftime.c /ChangeLog: -------------------------------------------------------------------------------- 1 | 2015-06-22 Arnold D. Robbins 2 | 3 | * strftime.3: Remove statement that only "C" locale is used. 4 | 5 | 2015-06-21 Arnold D. Robbins 6 | 7 | Add access to nl_langinfo. Update comments. 8 | 9 | * strftime.c (days_a, days_l, months_a, months_l, ampm): New functions. 10 | Use nl_langinfo if available. 11 | (strftime): Use functions instead of static arrays. In a few other 12 | places, use result from nl_langinfo. 13 | (main): Call setlocale. 14 | 15 | * date.c (main): Call setlocale. 16 | 17 | 2015-04-19 Arnold D. Robbins 18 | 19 | * strftime.c: Update leading comments. 20 | 21 | 2015-03-12 Arnold D. Robbins 22 | 23 | * strftime.3: Synchronized to April 12, 2011 draft standard. 24 | Added quotation from standard on ISO 8601 week numbers. 25 | * strftime.c (strftime): Synchronized to April 12, 2011 draft 26 | standard. This affects %c and %x. 27 | (main): Use ANSI C signature! Remove decl of time(). Updated the 28 | test strings to be in sync / more correct. 29 | 30 | 2015-01-12 Arnold D. Robbins 31 | 32 | * ChangeLog: Reconstituted from diffs and log. 33 | 34 | 2015-01-09 Arnold D. Robbins 35 | 36 | * Makefile, README, date.1, date.c, strftime.3, strftime.c: Converted 37 | the history over from RCS to git. 38 | * NOTES: Created. 39 | 40 | 2012-05-01 Arnold D. Robbins 41 | 42 | Release 9.0 made. 43 | 44 | * README: Update date, note that we're now POSIX 2008 compatible. 45 | * date.1: It's POSIX 1003, not 1003.2 anymore. 46 | * date.c: Add more header files, use ANSI prototype. (January 2011). 47 | * strftime.3: Document POSIX extensions. 48 | * strftime.c (strftime): Add POSIX 2008 extensions, more 49 | system-specific ifdefs. 50 | (iso_8601_2000_year): New function. 51 | 52 | 2011-12-06 Arnold D. Robbins 53 | 54 | * strftime.c: Sync with bash 4.2.20. 55 | 56 | 2011-01-24 Arnold D. Robbins 57 | 58 | * strftime.c: Add ifdef for inline. 59 | (strftime): For 'z', tm_isdst < 0, break. For not HAVE_TZNAME, 60 | call gettimeofday(). Use %02ld for long value. 61 | 62 | 2011-01-13 Arnold D. Robbins 63 | 64 | * date.c: Add more header files, use ANSI prototype. 65 | * strftime.c: Add HP/UX stuff. 66 | Fixes from Tanaka Akira , remove gawk stuff. 67 | (strftime): Make y (the year) a long. Adjust sprintf formats. 68 | Improve comments on preprocessor control lines. 69 | (isleap): Parameter is now a long. 70 | * date.1: Make POSIX uppercase (December, 2001). 71 | 72 | 2001-07-04 Arnold D. Robbins 73 | 74 | Release 8.0 made. 75 | 76 | Update to C99 stuff. 77 | 78 | * README: Refer to ISO C instead of ANSI C. 79 | * date.1: Adjust email address formatting. 80 | * strftime.3: Updated to reflect source from 1999 (draft) standard. 81 | Text adjusted and improved. ISO 8601 format documented. Lots more. 82 | * strftime.c: ISO instead of ANSI. Move to prototypes. More doc. 83 | C 99 formats added. (Many moved from what were extensions.) 84 | 85 | 2000-09-13 Arnold D. Robbins 86 | 87 | * Makefile: Make -DHAVE_TM_ZONE default. 88 | * README: Update email address to new personal domain. 89 | * date.1: Ditto. 90 | * strftime.3: Document HP/UX extensions. 91 | * strftime.c (strftime): Add HP/UX extensions. 92 | (timezone, altzone): Declare as long int instead of just long. 93 | (tst): Update. 94 | 95 | 1998-10-25 Arnold D. Robbins 96 | 97 | * README: Update date. 98 | * strftime.c (strftime): %v code fixed, better configuration (July 99 | 1997). Today: seconds are 0 - 60, not 61. 100 | * strftime.3: Seconds are 0 - 60, not 61. New email address. 101 | 102 | 1996-01-11 Arnold D. Robbins 103 | 104 | Release 7.0 / 7.0a made. 105 | Comp.sources.unix: Volume 29, Issue 72. 106 | 107 | * strftime.c: Fix up comments. 108 | (strftime): %V code fixed (again) and %G, %g added, 109 | * README: Date changed, email address updated. 110 | * strftime.3: Document ISO date format extensions. Update email. 111 | 112 | 1995-12-26 Arnold D. Robbins 113 | 114 | * README: Update date. 115 | * strftime.c: Update "updated" info. Fixes for TZNAME, other 116 | minor changes. 117 | 118 | 1995-09-11 Arnold D. Robbins 119 | 120 | * strftime.c (strftime): %z code from Chip Rosenthal 121 | . 122 | The code for %c, %x, and %X now follows the 1003.2 specification. 123 | * strftime.3: Document same. 124 | * README: Update date. 125 | 126 | 1994-05-18 Arnold D. Robbins 127 | 128 | * strftime.c (tst): Change VAX to VMS in test program description. 129 | 130 | 1994-05-11 Arnold D. Robbins 131 | 132 | Release 6.1 made. 133 | Comp.sources.unix: Volume 28, Issue 34 134 | 135 | * README: Update date. 136 | * strftime.c: Move include into ifdef for 137 | TM_IN_SYS_TIME. 138 | 139 | 1994-05-01 Arnold D. Robbins 140 | 141 | * strftime.c (strftime): Fixes to use timezone function if no tm_zone 142 | member, from ghazi@noc.rutgers.edu. 143 | (iso8601wknum): Fixes for ISO 8601 method of date calculation, use 144 | week 52 or 53. 145 | (isleap): New function 146 | * strftime.c: Update comments. 147 | * Makefile (CFLAGS): Update with more options. 148 | * README: Update date. 149 | * date.1: Posix isn't a draft anymore. 150 | * strftime.3: Fixes for ISO 8601 method of date calculation, use 151 | week 52 or 53. 152 | 153 | 1994-02-22 Arnold D. Robbins 154 | 155 | Release 6.0 made. 156 | Comp.sources.unix: Volume 27, Issue 207. 157 | 158 | * README: Update date. 159 | 160 | 1994-02-15 Arnold D. Robbins 161 | 162 | * Makefile: Document TM_IN_SYS_TIME define. 163 | * README: Update the date. 164 | 165 | 1994-02-13 Arnold D. Robbins 166 | 167 | Release 3.1 made. 168 | Comp.sources.unix: Volume 26, Issue 235. 169 | 170 | * Makefile: Document HAVE_TZNAME define. 171 | * date.c: Doc updates. Bug fix courtesy of Chris Ritson 172 | . 173 | * strftime.3: Make current with the code and POSIX standard. 174 | * strftime.c: Use %02d as needed, const, other fixes from 175 | ado@elsie.nci.nih.gov. (Apparently done April 1993). 176 | (iso8601wknum): More fixes, mostly from ado@elsie.nci.nih.gov. 177 | (weeknumber): Ditto 178 | 179 | 1993-05-07 Arnold D. Robbins 180 | 181 | * strftime.c (iso8601wknum): Fixes from Tol Lillqvist . 182 | (tst): Test driver from Karl Vogel at Control Data. 183 | 184 | 1993-05-02 Arnold D. Robbins 185 | 186 | Release 3.0 made. 187 | Comp.sources.unix: Volume 26, Issue 208. 188 | 189 | * Makefile (clean): Removed. 190 | * README: 1003.2 is not a draft anymore. 191 | * strftime.3: SunOS extensions. Improve SEE ALSO section. 192 | * strftime.c: SunOS extensions, compilation improvements. 193 | 194 | 1992-05-07 Arnold D. Robbins 195 | 196 | Release 2.0 made. 197 | Comp.sources.unix: Volume 26, Issue 115. 198 | 199 | * Makefile (clean): New target. 200 | * README: Indicate update to POSIX 1003.2 D11.3. 201 | * strftime.3: Add %u and %V, more doc. 202 | * strftime.c: Include gawk-specific bits. General code improvements, 203 | including ISO 8601 week calculations. 204 | 205 | 1992-03-23 Arnold D. Robbins 206 | 207 | * Makefile, README, date.1, date.c: New files. 208 | * strftime.3: Formatting fixes, document POSIX and VMS additions. 209 | Update address and acknowledgements. 210 | * strftime.c: Add POSIX date stuff, POSIX semantics, VMS date format. 211 | Improve code with a range checking function. 212 | 213 | 1991-03-02 Arnold D. Robbins 214 | 215 | Release 1.1 (not really an official version). 216 | Comp.sources.misc: Volume 17, Issue 30. 217 | 218 | * strftime.c (strftime): Call tzset() only once. Range checking on values. 219 | (weeknumber): Fixes from ado@elsie.nci.nih.gov. 220 | * strftime.3: Minor edits, document tzset() behavior. 221 | 222 | 1991-02-07 Arnold D. Robbins 223 | 224 | Release 1.0 (not really an official version). 225 | Comp.sources.misc: Volume 16, Issue 94. 226 | 227 | * strftime.c, strftime.3: Initial versions. 228 | (Initial work started in January 1991.) 229 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for PD date and strftime 2 | 3 | SRCS= date.c strftime.c 4 | OBJS= date.o strftime.o 5 | DOCS= date.1 strftime.3 6 | 7 | # Uncomment the define of HAVE_TZNAME if your system has the tzname[] array. 8 | # Uncomment the define of TM_IN_SYS_TIME if struct tm is in . 9 | # Uncomment the define of TM_ZONE if your struct tm has the tm_zone field. 10 | CFLAGS= -O #-DHAVE_TZNAME #-DTM_IN_SYS_TIME #-DHAVE_TM_ZONE 11 | CFLAGS= -O -DHAVE_TM_ZONE 12 | 13 | date: $(OBJS) 14 | $(CC) $(CFLAGS) $(OBJS) -o $@ 15 | 16 | date.o: date.c 17 | 18 | strftime.o: strftime.c 19 | -------------------------------------------------------------------------------- /NOTES: -------------------------------------------------------------------------------- 1 | Mon Jan 12 22:27:56 IST 2015 2 | ============================ 3 | 4 | This Git repo contains the full history of my public domain strftime and 5 | date package. Using some simple scripts I converted the files from RCS 6 | to Git and was able to preserve the history of the dates when the files 7 | were checked in. 8 | 9 | And, due to the wonders of the Google and the Internet, I was able to find 10 | when different versions of the package had been posted to USENET in order 11 | to tag different versions with their corresponding release information. 12 | 13 | As a brief history, the first two versions were posted in 14 | comp.sources.misc. Later I posted versions in comp.sources.unix. There 15 | was a disconnect in the numbering, such that releases 4.0 and 5.0 never 16 | existed. 17 | 18 | The last posted version was Release 7.0, made available for download 19 | from ftp://freefriends.org as Release 7.0a, since the posted version 20 | ended up with extra leading periods on the troff commands in the man page. 21 | 22 | Releases 8.0 and 9.0 were made available only via the freefriends.org 23 | site. 24 | 25 | The ChangeLog was reconstituted based on release dates and diffs. It's 26 | a best effort, but I think it's pretty close to what actually happened. 27 | 28 | Future work will be available via github. 29 | 30 | Arnold Robbins 31 | arnold@skeeve.com 32 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Mon Apr 16 16:10:00 MST 2012 2 | 3 | This package implements the POSIX 1003-2008 date command, as a wrapper around 4 | an extended version of the POSIX 1003-2008 ISO C strftime(3) library routine. 5 | Everything in it is public domain. 6 | 7 | Arnold Robbins | Laundry increases exponentially 8 | Internet: arnold@skeeve.com | in the number of children. 9 | | -- Miriam Robbins 10 | -------------------------------------------------------------------------------- /date.1: -------------------------------------------------------------------------------- 1 | .TH DATE 1 2 | .SH NAME 3 | date \- write the date and time 4 | .SH SYNOPSIS 5 | .B date 6 | [ 7 | .B \-u 8 | ] [ 9 | .RI + format 10 | ] 11 | .SH DESCRIPTION 12 | .I Date 13 | writes the current date and time to the standard output. 14 | It is intended to be compliant with the POSIX 15 | 1003 Command Language and Utilities standard. 16 | Therefore, it is purposely 17 | .I not 18 | usable by the super-user for setting the system time. 19 | .LP 20 | .I Date 21 | accepts one option: 22 | .TP 23 | .B \-u 24 | Perform operations as if the 25 | .B TZ 26 | environment variable was set to 27 | .BR GMT0 . 28 | .LP 29 | If an argument to 30 | .I date 31 | is given that begins with a ``+'', 32 | then the output is controlled by the contents of the rest of 33 | the string. Normal text is output unmodified, while field descriptors 34 | in the format string are substituted for. 35 | .LP 36 | The 37 | .I date 38 | program is essentially a wrapper around 39 | .IR strftime (3); 40 | see there for a description of the available formatting options. 41 | .LP 42 | If no format string is given, or if it does not begin with a ``+'', 43 | then the default format of \fB"%a %b %e %H:%M:%S %Z %Y"\fR will 44 | be used. This produces the traditional style of output, such as 45 | ``Sun Mar 17 10:32:47 EST 1991''. 46 | .SH SEE ALSO 47 | time(2), strftime(3), localtime(3) 48 | .SH BUGS 49 | This version only works for the POSIX locale. 50 | .SH AUTHOR 51 | Arnold Robbins 52 | 53 | -------------------------------------------------------------------------------- /date.c: -------------------------------------------------------------------------------- 1 | /* 2 | * date.c 3 | * 4 | * Public domain implementation of POSIX 1003.1 5 | * date command. Lets strftime() do the dirty work. 6 | * 7 | * Initial version. 8 | * April, 1991 9 | * 10 | * Bug fix courtesy of Chris Ritson (C.R.Ritson@newcastle.ac.uk). 11 | * February, 1994. 12 | * 13 | * Add more header files, use ANSI prototype. 14 | * January, 2011. 15 | * 16 | * Arnold Robbins 17 | * arnold@skeeve.com 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | extern size_t strftime(); 28 | extern int getopt(); 29 | extern int optind; 30 | 31 | int main(int argc, char **argv) 32 | { 33 | time_t clock; 34 | struct tm *now; 35 | int c, size, ret; 36 | char *defhow = "%a %b %e %H:%M:%S %Z %Y"; 37 | char *howto = defhow; 38 | char *buf; 39 | 40 | setlocale(LC_ALL, ""); 41 | 42 | while ((c = getopt(argc, argv, "u")) != -1) { 43 | switch (c) { 44 | case 'u': 45 | putenv("TZ=GMT0"); 46 | break; 47 | default: 48 | fprintf(stderr, "usage: %s [-u] [+format_str]\n", 49 | argv[0]); 50 | exit(1); 51 | } 52 | } 53 | 54 | time(& clock); 55 | now = localtime(& clock); 56 | 57 | if (optind < argc && argv[optind][0] == '+') 58 | howto = & argv[optind][1]; 59 | 60 | size = strlen(howto) * 10; 61 | if (size < 26) 62 | size = 26; 63 | if ((buf = malloc(size)) == NULL) { 64 | perror("not enough memory"); 65 | exit(1); 66 | } 67 | 68 | ret = strftime(buf, size, howto, now); 69 | if (ret != 0) 70 | printf("%s\n", buf); 71 | else { 72 | fprintf(stderr, "conversion failed\n"); 73 | exit(1); 74 | } 75 | 76 | exit(0); 77 | } 78 | -------------------------------------------------------------------------------- /strftime.3: -------------------------------------------------------------------------------- 1 | .TH STRFTIME 3 2 | .SH NAME 3 | strftime \- generate formatted time information 4 | .SH SYNOPSIS 5 | .ft B 6 | .nf 7 | #include 8 | #include 9 | .sp 10 | size_t strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr); 11 | .fi 12 | .SH DESCRIPTION 13 | The following description is transcribed verbatim from the April 12, 2011 14 | draft standard for ISO C. 15 | This draft is essentially identical in technical content 16 | to the final version of the standard. 17 | .LP 18 | ``The 19 | .B strftime 20 | function places characters into the array pointed to by 21 | .B s 22 | as controlled by the string pointed to by 23 | .BR format . 24 | The format shall be a multibyte character sequence, beginning and ending in 25 | its initial shift state. 26 | The 27 | .B format 28 | string consists of zero or more conversion specifiers and ordinary 29 | multibyte characters. A conversion specifier consists of a 30 | .B % 31 | character, possibly followed by an 32 | .B E 33 | or 34 | .B O 35 | modifier character (described below), 36 | followed by a character that determines the behavior of the 37 | conversion specifier. 38 | All ordinary multibyte characters (including the terminating null 39 | character) are copied unchanged into the array. 40 | If copying takes place between objects that overlap the behavior is undefined. 41 | No more than 42 | .B maxsize 43 | characters are placed into the array. 44 | .PP 45 | ``Each conversion specifier is replaced by appropriate characters as described 46 | in the following list. 47 | The appropriate characters are determined by the 48 | .B LC_TIME 49 | category of the current locale and by the values 50 | of zero or more members of the broken-down time 51 | structure pointed to by 52 | .BR timeptr , 53 | as specified by brackets in the description. 54 | If any of the specified values is outside the normal range, the characters 55 | stored are unspecified.'' 56 | .TP 57 | .B %a 58 | is replaced by the locale's abbreviated weekday name. 59 | .RB [ tm_wday ] 60 | .TP 61 | .B %A 62 | is replaced by the locale's full weekday name. 63 | .RB [ tm_wday ] 64 | .TP 65 | .B %b 66 | is replaced by the locale's abbreviated month name. 67 | .RB [ tm_mon ] 68 | .TP 69 | .B %B 70 | is replaced by the locale's full month name. 71 | .RB [ tm_mon ] 72 | .TP 73 | .B %c 74 | is replaced by the locale's appropriate date and time representation. 75 | (This is 76 | \fB"%a %b %e %T %Y"\fR 77 | in the \fB"C"\fR 78 | locale.) 79 | .TP 80 | .B %C 81 | is replaced by the year divided by 100 and truncated to an integer, 82 | as a decimal number 83 | .RB ( 00 - 99 ). 84 | .RB [ tm_year ] 85 | .TP 86 | .B %d 87 | is replaced by the day of the month as a decimal number 88 | .RB ( 01 - 31 ). 89 | .RB [ tm_mday ] 90 | .TP 91 | .B %D 92 | is equivalent to 93 | .BR %m/%d/%y . 94 | .RB [ tm_mon ", " tm_mday ", " tm_year ] 95 | .TP 96 | .B %e 97 | is replaced by the day of the month as a decimal number 98 | .RB ( 1 - 31 ); 99 | a single digit is preceded by a space. 100 | .RB [ tm_mday ] 101 | .TP 102 | .B %F 103 | is equivalent to 104 | .B %Y\-%m\-%d 105 | (the ISO 8601 date format). 106 | .RB [ tm_year ", " tm_mon ", " tm_mday ] 107 | .TP 108 | .B %g 109 | is replaced by the year without century of the ISO week number 110 | as a decimal number 111 | .RB ( 00 - 99 ). 112 | .RB [ tm_year ", " tm_wday ", " tm_yday ] 113 | .TP 114 | .B %G 115 | is replaced by the year with century of the ISO week number 116 | as a decimal number. 117 | .RB [ tm_year ", " tm_wday ", " tm_yday ] 118 | .TP 119 | .B %h 120 | is equivalent to 121 | .BR %b . 122 | .RB [ tm_mon ] 123 | .TP 124 | .B %H 125 | is replaced by the hour (24-hour clock) as a decimal number 126 | .RB ( 00 - 23 ). 127 | .RB [ tm_hour ] 128 | .TP 129 | .B %I 130 | is replaced by the hour (12-hour clock) as a decimal number 131 | .RB ( 01 - 12 ). 132 | .RB [ tm_hour ] 133 | .TP 134 | .B %j 135 | is replaced by the day of the year as a decimal number 136 | .RB ( 001 - 366 ). 137 | .RB [ tm_yday ] 138 | .TP 139 | .B %m 140 | is replaced by the month as a decimal number 141 | .RB ( 01 - 12 ). 142 | .RB [ tm_mon ] 143 | .TP 144 | .B %M 145 | is replaced by the minute as a decimal number 146 | .RB ( 00 - 59 ). 147 | .RB [ tm_min ] 148 | .TP 149 | .B %n 150 | is replaced with a newline character (\s-1ASCII LF\s+1). 151 | .TP 152 | .B %p 153 | is replaced by the locale's equivalent of the AM/PM designations associated 154 | with a 12-hour clock. 155 | .RB [ tm_hour ] 156 | .TP 157 | .B %r 158 | is replaced by the locale's 12-hour clock time. 159 | (This is 160 | \fB"%I:%M:%S %p"\fR 161 | in the \fB"C"\fR 162 | locale.) 163 | .RB [ tm_hour ", " tm_min ", " tm_sec ] 164 | .TP 165 | .B %R 166 | is equivalent to 167 | .BR %H:%M . 168 | .RB [ tm_hour ", " tm_min ] 169 | .TP 170 | .B %S 171 | is replaced by the second as a decimal number 172 | .RB ( 00 - 60 ). 173 | .RB [ tm_sec ] 174 | .TP 175 | .B %t 176 | is replaced with a \s-1TAB\s+1 character. 177 | .TP 178 | .B %T 179 | is equivalent to 180 | .BR %H:%M:%S . 181 | .RB [ tm_hour ", " tm_min ", " tm_sec ] 182 | .TP 183 | .B %u 184 | is replaced by the ISO 8601 weekday as a decimal number 185 | .RB [ "1 " (Monday)- 7 ]. 186 | .RB [ tm_wday ] 187 | .TP 188 | .B %U 189 | is replaced by the week number of the year (the first Sunday as the first 190 | day of week 1) as a decimal number 191 | .RB ( 00 - 53 ). 192 | .RB [ tm_year ", " tm_wday ", " tm_yday ] 193 | .TP 194 | .B %V 195 | is replaced by the ISO 8601 week number of the year (the first Monday as the first 196 | day of week 1) as a decimal number 197 | .RB ( 01 - 53 ). 198 | .RB [ tm_year ", " tm_wday ", " tm_yday ] 199 | .TP 200 | .B %w 201 | is replaced by the weekday as a decimal number 202 | .RB [ "0 " (Sunday)- 6 ]. 203 | .RB [ tm_wday ] 204 | .TP 205 | .B %W 206 | is replaced by the week number of the year (the first Monday as the first 207 | day of week 1) as a decimal number 208 | .RB ( 00 - 53 ). 209 | .RB [ tm_year ", " tm_wday ", " tm_yday ] 210 | .TP 211 | .B %x 212 | is replaced by the locale's appropriate date representation. 213 | (This is 214 | \fB"%x/%d/%y"\fR 215 | in the \fB"C"\fR 216 | locale.) 217 | .TP 218 | .B %X 219 | is replaced by the locale's appropriate time representation. 220 | (This is 221 | .B "%T" 222 | in the \fB"C"\fR 223 | locale.) 224 | .TP 225 | .B %y 226 | is replaced by the year without century as a decimal number 227 | .RB ( 00 - 99 ). 228 | .RB [ tm_year ] 229 | .TP 230 | .B %Y 231 | is replaced by the year with century as a decimal number. 232 | .RB [ tm_year ] 233 | .TP 234 | .B %z 235 | The timezone offset in a +HHMM format (e.g. the format necessary to 236 | produce RFC-822/RFC-1036 date headers). 237 | .RB [ tm_isdst ] 238 | .TP 239 | .B %Z 240 | is replaced by the time zone name or abbreviation, or by no characters if 241 | no time zone is determinable. 242 | .RB [ tm_isdst ] 243 | .TP 244 | .B %% 245 | is replaced by 246 | .BR % . 247 | .LP 248 | If a conversion specifier is not one of the above, the behavior is 249 | undefined. 250 | .LP 251 | The draft standard says this about the ISO 8601 week-based year: 252 | .RS 253 | .ll -.5i 254 | .LP 255 | .BR %g , 256 | .BR %G , 257 | and 258 | .B %V 259 | give values according to the ISO 8601 week-based year. 260 | In this system, weeks begin on a Monday and week 1 of the year is the week 261 | that includes January 4th, which is also the week that includes the 262 | first Thursday of the year, and is also the first week that contains at 263 | least four days in the year. 264 | If the first Monday of January is the 2nd, 3rd, or 4th, the preceding 265 | days are part of the last week of the preceding year; thus for Saturday 266 | 2nd January 1999, 267 | .B %G 268 | is replaced by 1998 and 269 | .B %V 270 | is replaced by 271 | .BR 53 . 272 | If December 29th, 30th, or 31st is a Monday, it and any following days 273 | are part of week 1 of the following year. 274 | Thus, for Tuesday 30th December 1997, 275 | .B %G 276 | is replaced by 1998 and 277 | .B %V 278 | is replaced by 279 | .BR 01 . 280 | .RE 281 | .ll 282 | .LP 283 | A somewhat more readable description of the algorithm is provided below. 284 | .SH RETURNS 285 | ``If the total number of resulting characters including the terminating null 286 | character is not more than 287 | .BR maxsize , 288 | the 289 | .B strftime 290 | function returns the number of characters placed into the array pointed to 291 | by 292 | .B s 293 | not including the terminating null character. 294 | Otherwise, zero is returned and the contents of the array are indeterminate.'' 295 | .SH ISO 8601 296 | The method for determining the week number as specified by ISO 8601 is: 297 | if the week containing January 1 has four or more days in the 298 | new year, then it is week 1, otherwise it is the highest numbered 299 | week of the previous year (52 or 53) 300 | and the next week is week 1. 301 | All days in a new year preceding the first Monday are considered to be 302 | in week 0. 303 | .PP 304 | For example, January 1, 1993, is in week 53 of 1992. Thus, the year 305 | of its ISO week number is 1992, even though its year is 1993. 306 | Similarly, December 31, 1973, is in week 1 of 1974. Thus, the year 307 | of its ISO week number is 1974, even though its year is 1973. 308 | .SH ALTERNATE REPRESENTATIONS 309 | The alternate representations 310 | .BR %Ec , 311 | .BR %EC , 312 | .BR %Ex , 313 | .BR %EX , 314 | .BR %Ey , 315 | .BR %EY , 316 | .BR %Od , 317 | .BR %Oe , 318 | .BR %OH , 319 | .BR %OI , 320 | .BR %Om , 321 | .BR %OM , 322 | .BR %OS , 323 | .BR %Ou , 324 | .BR %OU , 325 | .BR %OV , 326 | .BR %Ow , 327 | .BR %OW , 328 | and 329 | .B %Oy 330 | are recognized, but their normal representations are used. 331 | .SH NON-ISO EXTENSIONS 332 | .SS SunOS Extensions 333 | If 334 | .B SUNOS_EXT 335 | is defined when the routine is compiled, then the following additional 336 | conversions will be available. 337 | These are borrowed from the SunOS version of 338 | .IR strftime . 339 | .TP 340 | .B %k 341 | is replaced by the hour (24-hour clock) as a decimal number 342 | .RB ( 0 - 23 ). 343 | Single digit numbers are padded with a blank. 344 | .TP 345 | .B %l 346 | is replaced by the hour (12-hour clock) as a decimal number 347 | .RB ( 1 - 12 ). 348 | Single digit numbers are padded with a blank. 349 | .SS HP/UX Extensions 350 | If 351 | .B HPUX_EXT 352 | is defined when the routine is compiled, then the following additional 353 | conversions will be available. 354 | These are borrowed from the HP-UX version of 355 | .IR date . 356 | .TP 357 | .B %N 358 | The ``Emporer/Era'' name. 359 | Typically, this is equivalent to the century 360 | (same as 361 | .B %C ). 362 | .TP 363 | .B %o 364 | The ``Emporer/Era'' year. 365 | Typically, this is equivalent to the year 366 | (same as 367 | .B %y ). 368 | .SS VMS Extensions 369 | If 370 | .B VMS_EXT 371 | is defined, then the following additional conversion is available: 372 | .TP 373 | .B %v 374 | The date in VMS format (e.g. 20-JUN-1991). 375 | .SS POSIX 2008 Extensions 376 | The POSIX 1003.1-2008 standard allows for a leading 377 | .I flag 378 | and 379 | .I "field width" 380 | for the 381 | .BR %C , 382 | .BR %F , 383 | .BR %G , 384 | and 385 | .B %Y 386 | format specifiers. 387 | The flag may be either 388 | .BR 0 , 389 | which specifies that the generated field should be padded with zeros, or 390 | .BR + , 391 | which specifies that the padding with zeros should occur, and that the field 392 | will have a leading plus-sign 393 | if the value is positive, or a leading minus-sign if it is negative. 394 | .PP 395 | The field width is a value in decimal. 396 | Either the field width and a flag are present together, or neither of them 397 | may be supplied. 398 | The results are ``unspecified'' if only one or the other are provided, 399 | or if they are provided on a format specifier not in the preceding list. 400 | If 401 | .B POSIX_2008 402 | is defined, the code supports the leading flag and field width. 403 | .SS Other Extensions 404 | If 405 | .B HAVE_MKTIME 406 | is defined, then this conversion is available: 407 | .TP 408 | .B %s 409 | The time in ``seconds since the Epoch,'' 410 | usually Midnight January 1, 1970, UTC. 411 | .SH SEE ALSO 412 | .IR time (2), 413 | .IR ctime (3), 414 | .IR localtime (3), 415 | .IR mktime (3), 416 | .IR tzset (3) 417 | .SH BUGS 418 | This version does not handle multibyte characters. 419 | .SH CAVEATS 420 | The pre-processor symbol 421 | .B POSIX_SEMANTICS 422 | is automatically defined, which forces the code to call 423 | .IR tzset (3) 424 | whenever the 425 | .B TZ 426 | environment variable has changed. 427 | If this routine will be used in an application that will not be changing 428 | .BR TZ , 429 | then there may be some performance improvements by not defining 430 | .BR POSIX_SEMANTICS . 431 | .SH AUTHOR 432 | Arnold Robbins 433 | .SH ACKNOWLEDGEMENTS 434 | Thanks to Geoff Clare for helping debug earlier 435 | versions of this routine, and for advice about POSIX semantics. 436 | Additional thanks to Arthur David Olsen 437 | for some code improvements. 438 | Thanks also to Tor Lillqvist 439 | for code fixes to the ISO 8601 code. 440 | Thanks to Hume Smith for pointing out a problem with the ISO 8601 code 441 | and to Arthur David Olsen for further discussions. 442 | -------------------------------------------------------------------------------- /strftime.c: -------------------------------------------------------------------------------- 1 | /* 2 | * strftime.c 3 | * 4 | * Public-domain implementation of ISO C library routine. 5 | * 6 | * If you can't do prototypes, get GCC. 7 | * 8 | * The C99 standard now specifies just about all of the formats 9 | * that were additional in the earlier versions of this file. 10 | * 11 | * For extensions from SunOS, add SUNOS_EXT. 12 | * For extensions from HP/UX, add HPUX_EXT. 13 | * For VMS dates, add VMS_EXT. 14 | * For complete POSIX semantics, add POSIX_SEMANTICS. 15 | * 16 | * The code for %X follows the C99 specification for 17 | * the "C" locale. 18 | * 19 | * The code for %c, and %x follows the C11 specification for 20 | * the "C" locale. 21 | * 22 | * With HAVE_NL_LANGINFO defined, locale-based values are used. 23 | * 24 | * This version doesn't worry about multi-byte characters. 25 | * So there. 26 | * 27 | * Arnold Robbins 28 | * January, February, March, 1991 29 | * Updated March, April 1992 30 | * Updated April, 1993 31 | * Updated February, 1994 32 | * Updated May, 1994 33 | * Updated January, 1995 34 | * Updated September, 1995 35 | * Updated January, 1996 36 | * Updated July, 1997 37 | * Updated October, 1999 38 | * Updated September, 2000 39 | * Updated December, 2001 40 | * Updated January, 2011 41 | * Updated April, 2012 42 | * Updated March, 2015 43 | * Updated June, 2015 44 | * 45 | * Fixes from ado@elsie.nci.nih.gov, 46 | * February 1991, May 1992 47 | * Fixes from Tor Lillqvist tml@tik.vtt.fi, 48 | * May 1993 49 | * Further fixes from ado@elsie.nci.nih.gov, 50 | * February 1994 51 | * %z code from chip@chinacat.unicom.com, 52 | * Applied September 1995 53 | * %V code fixed (again) and %G, %g added, 54 | * January 1996 55 | * %v code fixed, better configuration, 56 | * July 1997 57 | * Moved to C99 specification. 58 | * September 2000 59 | * Fixes from Tanaka Akira 60 | * December 2001 61 | */ 62 | 63 | #include 64 | #include 65 | #include 66 | 67 | #if defined(TM_IN_SYS_TIME) 68 | #include 69 | #include 70 | #endif 71 | 72 | #include 73 | #include 74 | 75 | /* defaults: season to taste */ 76 | #define SUNOS_EXT 1 /* stuff in SunOS strftime routine */ 77 | #define VMS_EXT 1 /* include %v for VMS date format */ 78 | #define HPUX_EXT 1 /* non-conflicting stuff in HP-UX date */ 79 | #define POSIX_SEMANTICS 1 /* call tzset() if TZ changes */ 80 | #define POSIX_2008 1 /* flag and fw for C, F, G, Y formats */ 81 | #define HAVE_NL_LANGINFO 1 /* locale-based values */ 82 | 83 | #undef strchr /* avoid AIX weirdness */ 84 | 85 | #ifdef HAVE_NL_LANGINFO 86 | #include 87 | #endif 88 | 89 | extern void tzset(void); 90 | static int weeknumber(const struct tm *timeptr, int firstweekday); 91 | static int iso8601wknum(const struct tm *timeptr); 92 | 93 | #ifndef inline 94 | #ifdef __GNUC__ 95 | #define inline __inline__ 96 | #else 97 | #define inline /**/ 98 | #endif 99 | #endif 100 | 101 | #define range(low, item, hi) max(low, min(item, hi)) 102 | 103 | /* Whew! This stuff is a mess. */ 104 | #if !defined(OS2) && !defined(MSDOS) && !defined(__CYGWIN__) && defined(HAVE_TZNAME) 105 | extern char *tzname[2]; 106 | extern int daylight; 107 | #if defined(SOLARIS) || defined(mips) || defined (M_UNIX) 108 | extern long int timezone, altzone; 109 | #else 110 | #if defined(__hpux) 111 | extern long int timezone; 112 | #else 113 | #if !defined(__CYGWIN__) 114 | extern int timezone, altzone; 115 | #endif 116 | #endif 117 | #endif 118 | #endif 119 | 120 | #undef min /* just in case */ 121 | 122 | /* min --- return minimum of two numbers */ 123 | 124 | static inline int 125 | min(int a, int b) 126 | { 127 | return (a < b ? a : b); 128 | } 129 | 130 | #undef max /* also, just in case */ 131 | 132 | /* max --- return maximum of two numbers */ 133 | 134 | static inline int 135 | max(int a, int b) 136 | { 137 | return (a > b ? a : b); 138 | } 139 | 140 | #ifdef POSIX_2008 141 | /* iso_8601_2000_year --- format a year per ISO 8601:2000 as in 1003.1 */ 142 | 143 | static void 144 | iso_8601_2000_year(char *buf, int year, size_t fw) 145 | { 146 | int extra; 147 | char sign = '\0'; 148 | 149 | if (year >= -9999 && year <= 9999) { 150 | sprintf(buf, "%0*d", (int) fw, year); 151 | return; 152 | } 153 | 154 | /* now things get weird */ 155 | if (year > 9999) { 156 | sign = '+'; 157 | } else { 158 | sign = '-'; 159 | year = -year; 160 | } 161 | 162 | extra = year / 10000; 163 | year %= 10000; 164 | sprintf(buf, "%c_%04d_%d", sign, extra, year); 165 | } 166 | #endif /* POSIX_2008 */ 167 | 168 | /* days_a --- return the short name for the day of the week */ 169 | 170 | static const char * 171 | days_a(int index) 172 | { 173 | #ifdef HAVE_NL_LANGINFO 174 | static const nl_item data[] = { 175 | ABDAY_1, 176 | ABDAY_2, 177 | ABDAY_3, 178 | ABDAY_4, 179 | ABDAY_5, 180 | ABDAY_6, 181 | ABDAY_7, 182 | }; 183 | 184 | return nl_langinfo(data[index]); 185 | #else 186 | static const char *data[] = { 187 | "Sun", "Mon", "Tue", "Wed", 188 | "Thu", "Fri", "Sat", 189 | }; 190 | 191 | return data[index]; 192 | #endif 193 | } 194 | 195 | /* days_l --- return the long name for the day of the week */ 196 | 197 | static const char * 198 | days_l(int index) 199 | { 200 | #ifdef HAVE_NL_LANGINFO 201 | static const nl_item data[] = { 202 | DAY_1, 203 | DAY_2, 204 | DAY_3, 205 | DAY_4, 206 | DAY_5, 207 | DAY_6, 208 | DAY_7, 209 | }; 210 | 211 | return nl_langinfo(data[index]); 212 | #else 213 | static const char *data[] = { 214 | "Sunday", "Monday", "Tuesday", "Wednesday", 215 | "Thursday", "Friday", "Saturday", 216 | }; 217 | 218 | return data[index]; 219 | #endif 220 | } 221 | 222 | /* months_a --- return the short name for the month */ 223 | 224 | static const char * 225 | months_a(int index) 226 | { 227 | #ifdef HAVE_NL_LANGINFO 228 | static const nl_item data[] = { 229 | ABMON_1, 230 | ABMON_2, 231 | ABMON_3, 232 | ABMON_4, 233 | ABMON_5, 234 | ABMON_6, 235 | ABMON_7, 236 | ABMON_8, 237 | ABMON_9, 238 | ABMON_10, 239 | ABMON_11, 240 | ABMON_12, 241 | }; 242 | 243 | return nl_langinfo(data[index]); 244 | #else 245 | static const char *data[] = { 246 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", 247 | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 248 | }; 249 | 250 | return data[index]; 251 | #endif 252 | } 253 | 254 | /* months_l --- return the short name for the month */ 255 | 256 | static const char * 257 | months_l(int index) 258 | { 259 | #ifdef HAVE_NL_LANGINFO 260 | static const nl_item data[] = { 261 | MON_1, 262 | MON_2, 263 | MON_3, 264 | MON_4, 265 | MON_5, 266 | MON_6, 267 | MON_7, 268 | MON_8, 269 | MON_9, 270 | MON_10, 271 | MON_11, 272 | MON_12, 273 | }; 274 | 275 | return nl_langinfo(data[index]); 276 | #else 277 | static const char *data[] = { 278 | "January", "February", "March", "April", 279 | "May", "June", "July", "August", "September", 280 | "October", "November", "December", 281 | }; 282 | 283 | return data[index]; 284 | #endif 285 | } 286 | 287 | /* days_a --- return am/pm string */ 288 | 289 | static const char * 290 | ampm(int index) 291 | { 292 | #ifdef HAVE_NL_LANGINFO 293 | static const nl_item data[] = { 294 | AM_STR, 295 | PM_STR, 296 | }; 297 | 298 | return nl_langinfo(data[index]); 299 | #else 300 | static const char *data[] = { 301 | "AM", "PM", 302 | }; 303 | 304 | return data[index]; 305 | #endif 306 | } 307 | 308 | /* strftime --- produce formatted time */ 309 | 310 | size_t 311 | strftime(char *s, size_t maxsize, const char *format, const struct tm *timeptr) 312 | { 313 | char *endp = s + maxsize; 314 | char *start = s; 315 | auto char tbuf[100]; 316 | long off; 317 | int i, w; 318 | long y; 319 | static short first = 1; 320 | #ifdef POSIX_SEMANTICS 321 | static char *savetz = NULL; 322 | static int savetzlen = 0; 323 | char *tz; 324 | #endif /* POSIX_SEMANTICS */ 325 | #ifndef HAVE_TM_ZONE 326 | #ifndef HAVE_TM_NAME 327 | #ifndef HAVE_TZNAME 328 | #ifndef __CYGWIN__ 329 | extern char *timezone(); 330 | struct timeval tv; 331 | struct timezone zone; 332 | #endif /* __CYGWIN__ */ 333 | #endif /* HAVE_TZNAME */ 334 | #endif /* HAVE_TM_NAME */ 335 | #endif /* HAVE_TM_ZONE */ 336 | #ifdef POSIX_2008 337 | int pad; 338 | size_t fw; 339 | char flag; 340 | #endif /* POSIX_2008 */ 341 | 342 | 343 | if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0) 344 | return 0; 345 | 346 | /* quick check if we even need to bother */ 347 | if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize) 348 | return 0; 349 | 350 | #ifndef POSIX_SEMANTICS 351 | if (first) { 352 | tzset(); 353 | first = 0; 354 | } 355 | #else /* POSIX_SEMANTICS */ 356 | tz = getenv("TZ"); 357 | if (first) { 358 | if (tz != NULL) { 359 | int tzlen = strlen(tz); 360 | 361 | savetz = (char *) malloc(tzlen + 1); 362 | if (savetz != NULL) { 363 | savetzlen = tzlen + 1; 364 | strcpy(savetz, tz); 365 | } 366 | } 367 | tzset(); 368 | first = 0; 369 | } 370 | /* if we have a saved TZ, and it is different, recapture and reset */ 371 | if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) { 372 | i = strlen(tz) + 1; 373 | if (i > savetzlen) { 374 | savetz = (char *) realloc(savetz, i); 375 | if (savetz) { 376 | savetzlen = i; 377 | strcpy(savetz, tz); 378 | } 379 | } else 380 | strcpy(savetz, tz); 381 | tzset(); 382 | } 383 | #endif /* POSIX_SEMANTICS */ 384 | 385 | for (; *format && s < endp - 1; format++) { 386 | tbuf[0] = '\0'; 387 | if (*format != '%') { 388 | *s++ = *format; 389 | continue; 390 | } 391 | #ifdef POSIX_2008 392 | pad = '\0'; 393 | fw = 0; 394 | flag = '\0'; 395 | switch (*++format) { 396 | case '+': 397 | flag = '+'; 398 | /* fall through */ 399 | case '0': 400 | pad = '0'; 401 | format++; 402 | break; 403 | 404 | case '1': 405 | case '2': 406 | case '3': 407 | case '4': 408 | case '5': 409 | case '6': 410 | case '7': 411 | case '8': 412 | case '9': 413 | break; 414 | 415 | default: 416 | format--; 417 | goto again; 418 | } 419 | for (; isdigit(*format); format++) { 420 | fw = fw * 10 + (*format - '0'); 421 | } 422 | format--; 423 | #endif /* POSIX_2008 */ 424 | 425 | again: 426 | switch (*++format) { 427 | case '\0': 428 | *s++ = '%'; 429 | goto out; 430 | 431 | case '%': 432 | *s++ = '%'; 433 | continue; 434 | 435 | case 'a': /* abbreviated weekday name */ 436 | if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) 437 | strcpy(tbuf, "?"); 438 | else 439 | strcpy(tbuf, days_a(timeptr->tm_wday)); 440 | break; 441 | 442 | case 'A': /* full weekday name */ 443 | if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6) 444 | strcpy(tbuf, "?"); 445 | else 446 | strcpy(tbuf, days_l(timeptr->tm_wday)); 447 | break; 448 | 449 | case 'b': /* abbreviated month name */ 450 | short_month: 451 | if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) 452 | strcpy(tbuf, "?"); 453 | else 454 | strcpy(tbuf, months_a(timeptr->tm_mon)); 455 | break; 456 | 457 | case 'B': /* full month name */ 458 | if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11) 459 | strcpy(tbuf, "?"); 460 | else 461 | strcpy(tbuf, months_l(timeptr->tm_mon)); 462 | break; 463 | 464 | case 'c': /* appropriate date and time representation */ 465 | /* 466 | * This used to be: 467 | * 468 | * strftime(tbuf, sizeof tbuf, "%a %b %e %H:%M:%S %Y", timeptr); 469 | * 470 | * Per the ISO 1999 C standard, it was this: 471 | * strftime(tbuf, sizeof tbuf, "%A %B %d %T %Y", timeptr); 472 | * 473 | * Per the ISO 2011 C standard, it is now this: 474 | */ 475 | #ifdef HAVE_NL_LANGINFO 476 | strftime(tbuf, sizeof tbuf, nl_langinfo(D_T_FMT), timeptr); 477 | #else 478 | strftime(tbuf, sizeof tbuf, "%a %b %e %T %Y", timeptr); 479 | #endif 480 | break; 481 | 482 | case 'C': 483 | #ifdef POSIX_2008 484 | if (pad != '\0' && fw > 0) { 485 | size_t min_fw = (flag ? 3 : 2); 486 | 487 | fw = max(fw, min_fw); 488 | sprintf(tbuf, flag 489 | ? "%+0*ld" 490 | : "%0*ld", (int) fw, 491 | (timeptr->tm_year + 1900L) / 100); 492 | } else 493 | #endif /* POSIX_2008 */ 494 | century: 495 | sprintf(tbuf, "%02ld", (timeptr->tm_year + 1900L) / 100); 496 | break; 497 | 498 | case 'd': /* day of the month, 01 - 31 */ 499 | i = range(1, timeptr->tm_mday, 31); 500 | sprintf(tbuf, "%02d", i); 501 | break; 502 | 503 | case 'D': /* date as %m/%d/%y */ 504 | strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr); 505 | break; 506 | 507 | case 'e': /* day of month, blank padded */ 508 | sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31)); 509 | break; 510 | 511 | case 'E': 512 | /* POSIX (now C99) locale extensions, ignored for now */ 513 | goto again; 514 | 515 | case 'F': /* ISO 8601 date representation */ 516 | { 517 | #ifdef POSIX_2008 518 | /* 519 | * Field width for %F is for the whole thing. 520 | * It must be at least 10. 521 | */ 522 | char m_d[10]; 523 | strftime(m_d, sizeof m_d, "-%m-%d", timeptr); 524 | size_t min_fw = 10; 525 | 526 | if (pad != '\0' && fw > 0) { 527 | fw = max(fw, min_fw); 528 | } else { 529 | fw = min_fw; 530 | } 531 | 532 | fw -= 6; /* -XX-XX at end are invariant */ 533 | 534 | iso_8601_2000_year(tbuf, timeptr->tm_year + 1900, fw); 535 | strcat(tbuf, m_d); 536 | #else 537 | strftime(tbuf, sizeof tbuf, "%Y-%m-%d", timeptr); 538 | #endif /* POSIX_2008 */ 539 | } 540 | break; 541 | 542 | case 'g': 543 | case 'G': 544 | /* 545 | * Year of ISO week. 546 | * 547 | * If it's December but the ISO week number is one, 548 | * that week is in next year. 549 | * If it's January but the ISO week number is 52 or 550 | * 53, that week is in last year. 551 | * Otherwise, it's this year. 552 | */ 553 | w = iso8601wknum(timeptr); 554 | if (timeptr->tm_mon == 11 && w == 1) 555 | y = 1900L + timeptr->tm_year + 1; 556 | else if (timeptr->tm_mon == 0 && w >= 52) 557 | y = 1900L + timeptr->tm_year - 1; 558 | else 559 | y = 1900L + timeptr->tm_year; 560 | 561 | if (*format == 'G') { 562 | #ifdef POSIX_2008 563 | if (pad != '\0' && fw > 0) { 564 | size_t min_fw = 4; 565 | 566 | fw = max(fw, min_fw); 567 | sprintf(tbuf, flag 568 | ? "%+0*ld" 569 | : "%0*ld", (int) fw, 570 | y); 571 | } else 572 | #endif /* POSIX_2008 */ 573 | sprintf(tbuf, "%ld", y); 574 | } 575 | else 576 | sprintf(tbuf, "%02ld", y % 100); 577 | break; 578 | 579 | case 'h': /* abbreviated month name */ 580 | goto short_month; 581 | 582 | case 'H': /* hour, 24-hour clock, 00 - 23 */ 583 | i = range(0, timeptr->tm_hour, 23); 584 | sprintf(tbuf, "%02d", i); 585 | break; 586 | 587 | case 'I': /* hour, 12-hour clock, 01 - 12 */ 588 | i = range(0, timeptr->tm_hour, 23); 589 | if (i == 0) 590 | i = 12; 591 | else if (i > 12) 592 | i -= 12; 593 | sprintf(tbuf, "%02d", i); 594 | break; 595 | 596 | case 'j': /* day of the year, 001 - 366 */ 597 | sprintf(tbuf, "%03d", timeptr->tm_yday + 1); 598 | break; 599 | 600 | case 'm': /* month, 01 - 12 */ 601 | i = range(0, timeptr->tm_mon, 11); 602 | sprintf(tbuf, "%02d", i + 1); 603 | break; 604 | 605 | case 'M': /* minute, 00 - 59 */ 606 | i = range(0, timeptr->tm_min, 59); 607 | sprintf(tbuf, "%02d", i); 608 | break; 609 | 610 | case 'n': /* same as \n */ 611 | tbuf[0] = '\n'; 612 | tbuf[1] = '\0'; 613 | break; 614 | 615 | case 'O': 616 | /* POSIX (now C99) locale extensions, ignored for now */ 617 | goto again; 618 | 619 | case 'p': /* am or pm based on 12-hour clock */ 620 | i = range(0, timeptr->tm_hour, 23); 621 | if (i < 12) 622 | strcpy(tbuf, ampm(0)); 623 | else 624 | strcpy(tbuf, ampm(1)); 625 | break; 626 | 627 | case 'r': /* time as %I:%M:%S %p */ 628 | strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr); 629 | break; 630 | 631 | case 'R': /* time as %H:%M */ 632 | strftime(tbuf, sizeof tbuf, "%H:%M", timeptr); 633 | break; 634 | 635 | #if defined(HAVE_MKTIME) 636 | case 's': /* time as seconds since the Epoch */ 637 | { 638 | struct tm non_const_timeptr; 639 | 640 | non_const_timeptr = *timeptr; 641 | sprintf(tbuf, "%ld", mktime(& non_const_timeptr)); 642 | break; 643 | } 644 | #endif /* defined(HAVE_MKTIME) */ 645 | 646 | case 'S': /* second, 00 - 60 */ 647 | i = range(0, timeptr->tm_sec, 60); 648 | sprintf(tbuf, "%02d", i); 649 | break; 650 | 651 | case 't': /* same as \t */ 652 | tbuf[0] = '\t'; 653 | tbuf[1] = '\0'; 654 | break; 655 | 656 | case 'T': /* time as %H:%M:%S */ 657 | the_time: 658 | strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr); 659 | break; 660 | 661 | case 'u': 662 | /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */ 663 | sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 : 664 | timeptr->tm_wday); 665 | break; 666 | 667 | case 'U': /* week of year, Sunday is first day of week */ 668 | sprintf(tbuf, "%02d", weeknumber(timeptr, 0)); 669 | break; 670 | 671 | case 'V': /* week of year according ISO 8601 */ 672 | sprintf(tbuf, "%02d", iso8601wknum(timeptr)); 673 | break; 674 | 675 | case 'w': /* weekday, Sunday == 0, 0 - 6 */ 676 | i = range(0, timeptr->tm_wday, 6); 677 | sprintf(tbuf, "%d", i); 678 | break; 679 | 680 | case 'W': /* week of year, Monday is first day of week */ 681 | sprintf(tbuf, "%02d", weeknumber(timeptr, 1)); 682 | break; 683 | 684 | case 'x': /* appropriate date representation */ 685 | /* 686 | * Up to the 2011 standard, this code used: 687 | * strftime(tbuf, sizeof tbuf, "%A %B %d %Y", timeptr); 688 | * 689 | * Now, per the 2011 C standard, this is: 690 | */ 691 | #ifdef HAVE_NL_LANGINFO 692 | strftime(tbuf, sizeof tbuf, nl_langinfo(D_FMT), timeptr); 693 | #else 694 | strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr); 695 | #endif 696 | break; 697 | 698 | case 'X': /* appropriate time representation */ 699 | #ifdef HAVE_NL_LANGINFO 700 | strftime(tbuf, sizeof tbuf, nl_langinfo(T_FMT), timeptr); 701 | #else 702 | goto the_time; 703 | #endif 704 | break; 705 | 706 | case 'y': /* year without a century, 00 - 99 */ 707 | year: 708 | i = timeptr->tm_year % 100; 709 | sprintf(tbuf, "%02d", i); 710 | break; 711 | 712 | case 'Y': /* year with century */ 713 | #ifdef POSIX_2008 714 | if (pad != '\0' && fw > 0) { 715 | size_t min_fw = 4; 716 | 717 | fw = max(fw, min_fw); 718 | sprintf(tbuf, flag 719 | ? "%+0*ld" 720 | : "%0*ld", (int) fw, 721 | 1900L + timeptr->tm_year); 722 | } else 723 | #endif /* POSIX_2008 */ 724 | sprintf(tbuf, "%ld", 1900L + timeptr->tm_year); 725 | break; 726 | 727 | /* 728 | * From: Chip Rosenthal 729 | * Date: Sun, 19 Mar 1995 00:33:29 -0600 (CST) 730 | * 731 | * Warning: the %z [code] is implemented by inspecting the 732 | * timezone name conditional compile settings, and 733 | * inferring a method to get timezone offsets. I've tried 734 | * this code on a couple of machines, but I don't doubt 735 | * there is some system out there that won't like it. 736 | * Maybe the easiest thing to do would be to bracket this 737 | * with an #ifdef that can turn it off. The %z feature 738 | * would be an admittedly obscure one that most folks can 739 | * live without, but it would be a great help to those of 740 | * us that muck around with various message processors. 741 | */ 742 | case 'z': /* time zone offset east of GMT e.g. -0600 */ 743 | if (timeptr->tm_isdst < 0) 744 | break; 745 | #ifdef HAVE_TM_NAME 746 | /* 747 | * Systems with tm_name probably have tm_tzadj as 748 | * secs west of GMT. Convert to mins east of GMT. 749 | */ 750 | off = -timeptr->tm_tzadj / 60; 751 | #else /* !HAVE_TM_NAME */ 752 | #ifdef HAVE_TM_ZONE 753 | /* 754 | * Systems with tm_zone probably have tm_gmtoff as 755 | * secs east of GMT. Convert to mins east of GMT. 756 | */ 757 | off = timeptr->tm_gmtoff / 60; 758 | #else /* !HAVE_TM_ZONE */ 759 | #if HAVE_TZNAME 760 | /* 761 | * Systems with tzname[] probably have timezone as 762 | * secs west of GMT. Convert to mins east of GMT. 763 | */ 764 | #if defined(__hpux) || defined(__CYGWIN__) 765 | off = -timezone / 60; 766 | #else 767 | /* ADR: 4 August 2001, fixed this per gazelle@interaccess.com */ 768 | off = -(daylight ? altzone : timezone) / 60; 769 | #endif 770 | #else /* !HAVE_TZNAME */ 771 | gettimeofday(& tv, & zone); 772 | off = -zone.tz_minuteswest; 773 | #endif /* !HAVE_TZNAME */ 774 | #endif /* !HAVE_TM_ZONE */ 775 | #endif /* !HAVE_TM_NAME */ 776 | if (off < 0) { 777 | tbuf[0] = '-'; 778 | off = -off; 779 | } else { 780 | tbuf[0] = '+'; 781 | } 782 | sprintf(tbuf+1, "%02ld%02ld", off/60, off%60); 783 | break; 784 | 785 | case 'Z': /* time zone name or abbrevation */ 786 | #ifdef HAVE_TZNAME 787 | i = (daylight && timeptr->tm_isdst > 0); /* 0 or 1 */ 788 | strcpy(tbuf, tzname[i]); 789 | #else 790 | #ifdef HAVE_TM_ZONE 791 | strcpy(tbuf, timeptr->tm_zone); 792 | #else 793 | #ifdef HAVE_TM_NAME 794 | strcpy(tbuf, timeptr->tm_name); 795 | #else 796 | gettimeofday(& tv, & zone); 797 | strcpy(tbuf, timezone(zone.tz_minuteswest, 798 | timeptr->tm_isdst > 0)); 799 | #endif /* HAVE_TM_NAME */ 800 | #endif /* HAVE_TM_ZONE */ 801 | #endif /* HAVE_TZNAME */ 802 | break; 803 | 804 | #ifdef SUNOS_EXT 805 | case 'k': /* hour, 24-hour clock, blank pad */ 806 | sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23)); 807 | break; 808 | 809 | case 'l': /* hour, 12-hour clock, 1 - 12, blank pad */ 810 | i = range(0, timeptr->tm_hour, 23); 811 | if (i == 0) 812 | i = 12; 813 | else if (i > 12) 814 | i -= 12; 815 | sprintf(tbuf, "%2d", i); 816 | break; 817 | #endif 818 | 819 | #ifdef HPUX_EXT 820 | case 'N': /* Emperor/Era name */ 821 | #ifdef HAVE_NL_LANGINFO 822 | strftime(tbuf, sizeof tbuf, nl_langinfo(ERA), timeptr); 823 | #else 824 | /* this is essentially the same as the century */ 825 | goto century; /* %C */ 826 | #endif 827 | 828 | case 'o': /* Emperor/Era year */ 829 | goto year; /* %y */ 830 | #endif /* HPUX_EXT */ 831 | 832 | 833 | #ifdef VMS_EXT 834 | case 'v': /* date as dd-bbb-YYYY */ 835 | sprintf(tbuf, "%2d-%3.3s-%4ld", 836 | range(1, timeptr->tm_mday, 31), 837 | months_a(range(0, timeptr->tm_mon, 11)), 838 | timeptr->tm_year + 1900L); 839 | for (i = 3; i < 6; i++) 840 | if (islower(tbuf[i])) 841 | tbuf[i] = toupper(tbuf[i]); 842 | break; 843 | #endif 844 | 845 | default: 846 | tbuf[0] = '%'; 847 | tbuf[1] = *format; 848 | tbuf[2] = '\0'; 849 | break; 850 | } 851 | i = strlen(tbuf); 852 | if (i) { 853 | if (s + i < endp - 1) { 854 | strcpy(s, tbuf); 855 | s += i; 856 | } else 857 | return 0; 858 | } 859 | } 860 | out: 861 | if (s < endp && *format == '\0') { 862 | *s = '\0'; 863 | return (s - start); 864 | } else 865 | return 0; 866 | } 867 | 868 | /* isleap --- is a year a leap year? */ 869 | 870 | static int 871 | isleap(long year) 872 | { 873 | return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); 874 | } 875 | 876 | 877 | /* iso8601wknum --- compute week number according to ISO 8601 */ 878 | 879 | static int 880 | iso8601wknum(const struct tm *timeptr) 881 | { 882 | /* 883 | * From 1003.2: 884 | * If the week (Monday to Sunday) containing January 1 885 | * has four or more days in the new year, then it is week 1; 886 | * otherwise it is the highest numbered week of the previous 887 | * year (52 or 53), and the next week is week 1. 888 | * 889 | * ADR: This means if Jan 1 was Monday through Thursday, 890 | * it was week 1, otherwise week 52 or 53. 891 | * 892 | * XPG4 erroneously included POSIX.2 rationale text in the 893 | * main body of the standard. Thus it requires week 53. 894 | */ 895 | 896 | int weeknum, jan1day, diff; 897 | 898 | /* get week number, Monday as first day of the week */ 899 | weeknum = weeknumber(timeptr, 1); 900 | 901 | /* 902 | * With thanks and tip of the hatlo to tml@tik.vtt.fi 903 | * 904 | * What day of the week does January 1 fall on? 905 | * We know that 906 | * (timeptr->tm_yday - jan1.tm_yday) MOD 7 == 907 | * (timeptr->tm_wday - jan1.tm_wday) MOD 7 908 | * and that 909 | * jan1.tm_yday == 0 910 | * and that 911 | * timeptr->tm_wday MOD 7 == timeptr->tm_wday 912 | * from which it follows that. . . 913 | */ 914 | jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7); 915 | if (jan1day < 0) 916 | jan1day += 7; 917 | 918 | /* 919 | * If Jan 1 was a Monday through Thursday, it was in 920 | * week 1. Otherwise it was last year's highest week, which is 921 | * this year's week 0. 922 | * 923 | * What does that mean? 924 | * If Jan 1 was Monday, the week number is exactly right, it can 925 | * never be 0. 926 | * If it was Tuesday through Thursday, the weeknumber is one 927 | * less than it should be, so we add one. 928 | * Otherwise, Friday, Saturday or Sunday, the week number is 929 | * OK, but if it is 0, it needs to be 52 or 53. 930 | */ 931 | switch (jan1day) { 932 | case 1: /* Monday */ 933 | break; 934 | case 2: /* Tuesday */ 935 | case 3: /* Wednesday */ 936 | case 4: /* Thursday */ 937 | weeknum++; 938 | break; 939 | case 5: /* Friday */ 940 | case 6: /* Saturday */ 941 | case 0: /* Sunday */ 942 | if (weeknum == 0) { 943 | #ifdef USE_BROKEN_XPG4 944 | /* XPG4 (as of March 1994) says 53 unconditionally */ 945 | weeknum = 53; 946 | #else 947 | /* get week number of last week of last year */ 948 | struct tm dec31ly; /* 12/31 last year */ 949 | dec31ly = *timeptr; 950 | dec31ly.tm_year--; 951 | dec31ly.tm_mon = 11; 952 | dec31ly.tm_mday = 31; 953 | dec31ly.tm_wday = (jan1day == 0) ? 6 : jan1day - 1; 954 | dec31ly.tm_yday = 364 + isleap(dec31ly.tm_year + 1900L); 955 | weeknum = iso8601wknum(& dec31ly); 956 | #endif 957 | } 958 | break; 959 | } 960 | 961 | if (timeptr->tm_mon == 11) { 962 | /* 963 | * The last week of the year 964 | * can be in week 1 of next year. 965 | * Sigh. 966 | * 967 | * This can only happen if 968 | * M T W 969 | * 29 30 31 970 | * 30 31 971 | * 31 972 | */ 973 | int wday, mday; 974 | 975 | wday = timeptr->tm_wday; 976 | mday = timeptr->tm_mday; 977 | if ( (wday == 1 && (mday >= 29 && mday <= 31)) 978 | || (wday == 2 && (mday == 30 || mday == 31)) 979 | || (wday == 3 && mday == 31)) 980 | weeknum = 1; 981 | } 982 | 983 | return weeknum; 984 | } 985 | 986 | /* weeknumber --- figure how many weeks into the year */ 987 | 988 | /* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */ 989 | 990 | static int 991 | weeknumber(const struct tm *timeptr, int firstweekday) 992 | { 993 | int wday = timeptr->tm_wday; 994 | int ret; 995 | 996 | if (firstweekday == 1) { 997 | if (wday == 0) /* sunday */ 998 | wday = 6; 999 | else 1000 | wday--; 1001 | } 1002 | ret = ((timeptr->tm_yday + 7 - wday) / 7); 1003 | if (ret < 0) 1004 | ret = 0; 1005 | return ret; 1006 | } 1007 | 1008 | #if 0 1009 | /* ADR --- I'm loathe to mess with ado's code ... */ 1010 | 1011 | Date: Wed, 24 Apr 91 20:54:08 MDT 1012 | From: Michal Jaegermann 1013 | To: arnold@audiofax.com 1014 | 1015 | Hi Arnold, 1016 | in a process of fixing of strftime() in libraries on Atari ST I grabbed 1017 | some pieces of code from your own strftime. When doing that it came 1018 | to mind that your weeknumber() function compiles a little bit nicer 1019 | in the following form: 1020 | /* 1021 | * firstweekday is 0 if starting in Sunday, non-zero if in Monday 1022 | */ 1023 | { 1024 | return (timeptr->tm_yday - timeptr->tm_wday + 1025 | (firstweekday ? (timeptr->tm_wday ? 8 : 1) : 7)) / 7; 1026 | } 1027 | How nicer it depends on a compiler, of course, but always a tiny bit. 1028 | 1029 | Cheers, 1030 | Michal 1031 | ntomczak@vm.ucs.ualberta.ca 1032 | #endif 1033 | 1034 | #ifdef TEST_STRFTIME 1035 | 1036 | /* 1037 | * NAME: 1038 | * tst 1039 | * 1040 | * SYNOPSIS: 1041 | * tst 1042 | * 1043 | * DESCRIPTION: 1044 | * "tst" is a test driver for the function "strftime". 1045 | * 1046 | * OPTIONS: 1047 | * None. 1048 | * 1049 | * AUTHOR: 1050 | * Karl Vogel 1051 | * Control Data Systems, Inc. 1052 | * vogelke@c-17igp.wpafb.af.mil 1053 | * 1054 | * BUGS: 1055 | * None noticed yet. 1056 | * 1057 | * COMPILE: 1058 | * cc -o tst -DTEST_STRFTIME strftime.c 1059 | */ 1060 | 1061 | /* ADR: I reformatted this to my liking, and deleted some unneeded code. */ 1062 | 1063 | #ifndef NULL 1064 | #include 1065 | #endif 1066 | #include 1067 | #include 1068 | #include 1069 | 1070 | #define MAXTIME 132 1071 | 1072 | /* 1073 | * Array of time formats. 1074 | */ 1075 | 1076 | static char *array[] = 1077 | { 1078 | "(%%A) full weekday name, var length (Sunday..Saturday) %A", 1079 | "(%%B) full month name, var length (January..December) %B", 1080 | "(%%C) Century %C", 1081 | "(%%D) date (%%m/%%d/%%y) %D", 1082 | "(%%E) Locale extensions (ignored) %E", 1083 | "(%%F) year-month-day (YYYY-MM-DD) %F", 1084 | "(%%H) hour (24-hour clock, 00..23) %H", 1085 | "(%%I) hour (12-hour clock, 01..12) %I", 1086 | "(%%M) minute (00..59) %M", 1087 | "(%%N) Emporer/Era Name %N", 1088 | "(%%O) Locale extensions (ignored) %O", 1089 | "(%%R) time, 24-hour (%%H:%%M) %R", 1090 | "(%%S) second (00..60) %S", 1091 | "(%%T) time, 24-hour (%%H:%%M:%%S) %T", 1092 | "(%%U) week of year, Sunday as first day of week (00..53) %U", 1093 | "(%%V) week of year according to ISO 8601 %V", 1094 | "(%%W) week of year, Monday as first day of week (00..53) %W", 1095 | "(%%X) appropriate locale time representation (%%H:%%M:%%S) %X", 1096 | "(%%Y) year with century (1970...) %Y", 1097 | "(%%Z) timezone (EDT), or blank if timezone not determinable %Z", 1098 | "(%%a) locale's abbreviated weekday name (Sun..Sat) %a", 1099 | "(%%b) locale's abbreviated month name (Jan..Dec) %b", 1100 | "(%%c) full date + newline (Sat Nov 4 12:02:33 1989)%n%t%t%t %c", 1101 | "(%%d) day of the month (01..31) %d", 1102 | "(%%e) day of the month, blank-padded ( 1..31) %e", 1103 | "(%%h) should be same as (%%b) %h", 1104 | "(%%j) day of the year (001..366) %j", 1105 | "(%%k) hour, 24-hour clock, blank pad ( 0..23) %k", 1106 | "(%%l) hour, 12-hour clock, blank pad ( 0..12) %l", 1107 | "(%%m) month (01..12) %m", 1108 | "(%%o) Emporer/Era Year %o", 1109 | "(%%p) locale's AM or PM based on 12-hour clock %p", 1110 | "(%%r) time, 12-hour (same as %%I:%%M:%%S %%p) %r", 1111 | "(%%u) ISO 8601: Weekday as decimal number [1 (Monday) - 7] %u", 1112 | "(%%v) VMS date (dd-bbb-YYYY) %v", 1113 | "(%%w) day of week (0..6, Sunday == 0) %w", 1114 | "(%%x) appropriate locale date representation %x", 1115 | "(%%y) last two digits of year (00..99) %y", 1116 | "(%%z) timezone offset east of GMT as HHMM (e.g. -0500) %z", 1117 | (char *) NULL 1118 | }; 1119 | 1120 | /* main routine. */ 1121 | 1122 | int 1123 | main(int argc, char **argv) 1124 | { 1125 | char *next; 1126 | char string[MAXTIME]; 1127 | 1128 | int k; 1129 | int length; 1130 | 1131 | struct tm *tm; 1132 | 1133 | long clock; 1134 | 1135 | setlocale(LC_ALL, ""); 1136 | 1137 | /* Call the function. */ 1138 | 1139 | clock = time((long *) 0); 1140 | tm = localtime(&clock); 1141 | 1142 | for (k = 0; next = array[k]; k++) { 1143 | length = strftime(string, MAXTIME, next, tm); 1144 | printf("%s\n", string); 1145 | } 1146 | 1147 | return 0; 1148 | } 1149 | #endif /* TEST_STRFTIME */ 1150 | --------------------------------------------------------------------------------