strftime.c: implement %N format specifier with extra semantics
[girocco/readme.git] / src / strftime.c
blob08f053ac63ad5e5b7aad42a98795b57c5a82b75f
1 /*
3 strftime.c -- provide strftime functionality on the command line
4 Copyright (C) 2016,2017 Kyle J. McKay. All rights reserved.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 #define VERSION \
23 "strftime version 1.2.0\n" \
24 "Copyright (C) 2016,2017 Kyle J. McKay <mackyle at gmail dot com>\n" \
25 "License GPLv2+: GNU GPL version 2 or later.\n" \
26 "<http://gnu.org/licenses/gpl2.html>\n" \
27 "This is free software: you are free to change and redistribute it.\n" \
28 "There is NO WARRANTY, to the extent permitted by law.\n"
30 #define USAGE \
31 "Usage: strftime [option...] [\"strftime(3) format\" [<epochsecs> [<offset>]]]\n" \
32 " --help/-h show this help\n" \
33 " --version/-V show version/license info\n" \
34 " --locale/-l use default locale rather than \"C\" locale\n" \
35 " --adjust/-a n add n (which may be negative) to <epochsecs> before formatting\n" \
36 " -- terminate options, next arg is format even if it starts with -\n" \
37 " <epochsecs> if omitted or empty string ('') use current time\n" \
38 " <offset> must be [+|-]HH[MM[SS]] same meaning as strftime(3) '%z' value\n" \
39 "If \"strftime(3) format\" is omitted (or '') then \"%a, %e %b %Y %T %z\" is used.\n" \
40 "If <offset> is omitted default timezone (taking TZ into account) will be used.\n" \
41 "If <offset> is NOT omitted then a strftime(3) '%Z' value will format as either\n" \
42 "\"unknown\" or \"UTC\" (for a zero offset). Note that except for the time zone\n" \
43 "name, `date` and `strftime '%a %b %e %T %Z %Y' $(date +'%s %z')` should match.\n" \
44 "If --locale is NOT used the default format strips leading spaces from %e value.\n" \
45 "The %N format specifier is also supported for a 9-digit nanoseconds but the\n" \
46 "last three digits will always be zero in this implementation and only the first\n" \
47 "occurrence of %N will be replaced. Additionally, if the current time is used\n" \
48 "along with %N then a 1 ms sleep will occur before and after gettimeofday().\n"
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <locale.h>
53 #include <string.h>
54 #include <time.h>
55 #include <sys/time.h>
57 #define DEFAULT_FORMAT "%a, %e %b %Y %T %z"
58 #define MAXLENGTH 4096
60 static char buff[MAXLENGTH];
61 static char tzval[20]; /* TZ=unknown+HH:MM:SS\0 */
63 int main(int argc, char *argv[])
65 int arg = 1;
66 char *bp = buff;
67 struct timeval tv;
68 char *fmt = DEFAULT_FORMAT;
69 int defaultformat = 1;
70 int dosetlocale = 0;
71 int needtime = 1;
72 struct tm localvals;
73 long adjust = 0;
74 size_t fmtlen, i, firstN;
76 tv.tv_usec = 0;
77 while (arg < argc && *argv[arg] == '-') {
78 if (!strcmp(argv[arg], "--help") || !strcmp(argv[arg], "-h")) {
79 printf("%s", USAGE);
80 return 0;
82 if (!strcmp(argv[arg], "--version") || !strcmp(argv[arg], "-V")) {
83 printf("%s", VERSION);
84 return 0;
86 if (!strcmp(argv[arg], "--locale") || !strcmp(argv[arg], "-l")) {
87 dosetlocale = 1;
88 ++arg;
89 continue;
91 if (!strcmp(argv[arg], "--adjust") || !strcmp(argv[arg], "-a")) {
92 char *end;
94 if (++arg >= argc || !*argv[arg]) {
95 fprintf(stderr, "strftime: missing --adjust value "
96 "(see strftime -h)\n");
97 return 1;
99 adjust = strtol(argv[arg], &end, 10);
100 if (*end) {
101 fprintf(stderr, "strftime: invalid number: %s\n",
102 argv[arg]);
103 return 2;
105 ++arg;
106 continue;
108 if (!strcmp(argv[arg], "--")) {
109 ++arg;
110 break;
112 fprintf(stderr, "strftime: invalid option: %s (see strftime -h)\n",
113 argv[arg]);
114 return 1;
116 if (argc - arg > 3) {
117 fprintf(stderr, "strftime: invalid arguments (see strftime -h)\n");
118 return 1;
120 if (argc - arg >= 1) {
121 if (*argv[arg]) {
122 fmt = argv[arg];
123 defaultformat = 0;
125 if (argc - arg >= 2) {
126 if (*argv[arg+1]) {
127 char *end;
128 long l = strtol(argv[arg+1], &end, 10);
130 if (*end) {
131 fprintf(stderr, "strftime: invalid number: %s\n",
132 argv[arg+1]);
133 return 2;
135 tv.tv_sec = (time_t)l;
136 needtime = 0;
138 if (argc - arg >= 3) {
139 const char *o = argv[arg+2];
140 size_t l = strlen(o);
141 char tzsign = '-';
142 char *d = tzval;
144 if (*o == '+' || *o == '-') {
145 tzsign = (*o == '+') ? '-' : '+';
146 ++o;
147 --l;
149 if (l < 2 || l > 6 || (l & 0x1) || l != strspn(o, "0123456789")) {
150 fprintf(stderr, "strftime: invalid offset: %s\n",
151 argv[arg+2]);
152 return 2;
154 if (l == strspn(o, "0")) {
155 memcpy(d, "TZ=UTC", 6);
156 d += 6;
157 } else {
158 memcpy(d, "TZ=unknown", 10);
159 d += 10;
160 *d++ = tzsign;
161 *d++ = *o++;
162 *d++ = *o++;
163 if (*o) {
164 *d++ = ':';
165 *d++ = *o++;
166 *d++ = *o++;
168 if (*o) {
169 *d++ = ':';
170 *d++ = *o++;
171 *d++ = *o++;
174 *d = '\0';
175 putenv(tzval);
179 if (dosetlocale) {
180 setlocale(LC_ALL, "");
182 tzset();
183 fmtlen = strlen(fmt);
184 firstN = 0;
185 for (i=0; i<fmtlen; ++i) {
186 if (fmt[i] != '%')
187 continue;
188 ++i;
189 if (fmt[i] == '%')
190 continue;
191 if (fmt[i] == 'N') {
192 fmt[i-1] = '\0';
193 firstN = i + 1;
194 break;
197 if (needtime) {
198 struct timespec milli;
199 if (firstN) {
200 milli.tv_sec = 0;
201 milli.tv_nsec = 1000;
202 nanosleep(&milli, NULL);
204 gettimeofday(&tv, NULL);
205 if (firstN)
206 nanosleep(&milli, NULL);
208 tv.tv_sec += (time_t)adjust;
209 localvals = *localtime(&tv.tv_sec);
210 buff[0] = '\0';
211 if (*fmt && !strftime(buff, sizeof(buff), fmt, &localvals)) {
212 fprintf(stderr, "strftime: format string too long\n");
213 return 3;
215 if (firstN) {
216 size_t left = sizeof(buff) - (fmtlen = strlen(buff));
217 if (left < 10) {
218 fprintf(stderr, "strftime: format string too long\n");
219 return 3;
221 snprintf(buff + fmtlen, left, "%06u000", (unsigned)tv.tv_usec);
222 fmtlen += 9;
223 left -= 9;
224 fmt += firstN;
225 if (*fmt) {
226 if (!strftime(buff + fmtlen, left, fmt, &localvals)) {
227 fprintf(stderr, "strftime: format string too long\n");
228 return 3;
232 if (defaultformat && !dosetlocale && buff[3] == ',' && buff[5] == ' ') {
233 memmove(buff+1, buff, 4);
234 ++bp;
236 printf("%s\n", bp);
237 return 0;