datefmt

format unix timestamps over stdin
git clone git://jb55.com/datefmt
Log | Files | Refs | README | LICENSE

datefmt.c (7354B)


      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <ctype.h>
      4 #include <inttypes.h>
      5 #include <time.h>
      6 #include <string.h>
      7 
      8 #define VERSION "0.2.2"
      9 
     10 enum state {
     11 	BEGIN,
     12 	WAIT,
     13 	BOUNDARY,
     14 	MIDDLE,
     15 };
     16 
     17 struct parser {
     18 	enum state state;
     19 	unsigned char *buf;
     20 	size_t len;
     21 	int64_t after, before;
     22 	int n_digits;
     23 	int dir;
     24 	int ms;
     25 	int relative;
     26 	const char *format;
     27 	char digits[15];
     28 };
     29 
     30 static enum state update_state(int c, enum state state)
     31 {
     32 	int is_boundary = c == -1 || !isalnum(c);
     33 
     34 	if (is_boundary) {
     35 		if (state == WAIT || state == BEGIN) {
     36 			return BOUNDARY;
     37 		} else if (state == MIDDLE) {
     38 			return BOUNDARY;
     39 		}
     40 	} else if (isdigit(c)) {
     41 		if (state == BOUNDARY) {
     42 			return MIDDLE;
     43 		} else if (state == BEGIN) {
     44 			return MIDDLE;
     45 		}
     46 	}
     47 
     48 	if (state == BEGIN) {
     49 		return BOUNDARY;
     50 	}
     51 
     52 	return state;
     53 }
     54 
     55 static void print_rest(struct parser *parser, char *charbuf, enum state *new_state)
     56 {
     57 	*new_state = BOUNDARY;
     58 	printf("%.*s%s", parser->n_digits, parser->digits, charbuf);
     59 	parser->n_digits = 0;
     60 }
     61 
     62 static int time_matches(struct parser *parser, int64_t ts)
     63 {
     64 	// always match after
     65 	if (ts <= parser->after)
     66 		return 0;
     67 
     68 	// match before if it's set
     69 	if (parser->before != -1 && ts >= parser->before) {
     70 		return 0;
     71 	}
     72 
     73 	if (parser->dir == -1) {
     74 		return ts <= time(NULL);
     75 	} else if (parser->dir == 1) {
     76 		return ts > time(NULL);
     77 	}
     78 
     79 	// dir == 0 has no conditions on now
     80 	return 1;
     81 }
     82 
     83 static void print_relative(int64_t previous)
     84 {
     85 	time_t now = time(NULL);
     86 
     87 	int s_per_minute = 60;
     88 	int s_per_hour = s_per_minute * 60;
     89 	int s_per_day = s_per_hour * 24;
     90 	int s_per_month = s_per_day * 30;
     91 	int s_per_year = s_per_month * 365;
     92 
     93 	int elapsed = now - previous;
     94 
     95 	if (elapsed < s_per_minute) {
     96 		printf("%ds", elapsed);
     97 	} else if (elapsed < s_per_hour) {
     98 		printf("%dm", elapsed / s_per_minute);
     99 	} else if (elapsed < s_per_day) {
    100 		printf("%dh", elapsed / s_per_hour);
    101 	} else if (elapsed < s_per_month) {
    102 		printf("%dd", elapsed / s_per_day);
    103 	} else if (elapsed < s_per_year) {
    104 		printf("%dmth", elapsed / s_per_month);
    105 	} else {
    106 		printf("%dyr", elapsed / s_per_year);
    107 	}
    108 }
    109 
    110 static void print_time(const char *format, char *charbuf, int64_t ts, int relative)
    111 {
    112 	static char timebuf[128];
    113 
    114 	if (relative) {
    115 		print_relative(ts);
    116 		printf("%s", charbuf);
    117 	} else {
    118 		strftime(timebuf, sizeof(timebuf), format, localtime((time_t*)&ts));
    119 		printf("%s%s", timebuf, charbuf);
    120 	}
    121 }
    122 
    123 static enum state doaction(int c, enum state new_state, struct parser *parser)
    124 {
    125 	char charbuf[2];
    126 	int64_t ts;
    127 
    128 	if (c == -1)
    129 		charbuf[0] = 0;
    130 	else {
    131 		charbuf[0] = (char)c;
    132 		charbuf[1] = 0;
    133 	}
    134 
    135 	if (parser->state == MIDDLE && new_state == BOUNDARY) {
    136 		if (parser->ms)
    137 			parser->digits[10] = 0;
    138 
    139 		ts = strtoll(parser->digits, NULL, 10);
    140 		/* found date */
    141 		if (time_matches(parser, ts)) {
    142 			print_time(parser->format, charbuf, ts, parser->relative);
    143 		} else {
    144 			print_rest(parser, charbuf, &new_state);
    145 		}
    146 	} else if (new_state == MIDDLE) {
    147 		if (parser->n_digits < (parser->ms? 13 : 10)) {
    148 			parser->digits[parser->n_digits++] = (char)c;
    149 			parser->digits[parser->n_digits] = 0;
    150 		} else {
    151 			print_rest(parser, charbuf, &new_state);
    152 		}
    153 	} else {
    154 		if (c != -1)
    155 			fputc(c, stdout);
    156 	}
    157 
    158 	return new_state;
    159 }
    160 
    161 /* static const char *state_name(enum state state) */
    162 /* { */
    163 /*	 switch (state) { */
    164 /*	 case BOUNDARY: return "BOUNDARY"; */
    165 /*	 case WAIT: return "WAIT"; */
    166 /*	 case MIDDLE: return "MIDDLE"; */
    167 /*	 case BEGIN: return "BEGIN"; */
    168 /*	 } */
    169 /*	 return "UNKN"; */
    170 /* } */
    171 
    172 
    173 
    174 static void process(struct parser *parser, int last)
    175 {
    176 	enum state new_state;
    177 	int i;
    178 	int c;
    179 
    180 	for (i = 0; i < parser->len+last; i++) {
    181 		if (last && i == parser->len)
    182 			c = -1;
    183 		else
    184 			c = parser->buf[i];
    185 
    186 		/* transition state */
    187 		new_state = update_state(c, parser->state);
    188 
    189 		/* debug */
    190 		/* printf("%c | %s -> %s\n", (char)c, state_name(parser->state), state_name(new_state)); */
    191 
    192 		/* action */
    193 		new_state = doaction(c, new_state, parser);
    194 
    195 		if (new_state == BOUNDARY) {
    196 			parser->n_digits = 0;
    197 		}
    198 
    199 		parser->state = new_state;
    200 	}
    201 }
    202 
    203 #define READSIZE 4096
    204 
    205 static void parser_init(struct parser *parser)
    206 {
    207 	static unsigned char buf[READSIZE];
    208 	const char *fmt = getenv("DEFAULT_DATEFMT");
    209 
    210 	parser->buf = buf;
    211 	parser->state = BEGIN;
    212 	parser->len = 0;
    213 	parser->after = 946684800LL;
    214 	parser->before = -1;
    215 	parser->dir = 0;
    216 	parser->ms = 0;
    217 	parser->relative = 0;
    218 	parser->n_digits = 0;
    219 	parser->format = fmt? fmt : "%F %R";
    220 }
    221 
    222 static void usage() {
    223 	printf("usage: datefmt [OPTION...] [FORMAT]\n\n");
    224 	printf("format unix timestamps from stdin\n\n");
    225 	printf("  -a, --after <timestamp>  only format timestamps after this date (default: 2000-01-01Z)\n");
    226 	printf("  -b, --before <timestamp> only format timestamps before this date \n");
    227 	printf("  -f, --future             only format timestamps in the future \n");
    228 	printf("  -p, --past               only format timestamps in the past \n");
    229 	printf("      --version	           display version information and exit \n");
    230 	printf("  -m, --ms	           interpret timestamps as milliseconds instead of seconds \n");
    231 	printf("  -r, --relative           custom format: show age of timestamp (1h, 2d, etc) \n");
    232 
    233 	printf("\n  FORMAT\n    a strftime format string, defaults to '%%F %%R'\n");
    234 
    235 	printf("\n  EXAMPLE\n    datefmt --after $(date -d yesterday +%%s) %%R < spreadsheet.csv\n\n");
    236 	printf("  Created By: William Casarin <https://jb55.com>\n");
    237 	exit(0);
    238 }
    239 
    240 
    241 static void shift_arg(int *argc, char **argv)
    242 {
    243 	if (*argc < 1)
    244 		return;
    245 	memmove(argv, &argv[1], ((*argc)-1) * sizeof(char*));
    246 	*argc = *argc - 1;
    247 }
    248 
    249 static time_t parse_date_arg(int *argc, char **argv)
    250 {
    251 	char *endptr;
    252 	time_t res;
    253 
    254 	shift_arg(argc, argv);
    255 	if (*argc > 0) {
    256 		res = strtoll(argv[0], &endptr, 10);
    257 		if (endptr == argv[0]) {
    258 			printf("error: invalid after value '%s'\n", argv[0]);
    259 			exit(1);
    260 		}
    261 		shift_arg(argc, argv);
    262 	} else {
    263 		printf("error: expected argument to --after\n");
    264 		exit(1);
    265 	}
    266 
    267 	return res;
    268 }
    269 
    270 static void parse_arg(int *argc, char **argv, struct parser *parser)
    271 {
    272 	if (!strcmp(argv[0], "--after") || !strcmp(argv[0], "-a")) {
    273 		parser->after = parse_date_arg(argc, argv);
    274 	} else if (!strcmp("--help", argv[0])) {
    275 		usage();
    276 	} else if (!strcmp("--before", argv[0]) || !strcmp(argv[0], "-b")) {
    277 		parser->before = parse_date_arg(argc, argv);
    278 	} else if (!strcmp("--version", argv[0])) {
    279 		printf("datefmt " VERSION "\n");
    280 		exit(0);
    281 	} else if (!strcmp("--past", argv[0]) || !strcmp("-p", argv[0])) {
    282 		parser->dir = -1;
    283 		shift_arg(argc, argv);
    284 	} else if (!strcmp("--future", argv[0]) || !strcmp("-f", argv[0])) {
    285 		parser->dir = 1;
    286 		shift_arg(argc, argv);
    287 	} else if (!strcmp("--ms", argv[0]) || !strcmp("-m", argv[0])) {
    288 		parser->ms = 1;
    289 		shift_arg(argc, argv);
    290 	} else if (!strcmp("--relative", argv[0]) || !strcmp("-r", argv[0])) {
    291 		parser->relative = 1;
    292 		shift_arg(argc, argv);
    293 	} else {
    294 		parser->format = (const char*)argv[0];
    295 		shift_arg(argc, argv);
    296 	}
    297 }
    298 
    299 static void parse_args(int argc, char **argv, struct parser *parser)
    300 {
    301 	shift_arg(&argc, argv);
    302 
    303 	while (argc > 0) {
    304 		parse_arg(&argc, argv, parser);
    305 	}
    306 }
    307 
    308 int main(int argc, char *argv[])
    309 {
    310 	struct parser parser;
    311 
    312 	parser_init(&parser);
    313 	parse_args(argc, argv, &parser);
    314 
    315 	do {
    316 		parser.len = fread(parser.buf, 1, READSIZE, stdin);
    317 		process(&parser, parser.len != READSIZE);
    318 	} while (parser.len == READSIZE);
    319 
    320 	return 0;
    321 }