1 | /* date - Display (or set) the date and time Author: V. Archer */
|
---|
2 |
|
---|
3 | #include <sys/types.h>
|
---|
4 | #include <ctype.h>
|
---|
5 | #include <stddef.h>
|
---|
6 | #include <stdlib.h>
|
---|
7 | #include <time.h>
|
---|
8 | #include <string.h>
|
---|
9 | #include <unistd.h>
|
---|
10 |
|
---|
11 | #define MIN 60L /* # seconds in a minute */
|
---|
12 | #define HOUR (60 * MIN) /* # seconds in an hour */
|
---|
13 | #define DAY (24 * HOUR) /* # seconds in a day */
|
---|
14 | #define YEAR (365 * DAY) /* # seconds in a (non-leap) year */
|
---|
15 |
|
---|
16 | int qflag, uflag, sflag, Sflag;
|
---|
17 |
|
---|
18 | /* Default output file descriptor.
|
---|
19 | */
|
---|
20 | int outfd = 1;
|
---|
21 |
|
---|
22 | _PROTOTYPE(int main, (int argc, char **argv));
|
---|
23 | _PROTOTYPE(void putchar, (int c));
|
---|
24 | _PROTOTYPE(void pstring, (char *s, int len));
|
---|
25 | _PROTOTYPE(void pldecimal, (unsigned long d, int digits));
|
---|
26 | _PROTOTYPE(void pdecimal, (int d, int digits));
|
---|
27 | _PROTOTYPE(void fmtdate, (char *format, time_t t, struct tm *p));
|
---|
28 | _PROTOTYPE(time_t make_time, (char *t));
|
---|
29 | _PROTOTYPE(struct tm *september, (time_t *tp));
|
---|
30 | _PROTOTYPE(void usage, (void));
|
---|
31 |
|
---|
32 | /* Main module. Handles P1003.2 date and system administrator's date. The
|
---|
33 | * date entered should be given GMT, regardless of the system's TZ!
|
---|
34 | */
|
---|
35 | int main(argc, argv)
|
---|
36 | int argc;
|
---|
37 | char **argv;
|
---|
38 | {
|
---|
39 | time_t t;
|
---|
40 | struct tm *tm;
|
---|
41 | char *format;
|
---|
42 | char time_buf[40];
|
---|
43 | int n;
|
---|
44 | int i;
|
---|
45 |
|
---|
46 | time(&t);
|
---|
47 |
|
---|
48 | i = 1;
|
---|
49 | while (i < argc && argv[i][0] == '-') {
|
---|
50 | char *opt = argv[i++] + 1, *end;
|
---|
51 |
|
---|
52 | if (opt[0] == '-' && opt[1] == 0) break;
|
---|
53 |
|
---|
54 | while (*opt != 0) switch (*opt++) {
|
---|
55 | case 'q':
|
---|
56 | qflag = 1;
|
---|
57 | break;
|
---|
58 | case 's':
|
---|
59 | sflag = 1;
|
---|
60 | break;
|
---|
61 | case 'u':
|
---|
62 | uflag = 1;
|
---|
63 | break;
|
---|
64 | case 'S':
|
---|
65 | Sflag = 1;
|
---|
66 | break;
|
---|
67 | case 't':
|
---|
68 | /* (obsolete, now -r) */
|
---|
69 | case 'r':
|
---|
70 | if (*opt == 0) {
|
---|
71 | if (i == argc) usage();
|
---|
72 | opt = argv[i++];
|
---|
73 | }
|
---|
74 | t = strtoul(opt, &end, 10);
|
---|
75 | if (*end != 0) usage();
|
---|
76 | opt = "";
|
---|
77 | break;
|
---|
78 | default:
|
---|
79 | usage();
|
---|
80 | }
|
---|
81 | }
|
---|
82 |
|
---|
83 | if (!qflag && i < argc && ('0' <= argv[i][0] && argv[i][0] <= '9')) {
|
---|
84 | t = make_time(argv[i++]);
|
---|
85 | sflag = 1;
|
---|
86 | }
|
---|
87 |
|
---|
88 | format = "%c";
|
---|
89 | if (i < argc && argv[i][0] == '+') format = argv[i++] + 1;
|
---|
90 |
|
---|
91 | if (i != argc) usage();
|
---|
92 |
|
---|
93 | if (qflag) {
|
---|
94 | pstring("\nPlease enter date: MMDDYYhhmmss. Then hit the RETURN key.\n", -1);
|
---|
95 | n = read(0, time_buf, sizeof(time_buf));
|
---|
96 | if (n > 0 && time_buf[n-1] == '\n') n--;
|
---|
97 | if (n >= 0) time_buf[n] = 0;
|
---|
98 | t = make_time(time_buf);
|
---|
99 | sflag = 1;
|
---|
100 | }
|
---|
101 |
|
---|
102 | if (sflag && stime(&t) != 0) {
|
---|
103 | outfd = 2;
|
---|
104 | pstring("No permission to set time\n", -1);
|
---|
105 | return(1);
|
---|
106 | }
|
---|
107 |
|
---|
108 | tm = Sflag ? september(&t) : uflag ? gmtime(&t) : localtime(&t);
|
---|
109 |
|
---|
110 | fmtdate(format, t, tm);
|
---|
111 | putchar('\n');
|
---|
112 | return(0);
|
---|
113 | }
|
---|
114 |
|
---|
115 | /* Replacement for stdio putchar().
|
---|
116 | */
|
---|
117 | void putchar(c)
|
---|
118 | int c;
|
---|
119 | {
|
---|
120 | static char buf[1024];
|
---|
121 | static char *bp = buf;
|
---|
122 |
|
---|
123 | if (c != 0) *bp++ = c;
|
---|
124 | if (c == 0 || c == '\n' || bp == buf + sizeof(buf)) {
|
---|
125 | write(outfd, buf, bp - buf);
|
---|
126 | bp = buf;
|
---|
127 | }
|
---|
128 | }
|
---|
129 |
|
---|
130 | /* Internal function that prints a n-digits number. Replaces stdio in our
|
---|
131 | * specific case.
|
---|
132 | */
|
---|
133 | void pldecimal(d, digits)
|
---|
134 | unsigned long d;
|
---|
135 | int digits;
|
---|
136 | {
|
---|
137 | digits--;
|
---|
138 | if (d > 9 || digits > 0) pldecimal(d / 10, digits);
|
---|
139 | putchar('0' + (d % 10));
|
---|
140 | }
|
---|
141 |
|
---|
142 | void pdecimal(d, digits)
|
---|
143 | int d, digits;
|
---|
144 | {
|
---|
145 | pldecimal((unsigned long) d, digits);
|
---|
146 | }
|
---|
147 |
|
---|
148 | /* Internal function that prints a fixed-size string. Replaces stdio in our
|
---|
149 | * specific case.
|
---|
150 | */
|
---|
151 | void pstring(s, len)
|
---|
152 | char *s;
|
---|
153 | int len;
|
---|
154 | {
|
---|
155 | while (*s)
|
---|
156 | if (len--)
|
---|
157 | putchar(*s++);
|
---|
158 | else
|
---|
159 | break;
|
---|
160 | }
|
---|
161 |
|
---|
162 | /* Format the date, using the given locale string. A special case is the
|
---|
163 | * TZ which might be a sign followed by four digits (New format time zone).
|
---|
164 | */
|
---|
165 | void fmtdate(format, t, p)
|
---|
166 | char *format;
|
---|
167 | time_t t;
|
---|
168 | struct tm *p;
|
---|
169 | {
|
---|
170 | int i;
|
---|
171 | char *s;
|
---|
172 | static char *wday[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
|
---|
173 | "Thursday", "Friday", "Saturday"};
|
---|
174 | static char *month[] = {"January", "February", "March", "April",
|
---|
175 | "May", "June", "July", "August",
|
---|
176 | "September", "October", "November", "December"};
|
---|
177 |
|
---|
178 | while (*format)
|
---|
179 | if (*format == '%') {
|
---|
180 | switch (*++format) {
|
---|
181 | case 'A':
|
---|
182 | pstring(wday[p->tm_wday], -1);
|
---|
183 | break;
|
---|
184 | case 'B':
|
---|
185 | pstring(month[p->tm_mon], -1);
|
---|
186 | break;
|
---|
187 | case 'D':
|
---|
188 | pdecimal(p->tm_mon + 1, 2);
|
---|
189 | putchar('/');
|
---|
190 | pdecimal(p->tm_mday, 2);
|
---|
191 | putchar('/');
|
---|
192 | case 'y':
|
---|
193 | pdecimal(p->tm_year % 100, 2);
|
---|
194 | break;
|
---|
195 | case 'H':
|
---|
196 | pdecimal(p->tm_hour, 2);
|
---|
197 | break;
|
---|
198 | case 'I':
|
---|
199 | i = p->tm_hour % 12;
|
---|
200 | pdecimal(i ? i : 12, 2);
|
---|
201 | break;
|
---|
202 | case 'M':
|
---|
203 | pdecimal(p->tm_min, 2);
|
---|
204 | break;
|
---|
205 | case 'X':
|
---|
206 | case 'T':
|
---|
207 | pdecimal(p->tm_hour, 2);
|
---|
208 | putchar(':');
|
---|
209 | pdecimal(p->tm_min, 2);
|
---|
210 | putchar(':');
|
---|
211 | case 'S':
|
---|
212 | pdecimal(p->tm_sec, 2);
|
---|
213 | break;
|
---|
214 | case 'U':
|
---|
215 | pdecimal((p->tm_yday - p->tm_wday + 13) / 7, 2);
|
---|
216 | break;
|
---|
217 | case 'W':
|
---|
218 | if (--(p->tm_wday) < 0) p->tm_wday = 6;
|
---|
219 | pdecimal((p->tm_yday - p->tm_wday + 13) / 7, 2);
|
---|
220 | if (++(p->tm_wday) > 6) p->tm_wday = 0;
|
---|
221 | break;
|
---|
222 | case 'Y':
|
---|
223 | pdecimal(p->tm_year + 1900, 4);
|
---|
224 | break;
|
---|
225 | case 'Z':
|
---|
226 | if (uflag) {
|
---|
227 | s = "GMT";
|
---|
228 | } else {
|
---|
229 | s = (p->tm_isdst == 1) ? tzname[1] : tzname[0];
|
---|
230 | }
|
---|
231 | pstring(s, strlen(s));
|
---|
232 | break;
|
---|
233 | case 'a':
|
---|
234 | pstring(wday[p->tm_wday], 3);
|
---|
235 | break;
|
---|
236 | case 'b':
|
---|
237 | case 'h':
|
---|
238 | pstring(month[p->tm_mon], 3);
|
---|
239 | break;
|
---|
240 | case 'c':
|
---|
241 | if (!(s = getenv("LC_TIME")))
|
---|
242 | s = "%a %b %e %T %Z %Y";
|
---|
243 | fmtdate(s, t, p);
|
---|
244 | break;
|
---|
245 | case 'd':
|
---|
246 | pdecimal(p->tm_mday, 2);
|
---|
247 | break;
|
---|
248 | case 'e':
|
---|
249 | if (p->tm_mday < 10) putchar(' ');
|
---|
250 | pdecimal(p->tm_mday, 1);
|
---|
251 | break;
|
---|
252 | case 'j':
|
---|
253 | pdecimal(p->tm_yday + 1, 3);
|
---|
254 | break;
|
---|
255 | case 'm':
|
---|
256 | pdecimal(p->tm_mon + 1, 2);
|
---|
257 | break;
|
---|
258 | case 'n': putchar('\n'); break;
|
---|
259 | case 'p':
|
---|
260 | if (p->tm_hour < 12)
|
---|
261 | putchar('A');
|
---|
262 | else
|
---|
263 | putchar('P');
|
---|
264 | putchar('M');
|
---|
265 | break;
|
---|
266 | case 'r':
|
---|
267 | fmtdate("%I:%M:%S %p", t, p);
|
---|
268 | break;
|
---|
269 | case 's':
|
---|
270 | pldecimal((unsigned long) t, 0);
|
---|
271 | break;
|
---|
272 | case 't': putchar('\t'); break;
|
---|
273 | case 'w':
|
---|
274 | putchar('0' + p->tm_wday);
|
---|
275 | break;
|
---|
276 | case 'x':
|
---|
277 | fmtdate("%B %e %Y", t, p);
|
---|
278 | break;
|
---|
279 | case '%': putchar('%'); break;
|
---|
280 | case '\0': format--;
|
---|
281 | }
|
---|
282 | format++;
|
---|
283 | } else
|
---|
284 | putchar(*format++);
|
---|
285 | }
|
---|
286 |
|
---|
287 | /* Convert a local date string into GMT time in seconds. */
|
---|
288 | time_t make_time(t)
|
---|
289 | char *t;
|
---|
290 | {
|
---|
291 | struct tm tm; /* user specified time */
|
---|
292 | time_t now; /* current time */
|
---|
293 | int leap; /* current year is leap year */
|
---|
294 | int i; /* general index */
|
---|
295 | int fld; /* number of fields */
|
---|
296 | int f[6]; /* time fields */
|
---|
297 | static int days_per_month[2][12] = {
|
---|
298 | { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
|
---|
299 | { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }};
|
---|
300 |
|
---|
301 | /* Get current time just in case */
|
---|
302 | now = time((time_t *) 0);
|
---|
303 | tm = *localtime(&now);
|
---|
304 | tm.tm_sec = 0;
|
---|
305 | tm.tm_mon++;
|
---|
306 | tm.tm_year %= 100;
|
---|
307 |
|
---|
308 | /* Parse the time */
|
---|
309 | #if '0'+1 != '1' || '1'+1 != '2' || '2'+1 != '3' || '3'+1 != '4' || \
|
---|
310 | '4'+1 != '5' || '5'+1 != '6' || '6'+1 != '7' || '7'+1 != '8' || '8'+1 != '9'
|
---|
311 | << Code unsuitable for character collating sequence >>
|
---|
312 | #endif
|
---|
313 |
|
---|
314 | for (fld = 0; fld < sizeof(f)/sizeof(f[0]); fld++) {
|
---|
315 | if (*t == 0) break;
|
---|
316 | f[fld] = 0;
|
---|
317 | for (i = 0; i < 2; i++, t++) {
|
---|
318 | if (*t < '0' || *t > '9') usage();
|
---|
319 | f[fld] = f[fld] * 10 + *t - '0';
|
---|
320 | }
|
---|
321 | }
|
---|
322 |
|
---|
323 | switch (fld) {
|
---|
324 | case 2:
|
---|
325 | tm.tm_hour = f[0]; tm.tm_min = f[1]; break;
|
---|
326 |
|
---|
327 | case 3:
|
---|
328 | tm.tm_hour = f[0]; tm.tm_min = f[1]; tm.tm_sec = f[2];
|
---|
329 | break;
|
---|
330 |
|
---|
331 | case 5:
|
---|
332 | tm.tm_mon = f[0]; tm.tm_mday = f[1]; tm.tm_year = f[2];
|
---|
333 | tm.tm_hour = f[3]; tm.tm_min = f[4];
|
---|
334 | break;
|
---|
335 |
|
---|
336 | case 6:
|
---|
337 | tm.tm_mon = f[0]; tm.tm_mday = f[1]; tm.tm_year = f[2];
|
---|
338 | tm.tm_hour = f[3]; tm.tm_min = f[4]; tm.tm_sec = f[5];
|
---|
339 | break;
|
---|
340 |
|
---|
341 | default:
|
---|
342 | usage();
|
---|
343 | }
|
---|
344 |
|
---|
345 | /* Convert the time into seconds since 1 January 1970 */
|
---|
346 | if (tm.tm_year < 70)
|
---|
347 | tm.tm_year += 100;
|
---|
348 | leap = (tm.tm_year % 4 == 0 && tm.tm_year % 400 != 0);
|
---|
349 | if (tm.tm_mon < 1 || tm.tm_mon > 12 ||
|
---|
350 | tm.tm_mday < 1 || tm.tm_mday > days_per_month[leap][tm.tm_mon-1] ||
|
---|
351 | tm.tm_hour > 23 || tm.tm_min > 59) {
|
---|
352 | outfd = 2;
|
---|
353 | pstring("Illegal date format\n", -1);
|
---|
354 | exit(1);
|
---|
355 | }
|
---|
356 |
|
---|
357 | /* Convert the time into Minix time - zone independent code */
|
---|
358 | {
|
---|
359 | time_t utctime; /* guess at unix time */
|
---|
360 | time_t nextbit; /* next bit to try */
|
---|
361 | int rv; /* result of try */
|
---|
362 | struct tm *tmp; /* local time conversion */
|
---|
363 |
|
---|
364 | #define COMPARE(a,b) ((a) != (b)) ? ((a) - (b)) :
|
---|
365 |
|
---|
366 | utctime = 1;
|
---|
367 | do {
|
---|
368 | nextbit = utctime;
|
---|
369 | utctime = nextbit << 1;
|
---|
370 | } while (utctime >= 1);
|
---|
371 |
|
---|
372 | for (utctime = 0; ; nextbit >>= 1) {
|
---|
373 |
|
---|
374 | utctime |= nextbit;
|
---|
375 | tmp = localtime(&utctime);
|
---|
376 | if (tmp == 0) continue;
|
---|
377 |
|
---|
378 | rv = COMPARE(tmp->tm_year, tm.tm_year)
|
---|
379 | COMPARE(tmp->tm_mon + 1, tm.tm_mon)
|
---|
380 | COMPARE(tmp->tm_mday, tm.tm_mday)
|
---|
381 | COMPARE(tmp->tm_hour, tm.tm_hour)
|
---|
382 | COMPARE(tmp->tm_min, tm.tm_min)
|
---|
383 | COMPARE(tmp->tm_sec, tm.tm_sec)
|
---|
384 | 0;
|
---|
385 |
|
---|
386 | if (rv > 0)
|
---|
387 | utctime &= ~nextbit;
|
---|
388 | else if (rv == 0)
|
---|
389 | break;
|
---|
390 |
|
---|
391 | if (nextbit == 0) {
|
---|
392 | uflag = 1;
|
---|
393 | outfd = 2;
|
---|
394 | pstring("Inexact conversion to UTC from ", -1);
|
---|
395 | fmtdate("%c\n", utctime, localtime(&utctime) );
|
---|
396 | exit(1);
|
---|
397 | }
|
---|
398 | }
|
---|
399 | return utctime;
|
---|
400 | }
|
---|
401 | }
|
---|
402 |
|
---|
403 | /* Correct the time to the reckoning of Eternal September. */
|
---|
404 | struct tm *september(tp)
|
---|
405 | time_t *tp;
|
---|
406 | {
|
---|
407 | time_t t;
|
---|
408 | int days;
|
---|
409 | struct tm *tm;
|
---|
410 |
|
---|
411 | tm = localtime(tp);
|
---|
412 |
|
---|
413 | t = *tp - (tm->tm_hour - 12) * 3600L; /* No zone troubles around noon. */
|
---|
414 | days = 0;
|
---|
415 |
|
---|
416 | while (tm->tm_year > 93 || (tm->tm_year == 93 && tm->tm_mon >= 8)) {
|
---|
417 | /* Step back a year or a month. */
|
---|
418 | days += tm->tm_year > 93 ? tm->tm_yday+1 : tm->tm_mday;
|
---|
419 | t = *tp - days * (24 * 3600L);
|
---|
420 |
|
---|
421 | tm = localtime(&t);
|
---|
422 | }
|
---|
423 |
|
---|
424 | if (days > 0) {
|
---|
425 | tm = localtime(tp);
|
---|
426 | tm->tm_mday = days;
|
---|
427 | tm->tm_year = 93;
|
---|
428 | tm->tm_mon = 8;
|
---|
429 | #if SANITY
|
---|
430 | t = mktime(tm);
|
---|
431 | tm = localtime(&t);
|
---|
432 | #endif
|
---|
433 | }
|
---|
434 | return tm;
|
---|
435 | }
|
---|
436 |
|
---|
437 | /* (Extended) Posix prototype of date. */
|
---|
438 | void usage()
|
---|
439 | {
|
---|
440 | outfd = 2;
|
---|
441 | pstring("Usage: date [-qsuS] [-r seconds] [[MMDDYY]hhmm[ss]] [+format]\n", -1);
|
---|
442 | exit(1);
|
---|
443 | }
|
---|