hmd.c (15541B)
1#include "hmd.h" 2 3#include <stdint.h> 4#include <time.h> 5#include <errno.h> 6#include <stdarg.h> 7#include <stdio.h> 8#include <string.h> 9#include <stdbool.h> 10#include <stdlib.h> 11 12#define ABS(x) ((x) > 0 ? (x) : -(x)) 13#define DAYS(tm) ((tm)->tm_year * 365 + (tm)->tm_yday) 14#define BETWEEN(x, start, end) (((x) >= (start)) && ((x) < (end))) 15 16const char *hmd_wdnames[7] = { 17 [HMD_SUN] = "sunday", 18 [HMD_MON] = "monday", 19 [HMD_TUE] = "tuesday", 20 [HMD_WED] = "wednesday", 21 [HMD_THU] = "thursday", 22 [HMD_FRI] = "friday", 23 [HMD_SAT] = "saturday" 24}; 25 26const char *hmd_mnames[12] = { 27 [HMD_JAN] = "january", 28 [HMD_FEB] = "february", 29 [HMD_MAR] = "march", 30 [HMD_APR] = "april", 31 [HMD_MAY] = "may", 32 [HMD_JUN] = "june", 33 [HMD_JUL] = "july", 34 [HMD_AUG] = "august", 35 [HMD_SEP] = "september", 36 [HMD_OCT] = "october", 37 [HMD_NOV] = "november", 38 [HMD_DEC] = "december" 39}; 40 41const int hmd_mdays[12] = { 42 [HMD_JAN] = 31, 43 [HMD_FEB] = 28, 44 [HMD_MAR] = 31, 45 [HMD_APR] = 30, 46 [HMD_MAY] = 31, 47 [HMD_JUN] = 30, 48 [HMD_JUL] = 31, 49 [HMD_AUG] = 31, 50 [HMD_SEP] = 30, 51 [HMD_OCT] = 31, 52 [HMD_NOV] = 30, 53 [HMD_DEC] = 31, 54}; 55 56static char * 57strfmt(const struct allocator *allocator, int *rc, const char *fmtstr, ...) 58{ 59 va_list ap, cpy; 60 char *str; 61 ssize_t n; 62 63 va_copy(cpy, ap); 64 65 va_start(cpy, fmtstr); 66 n = vsnprintf(NULL, 0, fmtstr, cpy); 67 va_end(cpy); 68 69 if (n <= 0) { 70 if (rc) *rc = HMD_ERR_INT; 71 return NULL; 72 } 73 74 str = allocator->alloc(allocator, (size_t) (n + 1), rc); 75 if (!str && rc) *rc = -*rc; 76 if (!str) return NULL; 77 78 va_start(ap, fmtstr); 79 vsnprintf(str, (size_t) (n + 1), fmtstr, ap); 80 va_end(ap); 81 82 return str; 83} 84 85static bool 86has_prefix(const char *text, const char *prefix) 87{ 88 size_t len; 89 90 len = strlen(prefix); 91 if (len > strlen(text)) 92 return false; 93 94 return !strncmp(text, prefix, len); 95} 96 97static bool 98is_prefix(const char *prefix, const char *text, int minlen) 99{ 100 size_t len; 101 102 len = strlen(prefix); 103 if (len < minlen || len > strlen(text)) 104 return false; 105 106 return !strncmp(prefix, text, len); 107} 108 109static struct tm 110startofday(struct tm tm) 111{ 112 tm.tm_hour = 0; 113 tm.tm_min = 0; 114 tm.tm_sec = 0; 115 116 return tm; 117} 118 119static enum hmd_err 120tm2time(uint64_t *time, struct tm tm) 121{ 122 time_t res; 123 124 tm.tm_isdst = -1; /* infer timezone */ 125 res = mktime(&tm); 126 if (res == (time_t) -1) 127 return HMD_ERR_OOB; 128 *time = (uint64_t) res; 129 130 return HMD_OK; 131} 132 133static enum hmd_err 134time2tm(struct tm *tm, uint64_t time) 135{ 136 struct tm *res; 137 time_t tmp; 138 139 tmp = (time_t) time; 140 res = localtime(&tmp); 141 if (!res) return HMD_ERR_OOB; 142 *tm = *res; 143 144 return HMD_OK; 145} 146 147static char * 148allocdup(const struct allocator *allocator, int *rc, const char *str) 149{ 150 char *dup; 151 152 dup = allocator->alloc(allocator, strlen(str) + 1, rc); 153 if (!dup) return NULL; 154 155 strcpy(dup, str); 156 157 return dup; 158} 159 160static char * 161concat(char *buf, char *c, size_t *cap, const struct allocator *allocator, 162 int *rc, const char *fmt, ...) 163{ 164 va_list ap, cpy; 165 size_t left, off; 166 ssize_t n; 167 168 if (!c) return NULL; 169 170 va_copy(cpy, ap); 171 172 off = (size_t) (c - buf); 173 left = *cap - off; 174 va_start(ap, fmt); 175 n = vsnprintf(c, left, fmt, ap); 176 va_end(ap); 177 178 if (n >= left) { 179 if (off + (size_t) n + 1 > *cap * 2) 180 *cap = off + (size_t) n + 1; 181 else 182 *cap *= 2; 183 buf = allocator->realloc(allocator, buf, *cap, rc); 184 if (!buf && rc) *rc = -*rc; 185 if (!buf) return NULL; 186 187 c = buf + off; 188 left = *cap - off; 189 va_start(cpy, fmt); 190 n = vsnprintf(c, left, fmt, cpy); 191 va_end(cpy); 192 } 193 194 if (n < 0 || n > left) { 195 if (rc) *rc = HMD_ERR_INT; 196 return NULL; 197 } 198 199 return c + n; 200} 201 202enum hmd_err 203hmd_spn_parse(struct hmd_spn *spn, const char *arg) 204{ 205 const char *c; 206 char *end; 207 uint64_t v; 208 209 memset(spn, 0, sizeof(struct hmd_spn)); 210 for (c = arg; *c; c = end + 1) { 211 v = strtoul(c, &end, 10); 212 if (!end) return HMD_ERR_FMT; 213 if (v >= UINT32_MAX) return HMD_ERR_FMT; 214 switch (*end) { 215 case 'y': 216 spn->years += (uint32_t) v; 217 break; 218 case 'w': 219 spn->weeks += (uint32_t) v; 220 break; 221 case 'd': 222 spn->days += (uint32_t) v; 223 break; 224 case 'h': 225 spn->hours += (uint32_t) v; 226 break; 227 case 'm': 228 spn->mins += (uint32_t) v; 229 break; 230 case 's': 231 spn->secs += (uint32_t) v; 232 break; 233 default: 234 return HMD_ERR_FMT; 235 } 236 } 237 238 return HMD_OK; 239} 240 241enum hmd_err 242hmd_spn_ival_parse(struct hmd_spn *spn, const char *arg) 243{ 244 memset(spn, 0, sizeof(struct hmd_spn)); 245 if (!strcmp(arg, "yearly")) { 246 spn->years = 1; 247 } else if (!strcmp(arg, "weekly")) { 248 spn->weeks = 1; 249 } else if (!strcmp(arg, "daily")) { 250 spn->days = 1; 251 } else if (!strcmp(arg, "hourly")) { 252 spn->hours = 1; 253 } else { 254 return hmd_spn_parse(spn, arg); 255 } 256 257 return HMD_OK; 258} 259 260enum hmd_err 261hmd_spn_calc(struct hmd_spn *spn, uint64_t start, uint64_t end) 262{ 263 int years, days, hours, mins, secs; 264 struct tm tm_start, tm_end; 265 enum hmd_err err; 266 267 if (end < start) return HMD_ERR_ARG; 268 269 err = time2tm(&tm_start, start); 270 if (err) return err; 271 272 err = time2tm(&tm_end, end); 273 if (err) return err; 274 275 years = tm_end.tm_year - tm_start.tm_year; 276 days = tm_end.tm_yday - tm_start.tm_yday; 277 278 years = days = hours = mins = secs = 0; 279 secs += tm_end.tm_sec - tm_start.tm_sec; 280 if (secs < 0) { 281 mins -= 1; 282 secs += 60; 283 } 284 285 mins += tm_end.tm_min - tm_start.tm_min; 286 if (mins < 0) { 287 hours -= 1; 288 mins += 60; 289 } 290 291 hours += tm_end.tm_hour - tm_start.tm_hour; 292 if (hours < 0) { 293 days -= 1; 294 hours += 24; 295 } 296 297 days += tm_end.tm_yday - tm_start.tm_yday; 298 if (days < 0) { 299 years -= 1; 300 days += 365; 301 } 302 303 if (years < 0) return HMD_ERR_INT; 304 305 spn->years = (uint32_t) years; 306 spn->days = (uint32_t) days % 7; 307 spn->weeks = (uint32_t) days / 7; 308 spn->hours = (uint32_t) hours; 309 spn->mins = (uint32_t) mins; 310 spn->secs = (uint32_t) secs; 311 312 return HMD_OK; 313} 314 315uint64_t 316hmd_spn_secs(struct hmd_spn *spn) 317{ 318 return spn->years * 365 * 24 * 3600 319 + (spn->weeks * 7 + spn->days) * 24 * 3600 320 + spn->hours * 3600 + spn->mins * 60 + spn->secs; 321} 322 323char * 324hmd_spn_str(struct hmd_spn *spn, 325 const struct allocator *allocator, int *rc) 326{ 327 char *str, *c; 328 size_t cap; 329 330 cap = 1; 331 c = str = allocator->alloc(allocator, cap, rc); 332 if (!str && rc) *rc = -*rc; 333 if (!str) return NULL; 334 335 if (spn->years) c = concat(str, c, &cap, allocator, rc, "%uy", spn->years); 336 if (spn->weeks) c = concat(str, c, &cap, allocator, rc, "%uw", spn->weeks); 337 if (spn->days) c = concat(str, c, &cap, allocator, rc, "%ud", spn->days); 338 if (spn->hours) c = concat(str, c, &cap, allocator, rc, "%uh", spn->hours); 339 if (spn->mins) c = concat(str, c, &cap, allocator, rc, "%um", spn->mins); 340 if (spn->secs) c = concat(str, c, &cap, allocator, rc, "%us", spn->secs); 341 if (!c) { 342 allocator->free(allocator, str); 343 return NULL; 344 } 345 346 *c = '\0'; 347 348 return str; 349} 350 351char * 352hmd_spn_ival_str(struct hmd_spn *spn, 353 const struct allocator *allocator, int *rc) 354{ 355 if (!spn->secs && !spn->mins && spn->hours != 0 356 && !spn->days && !spn->weeks && !spn->years) { 357 return allocdup(allocator, rc, "hourly"); 358 } else if (!spn->secs && !spn->mins && !spn->hours 359 && spn->days != 0 && !spn->weeks && !spn->years) { 360 return allocdup(allocator, rc, "daily"); 361 } else if (!spn->secs && !spn->mins && !spn->hours 362 && !spn->days && spn->weeks != 0 && !spn->years) { 363 return allocdup(allocator, rc, "weekly"); 364 } else if (!spn->secs && !spn->mins && !spn->hours 365 && !spn->days && !spn->weeks && spn->years != 0) { 366 return allocdup(allocator, rc, "yearly"); 367 } else { 368 return hmd_spn_str(spn, allocator, rc); 369 } 370} 371 372enum hmd_err 373hmd_date_parse_daydate_reltime_weekday(struct hmd_date *date, char *arg) 374{ 375 struct tm datetm, nowtm; 376 enum hmd_err err; 377 time_t now; 378 int i; 379 380 now = time(NULL); 381 nowtm = *localtime(&now); 382 383 for (i = 0; i < 7; i++) { 384 if (is_prefix(arg, hmd_wdnames[i], 3)) { 385 datetm = startofday(nowtm); 386 datetm.tm_mday += i - datetm.tm_wday; 387 datetm.tm_mday += (i < datetm.tm_wday ? 7 : 0); 388 break; 389 } else if (has_prefix(arg, "next-") 390 && is_prefix(arg + 5, hmd_wdnames[i], 3)) { 391 datetm = startofday(nowtm); 392 datetm.tm_mday += i - datetm.tm_wday; 393 datetm.tm_mday += (i < datetm.tm_wday ? 7 : 0) + 7; 394 break; 395 } else if (has_prefix(arg, "last-") 396 && is_prefix(arg + 5, hmd_wdnames[i], 3)) { 397 datetm = startofday(nowtm); 398 datetm.tm_mday += i - datetm.tm_wday; 399 datetm.tm_mday += (i < datetm.tm_wday ? 7 : 0) - 7; 400 break; 401 } 402 } 403 404 if (i == 7) return HMD_ERR_FMT; 405 406 err = tm2time(&date->ts, datetm); 407 if (err) return err; 408 409 date->spn = HMD_DAY_SPN; 410 411 return HMD_OK; 412} 413 414enum hmd_err 415hmd_date_parse_daydate_reltime_coarse(struct hmd_date *date, char *arg) 416{ 417 time_t now; 418 struct tm datetm, nowtm; 419 uint64_t next; 420 enum hmd_err err; 421 size_t i; 422 423 now = time(NULL); 424 nowtm = *localtime(&now); 425 426 if (is_prefix(arg, "tomorrow", 3)) { 427 datetm = startofday(nowtm); 428 datetm.tm_mday += 1; 429 date->spn = HMD_DAY_SPN; 430 err = tm2time(&date->ts, datetm); 431 if (err) return err; 432 } else if (is_prefix(arg, "today", 3)) { 433 datetm = startofday(nowtm); 434 date->spn = HMD_DAY_SPN; 435 err = tm2time(&date->ts, datetm); 436 if (err) return err; 437 } else if (is_prefix(arg, "yesterday", 3)) { 438 datetm = startofday(nowtm); 439 datetm.tm_mday -= 1; 440 date->spn = HMD_DAY_SPN; 441 err = tm2time(&date->ts, datetm); 442 if (err) return err; 443 } else { 444 for (i = 0; i < 12; i++) { 445 if (is_prefix(arg, hmd_mnames[i], 3)) { 446 datetm = startofday(nowtm); 447 datetm.tm_mday = 1; 448 datetm.tm_mon = (uint8_t) i; 449 err = tm2time(&date->ts, datetm); 450 if (err) return err; 451 datetm.tm_mon += 1; 452 err = tm2time(&next, datetm); 453 if (err) return err; 454 date->spn = next - date->ts; 455 break; 456 } 457 } 458 459 if (i == 12) { 460 err = hmd_date_parse_daydate_reltime_weekday(date, arg); 461 if (err) return err; 462 } 463 } 464 465 return HMD_OK; 466} 467 468enum hmd_err 469hmd_date_parse_daydate_iso8601(struct hmd_date *date, char *arg) 470{ 471 struct tm nowtm, tm; 472 int year, month, day; 473 enum hmd_err err; 474 uint64_t next; 475 time_t now; 476 int n; 477 478 now = time(NULL); 479 nowtm = *localtime(&now); 480 481 n = 0; 482 if (sscanf(arg, "%u-%u-%u%n", &year, &month, &day, &n) == 3 && !arg[n]) { 483 tm = startofday(nowtm); 484 tm.tm_year = year - 1900; 485 tm.tm_mon = month - 1; 486 tm.tm_mday = day; 487 err = tm2time(&date->ts, tm); 488 if (err) return err; 489 date->spn = HMD_DAY_SPN; 490 } else if (sscanf(arg, "%u-%u%n", &year, &month, &n) == 2 && !arg[n]) { 491 tm = startofday(nowtm); 492 tm.tm_year = year - 1900; 493 tm.tm_mon = month - 1; 494 tm.tm_mday = 1; 495 err = tm2time(&date->ts, tm); 496 if (err) return err; 497 tm.tm_mon++; 498 err = tm2time(&next, tm); 499 if (err) return err; 500 date->spn = next - date->ts; 501 } else if (sscanf(arg, "%uY%n", &year, &n) == 1 && !arg[n]) { 502 tm = startofday(nowtm); 503 tm.tm_year = year - 1900; 504 tm.tm_mon = 0; 505 tm.tm_mday = 1; 506 err = tm2time(&date->ts, tm); 507 if (err) return err; 508 tm.tm_year++; 509 err = tm2time(&next, tm); 510 if (err) return err; 511 date->spn = next - date->ts; 512 } else { 513 return HMD_ERR_FMT; 514 } 515 516 return HMD_OK; 517} 518 519enum hmd_err 520hmd_date_parse_daydate(struct hmd_date *date, char *arg) 521{ 522 enum hmd_err err; 523 524 err = hmd_date_parse_daydate_reltime_coarse(date, arg); 525 if (err) err = hmd_date_parse_daydate_iso8601(date, arg); 526 if (err) return err; 527 528 return HMD_OK; 529} 530 531enum hmd_err 532hmd_timeofday_parse(struct hmd_tod *tod, char *arg) 533{ 534 unsigned int hour, min, sec; 535 char end; 536 537 hour = min = sec = 0; 538 if (sscanf(arg, "%u:%u:%u%c", &hour, &min, &sec, &end) == 3) { 539 tod->spn = HMD_SEC_SPN; 540 } else if (sscanf(arg, "%u:%u%c", &hour, &min, &end) == 2) { 541 tod->spn = HMD_MIN_SPN; 542 } else if (sscanf(arg, "%u%c", &hour, &end) == 1) { 543 tod->spn = HMD_HOUR_SPN; 544 } else { 545 return HMD_ERR_FMT; 546 } 547 548 if (hour >= 24 || min >= 60 || sec >= 60) 549 return HMD_ERR_OOB; 550 551 tod->hour = (uint8_t) hour; 552 tod->min = (uint8_t) min; 553 tod->sec = (uint8_t) sec; 554 555 return HMD_OK; 556} 557 558enum hmd_err 559hmd_date_parse(struct hmd_date *date, char *arg) 560{ 561 struct hmd_tod tod; 562 char *sep; 563 enum hmd_err err; 564 565 err = HMD_OK; 566 sep = strchr(arg, '@'); 567 if (sep) *sep = '\0'; 568 569 err = hmd_date_parse_daydate(date, arg); 570 if (err) goto exit; 571 572 if (sep) { 573 /* ambiguous due to DST */ 574 err = hmd_timeofday_parse(&tod, sep + 1); 575 if (err) goto exit; 576 577 date->ts += (uint64_t) tod.hour * HMD_HOUR_SPN 578 + tod.min * HMD_MIN_SPN + tod.sec; 579 date->spn = tod.spn; 580 } 581 582exit: 583 if (sep) *sep = '@'; 584 return err; 585} 586 587char * 588hmd_date_str_reltime_exact(struct hmd_date *date, 589 const struct allocator *allocator, int *rc) 590{ 591 time_t days, hours, min, sec; 592 time_t now, diff; 593 char *datestr; 594 595 now = time(NULL); 596 diff = (time_t) date->ts - now; 597 598 days = diff / HMD_DAY_SPN; 599 hours = (diff % HMD_DAY_SPN) / HMD_HOUR_SPN; 600 min = (diff % HMD_HOUR_SPN) / HMD_MIN_SPN; 601 sec = diff % HMD_MIN_SPN; 602 603 if (days) { 604 datestr = strfmt(allocator, rc, "%i day%s%s", ABS(days), 605 ABS(days) > 1 ? "s" : "", days < 0 ? " ago" : ""); 606 } else if (hours) { 607 datestr = strfmt(allocator, rc, "%i hour%s%s", ABS(hours), 608 ABS(hours) > 1 ? "s" : "" , hours < 0 ? " ago" : ""); 609 } else if (min) { 610 datestr = strfmt(allocator, rc, "%i min%s%s", ABS(min), 611 ABS(min) > 1 ? "s" : "", min < 0 ? " ago" : ""); 612 } else if (sec) { 613 datestr = strfmt(allocator, rc, "%i sec%s%s", ABS(sec), 614 ABS(sec) > 1 ? "s" : "", sec < 0 ? " ago" : ""); 615 } else { 616 datestr = strfmt(allocator, rc, "now"); 617 } 618 619 return datestr; 620} 621 622char * 623hmd_date_str_reltime_coarse(struct hmd_date *date, 624 const struct allocator *allocator, int *rc) 625{ 626 struct tm nowtm, datetm; 627 enum hmd_err err; 628 char *datestr; 629 time_t now; 630 631 now = time(NULL); 632 633 err = time2tm(&nowtm, (uint64_t) now); 634 if (err && rc) *rc = (int) err; 635 if (err) return NULL; 636 637 err = time2tm(&datetm, date->ts); 638 if (err && rc) *rc = (int) err; 639 if (err) return NULL; 640 641 if (DAYS(&datetm) == DAYS(&nowtm) - 1) { 642 datestr = strfmt(allocator, rc, "yesterday"); 643 } else if (DAYS(&datetm) == DAYS(&nowtm) + 1) { 644 datestr = strfmt(allocator, rc, "tomorrow"); 645 } else if (DAYS(&datetm) == DAYS(&nowtm)) { 646 datestr = strfmt(allocator, rc, "today"); 647 } else if (BETWEEN(DAYS(&datetm) - DAYS(&nowtm), -7, 0)) { 648 datestr = strfmt(allocator, rc, 649 "last %s", hmd_wdnames[datetm.tm_wday]); 650 } else if (BETWEEN(DAYS(&datetm) - DAYS(&nowtm), 0, 7)) { 651 datestr = strfmt(allocator, rc, 652 "%s", hmd_wdnames[datetm.tm_wday]); 653 } else if (BETWEEN(DAYS(&datetm) - DAYS(&nowtm), 7, 14)) { 654 datestr = strfmt(allocator, rc, 655 "next %s", hmd_wdnames[datetm.tm_wday]); 656 } else { 657 if (rc) *rc = HMD_ERR_FMT; 658 return NULL; 659 } 660 661 return datestr; 662} 663 664char * 665hmd_date_str_iso8601(struct hmd_date *date, 666 const struct allocator *allocator, int *rc) 667{ 668 struct tm tm; 669 enum hmd_err err; 670 size_t len; 671 char *str; 672 673 err = time2tm(&tm, date->ts); 674 if (err && rc) *rc = (int) err; 675 if (err) return NULL; 676 677 str = allocator->alloc(allocator, 20, rc); 678 if (!str && rc) *rc = -*rc; 679 if (!str) return NULL; 680 681 if (date->spn < HMD_MIN_SPN) { 682 len = strftime(str, 21, "%Y-%m-%d %H:%M:%S", &tm); 683 } else if (date->spn >= HMD_MIN_SPN && date->spn < HMD_DAY_SPN) { 684 len = strftime(str, 21, "%Y-%m-%d %H:%M", &tm); 685 } else { 686 len = strftime(str, 21, "%Y-%m-%d", &tm); 687 } 688 689 if (!len) { 690 if (rc) *rc = HMD_ERR_INT; 691 allocator->free(allocator, str); 692 return NULL; 693 } 694 695 return str; 696} 697 698char * 699hmd_date_str(struct hmd_date *date, 700 const struct allocator *allocator, int *rc) 701{ 702 struct tm nowtm, datetm; 703 enum hmd_err err; 704 time_t now; 705 char *str; 706 707 now = time(NULL); 708 709 err = time2tm(&nowtm, (uint64_t) now); 710 if (err && rc) *rc = (int) err; 711 if (err) return NULL; 712 713 err = time2tm(&datetm, date->ts); 714 if (err && rc) *rc = (int) err; 715 if (err) return NULL; 716 717 if (DAYS(&nowtm) == DAYS(&datetm)) 718 return hmd_date_str_reltime_exact(date, allocator, rc); 719 720 str = hmd_date_str_reltime_coarse(date, allocator, rc); 721 if (!str) str = hmd_date_str_iso8601(date, allocator, rc); 722 723 return str; 724} 725