logo

utils-std

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

date.c (6853B)


  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> /* exit(), 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 void
  19. usage(void)
  20. {
  21. fprintf(stderr, "\
  22. Usage:\n\
  23. date [-jRu] [-d datetime | -r epoch] [+format]\n\
  24. date [-jRu] mmddHHMM[[CC]yy] [+format]\n\
  25. date [-jRu] -f now_format now [+format]\n\
  26. ");
  27. }
  28. int
  29. main(int argc, char *argv[])
  30. {
  31. char outstr[BUFSIZ];
  32. struct tm tm = {
  33. .tm_year = 0,
  34. .tm_mon = 0,
  35. .tm_mday = 0,
  36. .tm_hour = 0,
  37. .tm_min = 0,
  38. .tm_sec = 0,
  39. .tm_isdst = -1, // unknown if DST is in effect
  40. .tm_gmtoff = 0,
  41. .tm_zone = NULL,
  42. };
  43. struct timespec tp = {
  44. .tv_sec = 0,
  45. .tv_nsec = 0,
  46. };
  47. const char *format = "%c";
  48. const char *input_format = NULL;
  49. int uflag = 0;
  50. int dflag = 0, rflag = 0;
  51. int c;
  52. bool jflag = false;
  53. bool settime = false;
  54. errno = 0;
  55. setlocale(LC_ALL, "");
  56. if(errno != 0)
  57. {
  58. fprintf(stderr, "%s: warning: Failed to initialize locales: %s\n", argv0, strerror(errno));
  59. errno = 0;
  60. }
  61. tp.tv_sec = time(NULL);
  62. if(tp.tv_sec == (time_t)-1)
  63. {
  64. fprintf(stderr, "%s: error: Failed getting current time: %s\n", argv0, strerror(errno));
  65. exit(EXIT_FAILURE);
  66. }
  67. while((c = getopt(argc, argv, ":d:f:jr:Ru")) != -1)
  68. {
  69. const char *errstr = NULL;
  70. switch(c)
  71. {
  72. case 'd': /* Custom datetime */
  73. if(input_format != NULL)
  74. {
  75. fprintf(stderr, "%s: error: Cannot both use '-d' and '-f'\n", argv0);
  76. exit(EXIT_FAILURE);
  77. }
  78. if(rflag == 1)
  79. {
  80. fprintf(stderr, "%s: error: Cannot both use '-d' and '-r'\n", argv0);
  81. exit(EXIT_FAILURE);
  82. }
  83. iso_parse(optarg, &tm, &tp.tv_nsec, &errstr);
  84. dflag = 1;
  85. if(errstr != NULL)
  86. {
  87. fprintf(stderr, "%s: error: iso_parse(\"%s\", …): %s\n", argv0, optarg, errstr);
  88. exit(EXIT_FAILURE);
  89. }
  90. assert(errno == 0);
  91. tp.tv_sec = mktime_tz(&tm);
  92. if(tp.tv_sec == (time_t)-1)
  93. {
  94. fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
  95. exit(EXIT_FAILURE);
  96. }
  97. errno = 0;
  98. break;
  99. case 'f': /* input datetime format */
  100. if(dflag == 1)
  101. {
  102. fprintf(stderr, "%s: error: Cannot both use '-d' and '-f'\n", argv0);
  103. exit(EXIT_FAILURE);
  104. }
  105. if(rflag == 1)
  106. {
  107. fprintf(stderr, "%s: error: Cannot both use '-f' and '-r'\n", argv0);
  108. exit(EXIT_FAILURE);
  109. }
  110. input_format = optarg;
  111. settime = true;
  112. break;
  113. case 'r': /* seconds relative to epoch */
  114. {
  115. if(input_format != NULL)
  116. {
  117. fprintf(stderr, "%s: error: Cannot both use '-f' and '-r'\n", argv0);
  118. exit(EXIT_FAILURE);
  119. }
  120. if(dflag == 1)
  121. {
  122. fprintf(stderr, "%s: error: Cannot both use '-d' and '-r'\n", argv0);
  123. exit(EXIT_FAILURE);
  124. }
  125. char *endptr = NULL;
  126. errno = 0;
  127. tp.tv_sec = strtol(optarg, &endptr, 10);
  128. if(errno != 0)
  129. {
  130. fprintf(stderr, "%s: error: Failed parsing '-r %s'\n", argv0, optarg);
  131. exit(EXIT_FAILURE);
  132. }
  133. if(!(endptr == NULL || *endptr == '\0'))
  134. {
  135. fprintf(stderr, "%s: error: Invalid characters in '-r %s': %s\n", argv0, optarg, endptr);
  136. exit(EXIT_FAILURE);
  137. }
  138. break;
  139. }
  140. case 'R': /* Email (RFC 5322) format */
  141. format = "%a, %d %b %Y %H:%M:%S %z";
  142. break;
  143. case 'u': /* UTC timezone */
  144. uflag++;
  145. setenv("TZ", "UTC", 1);
  146. tzset();
  147. break;
  148. case 'j':
  149. jflag = true;
  150. break;
  151. case ':':
  152. fprintf(stderr, "%s: error: Missing operand for option: '-%c'\n", argv0, optopt);
  153. usage();
  154. return 1;
  155. case '?':
  156. fprintf(stderr, "%s: error: Unrecognised option: '-%c'\n", argv0, optopt);
  157. usage();
  158. return 1;
  159. }
  160. }
  161. argc -= optind;
  162. argv += optind;
  163. if(uflag)
  164. {
  165. if(gmtime_r(&tp.tv_sec, &tm) == NULL)
  166. {
  167. fprintf(stderr, "%s: error: gmtime_r: %s\n", argv0, strerror(errno));
  168. exit(EXIT_FAILURE);
  169. }
  170. }
  171. else
  172. {
  173. if(localtime_r(&tp.tv_sec, &tm) == NULL)
  174. {
  175. fprintf(stderr, "%s: error: localtime_r: %s\n", argv0, strerror(errno));
  176. exit(EXIT_FAILURE);
  177. }
  178. }
  179. if(argc > 0 && input_format != NULL)
  180. {
  181. char *res = strptime(argv[0], input_format, &tm);
  182. if(res == NULL)
  183. {
  184. fprintf(stderr,
  185. "%s: error: strptime(\"%s\", \"%s\", …) as passed by '-f' option failed\n",
  186. argv0,
  187. argv[0],
  188. input_format);
  189. exit(EXIT_FAILURE);
  190. }
  191. assert(errno == 0);
  192. tp.tv_sec = mktime_tz(&tm);
  193. tp.tv_nsec = 0;
  194. if(tp.tv_sec == (time_t)-1)
  195. {
  196. fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
  197. exit(EXIT_FAILURE);
  198. }
  199. errno = 0;
  200. argv++;
  201. argc--;
  202. }
  203. if(input_format == NULL && argc > 0 && *argv && **argv != '+')
  204. {
  205. const char *fmt = "%m%d%H%M";
  206. char *s = strptime(argv[0], fmt, &tm);
  207. if(s == NULL)
  208. {
  209. fprintf(stderr, "%s: error: strptime(\"%s\", \"%s\", …) returned NULL\n", argv0, *argv, fmt);
  210. exit(EXIT_FAILURE);
  211. }
  212. size_t rem = strlen(s);
  213. if(rem > 0)
  214. {
  215. switch(rem)
  216. {
  217. case 2:
  218. fmt = "%y";
  219. break;
  220. case 4:
  221. fmt = "%Y";
  222. break;
  223. default:
  224. fprintf(stderr,
  225. "%s: error: Got %zu trailing characters after \"%s\" for mmddHHMM\n",
  226. argv0,
  227. rem,
  228. fmt);
  229. exit(EXIT_FAILURE);
  230. }
  231. s = strptime(s, fmt, &tm);
  232. if(s == NULL)
  233. {
  234. fprintf(
  235. stderr, "%s: error: strptime(\"%s\", \"%s\", …) returned NULL\n", argv0, *argv, fmt);
  236. exit(EXIT_FAILURE);
  237. }
  238. }
  239. assert(errno == 0);
  240. tp.tv_sec = mktime(&tm);
  241. tp.tv_nsec = 0;
  242. if(tp.tv_sec == (time_t)-1)
  243. {
  244. fprintf(stderr, "%s: error: mktime: %s\n", argv0, strerror(errno));
  245. exit(EXIT_FAILURE);
  246. }
  247. errno = 0;
  248. argv++;
  249. argc--;
  250. settime = true;
  251. }
  252. if(settime && !jflag)
  253. {
  254. if(clock_settime(CLOCK_REALTIME, &tp) != 0)
  255. {
  256. fprintf(stderr,
  257. "%s: error: clock_settime(CLOCK_REALTIME, {%ld, %ld}): %s\n",
  258. argv0,
  259. tp.tv_sec,
  260. tp.tv_nsec,
  261. strerror(errno));
  262. exit(EXIT_FAILURE);
  263. }
  264. }
  265. if(argc > 0 && *argv && **argv == '+')
  266. {
  267. format = *argv + 1;
  268. argv++;
  269. argc--;
  270. }
  271. errno = 0;
  272. if(strftime(outstr, sizeof(outstr), format, &tm) == 0 && errno != 0)
  273. {
  274. fprintf(stderr, "%s: error: Failed formatting time: %s\n", argv0, strerror(errno));
  275. exit(EXIT_FAILURE);
  276. }
  277. if(puts(outstr) < 0)
  278. {
  279. fprintf(stderr, "%s: error: Failed writing time: %s\n", argv0, strerror(errno));
  280. exit(EXIT_FAILURE);
  281. }
  282. return 0;
  283. }