logo

utils-std

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

date.c (9433B)


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