Add an RPN date manipulator
This commit is contained in:
		
							parent
							
								
									124df16711
								
							
						
					
					
						commit
						e59586b265
					
				| 
						 | 
				
			
			@ -25,9 +25,14 @@ then
 | 
			
		|||
            fi
 | 
			
		||||
        done
 | 
			
		||||
 | 
			
		||||
        case "${TEST}" in
 | 
			
		||||
        obj/tests/manip) TEST_EXTRAS="-lm" ;;
 | 
			
		||||
        *) TEST_EXTRAS="" ;;
 | 
			
		||||
        esac
 | 
			
		||||
 | 
			
		||||
        if [ ${MODIFIED} -ne 0 ]
 | 
			
		||||
        then
 | 
			
		||||
            do_cmd ${CC} -Iobj ${CFLAGS} -o ${TEST} ${SRC} ${LIBS} ${EXTRAS} || return 1
 | 
			
		||||
            do_cmd ${CC} -Iobj ${CFLAGS} -o ${TEST} ${SRC} ${LIBS} ${EXTRAS} ${TEST_EXTRAS} || return 1
 | 
			
		||||
            print_success "Built ${TEST}"
 | 
			
		||||
        else
 | 
			
		||||
            print_success "${TEST} is up to date"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,299 @@
 | 
			
		|||
/* libiso8601/src/tests/manip.c
 | 
			
		||||
 *
 | 
			
		||||
 *  (c)2007, Laurence Withers, <l@lwithers.me.uk>.
 | 
			
		||||
 *  Released under the GNU GPLv2. See file COPYING or
 | 
			
		||||
 *  http://www.gnu.org/copyleft/gpl.html for details.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#include "iso8601.h"
 | 
			
		||||
 | 
			
		||||
#include <math.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <string.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* 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_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)
 | 
			
		||||
{
 | 
			
		||||
    struct date_stack* next;
 | 
			
		||||
 | 
			
		||||
    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_size;
 | 
			
		||||
    next = date_stack_top->next;
 | 
			
		||||
    free(date_stack_top);
 | 
			
		||||
    date_stack_top = next;
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
{
 | 
			
		||||
    struct date_stack* next;
 | 
			
		||||
 | 
			
		||||
    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_size;
 | 
			
		||||
    next = date_stack_top->next;
 | 
			
		||||
    free(date_stack_top);
 | 
			
		||||
    date_stack_top = next;
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* 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, 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"
 | 
			
		||||
        "\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;
 | 
			
		||||
    double period_d;
 | 
			
		||||
    char date_str[40];
 | 
			
		||||
 | 
			
		||||
    int ret = 0;
 | 
			
		||||
 | 
			
		||||
    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, "<")) {
 | 
			
		||||
            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 {
 | 
			
		||||
            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
 | 
			
		||||
*/
 | 
			
		||||
		Loading…
	
		Reference in New Issue