logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/

date.c (9971B)


  1. // utils-std: Collection of commonly available Unix tools
  2. // SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me>
  3. // SPDX-License-Identifier: MPL-2.0
  4. #define _DEFAULT_SOURCE // tm_gmtoff/tm_zone
  5. #define _POSIX_C_SOURCE 200809L
  6. #define _XOPEN_SOURCE 700 // strptime is in XSI
  7. #include "../config.h"
  8. #include "../lib/absu.h"
  9. #include "../libutils/datetime_parse.h" /* datetime_parse */
  10. #include "../libutils/getopt_nolong.h"
  11. #include <assert.h>
  12. #include <errno.h>
  13. #include <inttypes.h> /* PRId64 */
  14. #include <locale.h> /* setlocale() */
  15. #include <stdbool.h>
  16. #include <stdio.h> /* BUFSIZ, fprintf(), puts() */
  17. #include <stdlib.h> /* strtol() */
  18. #include <string.h> /* strerror */
  19. #include <time.h> /* time, localtime, tm, strftime, strptime, clock_settime */
  20. #include <unistd.h> /* getopt(), opt… */
  21. #ifdef HAS_GETOPT_LONG
  22. #include <getopt.h>
  23. #endif
  24. const char *argv0 = "date";
  25. static size_t
  26. date_strftime(char *restrict buf,
  27. size_t buflen,
  28. const char *restrict fmt,
  29. const struct tm *restrict tm,
  30. long nsec)
  31. {
  32. size_t fmtlen = strlen(fmt);
  33. size_t printed = 0;
  34. if(fmtlen == 0)
  35. {
  36. buf[0] = '\0';
  37. return 0;
  38. }
  39. for(size_t i = 0; i < fmtlen;)
  40. {
  41. // size taken from musl strftime
  42. static char fmt_buf[100] = "";
  43. size_t fmt_bufi = 0;
  44. if(fmt[i] == '%')
  45. {
  46. fmt_buf[fmt_bufi++] = fmt[i++];
  47. if(fmt[i] == '%') // handle '%%'
  48. {
  49. *buf = '%';
  50. buf++;
  51. buflen--;
  52. printed++;
  53. i++;
  54. continue;
  55. }
  56. }
  57. for(; fmt[i] != '%' && i < fmtlen;)
  58. {
  59. fmt_buf[fmt_bufi++] = fmt[i++];
  60. assert(fmt_bufi < 100);
  61. }
  62. fmt_buf[fmt_bufi] = '\0';
  63. size_t fmt_bufused = 0;
  64. if(fmt_buf[0] == '%' && fmt_buf[1] == ':' && fmt_buf[2] == 'z')
  65. {
  66. size_t got = snprintf(
  67. buf, buflen, "%+.2ld:%.2u", tm->tm_gmtoff / 3600, absu(tm->tm_gmtoff % 3600 / 60));
  68. if(got == 0) return got;
  69. buf += got;
  70. buflen -= got;
  71. printed += got;
  72. fmt_bufused += 3;
  73. }
  74. else if(fmt_buf[0] == '%' && fmt_buf[1] == ':' && fmt_buf[2] == ':' && fmt_buf[3] == 'z')
  75. {
  76. unsigned tzhr = absu(tm->tm_gmtoff % 3600 / 60);
  77. unsigned tzsec = absu(tm->tm_gmtoff % 3600 % 60);
  78. size_t got = snprintf(buf, buflen, "%+.2ld:%.2u:%.2u", tm->tm_gmtoff / 3600, tzhr, tzsec);
  79. if(got == 0) return got;
  80. buf += got;
  81. buflen -= got;
  82. printed += got;
  83. fmt_bufused += 4;
  84. }
  85. else if(fmt_buf[0] == '%' && fmt_buf[1] == 'N')
  86. {
  87. size_t got = snprintf(buf, buflen, "%09ld", nsec);
  88. if(got == 0) return got;
  89. buf += got;
  90. buflen -= got;
  91. printed += got;
  92. fmt_bufused += 2;
  93. }
  94. if(fmt_bufi > fmt_bufused)
  95. {
  96. size_t got = strftime(buf, buflen, fmt_buf + fmt_bufused, tm);
  97. if(got == 0) return got;
  98. buf += got;
  99. buflen -= got;
  100. printed += got;
  101. }
  102. }
  103. return printed;
  104. }
  105. static void
  106. usage(void)
  107. {
  108. fprintf(stderr, "\
  109. Usage:\n\
  110. date [-jRu] [-I iso_fmt] [-d datetime | -r epoch] [+format]\n\
  111. date [-jRu] [-I iso_fmt] mmddHHMM[[CC]yy] [+format]\n\
  112. date [-jRu] [-I iso_fmt] -f now_format now [+format]\n\
  113. ");
  114. }
  115. int
  116. main(int argc, char *argv[])
  117. {
  118. char outstr[BUFSIZ] = "";
  119. struct tm tm = {
  120. .tm_year = 0,
  121. .tm_mon = 0,
  122. .tm_mday = 0,
  123. .tm_hour = 0,
  124. .tm_min = 0,
  125. .tm_sec = 0,
  126. .tm_isdst = -1, // unknown if DST is in effect
  127. .tm_gmtoff = 0,
  128. .tm_zone = NULL,
  129. };
  130. struct timespec tp = {
  131. .tv_sec = 0,
  132. .tv_nsec = 0,
  133. };
  134. const char *format = "%c";
  135. const char *input_format = NULL;
  136. int uflag = 0;
  137. int dflag = 0, rflag = 0;
  138. bool jflag = false;
  139. bool settime = false;
  140. char *lc_all = setlocale(LC_ALL, "");
  141. if(lc_all == NULL)
  142. {
  143. fprintf(stderr,
  144. "%s: warning: Failed loading locales. setlocale(LC_ALL, \"\"): %s\n",
  145. argv0,
  146. strerror(errno));
  147. }
  148. errno = 0;
  149. if(clock_gettime(CLOCK_REALTIME, &tp) != 0)
  150. {
  151. fprintf(stderr, "%s: error: Failed getting current time: %s\n", argv0, strerror(errno));
  152. return 1;
  153. }
  154. #ifdef HAS_GETOPT_LONG
  155. // Strictly for GNUisms compatibility so no long-only options
  156. // clang-format off
  157. static struct option opts[] = {
  158. {"date", required_argument, NULL, 'd'},
  159. {"iso-8601", optional_argument, NULL, 'I'},
  160. {"rfc-email", no_argument, NULL, 'R'},
  161. {"universal", no_argument, NULL, 'u'},
  162. {"utc", no_argument, NULL, 'u'},
  163. {0, 0, 0, 0},
  164. };
  165. // clang-format on
  166. // Need + as first character to get POSIX-style option parsing
  167. // Assume all getopt_long implementations support :: for optional args
  168. for(int c = -1; (c = getopt_long(argc, argv, "+:d:f:I::jr:Ru", opts, NULL)) != -1;)
  169. #else
  170. for(int c = -1; (c = getopt_nolong(argc, argv, ":d:f:I:jr:Ru")) != -1;)
  171. #endif
  172. {
  173. const char *errstr = NULL;
  174. switch(c)
  175. {
  176. case 'd': /* Custom datetime */
  177. if(input_format != NULL)
  178. {
  179. fprintf(stderr, "%s: error: Cannot both use '-d' and '-f'\n", argv0);
  180. return 1;
  181. }
  182. if(rflag == 1)
  183. {
  184. fprintf(stderr, "%s: error: Cannot both use '-d' and '-r'\n", argv0);
  185. return 1;
  186. }
  187. datetime_parse(optarg, &tm, &tp.tv_nsec, &errstr);
  188. dflag = 1;
  189. if(errstr != NULL)
  190. {
  191. fprintf(stderr, "%s: error: datetime_parse(\"%s\", …): %s\n", argv0, optarg, errstr);
  192. return 1;
  193. }
  194. tp.tv_sec = mktime_tz(&tm);
  195. if(tp.tv_sec == (time_t)-1)
  196. {
  197. fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
  198. return 1;
  199. }
  200. errno = 0;
  201. break;
  202. case 'f': /* input datetime format */
  203. if(dflag == 1)
  204. {
  205. fprintf(stderr, "%s: error: Cannot both use '-d' and '-f'\n", argv0);
  206. return 1;
  207. }
  208. if(rflag == 1)
  209. {
  210. fprintf(stderr, "%s: error: Cannot both use '-f' and '-r'\n", argv0);
  211. return 1;
  212. }
  213. input_format = optarg;
  214. settime = true;
  215. break;
  216. case 'r': /* seconds relative to epoch */
  217. {
  218. if(input_format != NULL)
  219. {
  220. fprintf(stderr, "%s: error: Cannot both use '-f' and '-r'\n", argv0);
  221. return 1;
  222. }
  223. if(dflag == 1)
  224. {
  225. fprintf(stderr, "%s: error: Cannot both use '-d' and '-r'\n", argv0);
  226. return 1;
  227. }
  228. char *endptr = NULL;
  229. errno = 0;
  230. tp.tv_sec = strtol(optarg, &endptr, 10);
  231. if(errno != 0)
  232. {
  233. fprintf(stderr, "%s: error: Failed parsing '-r %s'\n", argv0, optarg);
  234. return 1;
  235. }
  236. if(!(endptr == NULL || *endptr == '\0'))
  237. {
  238. fprintf(stderr, "%s: error: Invalid characters in '-r %s': %s\n", argv0, optarg, endptr);
  239. return 1;
  240. }
  241. break;
  242. }
  243. case 'R': /* Email (RFC 5322) format */
  244. format = "%a, %d %b %Y %H:%M:%S %z";
  245. break;
  246. case 'u': /* UTC timezone */
  247. uflag++;
  248. setenv("TZ", "UTC", 1);
  249. tzset();
  250. break;
  251. case 'I': /* ISO 8601 */
  252. if(optarg)
  253. {
  254. /* note: %:z (±ZZ:ZZ) and %N (nanoseconds) are date(1) GNU-isms absent from C libraries including glibc */
  255. switch(optarg[0])
  256. {
  257. case 'h': // hours
  258. format = "%Y-%m-%dT%H%:z";
  259. break;
  260. case 'm': // minutes
  261. format = "%Y-%m-%dT%H:%M%:z";
  262. break;
  263. case 's': // seconds
  264. format = "%Y-%m-%dT%H:%M:%S%:z";
  265. break;
  266. case 'n': // ns, nanoseconds
  267. format = "%Y-%m-%dT%H:%M:%S,%N%:z";
  268. break;
  269. case 'd': // date
  270. default:
  271. format = "%Y-%m-%d";
  272. break;
  273. }
  274. }
  275. else
  276. format = "%Y-%m-%d";
  277. break;
  278. case 'j':
  279. jflag = true;
  280. break;
  281. case ':':
  282. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  283. usage();
  284. return 1;
  285. case '?':
  286. GETOPT_UNKNOWN_OPT
  287. usage();
  288. return 1;
  289. }
  290. }
  291. argc -= optind;
  292. argv += optind;
  293. if(uflag)
  294. {
  295. if(gmtime_r(&tp.tv_sec, &tm) == NULL)
  296. {
  297. fprintf(stderr, "%s: error: gmtime_r: %s\n", argv0, strerror(errno));
  298. return 1;
  299. }
  300. }
  301. else
  302. {
  303. if(localtime_r(&tp.tv_sec, &tm) == NULL)
  304. {
  305. fprintf(stderr, "%s: error: localtime_r: %s\n", argv0, strerror(errno));
  306. return 1;
  307. }
  308. }
  309. if(argc > 0 && input_format != NULL)
  310. {
  311. char *res = strptime(argv[0], input_format, &tm);
  312. if(res == NULL)
  313. {
  314. fprintf(stderr,
  315. "%s: error: strptime(\"%s\", \"%s\", …) as passed by '-f' option failed\n",
  316. argv0,
  317. argv[0],
  318. input_format);
  319. return 1;
  320. }
  321. tp.tv_sec = mktime_tz(&tm);
  322. tp.tv_nsec = 0;
  323. if(tp.tv_sec == (time_t)-1)
  324. {
  325. fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
  326. return 1;
  327. }
  328. errno = 0;
  329. argv++;
  330. argc--;
  331. }
  332. if(input_format == NULL && argc > 0 && *argv && **argv != '+')
  333. {
  334. const char *fmt = "%m%d%H%M";
  335. char *s = strptime(argv[0], fmt, &tm);
  336. if(s == NULL)
  337. {
  338. fprintf(stderr, "%s: error: strptime(\"%s\", \"%s\", …) returned NULL\n", argv0, *argv, fmt);
  339. return 1;
  340. }
  341. size_t rem = strlen(s);
  342. if(rem > 0)
  343. {
  344. switch(rem)
  345. {
  346. case 2:
  347. fmt = "%y";
  348. break;
  349. case 4:
  350. fmt = "%Y";
  351. break;
  352. default:
  353. fprintf(stderr,
  354. "%s: error: Got %zu trailing characters after \"%s\" for mmddHHMM\n",
  355. argv0,
  356. rem,
  357. fmt);
  358. return 1;
  359. }
  360. s = strptime(s, fmt, &tm);
  361. if(s == NULL)
  362. {
  363. fprintf(
  364. stderr, "%s: error: strptime(\"%s\", \"%s\", …) returned NULL\n", argv0, *argv, fmt);
  365. return 1;
  366. }
  367. }
  368. tp.tv_sec = mktime(&tm);
  369. tp.tv_nsec = 0;
  370. if(tp.tv_sec == (time_t)-1)
  371. {
  372. fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
  373. return 1;
  374. }
  375. errno = 0;
  376. argv++;
  377. argc--;
  378. settime = true;
  379. }
  380. if(settime && !jflag)
  381. {
  382. if(clock_settime(CLOCK_REALTIME, &tp) != 0)
  383. {
  384. fprintf(stderr,
  385. "%s: error: clock_settime(CLOCK_REALTIME, {%" PRId64 ", %" PRId64 "}): %s\n",
  386. argv0,
  387. (int64_t)tp.tv_sec,
  388. (int64_t)tp.tv_nsec,
  389. strerror(errno));
  390. return 1;
  391. }
  392. }
  393. if(argc > 0 && *argv && **argv == '+')
  394. {
  395. format = *argv + 1;
  396. argv++;
  397. argc--;
  398. }
  399. errno = 0;
  400. if(date_strftime(outstr, sizeof(outstr), format, &tm, tp.tv_nsec) == 0 && errno != 0)
  401. {
  402. fprintf(stderr, "%s: error: Failed formatting time: %s\n", argv0, strerror(errno));
  403. return 1;
  404. }
  405. if(puts(outstr) < 0)
  406. {
  407. fprintf(stderr, "%s: error: Failed writing time: %s\n", argv0, strerror(errno));
  408. return 1;
  409. }
  410. return 0;
  411. }