parsetime.c revision 35729
1258343Sdes/*
298675Sdes *  parsetime.c - parse time for at(1)
398675Sdes *  Copyright (C) 1993, 1994  Thomas Koenig
498675Sdes *
598675Sdes *  modifications for english-language times
698675Sdes *  Copyright (C) 1993  David Parsons
798675Sdes *
898675Sdes * Redistribution and use in source and binary forms, with or without
998675Sdes * modification, are permitted provided that the following conditions
1098675Sdes * are met:
1198675Sdes * 1. Redistributions of source code must retain the above copyright
1298675Sdes *    notice, this list of conditions and the following disclaimer.
1398675Sdes * 2. The name of the author(s) may not be used to endorse or promote
1498675Sdes *    products derived from this software without specific prior written
1598675Sdes *    permission.
1698675Sdes *
1798675Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
1898675Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1998675Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2098675Sdes * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
2198675Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2298675Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2398675Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2498675Sdes * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2598675Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2698675Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2798675Sdes *
2898675Sdes *  at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS
29162852Sdes *     /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]]             \
3098937Sdes *     |NOON                       | |[TOMORROW]                          |
3198675Sdes *     |MIDNIGHT                   | |[DAY OF WEEK]                       |
3298937Sdes *     \TEATIME                    / |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
33162852Sdes *                                   \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
34162852Sdes */
3598675Sdes
36162852Sdes/* System Headers */
37162852Sdes
38255767Sdes
39162852Sdes#include <sys/types.h>
40162852Sdes#include <err.h>
41162852Sdes#include <errno.h>
4298675Sdes#include <stdio.h>
4398675Sdes#include <stdlib.h>
4498675Sdes#include <string.h>
4598675Sdes#include <time.h>
4698675Sdes#include <unistd.h>
4798675Sdes#include <ctype.h>
4898675Sdes#ifndef __FreeBSD__
49106121Sdes#include <getopt.h>
50106121Sdes#endif
51106121Sdes
52106121Sdes/* Local headers */
53106121Sdes
54106121Sdes#include "at.h"
55106121Sdes#include "panic.h"
56106121Sdes
5798675Sdes
5898675Sdes/* Structures and unions */
5998675Sdes
6098675Sdesenum {	/* symbols */
6198675Sdes    MIDNIGHT, NOON, TEATIME,
6298675Sdes    PM, AM, TOMORROW, TODAY, NOW,
6398675Sdes    MINUTES, HOURS, DAYS, WEEKS,
6498675Sdes    NUMBER, PLUS, DOT, SLASH, ID, JUNK,
6598675Sdes    JAN, FEB, MAR, APR, MAY, JUN,
6698675Sdes    JUL, AUG, SEP, OCT, NOV, DEC,
6798675Sdes    SUN, MON, TUE, WED, THU, FRI, SAT
68258343Sdes    };
6998675Sdes
7098675Sdes/* parse translation table - table driven parsers can be your FRIEND!
7198675Sdes */
7298675Sdesstruct {
7398675Sdes    char *name;	/* token name */
7498675Sdes    int value;	/* token id */
7598675Sdes    int plural;	/* is this plural? */
7698675Sdes} Specials[] = {
7798675Sdes    { "midnight", MIDNIGHT,0 },	/* 00:00:00 of today or tomorrow */
7898675Sdes    { "noon", NOON,0 },		/* 12:00:00 of today or tomorrow */
7998675Sdes    { "teatime", TEATIME,0 },	/* 16:00:00 of today or tomorrow */
8098675Sdes    { "am", AM,0 },		/* morning times for 0-12 clock */
8198675Sdes    { "pm", PM,0 },		/* evening times for 0-12 clock */
8298675Sdes    { "tomorrow", TOMORROW,0 },	/* execute 24 hours from time */
8398675Sdes    { "today", TODAY, 0 },	/* execute today - don't advance time */
8498675Sdes    { "now", NOW,0 },		/* opt prefix for PLUS */
8598675Sdes
8698675Sdes    { "minute", MINUTES,0 },	/* minutes multiplier */
8798675Sdes    { "minutes", MINUTES,1 },	/* (pluralized) */
8898675Sdes    { "hour", HOURS,0 },	/* hours ... */
8998675Sdes    { "hours", HOURS,1 },	/* (pluralized) */
9098675Sdes    { "day", DAYS,0 },		/* days ... */
91258343Sdes    { "days", DAYS,1 },		/* (pluralized) */
9298675Sdes    { "week", WEEKS,0 },	/* week ... */
9398675Sdes    { "weeks", WEEKS,1 },	/* (pluralized) */
9498675Sdes    { "jan", JAN,0 },
9598675Sdes    { "feb", FEB,0 },
9698675Sdes    { "mar", MAR,0 },
9798675Sdes    { "apr", APR,0 },
9898675Sdes    { "may", MAY,0 },
9998675Sdes    { "jun", JUN,0 },
10098675Sdes    { "jul", JUL,0 },
10198675Sdes    { "aug", AUG,0 },
102106121Sdes    { "sep", SEP,0 },
103146998Sdes    { "oct", OCT,0 },
10498675Sdes    { "nov", NOV,0 },
10598675Sdes    { "dec", DEC,0 },
10698675Sdes    { "sunday", SUN, 0 },
10798675Sdes    { "sun", SUN, 0 },
10898675Sdes    { "monday", MON, 0 },
10998675Sdes    { "mon", MON, 0 },
11098675Sdes    { "tuesday", TUE, 0 },
11198675Sdes    { "tue", TUE, 0 },
11298675Sdes    { "wednesday", WED, 0 },
11398675Sdes    { "wed", WED, 0 },
11498675Sdes    { "thursday", THU, 0 },
11598675Sdes    { "thu", THU, 0 },
11698675Sdes    { "friday", FRI, 0 },
11798675Sdes    { "fri", FRI, 0 },
11898675Sdes    { "saturday", SAT, 0 },
11998675Sdes    { "sat", SAT, 0 },
12098675Sdes} ;
12198675Sdes
12298675Sdes/* File scope variables */
12398675Sdes
12498675Sdesstatic char **scp;	/* scanner - pointer at arglist */
12598675Sdesstatic char scc;	/* scanner - count of remaining arguments */
12698675Sdesstatic char *sct;	/* scanner - next char pointer in current argument */
12798675Sdesstatic int need;	/* scanner - need to advance to next argument */
128255767Sdes
12998675Sdesstatic char *sc_token;	/* scanner - token buffer */
13098675Sdesstatic size_t sc_len;   /* scanner - lenght of token buffer */
13198675Sdesstatic int sc_tokid;	/* scanner - token id */
13298675Sdesstatic int sc_tokplur;	/* scanner - is token plural? */
13398675Sdes
13498675Sdesstatic char rcsid[] = "$Id: parsetime.c,v 1.11 1997/06/24 06:26:32 charnier Exp $";
13598675Sdes
13698675Sdes/* Local functions */
13798675Sdes
13898675Sdes/*
13998675Sdes * parse a token, checking if it's something special to us
14098675Sdes */
14198675Sdesstatic int
142106121Sdesparse_token(char *arg)
14398675Sdes{
14498675Sdes    int i;
14598675Sdes
14698937Sdes    for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++)
14799060Sdes	if (strcasecmp(Specials[i].name, arg) == 0) {
14898937Sdes	    sc_tokplur = Specials[i].plural;
14998937Sdes	    return sc_tokid = Specials[i].value;
15098675Sdes	}
151255767Sdes
15298675Sdes    /* not special - must be some random id */
15398675Sdes    return ID;
15498675Sdes} /* parse_token */
15598675Sdes
15698675Sdes
15798675Sdes/*
15898675Sdes * init_scanner() sets up the scanner to eat arguments
15998675Sdes */
16098675Sdesstatic void
16198675Sdesinit_scanner(int argc, char **argv)
16298675Sdes{
16398675Sdes    scp = argv;
164258343Sdes    scc = argc;
16598675Sdes    need = 1;
16698675Sdes    sc_len = 1;
16798675Sdes    while (argc-- > 0)
16898675Sdes	sc_len += strlen(*argv++);
16998675Sdes
17098675Sdes    sc_token = (char *) mymalloc(sc_len);
17198675Sdes} /* init_scanner */
17298675Sdes
17398675Sdes/*
17498675Sdes * token() fetches a token from the input stream
17598675Sdes */
17698675Sdesstatic int
17798675Sdestoken()
178106121Sdes{
179106121Sdes    int idx;
18098675Sdes
181106121Sdes    while (1) {
18298675Sdes	memset(sc_token, 0, sc_len);
18398675Sdes	sc_tokid = EOF;
18498675Sdes	sc_tokplur = 0;
18598675Sdes	idx = 0;
18698675Sdes
18798675Sdes	/* if we need to read another argument, walk along the argument list;
18898675Sdes	 * when we fall off the arglist, we'll just return EOF forever
18998675Sdes	 */
19098675Sdes	if (need) {
19198675Sdes	    if (scc < 1)
19298675Sdes		return sc_tokid;
19398675Sdes	    sct = *scp;
19498675Sdes	    scp++;
19598675Sdes	    scc--;
19698675Sdes	    need = 0;
19798675Sdes	}
19898675Sdes	/* eat whitespace now - if we walk off the end of the argument,
19998675Sdes	 * we'll continue, which puts us up at the top of the while loop
20098675Sdes	 * to fetch the next argument in
20198675Sdes	 */
20298675Sdes	while (isspace(*sct))
203255767Sdes	    ++sct;
20498675Sdes	if (!*sct) {
20598675Sdes	    need = 1;
20698675Sdes	    continue;
20798675Sdes	}
20898675Sdes
20998675Sdes	/* preserve the first character of the new token
21098675Sdes	 */
21198675Sdes	sc_token[0] = *sct++;
21298675Sdes
21398675Sdes	/* then see what it is
21498675Sdes	 */
21598675Sdes	if (isdigit(sc_token[0])) {
21698675Sdes	    while (isdigit(*sct))
21798675Sdes		sc_token[++idx] = *sct++;
21898675Sdes	    sc_token[++idx] = 0;
21998675Sdes	    return sc_tokid = NUMBER;
22098675Sdes	}
22198675Sdes	else if (isalpha(sc_token[0])) {
22298675Sdes	    while (isalpha(*sct))
22398675Sdes		sc_token[++idx] = *sct++;
22498675Sdes	    sc_token[++idx] = 0;
22598675Sdes	    return parse_token(sc_token);
22698675Sdes	}
22798675Sdes	else if (sc_token[0] == ':' || sc_token[0] == '.')
22898675Sdes	    return sc_tokid = DOT;
22998675Sdes	else if (sc_token[0] == '+')
23098675Sdes	    return sc_tokid = PLUS;
23198675Sdes	else if (sc_token[0] == '/')
23298675Sdes	    return sc_tokid = SLASH;
23398675Sdes	else
23498675Sdes	    return sc_tokid = JUNK;
23598675Sdes    } /* while (1) */
23698675Sdes} /* token */
23798675Sdes
23898675Sdes
23998675Sdes/*
24098675Sdes * plonk() gives an appropriate error message if a token is incorrect
24198675Sdes */
24298675Sdesstatic void
24398675Sdesplonk(int tok)
24498675Sdes{
24598675Sdes    panic((tok == EOF) ? "incomplete time"
24698675Sdes		       : "garbled time");
24798675Sdes} /* plonk */
24898675Sdes
24998675Sdes
25098675Sdes/*
25198675Sdes * expect() gets a token and dies most horribly if it's not the token we want
25298675Sdes */
25398675Sdesstatic void
25498675Sdesexpect(int desired)
25598675Sdes{
25698675Sdes    if (token() != desired)
25798675Sdes	plonk(sc_tokid);	/* and we die here... */
25898675Sdes} /* expect */
259255767Sdes
26098675Sdes
26198675Sdes/*
26298675Sdes * dateadd() adds a number of minutes to a date.  It is extraordinarily
26398675Sdes * stupid regarding day-of-month overflow, and will most likely not
26498675Sdes * work properly
26598675Sdes */
26698675Sdesstatic void
26798675Sdesdateadd(int minutes, struct tm *tm)
26898675Sdes{
26998675Sdes    /* increment days */
27098675Sdes
27198675Sdes    while (minutes > 24*60) {
27298675Sdes	minutes -= 24*60;
27398675Sdes	tm->tm_mday++;
27498675Sdes    }
27598675Sdes
27698675Sdes    /* increment hours */
27798675Sdes    while (minutes > 60) {
27898675Sdes	minutes -= 60;
27998675Sdes	tm->tm_hour++;
28098675Sdes	if (tm->tm_hour > 23) {
28198675Sdes	    tm->tm_mday++;
28298675Sdes	    tm->tm_hour = 0;
283255767Sdes	}
28498675Sdes    }
28598675Sdes
28698675Sdes    /* increment minutes */
28798675Sdes    tm->tm_min += minutes;
28898675Sdes
28998675Sdes    if (tm->tm_min > 59) {
29098675Sdes	tm->tm_hour++;
29198675Sdes	tm->tm_min -= 60;
29298675Sdes
29398675Sdes	if (tm->tm_hour > 23) {
29498675Sdes	    tm->tm_mday++;
29598675Sdes	    tm->tm_hour = 0;
29698675Sdes	}
29798675Sdes    }
29898675Sdes} /* dateadd */
29998675Sdes
30098675Sdes
30198675Sdes/*
30298675Sdes * plus() parses a now + time
30398675Sdes *
30498675Sdes *  at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS]
30598675Sdes *
30698675Sdes */
30798675Sdesstatic void
30898675Sdesplus(struct tm *tm)
30998675Sdes{
31098675Sdes    int delay;
31198675Sdes    int expectplur;
31298675Sdes
31398675Sdes    expect(NUMBER);
31498675Sdes
31598675Sdes    delay = atoi(sc_token);
31698675Sdes    expectplur = (delay != 1) ? 1 : 0;
31798675Sdes
31898675Sdes    switch (token()) {
31998675Sdes    case WEEKS:
32098675Sdes	    delay *= 7;
32198675Sdes    case DAYS:
32298675Sdes	    delay *= 24;
32398675Sdes    case HOURS:
32498675Sdes	    delay *= 60;
32598675Sdes    case MINUTES:
32698675Sdes	    if (expectplur != sc_tokplur)
32798675Sdes		warnx("pluralization is wrong");
32898675Sdes	    dateadd(delay, tm);
32998675Sdes	    return;
33098675Sdes    }
33198675Sdes    plonk(sc_tokid);
33298675Sdes} /* plus */
33398675Sdes
33498675Sdes
33598675Sdes/*
33698675Sdes * tod() computes the time of day
33798675Sdes *     [NUMBER [DOT NUMBER] [AM|PM]]
33898675Sdes */
33998675Sdesstatic void
34098675Sdestod(struct tm *tm)
34198675Sdes{
34298675Sdes    int hour, minute = 0;
34398675Sdes    int tlen;
34498675Sdes
34598675Sdes    hour = atoi(sc_token);
34698675Sdes    tlen = strlen(sc_token);
34798675Sdes
34898675Sdes    /* first pick out the time of day - if it's 4 digits, we assume
34998675Sdes     * a HHMM time, otherwise it's HH DOT MM time
35098675Sdes     */
35198675Sdes    if (token() == DOT) {
35298675Sdes	expect(NUMBER);
35398675Sdes	minute = atoi(sc_token);
35498675Sdes	if (minute > 59)
355	    panic("garbled time");
356	token();
357    }
358    else if (tlen == 4) {
359	minute = hour%100;
360	if (minute > 59)
361	    panic("garbeld time");
362	hour = hour/100;
363    }
364
365    /* check if an AM or PM specifier was given
366     */
367    if (sc_tokid == AM || sc_tokid == PM) {
368	if (hour > 12)
369	    panic("garbled time");
370
371	if (sc_tokid == PM) {
372	    if (hour != 12)	/* 12:xx PM is 12:xx, not 24:xx */
373			hour += 12;
374	} else {
375	    if (hour == 12)	/* 12:xx AM is 00:xx, not 12:xx */
376			hour = 0;
377	}
378	token();
379    }
380    else if (hour > 23)
381	panic("garbled time");
382
383    /* if we specify an absolute time, we don't want to bump the day even
384     * if we've gone past that time - but if we're specifying a time plus
385     * a relative offset, it's okay to bump things
386     */
387    if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) {
388	tm->tm_mday++;
389	tm->tm_wday++;
390    }
391
392    tm->tm_hour = hour;
393    tm->tm_min = minute;
394    if (tm->tm_hour == 24) {
395	tm->tm_hour = 0;
396	tm->tm_mday++;
397    }
398} /* tod */
399
400
401/*
402 * assign_date() assigns a date, wrapping to next year if needed
403 */
404static void
405assign_date(struct tm *tm, long mday, long mon, long year)
406{
407    if (year > 99) {
408	if (year > 1899)
409	    year -= 1900;
410	else
411	    panic("garbled time");
412    } else {
413	struct tm *lt;
414	time_t now;
415
416	time(&now);
417	lt = localtime(&now);
418
419	/*
420	 * check if the specified year is in the next century.
421	 * allow for one year of user error as many people will
422	 * enter n - 1 at the start of year n.
423	 */
424	if (year < (lt->tm_year % 100) - 1)
425	    year += 100;
426	/* adjust for the year 2000 and beyond */
427	year += lt->tm_year - (lt->tm_year % 100);
428    }
429
430    if (year < 0 &&
431	(tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday)))
432	year = tm->tm_year + 1;
433
434    tm->tm_mday = mday;
435    tm->tm_mon = mon;
436
437    if (year >= 0)
438	tm->tm_year = year;
439} /* assign_date */
440
441
442/*
443 * month() picks apart a month specification
444 *
445 *  /[<month> NUMBER [NUMBER]]           \
446 *  |[TOMORROW]                          |
447 *  |[DAY OF WEEK]                       |
448 *  |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
449 *  \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
450 */
451static void
452month(struct tm *tm)
453{
454    long year= (-1);
455    long mday, wday, mon;
456    int tlen;
457
458    switch (sc_tokid) {
459    case PLUS:
460	    plus(tm);
461	    break;
462
463    case TOMORROW:
464	    /* do something tomorrow */
465	    tm->tm_mday ++;
466	    tm->tm_wday ++;
467    case TODAY:	/* force ourselves to stay in today - no further processing */
468	    token();
469	    break;
470
471    case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
472    case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
473	    /* do month mday [year]
474	     */
475	    mon = (sc_tokid-JAN);
476	    expect(NUMBER);
477	    mday = atol(sc_token);
478	    if (token() == NUMBER) {
479		year = atol(sc_token);
480		token();
481	    }
482	    assign_date(tm, mday, mon, year);
483	    break;
484
485    case SUN: case MON: case TUE:
486    case WED: case THU: case FRI:
487    case SAT:
488	    /* do a particular day of the week
489	     */
490	    wday = (sc_tokid-SUN);
491
492	    mday = tm->tm_mday;
493
494	    /* if this day is < today, then roll to next week
495	     */
496	    if (wday < tm->tm_wday)
497		mday += 7 - (tm->tm_wday - wday);
498	    else
499		mday += (wday - tm->tm_wday);
500
501	    tm->tm_wday = wday;
502
503	    assign_date(tm, mday, tm->tm_mon, tm->tm_year);
504	    break;
505
506    case NUMBER:
507	    /* get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
508	     */
509	    tlen = strlen(sc_token);
510	    mon = atol(sc_token);
511	    token();
512
513	    if (sc_tokid == SLASH || sc_tokid == DOT) {
514		int sep;
515
516		sep = sc_tokid;
517		expect(NUMBER);
518		mday = atol(sc_token);
519		if (token() == sep) {
520		    expect(NUMBER);
521		    year = atol(sc_token);
522		    token();
523		}
524
525		/* flip months and days for european timing
526		 */
527		if (sep == DOT) {
528		    int x = mday;
529		    mday = mon;
530		    mon = x;
531		}
532	    }
533	    else if (tlen == 6 || tlen == 8) {
534		if (tlen == 8) {
535		    year = (mon % 10000) - 1900;
536		    mon /= 10000;
537		}
538		else {
539		    year = mon % 100;
540		    mon /= 100;
541		}
542		mday = mon % 100;
543		mon /= 100;
544	    }
545	    else
546		panic("garbled time");
547
548	    mon--;
549	    if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
550		panic("garbled time");
551
552	    assign_date(tm, mday, mon, year);
553	    break;
554    } /* case */
555} /* month */
556
557
558/* Global functions */
559
560time_t
561parsetime(int argc, char **argv)
562{
563/* Do the argument parsing, die if necessary, and return the time the job
564 * should be run.
565 */
566    time_t nowtimer, runtimer;
567    struct tm nowtime, runtime;
568    int hr = 0;
569    /* this MUST be initialized to zero for midnight/noon/teatime */
570
571    nowtimer = time(NULL);
572    nowtime = *localtime(&nowtimer);
573
574    runtime = nowtime;
575    runtime.tm_sec = 0;
576    runtime.tm_isdst = 0;
577
578    if (argc <= optind)
579	usage();
580
581    init_scanner(argc-optind, argv+optind);
582
583    switch (token()) {
584    case NOW:	/* now is optional prefix for PLUS tree */
585	    expect(PLUS);
586    case PLUS:
587	    plus(&runtime);
588	    break;
589
590    case NUMBER:
591	    tod(&runtime);
592	    month(&runtime);
593	    break;
594
595	    /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
596	     * hr to zero up above, then fall into this case in such a
597	     * way so we add +12 +4 hours to it for teatime, +12 hours
598	     * to it for noon, and nothing at all for midnight, then
599	     * set our runtime to that hour before leaping into the
600	     * month scanner
601	     */
602    case TEATIME:
603	    hr += 4;
604    case NOON:
605	    hr += 12;
606    case MIDNIGHT:
607	    if (runtime.tm_hour >= hr) {
608		runtime.tm_mday++;
609		runtime.tm_wday++;
610	    }
611	    runtime.tm_hour = hr;
612	    runtime.tm_min = 0;
613	    token();
614	    /* fall through to month setting */
615    default:
616	    month(&runtime);
617	    break;
618    } /* ugly case statement */
619    expect(EOF);
620
621    /* adjust for daylight savings time
622     */
623    runtime.tm_isdst = -1;
624    runtimer = mktime(&runtime);
625    if (runtime.tm_isdst > 0) {
626	runtimer -= 3600;
627	runtimer = mktime(&runtime);
628    }
629
630    if (runtimer < 0)
631	panic("garbled time");
632
633    if (nowtimer > runtimer)
634	panic("Trying to travel back in time");
635
636    return runtimer;
637} /* parsetime */
638