View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0000516 | LDMud 3.3 | Efuns | public | 2007-09-14 14:51 | 2008-12-12 15:20 |
Reporter | zesstra | Assigned To | zesstra | ||
Priority | normal | Severity | feature | Reproducibility | N/A |
Status | resolved | Resolution | fixed | ||
Product Version | 3.3 | ||||
Fixed in Version | 3.3.718 | ||||
Summary | 0000516: Additional efuns for date/time conversion to strings and calculating a unix timestamp | ||||
Description | I (and probably other wizards too) often miss 2 efuns concerning date and time: 1a) an efun to convert a timestamp to a string with the possibility to specify a format string (like strftime() in libc). 1b) this efun should be able to use languages other than english (e.g. for weekday names) (Both could be done as sefun, but I think, that is a really ineffective choice (see below)) 2) an efun to convert a date/time into the corresponding unix timestamp (mktime() in libc). This is probably less often used but it is a serious problem, if you have to do it. Many wizards write some lfuns for this, but it is really easy to miss something like leapyears/leapseconds this way. I would suggest: 1a) The driver already uses strftime() from libc. We could give ctime() a second optional argument for specifying a format string. That string would be passed through to strftime(), which provides the full flexibility of strftime() for ingame use. 1b) strftime() honours Locales. The driver may set the Locale category for time/date handling from the environment variable LC_TIME/LC_ALL through calling setlocale() at driver start. A new efun ltime() could use strftime() with the "correct" local Locale set (e.g. de_DE, es_ES). In case of ctime() the driver could temporarily switch to the standard "C" locale. This way we have the classical ctime() and a localized ltime() for non-english muds. 2) There ist a libc-function called mktime(), which does exactly this. It should be simple to create an efun mktime(), which gets an array of ints like the one localtime() returns. The efun creates a corresponding tm struct, calls mktime() and returns the time stamp. | ||||
Tags | No tags attached. | ||||
Attached Files | mktime_strftime_v1.diff (14,698 bytes)
diff -ru ldmud-3.3.714.orig/src/efuns.c ldmud-3.3.714.mod/src/efuns.c --- ldmud-3.3.714.orig/src/efuns.c 2006-07-10 04:42:05.000000000 +0200 +++ ldmud-3.3.714.mod/src/efuns.c 2007-09-20 16:21:32.000000000 +0200 @@ -809,7 +809,7 @@ if (max_array_size && arraysize > (long)max_array_size-1 ) { free_regexp(reg); inter_sp = sp; - errorf("Illegal array size"); + errorf("Illegal array size: %d",arraysize); /* NOTREACHED */ return sp; } @@ -8594,15 +8594,25 @@ * * Interpret the argument clock as number of seconds since Jan, * 1st, 1970, 0.00 and convert it to a nice date and time string. + * In this case, the result string will be cached and tabled. * * Alternatively, accept an array of two ints: the first is <clock> * value as in the first form, the second int is the number of * microseconds elapsed in the current second. + * In this case the result will not be cached as the value is very + * unlikely to be the same in 2 consecutive calls. */ { char *ts, *cp; string_t *rc; + + static mp_int last_time = -1; // letzte Uhrzeit + /* pointer to pointer to the last result string (has some sense, + * because then the actual pointer to the string is findable by the GC, + * so the cached string won't be deallocated (while still in use). The + * space for **last_res will be permanently allocated.) */ + static string_t **last_res = NULL; if (sp->type != T_NUMBER) { @@ -8615,28 +8625,74 @@ if (sp->u.vec->item[1].type != T_NUMBER) errorf("Bad arg 1 to ctime(): Element 1 is '%s', expected 'int'.\n" , efun_arg_typename(sp->u.vec->item[1].type)); + ts = utime_string( sp->u.vec->item[0].u.number , sp->u.vec->item[1].u.number); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); + } + else + { + memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); + } } else { - ts = time_string(sp->u.number); - } - - /* If the string contains nl characters, extract the substring - * before the first one. Else just copy the (volatile) result - * we got. - */ - cp = strchr(ts, '\n'); - if (cp) - { - int len = cp - ts; - memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); - } - else - { - memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); - } + // test if string for this time is cached + if (last_time != sp->u.number) + { + ts = time_fstring(sp->u.number, "%a %b %d %H:%M:%S %Y", 0); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + * Table strings, because they are probably used more then once. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_tabled(ts, len), len, + "ctime() result"); + } + else + { + memsafe(rc = new_tabled(ts), strlen(ts), + "ctime() result"); + } + /* fill cache, free last (invalid) string first and don't forget + * to increase the ref count for the cache. */ + if (!last_res) { + /* allocate space for pointer. Has to be permanent, because + * otherwise the GC would wrongly free the block, resulting + * in a double free here. */ + if (!(last_res = pxalloc(sizeof(string_t*)))) { + // free result string first + free_mstring(rc); + errorf("Out of memory: couldn't allocate ctime() cache pointer"); + } + } + else // free cached string + free_mstring(*last_res); + *last_res = rc; + ref_mstring(rc); + last_time = sp->u.number; + } + else { + // return last result (and increase ref count) + rc = *last_res; + ref_mstring(rc); + } + } // if (sp->type != T_NUMBER) + free_svalue(sp); put_string(sp, rc); return sp; @@ -8702,5 +8758,143 @@ return sp; } /* f_utime() */ -/***************************************************************************/ +/*-------------------------------------------------------------------------*/ +svalue_t * +f_mktime (svalue_t *sp) +/* EFUN mktime() + * + * int time(int* datum) + * + * Return the unix timestamp (number of seconds ellapsed since 1. Jan 1970, + * 0.0:0 GMT) of the date given in the array datum. datum being an array + * like the one localtime() or gmtime() return: + * int TM_SEC (0) : Seconds (0..59) + * int TM_MIN (1) : Minutes (0..59) + * int TM_HOUR (2) : Hours (0..23) + * int TM_MDAY (3) : Day of the month (1..31) + * int TM_MON (4) : Month of the year (0..11) + * int TM_YEAR (5) : Year (e.g. 2001) + * int TM_WDAY (6) : Day of the week (Sunday = 0) + * int TM_YDAY (7) : Day of the year (0..365) + * int TM_ISDST (8) : TRUE: Daylight saving time + * TM_YDAY and TM_WDAY are ignored (but must also be ints). + * + */ +{ + struct tm * pTm; // broken-down time structure for mktime() + time_t clk; // unix timestamp corresponding to datum + vector_t * v; // just for convenience, stores argument array + int i; + + v = sp->u.vec; + if (VEC_SIZE(v) != 9) + errorf("Bad arg 1 to mktime(): Invalid array size %ld, expected 2.\n" + , (long)VEC_SIZE(v)); + // all elements must be ints. + for(i=0; i<VEC_SIZE(v); i++) + { + if ( v->item[i].type != T_NUMBER) + errorf("Bad arg 1 to ctime(): Element %d is '%s', expected 'int'.\n" + ,i, efun_arg_typename(v->item[0].type)); + } + + // create the time structure + xallocate(pTm, sizeof(*pTm), "broken-down time structure for mktime()"); + pTm->tm_sec = v->item[TM_SEC].u.number; + pTm->tm_min = v->item[TM_MIN].u.number; + pTm->tm_hour = v->item[TM_HOUR].u.number; + pTm->tm_mday = v->item[TM_MDAY].u.number; + pTm->tm_mon = v->item[TM_MON].u.number; + pTm->tm_year = v->item[TM_YEAR].u.number - 1900; + pTm->tm_isdst = v->item[TM_ISDST].u.number; + + clk = mktime(pTm); + + // free time structure first + xfree(pTm); + + if (clk == -1) + errorf("Specified date/time cannot be represented as unix timestamp.\n"); + + // free argument and put result. + free_svalue(sp); + put_number(sp, (uint32)clk); + + return sp; +} /* f_mktime() */ + +/*-------------------------------------------------------------------------*/ +svalue_t * +v_strftime(svalue_t *sp, int num_arg) +/* EFUN strftime() + * + * string strftime() + * string strftime(string fmt) + * string strftime(int clock) + * string strftime(string fmt, int clock) + * string strftime(string fmt, int clock, int localized) + * + * Interpret the argument clock as number of seconds since Jan, + * 1st, 1970, 0.00 and convert it to a nice date and time string. + * The formatstring must be given in fmt and may contain the placeholders + * defined in 'man 3 strftime'. + * If localized == MY_TRUE then the time string will be created with the + * locale set in the environment variable LC_TIME + * Defaults: fmt="%c", clock=current_time, localized=MY_TRUE + * NOTE: the returned string will have at most 511 Characters. + * TODO: Implement proper caching of the result. + * TODO: instead of using pxalloc, efuns.c may implement support functions + * TODO::for the GC, which mark the appropriate blocks as referenced. + */ + +{ + char *ts; + string_t *rc = NULL; // ergebnisstring + + /* Begin of arguments on the stack */ + svalue_t *arg = sp - num_arg + 1; + + // defaults: + Bool localized = MY_TRUE; + mp_int clk = current_time; + char *cfmt = "%c"; + + // evaluate arguments + switch(num_arg) { + case 3: + localized = (Bool)arg[2].u.number; + // fall-through + case 2: + if (arg[1].u.number < 0) + errorf("Bad arg 2 to strftime(): got %ld, expected 0 .. %ld\n", + arg[1].u.number, PINT_MAX); + clk = arg[1].u.number; + // fall-through + case 1: + // empty strings default to "%c" => only set fmt if non-empty + if (arg[0].type == T_STRING && mstrsize(arg[0].u.str)) { + cfmt = get_txt(arg[0].u.str); + } + else if (arg[0].type == T_NUMBER) { + if (num_arg>1) // bei > 1 argument nur strings erlaubt + vefun_exp_arg_error(1, TF_STRING, sp->type, sp); + else if (arg[0].u.number >= 0) + clk = arg[0].u.number; + else + errorf("Bad argument 1 to strftime(): got %ld, expected 0 .. %ld\n", + arg[0].u.number, PINT_MAX); + } + break; + } + + ts = time_fstring(clk,cfmt,localized); + memsafe(rc = new_tabled(ts), strlen(ts)+sizeof(string_t), "strftime() result"); + + sp = pop_n_elems(num_arg, sp); + push_string(sp, rc); + + return sp; +} /* f_strftime() */ + +/***************************************************************************/ diff -ru ldmud-3.3.714.orig/src/efuns.h ldmud-3.3.714.mod/src/efuns.h --- ldmud-3.3.714.orig/src/efuns.h 2006-07-10 04:42:05.000000000 +0200 +++ ldmud-3.3.714.mod/src/efuns.h 2007-09-17 09:14:41.000000000 +0200 @@ -82,11 +82,13 @@ extern svalue_t *tell_room(svalue_t *sp); extern svalue_t *f_ctime(svalue_t *); +extern svalue_t *v_strftime(svalue_t *, int num_arg); extern svalue_t *v_debug_info(svalue_t *sp, int num_arg); extern svalue_t *f_rusage(svalue_t *sp); extern svalue_t *f_random(svalue_t *); extern svalue_t *f_shutdown(svalue_t *sp); extern svalue_t *f_time(svalue_t *); +extern svalue_t *f_mktime(svalue_t *); extern svalue_t *f_utime(svalue_t *); #endif /* EFUNS_H__ */ diff -ru ldmud-3.3.714.orig/src/func_spec ldmud-3.3.714.mod/src/func_spec --- ldmud-3.3.714.orig/src/func_spec 2006-07-10 04:44:07.000000000 +0200 +++ ldmud-3.3.714.mod/src/func_spec 2007-09-20 12:58:43.000000000 +0200 @@ -373,6 +373,8 @@ mixed restore_value(string); string ctime(int*|int default: F_TIME); +string strftime(string|int|void, int|void, int|void); +int mktime(int*); int random(int); int time(); int* utime(); diff -ru ldmud-3.3.714.orig/src/main.c ldmud-3.3.714.mod/src/main.c --- ldmud-3.3.714.orig/src/main.c 2006-07-10 04:42:05.000000000 +0200 +++ ldmud-3.3.714.mod/src/main.c 2007-09-20 16:09:04.000000000 +0200 @@ -265,7 +265,8 @@ boot_time = (mp_int)time(NULL); setlocale(LC_CTYPE, ""); /* Use the locale defined in the LANG env var */ - get_stack_direction(); + setlocale(LC_TIME, ""); + get_stack_direction(); mb_init(); init_interpret(); rx_init(); diff -ru ldmud-3.3.714.orig/src/port.c ldmud-3.3.714.mod/src/port.c --- ldmud-3.3.714.orig/src/port.c 2006-07-10 04:42:05.000000000 +0200 +++ ldmud-3.3.714.mod/src/port.c 2007-09-20 15:32:30.000000000 +0200 @@ -19,6 +19,7 @@ #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif +#include <locale.h> #include <time.h> #include "backend.h" @@ -83,24 +84,32 @@ /*-------------------------------------------------------------------------*/ char * -time_string (mp_int t) +time_fstring (mp_int t, const char* str, Bool localized) -/* Return a textual representation of the time <t>. */ - -{ - static char result[80]; - struct tm *tm; - mp_int last_time = -1; - - if (t != last_time) - { - time_t ti = (time_t)t; - last_time = t; - tm = localtime(&ti); - strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S %Y", tm); - } +/* Return a textual representation of the time <t> according to the format + * string <str>. Doesn't cache because it would be necessary to + * save the format string and compare. + * If localized is true, this function sets the locale according to the + * environment variable before calling strftime and resets it afterwards. + * TODO: It would be nicer to allocate the result buffer dynamically + * TODO::for using longer format strings. */ +{ + static char result[512]; + struct tm *tm; // broken-down time struct + + time_t ti = (time_t)t; + tm = localtime(&ti); + if (!localized) { + setlocale(LC_TIME, "C"); + strftime(result, sizeof(result)-1, str, tm); + setlocale(LC_TIME, ""); + } + else + strftime(result, sizeof(result)-1, str, tm); + return result; -} /* time_string() */ +} /* time_fstring() */ + /*-------------------------------------------------------------------------*/ char * @@ -112,18 +121,13 @@ static char result[80]; struct tm *tm; size_t len; - mp_int last_t = -1, last_ut = -1; - if (t != last_t || ut != last_ut) - { - time_t ti = (time_t)t; - last_t= t; - last_ut= ut; - tm = localtime(&ti); - len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); - sprintf(result+len, "%06ld", ut); - strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); - } + time_t ti = (time_t)t; + tm = localtime(&ti); + len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); + sprintf(result+len, "%06ld", ut); + strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); + return result; } /* utime_string() */ diff -ru ldmud-3.3.714.orig/src/port.h ldmud-3.3.714.mod/src/port.h --- ldmud-3.3.714.orig/src/port.h 2006-07-10 04:42:05.000000000 +0200 +++ ldmud-3.3.714.mod/src/port.h 2007-09-20 16:28:03.000000000 +0200 @@ -427,7 +427,7 @@ extern char current_time_stamp[]; extern mp_int get_current_time(void); -extern char * time_string(mp_int); +extern char * time_fstring(mp_int t, const char* str, Bool localized); extern char * utime_string(mp_int, mp_int); extern char * time_stamp(void); extern char *xmemmem(const char *, size_t, const char *, size_t); mktime.diff (3,544 bytes)
Index: trunk/src/efuns.c =================================================================== --- trunk/src/efuns.c (Revision 2364) +++ trunk/src/efuns.c (Arbeitskopie) @@ -76,6 +76,7 @@ * efun: shutdown() * efun: gmtime() * efun: localtime() + * efun: mktime() * efun: time() * efun: utime() * @@ -8734,5 +8735,70 @@ return sp; } /* f_utime() */ +/*-------------------------------------------------------------------------*/ +svalue_t * +f_mktime (svalue_t *sp) + +/* EFUN mktime() + * + * int time(int* datum) + * + * Return the unix timestamp (number of seconds ellapsed since 1. Jan 1970, + * 0.0:0 GMT) of the date given in the array datum. datum being an array + * like the one localtime() or gmtime() return: + * int TM_SEC (0) : Seconds (0..59) + * int TM_MIN (1) : Minutes (0..59) + * int TM_HOUR (2) : Hours (0..23) + * int TM_MDAY (3) : Day of the month (1..31) + * int TM_MON (4) : Month of the year (0..11) + * int TM_YEAR (5) : Year (e.g. 2001) + * int TM_WDAY (6) : Day of the week (Sunday = 0) + * int TM_YDAY (7) : Day of the year (0..365) + * int TM_ISDST (8) : TRUE: Daylight saving time + * TM_YDAY and TM_WDAY are ignored (but must also be ints). + * + */ +{ + struct tm * pTm; // broken-down time structure for mktime() + time_t clk; // unix timestamp corresponding to datum + vector_t * v; // just for convenience, stores argument array + int i; + + v = sp->u.vec; + if (VEC_SIZE(v) != 9) + errorf("Bad arg 1 to mktime(): Invalid array size %ld, expected 9.\n" + , (long)VEC_SIZE(v)); + // all elements must be ints. + for(i=0; i<VEC_SIZE(v); i++) + { + if ( v->item[i].type != T_NUMBER) + errorf("Bad arg 1 to mktime(): Element %d is '%s', expected 'int'.\n" + ,i, efun_arg_typename(v->item[0].type)); + } + + // create the time structure + xallocate(pTm, sizeof(*pTm), "broken-down time structure for mktime()"); + pTm->tm_sec = v->item[TM_SEC].u.number; + pTm->tm_min = v->item[TM_MIN].u.number; + pTm->tm_hour = v->item[TM_HOUR].u.number; + pTm->tm_mday = v->item[TM_MDAY].u.number; + pTm->tm_mon = v->item[TM_MON].u.number; + pTm->tm_year = v->item[TM_YEAR].u.number - 1900; + pTm->tm_isdst = v->item[TM_ISDST].u.number; + + clk = mktime(pTm); + + // free time structure first + xfree(pTm); + + if (clk == -1) + errorf("Specified date/time cannot be represented as unix timestamp.\n"); + + // free argument and put result. + free_svalue(sp); + put_number(sp, (uint32)clk); + + return sp; +} /* f_mktime() */ + /***************************************************************************/ - Index: trunk/src/efuns.h =================================================================== --- trunk/src/efuns.h (Revision 2364) +++ trunk/src/efuns.h (Arbeitskopie) @@ -88,6 +88,7 @@ extern svalue_t *f_shutdown(svalue_t *sp); extern svalue_t *f_time(svalue_t *); extern svalue_t *f_utime(svalue_t *); +extern svalue_t *f_mktime(svalue_t *); #endif /* EFUNS_H__ */ Index: trunk/src/func_spec =================================================================== --- trunk/src/func_spec (Revision 2364) +++ trunk/src/func_spec (Arbeitskopie) @@ -378,6 +378,7 @@ int* utime(); mixed *gmtime(int*|int default: F_TIME); mixed *localtime(int*|int default: F_TIME); +int mktime(int*); mixed *wizlist_info(); mixed get_extra_wizinfo(null|object|string); strftime.diff (10,044 bytes)
Index: trunk/src/port.c =================================================================== --- trunk/src/port.c (Revision 2364) +++ trunk/src/port.c (Arbeitskopie) @@ -19,6 +19,7 @@ #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif +#include <locale.h> #include <time.h> #include "backend.h" @@ -83,25 +84,33 @@ /*-------------------------------------------------------------------------*/ char * -time_string (mp_int t) +time_fstring (mp_int t, const char* str, Bool localized) -/* Return a textual representation of the time <t>. */ - +/* Return a textual representation of the time <t> according to the format + * string <str>. Doesn't cache because it would be necessary to + * save the format string and compare. + * If localized is true, this function sets the locale according to the + * environment variable before calling strftime and resets it afterwards. + * TODO: It would be nicer to allocate the result buffer dynamically + * TODO::for using longer format strings. */ { - static char result[80]; - struct tm *tm; - mp_int last_time = -1; - - if (t != last_time) - { - time_t ti = (time_t)t; - last_time = t; - tm = localtime(&ti); - strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S %Y", tm); + static char result[512]; + struct tm *tm; // broken-down time struct + + time_t ti = (time_t)t; + tm = localtime(&ti); + if (!localized) { + setlocale(LC_TIME, "C"); + strftime(result, sizeof(result)-1, str, tm); + setlocale(LC_TIME, ""); } + else + strftime(result, sizeof(result)-1, str, tm); + return result; -} /* time_string() */ +} /* time_fstring() */ + /*-------------------------------------------------------------------------*/ char * utime_string (mp_int t, mp_int ut) @@ -112,18 +121,13 @@ static char result[80]; struct tm *tm; size_t len; - mp_int last_t = -1, last_ut = -1; - if (t != last_t || ut != last_ut) - { - time_t ti = (time_t)t; - last_t= t; - last_ut= ut; - tm = localtime(&ti); - len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); - sprintf(result+len, "%06ld", ut); - strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); - } + time_t ti = (time_t)t; + tm = localtime(&ti); + len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); + sprintf(result+len, "%06ld", ut); + strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); + return result; } /* utime_string() */ Index: trunk/src/port.h =================================================================== --- trunk/src/port.h (Revision 2364) +++ trunk/src/port.h (Arbeitskopie) @@ -427,7 +427,7 @@ extern char current_time_stamp[]; extern mp_int get_current_time(void); -extern char * time_string(mp_int); +extern char * time_fstring(mp_int t, const char* str, Bool localized); extern char * utime_string(mp_int, mp_int); extern char * time_stamp(void); extern char *xmemmem(const char *, size_t, const char *, size_t); Index: trunk/src/efuns.c =================================================================== --- trunk/src/efuns.c (Revision 2364) +++ trunk/src/efuns.c (Arbeitskopie) @@ -8626,16 +8626,19 @@ * * Interpret the argument clock as number of seconds since Jan, * 1st, 1970, 0.00 and convert it to a nice date and time string. + * TODO: In this case, the result string will be cached and tabled. * * Alternatively, accept an array of two ints: the first is <clock> * value as in the first form, the second int is the number of * microseconds elapsed in the current second. + * In this case the result will not be cached as the value is very + * unlikely to be the same in 2 consecutive calls. */ { char *ts, *cp; string_t *rc; - + if (sp->type != T_NUMBER) { if (VEC_SIZE(sp->u.vec) != 2) @@ -8647,28 +8650,49 @@ if (sp->u.vec->item[1].type != T_NUMBER) errorf("Bad arg 1 to ctime(): Element 1 is '%s', expected 'int'.\n" , efun_arg_typename(sp->u.vec->item[1].type)); + ts = utime_string( sp->u.vec->item[0].u.number , sp->u.vec->item[1].u.number); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); + } + else + { + memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); + } } else { - ts = time_string(sp->u.number); - } - - /* If the string contains nl characters, extract the substring - * before the first one. Else just copy the (volatile) result - * we got. - */ - cp = strchr(ts, '\n'); - if (cp) - { - int len = cp - ts; - memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); - } - else - { - memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); - } + ts = time_fstring(sp->u.number, "%a %b %d %H:%M:%S %Y", 0); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + * Table strings, because they are probably used more then once. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_tabled(ts, len), len, + "ctime() result"); + } + else + { + memsafe(rc = new_tabled(ts), strlen(ts), + "ctime() result"); + } + + } // if (sp->type != T_NUMBER) + free_svalue(sp); put_string(sp, rc); return sp; @@ -8734,5 +8758,77 @@ return sp; } /* f_utime() */ +/*-------------------------------------------------------------------------*/ +svalue_t * +v_strftime(svalue_t *sp, int num_arg) +/* EFUN strftime() + * + * string strftime() + * string strftime(string fmt) + * string strftime(int clock) + * string strftime(string fmt, int clock) + * string strftime(string fmt, int clock, int localized) + * + * Interpret the argument clock as number of seconds since Jan, + * 1st, 1970, 0.00 and convert it to a nice date and time string. + * The formatstring must be given in fmt and may contain the placeholders + * defined in 'man 3 strftime'. + * If localized == MY_TRUE then the time string will be created with the + * locale set in the environment variable LC_TIME + * Defaults: fmt="%c", clock=current_time, localized=MY_TRUE + * NOTE: the returned string will have at most 511 Characters. + * TODO: Implement caching of the result. + * TODO: instead of using pxalloc, efuns.c may implement support functions + * TODO::for the GC, which mark the appropriate blocks as referenced. + */ + +{ + char *ts; + string_t *rc = NULL; // ergebnisstring + + /* Begin of arguments on the stack */ + svalue_t *arg = sp - num_arg + 1; + + // defaults: + Bool localized = MY_TRUE; + mp_int clk = current_time; + char *cfmt = "%c"; + + // evaluate arguments + switch(num_arg) { + case 3: + localized = (Bool)arg[2].u.number; + // fall-through + case 2: + if (arg[1].u.number < 0) + errorf("Bad arg 2 to strftime(): got %ld, expected 0 .. %ld\n", + arg[1].u.number, PINT_MAX); + clk = arg[1].u.number; + // fall-through + case 1: + // empty strings default to "%c" => only set fmt if non-empty + if (arg[0].type == T_STRING && mstrsize(arg[0].u.str)) { + cfmt = get_txt(arg[0].u.str); + } + else if (arg[0].type == T_NUMBER) { + if (num_arg>1) // bei > 1 argument nur strings erlaubt + vefun_exp_arg_error(1, TF_STRING, sp->type, sp); + else if (arg[0].u.number >= 0) + clk = arg[0].u.number; + else + errorf("Bad argument 1 to strftime(): got %ld, expected 0 .. %ld\n", + arg[0].u.number, PINT_MAX); + } + break; + } + + ts = time_fstring(clk,cfmt,localized); + memsafe(rc = new_tabled(ts), strlen(ts)+sizeof(string_t), "strftime() result"); + + sp = pop_n_elems(num_arg, sp); + push_string(sp, rc); + + return sp; +} /* f_strftime() */ + /***************************************************************************/ - Index: trunk/src/main.c =================================================================== --- trunk/src/main.c (Revision 2364) +++ trunk/src/main.c (Arbeitskopie) @@ -267,6 +267,7 @@ boot_time = (mp_int)time(NULL); setlocale(LC_CTYPE, ""); /* Use the locale defined in the LANG env var */ + setlocale(LC_TIME, ""); get_stack_direction(); mb_init(); init_interpret(); Index: trunk/src/efuns.h =================================================================== --- trunk/src/efuns.h (Revision 2364) +++ trunk/src/efuns.h (Arbeitskopie) @@ -82,6 +82,7 @@ extern svalue_t *tell_room(svalue_t *sp); extern svalue_t *f_ctime(svalue_t *); +extern svalue_t *v_strftime(svalue_t *, int num_arg); extern svalue_t *v_debug_info(svalue_t *sp, int num_arg); extern svalue_t *f_rusage(svalue_t *sp); extern svalue_t *f_random(svalue_t *); Index: trunk/src/func_spec =================================================================== --- trunk/src/func_spec (Revision 2364) +++ trunk/src/func_spec (Arbeitskopie) @@ -373,6 +373,7 @@ mixed restore_value(string); string ctime(int*|int default: F_TIME); +string strftime(string|int|void, int|void, int|void); int random(int); int time(); int* utime(); strftime_cached.diff (13,011 bytes)
Index: trunk.strftime/src/port.c =================================================================== --- trunk.strftime/src/port.c (Revision 2364) +++ trunk.strftime/src/port.c (Arbeitskopie) @@ -19,6 +19,7 @@ #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif +#include <locale.h> #include <time.h> #include "backend.h" @@ -83,25 +84,33 @@ /*-------------------------------------------------------------------------*/ char * -time_string (mp_int t) +time_fstring (mp_int t, const char* str, Bool localized) -/* Return a textual representation of the time <t>. */ - +/* Return a textual representation of the time <t> according to the format + * string <str>. Doesn't cache because it would be necessary to + * save the format string and compare. + * If localized is true, this function sets the locale according to the + * environment variable before calling strftime and resets it afterwards. + * TODO: It would be nicer to allocate the result buffer dynamically + * TODO::for using longer format strings. */ { - static char result[80]; - struct tm *tm; - mp_int last_time = -1; - - if (t != last_time) - { - time_t ti = (time_t)t; - last_time = t; - tm = localtime(&ti); - strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S %Y", tm); + static char result[512]; + struct tm *tm; // broken-down time struct + + time_t ti = (time_t)t; + tm = localtime(&ti); + if (!localized) { + setlocale(LC_TIME, "C"); + strftime(result, sizeof(result)-1, str, tm); + setlocale(LC_TIME, ""); } + else + strftime(result, sizeof(result)-1, str, tm); + return result; -} /* time_string() */ +} /* time_fstring() */ + /*-------------------------------------------------------------------------*/ char * utime_string (mp_int t, mp_int ut) @@ -112,18 +121,13 @@ static char result[80]; struct tm *tm; size_t len; - mp_int last_t = -1, last_ut = -1; - if (t != last_t || ut != last_ut) - { - time_t ti = (time_t)t; - last_t= t; - last_ut= ut; - tm = localtime(&ti); - len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); - sprintf(result+len, "%06ld", ut); - strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); - } + time_t ti = (time_t)t; + tm = localtime(&ti); + len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); + sprintf(result+len, "%06ld", ut); + strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); + return result; } /* utime_string() */ Index: trunk.strftime/src/port.h =================================================================== --- trunk.strftime/src/port.h (Revision 2364) +++ trunk.strftime/src/port.h (Arbeitskopie) @@ -427,7 +427,7 @@ extern char current_time_stamp[]; extern mp_int get_current_time(void); -extern char * time_string(mp_int); +extern char * time_fstring(mp_int t, const char* str, Bool localized); extern char * utime_string(mp_int, mp_int); extern char * time_stamp(void); extern char *xmemmem(const char *, size_t, const char *, size_t); Index: trunk.strftime/src/efuns.c =================================================================== --- trunk.strftime/src/efuns.c (Revision 2364) +++ trunk.strftime/src/efuns.c (Arbeitskopie) @@ -107,6 +107,7 @@ #include "comm.h" #include "dumpstat.h" #include "exec.h" +#include "gcollect.h" #include "heartbeat.h" #include "interpret.h" #include "lex.h" @@ -141,6 +142,11 @@ #include "../mudlib/sys/strings.h" #include "../mudlib/sys/time.h" +/* Variables */ +string_t *last_ctime_result = NULL; + /* points to the result of the last f_ctime() call. If the caller asks for + * the same timestamp, it will be returned. */ + /* Forward declarations */ static void copy_svalue (svalue_t *dest, svalue_t *, struct pointer_table *, int); @@ -8626,18 +8632,24 @@ * * Interpret the argument clock as number of seconds since Jan, * 1st, 1970, 0.00 and convert it to a nice date and time string. + * In this case, the result string will be cached and tabled. * * Alternatively, accept an array of two ints: the first is <clock> * value as in the first form, the second int is the number of * microseconds elapsed in the current second. + * In this case the result will not be cached as the value is very + * unlikely to be the same in 2 consecutive calls. */ { char *ts, *cp; string_t *rc; + + static mp_int last_time = -1; // letzte Uhrzeit if (sp->type != T_NUMBER) { + /* utime case */ if (VEC_SIZE(sp->u.vec) != 2) errorf("Bad arg 1 to ctime(): Invalid array size %ld, expected 2.\n" , (long)VEC_SIZE(sp->u.vec)); @@ -8647,28 +8659,65 @@ if (sp->u.vec->item[1].type != T_NUMBER) errorf("Bad arg 1 to ctime(): Element 1 is '%s', expected 'int'.\n" , efun_arg_typename(sp->u.vec->item[1].type)); + ts = utime_string( sp->u.vec->item[0].u.number , sp->u.vec->item[1].u.number); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); + } + else + { + memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); + } } else { - ts = time_string(sp->u.number); - } - - /* If the string contains nl characters, extract the substring - * before the first one. Else just copy the (volatile) result - * we got. - */ - cp = strchr(ts, '\n'); - if (cp) - { - int len = cp - ts; - memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); - } - else - { - memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); - } + /* second-precision case */ + // test if string for this time is cached + if (last_time != sp->u.number) + { + /* cache is outdated */ + ts = time_fstring(sp->u.number, "%a %b %d %H:%M:%S %Y", 0); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + * Table strings, because they are probably used more then once. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_tabled(ts, len), len, + "ctime() result"); + } + else + { + memsafe(rc = new_tabled(ts), strlen(ts), + "ctime() result"); + } + /* fill cache, free last (invalid) string first and don't forget + * to increase the ref count for the cache. */ + free_mstring(last_ctime_result); + last_ctime_result = rc; + ref_mstring(rc); + last_time = sp->u.number; + } + else { + // return last result (and increase ref count) + rc = last_ctime_result; + ref_mstring(rc); + } + } // if (sp->type != T_NUMBER) + free_svalue(sp); put_string(sp, rc); return sp; @@ -8734,5 +8783,108 @@ return sp; } /* f_utime() */ +/*-------------------------------------------------------------------------*/ +svalue_t * +v_strftime(svalue_t *sp, int num_arg) +/* EFUN strftime() + * + * string strftime() + * string strftime(string fmt) + * string strftime(int clock) + * string strftime(string fmt, int clock) + * string strftime(string fmt, int clock, int localized) + * + * Interpret the argument clock as number of seconds since Jan, + * 1st, 1970, 0.00 and convert it to a nice date and time string. + * The formatstring must be given in fmt and may contain the placeholders + * defined in 'man 3 strftime'. + * If localized == MY_TRUE then the time string will be created with the + * locale set in the environment variable LC_TIME + * Defaults: fmt="%c", clock=current_time, localized=MY_TRUE + * NOTE: the returned string will have at most 511 Characters. + * TODO: Implement proper caching of the result. + * TODO: instead of using pxalloc, efuns.c may implement support functions + * TODO::for the GC, which mark the appropriate blocks as referenced. + */ + +{ + char *ts; + string_t *rc = NULL; // ergebnisstring + + /* Begin of arguments on the stack */ + svalue_t *arg = sp - num_arg + 1; + + // defaults: + Bool localized = MY_TRUE; + mp_int clk = current_time; + char *cfmt = "%c"; + + // evaluate arguments + switch(num_arg) { + case 3: + localized = (Bool)arg[2].u.number; + // fall-through + case 2: + if (arg[1].u.number < 0) + errorf("Bad arg 2 to strftime(): got %ld, expected 0 .. %ld\n", + arg[1].u.number, PINT_MAX); + clk = arg[1].u.number; + // fall-through + case 1: + // empty strings default to "%c" => only set fmt if non-empty + if (arg[0].type == T_STRING && mstrsize(arg[0].u.str)) { + cfmt = get_txt(arg[0].u.str); + } + else if (arg[0].type == T_NUMBER) { + if (num_arg>1) // bei > 1 argument nur strings erlaubt + vefun_exp_arg_error(1, TF_STRING, sp->type, sp); + else if (arg[0].u.number >= 0) + clk = arg[0].u.number; + else + errorf("Bad argument 1 to strftime(): got %ld, expected 0 .. %ld\n", + arg[0].u.number, PINT_MAX); + } + break; + } + + ts = time_fstring(clk,cfmt,localized); + memsafe(rc = new_tabled(ts), strlen(ts)+sizeof(string_t), "strftime() result"); + + sp = pop_n_elems(num_arg, sp); + push_string(sp, rc); + + return sp; +} /* f_strftime() */ + /***************************************************************************/ +/*-------------------------------------------------------------------------*/ +#ifdef GC_SUPPORT + +void +clear_ref_from_efuns (void) + +/* GC support: Clear the refs for the memory containing the (ctime) cache. + */ + +{ + if (last_ctime_result) + clear_string_ref(last_ctime_result); +} /* clear_ref_from_efuns() */ + +/*-------------------------------------------------------------------------*/ +void +count_ref_from_efuns (void) + +/* GC support: Count the refs for the memory containing the (ctime) cache. + */ + +{ + if (last_ctime_result) + count_ref_from_string(last_ctime_result); +} /* count_ref_from_wiz_list() */ + +#endif /* GC_SUPPORT */ + +/*-------------------------------------------------------------------------*/ + Index: trunk.strftime/src/main.c =================================================================== --- trunk.strftime/src/main.c (Revision 2364) +++ trunk.strftime/src/main.c (Arbeitskopie) @@ -267,6 +267,7 @@ boot_time = (mp_int)time(NULL); setlocale(LC_CTYPE, ""); /* Use the locale defined in the LANG env var */ + setlocale(LC_TIME, ""); get_stack_direction(); mb_init(); init_interpret(); Index: trunk.strftime/src/efuns.h =================================================================== --- trunk.strftime/src/efuns.h (Revision 2364) +++ trunk.strftime/src/efuns.h (Arbeitskopie) @@ -82,6 +82,7 @@ extern svalue_t *tell_room(svalue_t *sp); extern svalue_t *f_ctime(svalue_t *); +extern svalue_t *v_strftime(svalue_t *, int num_arg); extern svalue_t *v_debug_info(svalue_t *sp, int num_arg); extern svalue_t *f_rusage(svalue_t *sp); extern svalue_t *f_random(svalue_t *); Index: trunk.strftime/src/func_spec =================================================================== --- trunk.strftime/src/func_spec (Revision 2364) +++ trunk.strftime/src/func_spec (Arbeitskopie) @@ -373,6 +373,7 @@ mixed restore_value(string); string ctime(int*|int default: F_TIME); +string strftime(string|int|void, int|void, int|void); int random(int); int time(); int* utime(); Index: trunk.strftime/src/gcollect.c =================================================================== --- trunk.strftime/src/gcollect.c (Revision 2364) +++ trunk.strftime/src/gcollect.c (Arbeitskopie) @@ -2061,6 +2061,7 @@ mstring_clear_refs(); clear_ref_from_wiz_list(); clear_ref_from_call_outs(); + clear_ref_from_efuns(); #if defined(USE_PARSE_COMMAND) clear_parse_refs(); #endif @@ -2268,6 +2269,7 @@ count_ref_from_wiz_list(); count_ref_from_call_outs(); + count_ref_from_efuns(); if (master_ob) master_ob->ref++; mktime_strftime_noncached.diff (12,561 bytes)
Index: trunk/src/port.c =================================================================== --- trunk/src/port.c (Revision 2364) +++ trunk/src/port.c (Arbeitskopie) @@ -19,6 +19,7 @@ #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif +#include <locale.h> #include <time.h> #include "backend.h" @@ -83,25 +84,33 @@ /*-------------------------------------------------------------------------*/ char * -time_string (mp_int t) +time_fstring (mp_int t, const char* str, Bool localized) -/* Return a textual representation of the time <t>. */ - +/* Return a textual representation of the time <t> according to the format + * string <str>. Doesn't cache because it would be necessary to + * save the format string and compare. + * If localized is true, this function sets the locale according to the + * environment variable before calling strftime and resets it afterwards. + * TODO: It would be nicer to allocate the result buffer dynamically + * TODO::for using longer format strings. */ { - static char result[80]; - struct tm *tm; - mp_int last_time = -1; - - if (t != last_time) - { - time_t ti = (time_t)t; - last_time = t; - tm = localtime(&ti); - strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S %Y", tm); + static char result[512]; + struct tm *tm; // broken-down time struct + + time_t ti = (time_t)t; + tm = localtime(&ti); + if (!localized) { + setlocale(LC_TIME, "C"); + strftime(result, sizeof(result)-1, str, tm); + setlocale(LC_TIME, ""); } + else + strftime(result, sizeof(result)-1, str, tm); + return result; -} /* time_string() */ +} /* time_fstring() */ + /*-------------------------------------------------------------------------*/ char * utime_string (mp_int t, mp_int ut) @@ -112,18 +121,13 @@ static char result[80]; struct tm *tm; size_t len; - mp_int last_t = -1, last_ut = -1; - if (t != last_t || ut != last_ut) - { - time_t ti = (time_t)t; - last_t= t; - last_ut= ut; - tm = localtime(&ti); - len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); - sprintf(result+len, "%06ld", ut); - strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); - } + time_t ti = (time_t)t; + tm = localtime(&ti); + len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); + sprintf(result+len, "%06ld", ut); + strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); + return result; } /* utime_string() */ Index: trunk/src/port.h =================================================================== --- trunk/src/port.h (Revision 2364) +++ trunk/src/port.h (Arbeitskopie) @@ -427,7 +427,7 @@ extern char current_time_stamp[]; extern mp_int get_current_time(void); -extern char * time_string(mp_int); +extern char * time_fstring(mp_int t, const char* str, Bool localized); extern char * utime_string(mp_int, mp_int); extern char * time_stamp(void); extern char *xmemmem(const char *, size_t, const char *, size_t); Index: trunk/src/efuns.c =================================================================== --- trunk/src/efuns.c (Revision 2364) +++ trunk/src/efuns.c (Arbeitskopie) @@ -8626,16 +8626,19 @@ * * Interpret the argument clock as number of seconds since Jan, * 1st, 1970, 0.00 and convert it to a nice date and time string. + * TODO: In this case, the result string will be cached and tabled. * * Alternatively, accept an array of two ints: the first is <clock> * value as in the first form, the second int is the number of * microseconds elapsed in the current second. + * In this case the result will not be cached as the value is very + * unlikely to be the same in 2 consecutive calls. */ { char *ts, *cp; string_t *rc; - + if (sp->type != T_NUMBER) { if (VEC_SIZE(sp->u.vec) != 2) @@ -8647,28 +8650,49 @@ if (sp->u.vec->item[1].type != T_NUMBER) errorf("Bad arg 1 to ctime(): Element 1 is '%s', expected 'int'.\n" , efun_arg_typename(sp->u.vec->item[1].type)); + ts = utime_string( sp->u.vec->item[0].u.number , sp->u.vec->item[1].u.number); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); + } + else + { + memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); + } } else { - ts = time_string(sp->u.number); - } - - /* If the string contains nl characters, extract the substring - * before the first one. Else just copy the (volatile) result - * we got. - */ - cp = strchr(ts, '\n'); - if (cp) - { - int len = cp - ts; - memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); - } - else - { - memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); - } + ts = time_fstring(sp->u.number, "%a %b %d %H:%M:%S %Y", 0); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + * Table strings, because they are probably used more then once. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_tabled(ts, len), len, + "ctime() result"); + } + else + { + memsafe(rc = new_tabled(ts), strlen(ts), + "ctime() result"); + } + + } // if (sp->type != T_NUMBER) + free_svalue(sp); put_string(sp, rc); return sp; @@ -8734,5 +8758,143 @@ return sp; } /* f_utime() */ +/*-------------------------------------------------------------------------*/ +svalue_t * +f_mktime (svalue_t *sp) + +/* EFUN mktime() + * + * int time(int* datum) + * + * Return the unix timestamp (number of seconds ellapsed since 1. Jan 1970, + * 0.0:0 GMT) of the date given in the array datum. datum being an array + * like the one localtime() or gmtime() return: + * int TM_SEC (0) : Seconds (0..59) + * int TM_MIN (1) : Minutes (0..59) + * int TM_HOUR (2) : Hours (0..23) + * int TM_MDAY (3) : Day of the month (1..31) + * int TM_MON (4) : Month of the year (0..11) + * int TM_YEAR (5) : Year (e.g. 2001) + * int TM_WDAY (6) : Day of the week (Sunday = 0) + * int TM_YDAY (7) : Day of the year (0..365) + * int TM_ISDST (8) : TRUE: Daylight saving time + * TM_YDAY and TM_WDAY are ignored (but must also be ints). + * + */ +{ + struct tm * pTm; // broken-down time structure for mktime() + time_t clk; // unix timestamp corresponding to datum + vector_t * v; // just for convenience, stores argument array + int i; + + v = sp->u.vec; + if (VEC_SIZE(v) != 9) + errorf("Bad arg 1 to mktime(): Invalid array size %ld, expected 2.\n" + , (long)VEC_SIZE(v)); + // all elements must be ints. + for(i=0; i<VEC_SIZE(v); i++) + { + if ( v->item[i].type != T_NUMBER) + errorf("Bad arg 1 to ctime(): Element %d is '%s', expected 'int'.\n" + ,i, efun_arg_typename(v->item[0].type)); + } + + // create the time structure + xallocate(pTm, sizeof(*pTm), "broken-down time structure for mktime()"); + pTm->tm_sec = v->item[TM_SEC].u.number; + pTm->tm_min = v->item[TM_MIN].u.number; + pTm->tm_hour = v->item[TM_HOUR].u.number; + pTm->tm_mday = v->item[TM_MDAY].u.number; + pTm->tm_mon = v->item[TM_MON].u.number; + pTm->tm_year = v->item[TM_YEAR].u.number - 1900; + pTm->tm_isdst = v->item[TM_ISDST].u.number; + + clk = mktime(pTm); + + // free time structure first + xfree(pTm); + + if (clk == -1) + errorf("Specified date/time cannot be represented as unix timestamp.\n"); + + // free argument and put result. + free_svalue(sp); + put_number(sp, (uint32)clk); + + return sp; +} /* f_mktime() */ + +/*-------------------------------------------------------------------------*/ +svalue_t * +v_strftime(svalue_t *sp, int num_arg) +/* EFUN strftime() + * + * string strftime() + * string strftime(string fmt) + * string strftime(int clock) + * string strftime(string fmt, int clock) + * string strftime(string fmt, int clock, int localized) + * + * Interpret the argument clock as number of seconds since Jan, + * 1st, 1970, 0.00 and convert it to a nice date and time string. + * The formatstring must be given in fmt and may contain the placeholders + * defined in 'man 3 strftime'. + * If localized == MY_TRUE then the time string will be created with the + * locale set in the environment variable LC_TIME + * Defaults: fmt="%c", clock=current_time, localized=MY_TRUE + * NOTE: the returned string will have at most 511 Characters. + * TODO: Implement caching of the result. + * TODO: instead of using pxalloc, efuns.c may implement support functions + * TODO::for the GC, which mark the appropriate blocks as referenced. + */ + +{ + char *ts; + string_t *rc = NULL; // ergebnisstring + + /* Begin of arguments on the stack */ + svalue_t *arg = sp - num_arg + 1; + + // defaults: + Bool localized = MY_TRUE; + mp_int clk = current_time; + char *cfmt = "%c"; + + // evaluate arguments + switch(num_arg) { + case 3: + localized = (Bool)arg[2].u.number; + // fall-through + case 2: + if (arg[1].u.number < 0) + errorf("Bad arg 2 to strftime(): got %ld, expected 0 .. %ld\n", + arg[1].u.number, PINT_MAX); + clk = arg[1].u.number; + // fall-through + case 1: + // empty strings default to "%c" => only set fmt if non-empty + if (arg[0].type == T_STRING && mstrsize(arg[0].u.str)) { + cfmt = get_txt(arg[0].u.str); + } + else if (arg[0].type == T_NUMBER) { + if (num_arg>1) // bei > 1 argument nur strings erlaubt + vefun_exp_arg_error(1, TF_STRING, sp->type, sp); + else if (arg[0].u.number >= 0) + clk = arg[0].u.number; + else + errorf("Bad argument 1 to strftime(): got %ld, expected 0 .. %ld\n", + arg[0].u.number, PINT_MAX); + } + break; + } + + ts = time_fstring(clk,cfmt,localized); + memsafe(rc = new_tabled(ts), strlen(ts)+sizeof(string_t), "strftime() result"); + + sp = pop_n_elems(num_arg, sp); + push_string(sp, rc); + + return sp; +} /* f_strftime() */ + /***************************************************************************/ - Index: trunk/src/main.c =================================================================== --- trunk/src/main.c (Revision 2364) +++ trunk/src/main.c (Arbeitskopie) @@ -267,6 +267,7 @@ boot_time = (mp_int)time(NULL); setlocale(LC_CTYPE, ""); /* Use the locale defined in the LANG env var */ + setlocale(LC_TIME, ""); get_stack_direction(); mb_init(); init_interpret(); Index: trunk/src/efuns.h =================================================================== --- trunk/src/efuns.h (Revision 2364) +++ trunk/src/efuns.h (Arbeitskopie) @@ -82,11 +82,13 @@ extern svalue_t *tell_room(svalue_t *sp); extern svalue_t *f_ctime(svalue_t *); +extern svalue_t *v_strftime(svalue_t *, int num_arg); extern svalue_t *v_debug_info(svalue_t *sp, int num_arg); extern svalue_t *f_rusage(svalue_t *sp); extern svalue_t *f_random(svalue_t *); extern svalue_t *f_shutdown(svalue_t *sp); extern svalue_t *f_time(svalue_t *); +extern svalue_t *f_mktime(svalue_t *); extern svalue_t *f_utime(svalue_t *); #endif /* EFUNS_H__ */ Index: trunk/src/func_spec =================================================================== --- trunk/src/func_spec (Revision 2364) +++ trunk/src/func_spec (Arbeitskopie) @@ -373,6 +373,8 @@ mixed restore_value(string); string ctime(int*|int default: F_TIME); +string strftime(string|int|void, int|void, int|void); +int mktime(int*); int random(int); int time(); int* utime(); mktime_strftime_cached.diff (15,527 bytes)
Index: trunk.strftime/src/port.c =================================================================== --- trunk.strftime/src/port.c (Revision 2364) +++ trunk.strftime/src/port.c (Arbeitskopie) @@ -19,6 +19,7 @@ #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #endif +#include <locale.h> #include <time.h> #include "backend.h" @@ -83,25 +84,33 @@ /*-------------------------------------------------------------------------*/ char * -time_string (mp_int t) +time_fstring (mp_int t, const char* str, Bool localized) -/* Return a textual representation of the time <t>. */ - +/* Return a textual representation of the time <t> according to the format + * string <str>. Doesn't cache because it would be necessary to + * save the format string and compare. + * If localized is true, this function sets the locale according to the + * environment variable before calling strftime and resets it afterwards. + * TODO: It would be nicer to allocate the result buffer dynamically + * TODO::for using longer format strings. */ { - static char result[80]; - struct tm *tm; - mp_int last_time = -1; - - if (t != last_time) - { - time_t ti = (time_t)t; - last_time = t; - tm = localtime(&ti); - strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S %Y", tm); + static char result[512]; + struct tm *tm; // broken-down time struct + + time_t ti = (time_t)t; + tm = localtime(&ti); + if (!localized) { + setlocale(LC_TIME, "C"); + strftime(result, sizeof(result)-1, str, tm); + setlocale(LC_TIME, ""); } + else + strftime(result, sizeof(result)-1, str, tm); + return result; -} /* time_string() */ +} /* time_fstring() */ + /*-------------------------------------------------------------------------*/ char * utime_string (mp_int t, mp_int ut) @@ -112,18 +121,13 @@ static char result[80]; struct tm *tm; size_t len; - mp_int last_t = -1, last_ut = -1; - if (t != last_t || ut != last_ut) - { - time_t ti = (time_t)t; - last_t= t; - last_ut= ut; - tm = localtime(&ti); - len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); - sprintf(result+len, "%06ld", ut); - strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); - } + time_t ti = (time_t)t; + tm = localtime(&ti); + len = strftime(result, sizeof(result)-1, "%a %b %d %H:%M:%S:", tm); + sprintf(result+len, "%06ld", ut); + strftime(result+len+6, sizeof(result)-7-len, " %Y", tm); + return result; } /* utime_string() */ Index: trunk.strftime/src/port.h =================================================================== --- trunk.strftime/src/port.h (Revision 2364) +++ trunk.strftime/src/port.h (Arbeitskopie) @@ -427,7 +427,7 @@ extern char current_time_stamp[]; extern mp_int get_current_time(void); -extern char * time_string(mp_int); +extern char * time_fstring(mp_int t, const char* str, Bool localized); extern char * utime_string(mp_int, mp_int); extern char * time_stamp(void); extern char *xmemmem(const char *, size_t, const char *, size_t); Index: trunk.strftime/src/efuns.c =================================================================== --- trunk.strftime/src/efuns.c (Revision 2364) +++ trunk.strftime/src/efuns.c (Arbeitskopie) @@ -107,6 +107,7 @@ #include "comm.h" #include "dumpstat.h" #include "exec.h" +#include "gcollect.h" #include "heartbeat.h" #include "interpret.h" #include "lex.h" @@ -141,6 +142,11 @@ #include "../mudlib/sys/strings.h" #include "../mudlib/sys/time.h" +/* Variables */ +string_t *last_ctime_result = NULL; + /* points to the result of the last f_ctime() call. If the caller asks for + * the same timestamp, it will be returned. */ + /* Forward declarations */ static void copy_svalue (svalue_t *dest, svalue_t *, struct pointer_table *, int); @@ -8626,18 +8632,24 @@ * * Interpret the argument clock as number of seconds since Jan, * 1st, 1970, 0.00 and convert it to a nice date and time string. + * In this case, the result string will be cached and tabled. * * Alternatively, accept an array of two ints: the first is <clock> * value as in the first form, the second int is the number of * microseconds elapsed in the current second. + * In this case the result will not be cached as the value is very + * unlikely to be the same in 2 consecutive calls. */ { char *ts, *cp; string_t *rc; + + static mp_int last_time = -1; // letzte Uhrzeit if (sp->type != T_NUMBER) { + /* utime case */ if (VEC_SIZE(sp->u.vec) != 2) errorf("Bad arg 1 to ctime(): Invalid array size %ld, expected 2.\n" , (long)VEC_SIZE(sp->u.vec)); @@ -8647,28 +8659,65 @@ if (sp->u.vec->item[1].type != T_NUMBER) errorf("Bad arg 1 to ctime(): Element 1 is '%s', expected 'int'.\n" , efun_arg_typename(sp->u.vec->item[1].type)); + ts = utime_string( sp->u.vec->item[0].u.number , sp->u.vec->item[1].u.number); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); + } + else + { + memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); + } } else { - ts = time_string(sp->u.number); - } - - /* If the string contains nl characters, extract the substring - * before the first one. Else just copy the (volatile) result - * we got. - */ - cp = strchr(ts, '\n'); - if (cp) - { - int len = cp - ts; - memsafe(rc = new_n_mstring(ts, len), len, "ctime() result"); - } - else - { - memsafe(rc = new_mstring(ts), strlen(ts), "ctime() result"); - } + /* second-precision case */ + // test if string for this time is cached + if (last_time != sp->u.number) + { + /* cache is outdated */ + ts = time_fstring(sp->u.number, "%a %b %d %H:%M:%S %Y", 0); + + /* If the string contains nl characters, extract the substring + * before the first one. Else just copy the (volatile) result + * we got. + * Table strings, because they are probably used more then once. + */ + cp = strchr(ts, '\n'); + if (cp) + { + int len = cp - ts; + memsafe(rc = new_n_tabled(ts, len), len, + "ctime() result"); + } + else + { + memsafe(rc = new_tabled(ts), strlen(ts), + "ctime() result"); + } + /* fill cache, free last (invalid) string first and don't forget + * to increase the ref count for the cache. */ + free_mstring(last_ctime_result); + last_ctime_result = rc; + ref_mstring(rc); + last_time = sp->u.number; + } + else { + // return last result (and increase ref count) + rc = last_ctime_result; + ref_mstring(rc); + } + } // if (sp->type != T_NUMBER) + free_svalue(sp); put_string(sp, rc); return sp; @@ -8734,5 +8783,174 @@ return sp; } /* f_utime() */ +/*-------------------------------------------------------------------------*/ +svalue_t * +f_mktime (svalue_t *sp) + +/* EFUN mktime() + * + * int time(int* datum) + * + * Return the unix timestamp (number of seconds ellapsed since 1. Jan 1970, + * 0.0:0 GMT) of the date given in the array datum. datum being an array + * like the one localtime() or gmtime() return: + * int TM_SEC (0) : Seconds (0..59) + * int TM_MIN (1) : Minutes (0..59) + * int TM_HOUR (2) : Hours (0..23) + * int TM_MDAY (3) : Day of the month (1..31) + * int TM_MON (4) : Month of the year (0..11) + * int TM_YEAR (5) : Year (e.g. 2001) + * int TM_WDAY (6) : Day of the week (Sunday = 0) + * int TM_YDAY (7) : Day of the year (0..365) + * int TM_ISDST (8) : TRUE: Daylight saving time + * TM_YDAY and TM_WDAY are ignored (but must also be ints). + * + */ +{ + struct tm * pTm; // broken-down time structure for mktime() + time_t clk; // unix timestamp corresponding to datum + vector_t * v; // just for convenience, stores argument array + int i; + + v = sp->u.vec; + if (VEC_SIZE(v) != 9) + errorf("Bad arg 1 to mktime(): Invalid array size %ld, expected 2.\n" + , (long)VEC_SIZE(v)); + // all elements must be ints. + for(i=0; i<VEC_SIZE(v); i++) + { + if ( v->item[i].type != T_NUMBER) + errorf("Bad arg 1 to ctime(): Element %d is '%s', expected 'int'.\n" + ,i, efun_arg_typename(v->item[0].type)); + } + + // create the time structure + xallocate(pTm, sizeof(*pTm), "broken-down time structure for mktime()"); + pTm->tm_sec = v->item[TM_SEC].u.number; + pTm->tm_min = v->item[TM_MIN].u.number; + pTm->tm_hour = v->item[TM_HOUR].u.number; + pTm->tm_mday = v->item[TM_MDAY].u.number; + pTm->tm_mon = v->item[TM_MON].u.number; + pTm->tm_year = v->item[TM_YEAR].u.number - 1900; + pTm->tm_isdst = v->item[TM_ISDST].u.number; + + clk = mktime(pTm); + + // free time structure first + xfree(pTm); + + if (clk == -1) + errorf("Specified date/time cannot be represented as unix timestamp.\n"); + + // free argument and put result. + free_svalue(sp); + put_number(sp, (uint32)clk); + + return sp; +} /* f_mktime() */ + +/*-------------------------------------------------------------------------*/ +svalue_t * +v_strftime(svalue_t *sp, int num_arg) +/* EFUN strftime() + * + * string strftime() + * string strftime(string fmt) + * string strftime(int clock) + * string strftime(string fmt, int clock) + * string strftime(string fmt, int clock, int localized) + * + * Interpret the argument clock as number of seconds since Jan, + * 1st, 1970, 0.00 and convert it to a nice date and time string. + * The formatstring must be given in fmt and may contain the placeholders + * defined in 'man 3 strftime'. + * If localized == MY_TRUE then the time string will be created with the + * locale set in the environment variable LC_TIME + * Defaults: fmt="%c", clock=current_time, localized=MY_TRUE + * NOTE: the returned string will have at most 511 Characters. + * TODO: Implement proper caching of the result. + * TODO: instead of using pxalloc, efuns.c may implement support functions + * TODO::for the GC, which mark the appropriate blocks as referenced. + */ + +{ + char *ts; + string_t *rc = NULL; // ergebnisstring + + /* Begin of arguments on the stack */ + svalue_t *arg = sp - num_arg + 1; + + // defaults: + Bool localized = MY_TRUE; + mp_int clk = current_time; + char *cfmt = "%c"; + + // evaluate arguments + switch(num_arg) { + case 3: + localized = (Bool)arg[2].u.number; + // fall-through + case 2: + if (arg[1].u.number < 0) + errorf("Bad arg 2 to strftime(): got %ld, expected 0 .. %ld\n", + arg[1].u.number, PINT_MAX); + clk = arg[1].u.number; + // fall-through + case 1: + // empty strings default to "%c" => only set fmt if non-empty + if (arg[0].type == T_STRING && mstrsize(arg[0].u.str)) { + cfmt = get_txt(arg[0].u.str); + } + else if (arg[0].type == T_NUMBER) { + if (num_arg>1) // bei > 1 argument nur strings erlaubt + vefun_exp_arg_error(1, TF_STRING, sp->type, sp); + else if (arg[0].u.number >= 0) + clk = arg[0].u.number; + else + errorf("Bad argument 1 to strftime(): got %ld, expected 0 .. %ld\n", + arg[0].u.number, PINT_MAX); + } + break; + } + + ts = time_fstring(clk,cfmt,localized); + memsafe(rc = new_tabled(ts), strlen(ts)+sizeof(string_t), "strftime() result"); + + sp = pop_n_elems(num_arg, sp); + push_string(sp, rc); + + return sp; +} /* f_strftime() */ + /***************************************************************************/ +/*-------------------------------------------------------------------------*/ +#ifdef GC_SUPPORT + +void +clear_ref_from_efuns (void) + +/* GC support: Clear the refs for the memory containing the (ctime) cache. + */ + +{ + if (last_ctime_result) + clear_string_ref(last_ctime_result); +} /* clear_ref_from_efuns() */ + +/*-------------------------------------------------------------------------*/ +void +count_ref_from_efuns (void) + +/* GC support: Count the refs for the memory containing the (ctime) cache. + */ + +{ + if (last_ctime_result) + count_ref_from_string(last_ctime_result); +} /* count_ref_from_wiz_list() */ + +#endif /* GC_SUPPORT */ + +/*-------------------------------------------------------------------------*/ + Index: trunk.strftime/src/main.c =================================================================== --- trunk.strftime/src/main.c (Revision 2364) +++ trunk.strftime/src/main.c (Arbeitskopie) @@ -267,6 +267,7 @@ boot_time = (mp_int)time(NULL); setlocale(LC_CTYPE, ""); /* Use the locale defined in the LANG env var */ + setlocale(LC_TIME, ""); get_stack_direction(); mb_init(); init_interpret(); Index: trunk.strftime/src/efuns.h =================================================================== --- trunk.strftime/src/efuns.h (Revision 2364) +++ trunk.strftime/src/efuns.h (Arbeitskopie) @@ -82,11 +82,13 @@ extern svalue_t *tell_room(svalue_t *sp); extern svalue_t *f_ctime(svalue_t *); +extern svalue_t *v_strftime(svalue_t *, int num_arg); extern svalue_t *v_debug_info(svalue_t *sp, int num_arg); extern svalue_t *f_rusage(svalue_t *sp); extern svalue_t *f_random(svalue_t *); extern svalue_t *f_shutdown(svalue_t *sp); extern svalue_t *f_time(svalue_t *); +extern svalue_t *f_mktime(svalue_t *); extern svalue_t *f_utime(svalue_t *); #endif /* EFUNS_H__ */ Index: trunk.strftime/src/func_spec =================================================================== --- trunk.strftime/src/func_spec (Revision 2364) +++ trunk.strftime/src/func_spec (Arbeitskopie) @@ -373,6 +373,8 @@ mixed restore_value(string); string ctime(int*|int default: F_TIME); +string strftime(string|int|void, int|void, int|void); +int mktime(int*); int random(int); int time(); int* utime(); Index: trunk.strftime/src/gcollect.c =================================================================== --- trunk.strftime/src/gcollect.c (Revision 2364) +++ trunk.strftime/src/gcollect.c (Arbeitskopie) @@ -2061,6 +2061,7 @@ mstring_clear_refs(); clear_ref_from_wiz_list(); clear_ref_from_call_outs(); + clear_ref_from_efuns(); #if defined(USE_PARSE_COMMAND) clear_parse_refs(); #endif @@ -2268,6 +2269,7 @@ count_ref_from_wiz_list(); count_ref_from_call_outs(); + count_ref_from_efuns(); if (master_ob) master_ob->ref++; | ||||
|
I uploaded a patch with the 2 new efuns: mktime() and strftime(). The patch got a bit larger than expected. ;-) 1. mktime() takes an array like the one localtime() returns and converts it to a timestamp by calling the libc mktime(). 2. strftime() is varargs and takes up to 3 arguments: format string, timestamp and a flag specifying the locale to use ("C" or the one specified by the environment variable LC_TIME/LC_ALL). This efun will not take a utime argument, because you would have to implement a specific format identifier for the microseconds and parse the strings yourself, which probably should better be done in an sefun. For strftime() I made some changes to port.c|h: a) New function: char *time_fstring (mp_int t, const char* str, Bool localized). If localization ist not requested, the locale is temporarily changed to the "C" locale and set back before leaving the function. I chose it this way, because the f_ctime()-Result ist cached, while the one for v_strftime() is not (at the moment). b) time_string() was removed, f_ctime() now calls time_fstring() with the appropriate arguments. c) utime_string() should have cached the result, but that didn't work because the variables weren't static. (same in time_string()) As it is quite unlikely, that the microsecond value does not change from one call to the other, I think it is not worth trying to cache the result and removed caching altogether from that function. Changes in efuns.c|h a) new efuns b) ctime() calls time_fstring(). c) ctime() now caches the result as tabled string in the efun. Consecutive calls with the same time argument to ctime() use about 20% of the first call. Changes to main.c a) activating the LC_TIME locale category and setting it to the locale specified by LC_ALL/LC_TIME. Potential Todos: 1. time_fstring() should be able to use format string longer than 511 Chars. 2. Caching for v_strftime(). This probably requires implementing some kind of GC support and initialization function in efuns.c. I will create manpages for the efuns soon and attach them. If you like to have a look at the changes, the new efuns and/or try the patch, please do and comment. ;-) |
|
I just want to note, that the caching in the suggested patch behaves rather badly with the garbage collector. I have removed the caching for now from my code at home until I have time to write some caching mechanism which actually works with the GC. If you are interested, I can upload the non-caching functions in the meantime. |
|
I prepared a new set of patches (applying cleanly against r2364). I splitted the patch into two and added a cache mechanism to ctime() which actually works this time. ;-) mktime.diff: only the efun mktime() strfime.diff: only the efun strftime() (including changes to port.c|h and ctime()) strfime_cached.diff: only the efun strftime(), cached version (including changes to port.c|h and ctime()) For convenience, because the 2 separate patches do not apply cleanly, I revised the combined patches: mktime_strftime_noncached.diff: mktime() and strftime(), non-caching ctime() mktime_strftime_cached.diff: mktime() and strftime(), caching ctime() Note: The cache for ctime() was formerly in time_string() in port.c. I removed it there and put it a level higher in the efun, which spares the driver for searching the string hash table for the cached result from time_string() in port.c. Comparison of 300k ctime() calls: old: 752 ms new, cached: 85 ms Note2: For compatibilty with the garbage collector I added clear_ref_from_efuns() and count_ref_from_efuns() to efuns.c and call them from gcollect.c for counting the refs to the ctime cache. |
|
I would like to include these efuns in the next release. We have them in use in Morgengrauen for about a year, the current versions for about 2 months. I still have to prepare the english manpages. Are there any objections against these 2 new efuns? |
|
Applied latest patches, added german and english manpages and committed as r2440 (3.3.718). |
Date Modified | Username | Field | Change |
---|---|---|---|
2007-09-14 14:51 | zesstra | New Issue | |
2007-09-20 08:33 | zesstra | File Added: mktime_strftime_v1.diff | |
2007-09-20 08:34 | zesstra | Note Added: 0000544 | |
2007-09-30 14:49 | zesstra | Note Added: 0000545 | |
2008-05-18 16:41 | zesstra | File Added: mktime.diff | |
2008-05-18 16:42 | zesstra | File Added: strftime.diff | |
2008-05-18 16:42 | zesstra | File Added: strftime_cached.diff | |
2008-05-18 16:42 | zesstra | File Added: mktime_strftime_noncached.diff | |
2008-05-18 16:42 | zesstra | File Added: mktime_strftime_cached.diff | |
2008-05-18 16:43 | zesstra | Note Added: 0000622 | |
2008-07-16 16:18 | zesstra | Status | new => assigned |
2008-07-16 16:18 | zesstra | Assigned To | => zesstra |
2008-10-04 16:12 | zesstra | Note Added: 0000803 | |
2008-12-12 15:20 | zesstra | Status | assigned => resolved |
2008-12-12 15:20 | zesstra | Fixed in Version | => 3.3.718 |
2008-12-12 15:20 | zesstra | Resolution | open => fixed |
2008-12-12 15:20 | zesstra | Note Added: 0000813 |