浏览代码

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.
Roberto Ierusalimschy 6 年之前
父节点
当前提交
0eb6aa4013
共有 2 个文件被更改,包括 100 次插入41 次删除
  1. 48 28
      loslib.c
  2. 52 13
      testes/files.lua

+ 48 - 28
loslib.c

@@ -58,18 +58,20 @@
 ** ===================================================================
 ** ===================================================================
 */
 */
 
 
-#if !defined(l_time_t)		/* { */
 /*
 /*
 ** type to represent time_t in Lua
 ** type to represent time_t in Lua
 */
 */
+#if !defined(LUA_NUMTIME)	/* { */
+
 #define l_timet			lua_Integer
 #define l_timet			lua_Integer
 #define l_pushtime(L,t)		lua_pushinteger(L,(lua_Integer)(t))
 #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				/* } */
 #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);
   lua_setfield(L, -2, key);
 }
 }
 
 
+
 static void setboolfield (lua_State *L, const char *key, int value) {
 static void setboolfield (lua_State *L, const char *key, int value) {
   if (value < 0)  /* undefined? */
   if (value < 0)  /* undefined? */
     return;  /* does not set field */
     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
 ** Set all fields from structure 'tm' in the table on top of the stack
 */
 */
 static void setallfields (lua_State *L, struct tm *stm) {
 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);
   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) {
 static int getfield (lua_State *L, const char *key, int d, int delta) {
   int isnum;
   int isnum;
   int t = lua_getfield(L, -1, key);  /* get field and its type */
   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;
     res = d;
   }
   }
   else {
   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);
       return luaL_error(L, "field '%s' is out-of-bound", key);
     res -= delta;
     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 */
 /* maximum size for an individual 'strftime' item */
 #define SIZETIMEFMT	250
 #define SIZETIMEFMT	250
 
 
@@ -293,7 +313,7 @@ static int os_date (lua_State *L) {
     stm = l_localtime(&t, &tmr);
     stm = l_localtime(&t, &tmr);
   if (stm == NULL)  /* invalid date? */
   if (stm == NULL)  /* invalid date? */
     return luaL_error(L,
     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) {
   if (strcmp(s, "*t") == 0) {
     lua_createtable(L, 0, 9);  /* 9 = number of fields */
     lua_createtable(L, 0, 9);  /* 9 = number of fields */
     setallfields(L, stm);
     setallfields(L, stm);
@@ -329,12 +349,12 @@ static int os_time (lua_State *L) {
     struct tm ts;
     struct tm ts;
     luaL_checktype(L, 1, LUA_TTABLE);
     luaL_checktype(L, 1, LUA_TTABLE);
     lua_settop(L, 1);  /* make sure table is at the top */
     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_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");
     ts.tm_isdst = getboolfield(L, "isdst");
     t = mktime(&ts);
     t = mktime(&ts);
     setallfields(L, &ts);  /* update fields with normalized values */
     setallfields(L, &ts);  /* update fields with normalized values */

+ 52 - 13
testes/files.lua

@@ -775,11 +775,24 @@ assert(os.date(string.rep("%d", 1000), t) ==
        string.rep(os.date("%d", t), 1000))
        string.rep(os.date("%d", t), 1000))
 assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
 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, "%")
 checkerr("invalid conversion specifier", os.date, "%9")
 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
 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
 if not _port then
   -- test Posix-specific modifiers
   -- test Posix-specific modifiers
   assert(type(os.date("%Ex")) == 'string')
   assert(type(os.date("%Ex")) == 'string')
   assert(type(os.date("%Oy")) == '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)
   -- test out-of-range dates (at least for Unix)
   if maxint >= 2^62 then  -- cannot do these tests in Small Lua
   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
         -- time_t has 8 bytes; an int year cannot represent a huge time
         print("  8-byte time_t")
         print("  8-byte time_t")
         checkerr("cannot be represented", os.date, "%Y", 2^60)
         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
       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
     else    -- 8-byte ints
       -- assume time_t has 8 bytes too
       -- assume time_t has 8 bytes too
       print("  8-byte time_t")
       print("  8-byte time_t")
       assert(tonumber(os.date("%Y", 2^60)))
       assert(tonumber(os.date("%Y", 2^60)))
+
       -- but still cannot represent a huge year
       -- but still cannot represent a huge year
       checkerr("cannot be represented", os.time, {year=2^60, month=1, day=1})
       checkerr("cannot be represented", os.time, {year=2^60, month=1, day=1})
     end
     end
   end
   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
 do
   local D = os.date("*t")
   local D = os.date("*t")
   local t = os.time(D)
   local t = os.time(D)
@@ -844,6 +882,7 @@ do
   assert(t == t1)   -- if isdst is absent uses correct default
   assert(t == t1)   -- if isdst is absent uses correct default
 end
 end
 
 
+local D = os.date("*t")
 t = os.time(D)
 t = os.time(D)
 D.year = D.year-1;
 D.year = D.year-1;
 local t1 = os.time(D)
 local t1 = os.time(D)