1
# -*- coding: utf-8 -*-
2 84
from __future__ import unicode_literals
3

4 84
import itertools
5 84
from datetime import datetime, timedelta
6 84
import unittest
7 84
import sys
8

9 84
from dateutil import tz
10 84
from dateutil.tz import tzoffset
11 84
from dateutil.parser import parse, parserinfo
12 84
from dateutil.parser import ParserError
13 84
from dateutil.parser import UnknownTimezoneWarning
14

15 84
from ._common import TZEnvContext
16

17 84
from six import assertRaisesRegex, PY2
18 84
from io import StringIO
19

20 84
import pytest
21

22
# Platform info
23 84
IS_WIN = sys.platform.startswith('win')
24

25 84
try:
26 84
    datetime.now().strftime('%-d')
27 75
    PLATFORM_HAS_DASH_D = True
28 9
except ValueError:
29 9
    PLATFORM_HAS_DASH_D = False
30

31

32 84
@pytest.fixture(params=[True, False])
33 27
def fuzzy(request):
34
    """Fixture to pass fuzzy=True or fuzzy=False to parse"""
35 84
    return request.param
36

37

38
# Parser test cases using no keyword arguments. Format: (parsable_text, expected_datetime, assertion_message)
39 84
PARSER_TEST_CASES = [
40
    ("Thu Sep 25 10:36:28 2003", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
41
    ("Thu Sep 25 2003", datetime(2003, 9, 25), "date command format strip"),
42
    ("2003-09-25T10:49:41", datetime(2003, 9, 25, 10, 49, 41), "iso format strip"),
43
    ("2003-09-25T10:49", datetime(2003, 9, 25, 10, 49), "iso format strip"),
44
    ("2003-09-25T10", datetime(2003, 9, 25, 10), "iso format strip"),
45
    ("2003-09-25", datetime(2003, 9, 25), "iso format strip"),
46
    ("20030925T104941", datetime(2003, 9, 25, 10, 49, 41), "iso stripped format strip"),
47
    ("20030925T1049", datetime(2003, 9, 25, 10, 49, 0), "iso stripped format strip"),
48
    ("20030925T10", datetime(2003, 9, 25, 10), "iso stripped format strip"),
49
    ("20030925", datetime(2003, 9, 25), "iso stripped format strip"),
50
    ("2003-09-25 10:49:41,502", datetime(2003, 9, 25, 10, 49, 41, 502000), "python logger format"),
51
    ("199709020908", datetime(1997, 9, 2, 9, 8), "no separator"),
52
    ("19970902090807", datetime(1997, 9, 2, 9, 8, 7), "no separator"),
53
    ("09-25-2003", datetime(2003, 9, 25), "date with dash"),
54
    ("25-09-2003", datetime(2003, 9, 25), "date with dash"),
55
    ("10-09-2003", datetime(2003, 10, 9), "date with dash"),
56
    ("10-09-03", datetime(2003, 10, 9), "date with dash"),
57
    ("2003.09.25", datetime(2003, 9, 25), "date with dot"),
58
    ("09.25.2003", datetime(2003, 9, 25), "date with dot"),
59
    ("25.09.2003", datetime(2003, 9, 25), "date with dot"),
60
    ("10.09.2003", datetime(2003, 10, 9), "date with dot"),
61
    ("10.09.03", datetime(2003, 10, 9), "date with dot"),
62
    ("2003/09/25", datetime(2003, 9, 25), "date with slash"),
63
    ("09/25/2003", datetime(2003, 9, 25), "date with slash"),
64
    ("25/09/2003", datetime(2003, 9, 25), "date with slash"),
65
    ("10/09/2003", datetime(2003, 10, 9), "date with slash"),
66
    ("10/09/03", datetime(2003, 10, 9), "date with slash"),
67
    ("2003 09 25", datetime(2003, 9, 25), "date with space"),
68
    ("09 25 2003", datetime(2003, 9, 25), "date with space"),
69
    ("25 09 2003", datetime(2003, 9, 25), "date with space"),
70
    ("10 09 2003", datetime(2003, 10, 9), "date with space"),
71
    ("10 09 03", datetime(2003, 10, 9), "date with space"),
72
    ("25 09 03", datetime(2003, 9, 25), "date with space"),
73
    ("03 25 Sep", datetime(2003, 9, 25), "strangely ordered date"),
74
    ("25 03 Sep", datetime(2025, 9, 3), "strangely ordered date"),
75
    ("  July   4 ,  1976   12:01:02   am  ", datetime(1976, 7, 4, 0, 1, 2), "extra space"),
76
    ("Wed, July 10, '96", datetime(1996, 7, 10, 0, 0), "random format"),
77
    ("1996.July.10 AD 12:08 PM", datetime(1996, 7, 10, 12, 8), "random format"),
78
    ("July 4, 1976", datetime(1976, 7, 4), "random format"),
79
    ("7 4 1976", datetime(1976, 7, 4), "random format"),
80
    ("4 jul 1976", datetime(1976, 7, 4), "random format"),
81
    ("4 Jul 1976", datetime(1976, 7, 4), "'%-d %b %Y' format"),
82
    ("7-4-76", datetime(1976, 7, 4), "random format"),
83
    ("19760704", datetime(1976, 7, 4), "random format"),
84
    ("0:01:02 on July 4, 1976", datetime(1976, 7, 4, 0, 1, 2), "random format"),
85
    ("July 4, 1976 12:01:02 am", datetime(1976, 7, 4, 0, 1, 2), "random format"),
86
    ("Mon Jan  2 04:24:27 1995", datetime(1995, 1, 2, 4, 24, 27), "random format"),
87
    ("04.04.95 00:22", datetime(1995, 4, 4, 0, 22), "random format"),
88
    ("Jan 1 1999 11:23:34.578", datetime(1999, 1, 1, 11, 23, 34, 578000), "random format"),
89
    ("950404 122212", datetime(1995, 4, 4, 12, 22, 12), "random format"),
90
    ("3rd of May 2001", datetime(2001, 5, 3), "random format"),
91
    ("5th of March 2001", datetime(2001, 3, 5), "random format"),
92
    ("1st of May 2003", datetime(2003, 5, 1), "random format"),
93
    ('0099-01-01T00:00:00', datetime(99, 1, 1, 0, 0), "99 ad"),
94
    ('0031-01-01T00:00:00', datetime(31, 1, 1, 0, 0), "31 ad"),
95
    ("20080227T21:26:01.123456789", datetime(2008, 2, 27, 21, 26, 1, 123456), "high precision seconds"),
96
    ('13NOV2017', datetime(2017, 11, 13), "dBY (See GH360)"),
97
    ('0003-03-04', datetime(3, 3, 4), "pre 12 year same month (See GH PR #293)"),
98
    ('December.0031.30', datetime(31, 12, 30), "BYd corner case (GH#687)"),
99

100
    # Cases with legacy h/m/s format, candidates for deprecation (GH#886)
101
    ("2016-12-21 04.2h", datetime(2016, 12, 21, 4, 12), "Fractional Hours"),
102
]
103
# Check that we don't have any duplicates
104 84
assert len(set([x[0] for x in PARSER_TEST_CASES])) == len(PARSER_TEST_CASES)
105

106

107 84
@pytest.mark.parametrize("parsable_text,expected_datetime,assertion_message", PARSER_TEST_CASES)
108 27
def test_parser(parsable_text, expected_datetime, assertion_message):
109 84
    assert parse(parsable_text) == expected_datetime, assertion_message
110

111

112
# Parser test cases using datetime(2003, 9, 25) as a default.
113
# Format: (parsable_text, expected_datetime, assertion_message)
114 84
PARSER_DEFAULT_TEST_CASES = [
115
    ("Thu Sep 25 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
116
    ("Thu Sep 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
117
    ("Thu 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
118
    ("Sep 10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
119
    ("10:36:28", datetime(2003, 9, 25, 10, 36, 28), "date command format strip"),
120
    ("10:36", datetime(2003, 9, 25, 10, 36), "date command format strip"),
121
    ("Sep 2003", datetime(2003, 9, 25), "date command format strip"),
122
    ("Sep", datetime(2003, 9, 25), "date command format strip"),
123
    ("2003", datetime(2003, 9, 25), "date command format strip"),
124
    ("10h36m28.5s", datetime(2003, 9, 25, 10, 36, 28, 500000), "hour with letters"),
125
    ("10h36m28s", datetime(2003, 9, 25, 10, 36, 28), "hour with letters strip"),
126
    ("10h36m", datetime(2003, 9, 25, 10, 36), "hour with letters strip"),
127
    ("10h", datetime(2003, 9, 25, 10), "hour with letters strip"),
128
    ("10 h 36", datetime(2003, 9, 25, 10, 36), "hour with letters strip"),
129
    ("10 h 36.5", datetime(2003, 9, 25, 10, 36, 30), "hour with letter strip"),
130
    ("36 m 5", datetime(2003, 9, 25, 0, 36, 5), "hour with letters spaces"),
131
    ("36 m 5 s", datetime(2003, 9, 25, 0, 36, 5), "minute with letters spaces"),
132
    ("36 m 05", datetime(2003, 9, 25, 0, 36, 5), "minute with letters spaces"),
133
    ("36 m 05 s", datetime(2003, 9, 25, 0, 36, 5), "minutes with letters spaces"),
134
    ("10h am", datetime(2003, 9, 25, 10), "hour am pm"),
135
    ("10h pm", datetime(2003, 9, 25, 22), "hour am pm"),
136
    ("10am", datetime(2003, 9, 25, 10), "hour am pm"),
137
    ("10pm", datetime(2003, 9, 25, 22), "hour am pm"),
138
    ("10:00 am", datetime(2003, 9, 25, 10), "hour am pm"),
139
    ("10:00 pm", datetime(2003, 9, 25, 22), "hour am pm"),
140
    ("10:00am", datetime(2003, 9, 25, 10), "hour am pm"),
141
    ("10:00pm", datetime(2003, 9, 25, 22), "hour am pm"),
142
    ("10:00a.m", datetime(2003, 9, 25, 10), "hour am pm"),
143
    ("10:00p.m", datetime(2003, 9, 25, 22), "hour am pm"),
144
    ("10:00a.m.", datetime(2003, 9, 25, 10), "hour am pm"),
145
    ("10:00p.m.", datetime(2003, 9, 25, 22), "hour am pm"),
146
    ("Wed", datetime(2003, 10, 1), "weekday alone"),
147
    ("Wednesday", datetime(2003, 10, 1), "long weekday"),
148
    ("October", datetime(2003, 10, 25), "long month"),
149
    ("31-Dec-00", datetime(2000, 12, 31), "zero year"),
150
    ("0:01:02", datetime(2003, 9, 25, 0, 1, 2), "random format"),
151
    ("12h 01m02s am", datetime(2003, 9, 25, 0, 1, 2), "random format"),
152
    ("12:08 PM", datetime(2003, 9, 25, 12, 8), "random format"),
153
    ("01h02m03", datetime(2003, 9, 25, 1, 2, 3), "random format"),
154
    ("01h02", datetime(2003, 9, 25, 1, 2), "random format"),
155
    ("01h02s", datetime(2003, 9, 25, 1, 0, 2), "random format"),
156
    ("01m02", datetime(2003, 9, 25, 0, 1, 2), "random format"),
157
    ("01m02h", datetime(2003, 9, 25, 2, 1), "random format"),
158
    ("2004 10 Apr 11h30m", datetime(2004, 4, 10, 11, 30), "random format")
159
]
160
# Check that we don't have any duplicates
161 84
assert len(set([x[0] for x in PARSER_DEFAULT_TEST_CASES])) == len(PARSER_DEFAULT_TEST_CASES)
162

163

164 84
@pytest.mark.parametrize("parsable_text,expected_datetime,assertion_message", PARSER_DEFAULT_TEST_CASES)
165 27
def test_parser_default(parsable_text, expected_datetime, assertion_message):
166 84
    assert parse(parsable_text, default=datetime(2003, 9, 25)) == expected_datetime, assertion_message
167

168

169 84
@pytest.mark.parametrize('sep', ['-', '.', '/', ' '])
170 27
def test_parse_dayfirst(sep):
171 84
    expected = datetime(2003, 9, 10)
172 84
    fmt = sep.join(['%d', '%m', '%Y'])
173 84
    dstr = expected.strftime(fmt)
174 84
    result = parse(dstr, dayfirst=True)
175 84
    assert result == expected
176

177

178 84
@pytest.mark.parametrize('sep', ['-', '.', '/', ' '])
179 27
def test_parse_yearfirst(sep):
180 84
    expected = datetime(2010, 9, 3)
181 84
    fmt = sep.join(['%Y', '%m', '%d'])
182 84
    dstr = expected.strftime(fmt)
183 84
    result = parse(dstr, yearfirst=True)
184 84
    assert result == expected
185

186

187 84
@pytest.mark.parametrize('dstr,expected', [
188
    ("Thu Sep 25 10:36:28 BRST 2003", datetime(2003, 9, 25, 10, 36, 28)),
189
    ("1996.07.10 AD at 15:08:56 PDT", datetime(1996, 7, 10, 15, 8, 56)),
190
    ("Tuesday, April 12, 1952 AD 3:30:42pm PST",
191
     datetime(1952, 4, 12, 15, 30, 42)),
192
    ("November 5, 1994, 8:15:30 am EST", datetime(1994, 11, 5, 8, 15, 30)),
193
    ("1994-11-05T08:15:30-05:00", datetime(1994, 11, 5, 8, 15, 30)),
194
    ("1994-11-05T08:15:30Z", datetime(1994, 11, 5, 8, 15, 30)),
195
    ("1976-07-04T00:01:02Z", datetime(1976, 7, 4, 0, 1, 2)),
196
    ("1986-07-05T08:15:30z", datetime(1986, 7, 5, 8, 15, 30)),
197
    ("Tue Apr 4 00:22:12 PDT 1995", datetime(1995, 4, 4, 0, 22, 12)),
198
])
199 27
def test_parse_ignoretz(dstr, expected):
200 84
    result = parse(dstr, ignoretz=True)
201 84
    assert result == expected
202

203

204 84
_brsttz = tzoffset("BRST", -10800)
205

206

207 84
@pytest.mark.parametrize('dstr,expected', [
208
    ("20030925T104941-0300",
209
     datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)),
210
    ("Thu, 25 Sep 2003 10:49:41 -0300",
211
     datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)),
212
    ("2003-09-25T10:49:41.5-03:00",
213
     datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)),
214
    ("2003-09-25T10:49:41-03:00",
215
     datetime(2003, 9, 25, 10, 49, 41, tzinfo=_brsttz)),
216
    ("20030925T104941.5-0300",
217
     datetime(2003, 9, 25, 10, 49, 41, 500000, tzinfo=_brsttz)),
218
])
219 27
def test_parse_with_tzoffset(dstr, expected):
220
    # In these cases, we are _not_ passing a tzinfos arg
221 84
    result = parse(dstr)
222 84
    assert result == expected
223

224

225 84
class TestFormat(object):
226

227 84
    def test_ybd(self):
228
        # If we have a 4-digit year, a non-numeric month (abbreviated or not),
229
        # and a day (1 or 2 digits), then there is no ambiguity as to which
230
        # token is a year/month/day.  This holds regardless of what order the
231
        # terms are in and for each of the separators below.
232

233 84
        seps = ['-', ' ', '/', '.']
234

235 84
        year_tokens = ['%Y']
236 84
        month_tokens = ['%b', '%B']
237 84
        day_tokens = ['%d']
238 84
        if PLATFORM_HAS_DASH_D:
239 75
            day_tokens.append('%-d')
240

241 84
        prods = itertools.product(year_tokens, month_tokens, day_tokens)
242 84
        perms = [y for x in prods for y in itertools.permutations(x)]
243 84
        unambig_fmts = [sep.join(perm) for sep in seps for perm in perms]
244

245 84
        actual = datetime(2003, 9, 25)
246

247 84
        for fmt in unambig_fmts:
248 84
            dstr = actual.strftime(fmt)
249 84
            res = parse(dstr)
250 84
            assert res == actual
251

252
    # TODO: some redundancy with PARSER_TEST_CASES cases
253 84
    @pytest.mark.parametrize("fmt,dstr", [
254
        ("%a %b %d %Y", "Thu Sep 25 2003"),
255
        ("%b %d %Y", "Sep 25 2003"),
256
        ("%Y-%m-%d", "2003-09-25"),
257
        ("%Y%m%d", "20030925"),
258
        ("%Y-%b-%d", "2003-Sep-25"),
259
        ("%d-%b-%Y", "25-Sep-2003"),
260
        ("%b-%d-%Y", "Sep-25-2003"),
261
        ("%m-%d-%Y", "09-25-2003"),
262
        ("%d-%m-%Y", "25-09-2003"),
263
        ("%Y.%m.%d", "2003.09.25"),
264
        ("%Y.%b.%d", "2003.Sep.25"),
265
        ("%d.%b.%Y", "25.Sep.2003"),
266
        ("%b.%d.%Y", "Sep.25.2003"),
267
        ("%m.%d.%Y", "09.25.2003"),
268
        ("%d.%m.%Y", "25.09.2003"),
269
        ("%Y/%m/%d", "2003/09/25"),
270
        ("%Y/%b/%d", "2003/Sep/25"),
271
        ("%d/%b/%Y", "25/Sep/2003"),
272
        ("%b/%d/%Y", "Sep/25/2003"),
273
        ("%m/%d/%Y", "09/25/2003"),
274
        ("%d/%m/%Y", "25/09/2003"),
275
        ("%Y %m %d", "2003 09 25"),
276
        ("%Y %b %d", "2003 Sep 25"),
277
        ("%d %b %Y", "25 Sep 2003"),
278
        ("%m %d %Y", "09 25 2003"),
279
        ("%d %m %Y", "25 09 2003"),
280
        ("%y %d %b", "03 25 Sep",),
281
    ])
282 27
    def test_strftime_formats_2003Sep25(self, fmt, dstr):
283 84
        expected = datetime(2003, 9, 25)
284

285
        # First check that the format strings behave as expected
286
        #  (not strictly necessary, but nice to have)
287 84
        assert expected.strftime(fmt) == dstr
288

289 84
        res = parse(dstr)
290 84
        assert res == expected
291

292

293 84
class TestInputTypes(object):
294 84
    def test_empty_string_invalid(self):
295 84
        with pytest.raises(ParserError):
296 84
            parse('')
297

298 84
    def test_none_invalid(self):
299 84
        with pytest.raises(TypeError):
300 84
            parse(None)
301

302 84
    def test_int_invalid(self):
303 84
        with pytest.raises(TypeError):
304 84
            parse(13)
305

306 84
    def test_duck_typing(self):
307
        # We want to support arbitrary classes that implement the stream
308
        # interface.
309

310 84
        class StringPassThrough(object):
311 84
            def __init__(self, stream):
312 84
                self.stream = stream
313

314 84
            def read(self, *args, **kwargs):
315 84
                return self.stream.read(*args, **kwargs)
316

317 84
        dstr = StringPassThrough(StringIO('2014 January 19'))
318

319 84
        res = parse(dstr)
320 84
        expected = datetime(2014, 1, 19)
321 84
        assert res == expected
322

323 84
    def test_parse_stream(self):
324 84
        dstr = StringIO('2014 January 19')
325

326 84
        res = parse(dstr)
327 84
        expected = datetime(2014, 1, 19)
328 84
        assert res == expected
329

330 84
    def test_parse_str(self):
331
        # Parser should be able to handle bytestring and unicode
332 84
        uni_str = '2014-05-01 08:00:00'
333 84
        bytes_str = uni_str.encode()
334

335 84
        res = parse(bytes_str)
336 84
        expected = parse(uni_str)
337 84
        assert res == expected
338

339 84
    def test_parse_bytes(self):
340 84
        res = parse(b'2014 January 19')
341 84
        expected = datetime(2014, 1, 19)
342 84
        assert res == expected
343

344 84
    def test_parse_bytearray(self):
345
        # GH#417
346 84
        res = parse(bytearray(b'2014 January 19'))
347 84
        expected = datetime(2014, 1, 19)
348 84
        assert res == expected
349

350

351 84
class TestTzinfoInputTypes(object):
352 84
    def assert_equal_same_tz(self, dt1, dt2):
353 84
        assert dt1 == dt2
354 84
        assert dt1.tzinfo is dt2.tzinfo
355

356 84
    def test_tzinfo_dict_could_return_none(self):
357 84
        dstr = "2017-02-03 12:40 BRST"
358 84
        result = parse(dstr, tzinfos={"BRST": None})
359 84
        expected = datetime(2017, 2, 3, 12, 40)
360 84
        self.assert_equal_same_tz(result, expected)
361

362 84
    def test_tzinfos_callable_could_return_none(self):
363 84
        dstr = "2017-02-03 12:40 BRST"
364 84
        result = parse(dstr, tzinfos=lambda *args: None)
365 84
        expected = datetime(2017, 2, 3, 12, 40)
366 84
        self.assert_equal_same_tz(result, expected)
367

368 84
    def test_invalid_tzinfo_input(self):
369 84
        dstr = "2014 January 19 09:00 UTC"
370
        # Pass an absurd tzinfos object
371 84
        tzinfos = {"UTC": ValueError}
372 84
        with pytest.raises(TypeError):
373 84
            parse(dstr, tzinfos=tzinfos)
374

375 84
    def test_valid_tzinfo_tzinfo_input(self):
376 84
        dstr = "2014 January 19 09:00 UTC"
377 84
        tzinfos = {"UTC": tz.UTC}
378 84
        expected = datetime(2014, 1, 19, 9, tzinfo=tz.UTC)
379 84
        res = parse(dstr, tzinfos=tzinfos)
380 84
        self.assert_equal_same_tz(res, expected)
381

382 84
    def test_valid_tzinfo_unicode_input(self):
383 84
        dstr = "2014 January 19 09:00 UTC"
384 84
        tzinfos = {u"UTC": u"UTC+0"}
385 84
        expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzstr("UTC+0"))
386 84
        res = parse(dstr, tzinfos=tzinfos)
387 84
        self.assert_equal_same_tz(res, expected)
388

389 84
    def test_valid_tzinfo_callable_input(self):
390 84
        dstr = "2014 January 19 09:00 UTC"
391

392 84
        def tzinfos(*args, **kwargs):
393 84
            return u"UTC+0"
394

395 84
        expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzstr("UTC+0"))
396 84
        res = parse(dstr, tzinfos=tzinfos)
397 84
        self.assert_equal_same_tz(res, expected)
398

399 84
    def test_valid_tzinfo_int_input(self):
400 84
        dstr = "2014 January 19 09:00 UTC"
401 84
        tzinfos = {u"UTC": -28800}
402 84
        expected = datetime(2014, 1, 19, 9, tzinfo=tz.tzoffset(u"UTC", -28800))
403 84
        res = parse(dstr, tzinfos=tzinfos)
404 84
        self.assert_equal_same_tz(res, expected)
405

406

407 84
class ParserTest(unittest.TestCase):
408

409 84
    @classmethod
410 27
    def setup_class(cls):
411 84
        cls.tzinfos = {"BRST": -10800}
412 84
        cls.brsttz = tzoffset("BRST", -10800)
413 84
        cls.default = datetime(2003, 9, 25)
414

415
        # Parser should be able to handle bytestring and unicode
416 84
        cls.uni_str = '2014-05-01 08:00:00'
417 84
        cls.str_str = cls.uni_str.encode()
418

419 84
    def testParserParseStr(self):
420 84
        from dateutil.parser import parser
421

422 84
        assert parser().parse(self.str_str) == parser().parse(self.uni_str)
423

424 84
    def testParseUnicodeWords(self):
425

426 84
        class rus_parserinfo(parserinfo):
427 84
            MONTHS = [("янв", "Январь"),
428
                      ("фев", "Февраль"),
429
                      ("мар", "Март"),
430
                      ("апр", "Апрель"),
431
                      ("май", "Май"),
432
                      ("июн", "Июнь"),
433
                      ("июл", "Июль"),
434
                      ("авг", "Август"),
435
                      ("сен", "Сентябрь"),
436
                      ("окт", "Октябрь"),
437
                      ("ноя", "Ноябрь"),
438
                      ("дек", "Декабрь")]
439

440 84
        expected = datetime(2015, 9, 10, 10, 20)
441 84
        res = parse('10 Сентябрь 2015 10:20', parserinfo=rus_parserinfo())
442 84
        assert res  == expected
443

444 84
    def testParseWithNulls(self):
445
        # This relies on the from __future__ import unicode_literals, because
446
        # explicitly specifying a unicode literal is a syntax error in Py 3.2
447
        # May want to switch to u'...' if we ever drop Python 3.2 support.
448 84
        pstring = '\x00\x00August 29, 1924'
449

450 84
        assert parse(pstring) == datetime(1924, 8, 29)
451

452 84
    def testDateCommandFormat(self):
453 84
        self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003",
454
                               tzinfos=self.tzinfos),
455
                         datetime(2003, 9, 25, 10, 36, 28,
456
                                  tzinfo=self.brsttz))
457

458 84
    def testDateCommandFormatReversed(self):
459 84
        self.assertEqual(parse("2003 10:36:28 BRST 25 Sep Thu",
460
                               tzinfos=self.tzinfos),
461
                         datetime(2003, 9, 25, 10, 36, 28,
462
                                  tzinfo=self.brsttz))
463

464 84
    def testDateCommandFormatWithLong(self):
465 84
        if PY2:
466 13
            self.assertEqual(parse("Thu Sep 25 10:36:28 BRST 2003",
467
                                   tzinfos={"BRST": long(-10800)}),
468
                             datetime(2003, 9, 25, 10, 36, 28,
469
                                      tzinfo=self.brsttz))
470

471 84
    def testISOFormatStrip2(self):
472 84
        self.assertEqual(parse("2003-09-25T10:49:41+03:00"),
473
                         datetime(2003, 9, 25, 10, 49, 41,
474
                                  tzinfo=tzoffset(None, 10800)))
475

476 84
    def testISOStrippedFormatStrip2(self):
477 84
        self.assertEqual(parse("20030925T104941+0300"),
478
                         datetime(2003, 9, 25, 10, 49, 41,
479
                                  tzinfo=tzoffset(None, 10800)))
480

481 84
    def testAMPMNoHour(self):
482 84
        with pytest.raises(ParserError):
483 84
            parse("AM")
484

485 84
        with pytest.raises(ParserError):
486 84
            parse("Jan 20, 2015 PM")
487

488 84
    def testAMPMRange(self):
489 84
        with pytest.raises(ParserError):
490 84
            parse("13:44 AM")
491

492 84
        with pytest.raises(ParserError):
493 84
            parse("January 25, 1921 23:13 PM")
494

495 84
    def testPertain(self):
496 84
        self.assertEqual(parse("Sep 03", default=self.default),
497
                         datetime(2003, 9, 3))
498 84
        self.assertEqual(parse("Sep of 03", default=self.default),
499
                         datetime(2003, 9, 25))
500

501 84
    def testFuzzy(self):
502 84
        s = "Today is 25 of September of 2003, exactly " \
503
            "at 10:49:41 with timezone -03:00."
504 84
        self.assertEqual(parse(s, fuzzy=True),
505
                         datetime(2003, 9, 25, 10, 49, 41,
506
                                  tzinfo=self.brsttz))
507

508 84
    def testFuzzyWithTokens(self):
509 84
        s1 = "Today is 25 of September of 2003, exactly " \
510
            "at 10:49:41 with timezone -03:00."
511 84
        self.assertEqual(parse(s1, fuzzy_with_tokens=True),
512
                         (datetime(2003, 9, 25, 10, 49, 41,
513
                                   tzinfo=self.brsttz),
514
                         ('Today is ', 'of ', ', exactly at ',
515
                          ' with timezone ', '.')))
516

517 84
        s2 = "http://biz.yahoo.com/ipo/p/600221.html"
518 84
        self.assertEqual(parse(s2, fuzzy_with_tokens=True),
519
                         (datetime(2060, 2, 21, 0, 0, 0),
520
                         ('http://biz.yahoo.com/ipo/p/', '.html')))
521

522 84
    def testFuzzyAMPMProblem(self):
523
        # Sometimes fuzzy parsing results in AM/PM flag being set without
524
        # hours - if it's fuzzy it should ignore that.
525 84
        s1 = "I have a meeting on March 1, 1974."
526 84
        s2 = "On June 8th, 2020, I am going to be the first man on Mars"
527

528
        # Also don't want any erroneous AM or PMs changing the parsed time
529 84
        s3 = "Meet me at the AM/PM on Sunset at 3:00 AM on December 3rd, 2003"
530 84
        s4 = "Meet me at 3:00AM on December 3rd, 2003 at the AM/PM on Sunset"
531

532 84
        self.assertEqual(parse(s1, fuzzy=True), datetime(1974, 3, 1))
533 84
        self.assertEqual(parse(s2, fuzzy=True), datetime(2020, 6, 8))
534 84
        self.assertEqual(parse(s3, fuzzy=True), datetime(2003, 12, 3, 3))
535 84
        self.assertEqual(parse(s4, fuzzy=True), datetime(2003, 12, 3, 3))
536

537 84
    def testFuzzyIgnoreAMPM(self):
538 84
        s1 = "Jan 29, 1945 14:45 AM I going to see you there?"
539 84
        with pytest.warns(UnknownTimezoneWarning):
540 84
            res = parse(s1, fuzzy=True)
541 84
        self.assertEqual(res, datetime(1945, 1, 29, 14, 45))
542

543 84
    def testRandomFormat24(self):
544 84
        self.assertEqual(parse("0:00 PM, PST", default=self.default,
545
                               ignoretz=True),
546
                         datetime(2003, 9, 25, 12, 0))
547

548 84
    def testRandomFormat26(self):
549 84
        with pytest.warns(UnknownTimezoneWarning):
550 84
            res = parse("5:50 A.M. on June 13, 1990")
551

552 84
        self.assertEqual(res, datetime(1990, 6, 13, 5, 50))
553

554 84
    def testUnspecifiedDayFallback(self):
555
        # Test that for an unspecified day, the fallback behavior is correct.
556 84
        self.assertEqual(parse("April 2009", default=datetime(2010, 1, 31)),
557
                         datetime(2009, 4, 30))
558

559 84
    def testUnspecifiedDayFallbackFebNoLeapYear(self):
560 84
        self.assertEqual(parse("Feb 2007", default=datetime(2010, 1, 31)),
561
                         datetime(2007, 2, 28))
562

563 84
    def testUnspecifiedDayFallbackFebLeapYear(self):
564 84
        self.assertEqual(parse("Feb 2008", default=datetime(2010, 1, 31)),
565
                         datetime(2008, 2, 29))
566

567 84
    def testErrorType01(self):
568 84
        with pytest.raises(ParserError):
569 84
            parse('shouldfail')
570

571 84
    def testCorrectErrorOnFuzzyWithTokens(self):
572 84
        assertRaisesRegex(self, ParserError, 'Unknown string format',
573
                          parse, '04/04/32/423', fuzzy_with_tokens=True)
574 84
        assertRaisesRegex(self, ParserError, 'Unknown string format',
575
                          parse, '04/04/04 +32423', fuzzy_with_tokens=True)
576 84
        assertRaisesRegex(self, ParserError, 'Unknown string format',
577
                          parse, '04/04/0d4', fuzzy_with_tokens=True)
578

579 84
    def testIncreasingCTime(self):
580
        # This test will check 200 different years, every month, every day,
581
        # every hour, every minute, every second, and every weekday, using
582
        # a delta of more or less 1 year, 1 month, 1 day, 1 minute and
583
        # 1 second.
584 84
        delta = timedelta(days=365+31+1, seconds=1+60+60*60)
585 84
        dt = datetime(1900, 1, 1, 0, 0, 0, 0)
586 84
        for i in range(200):
587 84
            assert parse(dt.ctime()) == dt
588 84
            dt += delta
589

590 84
    def testIncreasingISOFormat(self):
591 84
        delta = timedelta(days=365+31+1, seconds=1+60+60*60)
592 84
        dt = datetime(1900, 1, 1, 0, 0, 0, 0)
593 84
        for i in range(200):
594 84
            assert parse(dt.isoformat()) == dt
595 84
            dt += delta
596

597 84
    def testMicrosecondsPrecisionError(self):
598
        # Skip found out that sad precision problem. :-(
599 84
        dt1 = parse("00:11:25.01")
600 84
        dt2 = parse("00:12:10.01")
601 84
        assert dt1.microsecond == 10000
602 84
        assert dt2.microsecond == 10000
603

604 84
    def testMicrosecondPrecisionErrorReturns(self):
605
        # One more precision issue, discovered by Eric Brown.  This should
606
        # be the last one, as we're no longer using floating points.
607 84
        for ms in [100001, 100000, 99999, 99998,
608
                    10001,  10000,  9999,  9998,
609
                     1001,   1000,   999,   998,
610
                      101,    100,    99,    98]:
611 84
            dt = datetime(2008, 2, 27, 21, 26, 1, ms)
612 84
            assert parse(dt.isoformat()) == dt
613

614 84
    def testCustomParserInfo(self):
615
        # Custom parser info wasn't working, as Michael Elsdörfer discovered.
616 84
        from dateutil.parser import parserinfo, parser
617

618 84
        class myparserinfo(parserinfo):
619 84
            MONTHS = parserinfo.MONTHS[:]
620 84
            MONTHS[0] = ("Foo", "Foo")
621 84
        myparser = parser(myparserinfo())
622 84
        dt = myparser.parse("01/Foo/2007")
623 84
        assert dt == datetime(2007, 1, 1)
624

625 84
    def testCustomParserShortDaynames(self):
626
        # Horacio Hoyos discovered that day names shorter than 3 characters,
627
        # for example two letter German day name abbreviations, don't work:
628
        # https://github.com/dateutil/dateutil/issues/343
629 84
        from dateutil.parser import parserinfo, parser
630

631 84
        class GermanParserInfo(parserinfo):
632 84
            WEEKDAYS = [("Mo", "Montag"),
633
                        ("Di", "Dienstag"),
634
                        ("Mi", "Mittwoch"),
635
                        ("Do", "Donnerstag"),
636
                        ("Fr", "Freitag"),
637
                        ("Sa", "Samstag"),
638
                        ("So", "Sonntag")]
639

640 84
        myparser = parser(GermanParserInfo())
641 84
        dt = myparser.parse("Sa 21. Jan 2017")
642 84
        self.assertEqual(dt, datetime(2017, 1, 21))
643

644 84
    def testNoYearFirstNoDayFirst(self):
645 84
        dtstr = '090107'
646

647
        # Should be MMDDYY
648 84
        self.assertEqual(parse(dtstr),
649
                         datetime(2007, 9, 1))
650

651 84
        self.assertEqual(parse(dtstr, yearfirst=False, dayfirst=False),
652
                         datetime(2007, 9, 1))
653

654 84
    def testYearFirst(self):
655 84
        dtstr = '090107'
656

657
        # Should be MMDDYY
658 84
        self.assertEqual(parse(dtstr, yearfirst=True),
659
                         datetime(2009, 1, 7))
660

661 84
        self.assertEqual(parse(dtstr, yearfirst=True, dayfirst=False),
662
                         datetime(2009, 1, 7))
663

664 84
    def testDayFirst(self):
665 84
        dtstr = '090107'
666

667
        # Should be DDMMYY
668 84
        self.assertEqual(parse(dtstr, dayfirst=True),
669
                         datetime(2007, 1, 9))
670

671 84
        self.assertEqual(parse(dtstr, yearfirst=False, dayfirst=True),
672
                         datetime(2007, 1, 9))
673

674 84
    def testDayFirstYearFirst(self):
675 84
        dtstr = '090107'
676
        # Should be YYDDMM
677 84
        self.assertEqual(parse(dtstr, yearfirst=True, dayfirst=True),
678
                         datetime(2009, 7, 1))
679

680 84
    def testUnambiguousYearFirst(self):
681 84
        dtstr = '2015 09 25'
682 84
        self.assertEqual(parse(dtstr, yearfirst=True),
683
                         datetime(2015, 9, 25))
684

685 84
    def testUnambiguousDayFirst(self):
686 84
        dtstr = '2015 09 25'
687 84
        self.assertEqual(parse(dtstr, dayfirst=True),
688
                         datetime(2015, 9, 25))
689

690 84
    def testUnambiguousDayFirstYearFirst(self):
691 84
        dtstr = '2015 09 25'
692 84
        self.assertEqual(parse(dtstr, dayfirst=True, yearfirst=True),
693
                         datetime(2015, 9, 25))
694

695 84
    def test_mstridx(self):
696
        # See GH408
697 84
        dtstr = '2015-15-May'
698 84
        self.assertEqual(parse(dtstr),
699
                         datetime(2015, 5, 15))
700

701 84
    def test_idx_check(self):
702 84
        dtstr = '2017-07-17 06:15:'
703
        # Pre-PR, the trailing colon will cause an IndexError at 824-825
704
        # when checking `i < len_l` and then accessing `l[i+1]`
705 84
        res = parse(dtstr, fuzzy=True)
706 84
        assert res == datetime(2017, 7, 17, 6, 15)
707

708 84
    def test_hmBY(self):
709
        # See GH#483
710 84
        dtstr = '02:17NOV2017'
711 84
        res = parse(dtstr, default=self.default)
712 84
        assert res == datetime(2017, 11, self.default.day, 2, 17)
713

714 84
    def test_validate_hour(self):
715
        # See GH353
716 84
        invalid = "201A-01-01T23:58:39.239769+03:00"
717 84
        with pytest.raises(ParserError):
718 84
            parse(invalid)
719

720 84
    def test_era_trailing_year(self):
721 84
        dstr = 'AD2001'
722 84
        res = parse(dstr)
723 84
        assert res.year == 2001, res
724

725 84
    def test_includes_timestr(self):
726 84
        timestr = "2020-13-97T44:61:83"
727

728 84
        try:
729 84
            parse(timestr)
730 84
        except ParserError as e:
731 84
            assert e.args[1] == timestr
732
        else:
733 0
            pytest.fail("Failed to raise ParserError")
734

735

736 84
class TestOutOfBounds(object):
737

738 84
    def test_no_year_zero(self):
739 84
        with pytest.raises(ParserError):
740 84
            parse("0000 Jun 20")
741

742 84
    def test_out_of_bound_day(self):
743 84
        with pytest.raises(ParserError):
744 84
            parse("Feb 30, 2007")
745

746 84
    def test_illegal_month_error(self):
747 84
        with pytest.raises(ParserError):
748 84
            parse("0-100")
749

750 84
    def test_day_sanity(self, fuzzy):
751 84
        dstr = "2014-15-25"
752 84
        with pytest.raises(ParserError):
753 84
            parse(dstr, fuzzy=fuzzy)
754

755 84
    def test_minute_sanity(self, fuzzy):
756 84
        dstr = "2014-02-28 22:64"
757 84
        with pytest.raises(ParserError):
758 84
            parse(dstr, fuzzy=fuzzy)
759

760 84
    def test_hour_sanity(self, fuzzy):
761 84
        dstr = "2014-02-28 25:16 PM"
762 84
        with pytest.raises(ParserError):
763 84
            parse(dstr, fuzzy=fuzzy)
764

765 84
    def test_second_sanity(self, fuzzy):
766 84
        dstr = "2014-02-28 22:14:64"
767 84
        with pytest.raises(ParserError):
768 84
            parse(dstr, fuzzy=fuzzy)
769

770

771 84
class TestParseUnimplementedCases(object):
772 84
    @pytest.mark.xfail
773 27
    def test_somewhat_ambiguous_string(self):
774
        # Ref: github issue #487
775
        # The parser is choosing the wrong part for hour
776
        # causing datetime to raise an exception.
777 0
        dtstr = '1237 PM BRST Mon Oct 30 2017'
778 0
        res = parse(dtstr, tzinfo=self.tzinfos)
779 0
        assert res == datetime(2017, 10, 30, 12, 37, tzinfo=self.tzinfos)
780

781 84
    @pytest.mark.xfail
782 27
    def test_YmdH_M_S(self):
783
        # found in nasdaq's ftp data
784 0
        dstr = '1991041310:19:24'
785 0
        expected = datetime(1991, 4, 13, 10, 19, 24)
786 0
        res = parse(dstr)
787 0
        assert res == expected, (res, expected)
788

789 84
    @pytest.mark.xfail
790 27
    def test_first_century(self):
791 0
        dstr = '0031 Nov 03'
792 0
        expected = datetime(31, 11, 3)
793 0
        res = parse(dstr)
794 0
        assert res == expected, res
795

796 84
    @pytest.mark.xfail
797 27
    def test_era_trailing_year_with_dots(self):
798 0
        dstr = 'A.D.2001'
799 0
        res = parse(dstr)
800 0
        assert res.year == 2001, res
801

802 84
    @pytest.mark.xfail
803 27
    def test_ad_nospace(self):
804 0
        expected = datetime(6, 5, 19)
805 0
        for dstr in [' 6AD May 19', ' 06AD May 19',
806
                     ' 006AD May 19', ' 0006AD May 19']:
807 0
            res = parse(dstr)
808 0
            assert res == expected, (dstr, res)
809

810 84
    @pytest.mark.xfail
811 27
    def test_four_letter_day(self):
812 0
        dstr = 'Frid Dec 30, 2016'
813 0
        expected = datetime(2016, 12, 30)
814 0
        res = parse(dstr)
815 0
        assert res == expected
816

817 84
    @pytest.mark.xfail
818 27
    def test_non_date_number(self):
819 0
        dstr = '1,700'
820 0
        with pytest.raises(ParserError):
821 0
            parse(dstr)
822

823 84
    @pytest.mark.xfail
824 27
    def test_on_era(self):
825
        # This could be classified as an "eras" test, but the relevant part
826
        # about this is the ` on `
827 0
        dstr = '2:15 PM on January 2nd 1973 A.D.'
828 0
        expected = datetime(1973, 1, 2, 14, 15)
829 0
        res = parse(dstr)
830 0
        assert res == expected
831

832 84
    @pytest.mark.xfail
833 27
    def test_extraneous_year(self):
834
        # This was found in the wild at insidertrading.org
835 0
        dstr = "2011 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d NOVEMBER 7, 2012"
836 0
        res = parse(dstr, fuzzy_with_tokens=True)
837 0
        expected = datetime(2012, 11, 7)
838 0
        assert res == expected
839

840 84
    @pytest.mark.xfail
841 27
    def test_extraneous_year_tokens(self):
842
        # This was found in the wild at insidertrading.org
843
        # Unlike in the case above, identifying the first "2012" as the year
844
        # would not be a problem, but inferring that the latter 2012 is hhmm
845
        # is a problem.
846 0
        dstr = "2012 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d NOVEMBER 7, 2012"
847 0
        expected = datetime(2012, 11, 7)
848 0
        (res, tokens) = parse(dstr, fuzzy_with_tokens=True)
849 0
        assert res == expected
850 0
        assert tokens == ("2012 MARTIN CHILDREN'S IRREVOCABLE TRUST u/a/d ",)
851

852 84
    @pytest.mark.xfail
853 27
    def test_extraneous_year2(self):
854
        # This was found in the wild at insidertrading.org
855 0
        dstr = ("Berylson Amy Smith 1998 Grantor Retained Annuity Trust "
856
                "u/d/t November 2, 1998 f/b/o Jennifer L Berylson")
857 0
        res = parse(dstr, fuzzy_with_tokens=True)
858 0
        expected = datetime(1998, 11, 2)
859 0
        assert res == expected
860

861 84
    @pytest.mark.xfail
862 27
    def test_extraneous_year3(self):
863
        # This was found in the wild at insidertrading.org
864 0
        dstr = "SMITH R &  WEISS D 94 CHILD TR FBO M W SMITH UDT 12/1/1994"
865 0
        res = parse(dstr, fuzzy_with_tokens=True)
866 0
        expected = datetime(1994, 12, 1)
867 0
        assert res == expected
868

869 84
    @pytest.mark.xfail
870 27
    def test_unambiguous_YYYYMM(self):
871
        # 171206 can be parsed as YYMMDD. However, 201712 cannot be parsed
872
        # as instance of YYMMDD and parser could fallback to YYYYMM format.
873 0
        dstr = "201712"
874 0
        res = parse(dstr)
875 0
        expected = datetime(2017, 12, 1)
876 0
        assert res == expected
877

878

879 84
@pytest.mark.skipif(IS_WIN, reason="Windows does not use TZ var")
880 84
class TestTZVar(object):
881 84
    def test_parse_unambiguous_nonexistent_local(self):
882
        # When dates are specified "EST" even when they should be "EDT" in the
883
        # local time zone, we should still assign the local time zone
884 75
        with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'):
885 75
            dt_exp = datetime(2011, 8, 1, 12, 30, tzinfo=tz.tzlocal())
886 75
            dt = parse('2011-08-01T12:30 EST')
887

888 75
            assert dt.tzname() == 'EDT'
889 75
            assert dt == dt_exp
890

891 84
    def test_tzlocal_in_gmt(self):
892
        # GH #318
893 75
        with TZEnvContext('GMT0BST,M3.5.0,M10.5.0'):
894
            # This is an imaginary datetime in tz.tzlocal() but should still
895
            # parse using the GMT-as-alias-for-UTC rule
896 75
            dt = parse('2004-05-01T12:00 GMT')
897 75
            dt_exp = datetime(2004, 5, 1, 12, tzinfo=tz.UTC)
898

899 75
            assert dt == dt_exp
900

901 84
    def test_tzlocal_parse_fold(self):
902
        # One manifestion of GH #318
903 75
        with TZEnvContext('EST+5EDT,M3.2.0/2,M11.1.0/2'):
904 75
            dt_exp = datetime(2011, 11, 6, 1, 30, tzinfo=tz.tzlocal())
905 75
            dt_exp = tz.enfold(dt_exp, fold=1)
906 75
            dt = parse('2011-11-06T01:30 EST')
907

908
            # Because this is ambiguous, until `tz.tzlocal() is tz.tzlocal()`
909
            # we'll just check the attributes we care about rather than
910
            # dt == dt_exp
911 75
            assert dt.tzname() == dt_exp.tzname()
912 75
            assert dt.replace(tzinfo=None) == dt_exp.replace(tzinfo=None)
913 75
            assert getattr(dt, 'fold') == getattr(dt_exp, 'fold')
914 75
            assert dt.astimezone(tz.UTC) == dt_exp.astimezone(tz.UTC)
915

916