mirror of
https://github.com/lua/lua
synced 2024-11-22 12:51:30 +03:00
Some improvements in date/time functions
- Range in date table extended to full 32 bits. - Easier support for times represented as floats. - Added more tests.
This commit is contained in:
parent
7f5c31cdca
commit
0eb6aa4013
76
loslib.c
76
loslib.c
@ -58,18 +58,20 @@
|
||||
** ===================================================================
|
||||
*/
|
||||
|
||||
#if !defined(l_time_t) /* { */
|
||||
/*
|
||||
** type to represent time_t in Lua
|
||||
*/
|
||||
#if !defined(LUA_NUMTIME) /* { */
|
||||
|
||||
#define l_timet lua_Integer
|
||||
#define l_pushtime(L,t) lua_pushinteger(L,(lua_Integer)(t))
|
||||
#define l_gettime(L,arg) luaL_checkinteger(L, arg)
|
||||
|
||||
static time_t l_checktime (lua_State *L, int arg) {
|
||||
lua_Integer t = luaL_checkinteger(L, arg);
|
||||
luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds");
|
||||
return (time_t)t;
|
||||
}
|
||||
#else /* }{ */
|
||||
|
||||
#define l_timet lua_Number
|
||||
#define l_pushtime(L,t) lua_pushnumber(L,(lua_Number)(t))
|
||||
#define l_gettime(L,arg) luaL_checknumber(L, arg)
|
||||
|
||||
#endif /* } */
|
||||
|
||||
@ -193,11 +195,25 @@ static int os_clock (lua_State *L) {
|
||||
** =======================================================
|
||||
*/
|
||||
|
||||
static void setfield (lua_State *L, const char *key, int value) {
|
||||
lua_pushinteger(L, value);
|
||||
/*
|
||||
** About the overflow check: an overflow cannot occurr when time
|
||||
** is represented by a lua_Integer, because either lua_Integer is
|
||||
** large enough to represent all int fields or it is not large enough
|
||||
** to represent a time that cause a field to overflow. However, if
|
||||
** times are represented as doubles and lua_Integer is int, then the
|
||||
** time 0x1.e1853b0d184f6p+55 would cause an overflow when adding 1900
|
||||
** to compute the year.
|
||||
*/
|
||||
static void setfield (lua_State *L, const char *key, int value, int delta) {
|
||||
#if (defined(LUA_NUMTIME) && LUA_MAXINTEGER <= INT_MAX)
|
||||
if (value > LUA_MAXINTEGER - delta)
|
||||
luaL_error(L, "field '%s' is out-of-bound", key);
|
||||
#endif
|
||||
lua_pushinteger(L, (lua_Integer)value + delta);
|
||||
lua_setfield(L, -2, key);
|
||||
}
|
||||
|
||||
|
||||
static void setboolfield (lua_State *L, const char *key, int value) {
|
||||
if (value < 0) /* undefined? */
|
||||
return; /* does not set field */
|
||||
@ -210,14 +226,14 @@ static void setboolfield (lua_State *L, const char *key, int value) {
|
||||
** Set all fields from structure 'tm' in the table on top of the stack
|
||||
*/
|
||||
static void setallfields (lua_State *L, struct tm *stm) {
|
||||
setfield(L, "sec", stm->tm_sec);
|
||||
setfield(L, "min", stm->tm_min);
|
||||
setfield(L, "hour", stm->tm_hour);
|
||||
setfield(L, "day", stm->tm_mday);
|
||||
setfield(L, "month", stm->tm_mon + 1);
|
||||
setfield(L, "year", stm->tm_year + 1900);
|
||||
setfield(L, "wday", stm->tm_wday + 1);
|
||||
setfield(L, "yday", stm->tm_yday + 1);
|
||||
setfield(L, "year", stm->tm_year, 1900);
|
||||
setfield(L, "month", stm->tm_mon, 1);
|
||||
setfield(L, "day", stm->tm_mday, 0);
|
||||
setfield(L, "hour", stm->tm_hour, 0);
|
||||
setfield(L, "min", stm->tm_min, 0);
|
||||
setfield(L, "sec", stm->tm_sec, 0);
|
||||
setfield(L, "yday", stm->tm_yday, 1);
|
||||
setfield(L, "wday", stm->tm_wday, 1);
|
||||
setboolfield(L, "isdst", stm->tm_isdst);
|
||||
}
|
||||
|
||||
@ -230,11 +246,6 @@ static int getboolfield (lua_State *L, const char *key) {
|
||||
}
|
||||
|
||||
|
||||
/* maximum value for date fields (to avoid arithmetic overflows with 'int') */
|
||||
#if !defined(L_MAXDATEFIELD)
|
||||
#define L_MAXDATEFIELD (INT_MAX / 2)
|
||||
#endif
|
||||
|
||||
static int getfield (lua_State *L, const char *key, int d, int delta) {
|
||||
int isnum;
|
||||
int t = lua_getfield(L, -1, key); /* get field and its type */
|
||||
@ -247,7 +258,9 @@ static int getfield (lua_State *L, const char *key, int d, int delta) {
|
||||
res = d;
|
||||
}
|
||||
else {
|
||||
if (!(-L_MAXDATEFIELD <= res && res <= L_MAXDATEFIELD))
|
||||
/* unsigned avoids overflow when lua_Integer has 32 bits */
|
||||
if (!(res >= 0 ? (lua_Unsigned)res <= (lua_Unsigned)INT_MAX + delta
|
||||
: (lua_Integer)INT_MIN + delta <= res))
|
||||
return luaL_error(L, "field '%s' is out-of-bound", key);
|
||||
res -= delta;
|
||||
}
|
||||
@ -275,6 +288,13 @@ static const char *checkoption (lua_State *L, const char *conv,
|
||||
}
|
||||
|
||||
|
||||
static time_t l_checktime (lua_State *L, int arg) {
|
||||
l_timet t = l_gettime(L, arg);
|
||||
luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds");
|
||||
return (time_t)t;
|
||||
}
|
||||
|
||||
|
||||
/* maximum size for an individual 'strftime' item */
|
||||
#define SIZETIMEFMT 250
|
||||
|
||||
@ -293,7 +313,7 @@ static int os_date (lua_State *L) {
|
||||
stm = l_localtime(&t, &tmr);
|
||||
if (stm == NULL) /* invalid date? */
|
||||
return luaL_error(L,
|
||||
"time result cannot be represented in this installation");
|
||||
"date result cannot be represented in this installation");
|
||||
if (strcmp(s, "*t") == 0) {
|
||||
lua_createtable(L, 0, 9); /* 9 = number of fields */
|
||||
setallfields(L, stm);
|
||||
@ -329,12 +349,12 @@ static int os_time (lua_State *L) {
|
||||
struct tm ts;
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
lua_settop(L, 1); /* make sure table is at the top */
|
||||
ts.tm_sec = getfield(L, "sec", 0, 0);
|
||||
ts.tm_min = getfield(L, "min", 0, 0);
|
||||
ts.tm_hour = getfield(L, "hour", 12, 0);
|
||||
ts.tm_mday = getfield(L, "day", -1, 0);
|
||||
ts.tm_mon = getfield(L, "month", -1, 1);
|
||||
ts.tm_year = getfield(L, "year", -1, 1900);
|
||||
ts.tm_mon = getfield(L, "month", -1, 1);
|
||||
ts.tm_mday = getfield(L, "day", -1, 0);
|
||||
ts.tm_hour = getfield(L, "hour", 12, 0);
|
||||
ts.tm_min = getfield(L, "min", 0, 0);
|
||||
ts.tm_sec = getfield(L, "sec", 0, 0);
|
||||
ts.tm_isdst = getboolfield(L, "isdst");
|
||||
t = mktime(&ts);
|
||||
setallfields(L, &ts); /* update fields with normalized values */
|
||||
|
@ -775,11 +775,24 @@ assert(os.date(string.rep("%d", 1000), t) ==
|
||||
string.rep(os.date("%d", t), 1000))
|
||||
assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
|
||||
|
||||
local t = os.time()
|
||||
D = os.date("*t", t)
|
||||
load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
|
||||
D.hour==%H and D.min==%M and D.sec==%S and
|
||||
D.wday==%w+1 and D.yday==%j)]], t))()
|
||||
local function checkDateTable (t)
|
||||
_G.D = os.date("*t", t)
|
||||
assert(os.time(D) == t)
|
||||
load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
|
||||
D.hour==%H and D.min==%M and D.sec==%S and
|
||||
D.wday==%w+1 and D.yday==%j)]], t))()
|
||||
_G.D = nil
|
||||
end
|
||||
|
||||
checkDateTable(os.time())
|
||||
if not _port then
|
||||
-- assume that time_t can represent these values
|
||||
checkDateTable(0)
|
||||
checkDateTable(1)
|
||||
checkDateTable(1000)
|
||||
checkDateTable(0x7fffffff)
|
||||
checkDateTable(0x80000000)
|
||||
end
|
||||
|
||||
checkerr("invalid conversion specifier", os.date, "%")
|
||||
checkerr("invalid conversion specifier", os.date, "%9")
|
||||
@ -793,11 +806,24 @@ checkerr("not an integer", os.time, {year=1000, month=1, day=1, hour=1.5})
|
||||
|
||||
checkerr("missing", os.time, {hour = 12}) -- missing date
|
||||
|
||||
|
||||
if string.packsize("i") == 4 then -- 4-byte ints
|
||||
checkerr("field 'year' is out-of-bound", os.time,
|
||||
{year = -(1 << 31) + 1899, month = 1, day = 1})
|
||||
end
|
||||
|
||||
if not _port then
|
||||
-- test Posix-specific modifiers
|
||||
assert(type(os.date("%Ex")) == 'string')
|
||||
assert(type(os.date("%Oy")) == 'string')
|
||||
|
||||
-- test large dates (assume at least 4-byte ints and time_t)
|
||||
local t0 = os.time{year = 1970, month = 1, day = 0}
|
||||
local t1 = os.time{year = 1970, month = 1, day = 0, sec = (1 << 31) - 1}
|
||||
assert(t1 - t0 == (1 << 31) - 1)
|
||||
t0 = os.time{year = 1970, month = 1, day = 1}
|
||||
t1 = os.time{year = 1970, month = 1, day = 1, sec = -(1 << 31)}
|
||||
assert(t1 - t0 == -(1 << 31))
|
||||
|
||||
-- test out-of-range dates (at least for Unix)
|
||||
if maxint >= 2^62 then -- cannot do these tests in Small Lua
|
||||
@ -812,25 +838,37 @@ if not _port then
|
||||
-- time_t has 8 bytes; an int year cannot represent a huge time
|
||||
print(" 8-byte time_t")
|
||||
checkerr("cannot be represented", os.date, "%Y", 2^60)
|
||||
-- it should have no problems with year 4000
|
||||
assert(tonumber(os.time{year=4000, month=1, day=1}))
|
||||
|
||||
-- this is the maximum year
|
||||
assert(tonumber(os.time
|
||||
{year=(1 << 31) + 1899, month=12, day=31, hour=23, min=59, sec=59}))
|
||||
|
||||
-- this is too much
|
||||
checkerr("represented", os.time,
|
||||
{year=(1 << 31) + 1899, month=12, day=31, hour=23, min=59, sec=60})
|
||||
end
|
||||
|
||||
-- internal 'int' fields cannot hold these values
|
||||
checkerr("field 'day' is out-of-bound", os.time,
|
||||
{year = 0, month = 1, day = 2^32})
|
||||
|
||||
checkerr("field 'month' is out-of-bound", os.time,
|
||||
{year = 0, month = -((1 << 31) + 1), day = 1})
|
||||
|
||||
checkerr("field 'year' is out-of-bound", os.time,
|
||||
{year = (1 << 31) + 1900, month = 1, day = 1})
|
||||
|
||||
else -- 8-byte ints
|
||||
-- assume time_t has 8 bytes too
|
||||
print(" 8-byte time_t")
|
||||
assert(tonumber(os.date("%Y", 2^60)))
|
||||
|
||||
-- but still cannot represent a huge year
|
||||
checkerr("cannot be represented", os.time, {year=2^60, month=1, day=1})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
D = os.date("!*t", t)
|
||||
load(os.date([[!assert(D.year==%Y and D.month==%m and D.day==%d and
|
||||
D.hour==%H and D.min==%M and D.sec==%S and
|
||||
D.wday==%w+1 and D.yday==%j)]], t))()
|
||||
|
||||
do
|
||||
local D = os.date("*t")
|
||||
local t = os.time(D)
|
||||
@ -844,6 +882,7 @@ do
|
||||
assert(t == t1) -- if isdst is absent uses correct default
|
||||
end
|
||||
|
||||
local D = os.date("*t")
|
||||
t = os.time(D)
|
||||
D.year = D.year-1;
|
||||
local t1 = os.time(D)
|
||||
|
Loading…
Reference in New Issue
Block a user