logo

utils-std

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

date.c (9362B)


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