From 9633ee3f721625589b60d0fa21e413e93e2ce1c2 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Sun, 24 Dec 2023 09:09:24 +0900 Subject: [PATCH] New suite of time functions --- src/modules/module_time.c | 224 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/src/modules/module_time.c b/src/modules/module_time.c index 3dd094f..a1ed8a4 100644 --- a/src/modules/module_time.c +++ b/src/modules/module_time.c @@ -38,6 +38,192 @@ KRK_Function(time) { return FLOATING_VAL(out); } +static KrkClass * struct_time; + +struct struct_time_obj { + KrkInstance inst; + struct tm _value; +}; + +#define IS_struct_time(o) (krk_isInstanceOf(o,struct_time)) +#define AS_struct_time(o) ((struct struct_time_obj*)AS_OBJECT(o)) +#define CURRENT_CTYPE struct struct_time_obj * +#define CURRENT_NAME self + +KRK_Method(struct_time,__init__) { + KrkValue seq; + if (!krk_parseArgs(".V:struct_time", (const char *[]){"iterable"}, &seq)) return NONE_VAL(); + if (!IS_TUPLE(seq) || AS_TUPLE(seq)->values.count != 9) return krk_runtimeError(vm.exceptions->notImplementedError, "sequence other than 9-tuple unsupported"); + for (int i = 0; i < 9; ++i) { + if (!IS_INTEGER(AS_TUPLE(seq)->values.values[i])) return krk_runtimeError(vm.exceptions->valueError, "expected int, not %T", AS_TUPLE(seq)->values.values[i]); + } + + self->_value.tm_year = AS_INTEGER(AS_TUPLE(seq)->values.values[0]) - 1900; + self->_value.tm_mon = AS_INTEGER(AS_TUPLE(seq)->values.values[1]) - 1; + self->_value.tm_mday = AS_INTEGER(AS_TUPLE(seq)->values.values[2]); + self->_value.tm_hour = AS_INTEGER(AS_TUPLE(seq)->values.values[3]); + self->_value.tm_min = AS_INTEGER(AS_TUPLE(seq)->values.values[4]); + self->_value.tm_sec = AS_INTEGER(AS_TUPLE(seq)->values.values[5]); + self->_value.tm_wday = (AS_INTEGER(AS_TUPLE(seq)->values.values[6])+6)%7; + self->_value.tm_yday = AS_INTEGER(AS_TUPLE(seq)->values.values[7]) - 1; + self->_value.tm_isdst = AS_INTEGER(AS_TUPLE(seq)->values.values[8]); + + return NONE_VAL(); +} + +KRK_Method(struct_time,tm_year) { return INTEGER_VAL(self->_value.tm_year + 1900); } /* struct tm is 1900-indexed, snakes are not */ +KRK_Method(struct_time,tm_mon) { return INTEGER_VAL(self->_value.tm_mon + 1); } /* struct tm is 0-indexed, snakes are not */ +KRK_Method(struct_time,tm_mday) { return INTEGER_VAL(self->_value.tm_mday); } +KRK_Method(struct_time,tm_hour) { return INTEGER_VAL(self->_value.tm_hour); } +KRK_Method(struct_time,tm_min) { return INTEGER_VAL(self->_value.tm_min); } +KRK_Method(struct_time,tm_sec) { return INTEGER_VAL(self->_value.tm_sec); } +KRK_Method(struct_time,tm_wday) { return INTEGER_VAL((self->_value.tm_wday+1)%7); } /* struct tm has Sunday = 0, but snakes use Monday = 0 */ +KRK_Method(struct_time,tm_yday) { return INTEGER_VAL(self->_value.tm_yday+1); } /* struct tm is from 0, but snakes start from 1 */ +KRK_Method(struct_time,tm_isdst) { return INTEGER_VAL(self->_value.tm_isdst); } + +KRK_Method(struct_time,__repr__) { + return krk_stringFromFormat( + "time.struct_time(tm_year=%d, tm_mon=%d, tm_mday=%d, tm_hour=%d, tm_min=%d, " + "tm_sec=%d, tm_wday=%d, tm_yday=%d, tm_isdst=%d)", + self->_value.tm_year + 1900, + self->_value.tm_mon + 1, + self->_value.tm_mday, + self->_value.tm_hour, + self->_value.tm_min, + self->_value.tm_sec, + (self->_value.tm_wday + 1) % 7, + self->_value.tm_yday + 1, + self->_value.tm_isdst); +} + +static time_t time_or_now(int has_arg, long long secs) { + if (!has_arg) { + struct timeval tv; + gettimeofday(&tv,NULL); + return (time_t)tv.tv_sec; + } else { + return (time_t)secs; + } +} + +static void tm_or_now(const struct struct_time_obj * t, struct tm * _time) { + if (t) { + memcpy(_time,&t->_value,sizeof(struct tm)); + } else { + struct timeval tv; + gettimeofday(&tv,NULL); + time_t time = tv.tv_sec; + localtime_r(&time,_time); + } +} + +KRK_Function(localtime) { + int gave_seconds; + long long seconds; + if (!krk_parseArgs("|L?",(const char*[]){"seconds"},&gave_seconds, &seconds)) return NONE_VAL(); + time_t time = time_or_now(gave_seconds, seconds); + + /* Create a struct_time to store result in */ + CURRENT_CTYPE out = (CURRENT_CTYPE)krk_newInstance(struct_time); + krk_push(OBJECT_VAL(out)); + + if (!localtime_r(&time, &out->_value)) return krk_runtimeError(vm.exceptions->valueError, "?"); + + return krk_pop(); +} + +static KrkValue krk_asctime(const struct tm *_time) { + /* asctime is normally locale-aware, but the snake function is not, so we do this manually */ + static const char * monNames[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"}; + static const char * dayNames[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; + + char buf[40] = {0}; + + /* The normal strftime string for this is %a %b %d %T %Y + * Day Mon DD HH:MM:SS YYYY */ + snprintf(buf,39, "%s %s%3d %.2d:%.2d:%.2d %d", + dayNames[_time->tm_wday % 7], + monNames[_time->tm_mon % 12], + _time->tm_mday, + _time->tm_hour, + _time->tm_min, + _time->tm_sec, + _time->tm_year + 1900); + + return OBJECT_VAL(krk_copyString(buf,strlen(buf))); +} + +KRK_Function(asctime) { + struct struct_time_obj * t = NULL; + if (!krk_parseArgs("|O!",(const char*[]){"t"},struct_time,&t)) return NONE_VAL(); + struct tm _time; + tm_or_now(t,&_time); + return krk_asctime(&_time); +} + +KRK_Function(ctime) { + int has_arg; + long long secs; + if (!krk_parseArgs("|L?",(const char*[]){"secs"},&has_arg,&secs)) return NONE_VAL(); + time_t time = time_or_now(has_arg, secs); + struct tm _time; + if (!localtime_r(&time, &_time)) return krk_runtimeError(vm.exceptions->valueError, "?"); + + return krk_asctime(&_time); +} + +KRK_Function(gmtime) { + int gave_seconds; + long long seconds; + if (!krk_parseArgs("|L?",(const char*[]){"secs"},&gave_seconds, &seconds)) return NONE_VAL(); + time_t time = time_or_now(gave_seconds, seconds); + + /* Create a struct_time to store result in */ + CURRENT_CTYPE out = (CURRENT_CTYPE)krk_newInstance(struct_time); + krk_push(OBJECT_VAL(out)); + + if (!gmtime_r(&time, &out->_value)) return krk_runtimeError(vm.exceptions->valueError, "?"); + + return krk_pop(); +} + +KRK_Function(mktime) { + struct struct_time_obj * t; + if (!krk_parseArgs("O!",(const char*[]){"t"},struct_time,&t)) return NONE_VAL(); + + struct tm _time; + memcpy(&_time,&t->_value,sizeof(struct tm)); + _time.tm_wday = -1; + time_t out = mktime(&_time); + if (out == -1 && _time.tm_wday == -1) return krk_runtimeError(vm.exceptions->valueError, "invalid argument to mktime"); + return FLOATING_VAL(out); +} + +KRK_Function(strftime) { + const char * format; + struct struct_time_obj * t = NULL; + if (!krk_parseArgs("s|O!",(const char*[]){"format","t"},&format,struct_time,&t)) return NONE_VAL(); + struct tm _time; + tm_or_now(t,&_time); + + /* strftime wants a buffer size, but we have no way of knowing. Following + * what CPython does, start from 1024 and try doubling until we reach + * the length of our format string * 256, and then give up. */ + size_t fmt_len = strlen(format); + size_t size = 1024; + while (1) { + char * buf = malloc(size); + size_t ret = strftime(buf,size,format,&_time); + if (ret || size > fmt_len * 256) { + krk_push(OBJECT_VAL(krk_copyString(buf,ret))); + free(buf); + return krk_pop(); + } + size *= 2; + free(buf); + } +} + KRK_Module(time) { KRK_DOC(module, "@brief Provides timekeeping functions."); KRK_DOC(BIND_FUNC(module,sleep), "@brief Pause execution of the current thread.\n" @@ -48,5 +234,43 @@ KRK_Module(time) { "Returns a @ref float representation of the number of seconds since the platform's epoch date. " "On POSIX platforms, this is the number of seconds since 1 January 1970. " "The precision of the return value is platform-dependent."); + + krk_makeClass(module, &struct_time, "struct_time", KRK_BASE_CLASS(object)); + struct_time->allocSize = sizeof(struct struct_time_obj); + KRK_DOC(struct_time, "Time value returned by various functions."); + KRK_DOC(BIND_METHOD(struct_time,__init__), "@arguments iterable: tuple\n\n" + "Create a @ref struct_time from a 9-tuple of @ref int values.\n" + "The format of @p iterable is `(tm_year,tm_mon,tm_mday,tm_hour,tm_min,tm_sec,tm_wday,tm_yday,tm_isdst)`."); + KRK_DOC(BIND_PROP(struct_time,tm_year), "Calendar year"); + KRK_DOC(BIND_PROP(struct_time,tm_mon), "Month, [1, 12]"); + KRK_DOC(BIND_PROP(struct_time,tm_mday), "Day of the month, [1, 31]"); + KRK_DOC(BIND_PROP(struct_time,tm_hour), "Clock hour, [0, 23]"); + KRK_DOC(BIND_PROP(struct_time,tm_min), "Clock minute, [0, 59]"); + KRK_DOC(BIND_PROP(struct_time,tm_sec), "Clock seconds, [0, 61] (maybe, due to leap seconds, depends on platform)"); + KRK_DOC(BIND_PROP(struct_time,tm_wday), "Day of week, [0, 6], 0 is Monday."); + KRK_DOC(BIND_PROP(struct_time,tm_yday), "Day of year [1, 366]"); + KRK_DOC(BIND_PROP(struct_time,tm_isdst), "0, 1, -1 for unknown"); + BIND_METHOD(struct_time,__repr__); + krk_finalizeClass(struct_time); + + KRK_DOC(BIND_FUNC(module,localtime), "@brief Convert seconds since epoch to local time.\n" + "@arguments seconds=time.time()\n\n" + "If @p seconds is not provided, the current @ref time is used."); + KRK_DOC(BIND_FUNC(module,asctime), "@brief Convert time to string.\n" + "@arguments t=time.localtime()\n\n" + "If @p t is not provided, the current @ref localtime is used."); + KRK_DOC(BIND_FUNC(module,ctime), "@brief Convert seconds since epoch to string.\n" + "@arguments secs=time.time()\n\n" + "If @p secs is not provided, the current @ref time is used."); + KRK_DOC(BIND_FUNC(module,gmtime), "@brief Convert seconds since epoch to UTC time.\n" + "@arguments secs=time.time()\n\n" + "If @p secs is not provided, the current @ref time is used."); + KRK_DOC(BIND_FUNC(module,mktime), "@brief Convert from local time to seconds since epoch.\n" + "@arguments t\n\n" + "For compatibility with @ref time a @ref float is returned."); + KRK_DOC(BIND_FUNC(module,strftime), "@brief Format time string with system function.\n" + "@arguments format,t=time.localtime()\n\n" + "Uses the system `strftime` C function to convert a @ref struct_time to a string.\n" + "If @p t is not provided, the current @ref localtime is used."); }