Skip to content

Commit fe56435

Browse files
committed
lua, datetime: datetime tests
* created app-tap test for new builtin module `datetime.lua` * added case to check datetime string formatting using: - asctime (gmt time); - ctime (local TZ time); - strftime (using given format). * added positive/negative checks to datetime test - extended api of datetime.parse_date, .parse_time, .parse_time_zone with a length of parsed (sub)string; - this allows us to check partially valid strings like "20121224 Foo bar". Part of tarantool#5941
1 parent 50175cb commit fe56435

File tree

2 files changed

+197
-8
lines changed

2 files changed

+197
-8
lines changed

src/lua/datetime.lua

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ local function datetime_new(o)
318318
end,
319319

320320
month = function(v)
321-
assert(v > 0 and v < 12 )
321+
assert(v > 0 and v < 13 )
322322
M = v
323323
ymd = true
324324
end,
@@ -393,9 +393,8 @@ end
393393

394394
local function parse_date(str)
395395
local dt = ffi.new('dt_t[1]')
396-
local rc = cdt.dt_parse_iso_date(str, #str, dt)
397-
assert(rc > 0)
398-
return mk_timestamp(dt[0])
396+
local len = cdt.dt_parse_iso_date(str, #str, dt)
397+
return len > 0 and mk_timestamp(dt[0]) or nil, tonumber(len)
399398
end
400399

401400
--[[
@@ -411,9 +410,8 @@ end
411410
local function parse_time(str)
412411
local sp = ffi.new('int[1]')
413412
local fp = ffi.new('int[1]')
414-
local rc = cdt.dt_parse_iso_time(str, #str, sp, fp)
415-
assert(rc > 0)
416-
return mk_timestamp(nil, sp[0], fp[0])
413+
local len = cdt.dt_parse_iso_time(str, #str, sp, fp)
414+
return len > 0 and mk_timestamp(nil, sp[0], fp[0]) or nil, tonumber(len)
417415
end
418416

419417
--[[
@@ -448,7 +446,7 @@ local function parse_str(str)
448446
str = str:sub(tonumber(n) + 1)
449447

450448
local ch = str:sub(1,1)
451-
if ch ~= 't' and ch ~= 'T' and ch ~= ' ' then
449+
if ch:match('[Tt ]') == nil then
452450
return mk_timestamp(dt_)
453451
end
454452

test/app-tap/datetime.test.lua

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
#!/usr/bin/env tarantool
2+
3+
local tap = require('tap')
4+
local test = tap.test("errno")
5+
local date = require('datetime')
6+
7+
test:plan(5)
8+
9+
test:test("Simple tests for parser", function(test)
10+
test:plan(2)
11+
test:ok(date("1970-01-01T01:00:00Z") ==
12+
date {year=1970, month=1, day=1, hour=1, minute=0, second=0})
13+
test:ok(date("1970-01-01T02:00:00+02:00") ==
14+
date {year=1970, month=1, day=1, hour=2, minute=0, second=0, tz=120})
15+
end)
16+
17+
test:test("Multiple tests for parser (with nanoseconds)", function(test)
18+
test:plan(165)
19+
-- borrowed from p5-time-moments/t/180_from_string.t
20+
local tests =
21+
{
22+
{ '1970-01-01T00:00:00Z', 0, 0, 0 },
23+
{ '1970-01-01T02:00:00+02:00', 0, 0, 120 },
24+
{ '1970-01-01T01:30:00+01:30', 0, 0, 90 },
25+
{ '1970-01-01T01:00:00+01:00', 0, 0, 60 },
26+
{ '1970-01-01T00:01:00+00:01', 0, 0, 1 },
27+
{ '1970-01-01T00:00:00+00:00', 0, 0, 0 },
28+
{ '1969-12-31T23:59:00-00:01', 0, 0, -1 },
29+
{ '1969-12-31T23:00:00-01:00', 0, 0, -60 },
30+
{ '1969-12-31T22:30:00-01:30', 0, 0, -90 },
31+
{ '1969-12-31T22:00:00-02:00', 0, 0, -120 },
32+
{ '1970-01-01T00:00:00.123456789Z', 0, 123456789, 0 },
33+
{ '1970-01-01T00:00:00.12345678Z', 0, 123456780, 0 },
34+
{ '1970-01-01T00:00:00.1234567Z', 0, 123456700, 0 },
35+
{ '1970-01-01T00:00:00.123456Z', 0, 123456000, 0 },
36+
{ '1970-01-01T00:00:00.12345Z', 0, 123450000, 0 },
37+
{ '1970-01-01T00:00:00.1234Z', 0, 123400000, 0 },
38+
{ '1970-01-01T00:00:00.123Z', 0, 123000000, 0 },
39+
{ '1970-01-01T00:00:00.12Z', 0, 120000000, 0 },
40+
{ '1970-01-01T00:00:00.1Z', 0, 100000000, 0 },
41+
{ '1970-01-01T00:00:00.01Z', 0, 10000000, 0 },
42+
{ '1970-01-01T00:00:00.001Z', 0, 1000000, 0 },
43+
{ '1970-01-01T00:00:00.0001Z', 0, 100000, 0 },
44+
{ '1970-01-01T00:00:00.00001Z', 0, 10000, 0 },
45+
{ '1970-01-01T00:00:00.000001Z', 0, 1000, 0 },
46+
{ '1970-01-01T00:00:00.0000001Z', 0, 100, 0 },
47+
{ '1970-01-01T00:00:00.00000001Z', 0, 10, 0 },
48+
{ '1970-01-01T00:00:00.000000001Z', 0, 1, 0 },
49+
{ '1970-01-01T00:00:00.000000009Z', 0, 9, 0 },
50+
{ '1970-01-01T00:00:00.00000009Z', 0, 90, 0 },
51+
{ '1970-01-01T00:00:00.0000009Z', 0, 900, 0 },
52+
{ '1970-01-01T00:00:00.000009Z', 0, 9000, 0 },
53+
{ '1970-01-01T00:00:00.00009Z', 0, 90000, 0 },
54+
{ '1970-01-01T00:00:00.0009Z', 0, 900000, 0 },
55+
{ '1970-01-01T00:00:00.009Z', 0, 9000000, 0 },
56+
{ '1970-01-01T00:00:00.09Z', 0, 90000000, 0 },
57+
{ '1970-01-01T00:00:00.9Z', 0, 900000000, 0 },
58+
{ '1970-01-01T00:00:00.99Z', 0, 990000000, 0 },
59+
{ '1970-01-01T00:00:00.999Z', 0, 999000000, 0 },
60+
{ '1970-01-01T00:00:00.9999Z', 0, 999900000, 0 },
61+
{ '1970-01-01T00:00:00.99999Z', 0, 999990000, 0 },
62+
{ '1970-01-01T00:00:00.999999Z', 0, 999999000, 0 },
63+
{ '1970-01-01T00:00:00.9999999Z', 0, 999999900, 0 },
64+
{ '1970-01-01T00:00:00.99999999Z', 0, 999999990, 0 },
65+
{ '1970-01-01T00:00:00.999999999Z', 0, 999999999, 0 },
66+
{ '1970-01-01T00:00:00.0Z', 0, 0, 0 },
67+
{ '1970-01-01T00:00:00.00Z', 0, 0, 0 },
68+
{ '1970-01-01T00:00:00.000Z', 0, 0, 0 },
69+
{ '1970-01-01T00:00:00.0000Z', 0, 0, 0 },
70+
{ '1970-01-01T00:00:00.00000Z', 0, 0, 0 },
71+
{ '1970-01-01T00:00:00.000000Z', 0, 0, 0 },
72+
{ '1970-01-01T00:00:00.0000000Z', 0, 0, 0 },
73+
{ '1970-01-01T00:00:00.00000000Z', 0, 0, 0 },
74+
{ '1970-01-01T00:00:00.000000000Z', 0, 0, 0 },
75+
{ '1973-11-29T21:33:09Z', 123456789, 0, 0 },
76+
{ '2013-10-28T17:51:56Z', 1382982716, 0, 0 },
77+
-- { '9999-12-31T23:59:59Z', 253402300799, 0, 0 },
78+
}
79+
for _, value in ipairs(tests) do
80+
local str, epoch, nsec, offset
81+
str, epoch, nsec, offset = unpack(value)
82+
local dt = date(str)
83+
test:ok(dt.secs == epoch, ('%s: dt.secs == %d'):format(str, epoch))
84+
test:ok(dt.nsec == nsec, ('%s: dt.nsec == %d'):format(str, nsec))
85+
test:ok(dt.offset == offset, ('%s: dt.offset == %d'):format(str, offset))
86+
end
87+
end)
88+
89+
local ffi = require('ffi')
90+
91+
ffi.cdef [[
92+
void tzset(void);
93+
]]
94+
95+
test:test("Datetime string formatting", function(test)
96+
test:plan(7)
97+
local str = "1970-01-01"
98+
local t = date(str)
99+
test:ok(t.secs == 0, ('%s: t.secs == %d'):format(str, t.secs))
100+
test:ok(t.nsec == 0, ('%s: t.nsec == %d'):format(str, t.nsec))
101+
test:ok(t.offset == 0, ('%s: t.offset == %d'):format(str, t.offset))
102+
test:ok(date.asctime(t) == 'Thu Jan 1 00:00:00 1970\n', ('%s: asctime'):format(str))
103+
-- ctime() is local timezone dependent. To make sure that
104+
-- test is deterministic we enforce timezone via TZ environment
105+
-- manipulations and calling tzset()
106+
107+
-- redefine timezone to be always GMT-2
108+
os.setenv('TZ', 'GMT-2')
109+
ffi.C.tzset()
110+
test:ok(date.ctime(t) == 'Thu Jan 1 02:00:00 1970\n', ('%s: ctime with timezone'):format(str))
111+
test:ok(date.strftime('%d/%m/%Y', t) == '01/01/1970', ('%s: strftime #1'):format(str))
112+
test:ok(date.strftime('%A %d. %B %Y', t) == 'Thursday 01. January 1970', ('%s: strftime #2'):format(str))
113+
end)
114+
115+
test:test("Parse iso date - valid strings", function(test)
116+
test:plan(32)
117+
local good = {
118+
{2012, 12, 24, "20121224", 8 },
119+
{2012, 12, 24, "20121224 Foo bar", 8 },
120+
{2012, 12, 24, "2012-12-24", 10 },
121+
{2012, 12, 24, "2012-12-24 23:59:59", 10 },
122+
{2012, 12, 24, "2012-12-24T00:00:00+00:00", 10 },
123+
{2012, 12, 24, "2012359", 7 },
124+
{2012, 12, 24, "2012359T235959+0130", 7 },
125+
{2012, 12, 24, "2012-359", 8 },
126+
{2012, 12, 24, "2012W521", 8 },
127+
{2012, 12, 24, "2012-W52-1", 10 },
128+
{2012, 12, 24, "2012Q485", 8 },
129+
{2012, 12, 24, "2012-Q4-85", 10 },
130+
{ 1, 1, 1, "0001-Q1-01", 10 },
131+
{ 1, 1, 1, "0001-W01-1", 10 },
132+
{ 1, 1, 1, "0001-01-01", 10 },
133+
{ 1, 1, 1, "0001-001", 8 },
134+
}
135+
136+
for _, value in ipairs(good) do
137+
local year, month, day, str, date_part_len;
138+
year, month, day, str, date_part_len = unpack(value)
139+
local expected_date = date{year = year, month = month, day = day}
140+
local date_part, len
141+
date_part, len = date.parse_date(str)
142+
test:ok(len == date_part_len, ('%s: length check %d'):format(str, len))
143+
test:ok(expected_date == date_part, ('%s: expected date'):format(str))
144+
end
145+
end)
146+
147+
test:test("Parse iso date - invalid strings", function(test)
148+
test:plan(62)
149+
local bad = {
150+
"20121232" , -- Invalid day of month
151+
"2012-12-310", -- Invalid day of month
152+
"2012-13-24" , -- Invalid month
153+
"2012367" , -- Invalid day of year
154+
"2012-000" , -- Invalid day of year
155+
"2012W533" , -- Invalid week of year
156+
"2012-W52-8" , -- Invalid day of week
157+
"2012Q495" , -- Invalid day of quarter
158+
"2012-Q5-85" , -- Invalid quarter
159+
"20123670" , -- Trailing digit
160+
"201212320" , -- Trailing digit
161+
"2012-12" , -- Reduced accuracy
162+
"2012-Q4" , -- Reduced accuracy
163+
"2012-Q42" , -- Invalid
164+
"2012-Q1-1" , -- Invalid day of quarter
165+
"2012Q--420" , -- Invalid
166+
"2012-Q-420" , -- Invalid
167+
"2012Q11" , -- Incomplete
168+
"2012Q1234" , -- Trailing digit
169+
"2012W12" , -- Incomplete
170+
"2012W1234" , -- Trailing digit
171+
"2012W-123" , -- Invalid
172+
"2012-W12" , -- Incomplete
173+
"2012-W12-12", -- Trailing digit
174+
"2012U1234" , -- Invalid
175+
"2012-1234" , -- Invalid
176+
"2012-X1234" , -- Invalid
177+
"0000-Q1-01" , -- Year less than 0001
178+
"0000-W01-1" , -- Year less than 0001
179+
"0000-01-01" , -- Year less than 0001
180+
"0000-001" , -- Year less than 0001
181+
}
182+
183+
for _, str in ipairs(bad) do
184+
local date_part, len
185+
date_part, len = date.parse_date(str)
186+
test:ok(len == 0, ('%s: length check %d'):format(str, len))
187+
test:ok(date_part == nil, ('%s: empty date check %s'):format(str, date_part))
188+
end
189+
end)
190+
191+
os.exit(test:check() and 0 or 1)

0 commit comments

Comments
 (0)