Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/bin/date/date.c
102292 views
1
/*-
2
* SPDX-License-Identifier: BSD-3-Clause
3
*
4
* Copyright (c) 1985, 1987, 1988, 1993
5
* The Regents of the University of California. All rights reserved.
6
*
7
* Redistribution and use in source and binary forms, with or without
8
* modification, are permitted provided that the following conditions
9
* are met:
10
* 1. Redistributions of source code must retain the above copyright
11
* notice, this list of conditions and the following disclaimer.
12
* 2. Redistributions in binary form must reproduce the above copyright
13
* notice, this list of conditions and the following disclaimer in the
14
* documentation and/or other materials provided with the distribution.
15
* 3. Neither the name of the University nor the names of its contributors
16
* may be used to endorse or promote products derived from this software
17
* without specific prior written permission.
18
*
19
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29
* SUCH DAMAGE.
30
*/
31
32
#include <sys/param.h>
33
#include <sys/time.h>
34
#include <sys/stat.h>
35
36
#include <ctype.h>
37
#include <err.h>
38
#include <errno.h>
39
#include <inttypes.h>
40
#include <locale.h>
41
#include <stdbool.h>
42
#include <stdio.h>
43
#include <stdlib.h>
44
#include <string.h>
45
#include <syslog.h>
46
#include <unistd.h>
47
#include <utmpx.h>
48
49
#include "vary.h"
50
51
#ifndef TM_YEAR_BASE
52
#define TM_YEAR_BASE 1900
53
#endif
54
55
static void badformat(void);
56
static void iso8601_usage(const char *) __dead2;
57
static void multipleformats(void);
58
static void printdate(const char *);
59
static void printisodate(struct tm *, long, long);
60
static void setthetime(const char *, const char *, int, struct timespec *);
61
static size_t strftime_ns(char * __restrict, size_t, const char * __restrict,
62
const struct tm * __restrict, long, long);
63
static void usage(void) __dead2;
64
65
static const struct iso8601_fmt {
66
const char *refname;
67
const char *format_string;
68
} iso8601_fmts[] = {
69
{ "date", "%Y-%m-%d" },
70
{ "hours", "T%H" },
71
{ "minutes", ":%M" },
72
{ "seconds", ":%S" },
73
{ "ns", ",%N" },
74
};
75
static const struct iso8601_fmt *iso8601_selected;
76
77
static const char *rfc2822_format = "%a, %d %b %Y %T %z";
78
79
int
80
main(int argc, char *argv[])
81
{
82
struct timespec ts = { 0, 0 }, tres = { 0, 1 };
83
int ch, rflag;
84
bool Iflag, jflag, Rflag;
85
const char *format;
86
char buf[1024];
87
char *end, *fmt, *outzone = NULL;
88
struct vary *v;
89
const struct vary *badv;
90
struct tm *lt;
91
struct stat sb;
92
size_t i;
93
intmax_t number;
94
95
v = NULL;
96
fmt = NULL;
97
(void) setlocale(LC_TIME, "");
98
rflag = 0;
99
Iflag = jflag = Rflag = 0;
100
while ((ch = getopt(argc, argv, "f:I::jnRr:uv:z:")) != -1)
101
switch((char)ch) {
102
case 'f':
103
fmt = optarg;
104
break;
105
case 'I':
106
if (Rflag)
107
multipleformats();
108
Iflag = 1;
109
if (optarg == NULL) {
110
iso8601_selected = iso8601_fmts;
111
break;
112
}
113
for (i = 0; i < nitems(iso8601_fmts); i++)
114
if (strcmp(optarg, iso8601_fmts[i].refname) == 0)
115
break;
116
if (i == nitems(iso8601_fmts))
117
iso8601_usage(optarg);
118
119
iso8601_selected = &iso8601_fmts[i];
120
break;
121
case 'j':
122
jflag = 1; /* don't set time */
123
break;
124
case 'n':
125
break;
126
case 'R': /* RFC 2822 datetime format */
127
if (Iflag)
128
multipleformats();
129
Rflag = 1;
130
break;
131
case 'r': /* user specified seconds */
132
rflag = 1;
133
number = strtoimax(optarg, &end, 0);
134
if (end > optarg && *end == '\0') {
135
ts.tv_sec = number;
136
ts.tv_nsec = 0;
137
} else if (stat(optarg, &sb) == 0) {
138
ts.tv_sec = sb.st_mtim.tv_sec;
139
ts.tv_nsec = sb.st_mtim.tv_nsec;
140
} else {
141
usage();
142
}
143
break;
144
case 'u': /* do everything in UTC */
145
(void)setenv("TZ", "UTC0", 1);
146
break;
147
case 'z':
148
outzone = optarg;
149
break;
150
case 'v':
151
v = vary_append(v, optarg);
152
break;
153
default:
154
usage();
155
}
156
argc -= optind;
157
argv += optind;
158
159
if (!rflag) {
160
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
161
err(1, "clock_gettime");
162
if (clock_getres(CLOCK_REALTIME, &tres) == -1)
163
err(1, "clock_getres");
164
}
165
166
format = "%+";
167
168
if (Rflag)
169
format = rfc2822_format;
170
171
/* allow the operands in any order */
172
if (*argv && **argv == '+') {
173
if (Iflag)
174
multipleformats();
175
format = *argv + 1;
176
++argv;
177
}
178
179
if (*argv) {
180
setthetime(fmt, *argv, jflag, &ts);
181
++argv;
182
} else if (fmt != NULL)
183
usage();
184
185
if (*argv && **argv == '+') {
186
if (Iflag)
187
multipleformats();
188
format = *argv + 1;
189
}
190
191
if (outzone != NULL && setenv("TZ", outzone, 1) != 0)
192
err(1, "setenv(TZ)");
193
lt = localtime(&ts.tv_sec);
194
if (lt == NULL)
195
errx(1, "invalid time");
196
badv = vary_apply(v, lt);
197
if (badv) {
198
fprintf(stderr, "%s: Cannot apply date adjustment\n",
199
badv->arg);
200
vary_destroy(v);
201
usage();
202
}
203
vary_destroy(v);
204
205
if (Iflag)
206
printisodate(lt, ts.tv_nsec, tres.tv_nsec);
207
208
if (format == rfc2822_format)
209
/*
210
* When using RFC 2822 datetime format, don't honor the
211
* locale.
212
*/
213
setlocale(LC_TIME, "C");
214
215
216
(void)strftime_ns(buf, sizeof(buf), format, lt,
217
ts.tv_nsec, tres.tv_nsec);
218
printdate(buf);
219
}
220
221
static void
222
printdate(const char *buf)
223
{
224
(void)printf("%s\n", buf);
225
if (fflush(stdout))
226
err(1, "stdout");
227
exit(EXIT_SUCCESS);
228
}
229
230
static void
231
printisodate(struct tm *lt, long nsec, long res)
232
{
233
const struct iso8601_fmt *it;
234
char fmtbuf[64], buf[64], tzbuf[8];
235
236
fmtbuf[0] = 0;
237
for (it = iso8601_fmts; it <= iso8601_selected; it++)
238
strlcat(fmtbuf, it->format_string, sizeof(fmtbuf));
239
240
(void)strftime_ns(buf, sizeof(buf), fmtbuf, lt, nsec, res);
241
242
if (iso8601_selected > iso8601_fmts) {
243
(void)strftime_ns(tzbuf, sizeof(tzbuf), "%z", lt, nsec, res);
244
memmove(&tzbuf[4], &tzbuf[3], 3);
245
tzbuf[3] = ':';
246
strlcat(buf, tzbuf, sizeof(buf));
247
}
248
249
printdate(buf);
250
}
251
252
#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
253
254
static void
255
setthetime(const char *fmt, const char *p, int jflag, struct timespec *ts)
256
{
257
struct utmpx utx;
258
struct tm *lt;
259
const char *dot, *t;
260
int century;
261
262
lt = localtime(&ts->tv_sec);
263
if (lt == NULL)
264
errx(1, "invalid time");
265
lt->tm_isdst = -1; /* divine correct DST */
266
267
if (fmt != NULL) {
268
t = strptime(p, fmt, lt);
269
if (t == NULL) {
270
fprintf(stderr, "Failed conversion of ``%s''"
271
" using format ``%s''\n", p, fmt);
272
badformat();
273
} else if (*t != '\0')
274
fprintf(stderr, "Warning: Ignoring %ld extraneous"
275
" characters in date string (%s)\n",
276
(long) strlen(t), t);
277
} else {
278
for (t = p, dot = NULL; *t; ++t) {
279
if (isdigit(*t))
280
continue;
281
if (*t == '.' && dot == NULL) {
282
dot = t;
283
continue;
284
}
285
badformat();
286
}
287
288
if (dot != NULL) { /* .ss */
289
dot++; /* *dot++ = '\0'; */
290
if (strlen(dot) != 2)
291
badformat();
292
lt->tm_sec = ATOI2(dot);
293
if (lt->tm_sec > 61)
294
badformat();
295
} else
296
lt->tm_sec = 0;
297
298
century = 0;
299
/* if p has a ".ss" field then let's pretend it's not there */
300
switch (strlen(p) - ((dot != NULL) ? 3 : 0)) {
301
case 12: /* cc */
302
lt->tm_year = ATOI2(p) * 100 - TM_YEAR_BASE;
303
century = 1;
304
/* FALLTHROUGH */
305
case 10: /* yy */
306
if (century)
307
lt->tm_year += ATOI2(p);
308
else {
309
lt->tm_year = ATOI2(p);
310
if (lt->tm_year < 69) /* hack for 2000 ;-} */
311
lt->tm_year += 2000 - TM_YEAR_BASE;
312
else
313
lt->tm_year += 1900 - TM_YEAR_BASE;
314
}
315
/* FALLTHROUGH */
316
case 8: /* mm */
317
lt->tm_mon = ATOI2(p);
318
if (lt->tm_mon > 12)
319
badformat();
320
--lt->tm_mon; /* time struct is 0 - 11 */
321
/* FALLTHROUGH */
322
case 6: /* dd */
323
lt->tm_mday = ATOI2(p);
324
if (lt->tm_mday > 31)
325
badformat();
326
/* FALLTHROUGH */
327
case 4: /* HH */
328
lt->tm_hour = ATOI2(p);
329
if (lt->tm_hour > 23)
330
badformat();
331
/* FALLTHROUGH */
332
case 2: /* MM */
333
lt->tm_min = ATOI2(p);
334
if (lt->tm_min > 59)
335
badformat();
336
break;
337
default:
338
badformat();
339
}
340
}
341
342
/* convert broken-down time to GMT clock time */
343
lt->tm_yday = -1;
344
ts->tv_sec = mktime(lt);
345
if (lt->tm_yday == -1)
346
errx(1, "nonexistent time");
347
ts->tv_nsec = 0;
348
349
if (!jflag) {
350
utx.ut_type = OLD_TIME;
351
memset(utx.ut_id, 0, sizeof(utx.ut_id));
352
(void)gettimeofday(&utx.ut_tv, NULL);
353
pututxline(&utx);
354
if (clock_settime(CLOCK_REALTIME, ts) != 0)
355
err(1, "clock_settime");
356
utx.ut_type = NEW_TIME;
357
(void)gettimeofday(&utx.ut_tv, NULL);
358
pututxline(&utx);
359
360
if ((p = getlogin()) == NULL)
361
p = "???";
362
syslog(LOG_AUTH | LOG_NOTICE, "date set by %s", p);
363
}
364
}
365
366
/*
367
* The strftime_ns function is a wrapper around strftime(3), which adds support
368
* for features absent from strftime(3). Currently, the only extra feature is
369
* support for %N, the nanosecond conversion specification.
370
*
371
* The functions scans the format string for the non-standard conversion
372
* specifications and replaces them with the date and time values before
373
* passing the format string to strftime(3). The handling of the non-standard
374
* conversion specifications happens before the call to strftime(3) to handle
375
* cases like "%%N" correctly ("%%N" should yield "%N" instead of nanoseconds).
376
*/
377
static size_t
378
strftime_ns(char * __restrict s, size_t maxsize, const char * __restrict format,
379
const struct tm * __restrict t, long nsec, long res)
380
{
381
size_t ret;
382
char *newformat;
383
char *oldformat;
384
const char *prefix;
385
const char *suffix;
386
const char *tok;
387
long number;
388
int i, len, prefixlen, width, zeroes;
389
bool seen_percent, seen_dash, seen_width;
390
391
seen_percent = false;
392
if ((newformat = strdup(format)) == NULL)
393
err(1, "strdup");
394
tok = newformat;
395
for (tok = newformat; *tok != '\0'; tok++) {
396
switch (*tok) {
397
case '%':
398
/*
399
* If the previous token was a percent sign,
400
* then there are two percent tokens in a row.
401
*/
402
if (seen_percent) {
403
seen_percent = false;
404
} else {
405
seen_percent = true;
406
seen_dash = seen_width = false;
407
prefixlen = tok - newformat;
408
width = 0;
409
}
410
break;
411
case 'N':
412
if (!seen_percent)
413
break;
414
oldformat = newformat;
415
prefix = oldformat;
416
suffix = tok + 1;
417
/*
418
* Prepare the number we are about to print. If
419
* the requested width is less than 9, we need to
420
* cut off the least significant digits. If it is
421
* more than 9, we will have to append zeroes.
422
*/
423
if (seen_dash) {
424
/*
425
* Calculate number of singificant digits
426
* based on res which is the clock's
427
* resolution in nanoseconds.
428
*/
429
for (width = 9, number = res;
430
width > 0 && number > 0;
431
width--, number /= 10)
432
/* nothing */;
433
}
434
number = nsec;
435
zeroes = 0;
436
if (width == 0) {
437
width = 9;
438
} else if (width > 9) {
439
zeroes = width - 9;
440
width = 9;
441
} else {
442
for (i = 0; i < 9 - width; i++)
443
number /= 10;
444
}
445
/*
446
* Construct a new format string from the prefix
447
* (i.e., the part of the old format from its
448
* beginning to the currently handled "%N"
449
* conversion specification), the nanoseconds, and
450
* the suffix (i.e., the part of the old format
451
* from the next token to the end).
452
*/
453
asprintf(&newformat, "%.*s%.*ld%.*d%n%s", prefixlen,
454
prefix, width, number, zeroes, 0, &len, suffix);
455
if (newformat == NULL)
456
err(1, "asprintf");
457
free(oldformat);
458
tok = newformat + len - 1;
459
seen_percent = false;
460
break;
461
case '-':
462
if (seen_percent) {
463
if (seen_dash || seen_width) {
464
seen_percent = false;
465
break;
466
}
467
seen_dash = true;
468
}
469
break;
470
case '0': case '1': case '2': case '3': case '4':
471
case '5': case '6': case '7': case '8': case '9':
472
if (seen_percent) {
473
if (seen_dash) {
474
seen_percent = false;
475
break;
476
}
477
width = width * 10 + *tok - '0';
478
seen_width = true;
479
}
480
break;
481
default:
482
seen_percent = false;
483
break;
484
}
485
}
486
487
ret = strftime(s, maxsize, newformat, t);
488
free(newformat);
489
return (ret);
490
}
491
492
static void
493
badformat(void)
494
{
495
warnx("illegal time format");
496
usage();
497
}
498
499
static void
500
iso8601_usage(const char *badarg)
501
{
502
errx(1, "invalid argument '%s' for -I", badarg);
503
}
504
505
static void
506
multipleformats(void)
507
{
508
errx(1, "multiple output formats specified");
509
}
510
511
static void
512
usage(void)
513
{
514
(void)fprintf(stderr, "%s\n%s\n%s\n",
515
"usage: date [-jnRu] [-I[date|hours|minutes|seconds|ns]] [-f input_fmt]",
516
" "
517
"[ -z output_zone ] [-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]",
518
" "
519
"[[[[[[cc]yy]mm]dd]HH]MM[.SS] | new_date] [+output_fmt]"
520
);
521
exit(1);
522
}
523
524