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 }