/* libiso8601/src/tests/manip.c * * (c)2007, Laurence Withers, . * Released under the GNU GPLv2. See file COPYING or * http://www.gnu.org/copyleft/gpl.html for details. */ #include "iso8601.h" #include #include #include #include #include /* safe_strtod() * Converts `str' into a double, which it will store in `out'. If an error occurs, returns -1, * else 0 on success. */ int safe_strtod(const char* str, double* out) { char* endp = 0; errno = 0; *out = strtod(str, &endp); if(errno || !endp || *endp) return -1; return 0; } /* date_stack * We use a stack of dates and/or periods to implement effectively an RPN manipulator. */ enum date_stack_type { date_stack_type_date, date_stack_type_per }; struct date_stack { enum date_stack_type type; union { struct { struct iso8601_date d; struct iso8601_details det; }date; struct iso8601_elapsed per; }u; struct date_stack* next; }; struct date_stack* date_stack_top = 0; int date_stack_size = 0; /* date_stack_push_*() * Routines for pushing an object onto the date stack. */ void date_stack_push_date(const struct iso8601_date* d, const struct iso8601_details* det) { struct date_stack* s; s = malloc(sizeof(struct date_stack)); s->type = date_stack_type_date; s->u.date.d = *d; s->u.date.det = *det; s->next = date_stack_top; date_stack_top = s; ++date_stack_size; } void date_stack_push_elapsed(const struct iso8601_elapsed* per) { struct date_stack* s; s = malloc(sizeof(struct date_stack)); s->type = date_stack_type_per; s->u.per = *per; s->next = date_stack_top; date_stack_top = s; ++date_stack_size; } /* date_stack_pop() * Pops an element from the date stack. */ void date_stack_pop(void) { struct date_stack* next; --date_stack_size; next = date_stack_top->next; free(date_stack_top); date_stack_top = next; } /* date_stack_pop_date() * Pops a date from the stack, returning non-0 on error (underflow or type mismatch). */ int date_stack_pop_date(struct iso8601_date* d, struct iso8601_details* det) { if(!date_stack_top) { fputs("Stack underflow.\n", stderr); return -1; } if(date_stack_top->type != date_stack_type_date) { fputs("Type mismatch.\n", stderr); return -1; } *d = date_stack_top->u.date.d; *det = date_stack_top->u.date.det; date_stack_pop(); return 0; } /* date_stack_pop_elapsed() * Pops an elapsed period from the stack, returning non-0 on error (underflow or type mismatch). */ int date_stack_pop_elapsed(struct iso8601_elapsed* per) { if(!date_stack_top) { fputs("Stack underflow.\n", stderr); return -1; } if(date_stack_top->type != date_stack_type_per) { fputs("Type mismatch.\n", stderr); return -1; } *per = date_stack_top->u.per; date_stack_pop(); return 0; } /* date_stack_dump() * Dumps and clears the contents of the date stack. */ void date_stack_dump(void) { while(date_stack_top) { switch(date_stack_top->type) { case date_stack_type_date: fprintf(stderr, "Date (day=%d, sec=%d, nsec=%d)\n", date_stack_top->u.date.d.day, date_stack_top->u.date.d.sec, date_stack_top->u.date.d.nsec); break; case date_stack_type_per: fprintf(stderr, "Period (sec=%d, nsec=%d)\n", date_stack_top->u.per.sec, date_stack_top->u.per.nsec); break; default: fprintf(stderr, "Unknown type %d.\n", date_stack_top->type); break; } date_stack_pop(); } } /* usage() * Displays help. */ void usage(void) { fputs("Push a date or elapsed time onto the stack by entering it, e.g.:\n" " 2007-07-29 12345.67\n" "Operators:\n" " + (date, period) Advance date by elapsed time, push date.\n" " - (date, period) Regress date by elapsed time, push date.\n" " +* (date, period, num) Advance date by elapsed time multiplied by num, push date.\n" " -* (date, period, num) Regress date by elapsed time multiplied by num, push date.\n" " dp (date, date) Compute difference, print elapsed time.\n" " < (date, date) Prints 1 if first date less than second.\n" " <= (date, date) Prints 1 if first date less than or equal to second.\n" " == (date, date) Prints 1 if dates equal.\n" " p (date) Prints a date.\n" " ds Dump stack.\n" "\n" "Type help for this screen at any point.\n" "\n", stdout); } int parse_command(char* cmd) { struct iso8601_date date, date2; struct iso8601_details details, details2; struct iso8601_elapsed period, num; double period_d; char date_str[40]; int ret = 0, sign; cmd = strtok(cmd, " \n"); while(cmd) { if(!iso8601_parse(cmd, &date, 0, &details)) { date_stack_push_date(&date, &details); } else if(!safe_strtod(cmd, &period_d) && period_d >= 0) { period.sec = trunc(period_d); period.nsec = trunc((period_d - period.sec) * 1e9); date_stack_push_elapsed(&period); } else if(!strcmp(cmd, "+")) { if(date_stack_pop_elapsed(&period) || date_stack_pop_date(&date, &details)) { ret = 1; } else { iso8601_add_elapsed(&date, &period); date_stack_push_date(&date, &details); } } else if(!strcmp(cmd, "-")) { if(date_stack_pop_elapsed(&period) || date_stack_pop_date(&date, &details)) { ret = 1; } else { iso8601_subtract_elapsed(&date, &period); date_stack_push_date(&date, &details); } } else if(!strcmp(cmd, "+*") || !strcmp(cmd, "-*")) { if(date_stack_pop_elapsed(&num)) { ret = 1; } else if(num.nsec) { ret = 1; fputs("Number cannot be fractional.\n", stderr); } else { if(date_stack_pop_elapsed(&period) || date_stack_pop_date(&date, &details)) { ret = 1; } else { iso8601_add_multiple(&date, &period, ((cmd[0] == '-') ? -1 : 1) * num.sec); date_stack_push_date(&date, &details); } } } else if(!strcmp(cmd, "dp")) { if(date_stack_pop_date(&date, &details) || date_stack_pop_date(&date2, &details2)) { ret = 1; } else { iso8601_difference(&date2, &date, &period, &sign); printf("Difference: %c %lu.%09lu\n", (sign < 0) ? '-' : '+', (unsigned long)period.sec, (unsigned long)period.nsec); } } else if(!strcmp(cmd, "<")) { if(date_stack_pop_date(&date, &details) || date_stack_pop_date(&date2, &details2)) { ret = 1; } else { printf("%d\n", iso8601_lt(&date2, &date)); } } else if(!strcmp(cmd, "<=")) { if(date_stack_pop_date(&date, &details) || date_stack_pop_date(&date2, &details2)) { ret = 1; } else { printf("%d\n", iso8601_lte(&date2, &date)); } } else if(!strcmp(cmd, "==")) { if(date_stack_pop_date(&date, &details) || date_stack_pop_date(&date2, &details2)) { ret = 1; } else { printf("%d\n", iso8601_eq(&date2, &date)); } } else if(!strcmp(cmd, "p")) { if(date_stack_pop_date(&date, &details)) { ret = 1; } else { iso8601_print(date_str, sizeof(date_str), &date, &details); fputs(date_str, stdout); putc('\n', stdout); } } else if(!strcmp(cmd, "ds")) { date_stack_dump(); } else { fputs("Unrecognised command or argument.\n", stderr); ret = 1; } cmd = strtok(0, " \n"); } return ret; } int main(int argc, char* argv[]) { int ret = 0; char* buf = 0; size_t buf_len = 0; if(argc == 2 && !strcmp(argv[1], "--print-summary")) { printf("Interactive date manipulator. Also evaluates commandline.\n"); return 0; } /* commandline -- evaluate arguments as though interactive */ if(argc > 1) { for(ret = 1; ret < argc; ++ret) { if(parse_command(argv[ret])) return 1; } return 0; } usage(); while(!feof(stdin)) { printf("%d >> ", date_stack_size); ret = getline(&buf, &buf_len, stdin); if(ret == -1) break; parse_command(buf); } return 0; } /* options for text editors kate: replace-trailing-space-save true; space-indent true; tab-width 4; vim: expandtab:ts=4:sw=4 */