logo

utils-std

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

date.c (8402B)


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