Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix leap year (fixes #107 #109) #110

Merged
merged 5 commits into from Mar 10, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,4 +1,5 @@
import re
import calendar
import collections
import pathlib
import logging
@@ -26,6 +27,7 @@
async_enterable,
setlocale,
HALF_OF_YEAR_IN_SECONDS,
TWO_YEARS_IN_SECONDS,
)

__all__ = (
@@ -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".
@@ -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):
"""
@@ -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():
@@ -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.

@@ -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.

@@ -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,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)
-------------------

@@ -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
@@ -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
@@ -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
@@ -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():