logo

utils-std

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

date.c (8636B)


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