Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions aioftp/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import calendar
import collections
import pathlib
import logging
Expand Down Expand Up @@ -26,6 +27,7 @@
async_enterable,
setlocale,
HALF_OF_YEAR_IN_SECONDS,
TWO_YEARS_IN_SECONDS,
)

__all__ = (
Expand Down Expand Up @@ -354,7 +356,8 @@ def format_date_time(d):
"""
return d.strftime("%Y%m%d%H%M00")

def parse_ls_date(self, s, *, now=None):
@classmethod
def parse_ls_date(cls, s, *, now=None):
"""
Parsing dates from the ls unix utility. For example,
"Nov 18 1958" and "Nov 18 12:29".
Expand All @@ -368,16 +371,29 @@ def parse_ls_date(self, s, *, now=None):
try:
if now is None:
now = datetime.datetime.now()
d = datetime.datetime.strptime(s, "%b %d %H:%M")
d = d.replace(year=now.year)
diff = (now - d).total_seconds()
if diff > HALF_OF_YEAR_IN_SECONDS:
d = d.replace(year=now.year + 1)
elif diff < -HALF_OF_YEAR_IN_SECONDS:
d = d.replace(year=now.year - 1)
if s.startswith('Feb 29'):
# Need to find the nearest previous leap year
prev_leap_year = now.year
while not calendar.isleap(prev_leap_year):
prev_leap_year -= 1
d = datetime.datetime.strptime(
f"{prev_leap_year} {s}", "%Y %b %d %H:%M"
)
# Check if it's next leap year
diff = (now - d).total_seconds()
if diff > TWO_YEARS_IN_SECONDS:
d = d.replace(year=prev_leap_year + 4)
else:
d = datetime.datetime.strptime(s, "%b %d %H:%M")
d = d.replace(year=now.year)
diff = (now - d).total_seconds()
if diff > HALF_OF_YEAR_IN_SECONDS:
d = d.replace(year=now.year + 1)
elif diff < -HALF_OF_YEAR_IN_SECONDS:
d = d.replace(year=now.year - 1)
except ValueError:
d = datetime.datetime.strptime(s, "%b %d %Y")
return self.format_date_time(d)
return cls.format_date_time(d)

def parse_list_line_unix(self, b):
"""
Expand Down
1 change: 1 addition & 0 deletions aioftp/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
DEFAULT_PASSWORD = "anon@"
DEFAULT_ACCOUNT = ""
HALF_OF_YEAR_IN_SECONDS = 15778476
TWO_YEARS_IN_SECONDS = ((365 * 3 + 366) * 24 * 60 * 60) / 2


def _now():
Expand Down
16 changes: 12 additions & 4 deletions aioftp/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@


__all__ = (
"AIOFTPException",
"StatusCodeError",
"PathIsNotAbsolute",
"PathIOError",
"NoAvailablePort",
)


class StatusCodeError(Exception):
class AIOFTPException(Exception):
"""
Base exception class.
"""


class StatusCodeError(AIOFTPException):
"""
Raised for unexpected or "bad" status codes.

Expand Down Expand Up @@ -41,13 +49,13 @@ def __init__(self, expected_codes, received_codes, info):
self.info = info


class PathIsNotAbsolute(Exception):
class PathIsNotAbsolute(AIOFTPException):
"""
Raised when "path" is not absolute.
"""


class PathIOError(Exception):
class PathIOError(AIOFTPException):
"""
Universal exception for any path io errors.

Expand All @@ -67,7 +75,7 @@ def __init__(self, *args, reason=None, **kwargs):
self.reason = reason


class NoAvailablePort(OSError):
class NoAvailablePort(AIOFTPException, OSError):
"""
Raised when there is no available data port
"""
4 changes: 4 additions & 0 deletions history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ x.x.x (xx-xx-xxxx)
- server: remove obsolete `pass` to `pass_` command renaming
Thanks to `Puddly <https://github.com/puddly>`_

- client: fix leap year bug at `parse_ls_date` method
- all: add base exception class
Thanks to `decaz <https://github.com/decaz>`_

0.15.0 (07-01-2019)
-------------------

Expand Down
61 changes: 55 additions & 6 deletions tests/test_simple_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,56 @@ def _c_locale_time(d, format="%b %d %H:%M"):
return d.strftime(format)


def test_parse_list_datetime_not_older_than_6_month_format():
def test_parse_ls_date_of_leap_year():
def date_to_p(d):
return d.strftime("%Y%m%d%H%M00")
p = aioftp.Client.parse_ls_date
# Leap year date to test
d = datetime.datetime(year=2000, month=2, day=29)
current_and_expected_dates = (
# 2016 (leap)
(
datetime.datetime(year=2016, month=2, day=29),
datetime.datetime(year=2016, month=2, day=29)
),
# 2017
(
datetime.datetime(year=2017, month=2, day=28),
datetime.datetime(year=2016, month=2, day=29)
),
(
datetime.datetime(year=2017, month=3, day=1),
datetime.datetime(year=2016, month=2, day=29)
),
# 2018
(
datetime.datetime(year=2018, month=2, day=28),
datetime.datetime(year=2016, month=2, day=29)
),
(
datetime.datetime(year=2018, month=3, day=1),
datetime.datetime(year=2020, month=2, day=29)
),
# 2019
(
datetime.datetime(year=2019, month=2, day=28),
datetime.datetime(year=2020, month=2, day=29)
),
(
datetime.datetime(year=2019, month=3, day=1),
datetime.datetime(year=2020, month=2, day=29)
),
# 2020 (leap)
(
datetime.datetime(year=2020, month=2, day=29),
datetime.datetime(year=2020, month=2, day=29)
),
)
for now, expected in current_and_expected_dates:
assert p(_c_locale_time(d), now=now) == date_to_p(expected)


def test_parse_ls_date_not_older_than_6_month_format():
def date_to_p(d):
return d.strftime("%Y%m%d%H%M00")
p = aioftp.Client.parse_ls_date
Expand All @@ -73,10 +122,10 @@ def date_to_p(d):
deltas = (datetime.timedelta(), dt, -dt)
for now, delta in itertools.product(dates, deltas):
d = now + delta
assert p(aioftp.Client, _c_locale_time(d), now=now) == date_to_p(d)
assert p(_c_locale_time(d), now=now) == date_to_p(d)


def test_parse_list_datetime_older_than_6_month_format():
def test_parse_ls_date_older_than_6_month_format():
def date_to_p(d):
return d.strftime("%Y%m%d%H%M00")
p = aioftp.Client.parse_ls_date
Expand All @@ -92,10 +141,10 @@ def date_to_p(d):
expect = date_to_p(d.replace(year=d.year - 1))
else:
expect = date_to_p(d.replace(year=d.year + 1))
assert p(aioftp.Client, _c_locale_time(d), now=now) == expect
assert p(_c_locale_time(d), now=now) == expect


def test_parse_list_datetime_short():
def test_parse_ls_date_short():
def date_to_p(d):
return d.strftime("%Y%m%d%H%M00")
p = aioftp.Client.parse_ls_date
Expand All @@ -105,7 +154,7 @@ def date_to_p(d):
)
for d in dates:
s = _c_locale_time(d, format="%b %d %Y")
assert p(aioftp.Client, s) == date_to_p(d)
assert p(s) == date_to_p(d)


def test_parse_list_line_unix():
Expand Down