diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2019-07-24 15:01:59 -0300 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2019-07-24 15:01:59 -0300 |
commit | 0eb6aa4013051c8c0148c09d8c85ee7cbdc96f42 (patch) | |
tree | dcf79de5e0efc790142ed7e2fd418813c70d0c60 | |
parent | 7f5c31cdcac5388b3c48a26112dfb6d2cadb7321 (diff) | |
download | lua-0eb6aa4013051c8c0148c09d8c85ee7cbdc96f42.tar.gz lua-0eb6aa4013051c8c0148c09d8c85ee7cbdc96f42.tar.bz2 lua-0eb6aa4013051c8c0148c09d8c85ee7cbdc96f42.zip |
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.
-rw-r--r-- | loslib.c | 76 | ||||
-rw-r--r-- | testes/files.lua | 65 |
2 files changed, 100 insertions, 41 deletions
@@ -58,18 +58,20 @@ | |||
58 | ** =================================================================== | 58 | ** =================================================================== |
59 | */ | 59 | */ |
60 | 60 | ||
61 | #if !defined(l_time_t) /* { */ | ||
62 | /* | 61 | /* |
63 | ** type to represent time_t in Lua | 62 | ** type to represent time_t in Lua |
64 | */ | 63 | */ |
64 | #if !defined(LUA_NUMTIME) /* { */ | ||
65 | |||
65 | #define l_timet lua_Integer | 66 | #define l_timet lua_Integer |
66 | #define l_pushtime(L,t) lua_pushinteger(L,(lua_Integer)(t)) | 67 | #define l_pushtime(L,t) lua_pushinteger(L,(lua_Integer)(t)) |
68 | #define l_gettime(L,arg) luaL_checkinteger(L, arg) | ||
67 | 69 | ||
68 | static time_t l_checktime (lua_State *L, int arg) { | 70 | #else /* }{ */ |
69 | lua_Integer t = luaL_checkinteger(L, arg); | 71 | |
70 | luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds"); | 72 | #define l_timet lua_Number |
71 | return (time_t)t; | 73 | #define l_pushtime(L,t) lua_pushnumber(L,(lua_Number)(t)) |
72 | } | 74 | #define l_gettime(L,arg) luaL_checknumber(L, arg) |
73 | 75 | ||
74 | #endif /* } */ | 76 | #endif /* } */ |
75 | 77 | ||
@@ -193,11 +195,25 @@ static int os_clock (lua_State *L) { | |||
193 | ** ======================================================= | 195 | ** ======================================================= |
194 | */ | 196 | */ |
195 | 197 | ||
196 | static void setfield (lua_State *L, const char *key, int value) { | 198 | /* |
197 | lua_pushinteger(L, value); | 199 | ** About the overflow check: an overflow cannot occurr when time |
200 | ** is represented by a lua_Integer, because either lua_Integer is | ||
201 | ** large enough to represent all int fields or it is not large enough | ||
202 | ** to represent a time that cause a field to overflow. However, if | ||
203 | ** times are represented as doubles and lua_Integer is int, then the | ||
204 | ** time 0x1.e1853b0d184f6p+55 would cause an overflow when adding 1900 | ||
205 | ** to compute the year. | ||
206 | */ | ||
207 | static void setfield (lua_State *L, const char *key, int value, int delta) { | ||
208 | #if (defined(LUA_NUMTIME) && LUA_MAXINTEGER <= INT_MAX) | ||
209 | if (value > LUA_MAXINTEGER - delta) | ||
210 | luaL_error(L, "field '%s' is out-of-bound", key); | ||
211 | #endif | ||
212 | lua_pushinteger(L, (lua_Integer)value + delta); | ||
198 | lua_setfield(L, -2, key); | 213 | lua_setfield(L, -2, key); |
199 | } | 214 | } |
200 | 215 | ||
216 | |||
201 | static void setboolfield (lua_State *L, const char *key, int value) { | 217 | static void setboolfield (lua_State *L, const char *key, int value) { |
202 | if (value < 0) /* undefined? */ | 218 | if (value < 0) /* undefined? */ |
203 | return; /* does not set field */ | 219 | return; /* does not set field */ |
@@ -210,14 +226,14 @@ static void setboolfield (lua_State *L, const char *key, int value) { | |||
210 | ** Set all fields from structure 'tm' in the table on top of the stack | 226 | ** Set all fields from structure 'tm' in the table on top of the stack |
211 | */ | 227 | */ |
212 | static void setallfields (lua_State *L, struct tm *stm) { | 228 | static void setallfields (lua_State *L, struct tm *stm) { |
213 | setfield(L, "sec", stm->tm_sec); | 229 | setfield(L, "year", stm->tm_year, 1900); |
214 | setfield(L, "min", stm->tm_min); | 230 | setfield(L, "month", stm->tm_mon, 1); |
215 | setfield(L, "hour", stm->tm_hour); | 231 | setfield(L, "day", stm->tm_mday, 0); |
216 | setfield(L, "day", stm->tm_mday); | 232 | setfield(L, "hour", stm->tm_hour, 0); |
217 | setfield(L, "month", stm->tm_mon + 1); | 233 | setfield(L, "min", stm->tm_min, 0); |
218 | setfield(L, "year", stm->tm_year + 1900); | 234 | setfield(L, "sec", stm->tm_sec, 0); |
219 | setfield(L, "wday", stm->tm_wday + 1); | 235 | setfield(L, "yday", stm->tm_yday, 1); |
220 | setfield(L, "yday", stm->tm_yday + 1); | 236 | setfield(L, "wday", stm->tm_wday, 1); |
221 | setboolfield(L, "isdst", stm->tm_isdst); | 237 | setboolfield(L, "isdst", stm->tm_isdst); |
222 | } | 238 | } |
223 | 239 | ||
@@ -230,11 +246,6 @@ static int getboolfield (lua_State *L, const char *key) { | |||
230 | } | 246 | } |
231 | 247 | ||
232 | 248 | ||
233 | /* maximum value for date fields (to avoid arithmetic overflows with 'int') */ | ||
234 | #if !defined(L_MAXDATEFIELD) | ||
235 | #define L_MAXDATEFIELD (INT_MAX / 2) | ||
236 | #endif | ||
237 | |||
238 | static int getfield (lua_State *L, const char *key, int d, int delta) { | 249 | static int getfield (lua_State *L, const char *key, int d, int delta) { |
239 | int isnum; | 250 | int isnum; |
240 | int t = lua_getfield(L, -1, key); /* get field and its type */ | 251 | 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) { | |||
247 | res = d; | 258 | res = d; |
248 | } | 259 | } |
249 | else { | 260 | else { |
250 | if (!(-L_MAXDATEFIELD <= res && res <= L_MAXDATEFIELD)) | 261 | /* unsigned avoids overflow when lua_Integer has 32 bits */ |
262 | if (!(res >= 0 ? (lua_Unsigned)res <= (lua_Unsigned)INT_MAX + delta | ||
263 | : (lua_Integer)INT_MIN + delta <= res)) | ||
251 | return luaL_error(L, "field '%s' is out-of-bound", key); | 264 | return luaL_error(L, "field '%s' is out-of-bound", key); |
252 | res -= delta; | 265 | res -= delta; |
253 | } | 266 | } |
@@ -275,6 +288,13 @@ static const char *checkoption (lua_State *L, const char *conv, | |||
275 | } | 288 | } |
276 | 289 | ||
277 | 290 | ||
291 | static time_t l_checktime (lua_State *L, int arg) { | ||
292 | l_timet t = l_gettime(L, arg); | ||
293 | luaL_argcheck(L, (time_t)t == t, arg, "time out-of-bounds"); | ||
294 | return (time_t)t; | ||
295 | } | ||
296 | |||
297 | |||
278 | /* maximum size for an individual 'strftime' item */ | 298 | /* maximum size for an individual 'strftime' item */ |
279 | #define SIZETIMEFMT 250 | 299 | #define SIZETIMEFMT 250 |
280 | 300 | ||
@@ -293,7 +313,7 @@ static int os_date (lua_State *L) { | |||
293 | stm = l_localtime(&t, &tmr); | 313 | stm = l_localtime(&t, &tmr); |
294 | if (stm == NULL) /* invalid date? */ | 314 | if (stm == NULL) /* invalid date? */ |
295 | return luaL_error(L, | 315 | return luaL_error(L, |
296 | "time result cannot be represented in this installation"); | 316 | "date result cannot be represented in this installation"); |
297 | if (strcmp(s, "*t") == 0) { | 317 | if (strcmp(s, "*t") == 0) { |
298 | lua_createtable(L, 0, 9); /* 9 = number of fields */ | 318 | lua_createtable(L, 0, 9); /* 9 = number of fields */ |
299 | setallfields(L, stm); | 319 | setallfields(L, stm); |
@@ -329,12 +349,12 @@ static int os_time (lua_State *L) { | |||
329 | struct tm ts; | 349 | struct tm ts; |
330 | luaL_checktype(L, 1, LUA_TTABLE); | 350 | luaL_checktype(L, 1, LUA_TTABLE); |
331 | lua_settop(L, 1); /* make sure table is at the top */ | 351 | lua_settop(L, 1); /* make sure table is at the top */ |
332 | ts.tm_sec = getfield(L, "sec", 0, 0); | ||
333 | ts.tm_min = getfield(L, "min", 0, 0); | ||
334 | ts.tm_hour = getfield(L, "hour", 12, 0); | ||
335 | ts.tm_mday = getfield(L, "day", -1, 0); | ||
336 | ts.tm_mon = getfield(L, "month", -1, 1); | ||
337 | ts.tm_year = getfield(L, "year", -1, 1900); | 352 | ts.tm_year = getfield(L, "year", -1, 1900); |
353 | ts.tm_mon = getfield(L, "month", -1, 1); | ||
354 | ts.tm_mday = getfield(L, "day", -1, 0); | ||
355 | ts.tm_hour = getfield(L, "hour", 12, 0); | ||
356 | ts.tm_min = getfield(L, "min", 0, 0); | ||
357 | ts.tm_sec = getfield(L, "sec", 0, 0); | ||
338 | ts.tm_isdst = getboolfield(L, "isdst"); | 358 | ts.tm_isdst = getboolfield(L, "isdst"); |
339 | t = mktime(&ts); | 359 | t = mktime(&ts); |
340 | setallfields(L, &ts); /* update fields with normalized values */ | 360 | setallfields(L, &ts); /* update fields with normalized values */ |
diff --git a/testes/files.lua b/testes/files.lua index c8f23d18..6e7bd9e2 100644 --- a/testes/files.lua +++ b/testes/files.lua | |||
@@ -775,11 +775,24 @@ assert(os.date(string.rep("%d", 1000), t) == | |||
775 | string.rep(os.date("%d", t), 1000)) | 775 | string.rep(os.date("%d", t), 1000)) |
776 | assert(os.date(string.rep("%", 200)) == string.rep("%", 100)) | 776 | assert(os.date(string.rep("%", 200)) == string.rep("%", 100)) |
777 | 777 | ||
778 | local t = os.time() | 778 | local function checkDateTable (t) |
779 | D = os.date("*t", t) | 779 | _G.D = os.date("*t", t) |
780 | load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and | 780 | assert(os.time(D) == t) |
781 | D.hour==%H and D.min==%M and D.sec==%S and | 781 | load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and |
782 | D.wday==%w+1 and D.yday==%j)]], t))() | 782 | D.hour==%H and D.min==%M and D.sec==%S and |
783 | D.wday==%w+1 and D.yday==%j)]], t))() | ||
784 | _G.D = nil | ||
785 | end | ||
786 | |||
787 | checkDateTable(os.time()) | ||
788 | if not _port then | ||
789 | -- assume that time_t can represent these values | ||
790 | checkDateTable(0) | ||
791 | checkDateTable(1) | ||
792 | checkDateTable(1000) | ||
793 | checkDateTable(0x7fffffff) | ||
794 | checkDateTable(0x80000000) | ||
795 | end | ||
783 | 796 | ||
784 | checkerr("invalid conversion specifier", os.date, "%") | 797 | checkerr("invalid conversion specifier", os.date, "%") |
785 | checkerr("invalid conversion specifier", os.date, "%9") | 798 | 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}) | |||
793 | 806 | ||
794 | checkerr("missing", os.time, {hour = 12}) -- missing date | 807 | checkerr("missing", os.time, {hour = 12}) -- missing date |
795 | 808 | ||
809 | |||
810 | if string.packsize("i") == 4 then -- 4-byte ints | ||
811 | checkerr("field 'year' is out-of-bound", os.time, | ||
812 | {year = -(1 << 31) + 1899, month = 1, day = 1}) | ||
813 | end | ||
814 | |||
796 | if not _port then | 815 | if not _port then |
797 | -- test Posix-specific modifiers | 816 | -- test Posix-specific modifiers |
798 | assert(type(os.date("%Ex")) == 'string') | 817 | assert(type(os.date("%Ex")) == 'string') |
799 | assert(type(os.date("%Oy")) == 'string') | 818 | assert(type(os.date("%Oy")) == 'string') |
800 | 819 | ||
820 | -- test large dates (assume at least 4-byte ints and time_t) | ||
821 | local t0 = os.time{year = 1970, month = 1, day = 0} | ||
822 | local t1 = os.time{year = 1970, month = 1, day = 0, sec = (1 << 31) - 1} | ||
823 | assert(t1 - t0 == (1 << 31) - 1) | ||
824 | t0 = os.time{year = 1970, month = 1, day = 1} | ||
825 | t1 = os.time{year = 1970, month = 1, day = 1, sec = -(1 << 31)} | ||
826 | assert(t1 - t0 == -(1 << 31)) | ||
801 | 827 | ||
802 | -- test out-of-range dates (at least for Unix) | 828 | -- test out-of-range dates (at least for Unix) |
803 | if maxint >= 2^62 then -- cannot do these tests in Small Lua | 829 | if maxint >= 2^62 then -- cannot do these tests in Small Lua |
@@ -812,25 +838,37 @@ if not _port then | |||
812 | -- time_t has 8 bytes; an int year cannot represent a huge time | 838 | -- time_t has 8 bytes; an int year cannot represent a huge time |
813 | print(" 8-byte time_t") | 839 | print(" 8-byte time_t") |
814 | checkerr("cannot be represented", os.date, "%Y", 2^60) | 840 | checkerr("cannot be represented", os.date, "%Y", 2^60) |
815 | -- it should have no problems with year 4000 | 841 | |
816 | assert(tonumber(os.time{year=4000, month=1, day=1})) | 842 | -- this is the maximum year |
843 | assert(tonumber(os.time | ||
844 | {year=(1 << 31) + 1899, month=12, day=31, hour=23, min=59, sec=59})) | ||
845 | |||
846 | -- this is too much | ||
847 | checkerr("represented", os.time, | ||
848 | {year=(1 << 31) + 1899, month=12, day=31, hour=23, min=59, sec=60}) | ||
817 | end | 849 | end |
850 | |||
851 | -- internal 'int' fields cannot hold these values | ||
852 | checkerr("field 'day' is out-of-bound", os.time, | ||
853 | {year = 0, month = 1, day = 2^32}) | ||
854 | |||
855 | checkerr("field 'month' is out-of-bound", os.time, | ||
856 | {year = 0, month = -((1 << 31) + 1), day = 1}) | ||
857 | |||
858 | checkerr("field 'year' is out-of-bound", os.time, | ||
859 | {year = (1 << 31) + 1900, month = 1, day = 1}) | ||
860 | |||
818 | else -- 8-byte ints | 861 | else -- 8-byte ints |
819 | -- assume time_t has 8 bytes too | 862 | -- assume time_t has 8 bytes too |
820 | print(" 8-byte time_t") | 863 | print(" 8-byte time_t") |
821 | assert(tonumber(os.date("%Y", 2^60))) | 864 | assert(tonumber(os.date("%Y", 2^60))) |
865 | |||
822 | -- but still cannot represent a huge year | 866 | -- but still cannot represent a huge year |
823 | checkerr("cannot be represented", os.time, {year=2^60, month=1, day=1}) | 867 | checkerr("cannot be represented", os.time, {year=2^60, month=1, day=1}) |
824 | end | 868 | end |
825 | end | 869 | end |
826 | end | 870 | end |
827 | 871 | ||
828 | |||
829 | D = os.date("!*t", t) | ||
830 | load(os.date([[!assert(D.year==%Y and D.month==%m and D.day==%d and | ||
831 | D.hour==%H and D.min==%M and D.sec==%S and | ||
832 | D.wday==%w+1 and D.yday==%j)]], t))() | ||
833 | |||
834 | do | 872 | do |
835 | local D = os.date("*t") | 873 | local D = os.date("*t") |
836 | local t = os.time(D) | 874 | local t = os.time(D) |
@@ -844,6 +882,7 @@ do | |||
844 | assert(t == t1) -- if isdst is absent uses correct default | 882 | assert(t == t1) -- if isdst is absent uses correct default |
845 | end | 883 | end |
846 | 884 | ||
885 | local D = os.date("*t") | ||
847 | t = os.time(D) | 886 | t = os.time(D) |
848 | D.year = D.year-1; | 887 | D.year = D.year-1; |
849 | local t1 = os.time(D) | 888 | local t1 = os.time(D) |