--- source: src/main.rs expression: compiled input_file: test-data/lua5.4-tests/files.lua --- local debug = require("debug") local maxint = math.maxinteger assert(type(os.getenv("PATH")) == "string"); assert(io.input(io.stdin) == io.stdin); assert(!pcall(io.input, "non-existent-file")); assert(io.output(io.stdout) == io.stdout); local fn testerr(msg, f, ...) { local stat, err = pcall(f, ...) return (!stat && string.find(err, msg, 1, true)) } local fn checkerr(msg, f, ...) { assert(testerr(msg, f, ...)); } assert(!io.close(io.stdin) && !io.stdout::close() && !io.stderr::close()); checkerr("got no value", io.stdin.close); assert(type(io.input()) == "userdata" && io.type(io.output()) == "file"); assert(type(io.stdin) == "userdata" && io.type(io.stderr) == "file"); assert(!io.type(8)); local a = {} setmetatable(a, {}); assert(!io.type(a)); assert(getmetatable(io.input()).__name == "FILE*"); local a, b, c = io.open('xuxu_nao_existe') assert(!a && type(b) == "string" && type(c) == "number"); a, b, c = io.open('/a/b/c/d', 'w') assert(!a && type(b) == "string" && type(c) == "number"); local file = os.tmpname() local f, msg = io.open(file, "w") if !f { (Message || print)("'os.tmpname' file cannot be open; skipping file tests"); } else { f::close(); print('testing i/o'); local otherfile = os.tmpname() checkerr("invalid mode", io.open, file, "rw"); checkerr("invalid mode", io.open, file, "rb+"); checkerr("invalid mode", io.open, file, "r+bk"); checkerr("invalid mode", io.open, file, ""); checkerr("invalid mode", io.open, file, "+"); checkerr("invalid mode", io.open, file, "b"); assert(io.open(file, "r+b"))::close(); assert(io.open(file, "r+"))::close(); assert(io.open(file, "rb"))::close(); assert(os.setlocale('C', 'all')); io.input(io.stdin); io.output(io.stdout); os.remove(file); assert(!loadfile(file)); checkerr("", dofile, file); assert(!io.open(file)); io.output(file); assert(io.output() != io.stdout); if !_port { local status, msg, code = io.stdin::seek("set", 1000) assert(!status && type(msg) == "string" && type(code) == "number"); } assert(io.output()::seek() == 0); assert(io.write("alo alo")::seek() == string.len("alo alo")); assert(io.output()::seek("cur", -3) == string.len("alo alo") - 3); assert(io.write("joao")); assert(io.output()::seek("end") == string.len("alo joao")); assert(io.output()::seek("set") == 0); assert(io.write('"álo"', "{a}\n", "second line\n", "third line \n")); assert(io.write('çfourth_line')); io.output(io.stdout); collectgarbage(); assert(io.input() == io.stdin && rawequal(io.output(), io.stdout)); print('+'); collectgarbage(); for i = 1, 120 { for i = 1, 5 { io.input(file); assert(io.open(file, 'r')); io.lines(file); } collectgarbage(); } io.input()::close(); io.close(); assert(os.rename(file, otherfile)); assert(!os.rename(file, otherfile)); io.output(io.open(otherfile, "ab")); assert(io.write("\n\n\t\t ", 3450, "\n")); io.close(); { local F = nil { local f = assert(io.open(file, "w")) F = f getmetatable(f).__close(f); } assert(tostring(F) == "file (closed)"); } assert(os.remove(file)); { local f = assert(io.open(file, "w")) f::write(maxint, '\n'); f::write(string.format("0X%x\n", maxint)); f::write("0xABCp-3", '\n'); f::write(0, '\n'); f::write(-maxint, '\n'); f::write(string.format("0x%X\n", -maxint)); f::write("-0xABCp-3", '\n'); assert(f::close()); local f = assert(io.open(file, "r")) assert(f::read("n") == maxint); assert(f::read("n") == maxint); assert(f::read("n") == 343.5); assert(f::read("n") == 0); assert(f::read("*n") == -maxint); assert(f::read("n") == -maxint); assert(f::read("*n") == -343.5); getmetatable(f).__close(f); getmetatable(f).__close(f); } assert(os.remove(file)); { local f = assert(io.open(file, "w")) f::write([[ a line another line 1234 3.45 one two three ]]); local l1, l2, l3, l4, n1, n2, c, dummy assert(f::close()); local f = assert(io.open(file, "r")) l1, l2, n1, n2, dummy = f::read("l", "L", "n", "n") assert(l1 == "a line" && l2 == "another line\n" && n1 == 1234 && n2 == 3.45 && dummy == nil); assert(f::close()); local f = assert(io.open(file, "r")) l1, l2, n1, n2, c, l3, l4, dummy = f::read(7, "l", "n", "n", 1, "l", "l") assert(l1 == "a line\n" && l2 == "another line" && c == '\n' && n1 == 1234 && n2 == 3.45 && l3 == "one" && l4 == "two" && dummy == nil); assert(f::close()); local f = assert(io.open(file, "r")) l1, n1, n2, dummy = f::read("l", "n", "n", "l") assert(l1 == "a line" && !n1); getmetatable(f).__close(f); getmetatable(f).__close(f); getmetatable(f).__close(f); getmetatable(f).__close(f); } assert(os.remove(file)); f = assert(io.open(file, "w")) f::write([[ local x, z = coroutine.yield(10) local y = coroutine.yield(20) return x + y * z ]]); assert(f::close()); f = coroutine.wrap(dofile) assert(f(file) == 10); assert(f(100, 101) == 20); assert(f(200) == 100 + 200 * 101); assert(os.remove(file)); f = assert(io.open(file, "w")) f::write([[ -12.3- -0xffff+ .3|5.E-3X +234e+13E 0xDEADBEEFDEADBEEFx 0x1.13Ap+3e ]]); f::write("1234"); for i = 1, 1000 { f::write("0"); } f::write("\n"); f::write([[ .e+ 0.e; --; 0xX; ]]); assert(f::close()); f = assert(io.open(file, "r")) assert(f::read("n") == -12.3); assert(f::read(1) == "-"); assert(f::read("n") == -0xffff); assert(f::read(2) == "+ "); assert(f::read("n") == 0.3); assert(f::read(1) == "|"); assert(f::read("n") == 5e-3); assert(f::read(1) == "X"); assert(f::read("n") == 234e13); assert(f::read(1) == "E"); assert(f::read("n") == 0Xdeadbeefdeadbeef); assert(f::read(2) == "x\n"); assert(f::read("n") == 8.61328125); assert(f::read(1) == "e"); { assert(!f::read("n")); local s = f::read("L") assert(string.find(s, "^00*\n$")); } assert(!f::read("n")); assert(f::read(2) == "e+"); assert(!f::read("n")); assert(f::read(1) == ";"); assert(!f::read("n")); assert(f::read(2) == "-;"); assert(!f::read("n")); assert(f::read(1) == "X"); assert(!f::read("n")); assert(f::read(1) == ";"); assert(!f::read("n")); assert(!f::read(0)); assert(f::close()); assert(os.remove(file)); assert(!pcall(io.lines, "non-existent-file")); assert(os.rename(otherfile, file)); io.output(otherfile); local n = 0 local f = io.lines(file) while f() { n = n + 1 } assert(n == 6); checkerr("file is already closed", f); checkerr("file is already closed", f); n = 0 for l with io.lines(file) { io.write(l, "\n"); n = n + 1 } io.close(); assert(n == 6); local f = assert(io.open(otherfile)) assert(io.type(f) == "file"); io.output(file); assert(!io.output()::read()); n = 0 for l with f::lines() { io.write(l, "\n"); n = n + 1 } assert(tostring(f)::sub(1, 5) == "file "); assert(f::close()); io.close(); assert(n == 6); checkerr("closed file", io.close, f); assert(tostring(f) == "file (closed)"); assert(io.type(f) == "closed file"); io.input(file); f = io.open(otherfile)::lines() n = 0 for l with io.lines() { assert(l == f()); n = n + 1 } f = nil collectgarbage(); assert(n == 6); assert(os.remove(otherfile)); { io.output(otherfile); io.write(string.rep("a", 300), "\n"); io.close(); local t = {} for i = 1, 250 { t[(i)] = 1 } t = { io.lines(otherfile, table.unpack(t))() } assert(#t == 250 && t[(1)] == 'a' && t[(#t)] == 'a'); t[(#t + 1)] = 1 checkerr("too many arguments", io.lines, otherfile, table.unpack(t)); collectgarbage(); assert(os.remove(otherfile)); } io.input(file); { local a, b, c = io.input()::write("xuxu") assert(!a && type(b) == "string" && type(c) == "number"); } checkerr("invalid format", io.read, "x"); assert(io.read(0) == ""); assert(io.read(5, 'l') == '"álo"'); assert(io.read(0) == ""); assert(io.read() == "second line"); local x = io.input()::seek() assert(io.read() == "third line "); assert(io.input()::seek("set", x)); assert(io.read('L') == "third line \n"); assert(io.read(1) == "ç"); assert(io.read(string.len("fourth_line")) == "fourth_line"); assert(io.input()::seek("cur", -string.len("fourth_line"))); assert(io.read() == "fourth_line"); assert(io.read() == ""); assert(io.read('n') == 3450); assert(io.read(1) == '\n'); assert(!io.read(0)); assert(!io.read(1)); assert(!io.read(30000)); assert(({ io.read(1) })[(2)] == undef); assert(!io.read()); assert(({ io.read() })[(2)] == undef); assert(!io.read('n')); assert(({ io.read('n') })[(2)] == undef); assert(io.read('a') == ''); assert(io.read('a') == ''); collectgarbage(); print('+'); io.close(io.input()); checkerr(" input file is closed", io.read); assert(os.remove(file)); local t = '0123456789' for i = 1, 10 { t = t .. t } assert(string.len(t) == 10 * 2 ^ 10); io.output(file); io.write("alo")::write("\n"); io.close(); checkerr(" output file is closed", io.write); local f = io.open(file, "a+b") io.output(f); collectgarbage(); assert(io.write(' ' .. t .. ' ')); assert(io.write(';', 'end of file\n')); f::flush(); io.flush(); f::close(); print('+'); io.input(file); assert(io.read() == "alo"); assert(io.read(1) == ' '); assert(io.read(string.len(t)) == t); assert(io.read(1) == ' '); assert(io.read(0)); assert(io.read('a') == ';end of file\n'); assert(!io.read(0)); assert(io.close(io.input())); { local fn ismsg(m) { return (type(m) == "string" && !tonumber(m)) } local f = io.open(file, "w") local r, m, c = f::read() assert(!r && ismsg(m) && type(c) == "number"); assert(f::close()); f = io.open(file, "r") r, m, c = f::write("whatever") assert(!r && ismsg(m) && type(c) == "number"); assert(f::close()); f = io.open(file, "w") r, m = pcall(f::lines()) assert(r == false && ismsg(m)); assert(f::close()); } assert(os.remove(file)); io.output(file); io.write("\n\nline\nother")::close(); io.input(file); assert(io.read("L") == "\n"); assert(io.read("L") == "\n"); assert(io.read("L") == "line\n"); assert(io.read("L") == "other"); assert(!io.read("L")); io.input()::close(); local f = assert(io.open(file)) local s = "" for l with f::lines("L") { s = s .. l } assert(s == "\n\nline\nother"); f::close(); io.input(file); s = "" for l with io.lines(nil, "L") { s = s .. l } assert(s == "\n\nline\nother"); io.input()::close(); s = "" for l with io.lines(file, "L") { s = s .. l } assert(s == "\n\nline\nother"); s = "" for l with io.lines(file, "l") { s = s .. l } assert(s == "lineother"); io.output(file); io.write("a = 10 + 34\na = 2*a\na = -a\n")::close(); local t = {} assert(load(io.lines(file, "L"), nil, nil, t))(); assert(t.a == -((10 + 34) * 2)); { local fn gettoclose(lv) { lv = lv + 1 local stvar = 0 for i = 1, 1000 { local n, v = debug.getlocal(lv, i) if n == "(for state)" { stvar = stvar + 1 if stvar == 4 { return v } } } } local f for l with io.lines(file) { f = gettoclose(1) assert(io.type(f) == "file"); break } assert(io.type(f) == "closed file"); f = nil local fn foo(name) { for l with io.lines(name) { f = gettoclose(1) assert(io.type(f) == "file"); error(f); } } local st, msg = pcall(foo, file) assert(st == false && io.type(msg) == "closed file"); } io.output(file); io.write("0123456789\n")::close(); for a, b with io.lines(file, 1, 1) { if a == "\n" { assert(!b); } else { assert(tonumber(a) == tonumber(b) - 1); } } for a, b, c with io.lines(file, 1, 2, "a") { assert(a == "0" && b == "12" && c == "3456789\n"); } for a, b, c with io.lines(file, "a", 0, 1) { if a == "" { break } assert(a == "0123456789\n" && !b && !c); } collectgarbage(); io.output(file); io.write("00\n10\n20\n30\n40\n")::close(); for a, b with io.lines(file, "n", "n") { if a == 40 { assert(!b); } else { assert(a == b - 10); } } io.output(file); io.write([[ local y = X X = X * 2 + X; X = X - y; ]])::close(); _G.X = 1 assert(!load((io.lines(file)))); collectgarbage(); load((io.lines(file, "L")))(); assert(_G.X == 2); load((io.lines(file, 1)))(); assert(_G.X == 4); load((io.lines(file, 3)))(); assert(_G.X == 8); print('+'); local x1 = "string\n\n\\com \"\"''coisas [[estranhas]] ]]'" io.output(file); assert(io.write(string.format("x2 = %q\n-- comment without ending EOS", x1))); io.close(); assert(loadfile(file))(); assert(x1 == x2); print('+'); assert(os.remove(file)); assert(!os.remove(file)); assert(!os.remove(otherfile)); local fn testloadfile(s, expres) { io.output(file); if s { io.write(s); } io.close(); local res = assert(loadfile(file))() assert(os.remove(file)); assert(res == expres); } testloadfile(nil, nil); testloadfile("# a non-ending comment", nil); testloadfile("\xEF\xBB\xBF# some comment\nreturn 234", 234); testloadfile("\xEF\xBB\xBFreturn 239", 239); testloadfile("\xEF\xBB\xBF", nil); testloadfile("# a comment\nreturn require'debug'.getinfo(1).currentline", 2); io.output(io.open(file, "wb")); assert(io.write(string.dump(fn () { return 10, '\0alo\255', 'hi' }))); io.close(); a, b, c = assert(loadfile(file))() assert(a == 10 && b == "\0alo\255" && c == "hi"); assert(os.remove(file)); { io.output(io.open(file, "wb")); assert(io.write(string.dump(fn () { return 1 }))); io.close(); f = assert(loadfile(file, "b", {})) assert(type(f) == "function" && f() == 1); assert(os.remove(file)); } io.output(io.open(file, "wb")); assert(io.write("#this is a comment for a binary file\0\n", string.dump(fn () { return 20, '\0\0\0' }))); io.close(); a, b, c = assert(loadfile(file))() assert(a == 20 && b == "\0\0\0" && c == nil); assert(os.remove(file)); { local f = io.open(file, 'w') f::write([[ if (...) then a = 15; return b, c, d else return _ENV end ]]); f::close(); local t = { b = 12, c = "xuxu", d = print } local f = assert(loadfile(file, 't', t)) local b, c, d = f(1) assert(t.a == 15 && b == 12 && c == t.c && d == print); assert(f() == t); f = assert(loadfile(file, 't', nil)) assert(f() == nil); f = assert(loadfile(file)) assert(f() == _G); assert(os.remove(file)); } { io.open(file, 'w')::write("return 10")::close(); local s, m = loadfile(file, 'b') assert(!s && string.find(m, "a text chunk")); io.open(file, 'w')::write("\27 return 10")::close(); local s, m = loadfile(file, 't') assert(!s && string.find(m, "a binary chunk")); assert(os.remove(file)); } io.output(file); assert(io.write("qualquer coisa\n")); assert(io.write("mais qualquer coisa")); io.close(); assert(io.output(assert(io.open(otherfile, 'wb')))::write("outra coisa\0\1\3\0\0\0\0\255\0")::close()); local filehandle = assert(io.open(file, 'r+')) local otherfilehandle = assert(io.open(otherfile, 'rb')) assert(filehandle != otherfilehandle); assert(type(filehandle) == "userdata"); assert(filehandle::read('l') == "qualquer coisa"); io.input(otherfilehandle); assert(io.read(string.len("outra coisa")) == "outra coisa"); assert(filehandle::read('l') == "mais qualquer coisa"); filehandle::close(); assert(type(filehandle) == "userdata"); io.input(otherfilehandle); assert(io.read(4) == "\0\1\3\0"); assert(io.read(3) == "\0\0\0"); assert(io.read(0) == ""); assert(io.read(1) == "\255"); assert(io.read('a') == "\0"); assert(!io.read(0)); assert(otherfilehandle == io.input()); otherfilehandle::close(); assert(os.remove(file)); assert(os.remove(otherfile)); collectgarbage(); io.output(file)::write([[ 123.4 -56e-2 not a number second line third line and the rest of the file ]])::close(); io.input(file); local _, a, b, c, d, e, h, __ = io.read(1, 'n', 'n', 'l', 'l', 'l', 'a', 10) assert(io.close(io.input())); assert(_ == ' ' && !__); assert(type(a) == 'number' && a == 123.4 && b == -56e-2); assert(d == 'second line' && e == 'third line'); assert(h == ` and the rest of the file `); assert(os.remove(file)); collectgarbage(); { local f = assert(io.open(file, "w")) local fr = assert(io.open(file, "r")) assert(f::setvbuf("full", 2000)); f::write("x"); assert(fr::read("all") == ""); f::close(); fr::seek("set"); assert(fr::read("all") == "x"); f = assert(io.open(file), "w") assert(f::setvbuf("no")); f::write("x"); fr::seek("set"); assert(fr::read("all") == "x"); f::close(); f = assert(io.open(file, "a")) assert(f::setvbuf("line")); f::write("x"); fr::seek("set", 1); assert(fr::read("all") == ""); f::write("a\n")::seek("set", 1); assert(fr::read("all") == "xa\n"); f::close(); fr::close(); assert(os.remove(file)); } if !_soft { print("testing large files (> BUFSIZ)"); io.output(file); for i = 1, 5001 { io.write('0123456789123'); } io.write('\n12346')::close(); io.input(file); local x = io.read('a') io.input()::seek('set', 0); local y = io.read(30001) .. io.read(1005) .. io.read(0) .. io.read(1) .. io.read(100003) assert(x == y && string.len(x) == 5001 * 13 + 6); io.input()::seek('set', 0); y = io.read() assert(x == y .. '\n' .. io.read()); assert(!io.read()); io.close(io.input()); assert(os.remove(file)); x = nil y = nil } if !_port { local progname { local arg = arg || ARG local i = 0 while arg[(i)] { i = i - 1 } progname = '"' .. arg[(i + 1)] .. '"' } print("testing popen/pclose and execute"); checkerr("invalid mode", io.popen, "cat", ""); checkerr("invalid mode", io.popen, "cat", "r+"); checkerr("invalid mode", io.popen, "cat", "rw"); { local file = os.tmpname() local f = assert(io.popen("cat - > " .. file, "w")) f::write("a line"); assert(f::close()); local f = assert(io.popen("cat - < " .. file, "r")) assert(f::read("a") == "a line"); assert(f::close()); assert(os.remove(file)); } local tests = { { "ls > /dev/null", "ok" }, { "not-to-be-found-command", "exit" }, { "exit 3", "exit", 3 }, { "exit 129", "exit", 129 }, { "kill -s HUP $$", "signal", 1 }, { "kill -s KILL $$", "signal", 9 }, { "sh -c 'kill -s HUP $$'", "exit" }, { progname .. ' -e " "', "ok" }, { progname .. ' -e "os.exit(0, true)"', "ok" }, { progname .. ' -e "os.exit(20, true)"', "exit", 20 } } print("\n(some error messages are expected now)"); for _, v with ipairs(tests) { local x, y, z = io.popen(v[(1)])::close() local x1, y1, z1 = os.execute(v[(1)]) assert(x == x1 && y == y1 && z == z1); if v[(2)] == "ok" { assert(x && y == 'exit' && z == 0); } else { assert(!x && y == v[(2)]); assert((v[(3)] == nil && z > 0) || v[(3)] == z); } } } f = io.tmpfile() assert(io.type(f) == "file"); f::write("alo"); f::seek("set"); assert(f::read("a") == "alo"); } print('+'); print("testing date/time"); assert(os.date("") == ""); assert(os.date("!") == ""); assert(os.date("\0\0") == "\0\0"); assert(os.date("!\0\0") == "\0\0"); local x = string.rep("a", 10000) assert(os.date(x) == x); local t = os.time() D = os.date("*t", t) 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 fn 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 } checkDateTable(os.time()); if !_port { checkDateTable(0); checkDateTable(1); checkDateTable(1000); checkDateTable(0x7fffffff); checkDateTable(0x80000000); } checkerr("invalid conversion specifier", os.date, "%"); checkerr("invalid conversion specifier", os.date, "%9"); checkerr("invalid conversion specifier", os.date, "%"); checkerr("invalid conversion specifier", os.date, "%O"); checkerr("invalid conversion specifier", os.date, "%E"); checkerr("invalid conversion specifier", os.date, "%Ea"); checkerr("not an integer", os.time, { year = 1000, month = 1, day = 1, hour = 'x' }); checkerr("not an integer", os.time, { year = 1000, month = 1, day = 1, hour = 1.5 }); checkerr("missing", os.time, { hour = 12 }); if string.packsize("i") == 4 { checkerr("field 'year' is out-of-bound", os.time, { year = -(1 << 31) + 1899, month = 1, day = 1 }); } if !_port { assert(type(os.date("%Ex")) == 'string'); assert(type(os.date("%Oy")) == 'string'); 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)); if maxint >= 2 ^ 62 { checkerr("out-of-bound", os.time, { year = -maxint, month = 1, day = 1 }); if string.packsize("i") == 4 { if testerr("out-of-bound", os.date, "%Y", 2 ^ 40) { print(" 4-byte time_t"); checkerr("cannot be represented", os.time, { year = 4000, month = 1, day = 1 }); } else { print(" 8-byte time_t"); checkerr("cannot be represented", os.date, "%Y", 2 ^ 60); assert(tonumber(os.time({ year = (1 << 31) + 1899, month = 12, day = 31, hour = 23, min = 59, sec = 59 }))); checkerr("represented", os.time, { year = (1 << 31) + 1899, month = 12, day = 31, hour = 23, min = 59, sec = 60 }); } 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 { print(" 8-byte time_t"); assert(tonumber(os.date("%Y", 2 ^ 60))); checkerr("cannot be represented", os.time, { year = 2 ^ 60, month = 1, day = 1 }); } } } { local D = os.date("*t") local t = os.time(D) if D.isdst == nil { print("no daylight saving information"); } else { assert(type(D.isdst) == 'boolean'); } D.isdst = nil local t1 = os.time(D) assert(t == t1); } local D = os.date("*t") t = os.time(D) D.year = D.year - 1 local t1 = os.time(D) assert(math.abs(os.difftime(t, t1) / (24 * 3600) - 365) < 2); t = os.time() t1 = os.time(os.date("*t")) local diff = os.difftime(t1, t) assert(0 <= diff && diff <= 1); diff = os.difftime(t, t1) assert(-1 <= diff && diff <= 0); local t1 = os.time({ year = 2000, month = 10, day = 1, hour = 23, min = 12 }) local t2 = os.time({ year = 2000, month = 10, day = 1, hour = 23, min = 10, sec = 19 }) assert(os.difftime(t1, t2) == 60 * 2 - 19); t1 = { year = 2005, month = 1, day = 1, hour = 1, min = 0, sec = -3602 } os.time(t1); assert(t1.day == 31 && t1.month == 12 && t1.year == 2004 && t1.hour == 23 && t1.min == 59 && t1.sec == 58 && t1.yday == 366); io.output(io.stdout); local t = os.date('%d %m %Y %H %M %S') local d, m, a, h, min, s = string.match(t, "(%d+) (%d+) (%d+) (%d+) (%d+) (%d+)") d = tonumber(d) m = tonumber(m) a = tonumber(a) h = tonumber(h) min = tonumber(min) s = tonumber(s) io.write(string.format('test done on %2.2d/%2.2d/%d', d, m, a)); io.write(string.format(', at %2.2d:%2.2d:%2.2d\n', h, min, s)); io.write(string.format('%s\n', _VERSION));